|
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.""" |
@@ -1769,14 +1824,93 @@ if __name__ == "__main__": |
1769 | 1824 | _Full example: [examples/snippets/servers/lowlevel/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/structured_output.py)_ |
1770 | 1825 | <!-- /snippet-source --> |
1771 | 1826 |
|
1772 | | -Tools can return data in three ways: |
| 1827 | +Tools can return data in four ways: |
1773 | 1828 |
|
1774 | 1829 | 1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18) |
1775 | 1830 | 2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18) |
1776 | 1831 | 3. **Both**: Return a tuple of (content, structured_data) preferred option to use for backwards compatibility |
| 1832 | +4. **Direct CallToolResult**: Return `CallToolResult` directly for full control (including `_meta` field) |
1777 | 1833 |
|
1778 | 1834 | When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early. |
1779 | 1835 |
|
| 1836 | +##### Returning CallToolResult Directly |
| 1837 | + |
| 1838 | +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: |
| 1839 | + |
| 1840 | +<!-- snippet-source examples/snippets/servers/lowlevel/direct_call_tool_result.py --> |
| 1841 | +```python |
| 1842 | +""" |
| 1843 | +Run from the repository root: |
| 1844 | + uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py |
| 1845 | +""" |
| 1846 | + |
| 1847 | +import asyncio |
| 1848 | +from typing import Any |
| 1849 | + |
| 1850 | +import mcp.server.stdio |
| 1851 | +import mcp.types as types |
| 1852 | +from mcp.server.lowlevel import NotificationOptions, Server |
| 1853 | +from mcp.server.models import InitializationOptions |
| 1854 | + |
| 1855 | +server = Server("example-server") |
| 1856 | + |
| 1857 | + |
| 1858 | +@server.list_tools() |
| 1859 | +async def list_tools() -> list[types.Tool]: |
| 1860 | + """List available tools.""" |
| 1861 | + return [ |
| 1862 | + types.Tool( |
| 1863 | + name="advanced_tool", |
| 1864 | + description="Tool with full control including _meta field", |
| 1865 | + inputSchema={ |
| 1866 | + "type": "object", |
| 1867 | + "properties": {"message": {"type": "string"}}, |
| 1868 | + "required": ["message"], |
| 1869 | + }, |
| 1870 | + ) |
| 1871 | + ] |
| 1872 | + |
| 1873 | + |
| 1874 | +@server.call_tool() |
| 1875 | +async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult: |
| 1876 | + """Handle tool calls by returning CallToolResult directly.""" |
| 1877 | + if name == "advanced_tool": |
| 1878 | + message = str(arguments.get("message", "")) |
| 1879 | + return types.CallToolResult( |
| 1880 | + content=[types.TextContent(type="text", text=f"Processed: {message}")], |
| 1881 | + structuredContent={"result": "success", "message": message}, |
| 1882 | + _meta={"hidden": "data for client applications only"}, |
| 1883 | + ) |
| 1884 | + |
| 1885 | + raise ValueError(f"Unknown tool: {name}") |
| 1886 | + |
| 1887 | + |
| 1888 | +async def run(): |
| 1889 | + """Run the server.""" |
| 1890 | + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): |
| 1891 | + await server.run( |
| 1892 | + read_stream, |
| 1893 | + write_stream, |
| 1894 | + InitializationOptions( |
| 1895 | + server_name="example", |
| 1896 | + server_version="0.1.0", |
| 1897 | + capabilities=server.get_capabilities( |
| 1898 | + notification_options=NotificationOptions(), |
| 1899 | + experimental_capabilities={}, |
| 1900 | + ), |
| 1901 | + ), |
| 1902 | + ) |
| 1903 | + |
| 1904 | + |
| 1905 | +if __name__ == "__main__": |
| 1906 | + asyncio.run(run()) |
| 1907 | +``` |
| 1908 | + |
| 1909 | +_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)_ |
| 1910 | +<!-- /snippet-source --> |
| 1911 | + |
| 1912 | +**Note:** When returning `CallToolResult`, you bypass the automatic content/structured conversion. You must construct the complete response yourself. |
| 1913 | + |
1780 | 1914 | ### Pagination (Advanced) |
1781 | 1915 |
|
1782 | 1916 | 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. |
@@ -2299,7 +2433,7 @@ MCP servers declare capabilities during initialization: |
2299 | 2433 |
|
2300 | 2434 | - [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/) |
2301 | 2435 | - [Model Context Protocol documentation](https://modelcontextprotocol.io) |
2302 | | -- [Model Context Protocol specification](https://spec.modelcontextprotocol.io) |
| 2436 | +- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest) |
2303 | 2437 | - [Officially supported servers](https://github.com/modelcontextprotocol/servers) |
2304 | 2438 |
|
2305 | 2439 | ## Contributing |
|
0 commit comments