Skip to content

vMCP backend init fails fatally when server advertises resources capability without resources/list #5231

@tgrunnagle

Description

@tgrunnagle

Bug description

vMCP's per-session backend bootstrap (initAndQueryCapabilities in pkg/vmcp/session/internal/backend/mcp_session.go) treats any error from resources/list (and by symmetry prompts/list) as fatal. If a backend advertises the resources capability in its initialize response but does not actually implement resources/list, vMCP refuses to initialise the backend at all and silently drops every tool from that backend out of tools/list.

The ergonomic outcome: a user goes through the entire OAuth chain successfully, then has zero visibility that anything failed — the backend's tools just don't appear.

We've hit this against Atlassian's hosted Rovo MCP server (https://mcp.atlassian.com/v1/mcp), which advertises capabilities.resources in its initialize response but responds to resources/list with JSON-RPC -32601 Method not found. The same pattern is plausible for any third-party MCP server we don't control — we shouldn't require server-side spec compliance to use a backend at all.

Steps to reproduce

  1. Configure a VirtualMCPServer with at least one backend that advertises resources in its initialize response but doesn't implement resources/list. (Atlassian Rovo via MCPServerEntry + upstreamInject is one real-world example.)
  2. Complete the OAuth flow for the upstream so the user has a valid token for that backend.
  3. Open an MCP session against vMCP.

Expected behavior

The backend initialises successfully and its tools appear in tools/list. Resources/prompts are absent (because the backend doesn't actually offer any), and a WARN log records that the backend advertised the capability but didn't honour the list method.

Actual behavior

vMCP logs:

{\"level\":\"WARN\",\"msg\":\"Failed to initialise backend for session; continuing without it\",\"backendID\":\"atlassian-rovo\",\"backendName\":\"atlassian-rovo\",\"error\":\"failed to initialise backend atlassian-rovo: list resources failed: method not found: Method not found\"}

The backend's tools never appear in the session. The user has no UI signal that anything went wrong.

The fatal error originates here:

The doubled "method not found: Method not found" string is from mcp-go's JSONRPCErrorDetails.AsError() wrapping the sentinel mcp.ErrMethodNotFound ("method not found") with the upstream message ("Method not found"). Practical implication: callers can detect this case via errors.Is(err, mcp.ErrMethodNotFound).

Suggested fix

In initAndQueryCapabilities, treat mcp.ErrMethodNotFound from resources/list and prompts/list as "the backend has no resources/prompts" rather than as a fatal init error. Tools is intentionally left strict: a backend whose tools/list is unavailable is not useful to surface at all.

Sketch:

if serverCaps.Resources != nil {
    resResult, listErr := c.ListResources(ctx, mcp.ListResourcesRequest{})
    switch {
    case errors.Is(listErr, mcp.ErrMethodNotFound):
        slog.Warn(\"backend advertised resources capability but does not implement resources/list\",
            \"backendID\", target.WorkloadID)
    case listErr != nil:
        return nil, fmt.Errorf(\"list resources failed: %w\", listErr)
    default:
        for _, r := range resResult.Resources { /* existing append */ }
    }
}
// same shape for prompts/list

Acceptance criteria

  • When a backend advertises capabilities.resources in initialize but resources/list returns JSON-RPC error -32601 (mcp.ErrMethodNotFound), the backend initialises successfully with an empty resource set; its tools are reachable via tools/list on the vMCP session.
  • Same behaviour for prompts/list: a -32601 from a backend that advertised prompts does not abort backend init.
  • tools/list failures remain fatal (a backend with no working tool surface is not useful to expose).
  • Any non--32601 error from resources/list or prompts/list (timeout, transport error, other JSON-RPC error codes) still aborts init — we are not silencing arbitrary failures, only the specific spec-violation case where the server contradicts its own advertised capabilities.
  • A WARN-level log line records the spec violation, including the backend ID and which list method was missing, so operators can surface a bug to the upstream server author. Do not log at ERROR — this is a recoverable, expected-in-the-wild condition.
  • Unit tests in pkg/vmcp/session/internal/backend/ cover:
    • resources/list returns -32601 after server advertised resources → init succeeds, no resources, WARN logged
    • prompts/list returns -32601 after server advertised prompts → init succeeds, no prompts, WARN logged
    • tools/list returns -32601 → init still fails (regression guard)
    • resources/list returns a non--32601 JSON-RPC error → init still fails (regression guard)
  • Manual verification with the Atlassian Rovo MCP server (https://mcp.atlassian.com/v1/mcp) wired up via MCPServerEntry + upstreamInject: after OAuth completes, tools/list on the vMCP session returns the workload-prefixed Atlassian tools (e.g. atlassian-rovo_<tool>).

Environment

  • ToolHive version: main (reproduced on the gateway-5fdf997cdc-* image running in arn:aws:eks:us-east-1:781189302813:cluster/rovo-demo3-toolhive-dev)
  • mcp-go: v0.49.0

Additional context

  • The same fragility class is worth a quick audit elsewhere in the session bootstrap: any place that derives "the server supports method X" from the initialize response and then unconditionally calls X. prompts/list is the obvious sibling and is included above. Subscriptions / completions paths are worth a glance.
  • This is purely a vMCP-side fix; we cannot wait for upstream MCP servers to become spec-compliant before users can call their tools through vMCP.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggoPull requests that update go codevmcpVirtual MCP Server related issues

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions