Skip to main content

MCP Connect Auth for MCP servers

MCP Connect Auth lets you manage an MCP server listing on MCPBundles and offer standards-compliant MCP client OAuth without building your own authorization server. MCPBundles provisions a tenant authorization server per listing slug; your upstream JSON-RPC stays unchanged. Public directory publish to MCPBundles Skills is optional — you can connect clients via your vendor origin (Path A) or the MCPBundles bundle URL (Path B) without listing on /skills.

Overview

RoleWho serves itURL pattern
Authorization server (AS)MCPBundles (per listing)Tenant AS under Connect Auth API
Resource server (RS)Vendor origin or MCPBundles bundle proxyYour MCP base URL or https://mcp.mcpbundles.com/bundle/{slug}/

Two connect paths share one tenant AS:

PathVendor MCP codeClient connects toBest for
Path B (pilot default)Federation only — no pip/npm on MCP serverhttps://mcp.mcpbundles.com/bundle/{slug}/Fastest setup; full proxy telemetry
Path Amcpbundles-mcp-connect / @mcpbundles/mcp-connect-auth on originYour vendor MCP URLCustom hosting; origin client telemetry

Traffic diagram (Path B):

sequenceDiagram
participant Client as MCP client
participant Bundle as mcp.mcpbundles.com/bundle
participant AS as Connect Auth tenant AS
participant App as Your app (federation)

Client->>Bundle: MCP initialize / tools
Client->>AS: OAuth DCR + PKCE
Client->>AS: Authorize (resource = bundle URL)
AS->>App: Federation sign-in
App->>AS: federation/complete
AS->>Client: JWT access token
Client->>Bundle: Bearer JWT

Quickstart (~30 min)

Prerequisites

  1. MCP server listing on MCPBundles (MCP protocol, OAuth-capable upstream)
  2. MCP Connect Auth enabled in maintainer settings (at create time or on an existing listing; public /skills publish is optional)
  3. Federation sign-in URL configured in maintainer Connect Auth settings
  4. Federation secret in your app environment (server-side only)

Path B — bundle URL only

  1. Enable MCP Connect Auth in the Connect Auth section of your listing maintainer dashboard (open dashboard)
  2. Configure federation URL + secret there
  3. Point MCP clients at https://mcp.mcpbundles.com/bundle/{slug}/
  4. Smoke locally:
mcpbundles connect doctor --listing {slug} --env staging --surface bundle

No pip install on your MCP server.

Path A — vendor origin

from mcpbundles_mcp_connect import mcpbundles_fastmcp

mcp = mcpbundles_fastmcp(
"My Server",
listing_slug="{slug}",
base_url="https://mcp.example.com",
)

Or explicitly:

from fastmcp import FastMCP
from mcpbundles_mcp_connect.fastmcp import McpbundlesConnectProvider

provider = McpbundlesConnectProvider(listing_slug="{slug}", base_url="https://mcp.example.com")
mcp = FastMCP("My Server", auth=provider, middleware=provider.default_middleware())

Run doctor with origin surface:

mcpbundles connect doctor --listing {slug} --surface origin --base-url https://mcp.example.com

Protected resource metadata (RFC 9728)

  • Bundle URL: MCPBundles serves protected-resource metadata on mcp.mcpbundles.com for /bundle/{slug}/
  • Origin: Your server (via Connect Auth client library) serves metadata pointing at the tenant AS
  • Clients discover the tenant AS from authorization_servers in protected-resource JSON

Alternate well-known paths are supported on the bundle host (see platform routers.py patterns).

Federation

When an MCP client starts OAuth against the tenant AS, MCPBundles redirects the user to your sign-in URL. After authentication, your backend calls:

POST /connect-auth/tenants/{slug}/v1/federation/complete

with the federation secret, state, and subject (and optional organization_id, email, roles).

The federation secret is shown once on create/rotate — store it in your app secrets, never in client code.

Use the client libraries when possible:

  • Python: complete_federation() from mcpbundles-mcp-connect
  • Node: completeFederation() from @mcpbundles/mcp-connect-auth

What vendors do not implement

MCPBundles operates the tenant authorization server. You do not need to run DCR, authorize, or token endpoints on your origin for Path B. For Path A, the Connect Auth provider forwards AS metadata to the tenant AS — you still do not operate DCR yourself.

Access tokens

Connect Auth access tokens are ES256 JWTs issued by the tenant AS. These are MCP client OAuth tokens, not MCPBundles hub opaque tokens and not your vendor API keys on the MCP wire.

ClaimPurpose
subStable subject from your federation complete call
client_idDCR-registered MCP host (Cursor, Claude, …) — distinct from the human sub
scopeSpace-separated OAuth scopes granted to the MCP client
audAllowed MCP resource URL(s) — origin and/or bundle proxy
issTenant authorization server issuer
exp / iatShort-lived access token TTL
organization_idOptional tenant/org context from federation
emailOptional primary email when your complete call includes it
rolesOptional role names your app passes at complete (see below)

Refresh and revoke flows are tenant-scoped. On Path A, your MCP server validates the JWT and reads identity from sub, optional organization_id, email, and roles, plus OAuth metadata from client_id and scope. See Tool callback identity for framework mapping.

What roles are (and are not)

roles are pass-through hints from your federation complete call — whatever your app sends after sign-in. Connect Auth does not maintain a platform RBAC store, does not assign roles, and does not enforce them on tool calls.

For sensitive tools, enforce authorization in your MCP server (or call back to your app API using sub) rather than trusting JWT roles alone. Treat roles like convenience claims for logging, UI, or coarse checks; treat sub + your database as the source of truth for permissions.

Tool callback identity

Connect Auth access tokens carry both who signed in (federation subject) and which MCP client is calling (DCR client). Framework providers map those into different callback surfaces — the same split WorkOS AuthKit uses in mcp-use and FastMCP.

Required on every valid tool call

FieldJWT claimSemantics
User idsubFederated end-user subject from your federation complete call
Client idclient_idDCR-registered MCP host — not the human
ScopesscopeSpace-separated OAuth scopes
Audienceaudorigin_resource and/or bundle_proxy_resource

Optional

FieldJWT claimSemantics
Organization idorganization_idTenant/org context from federation
EmailemailPrimary email when your complete call includes it
RolesrolesRole names your complete call includes — pass-through only; not platform-enforced RBAC

Rules

  1. client_id never belongs on the user object — use framework OAuth metadata for the MCP client.
  2. Do not treat sub as the OAuth client — Connect Auth always mints client_id; MCPBundles verifiers reject tokens that omit it.
  3. Organization scoping uses organization_id on the identity side, not client_id.
  4. roles are hints, not authorization — Connect Auth mints whatever your app sends at complete; enforce sensitive access in your MCP server or app API using sub as the stable key.

Framework mapping

Intentmcp-useFastMCPNode (@mcpbundles/mcp-connect-auth)
User idctx.auth.user.idtoken.subject or token.claims["sub"]token.subject
Organizationctx.auth.user.organizationIdtoken.claims.get("organization_id")token.organizationId
Emailctx.auth.user.emailtoken.claims.get("email")token.email
Rolesctx.auth.user.rolestoken.claims.get("roles")token.roles
MCP clientctx.auth.clientIdtoken.client_idtoken.clientId
Scopesctx.auth.scopestoken.scopestoken.scopes
Expiryctx.auth.expiresAttoken.expires_attoken.expiresAt
Resourcectx.auth.resourceAudience in token.claims["aud"]token.audience
Raw claimsctx.auth.payloadtoken.claimstoken.rawClaims
Canonical JSONbuild from ctx.authconnect_auth_callback_identity(token)connectAuthCallbackIdentity(token)

Both official examples expose the same get-user-info tool shape:

{
"user": { "id": "…", "organizationId": "…", "email": "…", "roles": ["…"] },
"auth": {
"clientId": "…",
"scopes": ["…"],
"expiresAt": 1234567890,
"resource": "https://…/mcp"
}
}

There is no user.clientId.

Switching FastMCP ↔ mcp-use

You can change MCP server framework without rewriting identity logic:

  1. Keep the same listing slug, public MCP origin URL, and Connect Auth public-config.
  2. Map fields using the table above — do not copy client_id onto your user model in TypeScript.
  3. Use the shared golden JWT fixtures when writing provider tests: product/mcp-connect-auth/fixtures/golden_access_token.json (also vendored in upstream fork test trees).

Golden vectors include a signed ES256 token, public JWKS, and expected user_id, organization_id, client_id, and scopes. Third-party provider authors can copy the fixture into their test suite and assert the same logical identity surface as the official FastMCP and mcp-use providers.

Framework providers:

  • Python / FastMCP: McpbundlesConnectProvider + connect_auth_callback_identity()FastMCP docs or interim mcpbundles-mcp-connect on PyPI
  • TypeScript / mcp-use: oauthMcpbundlesProvidermcp-use provider docs
  • Node middleware: @mcpbundles/mcp-connect-auth + connectAuthCallbackIdentity()

Validate tokens on your MCP server (Path A)

When clients connect to your origin, your server must validate Bearer JWTs and return 401 with WWW-Authenticate and protected-resource Link headers when missing or invalid.

Path B (bundle URL only) skips origin validation — MCPBundles validates JWTs at the bundle proxy.

# Path A — FastMCP with Connect Auth provider
from fastmcp import FastMCP
from mcpbundles_mcp_connect.fastmcp import McpbundlesConnectProvider

provider = McpbundlesConnectProvider(listing_slug="{slug}", base_url="https://mcp.example.com")
mcp = FastMCP("My Server", auth=provider, middleware=provider.default_middleware())

Dynamic client registration (DCR)

MCP clients register against the tenant AS, not your vendor origin:

  • POST …/o/register
  • GET …/o/authorize (PKCE S256)
  • POST …/o/token

CIMD and PKCE policy are platform-owned. If DCR fails for a specific client, check the maintainer Connections panel and run mcpbundles connect doctor — do not add a parallel DCR endpoint on your server.

Connect Readiness and doctor

Maintainer dashboard shows Connect Readiness (green / yellow / red). Locally, use the same probe ladder:

mcpbundles connect doctor --listing {slug} --env staging --surface bundle --json

Probe order: public-config → JWKS → tenant AS metadata → bundle protected-resource → (origin probes if --surface origin).

Origin telemetry vs bundle proxy telemetry

SourceHow data arrivesMaintainer UI
Bundle proxy (Path B)Platform OAuth + proxy initializeConnections panel — zero vendor code
Origin (Path A)McpbundlesHandshakeMiddleware POST to ingest URLConnections panel (mount=connect_origin)

This is not FastMCP OpenTelemetry — OTEL is for your ops traces; Connections is MCP client cohort analytics.

Privacy: We store client name/version, protocol negotiation, and capability summaries from initialize. We do not store tool arguments or arbitrary JSON-RPC payloads.

Testing on staging

  1. Create or use a staging listing with MCP Connect Auth enabled
  2. mcpbundles connect doctor --listing {slug} --env staging --surface bundle
  3. Connect from a certified MCP host using the bundle URL
  4. Confirm Connections and readiness in maintainer UI

Troubleshooting

SymptomLikely causeFix
public-config 404Connect Auth not enabled or wrong slugEnable MCP Connect Auth in maintainer settings
Bundle protected-resource missing tenant ASHosting flag not MCPBUNDLES_CONNECTUpdate listing auth hosting
Origin AS forward mismatchProvider not installed on originPath A: install mcpbundles-mcp-connect
Federation complete failsWrong secret or expired stateRotate secret; retry authorize flow

Full probe output: mcpbundles connect doctor --listing {slug} --json

Operations

ResponsibilityOwner
DCR, PKCE, CIMD, client quirksMCPBundles tenant AS
MCP tools and upstream APIVendor
Federation secret leakVendor — rotate in maintainer UI
Bundle proxy JWT validationMCPBundles
Origin JWT validationVendor (Path A only)

Custom issuer CNAME is not supported in v1. Status and incidents: MCPBundles status.

FAQ

How is this different from API keys on my MCP server?
MCP client OAuth uses DCR + PKCE against a standards-compliant authorization server. API keys are a separate concern — Connect Auth issues JWT access tokens for MCP clients only.

Do I need pip/npm if I use the bundle URL?
No. Path B is federation + bundle URL only — no pip/npm on your MCP server.

Can I use my own OAuth provider for app login?
Yes. Connect Auth federates to your sign-in URL. It replaces building MCP-client OAuth (DCR, protected-resource, tenant AS), not your entire identity stack.

What happens when I revoke a federation secret?
Rotate in the maintainer panel, update your app env, and complete new authorize flows. Active sessions expire with access token TTL.

WorkOS / Scalekit — same thing?
No. Those products target application login. Connect Auth targets MCP client OAuth for MCP server listings you manage on MCPBundles.

Platform publishing

Published listings with Connect Auth can expose:

  • Claude web connector install links
  • ChatGPT custom connector URLs
  • MCP Registry export JSON from listing metadata

Connect Readiness should be green before submitting to external directories. See the publishing guide.