Skip to content

Commit aa96d8f

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 0fe16dd commit aa96d8f

File tree

5 files changed

+336
-196
lines changed

5 files changed

+336
-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: 33 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,10 @@ 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 -->
811+
811812
### Elicitation
812813

813814
Request additional information from users. This example shows an Elicitation during a Tool Call:
@@ -914,9 +915,9 @@ async def connect_service(service_name: str, ctx: Context[ServerSession, None])
914915
]
915916
)
916917
```
918+
<!-- /snippet-source -->
917919

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

921922
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.
922923

@@ -959,9 +960,9 @@ async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
959960
return result.content.text
960961
return str(result.content)
961962
```
963+
<!-- /snippet-source -->
962964

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

966967
### Logging and Notifications
967968

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

990991
return f"Processed: {data}"
991992
```
993+
<!-- /snippet-source -->
992994

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

996997
### Authentication
997998

@@ -1049,9 +1050,9 @@ async def get_weather(city: str = "London") -> dict[str, str]:
10491050
if __name__ == "__main__":
10501051
mcp.run(transport="streamable-http", json_response=True)
10511052
```
1053+
<!-- /snippet-source -->
10521054

10531055
_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 -->
10551056

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

@@ -1223,9 +1224,9 @@ def main():
12231224
if __name__ == "__main__":
12241225
main()
12251226
```
1227+
<!-- /snippet-source -->
12261228

12271229
_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 -->
12291230

12301231
Run it with:
12311232

@@ -1272,9 +1273,9 @@ if __name__ == "__main__":
12721273
# Stateful server with session persistence
12731274
# mcp.run(transport="streamable-http")
12741275
```
1276+
<!-- /snippet-source -->
12751277

12761278
_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 -->
12781279

12791280
You can mount multiple MCPServer servers in a Starlette application:
12801281

@@ -1334,9 +1335,9 @@ app = Starlette(
13341335
# echo_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
13351336
# math_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
13361337
```
1338+
<!-- /snippet-source -->
13371339

13381340
_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 -->
13401341

13411342
For low level server with Streamable HTTP implementations, see:
13421343

@@ -1429,9 +1430,9 @@ app = Starlette(
14291430
lifespan=lifespan,
14301431
)
14311432
```
1433+
<!-- /snippet-source -->
14321434

14331435
_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 -->
14351436

14361437
##### Host-based routing
14371438

@@ -1476,9 +1477,9 @@ app = Starlette(
14761477
lifespan=lifespan,
14771478
)
14781479
```
1480+
<!-- /snippet-source -->
14791481

14801482
_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 -->
14821483

14831484
##### Multiple servers with path configuration
14841485

@@ -1533,9 +1534,9 @@ app = Starlette(
15331534
lifespan=lifespan,
15341535
)
15351536
```
1537+
<!-- /snippet-source -->
15361538

15371539
_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 -->
15391540

15401541
##### Path configuration at initialization
15411542

@@ -1573,9 +1574,9 @@ app = Starlette(
15731574
]
15741575
)
15751576
```
1577+
<!-- /snippet-source -->
15761578

15771579
_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 -->
15791580

15801581
#### SSE servers
15811582

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

17391740
asyncio.run(run())
17401741
```
1742+
<!-- /snippet-source -->
17411743

17421744
_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 -->
17441745

17451746
The lifespan API provides:
17461747

@@ -1814,9 +1815,9 @@ async def run():
18141815
if __name__ == "__main__":
18151816
asyncio.run(run())
18161817
```
1818+
<!-- /snippet-source -->
18171819

18181820
_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 -->
18201821

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

@@ -1907,9 +1908,9 @@ async def run():
19071908
if __name__ == "__main__":
19081909
asyncio.run(run())
19091910
```
1911+
<!-- /snippet-source -->
19101912

19111913
_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 -->
19131914

19141915
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.
19151916

@@ -1982,9 +1983,9 @@ async def run():
19821983
if __name__ == "__main__":
19831984
asyncio.run(run())
19841985
```
1986+
<!-- /snippet-source -->
19851987

19861988
_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 -->
19881989

19891990
### Pagination (Advanced)
19901991

@@ -2030,9 +2031,9 @@ async def handle_list_resources(
20302031

20312032
server = Server("paginated-server", on_list_resources=handle_list_resources)
20322033
```
2034+
<!-- /snippet-source -->
20332035

20342036
_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 -->
20362037

20372038
#### Client-side Consumption
20382039

@@ -2078,9 +2079,9 @@ async def list_all_resources() -> None:
20782079
if __name__ == "__main__":
20792080
asyncio.run(list_all_resources())
20802081
```
2082+
<!-- /snippet-source -->
20812083

20822084
_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 -->
20842085

20852086
#### Key Points
20862087

@@ -2178,9 +2179,9 @@ def main():
21782179
if __name__ == "__main__":
21792180
main()
21802181
```
2182+
<!-- /snippet-source -->
21812183

21822184
_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 -->
21842185

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

@@ -2211,9 +2212,9 @@ async def main():
22112212
if __name__ == "__main__":
22122213
asyncio.run(main())
22132214
```
2215+
<!-- /snippet-source -->
22142216

22152217
_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 -->
22172218

22182219
### Client Display Utilities
22192220

@@ -2288,9 +2289,9 @@ def main():
22882289
if __name__ == "__main__":
22892290
main()
22902291
```
2292+
<!-- /snippet-source -->
22912293

22922294
_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 -->
22942295

22952296
The `get_display_name()` function implements the proper precedence rules for displaying names:
22962297

@@ -2394,9 +2395,9 @@ def run():
23942395
if __name__ == "__main__":
23952396
run()
23962397
```
2398+
<!-- /snippet-source -->
23972399

23982400
_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 -->
24002401

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

0 commit comments

Comments
 (0)