You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A cleanup pass over the whole MCP auth stack. No behaviour changes: the
chart renders byte-identically, with and without MCP enabled.
Dead state. $mcpOidcProxyEnabled was an alias for $mcpEnabled, so its three
conditionals were nested inside the condition they tested. $mcpMetadataUrl
and $mcpBaseUrl lost their only reader when the www-authenticate mapper was
deleted. mcp-service.yaml pre-declared six variables to hoist scope out of a
conditional that no longer exists.
Credentials are derived once per tool call instead of three times. Deleting
RequestContextMiddleware left get_request_credentials() recomputing from
scratch on every call -- twice in protocol.py and again per Gateway request
in tool_requests.py -- each one a regex over the bearer plus a scan of the
request-ID header. track_request_task() had become a contextmanager whose
body was a bare yield; it now binds the derivation so later calls reuse it.
Mandatory configuration stops pretending to be optional. The five fields
auth cannot start without were Optional-with-None plus a hand-rolled
missing-field check, which cost eight casts to undo. Declaring them required
lets pydantic report them, and mypy now catches an incomplete construction.
Tests. AnyTokenVerifier re-implemented FastMCP's DebugTokenVerifier, and the
user_name it threaded through the harness only ever wrote a claim nothing
reads. test_auth passed auth_enabled and oidc_access_token_jwks_url, both
deleted by this stack and silently ignored, one of them contradicting an
assertion five lines above. test_protocol hand-built its ASGI app, so it
skipped the origin guard and body limit that create_application installs.
Two create_autospec(Redis) calls cost ~1s to assert one awaited close.
Three BUILD targets kept a dep on //src/lib/utils:login they no longer use.
The render harness rendered the same release twice to slice one template out,
and counted the MCP audience across the whole document -- passing only
because the other occurrence happens to carry a value: prefix. It is now
scoped to the audiences list, and negative-tested.
Docs. Deleting direct mode took four sections of the user appendix with it
that were never about auth modes, orphaning the Safety and Limits bullets
under an authentication lead-in; they are restored. The deployment guide
still showed oidcProxy.enabled and the secret paths the chart now derives,
and the README told operators to keep replicas at 1, which the move to Redis
had already made false. Two RST tables were left malformed.
Comments are shortened throughout, and _derive_fernet_key no longer calls
derive_jwt_key private -- it is not; the real reason for pinning the
derivation locally is that an upstream change would make stored state
undecryptable.
Verification
- bazel test //src/service/mcp/... //test/smoke/... -- 76/76 pass
- render-tests.sh -- pass; MCP and non-MCP renders byte-identical to before
- negative-tested the audience assertion by making the append unconditional
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@@ -51,16 +49,12 @@ setting detects this rotation and triggers Envoy to reload.
51
49
{{- end }}
52
50
{{- $mcpResourceUrl= include "osmo.mcp-resource-url". }}
53
51
{{- /*
54
-
The relayed upstream token carries the MCP resource URL as its audience, so the
55
-
Gateway has to accept that audience from the identity provider MCP authenticates
56
-
against. That provider is already configured for this deployment's own clients,
57
-
and differs only in audience -- so the audience is appended to it rather than
58
-
asking for a second, near-identical entry.
59
-
60
-
OpenID Connect Discovery defines the configuration URL as the issuer followed by
61
-
/.well-known/openid-configuration, so the issuer is derivable. accessTokenIssuer
62
-
overrides it for providers whose access tokens are issued elsewhere, as an
63
-
application configured for v1-format tokens does.
52
+
The relayed token's audience is the MCP resource URL, so it is appended to the
53
+
provider already configured for that issuer rather than requiring a second,
54
+
near-identical entry. OpenID Connect Discovery defines the configuration URL as
55
+
the issuer plus /.well-known/openid-configuration, so the issuer is derivable;
56
+
accessTokenIssuer overrides it for providers that issue access tokens elsewhere,
57
+
as an application configured for v1-format tokens does.
64
58
*/ -}}
65
59
{{- $mcpTokenIssuer=$mcpOidcProxy.oidc.accessTokenIssuer| default (trimSuffix "/.well-known/openid-configuration" (required "services.mcp.oidcProxy.oidc.configUrl is required when MCP is enabled"$mcpOidcProxy.oidc.configUrl)) }}
{{- $oidcAccessTokenRequiredScope = required "services.mcp.oidcProxy.oidc.accessTokenRequiredScope is required when MCP is enabled" $oidcProxy.oidc.accessTokenRequiredScope }}
30
+
{{- $oidcConfigUrl := required "services.mcp.oidcProxy.oidc.configUrl is required when MCP is enabled" $oidcProxy.oidc.configUrl }}
31
+
{{- $oidcClientId := required "services.mcp.oidcProxy.oidc.clientId is required when MCP is enabled" $oidcProxy.oidc.clientId }}
{{- $oidcAccessTokenRequiredScope := required "services.mcp.oidcProxy.oidc.accessTokenRequiredScope is required when MCP is enabled" $oidcProxy.oidc.accessTokenRequiredScope }}
40
34
{{- if not (regexMatch "^[A-Za-z0-9:._~-]{1,128}$" $oidcAccessTokenRequiredScope) }}
41
35
{{- fail "services.mcp.oidcProxy.oidc.accessTokenRequiredScope must be one non-empty scope" }}
42
36
{{- end }}
@@ -47,11 +41,8 @@
47
41
{{- if and $oidcAccessTokenIssuer (not (regexMatch $oidcHttpsUrlPattern $oidcAccessTokenIssuer)) }}
48
42
{{- fail "services.mcp.oidcProxy.oidc.accessTokenIssuer must be an absolute HTTPS issuer without query or fragment" }}
0 commit comments