Skip to content

Commit 4e5211b

Browse files
Extract docstring code examples into type-checked companion files
Move inline code examples from docstrings in `src/mcp/` into standalone companion files at `examples/snippets/docstrings/mcp/`, mirroring the source tree structure. The `scripts/sync_snippets.py` script keeps the docstring content in sync with the companion files via `<!-- snippet-source #RegionName -->` markers. This ensures all docstring examples are checked by pyright and ruff, catching type errors and style drift that would otherwise go unnoticed in raw docstring text. The pattern follows the TypeScript SDK's approach of one companion file per source file, with each example in a named function whose parameters supply typed context. 19 companion files cover 42 code examples across the public API surface: `MCPServer`, `Client`, `ClientSession`, `Context`, `ResponseRouter`, `ServerTaskContext`, `ExperimentalTaskHandlers`, `ClientCredentialsOAuthProvider`, `PrivateKeyJWTOAuthProvider`, `SignedJWTParameters`, and others. All source markers use path-less form (`#RegionName`) — the companion path is derived automatically from the target file location. Region names follow `ClassName_methodName_variant` without abbreviation. Pyright execution environment for the companion files suppresses only "unused artifact" diagnostics inherent to example code (`reportUnusedFunction`, `reportUnusedVariable`, `reportAbstractUsage`, `reportUnusedClass`, `reportPrivateUsage`). All actual type-checking rules remain enabled.
1 parent 5c6bea5 commit 4e5211b

40 files changed

+893
-38
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""Companion examples for src/mcp/client/auth/extensions/client_credentials.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from mcp.client.auth import TokenStorage
6+
from mcp.client.auth.extensions.client_credentials import (
7+
ClientCredentialsOAuthProvider,
8+
PrivateKeyJWTOAuthProvider,
9+
SignedJWTParameters,
10+
static_assertion_provider,
11+
)
12+
13+
14+
async def fetch_token_from_identity_provider(*, audience: str) -> str: ...
15+
16+
17+
def ClientCredentialsOAuthProvider_init(my_token_storage: TokenStorage) -> None:
18+
# region ClientCredentialsOAuthProvider_init
19+
provider = ClientCredentialsOAuthProvider(
20+
server_url="https://api.example.com",
21+
storage=my_token_storage,
22+
client_id="my-client-id",
23+
client_secret="my-client-secret",
24+
)
25+
# endregion ClientCredentialsOAuthProvider_init
26+
27+
28+
def static_assertion_provider_usage(my_token_storage: TokenStorage, my_prebuilt_jwt: str) -> None:
29+
# region static_assertion_provider_usage
30+
provider = PrivateKeyJWTOAuthProvider(
31+
server_url="https://api.example.com",
32+
storage=my_token_storage,
33+
client_id="my-client-id",
34+
assertion_provider=static_assertion_provider(my_prebuilt_jwt),
35+
)
36+
# endregion static_assertion_provider_usage
37+
38+
39+
def SignedJWTParameters_usage(my_token_storage: TokenStorage, private_key_pem: str) -> None:
40+
# region SignedJWTParameters_usage
41+
jwt_params = SignedJWTParameters(
42+
issuer="my-client-id",
43+
subject="my-client-id",
44+
signing_key=private_key_pem,
45+
)
46+
provider = PrivateKeyJWTOAuthProvider(
47+
server_url="https://api.example.com",
48+
storage=my_token_storage,
49+
client_id="my-client-id",
50+
assertion_provider=jwt_params.create_assertion_provider(),
51+
)
52+
# endregion SignedJWTParameters_usage
53+
54+
55+
def PrivateKeyJWTOAuthProvider_workloadIdentity(my_token_storage: TokenStorage) -> None:
56+
# region PrivateKeyJWTOAuthProvider_workloadIdentity
57+
async def get_workload_identity_token(audience: str) -> str:
58+
# Fetch JWT from your identity provider
59+
# The JWT's audience must match the provided audience parameter
60+
return await fetch_token_from_identity_provider(audience=audience)
61+
62+
provider = PrivateKeyJWTOAuthProvider(
63+
server_url="https://api.example.com",
64+
storage=my_token_storage,
65+
client_id="my-client-id",
66+
assertion_provider=get_workload_identity_token,
67+
)
68+
# endregion PrivateKeyJWTOAuthProvider_workloadIdentity
69+
70+
71+
def PrivateKeyJWTOAuthProvider_staticJWT(my_token_storage: TokenStorage, my_prebuilt_jwt: str) -> None:
72+
# region PrivateKeyJWTOAuthProvider_staticJWT
73+
provider = PrivateKeyJWTOAuthProvider(
74+
server_url="https://api.example.com",
75+
storage=my_token_storage,
76+
client_id="my-client-id",
77+
assertion_provider=static_assertion_provider(my_prebuilt_jwt),
78+
)
79+
# endregion PrivateKeyJWTOAuthProvider_staticJWT
80+
81+
82+
def PrivateKeyJWTOAuthProvider_sdkSigned(my_token_storage: TokenStorage, private_key_pem: str) -> None:
83+
# region PrivateKeyJWTOAuthProvider_sdkSigned
84+
jwt_params = SignedJWTParameters(
85+
issuer="my-client-id",
86+
subject="my-client-id",
87+
signing_key=private_key_pem,
88+
)
89+
provider = PrivateKeyJWTOAuthProvider(
90+
server_url="https://api.example.com",
91+
storage=my_token_storage,
92+
client_id="my-client-id",
93+
assertion_provider=jwt_params.create_assertion_provider(),
94+
)
95+
# endregion PrivateKeyJWTOAuthProvider_sdkSigned
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Companion examples for src/mcp/client/client.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
7+
8+
def Client_usage() -> None:
9+
# region Client_usage
10+
from mcp.client import Client
11+
from mcp.server.mcpserver import MCPServer
12+
13+
server = MCPServer("test")
14+
15+
@server.tool()
16+
def add(a: int, b: int) -> int:
17+
return a + b
18+
19+
async def main():
20+
async with Client(server) as client:
21+
result = await client.call_tool("add", {"a": 1, "b": 2})
22+
23+
asyncio.run(main())
24+
# endregion Client_usage
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Companion examples for src/mcp/client/experimental/task_handlers.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
6+
7+
from mcp import types
8+
from mcp.client.experimental.task_handlers import ExperimentalTaskHandlers
9+
from mcp.client.session import ClientSession
10+
from mcp.shared._context import RequestContext
11+
from mcp.shared.session import SessionMessage
12+
13+
14+
async def my_get_task_handler(
15+
context: RequestContext[ClientSession],
16+
params: types.GetTaskRequestParams,
17+
) -> types.GetTaskResult | types.ErrorData: ...
18+
19+
20+
async def my_list_tasks_handler(
21+
context: RequestContext[ClientSession],
22+
params: types.PaginatedRequestParams | None,
23+
) -> types.ListTasksResult | types.ErrorData: ...
24+
25+
26+
def ExperimentalTaskHandlers_usage(
27+
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
28+
write_stream: MemoryObjectSendStream[SessionMessage],
29+
) -> None:
30+
# region ExperimentalTaskHandlers_usage
31+
handlers = ExperimentalTaskHandlers(
32+
get_task=my_get_task_handler,
33+
list_tasks=my_list_tasks_handler,
34+
)
35+
session = ClientSession(read_stream, write_stream, experimental_task_handlers=handlers)
36+
# endregion ExperimentalTaskHandlers_usage
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Companion examples for src/mcp/client/experimental/tasks.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
import anyio
6+
7+
from mcp.client.session import ClientSession
8+
from mcp.types import CallToolResult
9+
10+
11+
async def module_overview(session: ClientSession) -> None:
12+
# region module_overview
13+
# Call a tool as a task
14+
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
15+
task_id = result.task.task_id
16+
17+
# Get task status
18+
status = await session.experimental.get_task(task_id)
19+
20+
# Get task result when complete
21+
if status.status == "completed":
22+
result = await session.experimental.get_task_result(task_id, CallToolResult)
23+
24+
# List all tasks
25+
tasks = await session.experimental.list_tasks()
26+
27+
# Cancel a task
28+
await session.experimental.cancel_task(task_id)
29+
# endregion module_overview
30+
31+
32+
async def ExperimentalClientFeatures_call_tool_as_task_usage(session: ClientSession) -> None:
33+
# region ExperimentalClientFeatures_call_tool_as_task_usage
34+
# Create task
35+
result = await session.experimental.call_tool_as_task("long_running_tool", {"input": "data"})
36+
task_id = result.task.task_id
37+
38+
# Poll for completion
39+
while True:
40+
status = await session.experimental.get_task(task_id)
41+
if status.status == "completed":
42+
break
43+
await anyio.sleep(0.5)
44+
45+
# Get result
46+
final = await session.experimental.get_task_result(task_id, CallToolResult)
47+
# endregion ExperimentalClientFeatures_call_tool_as_task_usage
48+
49+
50+
async def ExperimentalClientFeatures_poll_task_usage(session: ClientSession, task_id: str) -> None:
51+
# region ExperimentalClientFeatures_poll_task_usage
52+
async for status in session.experimental.poll_task(task_id):
53+
print(f"Status: {status.status}")
54+
if status.status == "input_required":
55+
# Handle elicitation request via tasks/result
56+
pass
57+
58+
# Task is now terminal, get the result
59+
result = await session.experimental.get_task_result(task_id, CallToolResult)
60+
# endregion ExperimentalClientFeatures_poll_task_usage
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Companion examples for src/mcp/client/session.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from mcp.client.session import ClientSession
6+
from mcp.types import CallToolResult
7+
8+
9+
async def ClientSession_experimental_usage(session: ClientSession, task_id: str) -> None:
10+
# region ClientSession_experimental_usage
11+
status = await session.experimental.get_task(task_id)
12+
result = await session.experimental.get_task_result(task_id, CallToolResult)
13+
# endregion ClientSession_experimental_usage
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Companion examples for src/mcp/client/session_group.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from mcp.client.session_group import ClientSessionGroup
8+
9+
10+
async def ClientSessionGroup_usage(server_params: list[Any]) -> None:
11+
# region ClientSessionGroup_usage
12+
def name_fn(name: str, server_info: Any) -> str:
13+
return f"{server_info.name}_{name}"
14+
15+
async with ClientSessionGroup(component_name_hook=name_fn) as group:
16+
for server_param in server_params:
17+
await group.connect_to_server(server_param)
18+
...
19+
# endregion ClientSessionGroup_usage
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Companion examples for src/mcp/server/experimental/request_context.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from mcp.server.context import ServerRequestContext
8+
from mcp.server.experimental.task_context import ServerTaskContext
9+
from mcp.types import CallToolRequestParams, CallToolResult, CreateTaskResult, TextContent
10+
11+
12+
def Experimental_run_task_usage() -> None:
13+
# region Experimental_run_task_usage
14+
async def handle_tool(
15+
ctx: ServerRequestContext[Any, Any],
16+
params: CallToolRequestParams,
17+
) -> CreateTaskResult:
18+
async def work(task: ServerTaskContext) -> CallToolResult:
19+
result = await task.elicit(
20+
message="Are you sure?",
21+
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
22+
)
23+
if result.action == "accept" and result.content:
24+
confirmed = result.content.get("confirm", False)
25+
else:
26+
confirmed = False
27+
return CallToolResult(content=[TextContent(text="Done" if confirmed else "Cancelled")])
28+
29+
return await ctx.experimental.run_task(work)
30+
31+
# endregion Experimental_run_task_usage
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Companion examples for src/mcp/server/experimental/task_context.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from mcp.server.experimental.task_context import ServerTaskContext
6+
from mcp.types import CallToolResult, TextContent
7+
8+
9+
async def ServerTaskContext_usage(task: ServerTaskContext) -> None:
10+
# region ServerTaskContext_usage
11+
async def my_task_work(task: ServerTaskContext) -> CallToolResult:
12+
await task.update_status("Starting...")
13+
14+
result = await task.elicit(
15+
message="Continue?",
16+
requested_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
17+
)
18+
19+
if result.action == "accept" and result.content and result.content.get("ok"):
20+
return CallToolResult(content=[TextContent(text="Done!")])
21+
else:
22+
return CallToolResult(content=[TextContent(text="Cancelled")])
23+
24+
# endregion ServerTaskContext_usage
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Companion examples for src/mcp/server/experimental/task_support.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from mcp.server.lowlevel.server import Server
8+
from mcp.shared.experimental.tasks.message_queue import TaskMessageQueue
9+
from mcp.shared.experimental.tasks.store import TaskStore
10+
11+
12+
# Stubs for undefined references in examples
13+
class RedisTaskStore(TaskStore): # type: ignore[abstract]
14+
def __init__(self, redis_url: str) -> None: ...
15+
16+
17+
class RedisTaskMessageQueue(TaskMessageQueue): # type: ignore[abstract]
18+
def __init__(self, redis_url: str) -> None: ...
19+
20+
21+
def TaskSupport_simple(server: Server[Any]) -> None:
22+
# region TaskSupport_simple
23+
server.experimental.enable_tasks()
24+
# endregion TaskSupport_simple
25+
26+
27+
def TaskSupport_custom(server: Server[Any], redis_url: str) -> None:
28+
# region TaskSupport_custom
29+
server.experimental.enable_tasks(
30+
store=RedisTaskStore(redis_url),
31+
queue=RedisTaskMessageQueue(redis_url),
32+
)
33+
# endregion TaskSupport_custom
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Companion examples for src/mcp/server/lowlevel/experimental.py docstrings."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from mcp.server.lowlevel.server import Server
8+
from mcp.shared.experimental.tasks.message_queue import TaskMessageQueue
9+
from mcp.shared.experimental.tasks.store import TaskStore
10+
11+
12+
class RedisTaskStore(TaskStore): # type: ignore[abstract]
13+
def __init__(self, redis_url: str) -> None: ...
14+
15+
16+
class RedisTaskMessageQueue(TaskMessageQueue): # type: ignore[abstract]
17+
def __init__(self, redis_url: str) -> None: ...
18+
19+
20+
def ExperimentalHandlers_enable_tasks_simple(server: Server[Any]) -> None:
21+
# region ExperimentalHandlers_enable_tasks_simple
22+
server.experimental.enable_tasks()
23+
# endregion ExperimentalHandlers_enable_tasks_simple
24+
25+
26+
def ExperimentalHandlers_enable_tasks_custom(server: Server[Any], redis_url: str) -> None:
27+
# region ExperimentalHandlers_enable_tasks_custom
28+
server.experimental.enable_tasks(
29+
store=RedisTaskStore(redis_url),
30+
queue=RedisTaskMessageQueue(redis_url),
31+
)
32+
# endregion ExperimentalHandlers_enable_tasks_custom

0 commit comments

Comments
 (0)