vMCP health probe uses HTTP GET, breaking backends that only accept POST (e.g. Tableau MCP)
@jstar0 is already working on this.
Since Sep 4, 2026.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Bug description
VirtualMCPServer health probes use HTTP GET against backend URLs and treat non-200 responses as unhealthy. However, in the MCP Streamable HTTP transport, GET is defined as an optional mechanism for opening server-initiated SSE streams. Servers that do not support SSE streams may legitimately return 405 Method Not Allowed (per spec) or 400 Bad Request (e.g. when session management is enabled and no session ID is provided). A GET failure therefore does not imply that the MCP backend is unavailable.
The backend is then marked unavailable and excluded from tool routing, even though POST-based MCP calls (initialize, ping, tools/list, tools/call) all succeed.
Root cause: vMCP is using a transport-level GET response as a backend health signal, although GET semantics are optional/use-case-specific in Streamable HTTP and do not indicate whether the backend can process MCP JSON-RPC requests.
This is distinct from #6339 (probing unadvertised capabilities) — here the probe method itself produces a false-negative health signal.
Steps to reproduce
- Deploy Tableau MCP (
ghcr.io/tableau/tableau-mcp) as a standalone Deployment withTRANSPORT=http, listening on port 3927 at path/tableau-mcp. - Register it via
MCPServerEntrywithremoteUrl: http://tableau-mcp.<ns>.svc.cluster.local:3927/tableau-mcpandtransport: streamable-http. - Observe
VirtualMCPServerstatus:
$ kubectl get virtualmcpserver mcphub -o jsonpath='{.status.discoveredBackends[?(@.name=="tableau-mcp")]}'
{"name":"tableau-mcp","status":"unavailable","message":"Backend unavailable"}
- Verify the backend is actually healthy:
# MCP ping — succeeds (no authentication or session required)
curl -X POST http://tableau-mcp:3927/tableau-mcp \
-H "accept: application/json, text/event-stream" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"ping"}'
# → {"jsonrpc":"2.0","id":"1","result":{}}
# HTTP GET — rejected (this is what the health probe sends)
curl http://tableau-mcp:3927/tableau-mcp
# → HTTP 400: "Invalid or missing session ID"
The flow:
vMCP health probe
│
│ GET /tableau-mcp
▼
Tableau MCP → 400 (no session ID on GET)
│
└─→ vMCP marks "unavailable"
└─→ tools/list excludes Tableau tools
But actual MCP communication works:
MCP Client
│
│ POST /tableau-mcp {"method":"ping"}
▼
Tableau MCP → 200 + {"result":{}}
- Tools from this backend do not appear in the vMCP's
tools/listresponse to clients.
Why GET fails on this backend
In Streamable HTTP, a server that supports GET uses it to open an SSE stream for server-initiated messages. A server that does not support SSE streams returns 405 Method Not Allowed per the MCP spec. Tableau MCP returns 400 because it requires a session ID (issued during initialize) for all requests, and a bare GET without a session context is meaningless.
Both 405 and 400 are legitimate responses from a healthy backend that simply does not offer SSE streams or requires session context.
Expected behavior
The health probe should use an MCP-level check (e.g. POST with {"jsonrpc":"2.0","id":"healthcheck","method":"ping"}) rather than HTTP GET. The MCP specification defines ping as a standard utility method that requires no authentication or session, making it the natural health check primitive.
Alternatively, allow per-backend health probe configuration (method, path, or disable).
Actual behavior
The health probe sends GET to the backend URL. Backends that legitimately reject GET (no SSE support, or session-managed endpoints) return 400/405, and vMCP marks them unavailable, blocking all tool routing to that backend.
Workaround
None that preserves correct health semantics. An nginx sidecar returning 200 on GET masks real failures and defeats the purpose of health checking.
Environment
- ToolHive: v0.46.0 (operator + CRDs)
- Kubernetes: v1.33.7
- Backend:
ghcr.io/tableau/tableau-mcp:latest(v4.7.1) - Resource type:
MCPServerEntry(also reproducible withMCPRemoteProxy)
References
- MCP Streamable HTTP spec — GET returns
405 Method Not Allowedwhen SSE is not supported: https://modelcontextprotocol.io/specification/draft/basic/transports/streamable-http - Tableau MCP deployment guide — GET returns "Method not allowed" as expected behavior, recommends
pingfor health checks: https://github.com/tableau/tableau-mcp/blob/main/docs/docs/enterprise/tableau-cloud.md - Related: #6339 (vMCP marks backends degraded for capabilities they never advertised)
- Related: #4826 (CIMD upstream support, which would allow using hosted
mcp.tableau.comdirectly)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.