@@ -96,16 +96,17 @@ should be a configuration choice within it.
9696| Scenario | Recommended Mode | Why |
9797| ---| ---| ---|
9898| Public, unauthenticated remote (e.g., context7) | ` direct ` | No auth middleware needed; no pod required |
99- | Remote requiring only token exchange auth | ` direct ` | vMCP handles token exchange ; one fewer hop |
99+ | Remote with outgoing auth handled by vMCP ( token exchange, header injection, etc.) | ` direct ` | vMCP applies outgoing auth directly ; one fewer hop |
100100| Remote requiring its own OIDC validation boundary | ` proxy ` | Proxy pod validates tokens independently |
101101| Remote requiring Cedar authz policies per-endpoint | ` proxy ` | Authz policies run in the proxy pod |
102102| Remote needing audit logging at the endpoint level | ` proxy ` | Proxy pod has its own audit middleware |
103103| Standalone use without VirtualMCPServer | ` proxy ` | Direct mode requires vMCP to function |
104104| Many remotes where pod-per-remote is too costly | ` direct ` | No Deployment/Service/Pod per remote |
105105
106- ** Rule of thumb:** Use ` direct ` for simple, public, or token-exchange-only
107- remotes. Use ` proxy ` when you need an independent auth/authz/audit boundary
108- per remote, or when the backend needs to be accessible standalone.
106+ ** Rule of thumb:** Use ` direct ` for simple, public remotes or any remote
107+ fronted by vMCP where vMCP handles outgoing auth. Use ` proxy ` when you need an
108+ independent auth/authz/audit boundary per remote, or when the backend needs to
109+ be accessible standalone.
109110
110111## Proposed Solution
111112
@@ -173,12 +174,21 @@ graph TB
173174
174175** ` type: proxy ` — two independent auth legs:**
175176
176- ```
177- Client --[aud=vmcp token]--> vMCP [validates token at incoming boundary]
178- --[externalAuthConfigRef credential]--> Proxy Pod
179- [proxy pod oidcConfig validates the incoming request]
180- [proxy pod applies externalAuthConfigRef as outgoing middleware]
181- --> Remote Server
177+ ``` mermaid
178+ sequenceDiagram
179+ participant C as Client
180+ participant V as vMCP
181+ participant P as Proxy Pod
182+ participant R as Remote Server
183+
184+ C->>V: Request (aud=vmcp token)
185+ V->>V: Validate incoming token
186+ V->>P: Forward (externalAuthConfigRef credential)
187+ P->>P: oidcConfig validates incoming request
188+ P->>R: Forward (externalAuthConfigRef as outgoing middleware)
189+ R-->>P: Response
190+ P-->>V: Response
191+ V-->>C: Response
182192```
183193
184194` externalAuthConfigRef ` on a ` type: proxy ` endpoint is read by two separate
@@ -191,16 +201,26 @@ consumers:
191201 (` AddExternalAuthConfigOptions() ` in ` mcpremoteproxy_runconfig.go ` ). The pod
192202 applies it as outgoing middleware when forwarding requests ** to the remote server** .
193203
204+ In direct mode, only consumer 1 applies — there is no proxy pod.
205+
194206` proxyConfig.oidcConfig ` is a third, separate concern — it validates tokens
195207arriving at the proxy pod from vMCP. It is entirely independent of
196208` externalAuthConfigRef ` .
197209
198210** ` type: direct ` — single auth boundary:**
199211
200- ```
201- Client --[aud=vmcp token]--> vMCP [validates token at incoming boundary]
202- [vMCP applies externalAuthConfigRef as outgoing auth]
203- --> Remote Server
212+ ``` mermaid
213+ sequenceDiagram
214+ participant C as Client
215+ participant V as vMCP
216+ participant R as Remote Server
217+
218+ C->>V: Request (aud=vmcp token)
219+ V->>V: Validate incoming token
220+ V->>V: Apply externalAuthConfigRef as outgoing auth
221+ V->>R: Request (with outgoing credentials)
222+ R-->>V: Response
223+ V-->>C: Response
204224```
205225
206226vMCP reads ` externalAuthConfigRef ` and applies it when calling the remote
@@ -213,9 +233,6 @@ client's token.
213233- The STS must be configured to accept subject tokens from vMCP's IdP.
214234- Configure ` audience ` in the ` MCPExternalAuthConfig ` to match the remote
215235 server's expected audience claim.
216- - Client token lifetime should exceed the expected duration of the exchange
217- request. Exchanged tokens are managed by the ` golang.org/x/oauth2 ` token
218- source and refreshed automatically on expiry per connection.
219236
220237** Unsupported ` externalAuthConfigRef ` types for ` type: direct ` :**
221238
@@ -255,10 +272,12 @@ The four rules for `MCPRemoteEndpoint`, placed on their correct owning types:
255272// nolint:lll
256273type MCPRemoteEndpointSpec struct { ... }
257274
258- // MCPRemoteEndpointProxyConfig struct-level rule:
259- //
260- // +kubebuilder:validation:XValidation:rule="has(self.oidcConfig)",message="spec.proxyConfig.oidcConfig is required"
261- type MCPRemoteEndpointProxyConfig struct { ... }
275+ // MCPRemoteEndpointProxyConfig — oidcConfig uses standard required marker:
276+ type MCPRemoteEndpointProxyConfig struct {
277+ // +kubebuilder:validation:Required
278+ OIDCConfig OIDCConfigRef ` json:"oidcConfig"`
279+ // ...
280+ }
262281```
263282
264283** Important:** The ` oldSelf == null ` guard is required so the immutability rule
@@ -292,7 +311,8 @@ spec:
292311 # +kubebuilder:validation:Enum=streamable-http;sse
293312 transport : streamable-http
294313
295- # REQUIRED: Group membership.
314+ # REQUIRED: Group membership. MCPRemoteEndpoint only functions as part of
315+ # an MCPGroup (aggregated by VirtualMCPServer), so groupRef is always required.
296316 groupRef : engineering-team
297317
298318 # OPTIONAL: Auth for outgoing requests to the remote server.
@@ -589,11 +609,18 @@ Extend `ListWorkloadsInGroup()` and `GetWorkloadAsVMCPBackend()` in
589609- ` type: proxy ` — uses ` status.url ` (proxy Service URL), same as MCPRemoteProxy
590610- ` type: direct ` — uses ` spec.remoteURL ` directly
591611
592- ** Name collision handling:** ` fetchBackendResource() ` in
593- ` pkg/vmcp/k8s/backend_reconciler.go ` tries resources in order: MCPServer →
594- MCPRemoteProxy → MCPRemoteEndpoint. Same-name resources across types in the
595- same namespace always resolve to the first match. Log a warning when a
596- collision is detected.
612+ ** Name collision prevention:** The MCPRemoteEndpoint controller MUST reject
613+ creation if an MCPServer or MCPRemoteProxy with the same name already exists in
614+ the namespace, setting ` ConfigurationValid=False ` with reason
615+ ` NameCollision ` . Likewise, the MCPServer and MCPRemoteProxy controllers MUST
616+ be updated to reject collisions with MCPRemoteEndpoint. This prevents
617+ surprising fallback behaviour where deleting one resource type silently
618+ activates a different resource with the same name.
619+
620+ ` fetchBackendResource() ` in ` pkg/vmcp/k8s/backend_reconciler.go ` retains its
621+ existing resolution order (MCPServer → MCPRemoteProxy → MCPRemoteEndpoint) as
622+ a defensive fallback, but the admission-time rejection above makes same-name
623+ collisions a user error rather than an implicit resolution policy.
597624
598625##### vMCP: HTTP Client for Direct Mode
599626
0 commit comments