Context: this is part of Guides — Integrate. Start with the primer if you haven't.
Audience: Builders / operators with multiple OAuth clients behind a single Resource (multi-tier prod/canary, gateway → Mint, dynamically-introduced clients), wiring policy.runtime.client_ids so the broker agent-attestation gate resolves the calling client to its Resource.
- A clear picture of when
runtime.client_idsis required vs. optional - The Resource updated to authorize one or more
client_ids to act AS it - A failing exchange that turns green once the binding is in place
- A Mint Resource that participates in broker dispatch (an "actor MCP" in delegation flows). See Concepts → Delegation & agent chains.
- Concept context: Resource, Resource slug, Token exchange, Fronting link.
The OAuth client_id and the Resource slug are distinct identities with an N:N relationship at runtime. One OAuth client can play different roles across calls; one Resource can be served by multiple OAuth client identities (prod tier, canary, multiple regions). policy.runtime.client_ids is the only place Authplane learns which client_ids may act AS a given Resource — there is no implicit slug/client-id binding.
Default: empty list = default-deny. No client may act AS the Resource. (Opposite of policy.exchange.allowed_client_ids, which is permissive when empty.)
You need this when: an OAuth client authenticates to /oauth/token and the broker agent-attestation gate must resolve that client to its actor MCP — i.e., any time an agent or gateway exchanges a token for a broker Resource and no fronting link covers the source/target pair.
You don't need this when: the exchange targets a Mint Resource, or a fronting link already covers the (source → target) pair, or the dispatch doesn't hit the gate (direct user→MCP, refresh, client_credentials, jwt-bearer, authorization code).
You need the Resource slug (operator-meaningful identifier) and the OAuth client_id (opaque, auto-generated). List both:
# Command verified against docs/reference/cli.md#cli-admin-resource-list
authserver admin resource list --backend-kind mint
# Command verified against docs/reference/cli.md#cli-admin-client-list
authserver admin client list# Command verified against docs/reference/cli.md#cli-admin-resource-runtime-client-add
authserver admin resource runtime-client add \
--slug mcp-gw \
--client-id gw_prod_idEquivalent admin API:
# Wire shape verified against docs/reference/http-api.md#http-admin-resources-slug-policy-runtime-client-ids-create
curl -sS -X POST http://localhost:9001/admin/resources/mcp-gw/policy/runtime/client-ids \
-H "Authorization: Bearer $AUTHPLANE_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"client_id":"gw_prod_id"}'Or seed at startup in YAML:
resources:
- slug: mcp-gw
backend_kind: mint
policy:
exchange:
allowed_client_ids: [agent_x, agent_y] # who may exchange INTO this Resource
runtime:
client_ids: [gw_prod_id, gw_canary_id] # who may act AS this ResourceEach tier authenticates with its own credentials; all of them resolve to the same Resource at the runtime gate. Audit logs still record the real authenticated client_id, so you can tell which tier served any given call.
resources:
- slug: mcp-gw
backend_kind: mint
policy:
runtime:
client_ids:
- gw_prod_id
- gw_canary_id
- gw_dev_id# Command verified against docs/reference/cli.md#cli-admin-resource-runtime-client-list
authserver admin resource runtime-client list --slug mcp-gw
# Command verified against docs/reference/cli.md#cli-admin-resource-runtime-client-remove
authserver admin resource runtime-client remove --slug mcp-gw --client-id gw_prod_idThe list should include the client you added:
# Wire shape verified against docs/reference/http-api.md#http-admin-resources-slug-policy-runtime-client-ids-list
curl -sS http://localhost:9001/admin/resources/mcp-gw/policy/runtime/client-ids \
-H "Authorization: Bearer $AUTHPLANE_ADMIN_API_KEY" | jq .
# Expect: {"client_ids":["gw_prod_id"]}Run the token-exchange call from your agent. The actor MCP resolves through policy.runtime.client_ids and the exchange succeeds. The tier-01 verify.sh in the examples exercises this code path.
| Symptom | Likely cause | Fix |
|---|---|---|
Token exchange returns invalid_grant, audit row shows reason=agent_attestation_unknown_actor |
The calling client_id is not in any Resource's runtime.client_ids |
Add it: authserver admin resource runtime-client add --slug <actor-mcp> --client-id <caller> |
| Same call works when a fronting link is present and breaks when removed | The fronting link was masking missing runtime.client_ids config |
Configure runtime.client_ids so dispatch stands on its own; fronting then handles only its actual job (operator-vouching across hops) |
Audit log warning: resolved actor MCP via deprecated slug==client_id convention |
The legacy slug-match fallback fired (one-release deprecation window) | Configure runtime.client_ids explicitly — the fallback will be removed |
403 from the admin API when listing runtime clients |
Missing or wrong AUTHPLANE_ADMIN_API_KEY |
Check the env var and that you're hitting the admin port (:9001), not the public port |
Empty runtime.client_ids rejects calls you expected to allow |
Default-deny semantics: empty = no one. Different from exchange.allowed_client_ids (empty = any) |
Populate the list explicitly; the default is deliberate |
- Runnable proof:
examples/go/01-mcp-server-basic/scripts/verify.shand the equivalent in the TypeScript / Python tier-01 examples — they all hit this admin endpoint on setup. - Concepts → Delegation & agent chains
- Operate → Admin CLI & API
- Reference → CLI (
authserver admin resource runtime-client) - Reference → HTTP API (runtime client-ids endpoints)