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 →
401withWWW-Authenticate→ fetch PRM → discover the AS → pick a client-identification path (CIMD, DCR, or pre-registered) → run OAuth → present tokens ontools/*calls. This page has the per-client config (Claude Desktop, Cursor, VS Code Copilot, MCP Inspector) plus known quirks like Claude Code omittingscopeon/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 withcurl -s https://mcp.example.com/.well-known/oauth-protected-resource/mcp | jq. - AuthPlane AS is reachable at the URL your PRM’s
authorization_serversfield 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
scopeon/oauth/authorize(anthropics/claude-code#12077). Without a workaround, the AS issues a zero-scope token and you get403on every tool call.- Fix: set
oauth.require_scope: false(orAUTHPLANE_OAUTH_REQUIRE_SCOPE=false). AuthPlane then defaults missing scope to all registered scopes for the resource (ADR-012).
- Fix: set
- Omits
scopeon DCR (#4540) — harmless; AuthPlane doesn’t enforce scope at registration. - Omits
offline_access(#7744) — harmless; AuthPlane issues refresh tokens for allauthorization_codegrants regardless. - Omits
resourceon/oauth/authorize(#10572) — token’saudwill 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:
- Clients — every MCP client that registered via DCR appears here with its
client_id, redirect URIs, and grant types. - Grants — user consent decisions. One row per (user, client, resource) after consent.
- Issuances — every token issued, with
sub,client_id,resource,scope,token_type(Bearer or DPoP), andexpires_at. - Audit log — the full trail of
token_issued,token_revoked,consent_grantedevents 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
More at Reference: Errors and Troubleshooting: Debugging.
Related
- Guides: Testing with MCP Inspector — deep dive on Inspector-driven validation
- Quickstart — the full server-side setup that unlocks these clients
- Reference: Configuration → OAuth —
oauth.require_scopeand other client-facing knobs - Compatibility matrix (authserver repo) — living document, updated per client release
- SDKs overview — server-side adapter matrix