Skip to content

proxyMode configuration is confusing and inconsistent #3296

@danbarr

Description

@danbarr

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)

  1. 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
  2. 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
  3. 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
  4. 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

  1. Clear semantics: proxyMode = "what HTTP protocol is this workload actually using?"
  2. No client logic: Clients can use proxyMode directly without conditional logic
  3. Consistent representation: Same meaning across API responses, CRD status, workload listings
  4. Name matches behavior: "proxy mode" actually reflects the mode the proxy is operating in

Implementation Strategy

  1. Normalize at API boundaries: When returning workloads (API responses, CRD status), always populate proxyMode using GetEffectiveProxyMode()
  2. Keep backward compatibility: Input still allows empty proxyMode for stdio, but output always populates it
  3. Update documentation: Clarify that proxyMode is always the effective protocol
  4. 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

  1. Normalize proxyMode in API handlers when returning workload data
  2. Populate proxyMode in CRD status
  3. Update vMCP backend discovery to use proxyMode directly
  4. Update Optimizer to use proxyMode directly
  5. Update UI/Playground to use proxyMode directly
  6. Fix conflicting default in pkg/transport/stdio.go:124 (sse → streamable-http)
  7. 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:

  1. Eliminate user confusion about when/how to use proxyMode
  2. Simplify client code in vMCP, Optimizer, and UI
  3. Make the data model match the actual runtime behavior
  4. Fix the conflicting default in stdio.go:124

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiItems related to the APIcliChanges that impact CLI functionalityenhancementNew feature or requestgoPull requests that update go codeoperatorproxy

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions