Skip to content

Commit 423a745

Browse files
docs: Add comprehensive CallToolResult parsing documentation
Instead of inline parsing examples in basic client code, this commit adds a dedicated 'Parsing Tool Results' section under Advanced Usage that comprehensively documents: - The structure of CallToolResult (content, structuredContent, isError) - How to parse different content types (TextContent, ImageContent, EmbeddedResource, ResourceLink) - Structured output handling for tools with outputSchema - Proper error handling using the isError field This provides a cleaner, more comprehensive reference for developers working with tool results in MCP. Github-Issue: #812
1 parent a99711d commit 423a745

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed

README.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
- [Advanced Usage](#advanced-usage)
4545
- [Low-Level Server](#low-level-server)
4646
- [Writing MCP Clients](#writing-mcp-clients)
47+
- [Parsing Tool Results](#parsing-tool-results)
48+
- [Client Display Utilities](#client-display-utilities)
49+
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
4750
- [MCP Primitives](#mcp-primitives)
4851
- [Server Capabilities](#server-capabilities)
4952
- [Documentation](#documentation)
@@ -1467,6 +1470,158 @@ if __name__ == "__main__":
14671470
_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_
14681471
<!-- /snippet-source -->
14691472

1473+
### Parsing Tool Results
1474+
1475+
When calling tools through MCP, the `CallToolResult` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs.
1476+
1477+
<!-- snippet-source examples/snippets/clients/parsing_tool_results.py -->
1478+
```python
1479+
"""
1480+
Example showing how to parse CallToolResult from MCP servers.
1481+
1482+
cd to the `examples/snippets` directory and run:
1483+
uv run parsing-tool-results
1484+
"""
1485+
1486+
import asyncio
1487+
import os
1488+
1489+
from pydantic import AnyUrl
1490+
1491+
from mcp import ClientSession, StdioServerParameters, types
1492+
from mcp.client.stdio import stdio_client
1493+
1494+
# Create server parameters for stdio connection
1495+
server_params = StdioServerParameters(
1496+
command="uv", # Using uv to run the server
1497+
args=["run", "server", "fastmcp_quickstart", "stdio"],
1498+
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
1499+
)
1500+
1501+
1502+
async def demonstrate_tool_result_parsing():
1503+
"""Demonstrate parsing different types of tool results."""
1504+
async with stdio_client(server_params) as (read, write):
1505+
async with ClientSession(read, write) as session:
1506+
# Initialize the connection
1507+
await session.initialize()
1508+
1509+
# Call a tool and parse the result
1510+
result = await session.call_tool("add", arguments={"a": 5, "b": 3})
1511+
1512+
# Example 1: Parsing unstructured content (backward compatible)
1513+
print("=== Parsing Unstructured Content ===")
1514+
for i, item in enumerate(result.content):
1515+
print(f"\nContent item {i + 1}:")
1516+
1517+
if isinstance(item, types.TextContent):
1518+
# Most common case: plain text response
1519+
print(f" Type: TextContent")
1520+
print(f" Text: {item.text}")
1521+
1522+
elif isinstance(item, types.ImageContent):
1523+
# Binary image data
1524+
print(f" Type: ImageContent")
1525+
print(f" MIME Type: {item.mimeType}")
1526+
print(f" Data (first 50 chars): {item.data[:50]}...")
1527+
1528+
elif isinstance(item, types.EmbeddedResource):
1529+
# Embedded resource with either text or binary data
1530+
print(f" Type: EmbeddedResource")
1531+
print(f" Resource URI: {item.resource.uri}")
1532+
1533+
if isinstance(item.resource, types.TextResourceContents):
1534+
print(f" Resource Type: Text")
1535+
print(f" Text: {item.resource.text}")
1536+
elif isinstance(item.resource, types.BlobResourceContents):
1537+
print(f" Resource Type: Blob")
1538+
print(f" MIME Type: {item.resource.mimeType}")
1539+
print(f" Blob data (first 50 chars): {item.resource.blob[:50]}...")
1540+
1541+
elif isinstance(item, types.ResourceLink):
1542+
# Link to a resource that can be read separately
1543+
print(f" Type: ResourceLink")
1544+
print(f" URI: {item.uri}")
1545+
print(f" Name: {item.name}")
1546+
if item.description:
1547+
print(f" Description: {item.description}")
1548+
1549+
else:
1550+
# Handle any future content types
1551+
print(f" Type: {type(item).__name__} (unknown)")
1552+
1553+
# Example 2: Parsing structured content (spec revision 2025-06-18+)
1554+
print("\n\n=== Parsing Structured Content ===")
1555+
if result.structuredContent is not None:
1556+
print(f"Structured result: {result.structuredContent}")
1557+
# The structure depends on the tool's outputSchema
1558+
# For the 'add' tool, it might be: {"result": 8}
1559+
else:
1560+
print("No structured content (tool may not support it)")
1561+
1562+
# Example 3: Error handling
1563+
print("\n\n=== Error Handling ===")
1564+
if result.isError:
1565+
print("The tool call resulted in an error!")
1566+
# Error details will be in the content field
1567+
for item in result.content:
1568+
if isinstance(item, types.TextContent):
1569+
print(f"Error message: {item.text}")
1570+
else:
1571+
print("Tool call completed successfully")
1572+
1573+
1574+
async def demonstrate_resource_parsing():
1575+
"""Show how embedded resources might appear in tool results."""
1576+
print("\n\n=== Resource Examples ===")
1577+
1578+
# Example of what a tool might return with embedded resources
1579+
# This is just for demonstration - actual results come from the server
1580+
1581+
# Text resource example
1582+
text_resource = types.EmbeddedResource(
1583+
type="resource",
1584+
resource=types.TextResourceContents(
1585+
uri=AnyUrl("file:///example.txt"),
1586+
mimeType="text/plain",
1587+
text="This is the content of the file"
1588+
)
1589+
)
1590+
print(f"Text resource URI: {text_resource.resource.uri}")
1591+
print(f"Text resource content: {text_resource.resource.text}")
1592+
1593+
# Binary resource example
1594+
blob_resource = types.EmbeddedResource(
1595+
type="resource",
1596+
resource=types.BlobResourceContents(
1597+
uri=AnyUrl("file:///image.png"),
1598+
mimeType="image/png",
1599+
blob="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
1600+
)
1601+
)
1602+
print(f"\nBlob resource URI: {blob_resource.resource.uri}")
1603+
print(f"Blob resource MIME type: {blob_resource.resource.mimeType}")
1604+
print(f"Blob resource data (base64): {blob_resource.resource.blob[:30]}...")
1605+
1606+
1607+
async def run():
1608+
"""Run all demonstrations."""
1609+
await demonstrate_tool_result_parsing()
1610+
await demonstrate_resource_parsing()
1611+
1612+
1613+
def main():
1614+
"""Entry point for the parsing examples."""
1615+
asyncio.run(run())
1616+
1617+
1618+
if __name__ == "__main__":
1619+
main()
1620+
```
1621+
1622+
_Full example: [examples/snippets/clients/parsing_tool_results.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/parsing_tool_results.py)_
1623+
<!-- /snippet-source -->
1624+
14701625
### Client Display Utilities
14711626

14721627
When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts:
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""
2+
Example showing how to parse CallToolResult from MCP servers.
3+
4+
cd to the `examples/snippets` directory and run:
5+
uv run parsing-tool-results
6+
"""
7+
8+
import asyncio
9+
import os
10+
11+
from pydantic import AnyUrl
12+
13+
from mcp import ClientSession, StdioServerParameters, types
14+
from mcp.client.stdio import stdio_client
15+
16+
# Create server parameters for stdio connection
17+
server_params = StdioServerParameters(
18+
command="uv", # Using uv to run the server
19+
args=["run", "server", "fastmcp_quickstart", "stdio"],
20+
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
21+
)
22+
23+
24+
async def demonstrate_tool_result_parsing():
25+
"""Demonstrate parsing different types of tool results."""
26+
async with stdio_client(server_params) as (read, write):
27+
async with ClientSession(read, write) as session:
28+
# Initialize the connection
29+
await session.initialize()
30+
31+
# Call a tool and parse the result
32+
result = await session.call_tool("add", arguments={"a": 5, "b": 3})
33+
34+
# Example 1: Parsing unstructured content (backward compatible)
35+
print("=== Parsing Unstructured Content ===")
36+
for i, item in enumerate(result.content):
37+
print(f"\nContent item {i + 1}:")
38+
39+
if isinstance(item, types.TextContent):
40+
# Most common case: plain text response
41+
print(f" Type: TextContent")
42+
print(f" Text: {item.text}")
43+
44+
elif isinstance(item, types.ImageContent):
45+
# Binary image data
46+
print(f" Type: ImageContent")
47+
print(f" MIME Type: {item.mimeType}")
48+
print(f" Data (first 50 chars): {item.data[:50]}...")
49+
50+
elif isinstance(item, types.EmbeddedResource):
51+
# Embedded resource with either text or binary data
52+
print(f" Type: EmbeddedResource")
53+
print(f" Resource URI: {item.resource.uri}")
54+
55+
if isinstance(item.resource, types.TextResourceContents):
56+
print(f" Resource Type: Text")
57+
print(f" Text: {item.resource.text}")
58+
elif isinstance(item.resource, types.BlobResourceContents):
59+
print(f" Resource Type: Blob")
60+
print(f" MIME Type: {item.resource.mimeType}")
61+
print(f" Blob data (first 50 chars): {item.resource.blob[:50]}...")
62+
63+
elif isinstance(item, types.ResourceLink):
64+
# Link to a resource that can be read separately
65+
print(f" Type: ResourceLink")
66+
print(f" URI: {item.uri}")
67+
print(f" Name: {item.name}")
68+
if item.description:
69+
print(f" Description: {item.description}")
70+
71+
else:
72+
# Handle any future content types
73+
print(f" Type: {type(item).__name__} (unknown)")
74+
75+
# Example 2: Parsing structured content (spec revision 2025-06-18+)
76+
print("\n\n=== Parsing Structured Content ===")
77+
if result.structuredContent is not None:
78+
print(f"Structured result: {result.structuredContent}")
79+
# The structure depends on the tool's outputSchema
80+
# For the 'add' tool, it might be: {"result": 8}
81+
else:
82+
print("No structured content (tool may not support it)")
83+
84+
# Example 3: Error handling
85+
print("\n\n=== Error Handling ===")
86+
if result.isError:
87+
print("The tool call resulted in an error!")
88+
# Error details will be in the content field
89+
for item in result.content:
90+
if isinstance(item, types.TextContent):
91+
print(f"Error message: {item.text}")
92+
else:
93+
print("Tool call completed successfully")
94+
95+
96+
async def demonstrate_resource_parsing():
97+
"""Show how embedded resources might appear in tool results."""
98+
print("\n\n=== Resource Examples ===")
99+
100+
# Example of what a tool might return with embedded resources
101+
# This is just for demonstration - actual results come from the server
102+
103+
# Text resource example
104+
text_resource = types.EmbeddedResource(
105+
type="resource",
106+
resource=types.TextResourceContents(
107+
uri=AnyUrl("file:///example.txt"),
108+
mimeType="text/plain",
109+
text="This is the content of the file"
110+
)
111+
)
112+
print(f"Text resource URI: {text_resource.resource.uri}")
113+
print(f"Text resource content: {text_resource.resource.text}")
114+
115+
# Binary resource example
116+
blob_resource = types.EmbeddedResource(
117+
type="resource",
118+
resource=types.BlobResourceContents(
119+
uri=AnyUrl("file:///image.png"),
120+
mimeType="image/png",
121+
blob="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
122+
)
123+
)
124+
print(f"\nBlob resource URI: {blob_resource.resource.uri}")
125+
print(f"Blob resource MIME type: {blob_resource.resource.mimeType}")
126+
print(f"Blob resource data (base64): {blob_resource.resource.blob[:30]}...")
127+
128+
129+
async def run():
130+
"""Run all demonstrations."""
131+
await demonstrate_tool_result_parsing()
132+
await demonstrate_resource_parsing()
133+
134+
135+
def main():
136+
"""Entry point for the parsing examples."""
137+
asyncio.run(run())
138+
139+
140+
if __name__ == "__main__":
141+
main()

examples/snippets/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ client = "clients.stdio_client:main"
2020
completion-client = "clients.completion_client:main"
2121
direct-execution-server = "servers.direct_execution:main"
2222
display-utilities-client = "clients.display_utilities:main"
23+
parsing-tool-results = "clients.parsing_tool_results:main"

0 commit comments

Comments
 (0)