Summary
The proxyMode configuration field (CLI --proxy-mode, API/CRD proxyMode) causes user confusion and has led to bugs in workload discovery across vMCP, Optimizer, and UI (Playground). The field's name and behavior don't match, requiring clients to implement complex logic to determine the actual protocol in use.
Problem Statement
Current Behavior (Validated)
-
proxyMode is ONLY intended for transport=stdio
- Purpose: Tells the stdio transport which HTTP proxy protocol to use (sse or streamable-http)
- Documented in:
pkg/runner/config.go:149-151, cmd/thv-operator/api/v1alpha1/mcpserver_types.go:61-66
-
proxyMode can be set on non-stdio transports, but has no effect
- CLI, CRD, and API accept proxyMode on any transport type
- Non-stdio transports (sse, streamable-http) completely ignore the setting
- Code:
pkg/transport/factory.go:51 - only stdio transport receives proxyMode
-
The stored proxyMode value is only meaningful if transport=stdio
- For stdio: proxyMode indicates the actual HTTP protocol
- For sse/streamable-http: proxyMode may be empty, set incorrectly, or misleading
-
proxyMode has inconsistent defaults and may be absent
- Most of codebase defaults to
streamable-http
- CONFLICT:
pkg/transport/stdio.go:124 defaults to sse for "backward compatibility"
- Field is optional, requiring defensive checks when reading
Client Logic Required
Clients (vMCP, Optimizer, UI) must implement this logic to determine the actual protocol:
IF transport == stdio THEN
use proxyMode (or default to streamable-http)
ELSE
use transport type as the protocol
This logic is implemented in GetEffectiveProxyMode() in pkg/workloads/types/types.go:114-125, but every client must know to use it.
Root Causes of Confusion
1. Misleading Name
"proxyMode" suggests it controls how the proxy operates, but it actually means "HTTP protocol for stdio transports only". The name doesn't reflect its limited scope.
2. Input Accepts Invalid Combinations
Users can configure transport=sse, proxyMode=streamable-http without any warning that proxyMode is being ignored.
3. Conflicting Defaults
- Most defaults:
streamable-http (CLI, CRD, API, URL generation)
- StdioTransport internal default:
sse (stdio.go:124)
This creates undefined behavior when proxyMode is not explicitly set.
4. Effective vs. Stored Values
When reading a workload with transport=sse, the stored proxyMode might say something completely different, because it's only meaningful for stdio.
Impact
- User Confusion: Discord/GitHub questions about when to use proxyMode
- Discovery Bugs: vMCP, Optimizer, and UI (Playground) must implement client-side logic to determine actual protocol
- API Inconsistency: Returned data doesn't directly represent the running configuration
- Maintenance Burden: Every new client must understand and implement the effective-mode calculation
Proposed Solution
Make proxyMode always reflect the actual HTTP protocol the proxy is using.
Semantics Change
Current (confusing):
# stdio workload
transport: stdio
proxyMode: streamable-http # Meaningful input: tells proxy which protocol to use
# sse workload
transport: sse
proxyMode: "" # Empty, ignored, or misleading
# Client must calculate: if stdio then use proxyMode else use transport
Proposed (clear):
# stdio workload
transport: stdio
proxyMode: streamable-http # The actual protocol in use
# sse workload
transport: sse
proxyMode: sse # ALWAYS populated with actual protocol
# streamable-http workload
transport: streamable-http
proxyMode: streamable-http # ALWAYS matches reality
# Clients can always use proxyMode - it's the source of truth
Benefits
- Clear semantics:
proxyMode = "what HTTP protocol is this workload actually using?"
- No client logic: Clients can use
proxyMode directly without conditional logic
- Consistent representation: Same meaning across API responses, CRD status, workload listings
- Name matches behavior: "proxy mode" actually reflects the mode the proxy is operating in
Implementation Strategy
- Normalize at API boundaries: When returning workloads (API responses, CRD status), always populate
proxyMode using GetEffectiveProxyMode()
- Keep backward compatibility: Input still allows empty proxyMode for stdio, but output always populates it
- Update documentation: Clarify that proxyMode is always the effective protocol
- Optional: Deprecation path: Consider renaming in a future major version to
protocol or proxyProtocol
Implementation Considerations
Breaking Changes?
- API responses: proxyMode will always be populated (previously could be empty)
- CRD status: Same as API
- Backward compatibility: Old configs without proxyMode still work (normalized on read)
Migration
- Existing workloads: No migration needed - normalization happens at read time
- Client code: Clients can simplify by removing conditional logic
Code Changes Needed
- Normalize proxyMode in API handlers when returning workload data
- Populate proxyMode in CRD status
- Update vMCP backend discovery to use proxyMode directly
- Update Optimizer to use proxyMode directly
- Update UI/Playground to use proxyMode directly
- Fix conflicting default in
pkg/transport/stdio.go:124 (sse → streamable-http)
- Consider validation warning when user sets proxyMode on non-stdio transports
References
Key Code Locations
Definition:
pkg/runner/config.go:149-151 - ProxyMode in RunConfig
pkg/transport/types/transport.go:239-249 - ProxyMode type
Setting:
cmd/thv/app/run_flags.go:129-132 - CLI flag
cmd/thv-operator/api/v1alpha1/mcpserver_types.go:61-66 - CRD field
pkg/api/v1/workload_types.go:59 - API field
Usage:
pkg/transport/factory.go:51 - Only passed to stdio transport
pkg/transport/stdio.go:181-202 - Stdio switches on proxyMode
pkg/workloads/types/types.go:114-125 - GetEffectiveProxyMode calculates actual protocol
pkg/vmcp/workloads/k8s.go:197 - vMCP uses effective mode
Defaults:
pkg/transport/stdio.go:124 - ⚠️ Defaults to SSE (conflicts with everything else)
cmd/thv/app/run_flags.go:131 - Defaults to streamable-http
cmd/thv-operator/api/v1alpha1/mcpserver_types.go:64 - Defaults to streamable-http
pkg/api/v1/workload_service.go:116 - Defaults to streamable-http
Test Coverage
pkg/workloads/types/effective_transport_test.go - Tests GetEffectiveProxyMode
pkg/transport/url_test.go - Tests URL generation with proxyMode
Recommendation
Implement the proposed solution to:
- Eliminate user confusion about when/how to use proxyMode
- Simplify client code in vMCP, Optimizer, and UI
- Make the data model match the actual runtime behavior
- Fix the conflicting default in stdio.go:124
Summary
The
proxyModeconfiguration field (CLI--proxy-mode, API/CRDproxyMode) causes user confusion and has led to bugs in workload discovery across vMCP, Optimizer, and UI (Playground). The field's name and behavior don't match, requiring clients to implement complex logic to determine the actual protocol in use.Problem Statement
Current Behavior (Validated)
proxyMode is ONLY intended for
transport=stdiopkg/runner/config.go:149-151,cmd/thv-operator/api/v1alpha1/mcpserver_types.go:61-66proxyMode can be set on non-stdio transports, but has no effect
pkg/transport/factory.go:51- only stdio transport receives proxyModeThe stored proxyMode value is only meaningful if
transport=stdioproxyMode has inconsistent defaults and may be absent
streamable-httppkg/transport/stdio.go:124defaults tossefor "backward compatibility"Client Logic Required
Clients (vMCP, Optimizer, UI) must implement this logic to determine the actual protocol:
This logic is implemented in
GetEffectiveProxyMode()inpkg/workloads/types/types.go:114-125, but every client must know to use it.Root Causes of Confusion
1. Misleading Name
"proxyMode" suggests it controls how the proxy operates, but it actually means "HTTP protocol for stdio transports only". The name doesn't reflect its limited scope.
2. Input Accepts Invalid Combinations
Users can configure
transport=sse, proxyMode=streamable-httpwithout any warning that proxyMode is being ignored.3. Conflicting Defaults
streamable-http(CLI, CRD, API, URL generation)sse(stdio.go:124)This creates undefined behavior when proxyMode is not explicitly set.
4. Effective vs. Stored Values
When reading a workload with
transport=sse, the storedproxyModemight say something completely different, because it's only meaningful for stdio.Impact
Proposed Solution
Make
proxyModealways reflect the actual HTTP protocol the proxy is using.Semantics Change
Current (confusing):
Proposed (clear):
Benefits
proxyMode= "what HTTP protocol is this workload actually using?"proxyModedirectly without conditional logicImplementation Strategy
proxyModeusingGetEffectiveProxyMode()protocolorproxyProtocolImplementation Considerations
Breaking Changes?
Migration
Code Changes Needed
pkg/transport/stdio.go:124(sse → streamable-http)References
Key Code Locations
Definition:
pkg/runner/config.go:149-151- ProxyMode in RunConfigpkg/transport/types/transport.go:239-249- ProxyMode typeSetting:
cmd/thv/app/run_flags.go:129-132- CLI flagcmd/thv-operator/api/v1alpha1/mcpserver_types.go:61-66- CRD fieldpkg/api/v1/workload_types.go:59- API fieldUsage:
pkg/transport/factory.go:51- Only passed to stdio transportpkg/transport/stdio.go:181-202- Stdio switches on proxyModepkg/workloads/types/types.go:114-125- GetEffectiveProxyMode calculates actual protocolpkg/vmcp/workloads/k8s.go:197- vMCP uses effective modeDefaults:
pkg/transport/stdio.go:124-cmd/thv/app/run_flags.go:131- Defaults to streamable-httpcmd/thv-operator/api/v1alpha1/mcpserver_types.go:64- Defaults to streamable-httppkg/api/v1/workload_service.go:116- Defaults to streamable-httpTest Coverage
pkg/workloads/types/effective_transport_test.go- Tests GetEffectiveProxyModepkg/transport/url_test.go- Tests URL generation with proxyModeRecommendation
Implement the proposed solution to: