Skip to content

Commit d98da76

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 03e0490 + 202af49 commit d98da76

File tree

91 files changed

+3599
-1732
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+3599
-1732
lines changed

.github/CODEOWNERS

Lines changed: 0 additions & 23 deletions
This file was deleted.

.github/workflows/publish-docs-manually.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: astral-sh/setup-uv@v3
2020
with:
2121
enable-cache: true
22-
version: 0.7.2
22+
version: 0.9.5
2323

2424
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
2525
- uses: actions/cache@v4

.github/workflows/publish-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
uses: astral-sh/setup-uv@v3
1717
with:
1818
enable-cache: true
19-
version: 0.7.2
19+
version: 0.9.5
2020

2121
- name: Set up Python 3.12
2222
run: uv python install 3.12
@@ -68,7 +68,7 @@ jobs:
6868
uses: astral-sh/setup-uv@v3
6969
with:
7070
enable-cache: true
71-
version: 0.7.2
71+
version: 0.9.5
7272

7373
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
7474
- uses: actions/cache@v4

.github/workflows/shared.yml

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,62 @@ jobs:
1313
pre-commit:
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v4
16+
- uses: actions/checkout@v5
1717

18-
- uses: astral-sh/setup-uv@v5
18+
- uses: astral-sh/setup-uv@v7
1919
with:
2020
enable-cache: true
21-
version: 0.7.2
22-
21+
version: 0.9.5
2322
- name: Install dependencies
2423
run: uv sync --frozen --all-extras --python 3.10
2524

26-
- uses: pre-commit/[email protected].0
25+
- uses: pre-commit/[email protected].1
2726
with:
2827
extra_args: --all-files --verbose
2928
env:
3029
SKIP: no-commit-to-branch
3130

3231
test:
32+
name: test (${{ matrix.python-version }}, ${{ matrix.dep-resolution.name }}, ${{ matrix.os }})
3333
runs-on: ${{ matrix.os }}
3434
timeout-minutes: 10
3535
continue-on-error: true
3636
strategy:
3737
matrix:
3838
python-version: ["3.10", "3.11", "3.12", "3.13"]
39-
dep-resolution: ["lowest-direct", "highest"]
39+
dep-resolution:
40+
- name: lowest-direct
41+
install-flags: "--resolution lowest-direct"
42+
- name: highest
43+
install-flags: "--frozen"
4044
os: [ubuntu-latest, windows-latest]
4145

4246
steps:
43-
- uses: actions/checkout@v4
47+
- uses: actions/checkout@v5
4448

4549
- name: Install uv
46-
uses: astral-sh/setup-uv@v3
50+
uses: astral-sh/setup-uv@v7
4751
with:
4852
enable-cache: true
49-
version: 0.7.2
53+
version: 0.9.5
5054

5155
- name: Install the project
52-
run: uv sync --frozen --all-extras --python ${{ matrix.python-version }} --resolution ${{ matrix.dep-resolution }}
56+
run: uv sync ${{ matrix.dep-resolution.install-flags }} --all-extras --python ${{ matrix.python-version }}
5357

5458
- name: Run pytest
55-
run: uv run --frozen --no-sync pytest
59+
run: uv run ${{ matrix.dep-resolution.install-flags }} --no-sync pytest
60+
env:
61+
UV_RESOLUTION: ${{ matrix.dep-resolution.name == 'lowest-direct' && 'lowest-direct' || 'highest' }}
5662

5763
readme-snippets:
5864
runs-on: ubuntu-latest
5965
steps:
60-
- uses: actions/checkout@v4
66+
- uses: actions/checkout@v5
6167

62-
- uses: astral-sh/setup-uv@v5
68+
- uses: astral-sh/setup-uv@v7
6369
with:
6470
enable-cache: true
65-
version: 0.7.2
71+
version: 0.9.5
6672

6773
- name: Install dependencies
6874
run: uv sync --frozen --all-extras --python 3.10

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ ipython_config.py
8989
# pyenv
9090
# For a library or package, you might want to ignore these files since the code is
9191
# intended to run in multiple environments; otherwise, check them in:
92-
# .python-version
92+
.python-version
9393

9494
# pipenv
9595
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.

CLAUDE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ This document contains critical information about working with this codebase. Fo
4848
the problem it tries to solve, and how it is solved. Don't go into the specifics of the
4949
code unless it adds clarity.
5050

51-
- Always add `jerome3o-anthropic` and `jspahrsummers` as reviewer.
52-
5351
- NEVER ever mention a `co-authored-by` or similar aspects. In particular, never
5452
mention the tool used to create the commit message or PR.
5553

README.md

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
[![MIT licensed][mit-badge]][mit-url]
99
[![Python Version][python-badge]][python-url]
1010
[![Documentation][docs-badge]][docs-url]
11+
[![Protocol][protocol-badge]][protocol-url]
1112
[![Specification][spec-badge]][spec-url]
12-
[![GitHub Discussions][discussions-badge]][discussions-url]
1313

1414
</div>
1515

@@ -74,12 +74,12 @@
7474
[mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE
7575
[python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg
7676
[python-url]: https://www.python.org/downloads/
77-
[docs-badge]: https://img.shields.io/badge/docs-modelcontextprotocol.io-blue.svg
78-
[docs-url]: https://modelcontextprotocol.io
77+
[docs-badge]: https://img.shields.io/badge/docs-python--sdk-blue.svg
78+
[docs-url]: https://modelcontextprotocol.github.io/python-sdk/
79+
[protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg
80+
[protocol-url]: https://modelcontextprotocol.io
7981
[spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg
80-
[spec-url]: https://spec.modelcontextprotocol.io
81-
[discussions-badge]: https://img.shields.io/github/discussions/modelcontextprotocol/python-sdk
82-
[discussions-url]: https://github.com/modelcontextprotocol/python-sdk/discussions
82+
[spec-url]: https://modelcontextprotocol.io/specification/latest
8383

8484
## Overview
8585

@@ -383,6 +383,61 @@ causes the tool to be classified as structured _and this is undesirable_,
383383
the classification can be suppressed by passing `structured_output=False`
384384
to the `@tool` decorator.
385385

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+
386441
<!-- snippet-source examples/snippets/servers/structured_output.py -->
387442
```python
388443
"""Example showing structured output with tools."""
@@ -1769,14 +1824,93 @@ if __name__ == "__main__":
17691824
_Full example: [examples/snippets/servers/lowlevel/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/structured_output.py)_
17701825
<!-- /snippet-source -->
17711826

1772-
Tools can return data in three ways:
1827+
Tools can return data in four ways:
17731828

17741829
1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18)
17751830
2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18)
17761831
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)
17771833

17781834
When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early.
17791835

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+
17801914
### Pagination (Advanced)
17811915

17821916
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.
@@ -1840,7 +1974,7 @@ import asyncio
18401974

18411975
from mcp.client.session import ClientSession
18421976
from mcp.client.stdio import StdioServerParameters, stdio_client
1843-
from mcp.types import Resource
1977+
from mcp.types import PaginatedRequestParams, Resource
18441978

18451979

18461980
async def list_all_resources() -> None:
@@ -1857,7 +1991,7 @@ async def list_all_resources() -> None:
18571991

18581992
while True:
18591993
# Fetch a page of resources
1860-
result = await session.list_resources(cursor=cursor)
1994+
result = await session.list_resources(params=PaginatedRequestParams(cursor=cursor))
18611995
all_resources.extend(result.resources)
18621996

18631997
print(f"Fetched {len(result.resources)} resources")
@@ -2299,7 +2433,7 @@ MCP servers declare capabilities during initialization:
22992433

23002434
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
23012435
- [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)
23032437
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)
23042438

23052439
## Contributing

docs/authorization.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Authorization
2+
3+
!!! warning "Under Construction"
4+
5+
This page is currently being written. Check back soon for complete documentation.

docs/concepts.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Concepts
2+
3+
!!! warning "Under Construction"
4+
5+
This page is currently being written. Check back soon for complete documentation.
6+
7+
<!--
8+
- Server vs Client
9+
- Three primitives (tools, resources, prompts)
10+
- Transports (stdio, SSE, streamable HTTP)
11+
- Context and sessions
12+
- Lifecycle and state
13+
-->

0 commit comments

Comments
 (0)