Skip to content

Commit 1b74b06

Browse files
committed
Tighten comments and docstrings repo-wide
Cut comment and docstring volume roughly in half across src, tests, examples, and docs_src: removed comments that restate the adjacent code, leftover development narration, section banners, and self-evident Args/Returns blocks, and compressed the remaining docstrings to a Google-style summary line plus only the detail that earns its place. Kept (and tightened) the load-bearing content: Raises sections, deprecation and version-availability notes, spec/RFC/issue references, why-comments for non-obvious decisions, and all coverage pragmas. The generated mcp_types.v* wire modules are untouched.
1 parent 533c6a8 commit 1b74b06

434 files changed

Lines changed: 3860 additions & 13549 deletions

File tree

Some content is hidden

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

.github/actions/conformance/client.py

Lines changed: 31 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
1-
"""MCP unified conformance test client.
2-
3-
This client is designed to work with the @modelcontextprotocol/conformance npm package.
4-
It handles all conformance test scenarios via environment variables and CLI arguments.
1+
"""MCP conformance test client for the @modelcontextprotocol/conformance harness.
52
63
Contract:
7-
- MCP_CONFORMANCE_SCENARIO env var -> scenario name
8-
- MCP_CONFORMANCE_CONTEXT env var -> optional JSON (for client-credentials scenarios)
9-
- MCP_CONFORMANCE_PROTOCOL_VERSION env var -> spec version the harness mock
10-
server is speaking (e.g. "2025-11-25", "2026-07-28"). Always set; when
11-
--spec-version is omitted the harness picks per-scenario (LATEST_SPEC_VERSION
12-
for active scenarios, DRAFT_PROTOCOL_VERSION for draft-only ones).
4+
- MCP_CONFORMANCE_SCENARIO env var -> scenario name (see HANDLERS; auth/* falls back to the auth code flow)
5+
- MCP_CONFORMANCE_CONTEXT env var -> optional JSON (client-credentials scenarios)
6+
- MCP_CONFORMANCE_PROTOCOL_VERSION env var -> spec version the harness mock server speaks
137
- Server URL as last CLI argument (sys.argv[1])
148
- Must exit 0 within 30 seconds
15-
16-
Scenarios:
17-
initialize - Connect, initialize, list tools, close
18-
tools_call - Connect, call add_numbers(a=5, b=3), close
19-
sse-retry - Connect, call test_reconnection, close
20-
json-schema-ref-no-deref - Connect, list tools (no $ref deref)
21-
request-metadata - Connect with all callbacks; client stamps _meta
22-
http-standard-headers - Connect, call a tool (Mcp-* headers checked)
23-
http-invalid-tool-headers - List tools, call every surfaced tool (x-mcp-header filter)
24-
elicitation-sep1034-client-defaults - Elicitation with default accept callback
25-
sep-2322-client-request-state - Drive the MRTR auto-loop (SEP-2322)
26-
auth/client-credentials-jwt - Client credentials with private_key_jwt
27-
auth/client-credentials-basic - Client credentials with client_secret_basic
28-
auth/enterprise-managed-authorization - SEP-990 ID-JAG (RFC 8693 + RFC 7523 jwt-bearer)
29-
auth/* - Authorization code flow (default for auth scenarios)
309
"""
3110

3211
import asyncio
@@ -64,32 +43,24 @@
6443
)
6544
logger = logging.getLogger(__name__)
6645

67-
#: Spec version the harness is running this scenario at (e.g. "2025-11-25",
68-
#: "2026-07-28"). The harness always sets this (when --spec-version is omitted
69-
#: it picks per-scenario: LATEST_SPEC_VERSION for active scenarios,
70-
#: DRAFT_PROTOCOL_VERSION for draft-only ones), so None means we were invoked
71-
#: outside the harness.
46+
#: Spec version the harness mock server speaks (e.g. "2025-11-25"). The harness
47+
#: always sets this, so None means we were invoked outside it.
7248
PROTOCOL_VERSION: str | None = os.environ.get("MCP_CONFORMANCE_PROTOCOL_VERSION")
7349

7450

7551
def client_mode() -> str:
7652
"""Pick the Client(mode=) for the harness leg.
7753
78-
On a modern leg (2026-07-28+) -> 'auto' so Client.discover() runs and the
79-
_meta envelope + MCP-Protocol-Version header are stamped on every request.
80-
On a handshake-era leg -> 'legacy' so the initialize handshake runs exactly
81-
as before (no server/discover probe is sent against a mock that would 400 it).
82-
Outside the harness -> 'auto' (probe + fallback).
54+
'auto' on modern legs (2026-07-28+) and outside the harness; 'legacy' on handshake-era
55+
legs so no server/discover probe is sent against a mock that would 400 it.
8356
"""
8457
if PROTOCOL_VERSION is None or PROTOCOL_VERSION in MODERN_PROTOCOL_VERSIONS:
8558
return "auto"
8659
return "legacy"
8760

8861

89-
# Type for async scenario handler functions
9062
ScenarioHandler = Callable[[str], Coroutine[Any, None, None]]
9163

92-
# Registry of scenario handlers
9364
HANDLERS: dict[str, ScenarioHandler] = {}
9465

9566

@@ -138,17 +109,14 @@ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None
138109

139110

140111
class ConformanceOAuthCallbackHandler:
141-
"""OAuth callback handler that automatically fetches the authorization URL
142-
and extracts the auth code, without requiring user interaction.
143-
"""
112+
"""Fetches the authorization URL and extracts the auth code without user interaction."""
144113

145114
def __init__(self) -> None:
146115
self._auth_code: str | None = None
147116
self._state: str | None = None
148117
self._iss: str | None = None
149118

150119
async def handle_redirect(self, authorization_url: str) -> None:
151-
"""Fetch the authorization URL and extract the auth code from the redirect."""
152120
logger.debug(f"Fetching authorization URL: {authorization_url}")
153121

154122
async with httpx.AsyncClient() as client:
@@ -179,7 +147,6 @@ async def handle_redirect(self, authorization_url: str) -> None:
179147
raise RuntimeError(f"Expected redirect response, got {response.status_code} from {authorization_url}")
180148

181149
async def handle_callback(self) -> AuthorizationCodeResult:
182-
"""Return the captured auth code, state, and iss."""
183150
if self._auth_code is None:
184151
raise RuntimeError("No authorization code available - was handle_redirect called?")
185152
result = AuthorizationCodeResult(code=self._auth_code, state=self._state, iss=self._iss)
@@ -189,9 +156,6 @@ async def handle_callback(self) -> AuthorizationCodeResult:
189156
return result
190157

191158

192-
# --- Stub callbacks (declare capabilities in _meta without doing real work) ---
193-
194-
195159
async def stub_sampling_callback(
196160
context: ClientRequestContext,
197161
params: types.CreateMessageRequestParams,
@@ -214,7 +178,6 @@ async def default_elicitation_callback(
214178
"""Accept elicitation and apply defaults from the schema (SEP-1034)."""
215179
content: dict[str, str | int | float | bool | list[str] | None] = {}
216180

217-
# For form mode, extract defaults from the requested_schema
218181
if isinstance(params, types.ElicitRequestFormParams):
219182
schema = params.requested_schema
220183
logger.debug(f"Elicitation schema: {schema}")
@@ -227,12 +190,8 @@ async def default_elicitation_callback(
227190
return types.ElicitResult(action="accept", content=content)
228191

229192

230-
# --- Scenario Handlers ---
231-
232-
233193
@register("initialize")
234194
async def run_initialize(server_url: str) -> None:
235-
"""Connect, initialize, list tools, close."""
236195
async with Client(server_url, mode=client_mode()) as client:
237196
logger.debug("Initialized successfully")
238197
await client.list_tools()
@@ -241,20 +200,17 @@ async def run_initialize(server_url: str) -> None:
241200

242201
@register("json-schema-ref-no-deref")
243202
async def run_json_schema_ref_no_deref(server_url: str) -> None:
244-
"""Initialize and list tools; the scenario fails only if the client fetches a network $ref.
203+
"""List tools; the scenario fails only if the client fetches a network $ref (SEP-2106).
245204
246-
The client never walks inputSchema or resolves $refs, so listing is enough (SEP-2106).
247-
Pinned to mode='legacy': the harness reports PROTOCOL_VERSION=2026-07-28 for this
248-
scenario but its mock server only speaks the handshake-era lifecycle and 400s a
249-
modern-stamped tools/list. The check is lifecycle-agnostic so this is harmless.
205+
Pinned to mode='legacy': the harness reports PROTOCOL_VERSION=2026-07-28 here, but its
206+
mock only speaks the handshake-era lifecycle and 400s a modern-stamped tools/list.
250207
"""
251208
async with Client(server_url, mode="legacy") as client:
252209
await client.list_tools()
253210

254211

255212
@register("tools_call")
256213
async def run_tools_call(server_url: str) -> None:
257-
"""Connect, list tools, call add_numbers(a=5, b=3), close."""
258214
async with Client(server_url, mode=client_mode()) as client:
259215
await client.list_tools()
260216
result = await client.call_tool("add_numbers", {"a": 5, "b": 3})
@@ -263,7 +219,6 @@ async def run_tools_call(server_url: str) -> None:
263219

264220
@register("sse-retry")
265221
async def run_sse_retry(server_url: str) -> None:
266-
"""Connect, list tools, call test_reconnection, close."""
267222
async with Client(server_url, mode=client_mode()) as client:
268223
await client.list_tools()
269224
result = await client.call_tool("test_reconnection", {})
@@ -274,11 +229,9 @@ async def run_sse_retry(server_url: str) -> None:
274229
async def run_request_metadata(server_url: str) -> None:
275230
"""Connect on the modern path with every client capability declared.
276231
277-
The scenario inspects every request's `_meta` envelope (SEP-2575) for
278-
protocolVersion / clientInfo / clientCapabilities, and the matching
279-
MCP-Protocol-Version header. mode='auto' makes the SDK send
280-
server/discover (covering the unsupported-version retry check), then adopt
281-
and stamp the envelope on the follow-up requests.
232+
The scenario inspects each request's `_meta` envelope (SEP-2575) and MCP-Protocol-Version
233+
header; mode='auto' sends server/discover (covering the unsupported-version retry check),
234+
then stamps the envelope on follow-up requests.
282235
"""
283236
async with Client(
284237
server_url,
@@ -320,13 +273,9 @@ def _stub_required_args(input_schema: dict[str, Any]) -> dict[str, Any]:
320273
async def run_http_invalid_tool_headers(server_url: str) -> None:
321274
"""List tools, then call every tool the SDK surfaces (SEP-2243).
322275
323-
The harness mock advertises one valid tool plus several with malformed
324-
x-mcp-header annotations (empty, non-primitive type, duplicate, invalid
325-
chars). The scenario passes if valid_tool is called and the malformed
326-
ones are not -- so a conforming client filters them out of the list_tools
327-
result and the loop below never sees them. The scenario sets
328-
allowClientError, so a per-call failure is logged and skipped rather
329-
than aborting the whole run.
276+
The mock advertises one valid tool plus several with malformed x-mcp-header annotations;
277+
a conforming client filters those out of the list_tools result so the loop never sees
278+
them. The scenario sets allowClientError, so per-call failures are logged, not fatal.
330279
"""
331280
async with Client(server_url, mode=client_mode()) as client:
332281
listed = await client.list_tools()
@@ -340,13 +289,11 @@ async def run_http_invalid_tool_headers(server_url: str) -> None:
340289

341290
@register("http-custom-headers")
342291
async def run_http_custom_headers(server_url: str) -> None:
343-
"""List tools, then replay the harness's `toolCalls` so x-mcp-header args mirror into headers (SEP-2243).
292+
"""Replay the harness's `toolCalls` verbatim so x-mcp-header args mirror into headers (SEP-2243).
344293
345-
The scenario supplies the exact arguments to send (including the null/edge-case values that
346-
exercise omission and Base64 encoding) via the context `toolCalls`; using them verbatim is
347-
what drives every per-parameter check. `list_tools` first so the SDK caches each tool's
348-
annotations; a tool the SDK dropped (invalid annotations) is skipped. Per-call failures are
349-
logged and skipped rather than aborting the run.
294+
The context supplies the exact arguments (including null/edge-case values exercising omission
295+
and Base64 encoding). `list_tools` first so the SDK caches each tool's annotations; tools the
296+
SDK dropped (invalid annotations) are skipped, and per-call failures are logged, not fatal.
350297
"""
351298
tool_calls: list[dict[str, Any]] = []
352299
if os.environ.get("MCP_CONFORMANCE_CONTEXT"):
@@ -368,7 +315,6 @@ async def run_http_custom_headers(server_url: str) -> None:
368315

369316
@register("elicitation-sep1034-client-defaults")
370317
async def run_elicitation_defaults(server_url: str) -> None:
371-
"""Connect with elicitation callback that applies schema defaults."""
372318
async with Client(server_url, mode=client_mode(), elicitation_callback=default_elicitation_callback) as client:
373319
await client.list_tools()
374320
result = await client.call_tool("test_client_elicitation_defaults", {})
@@ -379,13 +325,10 @@ async def run_elicitation_defaults(server_url: str) -> None:
379325
async def run_mrtr_client(server_url: str) -> None:
380326
"""Drive the SEP-2322 client mock through `Client.call_tool`'s auto-loop.
381327
382-
The mock inspects raw `tools/call` params, so registering an
383-
`elicitation_callback` and letting the driver run is enough to satisfy
384-
all five wire-shape checks: the driver echoes `request_state` byte-exact
385-
and omits it when the server sent none, every retry mints a fresh
386-
JSON-RPC id, the unrelated call between auto-loops carries no MRTR
387-
params, and the no-`resultType` response parses as a terminal
388-
`CallToolResult` so the driver never retries it.
328+
The mock inspects raw `tools/call` params: the driver must echo `request_state`
329+
byte-exact (omitting it when the server sent none), mint a fresh JSON-RPC id per
330+
retry, keep MRTR params off unrelated calls, and treat a no-`resultType` response
331+
as a terminal `CallToolResult`.
389332
"""
390333

391334
async def confirm(
@@ -459,9 +402,7 @@ async def run_client_credentials_basic(server_url: str) -> None:
459402

460403
@register("auth/enterprise-managed-authorization")
461404
async def run_enterprise_managed_authorization(server_url: str) -> None:
462-
"""SEP-990 enterprise-managed authorization: RFC 8693 token-exchange at the
463-
enterprise IdP for an ID-JAG, then RFC 7523 jwt-bearer at the MCP
464-
authorization server."""
405+
"""SEP-990: RFC 8693 token-exchange at the IdP for an ID-JAG, then RFC 7523 jwt-bearer at the MCP AS."""
465406
context = get_conformance_context()
466407
client_id = context.get("client_id")
467408
client_secret = context.get("client_secret")
@@ -480,11 +421,9 @@ async def run_enterprise_managed_authorization(server_url: str) -> None:
480421
if not idp_token_endpoint:
481422
raise RuntimeError("MCP_CONFORMANCE_CONTEXT missing 'idp_token_endpoint'")
482423

483-
# IdentityAssertionOAuthProvider takes the AS issuer as configuration (the
484-
# SEP-990 trust model: the resource server is never asked which AS to use).
485-
# The harness does not put the issuer in context, so for conformance we
486-
# learn it from the harness's PRM document (RFC 9728); production
487-
# deployments would supply it as static configuration instead.
424+
# SEP-990 trust model: the AS issuer is client configuration, never asked of the resource
425+
# server. The harness omits it from context, so learn it from the PRM document (RFC 9728);
426+
# production deployments would configure it statically.
488427
prm_url = build_protected_resource_metadata_discovery_urls(None, server_url)[0]
489428
async with httpx.AsyncClient(timeout=30.0) as http:
490429
prm = (await http.get(prm_url)).raise_for_status().json()
@@ -526,7 +465,6 @@ async def run_auth_code_client(server_url: str) -> None:
526465
callback_handler = ConformanceOAuthCallbackHandler()
527466
storage = InMemoryTokenStorage()
528467

529-
# Check for pre-registered client credentials from context
530468
context_json = os.environ.get("MCP_CONFORMANCE_CONTEXT")
531469
if context_json:
532470
try:
@@ -573,7 +511,7 @@ async def _run_auth_session(server_url: str, oauth_auth: httpx.Auth) -> None:
573511
tools_result = await client.list_tools()
574512
logger.debug(f"Listed tools: {[t.name for t in tools_result.tools]}")
575513

576-
# Call the first available tool (different tests have different tools)
514+
# Different tests expose different tools; call the first one
577515
if tools_result.tools:
578516
tool_name = tools_result.tools[0].name
579517
try:
@@ -586,7 +524,6 @@ async def _run_auth_session(server_url: str, oauth_auth: httpx.Auth) -> None:
586524

587525

588526
def main() -> None:
589-
"""Main entry point for the conformance client."""
590527
if len(sys.argv) < 2:
591528
print(f"Usage: {sys.argv[0]} <server-url>", file=sys.stderr)
592529
sys.exit(1)

docs/hooks/gen_ref_pages.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
root = Path(__file__).parent.parent.parent
1010
src = root / "src"
1111

12-
# `src/mcp-types` is a distribution directory, not an import package, so each
13-
# package's dotted module path is taken relative to its own parent: deriving it
14-
# from `src/` would emit the unimportable `mcp-types.mcp_types.*`.
12+
# `src/mcp-types` is a distribution directory, not an import package, so each package's dotted module path
13+
# is taken relative to its own parent; deriving it from `src/` would emit the unimportable `mcp-types.mcp_types.*`.
1514
for package in (src / "mcp", src / "mcp-types" / "mcp_types"):
1615
base = package.parent
1716
for path in sorted(package.rglob("*.py")):

docs_src/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Complete, runnable source for every code example in `docs/`.
22
3-
Each `docs/<page>.md` includes its examples from `docs_src/<chapter>/tutorialNNN.py`
4-
via `--8<--`, and `tests/docs_src/test_<chapter>.py` imports the same module and
5-
exercises it through the in-memory `mcp.Client`. The file you read in the docs is
6-
the file CI runs.
3+
Docs pages include these files via `--8<--`, and `tests/docs_src/` exercises the same
4+
modules through the in-memory `mcp.Client` — the file you read in the docs is the file CI runs.
75
"""

0 commit comments

Comments
 (0)