@@ -34,8 +34,68 @@ def greet(name: str) -> str:
3434 client_span = next (s for s in spans if s ["name" ] == "MCP send tools/call greet" )
3535 server_span = next (s for s in spans if s ["name" ] == "MCP handle tools/call greet" )
3636
37+ # Base RPC + MCP attributes
38+ assert client_span ["attributes" ]["rpc.system" ] == "mcp"
3739 assert client_span ["attributes" ]["mcp.method.name" ] == "tools/call"
40+ assert client_span ["attributes" ]["jsonrpc.request.id" ] is not None
41+ assert server_span ["attributes" ]["rpc.system" ] == "mcp"
3842 assert server_span ["attributes" ]["mcp.method.name" ] == "tools/call"
3943
44+ # GenAI semconv attributes
45+ assert client_span ["attributes" ]["gen_ai.operation.name" ] == "execute_tool"
46+ assert client_span ["attributes" ]["gen_ai.tool.name" ] == "greet"
47+ assert server_span ["attributes" ]["gen_ai.operation.name" ] == "execute_tool"
48+ assert server_span ["attributes" ]["gen_ai.tool.name" ] == "greet"
49+
4050 # Server span should be in the same trace as the client span (context propagation).
4151 assert server_span ["context" ]["trace_id" ] == client_span ["context" ]["trace_id" ]
52+
53+
54+ async def test_list_tools_spans (capfire : CaptureLogfire ):
55+ """Verify that listing tools produces spans with list_tools operation."""
56+ server = MCPServer ("test" )
57+
58+ @server .tool ()
59+ def greet (name : str ) -> str :
60+ """Greet someone."""
61+ return f"Hello, { name } !"
62+
63+ async with Client (server ) as client :
64+ await client .list_tools ()
65+
66+ spans = capfire .exporter .exported_spans_as_dict ()
67+
68+ client_span = next (s for s in spans if s ["name" ] == "MCP send tools/list" )
69+ server_span = next (s for s in spans if s ["name" ] == "MCP handle tools/list" )
70+
71+ assert client_span ["attributes" ]["gen_ai.operation.name" ] == "list_tools"
72+ assert server_span ["attributes" ]["gen_ai.operation.name" ] == "list_tools"
73+ # No tool name on list — no specific tool targeted
74+ assert "gen_ai.tool.name" not in client_span ["attributes" ]
75+ assert "gen_ai.tool.name" not in server_span ["attributes" ]
76+
77+ assert server_span ["context" ]["trace_id" ] == client_span ["context" ]["trace_id" ]
78+
79+
80+ async def test_resource_read_spans (capfire : CaptureLogfire ):
81+ """Verify that reading a resource produces spans with resource URI."""
82+ server = MCPServer ("test" )
83+
84+ @server .resource ("test://greeting" )
85+ def greeting () -> str :
86+ return "hello"
87+
88+ async with Client (server ) as client :
89+ await client .read_resource ("test://greeting" )
90+
91+ spans = capfire .exporter .exported_spans_as_dict ()
92+
93+ client_span = next (s for s in spans if s ["name" ] == "MCP send resources/read" )
94+ server_span = next (s for s in spans if s ["name" ] == "MCP handle resources/read" )
95+
96+ assert client_span ["attributes" ]["gen_ai.operation.name" ] == "read_resource"
97+ assert client_span ["attributes" ]["mcp.resource.uri" ] == "test://greeting"
98+ assert server_span ["attributes" ]["gen_ai.operation.name" ] == "read_resource"
99+ assert server_span ["attributes" ]["mcp.resource.uri" ] == "test://greeting"
100+
101+ assert server_span ["context" ]["trace_id" ] == client_span ["context" ]["trace_id" ]
0 commit comments