Agent + single MCP
At a glance. One agent, one MCP, one user — the canonical baseline every other topology builds on. OAuth 2.1 authorization-code flow with PKCE. Per-user audit. No encapsulation. Shipped at v0.1.x.
Topology
flowchart LR
User["User<br/>(browser)"]
AS["AuthPlane"]
Agent["MCP Agent<br/>(Claude)"]
MCP["MCP Server<br/>resource: mcp-a<br/>scopes: read, write"]
User <-->|consent| AS
Agent -->|JWKS + PRM| AS
Agent -->|"Bearer (aud=mcp-a)"| MCP
Four components:
- User — provides consent at AuthPlane via browser.
- Agent — the OAuth client. Holds the user’s bearer token.
- AuthPlane — issues the JWT, signs with its own key (the Mint —
backend_kind: mint). - MCP Server — the Resource. Validates the JWT against AuthPlane’s JWKS on every call.
Flow
sequenceDiagram
participant User
participant Agent as MCP Agent
participant AS as AuthPlane
participant MCP as MCP Server
Agent->>MCP: GET /.well-known/oauth-protected-resource
MCP-->>Agent: PRM {authorization_servers, scopes_supported}
Agent->>AS: /oauth/authorize?resource=mcp-a&scope=read<br/>&code_challenge=…&code_challenge_method=S256
AS-->>User: consent screen (Agent × mcp-a × scopes)
User->>AS: approve
AS-->>Agent: 302 redirect + auth code
Agent->>AS: /oauth/token grant_type=authorization_code<br/>code_verifier=…
AS-->>Agent: access token bound to mcp-a (JWT, aud=mcp-a)
Agent->>MCP: POST /mcp Authorization: Bearer <token>
MCP->>AS: GET /.well-known/jwks.json (cached)
AS-->>MCP: JWKS
MCP-->>Agent: response
When to use
- Single-purpose MCP server with its own scope catalog.
- Agent only needs to reach this one MCP for the user.
- Per-MCP, per-agent consent is acceptable (typically the right default).
Don’t use when:
- Agent reaches multiple MCPs without separate per-MCP consent → direct-fanout or mcp-gateway-mint.
- MCP wraps an upstream IdP (Google, GitHub) → broker-mcp.
- No user (machine-to-machine only) → m2m-client-credentials.
How to configure
Two operations: register the MCP as a Mint Resource, then register the agent as a public OAuth client.
CLI:
# 1. Register the Resource
authserver admin resource create \
--slug mcp-a \
--uri https://mcp-a.example.com \
--backend-kind mint \
--display-name "MCP A" \
--scopes 'read||Read access' \
--scopes 'write||Write access'
# 2. Register the agent as a public client (PKCE-only)
authserver admin client create \
--name my-agent \
--grant-types authorization_code,refresh_token \
--auth-method none \
--scopes 'read||Read access' \
--scopes 'write||Write access'
# → copy the returned client_id
YAML seed:
resources:
- slug: mcp-a
backend_kind: mint
display_name: MCP A
uri: https://mcp-a.example.com
scopes:
- name: read
description: Read access
- name: write
description: Write access
For Admin UI + REST API forms, see Guides: Admin API.
Resource URI must match byte-for-byte what your MCP server publishes in its PRM. See Configuration → Resources for the URI-mismatch trap.
How AuthPlane handles it
/oauth/authorizelooks up the client and resource, records consent inconsent_grants, issues an auth code./oauth/token(auth-code grant) verifies PKCE, mints a JWT viaMintIssuer, writes an audit row inissuances, returns access + refresh.- Subsequent tool calls hit your MCP server; the SDK adapter verifies the JWT locally against the cached JWKS — no round trip to AuthPlane per request.
Refresh flow is standard OAuth 2.1 with mandatory rotation (see Concepts: Grants & flows).
Verify
# Inspect the issuance
authserver admin issuance list --resource mcp-a --limit 5
# → shows sub, client_id, scope, jti, expires_at
# Decode the JWT (aud should equal mcp-a's URI)
echo "<access_token>" | cut -d. -f2 | base64 -d | jq .aud
See also
- Choose your topology — decision flowchart
- Concepts: Grants & flows
- Guides: Connect an MCP client — client-side setup
- Quickstart — end-to-end runnable version of this topology