Add Tembo TAS and MCP OAuth Integration - #379
Conversation
Co-authored-by: Ry <4283+ryw@users.noreply.github.com>
| consentReferenceId: async ({ user, session, scopes }) => { | ||
| if (!hasMcpOAuthScope(scopes)) return undefined; | ||
| return ( | ||
| (await getMcpOAuthWorkspaceSelection(session.id, user.id)) ?? |
There was a problem hiding this comment.
A returning user can already have an oauthConsent for this client but no selection in their new session. In that case this returns undefined; the provider's consent lookup omits the referenceId filter, reuses the old consent, and issues a code without a workspace, which customAccessTokenClaims then rejects. Ensure authorization cannot reuse consent until this session has a valid workspace selection.
| validAudiences: [mcpOAuthResource()], | ||
| grantTypes: ["authorization_code", "refresh_token"], | ||
| allowDynamicClientRegistration: true, | ||
| allowUnauthenticatedClientRegistration: true, |
There was a problem hiding this comment.
This exposes dynamic registration to the public internet, while the provider only rate-limits authorize/token/introspect; every registration can insert an oauthClient row. Add registration-specific rate limiting or quotas/cleanup so anonymous traffic cannot grow this table indefinitely.
| {selectedWorkspace ? ( | ||
| <div className="text-foreground-weak space-y-2 text-sm"> | ||
| <p> | ||
| The MCP client will act as you in the workspace you select. |
There was a problem hiding this comment.
This screen doesn't identify the requesting client or show its exact requested scopes. With unauthenticated DCR, a phishing authorization URL looks the same as Claude and can request mcp:write. Surface the registered client name/redirect origin and requested scopes before the user allows access.
There was a problem hiding this comment.
Pull request overview
This PR adds per-user OAuth 2.1 authentication to the TAS MCP server so hosted MCP clients (notably Claude Web) can connect via Dynamic Client Registration + PKCE, select a workspace during consent, and receive scoped tokens that still enforce live workspace RBAC.
Changes:
- Added Better Auth OAuth Authorization Server support (JWT + oauth-provider) with workspace-binding claims and consent UI.
- Updated MCP endpoint/auth boundary to accept either
tas_API keys or OAuth access tokens, enforcingmcp:writefor write tools. - Published OAuth discovery metadata under
/.well-known/*and updated docs/changelogs accordingly.
Reviewed changes
Copilot reviewed 25 out of 27 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| web/src/proxy.ts | Marks /.well-known/* as public so OAuth discovery metadata is reachable without a session. |
| web/src/lib/mcp/server.ts | Enforces mcp:write scope for OAuth-based write/admin MCP tools while keeping API keys working. |
| web/src/lib/mcp/server.test.ts | Adds coverage for rejecting write-tool calls when OAuth lacks mcp:write. |
| web/src/lib/mcp-oauth.ts | Defines MCP OAuth scopes, issuer/resource helpers, and the workspace claim name. |
| web/src/lib/mcp-oauth-selection.ts | Persists and validates the user’s workspace choice for the OAuth consent flow. |
| web/src/lib/mcp-oauth-metadata.ts | Implements OAuth Protected Resource Metadata response for MCP discovery. |
| web/src/lib/mcp-oauth-metadata.test.ts | Tests the advertised resource + issuer metadata values. |
| web/src/lib/docs-content.ts | Regenerates in-app docs bundle to include updated API/MCP content and changelog entry. |
| web/src/lib/auth.ts | Wires Better Auth JWT + oauth-provider (DCR, PKCE, refresh tokens) and binds tokens to a selected workspace. |
| web/src/lib/auth.test.ts | Expands auth wiring tests to cover oauth-provider config and workspace-bound token claims. |
| web/src/lib/auth-client.ts | Adds oauth-provider client plugin so OAuth query params survive login/consent transitions. |
| web/src/lib/api-auth.ts | Adds OAuth-claims authorization path for MCP, binding claims to workspace + live role + scopes. |
| web/src/lib/api-auth.test.ts | Adds tests for OAuth-claims authorization (happy path, missing scope/claim, lost membership). |
| web/src/app/oauth/consent/page.tsx | New server-rendered consent page that lists the user’s workspaces for selection. |
| web/src/app/oauth/consent/consent-card.tsx | New client consent UI that stores workspace selection then completes oauth-provider consent. |
| web/src/app/mcp/route.ts | MCP endpoint now supports OAuth tokens via mcpHandler in addition to tas_ API keys. |
| web/src/app/api/mcp/oauth/select-workspace/route.ts | API route to persist a chosen workspace into the selection table for the current session. |
| web/src/app/.well-known/oauth-protected-resource/route.ts | Exposes protected-resource metadata at the standard well-known path. |
| web/src/app/.well-known/oauth-protected-resource/mcp/route.ts | Exposes protected-resource metadata at the resource-specific well-known subpath. |
| web/src/app/.well-known/oauth-authorization-server/api/auth/route.ts | Serves OAuth Authorization Server metadata for the Better Auth issuer under /.well-known. |
| web/pnpm-lock.yaml | Locks the new @better-auth/oauth-provider dependency. |
| web/package.json | Adds @better-auth/oauth-provider dependency. |
| docs/src/content/docs/mcp.md | Updates MCP docs with Claude Web connector setup and OAuth vs API-key auth model. |
| docs/src/content/docs/changelog.md | Adds an Unreleased changelog entry for per-user MCP OAuth. |
| docs/src/content/docs/api.md | Clarifies REST API uses API keys while MCP also supports OAuth for hosted clients. |
| CHANGELOG.md | Adds an Unreleased changelog entry for per-user MCP OAuth (repo root). |
| api/migrations/0072_mcp_oauth.sql | Adds OAuth provider storage tables plus a TAS-owned workspace-selection binding table. |
Files not reviewed (1)
- web/pnpm-lock.yaml: Generated file
Suppressed comments (1)
web/src/app/mcp/route.ts:67
- With
handleAuthorizedMcpRequestacceptingRequest, the OAuth path no longer needs to castrequesttoNextRequest. Keeping the cast hides type mismatches and can mask runtime differences betweenRequestandNextRequest.
);
}
return handleAuthorizedMcpRequest(request as NextRequest, auth);
},
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| async function handleAuthorizedMcpRequest( | ||
| request: NextRequest, | ||
| auth: AuthorizeApiSuccess, | ||
| ): Promise<Response> { |
Summary
Added per-user OAuth 2.1 support for the TAS MCP server enabling hosted clients like Claude Web to authenticate via OAuth with dynamic client registration, PKCE, and workspace selection. Updated MCP docs to include a new section for connecting Claude Web before Claude Code. Enhanced MCP server to accept OAuth tokens with scope checks and live workspace role enforcement. Added OAuth consent UI for workspace selection. Added OAuth metadata endpoints under /.well-known for MCP clients. Updated API auth to authorize OAuth claims with workspace binding and scopes. Added database migration for OAuth 2.1 authorization server tables and workspace selection binding. Added tests for OAuth MCP claims authorization and MCP server write scope enforcement. Updated changelog and docs to reflect these changes.