Skip to content

Derive the MCP proxy configuration and share OSMO's Redis - #1329

Open
jiaenren wants to merge 1 commit into
jiaenr/mcp-oauth-under-mcp-pathfrom
jiaenr/mcp-derive-auth-config
Open

Derive the MCP proxy configuration and share OSMO's Redis#1329
jiaenren wants to merge 1 commit into
jiaenr/mcp-oauth-under-mcp-pathfrom
jiaenr/mcp-derive-auth-config

Conversation

@jiaenren

@jiaenren jiaenren commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Stack (6): #1328#1329#1350#1335#1342#1347

Stacked on #1328.

Issue - None

+151 / -257. Three reductions to what a deployer must state, none of which changes what the deployment does. Consolidates the former #1329, #1330 and #1331.

Values the chart already knew

accessTokenAudience had to equal resourceUrl, scope had to equal resourceUrl plus the scope name, and the issuer had to be resourceUrl minus /mcp — and the chart refused to render unless each matched what it had already computed. A deployer was asked for an answer the chart held, then rejected for giving a different one.

They are derived now, so the 30-line cross-checking block in MCPAuthConfig, the matching Helm fail blocks, the three environment variables carrying the restated values, and the three CI cases that existed only to prove the cross-checks fired all go.

This also drops trustedHttpsRedirectOrigins, which no deployment sets and which cost a CSV environment variable, two properties, a validator branch and a Helm regex loop. Native clients use loopback redirects, still allowed.

A URL discovery already publishes

accessTokenJwksUrl is jwks_uri in every OIDC discovery document, which FastMCP already fetches at startup. OSMO hand-built a JWTVerifier only to pin it, and carried a process-lifetime httpx client to serve it.

OIDCProxy builds the verifier now, and OSMO overrides only what discovery cannot supply: an Entra resource application configured for v1 access tokens issues them from https://sts.windows.net/<tenant>/ even when its discovery document advertises the v2.0 issuer, so accessTokenIssuer stays configured.

The audience is applied inside that override rather than through OIDCProxy's own audience argument, because that argument is forwarded to the provider's authorize and token endpoints (oidc_proxy.py:432-434), which Entra does not accept. An existing test asserting that no extra token parameters are sent caught this.

Redis that every sibling already shares

MCP carried its own host, port and TLS settings. serviceName and port fell back to services.redis but tlsEnabled did not, so a deployment whose Redis requires TLS could satisfy every other service and still CrashLoop MCP. All three come from services.redis now; only dbNumber stays, to isolate proxy state from OSMO's other Redis users.

The chart also refused more than one MCP replica, on the premise that FastMCP serializes token refresh within a process. FastMCP says the opposite — "All state is stored in the configured client_storage backend (Redis, disk, etc.) enabling horizontal scaling across multiple instances" (oauth_proxy/proxy.py:212-213), with :1903-1912 handling the distributed refresh race explicitly. The rule also contradicted itself, since sharing state through Redis only matters across replicas it forbade.

Required OIDC proxy values: 10 → 6.

Migration note

Environment-variable deployments are unaffected. A stale --config file carrying issuer_url, auth_scope, oidc_access_token_audience or oidc_access_token_jwks_url will now fail at startup with an unrecognized-key error rather than being ignored.

Verification

  • bazel test //src/service/mcp/... //test/smoke/... — 76/76 pass at this commit
  • render-tests.sh — pass at this commit
  • The replica ban's negative test is replaced by a positive assertion that two replicas render, negative-tested by reintroducing a ban

🤖 Generated with Claude Code

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@jiaenren
jiaenren requested a review from a team as a code owner August 26, 2026 01:54
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e340d33b-7632-42f6-9536-14995318322f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Three reductions to what a deployer must state, none of which changes
what the deployment does.

**Values the chart already knew.** accessTokenAudience had to equal
resourceUrl, scope had to equal resourceUrl plus the scope name, and the
issuer had to be resourceUrl minus /mcp -- and the chart refused to
render unless each matched what it had already computed. A deployer was
asked for an answer the chart held, then rejected for giving a different
one. They are derived now, so the 30-line cross-checking block in
MCPAuthConfig, the matching Helm fail blocks, the three environment
variables carrying the restated values, and the three CI cases that
existed only to prove the cross-checks fired all go.

This also drops trustedHttpsRedirectOrigins, which no deployment sets and
which cost a CSV environment variable, two properties, a validator branch
and a Helm regex loop. Native clients use loopback redirects, still
allowed.

**A URL discovery publishes.** accessTokenJwksUrl is jwks_uri in every
OIDC discovery document, which FastMCP already fetches at startup. OSMO
hand-built a JWTVerifier only to pin it, and carried a process-lifetime
httpx client to serve it. OIDCProxy builds the verifier now, and OSMO
overrides only what discovery cannot supply: an Entra resource
application configured for v1 access tokens issues them from
https://sts.windows.net/<tenant>/ even when its discovery document
advertises the v2.0 issuer, so accessTokenIssuer stays configured.

The audience is applied inside that override rather than through
OIDCProxy's own audience argument, because that argument is forwarded to
the provider's authorize and token endpoints (oidc_proxy.py:432-434),
which Entra does not accept. An existing test asserting that no extra
token parameters are sent caught this.

**Redis that every sibling already shares.** MCP carried its own host,
port and TLS settings. serviceName and port fell back to services.redis
but tlsEnabled did not, so a deployment whose Redis requires TLS could
satisfy every other service and still CrashLoop MCP. All three come from
services.redis now; only dbNumber stays, to isolate proxy state from
OSMO's other Redis users, and its default moves off 0 for that reason.

The chart also refused more than one MCP replica, on the premise that
FastMCP serializes token refresh within a process. FastMCP says the
opposite -- "All state is stored in the configured client_storage
backend (Redis, disk, etc.) enabling horizontal scaling across multiple
instances" (oauth_proxy/proxy.py:212-213), with :1903-1912 handling the
distributed refresh race explicitly. The rule also contradicted itself,
since sharing state through Redis only matters across replicas it
forbade. The negative test asserting the ban is replaced by a positive
one that two replicas render, negative-tested by reintroducing a ban.

Required OIDC proxy values: 10 -> 6.

Note for anyone using --config rather than environment variables: a stale
file carrying issuer_url, auth_scope, oidc_access_token_audience or
oidc_access_token_jwks_url now fails at startup with an unrecognized-key
error rather than being ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

@jiaenren jiaenren changed the title Derive the MCP OIDC proxy configuration from the resource URL Derive the MCP proxy configuration and share OSMO's Redis Aug 28, 2026
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.55%. Comparing base (81f9c92) to head (26bfd8e).

Additional details and impacted files
@@                         Coverage Diff                         @@
##           jiaenr/mcp-oauth-under-mcp-path    #1329      +/-   ##
===================================================================
+ Coverage                            74.54%   74.55%   +0.01%     
===================================================================
  Files                                  243      243              
  Lines                                29942    29925      -17     
  Branches                              4538     4533       -5     
===================================================================
- Hits                                 22320    22312       -8     
+ Misses                                6718     6713       -5     
+ Partials                               904      900       -4     
Flag Coverage Δ
backend 77.40% <100.00%> (+0.01%) ⬆️
ui 38.07% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/service/mcp/auth.py 88.23% <100.00%> (+5.88%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant