3.2 KiB
Interface: MCP Client identity and registration (OIDC)
Contract for registering an MCP Client and the OIDC token it presents to the Gateway.
Implementation: src/emcp_bus/identity/models.py,
identity/authn.py,
identity/shared_client_lint.py. See
AS-BUILT.md.
Registering an MCP Client
One YAML file per client in config/clients/*.yaml, validated against ClientRegistration:
client_id: finance-payments-agent # exactly the token's azp/client_id claim
profile: Finance.Payments # name of a ClientProfile under config/clients/profiles/
environment: production # "dev" | "ci" | "production"
description: "Finance agent authorized to create/execute payments (R3, approval above threshold)"
(real example: config/clients/finance-payments-agent.yaml)
client_id is the sole link between an authenticated identity and a ClientProfile — two
distinct client_ids never share a profile unless that is an explicit and auditable decision
(the shared-client rule, identity/shared_client_lint.py, wired into
make validate-schemas).
Registering the client with the IdP
The client must exist in the IdP realm with the client_credentials grant enabled and an
audience mapper pointing at the Gateway's OIDC_AUDIENCE (emcp-gateway by default). See
deploy/keycloak/realm-export.json for the 3 example clients already configured this way
(sales-read-agent, sales-write-agent, finance-payments-agent).
Access token contract
Any OIDC-compliant IdP works (TokenValidator is provider-agnostic). Claims required and
validated by TokenValidator.validate:
| Claim | Use |
|---|---|
exp |
Expiration — required, validated with leeway_seconds=5. |
iss |
Must match OIDC_ISSUER exactly. |
aud |
Must contain OIDC_AUDIENCE. |
azp (preferred) or client_id |
Sole source of client identity — never clientInfo from the MCP request nor any body field. |
Signature verified via JWKS (OIDC_JWKS_URL), RS256 algorithm. Failure of any check raises
AuthenticationError, treated by the Gateway as UNAUTHENTICATED (fail-closed).
How a new client registers, end to end
- Create/update the client in the IdP (
client_credentialsgrant, audience mapper). - Create
config/clients/<client-id>.yaml(ClientRegistration). - Point
profileat an existingClientProfile, or create a new one underconfig/clients/profiles/(seeregistry-capability-manifest.mdfor how a capability references a profile viaentitlements). make validate-schemas.- Restart the Gateway (client registrations are loaded at boot, no hot-reload).
Errors
| Situation | Behavior |
|---|---|
| Missing/malformed/expired token or invalid signature | AuthenticationError → Gateway treats it as UNAUTHENTICATED. |
Incorrect aud/iss |
AuthenticationError → UNAUTHENTICATED (README.md §45 Test 8). |
Valid token, but client_id without a matching ClientRegistration |
UnknownClientError → Gateway responds UNKNOWN_CLIENT on tools/call, empty list on tools/list. |