Agent + multiple MCPs (Direct fanout)

At a glance. One agent talks to N MCPs directly. The user consents at each MCP separately. Each token’s aud is bound to its target MCP — cross-MCP token replay is rejected at audience validation. No infra between agent and MCPs. Simplest multi-resource shape.

Topology

flowchart TD
    AS["AuthPlane"]
    MCPA["MCP A<br/>read"]
    MCPB["MCP B<br/>query"]
    MCPC["MCP C<br/>write"]
    Agent["Agent"]

    AS --> MCPA
    AS --> MCPB
    AS --> MCPC
    Agent -->|Bearer per-MCP| MCPA
    Agent -->|Bearer per-MCP| MCPB
    Agent -->|Bearer per-MCP| MCPC

Each of MCP A / B / C is registered as its own Mint Resource with its own scope catalog. The agent is one OAuth client that gets a separate audience-bound token per MCP.

Flow

Per-MCP flow is identical to single-mcp. The user gets N consent screens on first use — one per MCP resource.

sequenceDiagram
    participant Agent
    participant MCP
    participant AS as AuthPlane
    participant User

    loop For each MCP in {A, B, C}
        Agent->>MCP: GET /.well-known/oauth-protected-resource
        MCP-->>Agent: PRM
        Agent->>AS: /oauth/authorize?resource=mcp-X&scope=…
        AS-->>User: consent screen for THIS mcp-X
        User->>AS: approve
        AS-->>Agent: 302 + code
        Agent->>AS: /oauth/token
        AS-->>Agent: token with aud=mcp-X
        Agent->>MCP: call with Bearer(aud=mcp-X)
    end

Token from MCP A replayed to MCP B is rejectedaud claim doesn’t match MCP B’s resource URI. This is the audience-binding guarantee of RFC 8707.

When to use

  • Multiple independent MCP servers with distinct scope catalogs.
  • User accepts per-MCP consent (the default; usually fine).
  • No gateway, no infrastructure between agent and MCPs.

Don’t use when:

  • User wants one consent screen for all MCPs → wait for RFC 8707 multi-resource authorization (on the roadmap), or use mcp-gateway-mint with one consented gateway.
  • MCPs need to reach each other with an agent-attributed token → mcp-gateway-mint.
  • Any MCP wraps an upstream IdP → mix in broker-mcp for that one.

How to configure

CLI:

# 1. Register every MCP
authserver admin resource create --slug mcp-a --backend-kind mint --uri https://mcp-a.example.com --scopes 'read||Read access'
authserver admin resource create --slug mcp-b --backend-kind mint --uri https://mcp-b.example.com --scopes 'query||Query access'
authserver admin resource create --slug mcp-c --backend-kind mint --uri https://mcp-c.example.com --scopes 'write||Write access'

# 2. Register the agent (one client across all MCPs)
authserver admin client create \
    --name my-agent \
    --grant-types authorization_code,refresh_token \
    --auth-method none \
    --scopes 'read||Read access' \
    --scopes 'query||Query access' \
    --scopes 'write||Write access'

YAML seed:

resources:
  - { slug: mcp-a, backend_kind: mint, uri: https://mcp-a.example.com, scopes: [{name: read}] }
  - { slug: mcp-b, backend_kind: mint, uri: https://mcp-b.example.com, scopes: [{name: query}] }
  - { slug: mcp-c, backend_kind: mint, uri: https://mcp-c.example.com, scopes: [{name: write}] }

How AuthPlane handles it

Each MCP is a separate resource with its own audit trail. consent_grants has one row per (user, agent, mcp-X). One agent client, three separate consent states — revoking consent for MCP-A doesn’t affect MCP-B or MCP-C.

Every token is minted with the exact URI of its target as aud. The SDK on each MCP verifies aud matches its own URI — cross-MCP replay fails audience validation without any coordination between MCPs.

Verify

# List issuances per resource
authserver admin issuance list --resource mcp-a --client my-agent

# Grants per user — should show 3 rows if all three MCPs have been consented
authserver admin grant list-user-grants --user user-42

See also