Skip to content

Commit 8a53d46

Browse files
ChrisJBurnsclaude
andcommitted
Address Jhrozek review feedback on RFC-0055
- Generalize mode selection guide to cover all auth types, not just token exchange - Reword rule of thumb to use "fronted by vMCP" vs "standalone" framing - Replace ASCII auth flow diagrams with mermaid sequence diagrams - Clarify dual-consumer auth behavior per mode - Remove implementation-level token lifetime detail - Add groupRef rationale inline comment - Replace name collision fallback with admission-time rejection - Replace CEL oidcConfig rule with standard kubebuilder Required marker Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c7afeab commit 8a53d46

File tree

1 file changed

+54
-27
lines changed

1 file changed

+54
-27
lines changed

rfcs/THV-0055-mcpremoteendpoint-unified-remote-backends.md

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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
195207
arriving 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

206226
vMCP 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
256273
type 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

Comments
 (0)