Repository navigation
Refuse sign-ins for another workspace's MCP server (#1609) - #1613
Merged
Merged
Conversation
A client that names one MCP server by URL no longer gets a token for an account outside that server's workspace: the sign-in form comes back with a message to use an account of that workspace. A sign-in token that still reaches a server of another workspace gets 401 with invalid_token instead of 403, so the client signs in again. API keys keep the 403.
helpcodeai
enabled auto-merge (squash)
November 9, 2026 17:06
6 of 7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A person who signs in with the account of their other workspace, while their AI client points at a server URL (
/mcp/<serverId>) of a workspace that account does not belong to, used to get a token that the server refused on every call with a 403. Clients do not sign in again after a 403, so the person could not fix it from the chat. On Cloud one user was refused 510 times in 13 hours.Now the consent step refuses such a sign-in, and the endpoint answers a stray sign-in token with a 401 that makes the client sign in again.
Changes
auth/login.controller.ts:completeSignIn(password, dashboard-session Approve, Google/Microsoft/GitHub, email code) checks the server the client asked for (RFC 8707resource, the signedmcp_resourcecookie). If the server exists and the user is neither in its workspace as primary organization nor an active member, nologin_usercookie and no grant: redirect to/auth/login?switch=1with a message to sign in with an account of that workspace. The server name is not shown. Same membership rule as the endpoint. Unknown server ids and lookup errors keep the existing flow (the endpoint still checks).mcp-server/mcp-endpoint.controller.ts: for a non-member withauthMethod: 'jwt'(OAuth access tokens and app JWTs), 401 withWWW-Authenticate: Bearer realm="AnythingMCP MCP Server", error="invalid_token", resource_metadata="<base>/.well-known/oauth-protected-resource/mcp/<serverId>". API keys keep the 403 and its message. The warn log line is unchanged.auth/mcp-www-authenticate.util.ts: the header value, moved out ofMcpCombinedAuthGuard.deny()so the guard and the endpoint build it the same way (no behaviour change in the guard).auth/resource-indicator.middleware.ts: comment updated: the requested resource may now also refuse an authorization; it can only narrow, never widen.Not changed: the SSO (identity provider) MCP path in
sso.controller.ts, which picks the provider from the requested server's workspace already; the endpoint 401 covers it anyway.Cloud and self-hosted behave the same. It only matters where an instance has more than one workspace. No Prisma change, nothing under
ee/.Type
Testing
New
auth/login.controller.wrong-workspace.spec.ts: refused for a non-member (nologin_user, no grant, redirect with the message); allowed for a member whose primary workspace is another one; allowed for the primary workspace without a membership query; no server lookup when the client named no server; unknown server id left to the flow.mcp-server/mcp-endpoint.controller.spec.ts: non-member JWT gets 401 with the exact header and the warn line; an API key of another workspace still gets 403 without the header; the listen and "organization unknown" cases now expect 401.Commands, in
packages/backend(afternpm ciandnpx prisma generate):npx jest --maxWorkers=4 src/auth src/mcp-server src/mcp-servers: 81 suites passed, 1,241 tests passed, 5 skipped. 1 suite failed:src/auth/jwt-or-management-key.guard.spec.ts(13 tests) needs a live Postgres (prisma.organization.createinbeforeAll), none was running locally; unrelated to this change, CI runs it with a database.npx tsc --noEmit -p tsconfig.json: exit 0.npx eslinton the 7 touched files: exit 0.Checklist
npm test)No user documentation describes this refusal, so none changed.
Related Issues
Fixes #1609