Skip to content

Commit bdfd063

Browse files
Replace update_readme_snippets.py with unified sync_snippets.py
The new script is a superset of the old one. It continues to handle the existing `<!-- snippet-source -->` markers in `README.v2.md`, and adds support for syncing code snippets into Python docstrings and markdown docs under `docs/`. New capabilities beyond the old script: - Region extraction from example files using `# region` / `# endregion` markers, so a single example file can provide multiple snippets - All source paths resolve relative to the repository root - Scans `src/**/*.py` and `docs/**/*.md` in addition to the README - Caches file contents and extracted regions for efficiency The marker format uses HTML comments (`<!-- snippet-source -->` / `<!-- /snippet-source -->`), which are invisible when rendered by `mkdocstrings` and do not interfere with `pymdownx.superfences` code fence parsing.
1 parent cb07ade commit bdfd063

File tree

5 files changed

+335
-196
lines changed

5 files changed

+335
-196
lines changed

.github/workflows/shared.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,5 @@ jobs:
9191
- name: Install dependencies
9292
run: uv sync --frozen --all-extras --python 3.10
9393

94-
- name: Check README snippets are up to date
95-
run: uv run --frozen scripts/update_readme_snippets.py --check --readme README.v2.md
94+
- name: Check snippets are up to date
95+
run: uv run --frozen python scripts/sync_snippets.py --check

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ repos:
6262
language: fail
6363
files: ^README\.md$
6464
- id: readme-snippets
65-
name: Check README snippets are up to date
66-
entry: uv run --frozen python scripts/update_readme_snippets.py --check
65+
name: Check snippets are up to date
66+
entry: uv run --frozen python scripts/sync_snippets.py --check
6767
language: system
68-
files: ^(README\.v2\.md|examples/.*\.py|scripts/update_readme_snippets\.py)$
68+
files: ^(README\.v2\.md|examples/.*\.py|src/mcp/.*\.py|docs/.*\.md|scripts/sync_snippets\.py)$
6969
pass_filenames: false

README.v2.md

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ def greet_user(name: str, style: str = "friendly") -> str:
181181
if __name__ == "__main__":
182182
mcp.run(transport="streamable-http", json_response=True)
183183
```
184+
<!-- /snippet-source -->
184185

185186
_Full example: [examples/snippets/servers/mcpserver_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/mcpserver_quickstart.py)_
186-
<!-- /snippet-source -->
187187

188188
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:
189189

@@ -279,9 +279,9 @@ def query_db(ctx: Context[AppContext]) -> str:
279279
db = ctx.request_context.lifespan_context.db
280280
return db.query()
281281
```
282+
<!-- /snippet-source -->
282283

283284
_Full example: [examples/snippets/servers/lifespan_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lifespan_example.py)_
284-
<!-- /snippet-source -->
285285

286286
### Resources
287287

@@ -310,9 +310,9 @@ def get_settings() -> str:
310310
"debug": false
311311
}"""
312312
```
313+
<!-- /snippet-source -->
313314

314315
_Full example: [examples/snippets/servers/basic_resource.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_resource.py)_
315-
<!-- /snippet-source -->
316316

317317
### Tools
318318

@@ -337,9 +337,9 @@ def get_weather(city: str, unit: str = "celsius") -> str:
337337
# This would normally call a weather API
338338
return f"Weather in {city}: 22degrees{unit[0].upper()}"
339339
```
340+
<!-- /snippet-source -->
340341

341342
_Full example: [examples/snippets/servers/basic_tool.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_tool.py)_
342-
<!-- /snippet-source -->
343343

344344
Tools can optionally receive a Context object by including a parameter with the `Context` type annotation. This context is automatically injected by the MCPServer framework and provides access to MCP capabilities:
345345

@@ -367,9 +367,9 @@ async def long_running_task(task_name: str, ctx: Context[ServerSession, None], s
367367

368368
return f"Task '{task_name}' completed"
369369
```
370+
<!-- /snippet-source -->
370371

371372
_Full example: [examples/snippets/servers/tool_progress.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py)_
372-
<!-- /snippet-source -->
373373

374374
#### Structured Output
375375

@@ -452,9 +452,9 @@ def empty_result_tool() -> CallToolResult:
452452
"""For empty results, return CallToolResult with empty content."""
453453
return CallToolResult(content=[])
454454
```
455+
<!-- /snippet-source -->
455456

456457
_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)_
457-
<!-- /snippet-source -->
458458

459459
**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`.
460460

@@ -558,9 +558,9 @@ def get_temperature(city: str) -> float:
558558
return 22.5
559559
# Returns: {"result": 22.5}
560560
```
561+
<!-- /snippet-source -->
561562

562563
_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_
563-
<!-- /snippet-source -->
564564

565565
### Prompts
566566

@@ -587,9 +587,9 @@ def debug_error(error: str) -> list[base.Message]:
587587
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
588588
]
589589
```
590+
<!-- /snippet-source -->
590591

591592
_Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_prompt.py)_
592-
<!-- /snippet-source -->
593593

594594
### Icons
595595

@@ -648,9 +648,9 @@ def create_thumbnail(image_path: str) -> Image:
648648
img.thumbnail((100, 100))
649649
return Image(data=img.tobytes(), format="png")
650650
```
651+
<!-- /snippet-source -->
651652

652653
_Full example: [examples/snippets/servers/images.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/images.py)_
653-
<!-- /snippet-source -->
654654

655655
### Context
656656

@@ -715,9 +715,9 @@ async def long_running_task(task_name: str, ctx: Context[ServerSession, None], s
715715

716716
return f"Task '{task_name}' completed"
717717
```
718+
<!-- /snippet-source -->
718719

719720
_Full example: [examples/snippets/servers/tool_progress.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py)_
720-
<!-- /snippet-source -->
721721

722722
### Completions
723723

@@ -805,9 +805,9 @@ def main():
805805
if __name__ == "__main__":
806806
main()
807807
```
808+
<!-- /snippet-source -->
808809

809810
_Full example: [examples/snippets/clients/completion_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/completion_client.py)_
810-
<!-- /snippet-source -->
811811
### Elicitation
812812

813813
Request additional information from users. This example shows an Elicitation during a Tool Call:
@@ -914,9 +914,9 @@ async def connect_service(service_name: str, ctx: Context[ServerSession, None])
914914
]
915915
)
916916
```
917+
<!-- /snippet-source -->
917918

918919
_Full example: [examples/snippets/servers/elicitation.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/elicitation.py)_
919-
<!-- /snippet-source -->
920920

921921
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.
922922

@@ -959,9 +959,9 @@ async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
959959
return result.content.text
960960
return str(result.content)
961961
```
962+
<!-- /snippet-source -->
962963

963964
_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_
964-
<!-- /snippet-source -->
965965

966966
### Logging and Notifications
967967

@@ -989,9 +989,9 @@ async def process_data(data: str, ctx: Context[ServerSession, None]) -> str:
989989

990990
return f"Processed: {data}"
991991
```
992+
<!-- /snippet-source -->
992993

993994
_Full example: [examples/snippets/servers/notifications.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/notifications.py)_
994-
<!-- /snippet-source -->
995995

996996
### Authentication
997997

@@ -1049,9 +1049,9 @@ async def get_weather(city: str = "London") -> dict[str, str]:
10491049
if __name__ == "__main__":
10501050
mcp.run(transport="streamable-http", json_response=True)
10511051
```
1052+
<!-- /snippet-source -->
10521053

10531054
_Full example: [examples/snippets/servers/oauth_server.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/oauth_server.py)_
1054-
<!-- /snippet-source -->
10551055

10561056
For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](examples/servers/simple-auth/).
10571057

@@ -1223,9 +1223,9 @@ def main():
12231223
if __name__ == "__main__":
12241224
main()
12251225
```
1226+
<!-- /snippet-source -->
12261227

12271228
_Full example: [examples/snippets/servers/direct_execution.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_execution.py)_
1228-
<!-- /snippet-source -->
12291229

12301230
Run it with:
12311231

@@ -1272,9 +1272,9 @@ if __name__ == "__main__":
12721272
# Stateful server with session persistence
12731273
# mcp.run(transport="streamable-http")
12741274
```
1275+
<!-- /snippet-source -->
12751276

12761277
_Full example: [examples/snippets/servers/streamable_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)_
1277-
<!-- /snippet-source -->
12781278

12791279
You can mount multiple MCPServer servers in a Starlette application:
12801280

@@ -1334,9 +1334,9 @@ app = Starlette(
13341334
# echo_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
13351335
# math_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
13361336
```
1337+
<!-- /snippet-source -->
13371338

13381339
_Full example: [examples/snippets/servers/streamable_starlette_mount.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_starlette_mount.py)_
1339-
<!-- /snippet-source -->
13401340

13411341
For low level server with Streamable HTTP implementations, see:
13421342

@@ -1429,9 +1429,9 @@ app = Starlette(
14291429
lifespan=lifespan,
14301430
)
14311431
```
1432+
<!-- /snippet-source -->
14321433

14331434
_Full example: [examples/snippets/servers/streamable_http_basic_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_basic_mounting.py)_
1434-
<!-- /snippet-source -->
14351435

14361436
##### Host-based routing
14371437

@@ -1476,9 +1476,9 @@ app = Starlette(
14761476
lifespan=lifespan,
14771477
)
14781478
```
1479+
<!-- /snippet-source -->
14791480

14801481
_Full example: [examples/snippets/servers/streamable_http_host_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_host_mounting.py)_
1481-
<!-- /snippet-source -->
14821482

14831483
##### Multiple servers with path configuration
14841484

@@ -1533,9 +1533,9 @@ app = Starlette(
15331533
lifespan=lifespan,
15341534
)
15351535
```
1536+
<!-- /snippet-source -->
15361537

15371538
_Full example: [examples/snippets/servers/streamable_http_multiple_servers.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_multiple_servers.py)_
1538-
<!-- /snippet-source -->
15391539

15401540
##### Path configuration at initialization
15411541

@@ -1573,9 +1573,9 @@ app = Starlette(
15731573
]
15741574
)
15751575
```
1576+
<!-- /snippet-source -->
15761577

15771578
_Full example: [examples/snippets/servers/streamable_http_path_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_path_config.py)_
1578-
<!-- /snippet-source -->
15791579

15801580
#### SSE servers
15811581

@@ -1738,9 +1738,9 @@ if __name__ == "__main__":
17381738

17391739
asyncio.run(run())
17401740
```
1741+
<!-- /snippet-source -->
17411742

17421743
_Full example: [examples/snippets/servers/lowlevel/lifespan.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/lifespan.py)_
1743-
<!-- /snippet-source -->
17441744

17451745
The lifespan API provides:
17461746

@@ -1814,9 +1814,9 @@ async def run():
18141814
if __name__ == "__main__":
18151815
asyncio.run(run())
18161816
```
1817+
<!-- /snippet-source -->
18171818

18181819
_Full example: [examples/snippets/servers/lowlevel/basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/basic.py)_
1819-
<!-- /snippet-source -->
18201820

18211821
Caution: The `uv run mcp run` and `uv run mcp dev` tool doesn't support low-level server.
18221822

@@ -1907,9 +1907,9 @@ async def run():
19071907
if __name__ == "__main__":
19081908
asyncio.run(run())
19091909
```
1910+
<!-- /snippet-source -->
19101911

19111912
_Full example: [examples/snippets/servers/lowlevel/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/lowlevel/structured_output.py)_
1912-
<!-- /snippet-source -->
19131913

19141914
With the low-level server, handlers always return `CallToolResult` directly. You construct both the human-readable `content` and the machine-readable `structured_content` yourself, giving you full control over the response.
19151915

@@ -1982,9 +1982,9 @@ async def run():
19821982
if __name__ == "__main__":
19831983
asyncio.run(run())
19841984
```
1985+
<!-- /snippet-source -->
19851986

19861987
_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)_
1987-
<!-- /snippet-source -->
19881988

19891989
### Pagination (Advanced)
19901990

@@ -2030,9 +2030,9 @@ async def handle_list_resources(
20302030

20312031
server = Server("paginated-server", on_list_resources=handle_list_resources)
20322032
```
2033+
<!-- /snippet-source -->
20332034

20342035
_Full example: [examples/snippets/servers/pagination_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/pagination_example.py)_
2035-
<!-- /snippet-source -->
20362036

20372037
#### Client-side Consumption
20382038

@@ -2078,9 +2078,9 @@ async def list_all_resources() -> None:
20782078
if __name__ == "__main__":
20792079
asyncio.run(list_all_resources())
20802080
```
2081+
<!-- /snippet-source -->
20812082

20822083
_Full example: [examples/snippets/clients/pagination_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/pagination_client.py)_
2083-
<!-- /snippet-source -->
20842084

20852085
#### Key Points
20862086

@@ -2178,9 +2178,9 @@ def main():
21782178
if __name__ == "__main__":
21792179
main()
21802180
```
2181+
<!-- /snippet-source -->
21812182

21822183
_Full example: [examples/snippets/clients/stdio_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/stdio_client.py)_
2183-
<!-- /snippet-source -->
21842184

21852185
Clients can also connect using [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http):
21862186

@@ -2211,9 +2211,9 @@ async def main():
22112211
if __name__ == "__main__":
22122212
asyncio.run(main())
22132213
```
2214+
<!-- /snippet-source -->
22142215

22152216
_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_
2216-
<!-- /snippet-source -->
22172217

22182218
### Client Display Utilities
22192219

@@ -2288,9 +2288,9 @@ def main():
22882288
if __name__ == "__main__":
22892289
main()
22902290
```
2291+
<!-- /snippet-source -->
22912292

22922293
_Full example: [examples/snippets/clients/display_utilities.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/display_utilities.py)_
2293-
<!-- /snippet-source -->
22942294

22952295
The `get_display_name()` function implements the proper precedence rules for displaying names:
22962296

@@ -2394,9 +2394,9 @@ def run():
23942394
if __name__ == "__main__":
23952395
run()
23962396
```
2397+
<!-- /snippet-source -->
23972398

23982399
_Full example: [examples/snippets/clients/oauth_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/oauth_client.py)_
2399-
<!-- /snippet-source -->
24002400

24012401
For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).
24022402

0 commit comments

Comments
 (0)