Select FastMCP client spans by component.key; assert span descendant vs SDK v2 intermediate spans

This commit is contained in:
Jeremiah Lowin 2026-07-05 23:10:57 -04:00
commit 739de66e8e
No known key found for this signature in database
2 changed files with 36 additions and 5 deletions

View file

@ -32,6 +32,7 @@ class TestClientListToolsTracing:
if s.name == "tools/list"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
]
assert len(client_spans) >= 1
@ -63,6 +64,7 @@ class TestClientListToolsTracing:
for s in tools_list_spans
if s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -105,6 +107,7 @@ class TestClientListResourcesTracing:
if s.name == "resources/list"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
]
assert len(client_spans) >= 1
@ -138,6 +141,7 @@ class TestClientListResourceTemplatesTracing:
if s.name == "resources/templates/list"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
]
assert len(client_spans) >= 1
@ -171,6 +175,7 @@ class TestClientListPromptsTracing:
if s.name == "prompts/list"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
]
assert len(client_spans) >= 1

View file

@ -56,6 +56,7 @@ class TestClientToolTracing:
if s.name == "tools/call add"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -95,6 +96,7 @@ class TestClientToolTracing:
if s.name == "tools/call failing_tool"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
]
# Exactly one client span should exist (no duplicate from call_tool)
@ -154,6 +156,7 @@ class TestClientResourceTracing:
if s.name == "resources/read"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -214,6 +217,7 @@ class TestClientPromptTracing:
if s.name == "prompts/get welcome"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -257,6 +261,7 @@ class TestClientServerSpanHierarchy:
if s.name == "tools/call echo"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -309,6 +314,7 @@ class TestClientServerSpanHierarchy:
if s.name == "tools/call add"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -326,16 +332,30 @@ class TestClientServerSpanHierarchy:
assert client_span is not None, "Client span should exist"
assert server_span is not None, "Server span should exist"
# Verify trace context propagation: server span should be child of client span
# Both should share the same trace_id
# Verify trace context propagation: server span should be a descendant
# of the client span, sharing the same trace_id. SDK v2 emits its own
# intermediate spans (e.g. "MCP send tools/call add") between the FastMCP
# client span and the server span, so the server span's parent is one of
# those SDK spans rather than the client span directly. Assert descendant
# relationship by walking the parent chain instead of a direct parent.
assert server_span.context.trace_id == client_span.context.trace_id, (
"Server and client spans should share the same trace_id"
)
# Server span's parent should be the client span
spans_by_id = {s.context.span_id: s for s in spans}
assert server_span.parent is not None, "Server span should have a parent"
assert server_span.parent.span_id == client_span.context.span_id, (
"Server span's parent should be the client span"
current = server_span
found_client_ancestor = False
while current.parent is not None:
parent = spans_by_id.get(current.parent.span_id)
if parent is None:
break
if parent.context.span_id == client_span.context.span_id:
found_client_ancestor = True
break
current = parent
assert found_client_ancestor, (
"Server span should be a descendant of the client span"
)
@ -376,6 +396,7 @@ class TestClientErrorTracing:
if s.name == "tools/call failing_tool"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -423,6 +444,7 @@ class TestClientErrorTracing:
if s.name == "resources/read"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -470,6 +492,7 @@ class TestClientErrorTracing:
if s.name == "prompts/get failing_prompt"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -513,6 +536,7 @@ class TestClientErrorTracing:
if s.name == "tools/call nonexistent"
and s.attributes is not None
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -582,6 +606,7 @@ class TestSessionIdOnSpans:
and s.attributes is not None
and "fastmcp.component.key" in s.attributes
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)
@ -648,6 +673,7 @@ class TestSessionIdOnSpans:
and s.attributes is not None
and "fastmcp.component.key" in s.attributes
and "fastmcp.server.name" not in s.attributes
and "fastmcp.component.key" in s.attributes
),
None,
)