On a self-hosted installation configured with ARTIFACT_SERVER_OIDC_*, /mcp accepts managed API keys only. Every agent behind a shared gateway therefore acts as one identity, and revoking one person means rotating a key that everybody holds.
0020 records this as out of scope. The stated reason is that the MCP bearer path "assumes an OAuth authorization server that MCP clients can register against and that can introspect access tokens. A bare enterprise IdP is generally not that server."
Two observations against that reading.
WorkOsMcpBearerVerifier does not introspect anything either. It verifies a JWT against the discovered JWKS, which is exactly what an access token from Keycloak, Entra, or Okta supports.
Client registration does not have to be dynamic. An operator can register one client and configure its ID, the same way the WorkOS client ID is configured today.
The requirement that does need operator work is audience binding, because a plain issuer will not put <origin>/mcp into aud without an audience mapper. That is a documented setup step rather than a blocker.
I have a working implementation that mirrors the WorkOS plug into the same ExternalMcpBearerVerifier port. It reuses the existing issuer rules from oidc-issuer.ts, writes the same oidc:<issuer> plus sub member binding that browser login already writes, and serves RFC 9728 protected-resource metadata so a client discovers the issuer from a 401. It is verified against a real Keycloak through this repository's own test:oidc harness.
Before I open a pull request, two questions.
Would you take a change that reverses the MCP OAuth exclusion recorded in 0020?
Which conformance requirement should own the generic path? MCP-013 already states that self-hosted mode "uses browser OAuth when compatible authorization is configured", while MCP-011 and MCP-012 are written for the hosted WorkOS flavor.
On a self-hosted installation configured with
ARTIFACT_SERVER_OIDC_*,/mcpaccepts managed API keys only. Every agent behind a shared gateway therefore acts as one identity, and revoking one person means rotating a key that everybody holds.0020 records this as out of scope. The stated reason is that the MCP bearer path "assumes an OAuth authorization server that MCP clients can register against and that can introspect access tokens. A bare enterprise IdP is generally not that server."
Two observations against that reading.
WorkOsMcpBearerVerifierdoes not introspect anything either. It verifies a JWT against the discovered JWKS, which is exactly what an access token from Keycloak, Entra, or Okta supports.Client registration does not have to be dynamic. An operator can register one client and configure its ID, the same way the WorkOS client ID is configured today.
The requirement that does need operator work is audience binding, because a plain issuer will not put
<origin>/mcpintoaudwithout an audience mapper. That is a documented setup step rather than a blocker.I have a working implementation that mirrors the WorkOS plug into the same
ExternalMcpBearerVerifierport. It reuses the existing issuer rules fromoidc-issuer.ts, writes the sameoidc:<issuer>plussubmember binding that browser login already writes, and serves RFC 9728 protected-resource metadata so a client discovers the issuer from a 401. It is verified against a real Keycloak through this repository's owntest:oidcharness.Before I open a pull request, two questions.
Would you take a change that reverses the MCP OAuth exclusion recorded in 0020?
Which conformance requirement should own the generic path? MCP-013 already states that self-hosted mode "uses browser OAuth when compatible authorization is configured", while MCP-011 and MCP-012 are written for the hosted WorkOS flavor.