Skip to content

Identity and authorization

MCP Runtime uses separate identities for platform administration, delegated agent access, and Kubernetes workloads. Each identity defines who is acting, what they may do, and which component enforces the decision.

The three identity planes

Identity plane Identity shape Used for Enforced by
Platform User or service principal with role, subject, teams, and namespaces UI, CLI, and platform API operations platform-api, runtime-api, and analytics-api
Agent governance humanID + agentID + teamID + sessionID MCP tools/call authorization MCP gateway
Kubernetes workload ServiceAccount plus RBAC bindings Reading and changing cluster resources Kubernetes API server

Each plane is enforced independently. A platform administrator does not automatically have an agent session, and an MCP agent identity is separate from any Kubernetes ServiceAccount.

Platform identity: who controls the platform

platform-api, runtime-api, and analytics-api authenticate a request using one of these credentials:

  • A browser or CLI bearer token issued after local or OIDC login
  • A user-owned API key sent as x-api-key
  • A configured service or administrator API key sent as x-api-key

Authentication produces a platform principal containing fields such as:

role
subject / user ID
email
primary namespace
allowed namespaces
team memberships and team roles
authentication type

The API uses this principal to authorize control-plane actions.

Actor Allowed actions Why
Anonymous caller Health, login, OIDC exchange, and explicitly public catalog reads These routes are public by design
Authenticated user Read visible teams, namespaces, servers, and deployments, plus team-scoped analytics on GET /api/v1/user/analytics/usage Visibility is scoped by the principal's namespaces and teams
Server owner Change their server and administer its grants and sessions The server carries the owner's platform user label
Team owner Manage team members and administer servers and access resources in the team's namespace Team-owner membership grants administrative authority within that team
Platform administrator Manage teams, users, namespaces, platform operations, and resources across tenants, and read the unscoped analytics routes (/api/v1/events, /stats, /sources, /event-types, /analytics/usage) The admin role is the platform-wide control-plane authority
Service API key Only the API operations permitted by its assigned role Service authentication does not automatically create a human or agent identity

Creating or changing an access grant requires authority over the referenced server. In normal platform flows, that means the caller is the server owner, a team owner for the server namespace, or a platform administrator.

Direct creation of an MCPAgentSession through /api/v1/runtime/sessions is administrator/internal-only. Normal users obtain a session through the adapter session endpoint.

See the complete endpoint matrix in API authn/authz matrix.

Trusted ingress and client IP

The API derives the caller's client IP from the left-most X-Forwarded-For hop, falling back to RemoteAddr. This IP is used both for audit ActorIP labelling and for login-lockout bucketing (brute-force throttling on /api/v1/auth/login). Because clients can set X-Forwarded-For freely, the ingress in front of platform-api must set/overwrite this header authoritatively and strip any client-supplied value. Otherwise a caller can rotate the header to evade lockout or poison another address's bucket.

Configure Traefik (and any upstream load balancer) so platform-api sees the real client address, and so inbound X-Forwarded-For from untrusted clients is discarded, not appended. The ingress is the only trusted boundary for client IP; platform-api uses the header value it receives.

Agent identity: who is using an MCP tool

An agent call carries a delegated governance identity:

humanID   the platform user responsible for the call
agentID   the agent or client acting for that user
teamID    the stable team whose server and policy are in use
sessionID the active platform-owned session name

The adapter obtains a session-bound certificate from the platform. Traefik verifies it and the gateway resolves its SPIFFE URI against its synchronized platform session cache. Caller-supplied identity headers are never accepted as authentication.

The platform agent directory gives each managed AgentID a stable record owned by one team. Its display name and active status help administrators select and govern agents in grants and sessions. The directory is governance metadata; the ID alone does not authenticate a runtime or prove which software made a request. Authentication comes from optional OAuth for direct clients and from the session-bound certificate for enrolled adapters. OAuth-enabled adapter targets require both.

In the dashboard, open Agents from the top navigation. Platform admins can search and page across all teams or select one team. Team owners can create, rename, deactivate, and reactivate agents in their team. Members see only active agents covered by an applicable grant or their own active session. Open an agent to see its stable ID, owning team, status, applicable grants, and session history. The detail view shows grant subjects and session human IDs where those records provide them; agents have no separate user owner. Managers can grant access from the detail view with the agent and team already selected, change grants, and revoke sessions in namespaces they manage. An inactive agent stays in the directory with its history but cannot receive a new grant or session. Team and membership views link to the matching Agents view.

Access changes require a platform admin or team owner. Members can inspect their own applicable access and sessions, but the dashboard has no access request submission workflow yet. Ask a team owner outside the dashboard for additional access; no request is created by viewing an agent. After a grant, sign in with mcp-runtime auth login --api-url <platform-url>, then run mcp-runtime adapter proxy --runtime-url <https-mcp-url> --server <server> --namespace <namespace> --agent <agent-id> --auto-refresh. The proxy requests an authorized session and enrolls a certificate in memory. For a saved certificate, use mcp-runtime adapter enroll with the same server, namespace, and agent. The private key stays local; an OAuth-enabled target also needs its OAuth bearer token. A grant alone does not establish a connection. See Agent adapters for the supported CLI steps. Direct MCP clients remain governed by their existing gateway policy.

Agent IDs are platform-generated immutable agt_<26-character lowercase ULID> values. Agent subjects must resolve to an active directory record owned by the selected subject team. Unknown, malformed, inactive, or wrong-team IDs are rejected, and agent IDs cannot be entered as free text in the admin access forms. The runtime API checks grants, sessions, adapter session issuance, and certificate enrollment; use the runtime API for access changes so it can validate agent ownership against the identity store. Direct Kubernetes CRD writes do not consult the identity store and therefore bypass these directory checks; adapter session issuance and certificate enrollment still verify the agent before they issue credentials. Platform-api marks the agent inactive and revokes its active sessions in one Postgres transaction. Session issuance locks and validates the same directory record, so a concurrent enrollment cannot escape deactivation.

For audit, keep the actor (the agent named by the session) separate from the authority (the human or team that delegated access). A session and its gateway decisions should preserve both identities, along with the grant, server, MCP method/tool, decision, and request trace ID. MCP's OAuth roles and emerging workload identity guidance provide useful context; an OAuth client registration or client_id does not automatically create a managed agent.

Direct clients use OAuth only when the target server configures spec.auth. When MCP_ADAPTER_CERTIFICATES=true, an adapter presents its session-bound client certificate during the HTTPS handshake; Traefik verifies the certificate and the gateway resolves its session identity for grant/session authorization. On an OAuth-enabled target, the adapter also forwards a bearer on each HTTP request, and its subject must match the session human identity. Certificate identity and OAuth are separate credentials. The ingress switch is opt-in. Enrollment uses the platform-wide MCP_MTLS_CLUSTER_ISSUER and MCP_TRUST_DOMAIN settings and does not by itself make Traefik request the certificate. For the adapter request flow, including how one certificate can accompany different per-request OAuth tokens, see Certificate identity and OAuth tokens.

Further reading: MCP authorization, WIMSE Agent Identity Management Services draft (work in progress), AUDIT delegation and interaction traceability proposal, and SPIFFE SVIDs.

Delegated header authentication

auth.mode: header is an explicit compatibility mode for an MCP server that authenticates clients with its own credential headers. Runtime does not validate those credentials and does not turn a header value into a human, agent, team, or session. The gateway checks that the configured headers are present, applies server and tool rules, forwards the headers only to that server, and records the caller as unverified.

auth:
  mode: header
  credentialPresence: any
  headers:
    - X-Example-Credential
    - Private-Token
    - Authorization
policy:
  mode: allow-list
  defaultDecision: deny
  delegatedToolRules:
    - name: list_projects
      decision: allow

credentialPresence: any accepts one or more of the configured headers and forwards every present header. all requires every configured name. Neither mode selects a preferred header. Header names are arbitrary valid HTTP tokens except reserved transport, routing, and trusted identity headers.

The configured name list is the whole credential contract. The gateway does not recognize auth schemes or vendors. After the required configured headers are present, every listed header is forwarded unchanged for the MCP server to authenticate. The value is opaque. Runtime does not parse it or turn it into an OAuth identity.

Authorization is included only when the server lists that name. Otherwise the gateway still removes it. Tool rules still run before the request is forwarded: a denied tool never reaches the server.

Header mode requires allow-list enforcement and a default deny. Unlisted tools and tools without a declared side effect are denied before the upstream call. Grants, sessions, observation mode, and OAuth settings are rejected. A failed OAuth server does not fall back to this mode. Direct clients send the headers themselves. An optional adapter can inject them from local sources; that adapter path is separate and does not create a Runtime principal.

The server list and mcp-runtime server connect-config copy auth.headers into the client snippet as a headers object with empty values. The console Claude, Cursor, and VS Code tabs use that same object. Fill one value when credentialPresence is any and delete the other empty headers, because an empty header is rejected. all requires every listed header. The snippet never contains a credential value.

Grant: administrator-approved authority

An MCPAccessGrant answers:

Which human, agent, or team may use which MCP server, with which tools, side effects, and maximum trust?

Its important fields are:

spec:
  serverRef:
    name: payments
    namespace: mcp-team-finance
  subject:
    humanID: user-123
    agentID: agt_01arz3ndektsv4rrffq69g5fav
    teamID: team-finance-id
  maxTrust: medium
  allowedSideEffects: [read, write]
  toolRules:
    - name: create_invoice
      decision: allow
      requiredTrust: medium

Every populated subject field must match the request identity. A grant bound to all three subject fields does not match a different human, agent, or team. For cross-team access, subject.teamID names the grantee team while the referenced MCPServer.spec.teamID remains the resource-owning authority team. The grant does not transfer or rewrite server ownership. Cross-team subjects must be active members of the named team, and their grant must expire within MCP_CROSS_TEAM_GRANT_MAX_TTL (default seven days). Adapter sessions stop at the grant expiry. Authorization and audit records preserve both dimensions: the caller's subject team and the server's resource team. For administrator-created sessions, pass grantName to link the session to an active grant; the API verifies its server and every populated subject field, then caps session trust and expiry to the grant. Grant-wide session revocation targets sessions with this explicit link, including adapter-issued sessions. Manually created sessions without grantName are not part of a grant's revoke-all operation.

Resource namespace owners can look up one known recipient team through GET /api/v1/runtime/delegation-recipients?namespace=<resource-namespace>&team=<exact-team-slug>. The response contains only the team's ID/name, active agent identities, and member user IDs. It does not grant access to global team listings or foreign account metadata. limit defaults to 50 and is capped at 100; use agent_cursor and member_cursor with the corresponding next cursors to continue each list. Ordinary team members and unrelated namespace owners cannot use this lookup for a resource they do not manage. Directory outages return 503; invalid foreign subjects and invalid grant expiry return 422.

Grant-wide revocation is a single platform session-store transaction, scoped by the exact grant namespace and name, including migrated links in other namespaces. The response counts newly revoked sessions; repeating the action is safe and returns zero when no active linked sessions remain. Per-session audits and an operation audit preserve subject and resource teams. A store failure returns an error and requires a retry, without a successful revocation audit. An enabled grant can issue a fresh session later; its new identity does not reactivate a previously revoked certificate.

The grant controls:

  • Whether a specific tool is allowed or denied
  • Which side-effect classes are allowed
  • The administrator-approved maximum trust
  • Whether the grant is disabled

The grant does not prove that the agent has a current session.

Managed agent IDs are created with mcp-runtime agent create <team-slug> --name <name> and selected from the team's agent directory when creating a grant or session. The directory page and mcp-runtime agent list|get|rename| deactivate|reactivate expose the same records. IDs are generated once and never reused; do not invent an agent ID for a new managed subject.

A platform session, represented by MCPAgentSession YAML for CLI input, answers:

Is this human-agent-team identity currently active for this server, and how much trust is consented for this session?

Its important fields are:

spec:
  serverRef:
    name: payments
    namespace: mcp-team-finance
  subject:
    humanID: user-123
    agentID: agt_01arz3ndektsv4rrffq69g5fav
    teamID: team-finance-id
  consentedTrust: low
  expiresAt: 2030-06-12T12:00:00Z
  revoked: false

For the normal adapter flow:

  1. An authenticated platform user asks for a session for a server and agent.
  2. The platform verifies the user's team and namespace access.
  3. The platform selects a matching enabled grant.
  4. Requested trust is capped by the grant's maxTrust.
  5. The platform persists a new session or reuses a live matching session.
  6. The adapter presents the certificate and, when the target enables OAuth, forwards the bearer token.

The session controls expiry, revocation, and consented trust. It does not contain tool rules or allowedSideEffects; those remain grant policy.

Session state lives in Postgres, owned by platform-api. The gateway synchronizes only its server UID's sessions through a separate, authenticated internal port; ordinary authorization uses its in-process cache. New adapter session names include a random suffix so a retired certificate cannot name a replacement session. Use the platform CLI/UI for session creation, inspection and revocation; Kubernetes session edits do not affect access.

The gateway polls changes every 500 ms. A cold gateway denies until the first sync. A disconnected gateway retains its last complete snapshot for at most ten minutes (configurable downward with SESSION_SYNC_STALE_WINDOW), then denies. An epoch/cursor reset immediately invalidates the cache until relisting succeeds. See session sync for restore requirements and the policy schema v4 migration.

Tool metadata: what the server declares

When the MCP server is defined, its owner or deployer classifies each governed tool:

tools:
  - name: list_invoices
    requiredTrust: low
    sideEffect: read
  - name: delete_invoice
    requiredTrust: high
    sideEffect: destructive

The gateway uses the declared metadata as its policy input; it does not inspect tool implementations. A tool that the server never declared, or whose side-effect metadata is missing or unknown, is denied with tool_side_effect_unknown.

Gateway decision for every tools/call

For a tool call, the gateway authorizes the intersection of identity, policy, consent, and tool metadata:

valid identity
AND valid matching session
AND matching enabled grant
AND tool allowed by the grant
AND tool side effect allowed by the grant
AND effective trust >= required trust

The decision ladder

The gateway extracts the tool name from the JSON-RPC request, loads the rendered policy for the target server, and walks these checks in order. The first failing rung wins, and its reason code is what lands in the audit event and the denial response. A denied call never reaches the MCP server.

# Check Fails with
0 Is policy.mode: observe? If so, allow now; nothing below runs. (allowed, still audited)
1 Is there any human, agent, or team identity? missing_identity (401)
2 With session.required: true: is there a session ID, a matching session, not revoked, not expired? missing_session, session_not_found, session_revoked, session_expired (401)
3 Does any grant's subject match? Every populated subject field must match exactly. no_matching_grant
4 Does a tool rule deny this tool, or does no enabled grant allow it? Disabled and expired grants are skipped; a grant with no toolRules allows every tool name. tool_denied (403), tool_not_granted, grant_expired (every matching grant that is not disabled has passed its expiresAt)
5 Did the server declare this tool's side effect, and does the grant's allowedSideEffects include it? tool_side_effect_unknown, side_effect_not_allowed (403)
6 Does the grant carry a maxTrust? grant_without_trust
7 Is effective trust at least the required trust? trust_too_low (403)
✓ Forward to the MCP server and emit the audit event. allowed

The trust comparison on rung 7 is:

effectiveTrust = min(grant.maxTrust, session.consentedTrust)
requiredTrust  = max(tool.requiredTrust, matchingToolRule.requiredTrust)

When no session is required or none matches, consentedTrust falls back to the grant's maxTrust.

Reasons without a status code (no_matching_grant, tool_not_granted, grant_expired, grant_without_trust) follow policy.defaultDecision: 403 under the shipped deny default, allowed only if a server explicitly sets defaultDecision: allow.

Every decision, allowed or denied, emits an audit event containing the identity, tool, decision, reason, trust values, server, namespace, and policy version.

Observe mode is reporting, not enforcement

policy.mode: observe allows the call without any identity, session, grant, side-effect, or trust check; only the audit trail keeps visibility. Use it to preview what enforcement would deny on a new server, then switch back to allow-list.

Example:

Tool:               delete_invoice
Declared effect:    destructive
Declared trust:     high
Grant tool rule:    allow
Grant side effects: [read, write]
Grant max trust:    high
Session consent:    high
Result:             deny, side_effect_not_allowed

Authorization is an intersection, so the allow rule alone is not enough. The grant must also permit destructive.

Kubernetes workload identity

The operator and runtime-api use Kubernetes ServiceAccounts and RBAC to perform cluster operations. This identity plane controls actions such as:

  • Reading and reconciling MCPServer, MCPAccessGrant, and MCPAgentSession resources
  • Creating workloads, Services, Ingresses, Secrets, and namespace-scoped access resources
  • Reading platform configuration required by the service

Most workloads that do not call the Kubernetes API disable automatic ServiceAccount token mounting. Kubernetes RBAC is a separate enforcement layer from platform roles and gateway policy.

Responsibility summary

Question Source of truth
Who is operating the platform? Authenticated platform principal
Which tenant resources can they manage? Role, team membership, namespace scope, and ownership
Which human and agent are making the tool call? OAuth claims and/or the certificate-bound grant/session subject
Is that delegated access active now? Session expiry and revocation
Which tools can be called? Grant tool rules
Which classes of effects are permitted? Grant allowedSideEffects
How risky is the tool? MCPServer tool metadata
How much authority is effective? Minimum of grant maximum and session consent
Who enforces the tool call? MCP gateway
Who can change cluster resources? Kubernetes ServiceAccount and RBAC

Next: Architecture shows where identity and policy are enforced across components. Use Teams and Access to apply the model to namespace boundaries and cross-team delegation.