Skip to content

bug(operator): VirtualMCPServer inline config.telemetry silently ignored; GET /metrics returns MCP 406 #6276

Description

@RaviTharuma

Bug description

On operator-managed VirtualMCPServer (observed on v0.42.1), setting:

spec:
  config:
    telemetry:
      enablePrometheusMetricsPath: true

is accepted by the API and stored on the CR, but the operator does not
render it into the vmcp ConfigMap
. The running process never registers a
Prometheus handler on /metrics.

GET /metrics with Accept: text/plain (or Prometheus default) then falls
through to the MCP streamable-HTTP handler and returns:

HTTP/1.1 406 Not Acceptable
Content-Type: application/json

{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Not Acceptable: Client must accept text/event-stream"}}

That makes Prometheus ServiceMonitors look broken even though the CR “enabled”
metrics. This blocked collecting Go runtime memory series requested on #5860.

Root cause (code)

cmd/thv-operator/pkg/vmcpconfig/converter.go normalizeTelemetry only reads
spec.telemetryConfigRefMCPTelemetryConfig. Inline spec.config.telemetry
returns nil after #4819 (“Remove deprecated inline telemetry field from CRDs”),
while:

  • the CRD still exposes spec.config.telemetry (embedded vmcp config type)
  • docs still said the inline field “still works” for VirtualMCPServer
  • there is no status condition or validation error when inline is set without a ref

When config.Telemetry is nil at serve time, pkg/vmcp/server/server.go skips
mux.Handle("/metrics", …) and the MCP catch-all owns the path.

Steps to reproduce

  1. Install operator + CRDs v0.42.1 (or current main with the ignore behavior).

  2. Create a VirtualMCPServer with only inline:

    apiVersion: toolhive.stacklok.dev/v1beta1
    kind: VirtualMCPServer
    metadata:
      name: demo
    spec:
      groupRef:
        name: <existing-group>
      incomingAuth:
        type: anonymous
      config:
        telemetry:
          enablePrometheusMetricsPath: true
  3. Inspect the operator-managed ConfigMap (*-vmcp-config): no telemetry: key.

  4. curl -H 'Accept: text/plain' http://<vmcp-svc>:4483/metrics406 JSON-RPC as above.

  5. curl http://<vmcp-svc>:4483/health200 {"status":"ok"} (health is registered).

Expected behavior

Either:

  1. Documented preferred path works and is discoverable: requiring
    spec.telemetryConfigRef + MCPTelemetryConfig is fine, but inline must
    not be a silent no-op (status Warning condition and/or validation), and docs
    must not claim inline still works for the operator; or
  2. Deprecated inline continues to apply until removed from the CRD, with a
    deprecation log / condition pointing at telemetryConfigRef.

When Prometheus path is actually enabled, GET /metrics should return
Prometheus text (HTTP 200), not MCP 406.

Actual behavior

Silent ignore of inline telemetry → no /metrics route → MCP 406 on scrape.
No condition on the VirtualMCPServer warns that telemetry was dropped.

Environment

  • ToolHive operator + vmcp image: v0.42.1
  • Single-node Kubernetes
  • Four VirtualMCPServers (backend counts ~12–17); no private hostnames in this report

Workaround

Use the shared config resource:

apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPTelemetryConfig
metadata:
  name: vmcp-prometheus
spec:
  prometheus:
    enabled: true
---
apiVersion: toolhive.stacklok.dev/v1beta1
kind: VirtualMCPServer
metadata:
  name: demo
spec:
  telemetryConfigRef:
    name: vmcp-prometheus
    serviceName: demo
  # ...groupRef, incomingAuth, etc.

Related

Proposed fix

Restore deprecated inline fallback when TelemetryConfigRef is unset (ref
still wins), log a migration warning, fix docs, and cover with unit tests.
PR forthcoming.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions