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 Mintbackend_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:

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/authorize looks up the client and resource, records consent in consent_grants, issues an auth code.
  • /oauth/token (auth-code grant) verifies PKCE, mints a JWT via MintIssuer, writes an audit row in issuances, 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