Skip to content

Commit 90428fe

Browse files
committed
Merge branch 'main' of https://github.com/modelcontextprotocol/python-sdk into feat/rfc7523
2 parents 50a38a6 + 80c0d23 commit 90428fe

File tree

74 files changed

+3526
-559
lines changed

Some content is hidden

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

74 files changed

+3526
-559
lines changed

.gitattribute

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated
2+
uv.lock linguist-generated=true

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ coverage.xml
5252
*.py,cover
5353
.hypothesis/
5454
.pytest_cache/
55+
.ruff_cache/
5556
cover/
5657

5758
# Translations
@@ -168,3 +169,6 @@ cython_debug/
168169
.vscode/
169170
.windsurfrules
170171
**/CLAUDE.local.md
172+
173+
# claude code
174+
.claude/

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,22 @@ repos:
2525
hooks:
2626
- id: ruff-format
2727
name: Ruff Format
28-
entry: uv run ruff
28+
entry: uv run --frozen ruff
2929
args: [format]
3030
language: system
3131
types: [python]
3232
pass_filenames: false
3333
- id: ruff
3434
name: Ruff
35-
entry: uv run ruff
35+
entry: uv run --frozen ruff
3636
args: ["check", "--fix", "--exit-non-zero-on-fix"]
3737
types: [python]
3838
language: system
3939
pass_filenames: false
4040
exclude: ^README\.md$
4141
- id: pyright
4242
name: pyright
43-
entry: uv run pyright
43+
entry: uv run --frozen pyright
4444
language: system
4545
types: [python]
4646
pass_filenames: false
@@ -52,7 +52,7 @@ repos:
5252
pass_filenames: false
5353
- id: readme-snippets
5454
name: Check README snippets are up to date
55-
entry: uv run scripts/update_readme_snippets.py --check
55+
entry: uv run --frozen python scripts/update_readme_snippets.py --check
5656
language: system
5757
files: ^(README\.md|examples/.*\.py|scripts/update_readme_snippets\.py)$
5858
pass_filenames: false

README.md

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,33 @@
3131
- [Prompts](#prompts)
3232
- [Images](#images)
3333
- [Context](#context)
34+
- [Getting Context in Functions](#getting-context-in-functions)
35+
- [Context Properties and Methods](#context-properties-and-methods)
3436
- [Completions](#completions)
3537
- [Elicitation](#elicitation)
3638
- [Sampling](#sampling)
3739
- [Logging and Notifications](#logging-and-notifications)
3840
- [Authentication](#authentication)
3941
- [FastMCP Properties](#fastmcp-properties)
40-
- [Session Properties](#session-properties-and-methods)
42+
- [Session Properties and Methods](#session-properties-and-methods)
4143
- [Request Context Properties](#request-context-properties)
4244
- [Running Your Server](#running-your-server)
4345
- [Development Mode](#development-mode)
4446
- [Claude Desktop Integration](#claude-desktop-integration)
4547
- [Direct Execution](#direct-execution)
4648
- [Streamable HTTP Transport](#streamable-http-transport)
49+
- [CORS Configuration for Browser-Based Clients](#cors-configuration-for-browser-based-clients)
4750
- [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server)
51+
- [StreamableHTTP servers](#streamablehttp-servers)
52+
- [Basic mounting](#basic-mounting)
53+
- [Host-based routing](#host-based-routing)
54+
- [Multiple servers with path configuration](#multiple-servers-with-path-configuration)
55+
- [Path configuration at initialization](#path-configuration-at-initialization)
56+
- [SSE servers](#sse-servers)
4857
- [Advanced Usage](#advanced-usage)
4958
- [Low-Level Server](#low-level-server)
59+
- [Structured Output Support](#structured-output-support)
60+
- [Pagination (Advanced)](#pagination-advanced)
5061
- [Writing MCP Clients](#writing-mcp-clients)
5162
- [Client Display Utilities](#client-display-utilities)
5263
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
@@ -400,7 +411,7 @@ def get_weather(city: str) -> WeatherData:
400411
"""Get weather for a city - returns structured data."""
401412
# Simulated weather data
402413
return WeatherData(
403-
temperature=72.5,
414+
temperature=22.5,
404415
humidity=45.0,
405416
condition="sunny",
406417
wind_speed=5.2,
@@ -505,6 +516,41 @@ def debug_error(error: str) -> list[base.Message]:
505516
_Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_prompt.py)_
506517
<!-- /snippet-source -->
507518

519+
### Icons
520+
521+
MCP servers can provide icons for UI display. Icons can be added to the server implementation, tools, resources, and prompts:
522+
523+
```python
524+
from mcp.server.fastmcp import FastMCP, Icon
525+
526+
# Create an icon from a file path or URL
527+
icon = Icon(
528+
src="icon.png",
529+
mimeType="image/png",
530+
sizes="64x64"
531+
)
532+
533+
# Add icons to server
534+
mcp = FastMCP(
535+
"My Server",
536+
website_url="https://example.com",
537+
icons=[icon]
538+
)
539+
540+
# Add icons to tools, resources, and prompts
541+
@mcp.tool(icons=[icon])
542+
def my_tool():
543+
"""Tool with an icon."""
544+
return "result"
545+
546+
@mcp.resource("demo://resource", icons=[icon])
547+
def my_resource():
548+
"""Resource with an icon."""
549+
return "content"
550+
```
551+
552+
_Full example: [examples/fastmcp/icons_demo.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/fastmcp/icons_demo.py)_
553+
508554
### Images
509555

510556
FastMCP provides an `Image` class that automatically handles image data:
@@ -736,6 +782,8 @@ async def book_table(date: str, time: str, party_size: int, ctx: Context[ServerS
736782
_Full example: [examples/snippets/servers/elicitation.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/elicitation.py)_
737783
<!-- /snippet-source -->
738784

785+
Elicitation schemas support default values for all field types. Default values are automatically included in the JSON schema sent to clients, allowing them to pre-populate forms.
786+
739787
The `elicit()` method returns an `ElicitationResult` with:
740788

741789
- `action`: "accept", "decline", or "cancel"
@@ -885,6 +933,8 @@ The FastMCP server instance accessible via `ctx.fastmcp` provides access to serv
885933

886934
- `ctx.fastmcp.name` - The server's name as defined during initialization
887935
- `ctx.fastmcp.instructions` - Server instructions/description provided to clients
936+
- `ctx.fastmcp.website_url` - Optional website URL for the server
937+
- `ctx.fastmcp.icons` - Optional list of icons for UI display
888938
- `ctx.fastmcp.settings` - Complete server configuration object containing:
889939
- `debug` - Debug mode flag
890940
- `log_level` - Current logging level
@@ -1727,6 +1777,116 @@ Tools can return data in three ways:
17271777

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

1780+
### Pagination (Advanced)
1781+
1782+
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.
1783+
1784+
#### Server-side Implementation
1785+
1786+
<!-- snippet-source examples/snippets/servers/pagination_example.py -->
1787+
```python
1788+
"""
1789+
Example of implementing pagination with MCP server decorators.
1790+
"""
1791+
1792+
from pydantic import AnyUrl
1793+
1794+
import mcp.types as types
1795+
from mcp.server.lowlevel import Server
1796+
1797+
# Initialize the server
1798+
server = Server("paginated-server")
1799+
1800+
# Sample data to paginate
1801+
ITEMS = [f"Item {i}" for i in range(1, 101)] # 100 items
1802+
1803+
1804+
@server.list_resources()
1805+
async def list_resources_paginated(request: types.ListResourcesRequest) -> types.ListResourcesResult:
1806+
"""List resources with pagination support."""
1807+
page_size = 10
1808+
1809+
# Extract cursor from request params
1810+
cursor = request.params.cursor if request.params is not None else None
1811+
1812+
# Parse cursor to get offset
1813+
start = 0 if cursor is None else int(cursor)
1814+
end = start + page_size
1815+
1816+
# Get page of resources
1817+
page_items = [
1818+
types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}")
1819+
for item in ITEMS[start:end]
1820+
]
1821+
1822+
# Determine next cursor
1823+
next_cursor = str(end) if end < len(ITEMS) else None
1824+
1825+
return types.ListResourcesResult(resources=page_items, nextCursor=next_cursor)
1826+
```
1827+
1828+
_Full example: [examples/snippets/servers/pagination_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/pagination_example.py)_
1829+
<!-- /snippet-source -->
1830+
1831+
#### Client-side Consumption
1832+
1833+
<!-- snippet-source examples/snippets/clients/pagination_client.py -->
1834+
```python
1835+
"""
1836+
Example of consuming paginated MCP endpoints from a client.
1837+
"""
1838+
1839+
import asyncio
1840+
1841+
from mcp.client.session import ClientSession
1842+
from mcp.client.stdio import StdioServerParameters, stdio_client
1843+
from mcp.types import Resource
1844+
1845+
1846+
async def list_all_resources() -> None:
1847+
"""Fetch all resources using pagination."""
1848+
async with stdio_client(StdioServerParameters(command="uv", args=["run", "mcp-simple-pagination"])) as (
1849+
read,
1850+
write,
1851+
):
1852+
async with ClientSession(read, write) as session:
1853+
await session.initialize()
1854+
1855+
all_resources: list[Resource] = []
1856+
cursor = None
1857+
1858+
while True:
1859+
# Fetch a page of resources
1860+
result = await session.list_resources(cursor=cursor)
1861+
all_resources.extend(result.resources)
1862+
1863+
print(f"Fetched {len(result.resources)} resources")
1864+
1865+
# Check if there are more pages
1866+
if result.nextCursor:
1867+
cursor = result.nextCursor
1868+
else:
1869+
break
1870+
1871+
print(f"Total resources: {len(all_resources)}")
1872+
1873+
1874+
if __name__ == "__main__":
1875+
asyncio.run(list_all_resources())
1876+
```
1877+
1878+
_Full example: [examples/snippets/clients/pagination_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/pagination_client.py)_
1879+
<!-- /snippet-source -->
1880+
1881+
#### Key Points
1882+
1883+
- **Cursors are opaque strings** - the server defines the format (numeric offsets, timestamps, etc.)
1884+
- **Return `nextCursor=None`** when there are no more pages
1885+
- **Backward compatible** - clients that don't support pagination will still work (they'll just get the first page)
1886+
- **Flexible page sizes** - Each endpoint can define its own page size based on data characteristics
1887+
1888+
See the [simple-pagination example](examples/servers/simple-pagination) for a complete implementation.
1889+
17301890
### Writing MCP Clients
17311891

17321892
The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports):
@@ -2137,6 +2297,7 @@ MCP servers declare capabilities during initialization:
21372297

21382298
## Documentation
21392299

2300+
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
21402301
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
21412302
- [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
21422303
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)

examples/clients/simple-auth-client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@ mcp> quit
7171
## Configuration
7272

7373
- `MCP_SERVER_PORT` - Server URL (default: 8000)
74-
- `MCP_TRANSPORT_TYPE` - Transport type: `streamable_http` (default) or `sse`
74+
- `MCP_TRANSPORT_TYPE` - Transport type: `streamable-http` (default) or `sse`

examples/clients/simple-auth-client/mcp_simple_auth_client/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def get_state(self):
150150
class SimpleAuthClient:
151151
"""Simple MCP client with auth support."""
152152

153-
def __init__(self, server_url: str, transport_type: str = "streamable_http"):
153+
def __init__(self, server_url: str, transport_type: str = "streamable-http"):
154154
self.server_url = server_url
155155
self.transport_type = transport_type
156156
self.session: ClientSession | None = None
@@ -334,10 +334,10 @@ async def main():
334334
# Default server URL - can be overridden with environment variable
335335
# Most MCP streamable HTTP servers use /mcp as the endpoint
336336
server_url = os.getenv("MCP_SERVER_PORT", 8000)
337-
transport_type = os.getenv("MCP_TRANSPORT_TYPE", "streamable_http")
337+
transport_type = os.getenv("MCP_TRANSPORT_TYPE", "streamable-http")
338338
server_url = (
339339
f"http://localhost:{server_url}/mcp"
340-
if transport_type == "streamable_http"
340+
if transport_type == "streamable-http"
341341
else f"http://localhost:{server_url}/sse"
342342
)
343343

examples/fastmcp/icons_demo.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
FastMCP Icons Demo Server
3+
4+
Demonstrates using icons with tools, resources, prompts, and implementation.
5+
"""
6+
7+
import base64
8+
from pathlib import Path
9+
10+
from mcp.server.fastmcp import FastMCP, Icon
11+
12+
# Load the icon file and convert to data URI
13+
icon_path = Path(__file__).parent / "mcp.png"
14+
icon_data = base64.standard_b64encode(icon_path.read_bytes()).decode()
15+
icon_data_uri = f"data:image/png;base64,{icon_data}"
16+
17+
icon_data = Icon(src=icon_data_uri, mimeType="image/png", sizes=["64x64"])
18+
19+
# Create server with icons in implementation
20+
mcp = FastMCP("Icons Demo Server", website_url="https://github.com/modelcontextprotocol/python-sdk", icons=[icon_data])
21+
22+
23+
@mcp.tool(icons=[icon_data])
24+
def demo_tool(message: str) -> str:
25+
"""A demo tool with an icon."""
26+
return message
27+
28+
29+
@mcp.resource("demo://readme", icons=[icon_data])
30+
def readme_resource() -> str:
31+
"""A demo resource with an icon"""
32+
return "This resource has an icon"
33+
34+
35+
@mcp.prompt("prompt_with_icon", icons=[icon_data])
36+
def prompt_with_icon(text: str) -> str:
37+
"""A demo prompt with an icon"""
38+
return text
39+
40+
41+
@mcp.tool(
42+
icons=[
43+
Icon(src=icon_data_uri, mimeType="image/png", sizes=["16x16"]),
44+
Icon(src=icon_data_uri, mimeType="image/png", sizes=["32x32"]),
45+
Icon(src=icon_data_uri, mimeType="image/png", sizes=["64x64"]),
46+
]
47+
)
48+
def multi_icon_tool(action: str) -> str:
49+
"""A tool demonstrating multiple icons."""
50+
return "multi_icon_tool"
51+
52+
53+
if __name__ == "__main__":
54+
# Run the server
55+
mcp.run()

0 commit comments

Comments
 (0)