Enable multi-upstream for MCPServer, MCPRemoteProxy, and proxy runner#4322
Enable multi-upstream for MCPServer, MCPRemoteProxy, and proxy runner#4322
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4322 +/- ##
==========================================
- Coverage 68.86% 68.66% -0.21%
==========================================
Files 479 479
Lines 48505 48619 +114
==========================================
- Hits 33404 33385 -19
- Misses 12347 12359 +12
- Partials 2754 2875 +121 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Question: Should there be some validation at the proxy runner and remote proxy controllers that restricts upstream providers to length = 1? |
Move multi-upstream restrictions from the authserver library to consumer layers. The library now accepts multi-upstream configs but enforces name semantics: single-upstream defaults empty names to "default", while multi-upstream requires explicit non-"default" names with distinct error messages for empty vs reserved names. Validate upstream names against a DNS-label regex (no leading/trailing hyphens, lowercase alphanumeric only) to prevent delimiter injection in storage keys. Add test coverage for invalid name formats (uppercase, underscores, leading/trailing hyphens). Remove the GetUpstream() convenience method (no callers remain after Phase 2). Add a cardinality guard in the proxy runner's Run() that rejects len(Upstreams) > 1 with an actionable error pointing to VirtualMCPServer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the len > 1 guard from MCPExternalAuthConfig.validateEmbeddedAuthServer() so the CRD accepts multi-upstream configs. Add multi-upstream rejection to MCPServer and MCPRemoteProxy controllers in handleExternalAuthConfig(), setting a ConditionFalse status with reason MultiUpstreamNotSupported and an actionable error directing users to VirtualMCPServer. Add duplicate upstream name validation in the CRD webhook so conflicts are caught at admission time rather than Pod startup. Tighten the Name field pattern to disallow trailing hyphens and add MaxLength=63 for RFC 1123 compliance. VirtualMCPServer remains unrestricted as the intended multi-upstream consumer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The embedded auth server has supported sequential multi-upstream chains since the Phase 2 work, but every consumer layer (MCPServer controller, MCPRemoteProxy controller, proxy runner) blocked configs with more than one upstream provider. This commit lifts all those restrictions and fixes the two bugs that would have broken multi-upstream even without the guards. Guard removal: - MCPServer controller: remove len > 1 check in handleExternalAuthConfig - MCPRemoteProxy controller: same removal - Proxy runner Run(): remove len > 1 early-return - Remove associated condition type/reason constants and rejection tests Converter fix (authserver.go): - GenerateAuthServerEnvVars now iterates all providers and emits name-keyed env vars (TOOLHIVE_UPSTREAM_CLIENT_SECRET_OKTA, _GITHUB, …) derived from each provider's Name field, instead of only reading UpstreamProviders[0] into a single unindexed var. Name-keyed bindings are position-independent, so reordering providers in the CRD does not change the secret-to-provider mapping. - buildEmbeddedAuthServerRunnerConfig now builds Upstreams from all providers instead of only the first; buildUpstreamRunConfig gains an envVarName parameter to match the env var naming Middleware fix (middleware.go): - Restore ProviderName auto-derivation in addUpstreamSwapMiddleware, defaulting to the first upstream's name for single-upstream configs. Multi-upstream ProviderName selection will be addressed when a CRD field is added in a follow-up. Observability (upstreamswap/middleware.go): - Add provider field to all upstreamswap log lines (injection success, empty-token warning, get-tokens error) to make confused-deputy debugging tractable when multiple providers are in play. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract validateUpstreamName and validateUpstreamType from validateUpstreams to bring cyclomatic complexity under the limit. Break long fmt.Errorf line that exceeded 130 characters. Update stale godoc comments in controllerutil/authserver.go that still referenced the old index-based env var naming scheme. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client-attributable errors (session not found, no refresh token, refresh failed, invalid binding) are expected conditions that trigger re-auth, not server issues an operator needs to investigate. Log these at Debug instead of Warn; keep Warn for unexpected server-side failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Don't we want to extend to 1 or 2? I'd like to enable the multi-hop through corporate IDPs. You are right though that we should add that as a separate commit with separate tests and docs. I'm adding a new patch that re-adds the guards. |
f86c222 to
0c9ef62
Compare
The upstream swap middleware only injects one provider's token, and there is no CRD field to control which provider. Until tokenInjection- Provider is added and Cedar upstream claims extraction is implemented, multi-upstream configs would silently inject only the first upstream's token while the second is stored but unused. Re-add controller-level guards (MCPServer, MCPRemoteProxy, proxy runner) that reject len > 1 with actionable errors directing users to VirtualMCPServer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0c9ef62 to
cdf5377
Compare
Summary
The embedded auth server has supported sequential multi-upstream authorization chains since Phase 2, but every consumer layer (MCPServer controller, MCPRemoteProxy controller, proxy runner) blocked configs with more than one upstream provider. This PR lifts those restrictions and fixes the env var naming scheme to support multiple providers correctly.
TOOLHIVE_UPSTREAM_CLIENT_SECRETto name-keyedTOOLHIVE_UPSTREAM_CLIENT_SECRET_<PROVIDER>, making bindings position-independent across provider reorderingFixes #4138
Type of change
Test plan
task test)task lint-fix)Changes
cmd/thv-operator/api/v1alpha1/mcpexternalauthconfig_types.gocmd/thv-operator/pkg/controllerutil/authserver.goupstreamSecretBinding, multi-provider iterationpkg/authserver/config.goGetUpstream()pkg/auth/upstreamswap/middleware.godeploy/charts/operator-crds/docs/operator/crd-api.mdSpecial notes for reviewers
upstreamSecretBindingstruct is the single source of truth for env var naming, shared betweenGenerateAuthServerEnvVars(Pod env) andbuildUpstreamRunConfig(runtime config) to prevent desync.Name(uppercased, hyphens→underscores). Since the CRD regex forbids underscores, the transform is injective — no two valid names can produce the same suffix.Upstreams[0]for now; explicit per-backendProviderNameselection is addressed in the follow-up branch (authserver-multi-upstream/4-tokens-in-identity).Generated with Claude Code