Skip to content

Commit f401457

Browse files
docs: add snippet verification for docs/ pages
Extend the snippet verification system to cover docs/*.md in addition to README.md. Create runnable example files for all 18 new code snippets. Script changes: - Rename update_readme_snippets.py -> update_doc_snippets.py - Process README.md + all docs/*.md files via glob - Update CI job and pre-commit hook references New snippet files (18): - examples/snippets/servers/: audio, binary resources, resource templates, resource subscriptions, embedded resource results (text + binary), prompt embedded resources, prompt image content, prompt change notifications, tool change notifications, elicitation enum/complete, set logging level, JSON schema - examples/snippets/clients/: roots, SSE client, logging, cancellation All snippet-source tags verified: uv run scripts/update_doc_snippets.py --check
1 parent 09b9e62 commit f401457

26 files changed

+446
-43
lines changed

.github/workflows/shared.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
uv run --frozen --no-sync coverage combine
6262
uv run --frozen --no-sync coverage report
6363
64-
readme-snippets:
64+
doc-snippets:
6565
runs-on: ubuntu-latest
6666
steps:
6767
- uses: actions/checkout@v5
@@ -74,5 +74,5 @@ jobs:
7474
- name: Install dependencies
7575
run: uv sync --frozen --all-extras --python 3.10
7676

77-
- name: Check README snippets are up to date
78-
run: uv run --frozen scripts/update_readme_snippets.py --check
77+
- name: Check doc snippets are up to date
78+
run: uv run --frozen scripts/update_doc_snippets.py --check

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ repos:
5555
language: system
5656
files: ^(pyproject\.toml|uv\.lock)$
5757
pass_filenames: false
58-
- id: readme-snippets
59-
name: Check README snippets are up to date
60-
entry: uv run --frozen python scripts/update_readme_snippets.py --check
58+
- id: doc-snippets
59+
name: Check doc snippets are up to date
60+
entry: uv run --frozen python scripts/update_doc_snippets.py --check
6161
language: system
62-
files: ^(README\.md|examples/.*\.py|scripts/update_readme_snippets\.py)$
62+
files: ^(README\.md|docs/.*\.md|examples/.*\.py|scripts/update_doc_snippets\.py)$
6363
pass_filenames: false

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ if __name__ == "__main__":
131131
mcp.run(transport="streamable-http")
132132
```
133133

134-
_Full example: [examples/snippets/servers/fastmcp_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/fastmcp_quickstart.py)_
134+
_Full example: [examples/snippets/servers/fastmcp_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/fastmcp_quickstart.py)_
135135
<!-- /snippet-source -->
136136

137137
You can install this server in [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) and interact with it right away. First, run the server:

docs/client.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ For OAuth 2.1 client authentication, see [Authorization](authorization.md#client
225225

226226
Clients can provide a `list_roots_callback` so that servers can discover the client's workspace roots (directories, project folders, etc.):
227227

228+
<!-- snippet-source examples/snippets/clients/roots_example.py -->
228229
```python
229230
from mcp import ClientSession, types
230231
from mcp.shared.context import RequestContext
@@ -250,6 +251,9 @@ session = ClientSession(
250251
)
251252
```
252253

254+
_Full example: [examples/snippets/clients/roots_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/clients/roots_example.py)_
255+
<!-- /snippet-source -->
256+
253257
When a `list_roots_callback` is provided, the client automatically declares the `roots` capability (with `listChanged=True`) during initialization.
254258

255259
### Roots Change Notifications
@@ -265,6 +269,7 @@ await session.send_roots_list_changed()
265269

266270
For servers that use the older SSE transport, use `sse_client()` from `mcp.client.sse`:
267271

272+
<!-- snippet-source examples/snippets/clients/sse_client.py -->
268273
```python
269274
import asyncio
270275

@@ -284,6 +289,9 @@ async def main():
284289
asyncio.run(main())
285290
```
286291

292+
_Full example: [examples/snippets/clients/sse_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/clients/sse_client.py)_
293+
<!-- /snippet-source -->
294+
287295
The `sse_client()` function accepts optional `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters. The SSE transport is considered legacy; prefer [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) for new servers.
288296

289297
## Ping
@@ -302,6 +310,7 @@ result = await session.send_ping()
302310

303311
Pass a `logging_callback` to receive log messages from the server:
304312

313+
<!-- snippet-source examples/snippets/clients/logging_client.py -->
305314
```python
306315
from mcp import ClientSession, types
307316

@@ -318,6 +327,9 @@ session = ClientSession(
318327
)
319328
```
320329

330+
_Full example: [examples/snippets/clients/logging_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/clients/logging_client.py)_
331+
<!-- /snippet-source -->
332+
321333
### Setting the Server Log Level
322334

323335
Request that the server change its minimum log level:

docs/protocol.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,29 @@ Both return an `EmptyResult` on success. If the remote side does not respond wit
4242

4343
Either side can cancel a previously-issued request by sending a `CancelledNotification`:
4444

45+
<!-- snippet-source examples/snippets/clients/cancellation.py -->
4546
```python
4647
import mcp.types as types
47-
48-
# Send a cancellation notification
49-
await session.send_notification(
50-
types.ClientNotification(
51-
types.CancelledNotification(
52-
params=types.CancelledNotificationParams(
53-
requestId="request-id-to-cancel",
54-
reason="User navigated away",
48+
from mcp import ClientSession
49+
50+
51+
async def cancel_request(session: ClientSession) -> None:
52+
"""Send a cancellation notification for a previously-issued request."""
53+
await session.send_notification(
54+
types.ClientNotification(
55+
types.CancelledNotification(
56+
params=types.CancelledNotificationParams(
57+
requestId="request-id-to-cancel",
58+
reason="User navigated away",
59+
)
5560
)
5661
)
5762
)
58-
)
5963
```
6064

65+
_Full example: [examples/snippets/clients/cancellation.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/clients/cancellation.py)_
66+
<!-- /snippet-source -->
67+
6168
The `CancelledNotificationParams` fields:
6269

6370
- `requestId` (optional): The ID of the request to cancel. Required for non-task cancellations.
@@ -106,6 +113,7 @@ During initialization, the client sends `LATEST_PROTOCOL_VERSION`. If the server
106113

107114
MCP uses [JSON Schema 2020-12](https://json-schema.org/draft/2020-12) for tool input schemas, output schemas, and elicitation schemas. When using Pydantic models, schemas are generated automatically via `model_json_schema()`:
108115

116+
<!-- snippet-source examples/snippets/servers/json_schema_example.py -->
109117
```python
110118
from pydantic import BaseModel, Field
111119

@@ -132,6 +140,9 @@ schema = SearchParams.model_json_schema()
132140
# }
133141
```
134142

143+
_Full example: [examples/snippets/servers/json_schema_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/json_schema_example.py)_
144+
<!-- /snippet-source -->
145+
135146
For FastMCP tools, input schemas are derived automatically from function signatures. For structured output, the output schema is derived from the return type annotation.
136147

137148
## Pagination

docs/server.md

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ _Full example: [examples/snippets/servers/basic_resource.py](https://github.com/
105105

106106
Resources with URI parameters (e.g., `{name}`) are registered as templates. When a client reads a templated resource, the URI parameters are extracted and passed to the function:
107107

108+
<!-- snippet-source examples/snippets/servers/resource_templates.py -->
108109
```python
109110
from mcp.server.fastmcp import FastMCP
110111

@@ -117,6 +118,9 @@ def get_user_profile(user_id: str) -> str:
117118
return f'{{"user_id": "{user_id}", "name": "User {user_id}"}}'
118119
```
119120

121+
_Full example: [examples/snippets/servers/resource_templates.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/resource_templates.py)_
122+
<!-- /snippet-source -->
123+
120124
Clients read a template resource by providing a concrete URI:
121125

122126
```python
@@ -137,6 +141,7 @@ def get_readme(owner: str, repo: str) -> str:
137141

138142
Resources can return binary data by returning `bytes` instead of `str`. Set the `mime_type` to indicate the content type:
139143

144+
<!-- snippet-source examples/snippets/servers/binary_resources.py -->
140145
```python
141146
from mcp.server.fastmcp import FastMCP
142147

@@ -150,14 +155,17 @@ def get_logo() -> bytes:
150155
return f.read()
151156
```
152157

158+
_Full example: [examples/snippets/servers/binary_resources.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/binary_resources.py)_
159+
<!-- /snippet-source -->
160+
153161
Binary content is automatically base64-encoded and returned as `BlobResourceContents` in the MCP response.
154162

155163
#### Resource Subscriptions
156164

157165
Clients can subscribe to resource updates. Use the low-level server API to handle subscription and unsubscription requests:
158166

167+
<!-- snippet-source examples/snippets/servers/resource_subscriptions.py -->
159168
```python
160-
import mcp.types as types
161169
from mcp.server.lowlevel import Server
162170

163171
server = Server("Subscription Example")
@@ -178,6 +186,9 @@ async def handle_unsubscribe(uri) -> None:
178186
subscriptions[str(uri)].discard("current_session")
179187
```
180188

189+
_Full example: [examples/snippets/servers/resource_subscriptions.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/resource_subscriptions.py)_
190+
<!-- /snippet-source -->
191+
181192
When a subscribed resource changes, notify clients with `send_resource_updated()`:
182193

183194
```python
@@ -468,6 +479,7 @@ _Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/mo
468479

469480
Prompts can include embedded resources to provide file contents or data alongside the conversation messages:
470481

482+
<!-- snippet-source examples/snippets/servers/prompt_embedded_resources.py -->
471483
```python
472484
import mcp.types as types
473485
from mcp.server.fastmcp import FastMCP
@@ -497,10 +509,14 @@ def review_file(filename: str) -> list[base.Message]:
497509
]
498510
```
499511

512+
_Full example: [examples/snippets/servers/prompt_embedded_resources.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/prompt_embedded_resources.py)_
513+
<!-- /snippet-source -->
514+
500515
#### Prompts with Image Content
501516

502517
Prompts can include images using `ImageContent` or the `Image` helper class:
503518

519+
<!-- snippet-source examples/snippets/servers/prompt_image_content.py -->
504520
```python
505521
import mcp.types as types
506522
from mcp.server.fastmcp import FastMCP
@@ -524,10 +540,14 @@ def describe_image(image_path: str) -> list[base.Message]:
524540
]
525541
```
526542

543+
_Full example: [examples/snippets/servers/prompt_image_content.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/prompt_image_content.py)_
544+
<!-- /snippet-source -->
545+
527546
#### Prompt Change Notifications
528547

529548
When your server dynamically adds or removes prompts, notify connected clients:
530549

550+
<!-- snippet-source examples/snippets/servers/prompt_change_notifications.py -->
531551
```python
532552
from mcp.server.fastmcp import Context, FastMCP
533553
from mcp.server.session import ServerSession
@@ -543,6 +563,9 @@ async def update_prompts(ctx: Context[ServerSession, None]) -> str:
543563
return "Prompts updated"
544564
```
545565

566+
_Full example: [examples/snippets/servers/prompt_change_notifications.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/prompt_change_notifications.py)_
567+
<!-- /snippet-source -->
568+
546569
### Icons
547570

548571
MCP servers can provide icons for UI display. Icons can be added to the server implementation, tools, resources, and prompts:
@@ -608,6 +631,7 @@ _Full example: [examples/snippets/servers/images.py](https://github.com/modelcon
608631

609632
FastMCP provides an `Audio` class for returning audio data from tools, similar to `Image`:
610633

634+
<!-- snippet-source examples/snippets/servers/audio_example.py -->
611635
```python
612636
from mcp.server.fastmcp import FastMCP
613637
from mcp.server.fastmcp.utilities.types import Audio
@@ -627,12 +651,16 @@ def get_audio_from_bytes(raw_audio: bytes) -> Audio:
627651
return Audio(data=raw_audio, format="wav")
628652
```
629653

654+
_Full example: [examples/snippets/servers/audio_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/audio_example.py)_
655+
<!-- /snippet-source -->
656+
630657
The `Audio` class accepts `path` or `data` (mutually exclusive) and an optional `format` string. Supported formats include `wav`, `mp3`, `ogg`, `flac`, `aac`, and `m4a`. When using a file path, the MIME type is inferred from the file extension.
631658

632659
### Embedded Resource Results
633660

634661
Tools can return `EmbeddedResource` to attach file contents or data inline in the result:
635662

663+
<!-- snippet-source examples/snippets/servers/embedded_resource_results.py -->
636664
```python
637665
from mcp.server.fastmcp import FastMCP
638666
from mcp.types import EmbeddedResource, TextResourceContents
@@ -655,13 +683,20 @@ def read_config(path: str) -> EmbeddedResource:
655683
)
656684
```
657685

686+
_Full example: [examples/snippets/servers/embedded_resource_results.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/embedded_resource_results.py)_
687+
<!-- /snippet-source -->
688+
658689
For binary embedded resources, use `BlobResourceContents` with base64-encoded data:
659690

691+
<!-- snippet-source examples/snippets/servers/embedded_resource_results_binary.py -->
660692
```python
661693
import base64
662694

695+
from mcp.server.fastmcp import FastMCP
663696
from mcp.types import BlobResourceContents, EmbeddedResource
664697

698+
mcp = FastMCP("Binary Embedded Resource Example")
699+
665700

666701
@mcp.tool()
667702
def read_binary_file(path: str) -> EmbeddedResource:
@@ -678,10 +713,14 @@ def read_binary_file(path: str) -> EmbeddedResource:
678713
)
679714
```
680715

716+
_Full example: [examples/snippets/servers/embedded_resource_results_binary.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/embedded_resource_results_binary.py)_
717+
<!-- /snippet-source -->
718+
681719
### Tool Change Notifications
682720

683721
When your server dynamically adds or removes tools at runtime, notify connected clients so they can refresh their tool list:
684722

723+
<!-- snippet-source examples/snippets/servers/tool_change_notifications.py -->
685724
```python
686725
from mcp.server.fastmcp import Context, FastMCP
687726
from mcp.server.session import ServerSession
@@ -700,6 +739,9 @@ async def register_plugin(name: str, ctx: Context[ServerSession, None]) -> str:
700739
return f"Plugin '{name}' registered"
701740
```
702741

742+
_Full example: [examples/snippets/servers/tool_change_notifications.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/tool_change_notifications.py)_
743+
<!-- /snippet-source -->
744+
703745
### Context
704746

705747
The Context object is automatically injected into tool and resource functions that request it via type hints. It provides access to MCP capabilities like logging, progress reporting, resource reading, user interaction, and request metadata.
@@ -979,6 +1021,7 @@ The `elicit()` method returns an `ElicitationResult` with:
9791021

9801022
To present a dropdown or selection list in elicitation forms, use `json_schema_extra` with an `enum` key on a `str` field. Do not use `Literal` -- use a plain `str` field with the enum constraint in the JSON schema:
9811023

1024+
<!-- snippet-source examples/snippets/servers/elicitation_enum.py -->
9821025
```python
9831026
from pydantic import BaseModel, Field
9841027

@@ -1007,10 +1050,14 @@ async def pick_color(ctx: Context[ServerSession, None]) -> str:
10071050
return "No color selected"
10081051
```
10091052

1053+
_Full example: [examples/snippets/servers/elicitation_enum.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/elicitation_enum.py)_
1054+
<!-- /snippet-source -->
1055+
10101056
#### Elicitation Complete Notification
10111057

10121058
For URL mode elicitations, send a completion notification after the out-of-band interaction finishes. This tells the client that the elicitation is done and it may retry any blocked requests:
10131059

1060+
<!-- snippet-source examples/snippets/servers/elicitation_complete.py -->
10141061
```python
10151062
from mcp.server.fastmcp import Context, FastMCP
10161063
from mcp.server.session import ServerSession
@@ -1019,9 +1066,7 @@ mcp = FastMCP("Elicit Complete Example")
10191066

10201067

10211068
@mcp.tool()
1022-
async def handle_oauth_callback(
1023-
elicitation_id: str, ctx: Context[ServerSession, None]
1024-
) -> str:
1069+
async def handle_oauth_callback(elicitation_id: str, ctx: Context[ServerSession, None]) -> str:
10251070
"""Called when OAuth flow completes out-of-band."""
10261071
# ... process the callback ...
10271072

@@ -1031,6 +1076,9 @@ async def handle_oauth_callback(
10311076
return "Authorization complete"
10321077
```
10331078

1079+
_Full example: [examples/snippets/servers/elicitation_complete.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/elicitation_complete.py)_
1080+
<!-- /snippet-source -->
1081+
10341082
### Sampling
10351083

10361084
Tools can interact with LLMs through sampling (generating text):
@@ -1102,6 +1150,7 @@ _Full example: [examples/snippets/servers/notifications.py](https://github.com/m
11021150

11031151
Clients can request a minimum logging level via `logging/setLevel`. Use the low-level server API to handle this:
11041152

1153+
<!-- snippet-source examples/snippets/servers/set_logging_level.py -->
11051154
```python
11061155
import mcp.types as types
11071156
from mcp.server.lowlevel import Server
@@ -1118,6 +1167,9 @@ async def handle_set_level(level: types.LoggingLevel) -> None:
11181167
current_level = level
11191168
```
11201169

1170+
_Full example: [examples/snippets/servers/set_logging_level.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/set_logging_level.py)_
1171+
<!-- /snippet-source -->
1172+
11211173
When this handler is registered, the server automatically declares the `logging` capability during initialization.
11221174

11231175
### Authentication

0 commit comments

Comments
 (0)