Skip to content

Fix MCP OAuth callback URL leaking instance name#868

Merged
threepointone merged 1 commit into
mainfrom
fix-basepath-mcp-callbacks
Feb 8, 2026
Merged

Fix MCP OAuth callback URL leaking instance name#868
threepointone merged 1 commit into
mainfrom
fix-basepath-mcp-callbacks

Conversation

@threepointone

@threepointone threepointone commented Feb 8, 2026

Copy link
Copy Markdown
Contributor

Closes #854

Problem

When an agent uses sendIdentityOnConnect: false to protect sensitive instance names (session IDs, user IDs), the MCP OAuth callback URL still exposes the raw instance name:

https://example.com/agents/secure-agent/secret-user-id-123/callback

This URL is sent to the remote OAuth provider as redirect_uri, displayed in the browser address bar, and saved in browser history — completely undermining the security intent. Additionally, the hardcoded /agents/{class}/{name}/callback pattern doesn't work for developers using basePath routing with getAgentByName.

Design decisions

callbackPath is required when sendIdentityOnConnect: false.
Rather than silently hashing or obfuscating the instance name (which would require complex coordination between the routing layer and DO storage), we make the developer explicitly provide a safe callback path. This is consistent with how basePath already works — if you've opted into custom routing to protect instance names, you handle the routing yourself via getAgentByName. The error message is actionable and tells the developer exactly what to do.

callbackPath is optional when sendIdentityOnConnect: true.
The default auto-constructed URL (/agents/{class}/{name}/callback) still works for developers who haven't opted out of identity exposure. This keeps the simple case simple. Developers can still provide callbackPath if they prefer cleaner URLs or a different routing pattern.

Callback detection uses state parameter with origin + pathname verification.
Previously, isCallbackRequest() checked url.includes("/callback") as a loose heuristic — this would break with custom callback paths (e.g., /mcp-oauth-return). The state query parameter (format: {nonce}.{serverId}) is now the primary mechanism for identifying which server a callback belongs to. As defense-in-depth, we also verify that the request's origin and pathname match the registered callback_url for that server, preventing unrelated GET requests with a state param from being intercepted.

Changes

  • AddMcpServerOptions type — new callbackPath?: string field with JSDoc documenting it should be a plain path (no query strings or fragments)
  • addMcpServer() — resolves callbackPath, enforces it when sendIdentityOnConnect: false, uses it for URL construction when provided. Also normalizes trailing slashes on callbackHost to prevent double-slash URLs.
  • isCallbackRequest() — replaced loose url.includes("/callback") substring check with state-based server ID matching plus origin + pathname verification against the stored callback_url
  • Tests — new unit tests for custom callback path detection, pathname-based matching, and origin defense-in-depth in client-manager.test.ts. New integration tests in add-mcp-server.test.ts verifying the callbackPath enforcement (throws when sendIdentityOnConnect: false without callbackPath, does not throw when callbackPath is provided).

Notes for reviewers

  • The isCallbackRequest change is the subtlest part. The old /callback substring check is replaced by a two-layer approach: (1) extract serverId from the state param and confirm it exists in storage, (2) verify the request's origin + pathname match the stored callback_url for that server. This is both more precise and more permissive than before — it works with any callback path while preventing false-positive interception.

  • The runtime enforcement (throw when sendIdentityOnConnect: false without callbackPath) can't be done at the type level since sendIdentityOnConnect is on static options and callbackPath is on the addMcpServer call — TypeScript can't connect those two. The runtime error is clear and fails fast.

@changeset-bot

changeset-bot Bot commented Feb 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 47633ba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agents Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@claude

This comment was marked as resolved.

agents-git-bot Bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Feb 8, 2026
Add documentation for the new callbackPath parameter in addMcpServer() that allows custom OAuth callback URL paths. This is required when sendIdentityOnConnect is false to prevent instance name leakage.

Changes:
- Add callbackPath parameter to addMcpServer() API reference
- Add security section explaining when and why callbackPath is required
- Include example showing how to route custom callback paths with getAgentByName
- Note that callback matching uses state parameter, not URL path

Relates to cloudflare/agents#868

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Feb 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@868

commit: 47633ba

Comment thread packages/agents/src/index.ts
@threepointone
threepointone requested review from mattzcarey and removed request for mattzcarey February 8, 2026 16:26
elithrar added a commit to cloudflare/cloudflare-docs that referenced this pull request Feb 8, 2026
* Document callbackPath option for MCP OAuth callback security

Add documentation for the new callbackPath parameter in addMcpServer() that allows custom OAuth callback URL paths. This is required when sendIdentityOnConnect is false to prevent instance name leakage.

Changes:
- Add callbackPath parameter to addMcpServer() API reference
- Add security section explaining when and why callbackPath is required
- Include example showing how to route custom callback paths with getAgentByName
- Note that callback matching uses state parameter, not URL path

Relates to cloudflare/agents#868

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix export and await in code example

Co-authored-by: elithrar <elithrar@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: elithrar <elithrar@users.noreply.github.com>
@threepointone
threepointone force-pushed the fix-basepath-mcp-callbacks branch from edd9811 to 9f1a3a8 Compare February 8, 2026 23:01
Add `callbackPath` option to `addMcpServer` to prevent instance name
leakage in MCP OAuth callback URLs. When `sendIdentityOnConnect` is
`false`, `callbackPath` is now required — the default callback URL would
expose the instance name, undermining the security intent.

Replace the loose `url.includes("/callback")` check in
`isCallbackRequest` with origin + pathname matching against the stored
callback URL, enabling custom callback paths while preventing unrelated
GET requests from being intercepted.

Co-authored-by: Cursor <cursoragent@cursor.com>
agents-git-bot Bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Feb 8, 2026
Update MCP client API documentation to reflect new security requirements
from agents PR #868:

- Clarify that callbackPath is required when sendIdentityOnConnect is false
- Document that addMcpServer() throws an error without callbackPath
- Explain the enhanced callback matching logic (state param + origin/pathname verification)
- Add details about callbackPath format requirements (plain path, no query strings)

Related to: cloudflare/agents#868

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@threepointone
threepointone force-pushed the fix-basepath-mcp-callbacks branch from 9f1a3a8 to 47633ba Compare February 8, 2026 23:06
agents-git-bot Bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Feb 8, 2026
Update MCP client API documentation to reflect the new security requirement
that callbackPath must be provided when sendIdentityOnConnect is false.

Changes:
- Emphasize that callbackPath is REQUIRED (not optional) when sendIdentityOnConnect is false
- Add error behavior information (throws if callbackPath is missing)
- Update callback matching explanation (state parameter + origin/pathname verification)
- Add example callback URL format for custom callbackPath
- Clarify security implications (OAuth provider, browser history, address bar)

Related to cloudflare/agents#868

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

@mattzcarey mattzcarey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@threepointone
threepointone merged commit b3e2dc1 into main Feb 8, 2026
6 checks passed
@threepointone
threepointone deleted the fix-basepath-mcp-callbacks branch February 8, 2026 23:43
@github-actions github-actions Bot mentioned this pull request Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP OAuth callback URL leaks instance name when sendIdentityOnConnect is false

2 participants