Repository navigation
feat(tui,mcpdo): decode and show access-token claims - #2891
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
String-valued scp claims are not split into individual scopes, causing incorrect JSON scope lists and inconsistent text/TUI output compared with equivalent array-valued claims.
1 open issue
What changed in this PR
Adds shared access-token inspection to the TUI and mcpdo, helping diagnose OAuth failures without an external decoder. JWT claims are displayed without signature verification; opaque tokens use token-response metadata.
Changes:
- Adds shared claim decoding, control-character stripping, and relative-time formatting.
- Adds TUI Access Token Details and
mcpdo auth/show, reusing key resolution fromauth/clear. - Adds tests and documents both inspection surfaces.
| File | Description |
|---|---|
skills/mcpdo/SKILL.md |
Documents auth/show for OAuth troubleshooting. |
core/auth/accessTokenClaims.ts |
Adds shared token summaries and display formatting. |
clients/web/src/test/core/auth/accessTokenClaims.test.ts |
Tests claim parsing, sanitization, and time formatting. |
clients/tui/src/components/AuthTab.tsx |
Displays access-token details on the Auth tab. |
clients/tui/README.md |
Documents token inspection in the TUI. |
clients/tui/__tests__/AuthTab.test.tsx |
Tests JWT and opaque-token rendering. |
clients/mcpdo/src/connection/stored-auth.ts |
Reads stored tokens into display summaries. |
clients/mcpdo/src/connection/mcp.ts |
Registers auth/show and shares auth-key resolution. |
clients/mcpdo/src/connection/format-human.ts |
Formats human-readable token details. |
clients/mcpdo/src/connection/format-connection.ts |
Routes auth/show text and JSON output. |
clients/mcpdo/README.md |
Documents command usage and supported key forms. |
clients/mcpdo/__tests__/connection-stored-auth.test.ts |
Tests stored-token output and raw-token omission. |
clients/mcpdo/__tests__/auth-names-commands.test.ts |
Tests friendly-name resolution and stdio errors. |
Review effort: Balanced
dad2195 to
4ab6019
Compare
|
Copilot round 1: 1 inline finding (a string-valued |
There was a problem hiding this comment.
🟡 Changes recommended
Time-sensitive assertions use fixture timestamps captured before execution while rendering reads the real clock, so delays can cause correct behavior to fail unless Date.now() is frozen and restored.
1 open issue
1 resolved since last review
Review effort: Balanced
|
Copilot round 2: 1 inline finding (relative-time assertions could flip under test delay). Fixed in 0a560d9 with day-scale fixture offsets rather than a frozen clock (reason in the thread). Nothing suppressed. |
|
Copilot review loop closed: round 3 was clean (no inline comments, no suppressed findings, approval recommended). Three rounds in total, with one in-scope finding fixed in each of rounds 1 and 2. |
Add core/auth/accessTokenClaims.ts: an unverified display summary of an access token — JWT claims (iss, aud, sub, client_id/azp, scope/scp, iat, exp with expiry relative to now), or the token response's scope and expires_in for an opaque token. Control characters are stripped from claim strings before they reach a terminal. - TUI Auth tab: new Access Token Details section under OAuth Details. - mcpdo: new auth/show <key> command (same key forms as auth/clear, whose key resolution is factored out and shared). Prints the summary, never the token, in text or --format json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
0a560d9 to
d2430c9
Compare
|
Rebased onto |

Closes #2845
What
Most OAuth failures against an MCP server — wrong audience, missing scope, expired token — are visible in the access token itself, but the TUI Auth tab and mcpdo showed it only as an opaque prefix. This decodes it, without verifying the signature (a viewer, not a validator).
core/auth/accessTokenClaims.ts(new, pure):summarizeAccessTokenreads a JWT'siss,aud(string or list),sub,client_id(falling back toazp),scope(falling back toscp),iatandexp. For an opaque token it falls back to thescope/expires_inthe token response carried.accessTokenDetailLinesrenders labelled rows with times relative to now ((in 34m),(expired 2m ago)), shared by both surfaces so they word things the same way. Claim strings have control characters stripped, because the authorization server mints them and they go to a terminal. A JWT-shaped token whose payload isn't a JSON object is treated as opaque.auth/show <key>command. It takes the same key forms asauth/clear(store URL, exact store key, EMA issuer URL, or catalog/connection name). That key resolution is moved out ofauth/clearinto a sharedresolveAuthKeyArgument, andauth/clearbehaves as before. It prints the summary in text or--format jsonand never prints the token.clients/tui/README.md,clients/mcpdo/README.md,skills/mcpdo/SKILL.md.Screenshots
These are frames of the real
AuthTabcomponent rendered by the TUI test harness withFORCE_COLORand converted to an image. They are not from a live OAuth session; the token state is seeded.JWT access token:
Opaque access token (shows
expires_in/scopefrom the token response):Tests
clients/web/src/test/core/auth/accessTokenClaims.test.ts: 100% on all four dimensions for the new core module.clients/tui/__tests__/AuthTab.test.tsx: JWT and opaque rendering.clients/mcpdo/__tests__/connection-stored-auth.test.ts:auth/showJSON and text against a real store fixture, a token-less entry, a blank key, and a check that the token never appears in the output.clients/mcpdo/__tests__/auth-names-commands.test.ts:auth/showby friendly name, and the stdio-name error.npm run local:gatepasses.🤖 Generated with Claude Code