Skip to content
This repository was archived by the owner on Jul 8, 2026. It is now read-only.

sc-308975: fix MCP transport error during Codex startup - #162

Merged
mdthorpe-sc merged 2 commits into
mainfrom
kurt/sc-308975/fix-mcp-transport-error-during-codex
Mar 2, 2026
Merged

mdthorpe-sc merged 2 commits into
mainfrom
kurt/sc-308975/fix-mcp-transport-error-during-codex

Conversation

@kschrader

@kschrader kschrader commented Mar 2, 2026

Copy link
Copy Markdown
Member

Summary

  • verify uncached OAuth bearer tokens using Authorization header in MCP auth provider
  • keep legacy Shortcut-Token verification as fallback
  • add regression tests for bearer path, fallback path, and invalid-token behavior

Problem

Codex startup against the hosted Shortcut MCP server can fail with a transport handshake error when bearer-token verification relies only on in-memory issued-token cache across restarts / multi-instance routing.

Verification

  • bun run lint
  • bun run ts
  • bun test src/auth/provider.test.ts
  • bun test (pre-push hook)

Summary by CodeRabbit

  • New Features

    • Added OAuth bearer verification with automatic fallback to legacy token verification; token responses are normalized and mapped to clearer error types.
    • Session establishment now uses a session-bound token, requiring and validating it during initialization for stronger session binding.
  • Tests

    • Added comprehensive tests covering bearer and legacy verification paths, token exchange/refresh edge cases, and session continuity for repeated requests.

@coderabbitai

coderabbitai Bot commented Mar 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds OAuth bearer-token verification to the auth provider with a fallback to legacy Shortcut API token verification, updates token exchange/refresh normalization and error mapping, introduces comprehensive tests for verifier and token flows, and threads a sessionToken into session/transport creation in the HTTP server flow.

Changes

Cohort / File(s) Summary
Auth provider tests
src/auth/provider.test.ts
New comprehensive tests covering bearer verification, legacy Shortcut-Token fallback, InvalidTokenError scenarios, token exchange error mapping, refresh normalization; introduces MockShortcutClient and header tracking.
OAuth behavior tests
src/auth/oauth.test.ts
Extends MCP test to capture and reuse Mcp-Session-Id and validate session continuity with subsequent requests using the same bearer token.
Provider implementation
src/auth/provider.ts
Adds OAuth verification client factory, dual-path token verification (primary OAuth bearer, fallback legacy Shortcut API), token normalization (default token_type and expires_in), and upstream OAuth error mapping to specific exceptions.
Server session/token flow
src/server-http.ts
Updates session lifecycle and transport creation signatures to accept and propagate sessionToken (bearer token); sessionManager.add and createTransport signatures updated and initialization path now requires/validates sessionToken.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Provider as OAuth Provider
    participant AuthServer as Auth Server
    participant ShortcutAPI as Shortcut API
    participant ErrorHandler

    Client->>Provider: verifyAccessToken(token)
    rect rgba(100,200,150,0.5)
    Note over Provider,AuthServer: Primary OAuth Path (Bearer)
    Provider->>AuthServer: Validate bearer token / get member
    alt OAuth Success
        AuthServer-->>Provider: Member data
        Provider-->>Client: AuthInfo (memberId, mentionName)
    else OAuth Fails
        AuthServer-->>Provider: OAuth error
        end
    end

    rect rgba(200,150,100,0.5)
    Note over Provider,ShortcutAPI: Fallback Legacy Path (Shortcut-Token)
    Provider->>ShortcutAPI: getCurrentMemberInfo(legacy token)
    alt Legacy Success
        ShortcutAPI-->>Provider: Member data
        Provider-->>Client: AuthInfo
    else Legacy Fails
        ShortcutAPI-->>Provider: Error
        Provider->>ErrorHandler: Throw InvalidTokenError / mapped error
        ErrorHandler-->>Client: Error
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mdthorpe-sc
  • opoku

Poem

🐰 I hopped through headers, quick and spry,
Bearer first, then legacy I try—
Tokens tidy, errors mapped just so,
Sessions bound where transports go.
A carrot-cheered deploy—let validations fly!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: fixing an MCP transport error during Codex startup by implementing OAuth bearer token verification. It is specific, concise, and directly reflects the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kurt/sc-308975/fix-mcp-transport-error-during-codex

Comment @coderabbitai help to get the list of available commands and usage tips.

@kschrader
kschrader requested a review from mdthorpe-sc March 2, 2026 17:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/auth/provider.test.ts (1)

94-131: Consider asserting header shape per call in fallback/failure tests.

You already assert call count; adding per-call header assertions would make regressions more diagnosable.

Optional test hardening
 		expect(authInfo.extra).toEqual({
 			memberId: "member-2",
 			mentionName: "legacy-user",
 		});
 		expect(mockState.calls.length).toBe(2);
+		expect(getHeader(mockState.calls[0], "Authorization")).toBe("Bearer legacy-token");
+		expect(getHeader(mockState.calls[0], "Shortcut-Token")).toBeUndefined();
+		expect(getHeader(mockState.calls[1], "Shortcut-Token")).toBe("legacy-token");
 	});
@@
 		await expect(provider.verifyAccessToken("invalid-token")).rejects.toBeInstanceOf(
 			InvalidTokenError,
 		);
 		expect(mockState.calls.length).toBe(2);
+		expect(getHeader(mockState.calls[0], "Authorization")).toBe("Bearer invalid-token");
+		expect(getHeader(mockState.calls[1], "Shortcut-Token")).toBe("invalid-token");
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/auth/provider.test.ts` around lines 94 - 131, The tests should assert the
exact headers sent on each mock call to make failures clearer: update the
fallback test (using mockState.handler/getHeader and
createOAuthProvider().verifyAccessToken) to assert that mockState.calls[0]
contains an Authorization header with value "Bearer legacy-token" and
mockState.calls[1] contains a Shortcut-Token header with value "legacy-token";
likewise update the failure test to assert mockState.calls[0] had Authorization
"Bearer invalid-token" and mockState.calls[1] had Shortcut-Token "invalid-token"
(use the same mockState.calls array and getHeader helper to inspect each call)
so per-call header shape is verified.
src/auth/provider.ts (1)

152-160: Use config.headers instead of mutating the axios instance to remove the Shortcut-Token header.

The current approach accesses private axios internals via type-casting, which is brittle. Since @shortcut/client accepts a config.headers parameter that overrides the default headers, pass "Shortcut-Token": undefined (or use an alternative initialization pattern) to prevent the header from being set, rather than deleting it from the instance after construction.

Current code (lines 152–160)
	// Remove Shortcut-Token header so Shortcut API only sees Bearer auth.
	// biome-ignore lint/suspicious/noExplicitAny: accessing axios internals
	const instance = (client as any).instance;
	if (instance?.defaults?.headers) {
		delete instance.defaults.headers["Shortcut-Token"];
		if (instance.defaults.headers.common) {
			delete instance.defaults.headers.common["Shortcut-Token"];
		}
	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/auth/provider.ts` around lines 152 - 160, The code mutates axios
internals on the created client to remove the "Shortcut-Token" header; instead
remove this block and initialize or reconstruct the Shortcut client using its
config.headers override so the header is never set (e.g., when calling the
`@shortcut/client` constructor or factory that returns `client`, pass a
config.headers object with "Shortcut-Token": undefined or simply omit that key).
Locate the block referencing `client`/`instance` and `delete
instance.defaults.headers["Shortcut-Token"]` and replace the approach by
supplying `config.headers` on client creation (or re-create the client with
config.headers) rather than deleting headers from the axios `instance`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/auth/provider.ts`:
- Around line 91-114: The current bare catch around the OAuth verification (the
try that calls createOAuthVerificationClient and
oauthClient.getCurrentMemberInfo) swallows non-auth failures; change it to only
fall back for explicit auth-denial errors (e.g., InvalidTokenError or HTTP
401/403 responses) and rethrow or propagate other errors (network, 5xx,
timeouts). Inspect the thrown error from oauthClient.getCurrentMemberInfo
(status/code/message) and only continue to legacy verification when it
represents an unauthorized/forbidden token; otherwise rethrow the error so
upstream/transport failures are not misclassified; apply the same tightened
catch logic to the second OAuth try/catch block handling lines ~136-141
referencing the same functions.

---

Nitpick comments:
In `@src/auth/provider.test.ts`:
- Around line 94-131: The tests should assert the exact headers sent on each
mock call to make failures clearer: update the fallback test (using
mockState.handler/getHeader and createOAuthProvider().verifyAccessToken) to
assert that mockState.calls[0] contains an Authorization header with value
"Bearer legacy-token" and mockState.calls[1] contains a Shortcut-Token header
with value "legacy-token"; likewise update the failure test to assert
mockState.calls[0] had Authorization "Bearer invalid-token" and
mockState.calls[1] had Shortcut-Token "invalid-token" (use the same
mockState.calls array and getHeader helper to inspect each call) so per-call
header shape is verified.

In `@src/auth/provider.ts`:
- Around line 152-160: The code mutates axios internals on the created client to
remove the "Shortcut-Token" header; instead remove this block and initialize or
reconstruct the Shortcut client using its config.headers override so the header
is never set (e.g., when calling the `@shortcut/client` constructor or factory
that returns `client`, pass a config.headers object with "Shortcut-Token":
undefined or simply omit that key). Locate the block referencing
`client`/`instance` and `delete instance.defaults.headers["Shortcut-Token"]` and
replace the approach by supplying `config.headers` on client creation (or
re-create the client with config.headers) rather than deleting headers from the
axios `instance`.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc28650 and 997f2bf.

📒 Files selected for processing (2)
  • src/auth/provider.test.ts
  • src/auth/provider.ts

Comment thread src/auth/provider.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/auth/provider.ts (1)

157-159: ⚠️ Potential issue | 🟠 Major

Only fallback on auth-denial failures in bearer verification.

Line 157 catches all errors and always falls back to legacy verification. This can misclassify upstream/transport failures as invalid credentials. Restrict fallback to explicit auth failures and rethrow everything else.

Suggested tightening
-	} catch {
-		// Fall through to legacy token verification below.
+	} catch (error) {
+		const status = (error as { response?: { status?: number } }).response?.status;
+		const isAuthFailure =
+			error instanceof InvalidTokenError || status === 401 || status === 403;
+		if (!isAuthFailure) {
+			throw error;
+		}
+		// Fall through to legacy token verification below.
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/auth/provider.ts` around lines 157 - 159, The current bare catch after
the bearer-token verification swallows all errors and always falls back to
legacy verification; modify the try/catch around the bearer verification (the
block that currently ends with "// Fall through to legacy token verification
below.") to only swallow explicit authentication-denial errors (e.g., an
AuthError/UnauthorizedError or errors with status 401/invalid-credentials
markers) and rethrow any other exceptions (network/transport/internal errors) so
they are not misclassified; keep the fallback to legacy verification only for
genuine auth-denial conditions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/auth/provider.ts`:
- Around line 157-159: The current bare catch after the bearer-token
verification swallows all errors and always falls back to legacy verification;
modify the try/catch around the bearer verification (the block that currently
ends with "// Fall through to legacy token verification below.") to only swallow
explicit authentication-denial errors (e.g., an AuthError/UnauthorizedError or
errors with status 401/invalid-credentials markers) and rethrow any other
exceptions (network/transport/internal errors) so they are not misclassified;
keep the fallback to legacy verification only for genuine auth-denial
conditions.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 997f2bf and 83403ed.

📒 Files selected for processing (4)
  • src/auth/oauth.test.ts
  • src/auth/provider.test.ts
  • src/auth/provider.ts
  • src/server-http.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/auth/provider.test.ts

@mdthorpe-sc
mdthorpe-sc merged commit 81d51a7 into main Mar 2, 2026
2 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants