Skip to content

Commit dcd3127

Browse files
jiaenrenclaude
andcommitted
Clean up the MCP authentication simplification
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>
1 parent 81532f1 commit dcd3127

21 files changed

Lines changed: 322 additions & 380 deletions

File tree

deployments/charts/service/README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,9 @@ OSMO services write logs to standard streams for collection by the platform log
7575
### Self-hosted MCP service
7676

7777
The optional MCP workload exposes predefined OSMO operations to compatible
78-
native or desktop MCP clients. In direct-provider mode, the Gateway validates
79-
the bearer on `/mcp`. FastMCP validates its own proxy
80-
token and relays the verified upstream access token through the same Gateway
81-
for each mapped OSMO API request. That API request still passes the deployment's
78+
native or desktop MCP clients. FastMCP validates its own proxy token and relays
79+
the verified upstream access token through the same Gateway for each mapped
80+
OSMO API request. That API request still passes the deployment's
8281
normal identity-provider validation and semantic RBAC.
8382

8483
`services.mcp.resourceUrl` is the single source of truth for the public MCP
@@ -137,9 +136,8 @@ rotation.
137136
Enabling MCP always renders an ingress NetworkPolicy whose allow rule selects
138137
only this release's Gateway Envoy pods, even when
139138
`gateway.networkPolicies.enabled` is false for other upstreams. This is
140-
required because direct-provider mode trusts the identity context created by
141-
Gateway, while OIDC-proxy mode accepts traffic only through the same public
142-
Gateway boundary.
139+
required because MCP accepts traffic only through the same public Gateway
140+
boundary.
143141
NetworkPolicies require enforcement by the cluster CNI and are additive, so
144142
operators must also ensure no other policy grants MCP ingress. The pod does
145143
not mount a service-account token, and the chart creates no MCP credential

deployments/charts/service/templates/_gateway-envoy-config.tpl

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,10 @@ setting detects this rotation and triggers Envoy to reload.
3131
{{- $mcp := .Values.services.mcp }}
3232
{{- $mcpEnabled := $mcp.enabled | default false }}
3333
{{- $mcpOidcProxy := $mcp.oidcProxy }}
34-
{{- $mcpOidcProxyEnabled := $mcpEnabled }}
3534
{{- $mcpPath := "/mcp" }}
3635
{{- $mcpMetadataPath := "/.well-known/oauth-protected-resource/mcp" }}
3736
{{- $mcpResourceUrl := "" }}
3837
{{- $mcpTokenIssuer := "" }}
39-
{{- $mcpMetadataUrl := "" }}
4038
{{- $skipAuthPaths := concat (default (list) $envoy.skipAuthPaths) (default (list) $envoy.extraSkipAuthPaths) }}
4139
{{- $authnSkipPaths := $skipAuthPaths }}
4240
{{- if $gw.oauth2Proxy.enabled }}
@@ -51,16 +49,12 @@ setting detects this rotation and triggers Envoy to reload.
5149
{{- end }}
5250
{{- $mcpResourceUrl = include "osmo.mcp-resource-url" . }}
5351
{{- /*
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.
6458
*/ -}}
6559
{{- $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)) }}
6660
{{- $mcpTokenIssuer = trimSuffix "/" $mcpTokenIssuer }}
@@ -82,13 +76,11 @@ application configured for v1-format tokens does.
8276
{{- $overlapsMcpPath := or (hasPrefix $skipPath $mcpPath) (hasPrefix $mcpPath $skipPath) }}
8377
{{- $overlapsMcpMetadataPath := or (hasPrefix $skipPath $mcpMetadataPath) (hasPrefix $mcpMetadataPath $skipPath) }}
8478
{{- $mcpAuthServerMetadataPath := "/.well-known/oauth-authorization-server/mcp" }}
85-
{{- $overlapsMcpAuthServerPath := and $mcpOidcProxyEnabled (or (hasPrefix $skipPath $mcpAuthServerMetadataPath) (hasPrefix $mcpAuthServerMetadataPath $skipPath)) }}
79+
{{- $overlapsMcpAuthServerPath := (or (hasPrefix $skipPath $mcpAuthServerMetadataPath) (hasPrefix $mcpAuthServerMetadataPath $skipPath)) }}
8680
{{- if or $overlapsMcpPath $overlapsMcpMetadataPath $overlapsMcpAuthServerPath }}
8781
{{- fail (printf "gateway auth bypass prefix %q overlaps a protected MCP path" $skipPath) }}
8882
{{- end }}
8983
{{- end }}
90-
{{- $mcpBaseUrl := trimSuffix $mcpPath $mcpResourceUrl }}
91-
{{- $mcpMetadataUrl = printf "%s%s" $mcpBaseUrl $mcpMetadataPath }}
9284
{{- end }}
9385
{{- $gwName := include "osmo.gateway-name" . }}
9486
{{- if $envoy.enabled }}
@@ -298,22 +290,13 @@ data:
298290
timeout: 15s
299291
typed_per_filter_config:
300292
{{- include "osmo.gateway-auth-filters-disabled" . | nindent 20 }}
301-
302-
{{- if $mcpOidcProxyEnabled }}
303-
# FastMCP owns the whole OAuth surface for this deployment. The
304-
# MCP SDK registers its handlers at fixed root paths, so the
305-
# gateway publishes them under /mcp -- matching the endpoint
306-
# URLs FastMCP advertises in its metadata -- and rewrites the
307-
# prefix back off before forwarding. One prefix route keeps the
308-
# gateway from having to enumerate FastMCP's endpoint names.
309-
# The prefix route below publishes the container's whole root
310-
# namespace, so anything registered at the app root becomes
311-
# public. Keep the health endpoints off the internet; any new
312-
# non-OAuth root route must be carved out here too. The auth
313-
# filters are disabled so this route answers 404 itself rather
314-
# than letting jwt_authn answer 401 first -- the carve-out has
315-
# to be the thing that rejects, not a side effect of another
316-
# filter that a later change could switch off.
293+
# The MCP SDK registers OAuth at fixed root paths, so the
294+
# gateway publishes them under /mcp -- matching what FastMCP
295+
# advertises -- and rewrites the prefix off before forwarding.
296+
# That prefix publishes the container's whole root namespace,
297+
# so any new non-OAuth root route must be carved out here too.
298+
# Auth filters are off on this route so the 404 is its own
299+
# answer, not jwt_authn's 401 that a later change could move.
317300
- name: mcp-health-not-public
318301
match:
319302
prefix: /mcp/health
@@ -346,21 +329,17 @@ data:
346329
timeout: 15s
347330
typed_per_filter_config:
348331
{{- include "osmo.gateway-auth-filters-disabled" . | nindent 20 }}
349-
{{- end }}
350332

351-
# In direct mode Gateway validates and authorizes /mcp. With
352-
# the in-process proxy enabled, FastMCP validates its own token
353-
# and relays the verified upstream token to protected /api.
333+
# FastMCP validates its own token and relays the verified
334+
# upstream token to protected /api.
354335
- name: osmo-mcp
355336
match:
356337
path: {{ $mcpPath }}
357338
route:
358339
cluster: osmo-mcp
359340
timeout: 0s
360-
{{- if $mcpOidcProxyEnabled }}
361341
typed_per_filter_config:
362342
{{- include "osmo.gateway-auth-filters-disabled" . | nindent 20 }}
363-
{{- end }}
364343
{{- end }}
365344

366345
{{- if $gw.upstreams.router.enabled }}
@@ -624,7 +603,6 @@ data:
624603
header_name: ":method"
625604
value_match:
626605
exact: "GET"
627-
{{- if $mcpOidcProxyEnabled }}
628606
# FastMCP is authoritative for its own OAuth surface.
629607
- single_predicate:
630608
input:
@@ -637,7 +615,6 @@ data:
637615
google_re2: {}
638616
regex: "^(/mcp/.*|/[.]well-known/oauth-authorization-server/mcp([?].*)?)$"
639617
{{- end }}
640-
{{- end }}
641618
{{- if $authnSkipPaths }}
642619
- single_predicate:
643620
input:

deployments/charts/service/templates/mcp-service.yaml

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,13 @@
2424
{{- if not (regexMatch "^([1-9]|[1-5][0-9]|60)$" $requestTimeoutSeconds) }}
2525
{{- fail "services.mcp.requestTimeoutSeconds must be between 1 and 60" }}
2626
{{- end }}
27-
{{- $oidcConfigUrl := "" }}
28-
{{- $oidcClientId := "" }}
2927
{{- $oidcClientSecretFile := "" }}
3028
{{- $redisPasswordFile := "" }}
3129
{{- $secretMountPath := "" }}
32-
{{- $oidcAccessTokenIssuer := "" }}
33-
{{- $oidcAccessTokenRequiredScope := "" }}
34-
{{- $redisKeyPrefix := "" }}
35-
{{- $redisUrl := "" }}
36-
{{- $oidcConfigUrl = required "services.mcp.oidcProxy.oidc.configUrl is required when MCP is enabled" $oidcProxy.oidc.configUrl }}
37-
{{- $oidcClientId = required "services.mcp.oidcProxy.oidc.clientId is required when MCP is enabled" $oidcProxy.oidc.clientId }}
38-
{{- $oidcAccessTokenIssuer = $oidcProxy.oidc.accessTokenIssuer | default "" }}
39-
{{- $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 }}
32+
{{- $oidcAccessTokenIssuer := $oidcProxy.oidc.accessTokenIssuer }}
33+
{{- $oidcAccessTokenRequiredScope := required "services.mcp.oidcProxy.oidc.accessTokenRequiredScope is required when MCP is enabled" $oidcProxy.oidc.accessTokenRequiredScope }}
4034
{{- if not (regexMatch "^[A-Za-z0-9:._~-]{1,128}$" $oidcAccessTokenRequiredScope) }}
4135
{{- fail "services.mcp.oidcProxy.oidc.accessTokenRequiredScope must be one non-empty scope" }}
4236
{{- end }}
@@ -47,11 +41,8 @@
4741
{{- if and $oidcAccessTokenIssuer (not (regexMatch $oidcHttpsUrlPattern $oidcAccessTokenIssuer)) }}
4842
{{- fail "services.mcp.oidcProxy.oidc.accessTokenIssuer must be an absolute HTTPS issuer without query or fragment" }}
4943
{{- end }}
50-
{{- $urlsToCheck := dict "configUrl" $oidcConfigUrl }}
51-
{{- if $oidcAccessTokenIssuer }}
52-
{{- $_ := set $urlsToCheck "accessTokenIssuer" $oidcAccessTokenIssuer }}
53-
{{- end }}
54-
{{- range $name, $url := $urlsToCheck }}
44+
{{- range $name, $url := (dict "configUrl" $oidcConfigUrl "accessTokenIssuer" $oidcAccessTokenIssuer) }}
45+
{{- if $url }}
5546
{{- $portMatch := regexFind ":[0-9]+(/|$)" $url }}
5647
{{- if $portMatch }}
5748
{{- $urlPort := trimSuffix "/" (trimPrefix ":" $portMatch) | int }}
@@ -60,12 +51,13 @@
6051
{{- end }}
6152
{{- end }}
6253
{{- end }}
63-
{{- $redisHost := .Values.services.redis.serviceName | required "services.redis.serviceName is required when the MCP OIDC proxy is enabled" }}
54+
{{- end }}
55+
{{- $redisHost := .Values.services.redis.serviceName | required "services.redis.serviceName is required when MCP is enabled" }}
6456
{{- $redisPort := .Values.services.redis.port }}
6557
{{- if or (lt (int $oidcProxy.redis.dbNumber) 0) (gt (int $oidcProxy.redis.dbNumber) 15) }}
6658
{{- fail "services.mcp.oidcProxy.redis.dbNumber must be between 0 and 15" }}
6759
{{- end }}
68-
{{- $redisKeyPrefix = required "services.mcp.oidcProxy.redis.keyPrefix is required" $oidcProxy.redis.keyPrefix }}
60+
{{- $redisKeyPrefix := required "services.mcp.oidcProxy.redis.keyPrefix is required" $oidcProxy.redis.keyPrefix }}
6961
{{- if not (regexMatch "^[A-Za-z0-9:._~-]{1,128}$" $redisKeyPrefix) }}
7062
{{- fail "services.mcp.oidcProxy.redis.keyPrefix contains unsupported characters" }}
7163
{{- end }}
@@ -104,7 +96,7 @@
10496
{{- fail "services.mcp.oidcProxy.oidc.clientSecretFile must be an absolute path" }}
10597
{{- end }}
10698
{{- $redisScheme := ternary "rediss" "redis" .Values.services.redis.tlsEnabled }}
107-
{{- $redisUrl = printf "%s://%s:%v/%v" $redisScheme $redisHost $redisPort $oidcProxy.redis.dbNumber }}
99+
{{- $redisUrl := printf "%s://%s:%v/%v" $redisScheme $redisHost $redisPort $oidcProxy.redis.dbNumber }}
108100
{{- if not (kindIs "slice" $mcp.allowedOrigins) }}
109101
{{- fail "services.mcp.allowedOrigins must be a list" }}
110102
{{- end }}

deployments/charts/service/tests/mcp-proxy-values.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ services:
2929
accessTokenRequiredScope: access_as_user
3030
redis:
3131
dbNumber: 14
32-
passwordFile: /etc/osmo/mcp-auth/redis-password
3332
keyPrefix: test:mcp-auth
3433
existingSecret:
3534
name: mcp-oidc-proxy-secrets

deployments/charts/service/tests/render-tests.sh

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ for forbidden in 'name: osmo-mcp' 'cluster: osmo-mcp' 'path: /mcp'; do
9393
done
9494

9595
mcp_render=$(helm template mcp-test "$CHART_DIR" --values "$mcp_values")
96-
mcp_workload=$(helm template mcp-test "$CHART_DIR" --values "$mcp_values" \
97-
--show-only templates/mcp-service.yaml)
96+
mcp_workload=$(awk '/^# Source: /{keep = ($3 ~ /templates\/mcp-service\.yaml$/)} keep' \
97+
<<<"$mcp_render")
9898

9999
# FastMCP advertises its OAuth endpoints under /mcp; the gateway publishes that
100100
# prefix and rewrites it off before forwarding to the root paths the MCP SDK
@@ -111,8 +111,9 @@ mcp_route() {
111111
' <<<"$mcp_render"
112112
}
113113

114-
grep -q 'prefix: /mcp/' <<<"$(mcp_route mcp-oauth)"
115-
grep -q 'prefix_rewrite: /$' <<<"$(mcp_route mcp-oauth)"
114+
oauth_route=$(mcp_route mcp-oauth)
115+
grep -q 'prefix: /mcp/' <<<"$oauth_route"
116+
grep -q 'prefix_rewrite: /$' <<<"$oauth_route"
116117
grep -q 'prefix_rewrite: /.well-known/oauth-authorization-server' \
117118
<<<"$(mcp_route mcp-authorization-server-metadata)"
118119

@@ -173,14 +174,23 @@ grep -q 'value: "/var/run/mcp/redis-password"' <<<"$mount_render"
173174
grep -q 'mountPath: /var/run/mcp' <<<"$mount_render"
174175

175176
# The MCP audience is added to the provider for its issuer, so no deployment
176-
# writes a second provider differing only in audience.
177+
# writes a second provider differing only in audience. Scoped to the audiences
178+
# list: the resource URL also renders as the service's own env var.
179+
jwt_audiences() {
180+
awk '
181+
/^ *audiences:/ { found = 1; match($0, /^ */); depth = RLENGTH; next }
182+
found && /^ *- / && index($0, "- ") == depth + 1 { print $2; next }
183+
found { found = 0 }
184+
' <<<"$1"
185+
}
186+
177187
aud_render=$(helm template mcp-aud "$CHART_DIR" --values "$mcp_values" \
178188
--set 'gateway.envoy.jwt.providers[0].audience=some-client-id')
179-
grep -q -- '- some-client-id' <<<"$aud_render"
180-
grep -q -- '- https://osmo.example.com/mcp' <<<"$aud_render"
189+
grep -qx 'some-client-id' <<<"$(jwt_audiences "$aud_render")"
190+
grep -qx 'https://osmo.example.com/mcp' <<<"$(jwt_audiences "$aud_render")"
181191

182192
# A provider already carrying that audience must not have it added twice.
183-
dupes=$(grep -c -- '- https://osmo.example.com/mcp' <<<"$mcp_render" || true)
193+
dupes=$(grep -cx 'https://osmo.example.com/mcp' <<<"$(jwt_audiences "$mcp_render")" || true)
184194
if [ "$dupes" -ne 1 ]; then
185195
echo "MCP audience appears $dupes times on the gateway provider, want 1" >&2
186196
exit 1

docs/deployment_guide/advanced_config/mcp.rst

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ routes, and an ingress NetworkPolicy. The recommended authentication mode passes
2727
FastMCP's built-in ``OIDCProxy`` directly to the existing MCP server; it does
2828
not deploy a second OAuth broker service.
2929

30-
Authentication Modes
31-
====================
32-
33-
OSMO supports two MCP authentication modes:
30+
Authentication
31+
==============
3432

3533
.. list-table::
3634
:header-rows: 1
@@ -72,7 +70,7 @@ Before enabling MCP:
7270
Configure OIDC Proxy Mode
7371
=========================
7472

75-
OIDC proxy mode provides endpoint-only client setup. The deployment owns one
73+
The OIDC proxy provides endpoint-only client setup. The deployment owns one
7674
confidential upstream OIDC application. Individual MCP clients do not need to
7775
configure its client ID and never receive its client secret.
7876

@@ -171,24 +169,18 @@ to the deployment:
171169
replicas: 1
172170
resourceUrl: https://osmo.example.com/mcp
173171
oidcProxy:
174-
enabled: true
175172
oidc:
176173
configUrl: https://idp.example.com/.well-known/openid-configuration
177174
clientId: <confidential-oidc-client-id>
178-
clientSecretFile: /etc/osmo/mcp-auth/client-secret
179-
accessTokenIssuer: https://issuer.example.com/
180-
accessTokenRequiredScope: access_as_user
181-
redis:
182-
dbNumber: 0
183-
keyPrefix: osmo:mcp-fastmcp
184-
passwordFile: /etc/osmo/mcp-auth/redis-password
185175
existingSecret:
186176
name: osmo-mcp-oidc
187-
mountPath: /etc/osmo/mcp-auth
188-
clientSecretKey: client-secret
189-
redisPasswordKey: redis-password
190177
191-
Blank OIDC proxy Redis host and port values inherit ``services.redis``.
178+
The secret file paths follow ``existingSecret.mountPath``, and Redis is
179+
inherited from ``services.redis``, so neither is stated. Set
180+
``oidc.accessTokenIssuer`` only for a provider whose access tokens come from an
181+
issuer its discovery document does not advertise, as an application configured
182+
for v1-format tokens does. Name ``existingSecret.redisPasswordKey`` only when
183+
that Redis requires a password.
192184
Native clients normally omit ``Origin`` and need no extra configuration. For a
193185
browser-hosted MCP client, ``services.mcp.allowedOrigins`` controls which
194186
browser origins may call ``/mcp``.
@@ -258,7 +250,7 @@ For either mode, verify protected-resource metadata:
258250
$ curl --fail --silent --show-error \
259251
https://osmo.example.com/.well-known/oauth-protected-resource/mcp
260252
261-
In OIDC proxy mode, also verify authorization-server metadata:
253+
Also verify authorization-server metadata:
262254

263255
.. code-block:: bash
264256
@@ -273,11 +265,9 @@ login and run the read-only verification in :ref:`getting_started_mcp`.
273265
Provide Connection Details
274266
==========================
275267

276-
For OIDC proxy mode, give users only the MCP URL. Clients discover scopes from
277-
the proxy metadata, and the proxy accepts native loopback redirects
278-
automatically.
279-
280-
:ref:`getting_started_mcp`.
268+
Give users only the MCP URL. Clients discover scopes from the proxy metadata,
269+
and the proxy accepts native loopback redirects automatically. Client setup is
270+
covered in :ref:`getting_started_mcp`.
281271

282272
Operate OIDC Proxy Safely
283273
=========================
@@ -301,8 +291,8 @@ The public proxy surface is the protected-resource and authorization-server
301291
metadata documents plus everything under ``/mcp``, where FastMCP serves
302292
``authorize``, ``token``, ``register``, ``consent`` and the callback. Gateway
303293
bypasses its own JWT and semantic authorization filters only for that prefix
304-
and the two metadata documents in proxy mode. FastMCP authenticates ``/mcp``; all ``/api`` calls keep
305-
normal Gateway validation and authorization.
294+
and the two metadata documents. FastMCP authenticates ``/mcp``; all ``/api``
295+
calls keep normal Gateway validation and authorization.
306296

307297
.. _mcp_deployment_troubleshooting:
308298

@@ -318,15 +308,14 @@ Troubleshooting
318308
* - Metadata returns ``404`` or unexpected values
319309
- Verify that ``services.mcp.enabled`` is true, DNS points to this
320310
release's Gateway, and ``resourceUrl`` ends with the exact path
321-
``/mcp``. Also verify the
322-
authorization-server metadata route.
311+
``/mcp``. Also verify the authorization-server metadata route.
323312
* - MCP pod does not become ready
324313
- Inspect configuration and credential-file errors first. The client
325314
secret and optional Redis password must exist at the configured absolute
326315
paths.
327316
* - Browser reports a redirect mismatch or no reply address
328-
- Register exact ``https://<osmo-host>/mcp/auth/callback`` on the confidential
329-
upstream application. For Entra, use the Web platform for this
317+
- Register exact ``https://<osmo-host>/mcp/auth/callback`` on the
318+
confidential upstream application. For Entra, use the Web platform for this
330319
server-side client.
331320
* - Browser reports ``Approval required``
332321
- Grant administrator consent and assign the intended users or groups to

0 commit comments

Comments
 (0)