Skip to content

Allow MCPRemoteProxy to work without upstream or client auth#4168

Draft
aron-muon wants to merge 3 commits intostacklok:mainfrom
aron-muon:aron/mcpremoteproxy-optional-auth
Draft

Allow MCPRemoteProxy to work without upstream or client auth#4168
aron-muon wants to merge 3 commits intostacklok:mainfrom
aron-muon:aron/mcpremoteproxy-optional-auth

Conversation

@aron-muon
Copy link
Contributor

@aron-muon aron-muon commented Mar 16, 2026

Summary

MCPRemoteProxy required OIDC authentication and always injected upstream tokens when the
embedded auth server was configured. This made two common deployment patterns impossible
and the transparent proxy could not reach upstream servers behind HTTP redirects.

Why: Operators sometimes need to proxy third-party public MCP servers (e.g., documentation APIs)
with client authentication (Okta, GitHub) but without injecting tokens upstream — the upstream
is public and doesn't expect auth headers. Additionally, third-party servers may use HTTP
redirects (API gateways, scheme changes, path canonicalization) that the proxy must follow
transparently.

What changed (3 commits):

  1. Add disableUpstreamTokenInjection — when true, the embedded auth server still
    handles OAuth flows for clients but does not inject upstream IdP tokens into outgoing
    requests. A strip-auth middleware removes the client's ToolHive JWT from the
    Authorization header to prevent it leaking to the upstream.

  2. Make oidcConfig optional — when omitted, the proxy allows anonymous access with no
    authentication on either side.

  3. Fix transparent proxy for remote servers behind redirects — three issues prevented
    MCPRemoteProxy from connecting to third-party upstreams that use redirects:

    • X-Forwarded-Host leaked the proxy's hostname; the upstream used it to build 307
      redirect URLs pointing back to the proxy (redirect loop). Fix: skip SetXForwarded()
      for remote upstreams.
    • Go's http.Transport.RoundTrip does not follow redirects, but httputil.ReverseProxy
      uses Transport directly. Fix: add forwardFollowingRedirects that follows up to 10
      redirects, preserving method/body for 307/308 (RFC 7538).
    • Adds debug logging for outbound request headers and upstream response status codes.

Type of change

  • New feature
  • Bug fix

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Deployed MCPRemoteProxy with embedded auth server + disableUpstreamTokenInjection: true
proxying a public third-party MCP server behind an API gateway that 307-redirects
(HTTPS→HTTP scheme change). Verified:

  • Client OAuth flow via Okta completes successfully
  • Proxy follows upstream 307 redirects transparently (logged at WARN)
  • MCP initialize returns 200 from the final upstream destination
  • tools/list, prompts/list, resources/list all return 200
  • Cedar authorization evaluates correctly against ToolHive JWTs
  • Authorization header is stripped before forwarding to the public upstream

Changes

File Change
api/v1alpha1/mcpexternalauthconfig_types.go Add DisableUpstreamTokenInjection to EmbeddedAuthServerConfig CRD
pkg/authserver/config.go Add DisableUpstreamTokenInjection to runtime RunConfig
pkg/runner/middleware.go Add strip-auth middleware; skip upstream swap when injection disabled
pkg/runner/middleware_test.go Tests for strip-auth and upstream swap middleware selection
controllerutil/authserver.go Wire CRD field through to RunConfig
api/v1alpha1/mcpremoteproxy_types.go Make OIDCConfig optional (value to pointer)
controllers/mcpremoteproxy_controller.go Add nil guard for OIDC validation
controllers/mcpremoteproxy_deployment.go Add nil guard for OIDC env vars
controllers/mcpremoteproxy_runconfig.go Extract auth config helper, skip OIDC when nil
proxy/transparent/transparent_proxy.go Skip SetXForwarded() for remote upstreams; add forwardFollowingRedirects; add outbound/response debug logging

Does this introduce a user-facing change?

Yes. Three new capabilities for MCPRemoteProxy:

  1. Anonymous proxy: Omit oidcConfig to allow unauthenticated access to a proxied MCP server.
  2. Embedded auth without upstream injection: Set disableUpstreamTokenInjection: true on an embeddedAuthServer MCPExternalAuthConfig to authenticate clients without injecting tokens upstream.
  3. Transparent redirect following: The proxy now follows HTTP 3xx redirects from upstream servers automatically, enabling proxying of MCP servers behind API gateways or CDNs that redirect.

Large PR Justification

  • ~700 of the changed lines are generated code (CRD manifests, deepcopy, swagger docs) that cannot be split from the source changes
  • The remaining hand-written code includes the three tightly coupled features (all address the same gap: MCPRemoteProxy for third-party public MCP servers) plus required test updates for the value-to-pointer type change

Generated with Claude Code

@github-actions github-actions bot added the size/S Small PR: 100-299 lines changed label Mar 16, 2026
@aron-muon aron-muon changed the title draft Allow MCPRemoteProxy to work without upstream or client auth Mar 16, 2026
@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from e7fec58 to 1c8c7cd Compare March 16, 2026 13:08
@github-actions github-actions bot removed the size/S Small PR: 100-299 lines changed label Mar 16, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@github-actions github-actions bot added the size/XL Extra large PR: 1000+ lines changed label Mar 16, 2026
@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 42.74809% with 75 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.40%. Comparing base (6bb814d) to head (49e9690).

Files with missing lines Patch % Lines
...g/transport/proxy/transparent/transparent_proxy.go 31.14% 36 Missing and 6 partials ⚠️
pkg/runner/middleware.go 22.72% 15 Missing and 2 partials ⚠️
...-operator/controllers/mcpremoteproxy_deployment.go 59.09% 2 Missing and 7 partials ⚠️
...v-operator/controllers/mcpremoteproxy_runconfig.go 65.00% 3 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4168      +/-   ##
==========================================
- Coverage   68.95%   68.40%   -0.55%     
==========================================
  Files         467      467              
  Lines       46981    47130     +149     
==========================================
- Hits        32395    32239     -156     
- Misses      11965    12072     +107     
- Partials     2621     2819     +198     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from 0aa7f12 to 68e79e9 Compare March 17, 2026 14:23
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 17, 2026
@github-actions github-actions bot dismissed their stale review March 17, 2026 14:24

Large PR justification has been provided. Thank you!

@github-actions
Copy link
Contributor

✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review.

@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from 68e79e9 to 1037865 Compare March 17, 2026 14:35
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 17, 2026
@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from 1037865 to 5b110c0 Compare March 17, 2026 14:35
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 17, 2026
@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from 5b110c0 to 010f7fc Compare March 17, 2026 14:49
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 17, 2026
aron-muon and others added 3 commits March 17, 2026 16:30
The embedded auth server always injected upstream IdP tokens into
requests forwarded to backend MCP servers. This made it impossible
to use the embedded auth server for client-facing OAuth flows when
the upstream MCP server is public and doesn't require authentication
— the injected token caused 401 rejections from the upstream.

Add a `disableUpstreamTokenInjection` field to EmbeddedAuthServerConfig
that skips the upstream swap middleware while keeping the embedded auth
server running for client authentication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MCPRemoteProxy previously required oidcConfig, making it impossible
to proxy public MCP servers without configuring authentication.

Make the oidcConfig field optional — when omitted, the proxy allows
anonymous access and forwards requests to the upstream without any
authentication on either side. This enables "case 1" where both the
upstream MCP and the proxy are fully public.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues prevented MCPRemoteProxy from connecting to third-party
upstream MCP servers that use HTTP redirects:

1. X-Forwarded-Host leaked the proxy's hostname to the upstream. The
   upstream used it to construct 307 redirect URLs pointing back to
   the proxy, creating a redirect loop. Fix: skip SetXForwarded() for
   remote upstreams (isRemote == true).

2. Go's http.Transport.RoundTrip does not follow redirects, but
   httputil.ReverseProxy uses Transport directly. Upstream 307/308
   redirects (e.g. HTTPS→HTTP scheme changes, path canonicalization)
   were returned to the MCP client which cannot follow them through
   the proxy. Fix: add forwardFollowingRedirects that transparently
   follows up to 10 redirects, preserving method and body for
   307/308 (RFC 7538).

3. When disableUpstreamTokenInjection is true, the client's ToolHive
   JWT was still forwarded to the upstream in the Authorization
   header. Fix: add strip-auth middleware that removes the
   Authorization header before forwarding.

Also adds debug logging for outbound request headers and upstream
response status codes to aid diagnosis of remote proxy issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aron-muon aron-muon force-pushed the aron/mcpremoteproxy-optional-auth branch from 010f7fc to 49e9690 Compare March 17, 2026 16:31
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant