Skip to content

Add OpenTelemetry tracing and metrics for MCP tool calls#24

Open
diego-ferrand wants to merge 1 commit into
developmentfrom
OTEL_SUPPORT
Open

Add OpenTelemetry tracing and metrics for MCP tool calls#24
diego-ferrand wants to merge 1 commit into
developmentfrom
OTEL_SUPPORT

Conversation

@diego-ferrand

Copy link
Copy Markdown
Collaborator

No description provided.

Comment thread telemetry.py
Comment on lines +111 to +130
def _get_meta(ctx: Any) -> dict:
try:
return ctx.request_context.request.params.meta or {}
except Exception:
return {}


def _extract_trace_context(meta: dict):
if not meta:
return None
try:
from opentelemetry.propagate import extract
carrier = {}
if "traceparent" in meta:
carrier["traceparent"] = meta["traceparent"]
if "tracestate" in meta:
carrier["tracestate"] = meta["tracestate"]
return extract(carrier) if carrier else None
except Exception:
return None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @diego-ferrand I was interiorizing with the PR and I got a pretty big alert on how are we handling the meta withing the context.
This is so far the research I could put through:

_extract_trace_context never extracts the parent context, so server spans are never linked to the caller's trace. Local spans/metrics still work, which is why this
fails quietly rather than erroring.

Root cause: request.params.meta is not a dict — it's the SDK's RequestParams.Meta Pydantic model (alias="_meta", extra="allow"). The _meta.meta
mapping is handled correctly by the alias, but traceparent/tracestate arrive as model attributes, not dict keys. The current code does dict-style access on it:

if "traceparent" in meta:                 # always False on a BaseModel
    carrier["traceparent"] = meta["traceparent"]   # 'Meta' object is not subscriptable

So carrier is always empty → extract() is never called → parent_ctx is always None.
Reproduced against the real SDK type:

type(meta): mcp.types.RequestParams.Meta
"traceparent" in meta  => False
meta["traceparent"]    => TypeError: 'Meta' object is not subscriptable
_extract_trace_context => None

Suggested fix — normalize to a dict first (handles both the Pydantic-model and plain-dict shapes, so we don't have to assume the client/transport):

def _extract_trace_context(meta):
    if not meta:
        return None
    if hasattr(meta, "model_dump"):
        meta = meta.model_dump()
    ...

model_dump() surfaces the extras correctly:

{'progressToken': None, 'traceparent': '00-...-01', 'tracestate': 'rojo=...'}

Tests: tests/test_telemetry.py currently has no coverage for traceparent / _get_meta / _extract_trace_context, which is how this regressed without anything
failing. A MagicMock ctx would fake in/[] and mask the bug, so the test should build the actual SDK type.
Non-breaking for everything else, but blocking for the OTEL propagation goal — it defeats the whole point of passing traceparent.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants