Connect an MCP client

TL;DR — Your MCP server is running, tokens issue, everything is wired. Now point a client at it. The MCP flow is: first request → 401 with WWW-Authenticate → fetch PRM → discover the AS → pick a client-identification path (CIMD, DCR, or pre-registered) → run OAuth → present tokens on tools/* calls. This page has the per-client config (Claude Desktop, Cursor, VS Code Copilot, MCP Inspector) plus known quirks like Claude Code omitting scope on /authorize.

Prerequisites

  • Your MCP server is running at https://mcp.example.com/mcp (or local equivalent).
  • Your SDK wired PRM at /.well-known/oauth-protected-resource/mcp (RFC 9728 §3 path-scoped form) — verify with curl -s https://mcp.example.com/.well-known/oauth-protected-resource/mcp | jq.
  • AuthPlane AS is reachable at the URL your PRM’s authorization_servers field points to.
  • Your MCP client can reach both hosts (no CORS blocks, no firewall).

If any of these are broken, no client will connect — start with Debugging checklist before this page.

What every MCP client does

Per MCP 2025-11-25, the client kicks off with an unauthenticated request and lets the resource server point at the PRM. It then picks a client-identification path — MCP mandates client-metadata discovery (CIMD) as the SHOULD, with DCR (RFC 7591) and pre-registered client_ids as the two other MAY paths. All three converge on the same OAuth authorize/token exchange:

0. Client → your MCP server        POST /mcp   (no Authorization header)
                                   ← 401 WWW-Authenticate: Bearer resource_metadata="…"

1. Client → your MCP server        GET  <resource_metadata URL from step 0>
                                        (path-scoped: /.well-known/oauth-protected-resource/mcp)
                                        (falls back to root /.well-known/oauth-protected-resource
                                         only if step 0 didn't carry a URL)
                                   ← PRM {authorization_servers, scopes_supported}

2. Client → AuthPlane              GET /.well-known/oauth-authorization-server
                                   ← AS metadata

3. Client → AuthPlane              *one of*:
                                     · CIMD lookup (SHOULD)
                                     · POST /oauth/register            (DCR, RFC 7591)
                                     · use its pre-registered client_id
                                   ← client_id (+ secret if confidential)

4. Client → AuthPlane              GET /oauth/authorize?response_type=code
                                                        &code_challenge=…
                                                        &code_challenge_method=S256
                                                        &resource=https://mcp.example.com/mcp
                                                        &scope=tools/read
                                   ← redirect through login + consent → code

5. Client → AuthPlane              POST /oauth/token
                                   ← access_token + refresh_token
                                   (token_type=DPoP if client sent a DPoP header)

6. Client → your MCP server        POST /mcp
                                   Authorization: Bearer (or DPoP) <token>
                                   ← tool result

Clients MUST honor the resource_metadata URL from the WWW-Authenticate challenge when present (MCP 2025-11-25) — the well-known lookup at step 1 is the fallback path for clients that already know the resource, or when the challenge is missing.

Every client below implements this same flow with slight configuration and UI differences.

MCP Inspector

The reference client — fastest way to smoke-test everything.

npx @modelcontextprotocol/inspector https://mcp.example.com/mcp

Or for local dev:

npx @modelcontextprotocol/inspector http://localhost:8080/mcp

The URL must include the /mcp path (or whatever endpoint your SDK is configured for). http://localhost:8080 alone points at the server root and Inspector will report “no tools”.

Inspector opens a browser tab with the OAuth flow, walks through discovery, DCR, authorize, consent, and lands you on a UI where you can invoke tools with a valid token. Every scenario is covered by e2e/scenarios/mcp_inspector_test.go in the authserver repo — if Inspector works, your server is spec-compliant.

Full walkthrough in Guides: Testing with MCP Inspector.

Claude Desktop

Config file location:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Add your server to mcpServers:

{
  "mcpServers": {
    "my-server": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

Restart Claude Desktop. On first tool use, Claude Desktop pops up the OAuth flow in a browser window. Once you approve consent, the tokens are cached and subsequent tool calls are silent (with refresh happening in the background).

No API key or client_id needed in the config — Claude Desktop uses DCR to register itself dynamically.

Claude Code

Command-line invocation:

claude --mcp-server https://mcp.example.com/mcp

Or add to ~/.claude/config.json:

{
  "mcpServers": {
    "my-server": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

Known Claude Code quirks

Track authserver/docs/compatibility.md for the current state. As of writing:

  • Omits scope on /oauth/authorize (anthropics/claude-code#12077). Without a workaround, the AS issues a zero-scope token and you get 403 on every tool call.
    • Fix: set oauth.require_scope: false (or AUTHPLANE_OAUTH_REQUIRE_SCOPE=false). AuthPlane then defaults missing scope to all registered scopes for the resource (ADR-012).
  • Omits scope on DCR (#4540) — harmless; AuthPlane doesn’t enforce scope at registration.
  • Omits offline_access (#7744) — harmless; AuthPlane issues refresh tokens for all authorization_code grants regardless.
  • Omits resource on /oauth/authorize (#10572) — token’s aud will be empty; ADR-012 scope defaulting still applies.

Minimal Claude-Code-compatible config:

oauth:
  require_scope: false     # workaround for anthropics/claude-code#12077

Or env var: AUTHPLANE_OAUTH_REQUIRE_SCOPE=false.

Cursor

Cursor’s config lives at:

  • macOS/Linux: ~/.cursor/mcp.json
  • Windows: %APPDATA%\Cursor\mcp.json
{
  "mcpServers": {
    "my-server": {
      "url": "https://mcp.example.com/mcp",
      "type": "streamable-http"
    }
  }
}

Restart Cursor. First tool call triggers the OAuth flow in an embedded webview.

VS Code Copilot Chat

Uses settings.json:

{
  "github.copilot.chat.mcp.servers": {
    "my-server": {
      "url": "https://mcp.example.com/mcp",
      "type": "streamable-http"
    }
  }
}

Compatibility status: Pending — not yet in AuthPlane’s automated E2E suite. Manual validation required. If you hit issues, file a compat report following the MCP compatibility template.

Once connected — verifying with the Admin UI

Open https://auth-admin.internal.example.com/admin/ui/ (or the local http://localhost:9001/admin/ui/) and check:

  1. Clients — every MCP client that registered via DCR appears here with its client_id, redirect URIs, and grant types.
  2. Grants — user consent decisions. One row per (user, client, resource) after consent.
  3. Issuances — every token issued, with sub, client_id, resource, scope, token_type (Bearer or DPoP), and expires_at.
  4. Audit log — the full trail of token_issued, token_revoked, consent_granted events with timestamps.

If a client connected but tool calls fail, the token IS in Issuances but not usable — inspect its scope and aud claims against what your MCP server requires. See Reference: Errors for the top-20 debugging fixes.

Common failure modes

SymptomCauseFix
”No tools available” in InspectorURL missing /mcp pathPoint at http://host:port/mcp (or your configured endpoint)
Client hangs on “authenticating…”AS unreachable from clientVerify curl <authserver_url>/.well-known/oauth-authorization-server from where the client runs
403 on every tool call from Claude CodeZero-scope token due to scope omission bugSet AUTHPLANE_OAUTH_REQUIRE_SCOPE=false
Client shows OAuth flow but Consent screen 500sSession secret unstable or missingSet AUTHPLANE_SESSION_SECRET to a stable 32+ byte value
Discovery fetches, but redirects loopReverse proxy rewriting scheme/host without X-Forwarded-ProtoFix proxy headers; AuthPlane derives issuer URL from server.issuer, not the incoming request
PRM 404 at your MCP serverSDK didn’t mount the PRM handler (Go only)http.Handle(adapter.WellKnownPRMPath(), adapter.ProtectedResourceMetadataHandler())

More at Reference: Errors and Troubleshooting: Debugging.