Telemetry Migration Guide

February 12, 2026 · View on GitHub

This guide covers the migration from ToolHive's legacy telemetry attribute names to the new names that align with the OTEL MCP semantic conventions and the OTEL HTTP semantic conventions.

For the complete metrics and attributes reference, see the Observability and Telemetry documentation and the Virtual MCP Server Observability documentation.


What Changed

ToolHive's telemetry has been updated across two areas:

  1. Span attribute names — Renamed to follow OTEL semantic conventions (HTTP, RPC, MCP/gen_ai namespaces).
  2. New metrics — Two new histogram metrics following the OTEL MCP spec: mcp.server.operation.duration and mcp.client.operation.duration.

Existing metrics (toolhive_mcp_requests, toolhive_mcp_request_duration, toolhive_mcp_tool_calls, toolhive_mcp_active_connections, and all toolhive_vmcp_* metrics) are unchanged — their names and label names remain the same.

What Is New

AdditionDescription
mcp.server.operation.duration metricOTEL MCP spec histogram for server-side operation latency
mcp.client.operation.duration metricOTEL MCP spec histogram for vMCP-to-backend latency
MCP _meta trace context propagationExtract/inject traceparent/tracestate from MCP params._meta
MCP request parsing middlewareDedicated middleware extracts method, resource ID, arguments, and _meta
--otel-custom-attributes flagAdd custom resource attributes to all telemetry signals
--otel-env-vars flagInclude host environment variables in spans
--otel-use-legacy-attributes flagControl legacy attribute dual emission
OTLP header credential redactionConfig.String() / Config.GoString() redact header values

Backward Compatibility

The useLegacyAttributes Flag

To avoid breaking existing dashboards and alerts, ToolHive uses a dual emission strategy:

SettingBehavior
useLegacyAttributes: true (current default)Emits both legacy and new attribute names on every span
useLegacyAttributes: falseEmits only new OTEL semantic convention attribute names

Deprecation timeline:

  • Current release: Default is true. Both old and new attributes emitted.
  • Future release: Default will change to false. Legacy attributes still available but opt-in.
  • Later release: Legacy attributes removed entirely.

How to Set the Flag

CLI:

thv run --otel-use-legacy-attributes=false ...

Configuration file (~/.toolhive/config.yaml):

otel:
  use-legacy-attributes: false

Kubernetes CRD (MCPServer):

spec:
  openTelemetry:
    useLegacyAttributes: false

Kubernetes CRD (VirtualMCPServer):

spec:
  config:
    telemetry:
      useLegacyAttributes: false

Attribute Name Mapping

HTTP Request Attributes

Legacy NameNew NameNotes
http.methodhttp.request.methodRenamed for clarity
http.urlurl.fullMoved to url.* namespace
http.schemeurl.schemeMoved to url.* namespace
http.hostserver.addressRenamed per OTEL spec
http.targeturl.pathMoved to url.* namespace
http.user_agentuser_agent.originalRenamed per OTEL spec
http.request_content_lengthhttp.request.body.sizeRenamed; type changed string → int64
http.queryurl.queryMoved to url.* namespace

HTTP Response Attributes

Legacy NameNew NameNotes
http.status_codehttp.response.status_codeNamespaced under http.response.*
http.response_content_lengthhttp.response.body.sizeRenamed
http.duration_ms(removed)Duration is captured in histogram metrics; no span attribute replacement

MCP Protocol Attributes

Legacy NameNew NameNotes
mcp.methodmcp.method.nameAdded .name suffix per OTEL convention
rpc.systemrpc.system.nameOTEL deprecated rpc.system
rpc.service(removed)Value was always "mcp"; redundant
mcp.request.idjsonrpc.request.idMoved to jsonrpc.* namespace
mcp.resource.idmcp.resource.uriRenamed to reflect URI semantics; now only set for resource methods

Tool and Prompt Attributes

Legacy NameNew NameNotes
mcp.tool.namegen_ai.tool.nameMoved to gen_ai.* namespace per OTEL MCP semconv
mcp.tool.argumentsgen_ai.tool.call.argumentsMoved to gen_ai.* namespace
mcp.prompt.namegen_ai.prompt.nameMoved to gen_ai.* namespace

Transport Attributes

Legacy NameNew NameNotes
mcp.transportnetwork.transport + network.protocol.nameSplit into standard OTEL network attributes

Mapping of mcp.transport values to new attributes:

mcp.transport valuenetwork.transportnetwork.protocol.name
"stdio""pipe"(empty)
"sse""tcp""http"
"streamable-http""tcp""http"

Attributes With No Legacy Equivalent (New Only)

These attributes are new and have no legacy predecessor:

AttributeWhen SetDescription
jsonrpc.protocol.versionMCP requestsAlways "2.0"
gen_ai.operation.nametools/callAlways "execute_tool"
mcp.backend.protocol.versionSSE transportBackend protocol version
network.protocol.versionHTTP requestsHTTP protocol version (1.1, 2)
error.typeHTTP 5xx errorsHTTP status code as string
mcp.session.idStreamable HTTPFrom Mcp-Session-Id header
mcp.protocol.versionStreamable HTTPFrom MCP-Protocol-Version header
mcp.client.nameinitializeClient name from clientInfo
mcp.is_batchBatch requestsBatch request indicator
client.addressAll requestsClient IP address
client.portAll requestsClient port
sse.event_typeSSE connectionsAlways "connection_established"
environment.{VAR}If configuredHost environment variable values

Migration Steps

Step 1: Upgrade with Defaults (No Action Required)

When upgrading to this release, dual emission is enabled by default. Both old and new attribute names appear on spans. Your existing dashboards and alerts continue to work without changes.

Step 2: Adopt New Metrics (Optional)

Consider adopting the new spec-compliant metrics alongside your existing ones:

# Existing metric (unchanged)
rate(toolhive_mcp_requests_total{mcp_method="tools/call"}[5m])

# New spec-compliant metric for operation duration
histogram_quantile(0.95,
  rate(mcp_server_operation_duration_seconds_bucket{
    mcp_method_name="tools/call"
  }[5m])
)

Step 3: Update Trace Queries

Update any trace queries (Jaeger, Tempo, Datadog, etc.) that filter on legacy attribute names:

# Before
http.method = "POST" AND mcp.method = "tools/call" AND mcp.tool.name = "fetch"

# After
http.request.method = "POST" AND mcp.method.name = "tools/call" AND gen_ai.tool.name = "fetch"

Step 4: Update Dashboard Panels

For Grafana dashboards that visualize span attributes, update the attribute references using the mapping tables above. You can run both old and new queries side-by-side during migration to verify equivalence.

Step 5: Disable Legacy Attributes

Once all dashboards, alerts, and queries have been migrated:

thv run --otel-use-legacy-attributes=false ...

Or in config.yaml:

otel:
  use-legacy-attributes: false

This reduces span size and improves performance by eliminating duplicate attributes.


Metric Label Changes

Important: The metric label names on existing toolhive_mcp_* and toolhive_vmcp_* metrics have not changed. The useLegacyAttributes flag only affects span attributes (trace data), not metric labels.

The new mcp.server.operation.duration and mcp.client.operation.duration metrics use OTEL MCP semantic convention attribute names exclusively (e.g., mcp.method.name instead of mcp_method).


vMCP Backend Client Attributes

The vMCP backend client (pkg/vmcp/server/telemetry.go) emits both ToolHive-specific and OTEL spec attributes on spans. These are always emitted regardless of useLegacyAttributes since they serve different purposes:

ToolHive-Specific (always emitted)OTEL Spec (always emitted)Description
target.workload_idBackend workload ID
target.workload_nameBackend workload name
target.base_urlBackend base URL
target.transport_typeBackend transport type
actionmcp.method.nameAction / MCP method
tool_namegen_ai.tool.nameTool name (for call_tool)
resource_urimcp.resource.uriResource URI (for read_resource)
prompt_namegen_ai.prompt.namePrompt name (for get_prompt)

The mcp.client.operation.duration metric uses only mcp.method.name and network.transport as labels (plus error.type on error), following the OTEL MCP semantic conventions.


Known Limitations

  • error.type is HTTP-only: Currently set only for HTTP 5xx errors. JSON-RPC error codes (e.g., -32601) returned in HTTP 200 responses are not yet captured. Tracked in #3765.
  • mcp.server.session.duration not implemented: The OTEL MCP spec recommends this metric. Tracked in #3764.
  • rpc.response.status_code not implemented: Requires response body parsing. Tracked in #3765.