Telemetry reference (semantic conventions)

mcpgw v1.x emits OpenTelemetry SemConv schema URL https://opentelemetry.io/schemas/1.26.0 and the experimental mcp.* namespace defined here. Mappings to OTel GenAI conventions track upstream stabilization; expect breaking attribute name changes only across major versions.

This page is the reference. For setup, see How-to: enable Datadog tracing. For the wire transport, see Configuration: telemetry.


Span names

Spans are named mcp.<method> with slashes replaced by dots.

JSON-RPC method Span name
initialize mcp.initialize
tools/list mcp.tools.list
tools/call mcp.tools.call
resources/list mcp.resources.list
resources/read mcp.resources.read
prompts/list mcp.prompts.list
ping mcp.ping

The pattern is mechanical: replace / with . and prepend mcp..

Span kind

Direction Kind
Inbound request from MCP client SERVER
Outbound forward to upstream MCP server CLIENT

Each request produces one SERVER span (the gateway-side) and, if the request reached the upstream, one CLIENT child span.


Attributes

Set on every span unless noted otherwise.

Attribute Type Example Notes
mcp.method string tools/call Original JSON-RPC method, with the slash.
mcp.tool.name string fs_read Empty string for non-tools/call methods.
mcp.session.id string 01HXYZ... From the Mcp-Session-Id header; empty if absent.
mcp.protocol.version string 2026-07-28 Resolved protocol revision (_meta wins over the MCP-Protocol-Version header). Set only when the client declared a version. Constant: telemetry.AttrProtocolVersion.
mcp.task.handle string 786512e2-... Tasks-extension taskId (SEP-2663) — set when a response mints a task or a tasks/* request polls one. Correlates a logical task across RPCs. Constant: telemetry.AttrTaskHandle.
mcp.transport string http+sse See enum below.
mcp.upstream string filesystem Logical name from config. Never the URL.
mcp.policy.decision string allow See enum below.
mcp.policy.rule_id string deny-shell Set only when a rule fired.
mcp.auth.key_id string claude-desktop-prod Set when auth.enabled and authentication succeeds via the API-key path.
mcp.auth.result string ok Set when auth.enabled. See mcp.auth.result enum below.
mcp.auth.oauth_client_id string my-agent OAuth client_id (or azp fallback) of the verified Bearer token. Set on OAuth path only. Constant: telemetry.AttrOAuthClientID.
mcp.auth.scopes string mcp:read mcp:write Space-separated scopes from the verified token. Set on OAuth path only. See audit-log.md for the same field on the audit envelope. Constant: telemetry.AttrAuthScopes.
mcp.auth.oauth_client_name string Claude Desktop Human-readable client name resolved from CIMD document. Set on OAuth path when CIMD is enabled and the fetch succeeded. Constant: telemetry.AttrOAuthClientName.
mcp.payload.bytes_in int 312 Length of the incoming JSON-RPC request body.
mcp.payload.bytes_out int 1024 Length of the response body. Success spans only.
mcp.error.code int -32001 Error spans only. JSON-RPC error code.
mcp.error.kind string upstream_unreachable Error spans only — upstream/SSE failures. See enum below.
mcp.license.grace bool true Set only when the gateway is operating in the license grace window.
mcp.synthesized string tools_list, tools_call_search Set on synthesized responses to indicate which path produced them. tools_list for intercepted tools/list responses; tools_call_search for intercepted tools/call mcp_search responses. Only present when tool_search.mode: synthesize is active. Constant: telemetry.AttrSynthesized.
mcp.toolsearch.hits int 5 Number of tools returned by a mcp_search call. Set on tools_call_search synthesized spans. Constant: telemetry.AttrToolSearchHits.
mcp.frames.total int 5 Total SSE frames seen by the inspector (envelopes + opaque). Use mcp.frames.denied and mcp.frames.redacted to compute the policy-evaluated subset. Set on the proxy span for SSE responses. Constant: telemetry.AttrFramesTotal.
mcp.frames.denied int 1 Number of SSE frames dropped by deny or rate_limit rules. Subset of mcp.frames.total. Constant: telemetry.AttrFramesDenied.
mcp.frames.redacted int 2 Number of SSE frames whose data: bytes were rewritten by redact rules. Subset of mcp.frames.total. Constant: telemetry.AttrFramesRedacted.
mcp.direction string server_to_client Direction of the policy evaluation path. Set to "server_to_client" on the proxy span when the upstream response was an SSE stream. Also present on per-frame SSE audit lines. See enum below. Constant: telemetry.AttrDirection.
mcp.app.served int 3 Number of UI blocks forwarded unmodified to the client. Set on tools/call spans when the upstream response contained at least one UI block and no strip_app rule removed it. 0 or absent means no UI blocks were present or all were stripped. Constant: telemetry.AttrAppServed.
mcp.app.stripped int 2 Number of UI blocks removed by a strip_app action. Set on tools/call spans when a strip_app rule fired. 0 means the rule matched but found no UI blocks to remove. Absent when no strip_app rule fired. Constant: telemetry.AttrAppStripped.
mcp.guardrail.name string pii-scanner Guardrail endpoint behind a matched guardrail rule. Set when a webhook callout was attempted, in either direction. On a dual-direction request the response-direction value overwrites the request-direction one (last write wins). Constant: telemetry.AttrGuardrailName.
mcp.guardrail.verdict string pass Webhook verdict. See enum below. Absent on a fail-closed callout failure, where mcp.error.kind = guardrail_unreachable carries the signal. Constant: telemetry.AttrGuardrailVerdict.
mcp.guardrail.latency_ms int 4 Wall time of the webhook Check call alone (never conflated with upstream latency). Constant: telemetry.AttrGuardrailLatency.

Enum values

mcp.transport

Value Description
http+sse HTTP request with SSE streaming response
sse Server-Sent Events transport
stdio-bridge Request arrived via the mcpgw stdio bridge
http Plain HTTP request, no SSE

mcp.policy.decision

Value Condition
allow No rule fired, or a matching rule had action: allow
deny A rule with action: deny matched
redact A rule with action: redact matched and the payload was modified
rate_limit_blocked A rate-limit rule matched and the bucket was empty
strip_app A strip_app rule matched; UI blocks (if any) were removed from the response
guardrail A guardrail rule matched; the webhook verdict is in mcp.guardrail.verdict

mcp.error.kind

The span attribute is set only on upstream, streaming, and guardrail-callout failures. (Pre-parse, auth, policy, and rate-limit outcomes are recorded in the audit error_kind field and the mcp.policy.decision attribute, not here.)

Value Condition
upstream_invalid Gateway could not build the upstream request
upstream_unreachable Connect to upstream failed / no response
upstream_read Error reading the upstream response
upstream_too_large Upstream response exceeded the 16 MiB cap
sse_truncated SSE response cut mid-stream (e.g. a frame exceeded max_sse_frame_bytes)
guardrail_unreachable Guardrail webhook callout failed (non-200, timeout, malformed verdict) with fail_open: false; the request was denied

mcp.guardrail.verdict

Value Condition
pass The webhook returned pass; the envelope continues unchanged
mask The webhook returned mask + body; the mutated body is forwarded (audit action redact)
reject The webhook returned reject; the request is denied (-32001 policy_denied)
error_failopen The callout failed and the endpoint has fail_open: true; the envelope continues

The webhook’s reason string is never recorded on spans (operator-facing only, via gateway debug logs).

mcp.direction

Value Description
client_to_server Policy evaluated on an inbound request from a client.
server_to_client Policy evaluated on an SSE frame from an upstream server.

mcp.auth.result

Value Condition
ok Request authenticated successfully (API-key or OAuth path)
missing No configured auth header was present
invalid Header was present but did not match a configured key (API-key path), or JWT signature/format was rejected
expired Header matched a configured key whose expires_at is in the past (API-key path)
bad_audience JWT aud claim did not match auth.oauth.audience
bad_issuer JWT iss claim did not match auth.oauth.issuer
insufficient_scope JWT was valid but lacked a scope listed in auth.oauth.required_scopes

Resource attributes

Set on the OTel Resource (every span carries them).

Attribute Source Example
service.name Enabled customer exporter, otherwise enabled operator exporter; blank falls back to mcpgw mcpgw
service.version gateway version 1.0.0
telemetry.sdk.name OTel opentelemetry
telemetry.sdk.language OTel go
User-defined Primary enabled exporter’s resource_attrs (customer wins when both are enabled) env=production, team=platform

See How-to: enable Datadog tracing for ready-to-build dashboard templates.


Notes on stability

  • mcp.upstream is always the logical name from upstreams[].name in mcpgw.yaml, not the URL. This keeps spans clean and avoids leaking internal addresses into telemetry.
  • mcp.tool.name is set on every span regardless of method; it is an empty string for methods other than tools/call. This keeps Datadog facet cardinality predictable.
  • mcp.policy.rule_id is only present when a rule fires. Absence means no rule matched (decision is allow).
  • The mcp.* namespace is experimental and may evolve. Breaking changes are reserved for major versions only.