|
79 | 79 | [protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg |
80 | 80 | [protocol-url]: https://modelcontextprotocol.io |
81 | 81 | [spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg |
82 | | -[spec-url]: https://spec.modelcontextprotocol.io |
| 82 | +[spec-url]: https://modelcontextprotocol.io/specification/latest |
83 | 83 |
|
84 | 84 | ## Overview |
85 | 85 |
|
@@ -383,6 +383,61 @@ causes the tool to be classified as structured _and this is undesirable_, |
383 | 383 | the classification can be suppressed by passing `structured_output=False` |
384 | 384 | to the `@tool` decorator. |
385 | 385 |
|
| 386 | +##### Advanced: Direct CallToolResult |
| 387 | + |
| 388 | +For full control over tool responses including the `_meta` field (for passing data to client applications without exposing it to the model), you can return `CallToolResult` directly: |
| 389 | + |
| 390 | +<!-- snippet-source examples/snippets/servers/direct_call_tool_result.py --> |
| 391 | +```python |
| 392 | +"""Example showing direct CallToolResult return for advanced control.""" |
| 393 | + |
| 394 | +from typing import Annotated |
| 395 | + |
| 396 | +from pydantic import BaseModel |
| 397 | + |
| 398 | +from mcp.server.fastmcp import FastMCP |
| 399 | +from mcp.types import CallToolResult, TextContent |
| 400 | + |
| 401 | +mcp = FastMCP("CallToolResult Example") |
| 402 | + |
| 403 | + |
| 404 | +class ValidationModel(BaseModel): |
| 405 | + """Model for validating structured output.""" |
| 406 | + |
| 407 | + status: str |
| 408 | + data: dict[str, int] |
| 409 | + |
| 410 | + |
| 411 | +@mcp.tool() |
| 412 | +def advanced_tool() -> CallToolResult: |
| 413 | + """Return CallToolResult directly for full control including _meta field.""" |
| 414 | + return CallToolResult( |
| 415 | + content=[TextContent(type="text", text="Response visible to the model")], |
| 416 | + _meta={"hidden": "data for client applications only"}, |
| 417 | + ) |
| 418 | + |
| 419 | + |
| 420 | +@mcp.tool() |
| 421 | +def validated_tool() -> Annotated[CallToolResult, ValidationModel]: |
| 422 | + """Return CallToolResult with structured output validation.""" |
| 423 | + return CallToolResult( |
| 424 | + content=[TextContent(type="text", text="Validated response")], |
| 425 | + structuredContent={"status": "success", "data": {"result": 42}}, |
| 426 | + _meta={"internal": "metadata"}, |
| 427 | + ) |
| 428 | + |
| 429 | + |
| 430 | +@mcp.tool() |
| 431 | +def empty_result_tool() -> CallToolResult: |
| 432 | + """For empty results, return CallToolResult with empty content.""" |
| 433 | + return CallToolResult(content=[]) |
| 434 | +``` |
| 435 | + |
| 436 | +_Full example: [examples/snippets/servers/direct_call_tool_result.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_call_tool_result.py)_ |
| 437 | +<!-- /snippet-source --> |
| 438 | + |
| 439 | +**Important:** `CallToolResult` must always be returned (no `Optional` or `Union`). For empty results, use `CallToolResult(content=[])`. For optional simple types, use `str | None` without `CallToolResult`. |
| 440 | + |
386 | 441 | <!-- snippet-source examples/snippets/servers/structured_output.py --> |
387 | 442 | ```python |
388 | 443 | """Example showing structured output with tools.""" |
@@ -1773,14 +1828,93 @@ if __name__ == "__main__": |
1773 | 1828 | _Full example: [examples/snippets/servers/lowlevel/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/structured_output.py)_ |
1774 | 1829 | <!-- /snippet-source --> |
1775 | 1830 |
|
1776 | | -Tools can return data in three ways: |
| 1831 | +Tools can return data in four ways: |
1777 | 1832 |
|
1778 | 1833 | 1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18) |
1779 | 1834 | 2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18) |
1780 | 1835 | 3. **Both**: Return a tuple of (content, structured_data) preferred option to use for backwards compatibility |
| 1836 | +4. **Direct CallToolResult**: Return `CallToolResult` directly for full control (including `_meta` field) |
1781 | 1837 |
|
1782 | 1838 | When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early. |
1783 | 1839 |
|
| 1840 | +##### Returning CallToolResult Directly |
| 1841 | + |
| 1842 | +For full control over the response including the `_meta` field (for passing data to client applications without exposing it to the model), return `CallToolResult` directly: |
| 1843 | + |
| 1844 | +<!-- snippet-source examples/snippets/servers/lowlevel/direct_call_tool_result.py --> |
| 1845 | +```python |
| 1846 | +""" |
| 1847 | +Run from the repository root: |
| 1848 | + uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py |
| 1849 | +""" |
| 1850 | + |
| 1851 | +import asyncio |
| 1852 | +from typing import Any |
| 1853 | + |
| 1854 | +import mcp.server.stdio |
| 1855 | +import mcp.types as types |
| 1856 | +from mcp.server.lowlevel import NotificationOptions, Server |
| 1857 | +from mcp.server.models import InitializationOptions |
| 1858 | + |
| 1859 | +server = Server("example-server") |
| 1860 | + |
| 1861 | + |
| 1862 | +@server.list_tools() |
| 1863 | +async def list_tools() -> list[types.Tool]: |
| 1864 | + """List available tools.""" |
| 1865 | + return [ |
| 1866 | + types.Tool( |
| 1867 | + name="advanced_tool", |
| 1868 | + description="Tool with full control including _meta field", |
| 1869 | + inputSchema={ |
| 1870 | + "type": "object", |
| 1871 | + "properties": {"message": {"type": "string"}}, |
| 1872 | + "required": ["message"], |
| 1873 | + }, |
| 1874 | + ) |
| 1875 | + ] |
| 1876 | + |
| 1877 | + |
| 1878 | +@server.call_tool() |
| 1879 | +async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult: |
| 1880 | + """Handle tool calls by returning CallToolResult directly.""" |
| 1881 | + if name == "advanced_tool": |
| 1882 | + message = str(arguments.get("message", "")) |
| 1883 | + return types.CallToolResult( |
| 1884 | + content=[types.TextContent(type="text", text=f"Processed: {message}")], |
| 1885 | + structuredContent={"result": "success", "message": message}, |
| 1886 | + _meta={"hidden": "data for client applications only"}, |
| 1887 | + ) |
| 1888 | + |
| 1889 | + raise ValueError(f"Unknown tool: {name}") |
| 1890 | + |
| 1891 | + |
| 1892 | +async def run(): |
| 1893 | + """Run the server.""" |
| 1894 | + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): |
| 1895 | + await server.run( |
| 1896 | + read_stream, |
| 1897 | + write_stream, |
| 1898 | + InitializationOptions( |
| 1899 | + server_name="example", |
| 1900 | + server_version="0.1.0", |
| 1901 | + capabilities=server.get_capabilities( |
| 1902 | + notification_options=NotificationOptions(), |
| 1903 | + experimental_capabilities={}, |
| 1904 | + ), |
| 1905 | + ), |
| 1906 | + ) |
| 1907 | + |
| 1908 | + |
| 1909 | +if __name__ == "__main__": |
| 1910 | + asyncio.run(run()) |
| 1911 | +``` |
| 1912 | + |
| 1913 | +_Full example: [examples/snippets/servers/lowlevel/direct_call_tool_result.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/direct_call_tool_result.py)_ |
| 1914 | +<!-- /snippet-source --> |
| 1915 | + |
| 1916 | +**Note:** When returning `CallToolResult`, you bypass the automatic content/structured conversion. You must construct the complete response yourself. |
| 1917 | + |
1784 | 1918 | ### Pagination (Advanced) |
1785 | 1919 |
|
1786 | 1920 | For servers that need to handle large datasets, the low-level server provides paginated versions of list operations. This is an optional optimization - most servers won't need pagination unless they're dealing with hundreds or thousands of items. |
@@ -2303,7 +2437,7 @@ MCP servers declare capabilities during initialization: |
2303 | 2437 |
|
2304 | 2438 | - [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/) |
2305 | 2439 | - [Model Context Protocol documentation](https://modelcontextprotocol.io) |
2306 | | -- [Model Context Protocol specification](https://spec.modelcontextprotocol.io) |
| 2440 | +- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest) |
2307 | 2441 | - [Officially supported servers](https://github.com/modelcontextprotocol/servers) |
2308 | 2442 |
|
2309 | 2443 | ## Contributing |
|
0 commit comments