Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ async def traced_method(wrapped, instance, args, kwargs):
if self._server_name:
mcp_span.set_attribute(SpanAttributes.TRACELOOP_WORKFLOW_NAME, self._server_name)

# Add MCP_REQUEST_ID to the parent span
import time
request_id = str(int(time.time() * 1000)) # milliseconds
mcp_span.set_attribute(SpanAttributes.MCP_REQUEST_ID, request_id)

# Create nested tool span
span_name = f"{entity_name}.tool"
with self._tracer.start_as_current_span(span_name) as tool_span:
Expand All @@ -112,27 +117,47 @@ async def traced_method(wrapped, instance, args, kwargs):
try:
result = await wrapped(*args, **kwargs)

# Add output in traceloop format to tool span
if self._should_send_prompts() and result:
# Always add response to MCP span regardless of content tracing setting
if result:
try:
# Convert FastMCP Content objects to serializable format
output_data = []
for item in result:
if hasattr(item, 'text'):
output_data.append({"type": "text", "content": item.text})
elif hasattr(item, '__dict__'):
output_data.append(item.__dict__)
# Handle FastMCP ToolResult object
if hasattr(result, 'content') and result.content:
# Convert FastMCP Content objects to serializable format
output_data = []
for item in result.content:
if hasattr(item, 'text'):
output_data.append({"type": "text", "content": item.text})
elif hasattr(item, '__dict__'):
output_data.append(item.__dict__)
else:
output_data.append(str(item))

json_output = json.dumps(output_data, cls=self._get_json_encoder())
truncated_output = self._truncate_json_if_needed(json_output)
else:
# Handle other result types
if hasattr(result, '__dict__'):
# Convert object to dict
result_dict = {}
for key, value in result.__dict__.items():
if not key.startswith('_'):
result_dict[key] = str(value)
json_output = json.dumps(result_dict, cls=self._get_json_encoder())
truncated_output = self._truncate_json_if_needed(json_output)
else:
output_data.append(str(item))

json_output = json.dumps(output_data, cls=self._get_json_encoder())
truncated_output = self._truncate_json_if_needed(json_output)
tool_span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, truncated_output)
# Fallback to string representation
truncated_output = str(result)

# Also add response to MCP span
# Add response to MCP span
mcp_span.set_attribute(SpanAttributes.MCP_RESPONSE_VALUE, truncated_output)

# Also add to tool span if content tracing is enabled
if self._should_send_prompts():
tool_span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_OUTPUT, truncated_output)

except (TypeError, ValueError):
pass # Skip output logging if serialization fails
# Fallback: add raw result as string
mcp_span.set_attribute(SpanAttributes.MCP_RESPONSE_VALUE, str(result))

tool_span.set_status(Status(StatusCode.OK))
mcp_span.set_status(Status(StatusCode.OK))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def get_greeting() -> str:

# Test tool calling
result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
assert len(result) == 1
assert result[0].text == "8"
assert len(result.content) == 1
assert result.content[0].text == "8"

# Test resource listing
resources_res = await client.list_resources()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ async def test_tool(x: int) -> int:
async with Client(server) as client:
# Test tool calling
result = await client.call_tool("test_tool", {"x": 5})
assert len(result) == 1
assert result[0].text == "10"
assert len(result.content) == 1
assert result.content[0].text == "10"

# Get the finished spans
spans = span_exporter.get_finished_spans()
Expand Down
Loading