Skip to content

Commit 21a779a

Browse files
committed
Trim comments and docstrings
1 parent d1cebd1 commit 21a779a

12 files changed

Lines changed: 261 additions & 789 deletions

File tree

src/mcp-types/mcp_types/methods.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,17 +416,14 @@
416416
"server/discover",
417417
"tools/list",
418418
]
419-
"""The methods whose results carry `ttlMs`/`cacheScope`. Closed set: the spec
420-
defines caching hints on exactly these six. Hand-written because a Literal
421-
cannot be computed at runtime; tests weld it to `CACHEABLE_METHODS`."""
419+
"""Methods whose results carry `ttlMs`/`cacheScope`; hand-written Literal, welded to `CACHEABLE_METHODS` by tests."""
422420

423421
CACHEABLE_METHODS: Final[frozenset[str]] = frozenset(
424422
method
425423
for method, row in MONOLITH_RESULTS.items()
426424
if any(issubclass(arm, types.CacheableResult) for arm in (get_args(row) if isinstance(row, UnionType) else (row,)))
427425
)
428-
"""Runtime mirror of `CacheableMethod`, derived from `MONOLITH_RESULTS`: a
429-
method is cacheable iff its result row has a `CacheableResult` arm."""
426+
"""Runtime mirror of `CacheableMethod`, derived from `MONOLITH_RESULTS`."""
430427

431428

432429
# --- Parse functions ---

src/mcp/client/caching.py

Lines changed: 63 additions & 176 deletions
Large diffs are not rendered by default.

src/mcp/client/client.py

Lines changed: 28 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,7 @@ def _connected(value: _T | None) -> _T:
130130
def _strip_userinfo(url: str) -> str:
131131
"""Drop any userinfo from the URL's authority component; byte-exact otherwise.
132132
133-
Cache identity must never over-normalize (case-folding or query rewriting could
134-
merge distinct servers, e.g. `?tenant=a` vs `?tenant=b`), and credentials must
135-
never enter cache-key material — userinfo removal is the single permitted rewrite.
133+
Credentials must not enter cache-key material; any further normalization could merge distinct servers.
136134
"""
137135
parts = urlsplit(url)
138136
if "@" not in parts.netloc:
@@ -141,13 +139,7 @@ def _strip_userinfo(url: str) -> str:
141139

142140

143141
def _evicting_message_handler(cache: ClientResponseCache, user_handler: MessageHandlerFnT | None) -> MessageHandlerFnT:
144-
"""Wrap the session message handler with cache eviction on server notifications.
145-
146-
Eviction runs before delegation, inside its own boundary, so a cache fault can
147-
never suppress delivery. Every item — notification, `RequestResponder`, or
148-
transport `Exception` — then reaches the user's handler; with none supplied, the
149-
wrapper performs the same bare checkpoint `ClientSession` installs by default.
150-
"""
142+
"""Wrap the session message handler with cache eviction on server notifications."""
151143

152144
async def handler(
153145
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
@@ -276,15 +268,10 @@ async def main():
276268
"""Client-side response caching for the SEP-2549 cacheable methods (2026-07-28).
277269
278270
`None` (the default) honors server `ttlMs`/`cacheScope` hints with a per-client
279-
in-memory store; results carrying no hints are not cached. Pass a `CacheConfig`
280-
to customize (shared store, partition, default TTL), or `False` to disable
281-
caching entirely. The cacheable verbs (`list_tools`, `list_prompts`,
282-
`list_resources`, `list_resource_templates`, `read_resource`) take a per-call
283-
`cache_mode` to narrow caching for one call; with `cache=False` it is inert.
284-
285-
Construction raises `ValueError` for a `CacheConfig` with a custom `store` when
286-
no server identity can be derived (an in-process server or a `Transport`
287-
instance) — set `CacheConfig.target_id` to name the server."""
271+
in-memory store; pass a `CacheConfig` to customize, or `False` to disable. The
272+
cacheable verbs take a per-call `cache_mode` (see `CacheMode`); calls carrying
273+
`meta` always reach the server. A `CacheConfig` with a custom `store` requires
274+
`target_id` when the server is not a URL (no identity can be derived)."""
288275

289276
_entered: bool = field(init=False, default=False)
290277
_session: ClientSession | None = field(init=False, default=None)
@@ -315,10 +302,7 @@ def __post_init__(self) -> None:
315302

316303
if self.cache is not False:
317304
config = self.cache if self.cache is not None else CacheConfig()
318-
# Server identity, in resolution order: explicit override, server URL
319-
# (userinfo stripped, byte-exact otherwise), per-Client random. Only the
320-
# hash below leaves this scope — the raw identity may carry credentials
321-
# in its query string and must never be logged or stored.
305+
# Only the hash below leaves this scope - the raw identity may carry credentials; never log or store it.
322306
target_id = config.target_id
323307
if target_id is None and isinstance(self.server, str):
324308
target_id = _strip_userinfo(self.server)
@@ -337,8 +321,7 @@ def __post_init__(self) -> None:
337321
default_ttl_ms=config.default_ttl_ms,
338322
clock=config.clock,
339323
share_public=config.share_public,
340-
# Lazy: the era is unknown until __aenter__'s handshake, and the
341-
# session is unpublished outside the context manager.
324+
# Lazy: the negotiated version is unknown until __aenter__'s handshake.
342325
negotiated_version=lambda: self._session.protocol_version if self._session is not None else None,
343326
)
344327

@@ -471,35 +454,26 @@ async def _cached_fetch(
471454
) -> _CacheableT:
472455
"""Serve one of the four list verbs through the response cache.
473456
474-
`send` performs the fetch via the session; `absorb` (tools/list only)
475-
re-applies session-side derived state to a served cache hit.
457+
`absorb` (tools/list only) re-applies session-side derived state to a served cache hit.
476458
"""
477459
cache = self._response_cache
478460
if cache is None or cache_mode == "bypass":
479-
return await send() # no read, no write, no eviction side-effects
480-
# Cache participation requires a live session: a closed (or never-entered)
481-
# client raises the no-context RuntimeError on every verb, exactly as the
482-
# verbs did before the cache existed - never serving stale entries.
461+
return await send()
462+
# A closed (or never-entered) client must raise, never serve cached entries.
483463
_ = self.session
484464
if meta is not None and cache_mode == "use":
485-
# A call carrying meta (a progress token, tracing fields) expects a
486-
# wire request, so it is never served from the cache; the fetched
487-
# result still replaces the entry, the same as an explicit refresh.
465+
# meta (a progress token, tracing fields) expects a wire request; fetch and replace the entry.
488466
cache_mode = "refresh"
489467
if cursor is not None:
490-
# Continuation pages never read or write the (cursor-less) entry, but an
491-
# expired-cursor rejection signals the listing changed since the entry was
492-
# fetched, so it is evicted (spec SHOULD; over-eviction is harmless).
468+
# Continuation pages skip the cache, but an expired cursor means the listing changed (spec SHOULD evict).
493469
try:
494470
return await send()
495471
except MCPError as e:
496472
if e.code == INVALID_PARAMS:
497473
await cache.evict_method(method)
498474
raise
499475
if cache_mode == "use" and (hit := await cache.read(method, "")) is not None:
500-
# The store key carries the method, so the entry under it has `send`'s
501-
# result type. The hit is already a private deep copy of the stored
502-
# value, so absorption may mutate it freely.
476+
# The hit is a private deep copy, so absorption may mutate it freely.
503477
served = cast(_CacheableT, hit)
504478
return served if absorb is None else absorb(served)
505479
gen = cache.capture(method, "")
@@ -514,11 +488,7 @@ async def list_resources(
514488
meta: RequestParamsMeta | None = None,
515489
cache_mode: CacheMode = "use",
516490
) -> ListResourcesResult:
517-
"""List available resources from the server.
518-
519-
`cache_mode` adjusts the response cache's behavior for this call (see `CacheMode`);
520-
calls carrying `meta` always reach the server.
521-
"""
491+
"""List available resources from the server."""
522492
return await self._cached_fetch(
523493
"resources/list",
524494
cursor=cursor,
@@ -534,11 +504,7 @@ async def list_resource_templates(
534504
meta: RequestParamsMeta | None = None,
535505
cache_mode: CacheMode = "use",
536506
) -> ListResourceTemplatesResult:
537-
"""List available resource templates from the server.
538-
539-
`cache_mode` adjusts the response cache's behavior for this call (see `CacheMode`);
540-
calls carrying `meta` always reach the server.
541-
"""
507+
"""List available resource templates from the server."""
542508
return await self._cached_fetch(
543509
"resources/templates/list",
544510
cursor=cursor,
@@ -568,13 +534,9 @@ async def read_resource(
568534
input_responses: Responses to seed the first call with (e.g. when
569535
resuming from a persisted `InputRequiredResult`).
570536
request_state: Opaque state to seed the first call with.
571-
meta: Additional metadata for the request. Calls carrying `meta`
572-
always reach the server.
573-
cache_mode: Adjusts the response cache's behavior for this call
574-
(see `CacheMode`). Seeded calls (either `input_responses` or
575-
`request_state` set) are resumptions of a multi-round-trip
576-
read and ignore it entirely: no cache read, no write, no
577-
refresh purge.
537+
meta: Additional metadata for the request.
538+
cache_mode: Cache behavior for this call (see `CacheMode`); seeded
539+
calls (`input_responses` or `request_state` set) ignore it.
578540
579541
Returns:
580542
The resource content.
@@ -589,36 +551,28 @@ async def retry(r: InputResponses | None, s: str | None) -> ReadResourceResult |
589551
uri, input_responses=r, request_state=s, meta=meta, allow_input_required=True
590552
)
591553

592-
# Results of requests carrying inputResponses or requestState must never be
593-
# cached (spec MUST), and a seeded call exists to resume a specific exchange -
594-
# serving it from the cache would skip the resumption.
554+
# Seeded calls resume a specific exchange and must never be cached (spec MUST).
595555
seeded = input_responses is not None or request_state is not None
596556
cache = None if seeded else self._response_cache
597557
if cache is None or cache_mode == "bypass":
598558
return await self._drive_input_required(await retry(input_responses, request_state), retry)
599-
# Cache participation requires a live session: a closed (or never-entered)
600-
# client raises the no-context RuntimeError here, never serving stale entries.
559+
# A closed (or never-entered) client must raise, never serve cached entries.
601560
_ = self.session
602561
if meta is not None and cache_mode == "use":
603562
# Calls carrying meta always reach the server (mirrors `_cached_fetch`).
604563
cache_mode = "refresh"
605564
if cache_mode == "use" and (hit := await cache.read("resources/read", uri)) is not None:
606-
# InputRequiredResult is never stored (only terminal first-round results
607-
# are written below), so a hit is always terminal and legitimately skips
608-
# the driver.
565+
# Only terminal first-round results are stored, so a hit legitimately skips the driver.
609566
return cast(ReadResourceResult, hit)
610567
gen = cache.capture("resources/read", uri)
611568
first = await retry(None, None)
612569
if not isinstance(first, InputRequiredResult):
613570
await cache.write("resources/read", uri, first, gen, cache_mode)
614571
elif cache_mode == "refresh":
615-
# An input_required resolution can never be stored, but the explicit
616-
# refresh still superseded whatever was cached: purge the warm entry
617-
# so it cannot be served again (the same supersession rule as a
618-
# refreshed ttl<=0 result in `ClientResponseCache.write`).
572+
# The refresh superseded whatever was cached, but an input_required resolution
573+
# cannot be stored: purge the warm entry so it cannot be served again.
619574
await cache.evict_key("resources/read", uri)
620-
# A terminal result reached through driver rounds is never cached: the rounds
621-
# carried inputResponses (the same spec MUST as the seeded skip above).
575+
# Driver rounds carry inputResponses, so a terminal result reached through them is never cached (spec MUST).
622576
return await self._drive_input_required(first, retry)
623577

624578
async def subscribe_resource(self, uri: str, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
@@ -688,11 +642,7 @@ async def list_prompts(
688642
meta: RequestParamsMeta | None = None,
689643
cache_mode: CacheMode = "use",
690644
) -> ListPromptsResult:
691-
"""List available prompts from the server.
692-
693-
`cache_mode` adjusts the response cache's behavior for this call (see `CacheMode`);
694-
calls carrying `meta` always reach the server.
695-
"""
645+
"""List available prompts from the server."""
696646
return await self._cached_fetch(
697647
"prompts/list",
698648
cursor=cursor,
@@ -788,20 +738,15 @@ async def list_tools(
788738
meta: RequestParamsMeta | None = None,
789739
cache_mode: CacheMode = "use",
790740
) -> ListToolsResult:
791-
"""List available tools from the server.
792-
793-
`cache_mode` adjusts the response cache's behavior for this call (see `CacheMode`);
794-
calls carrying `meta` always reach the server.
795-
"""
741+
"""List available tools from the server."""
796742
return await self._cached_fetch(
797743
"tools/list",
798744
cursor=cursor,
799745
meta=meta,
800746
cache_mode=cache_mode,
801747
send=lambda: self.session.list_tools(params=PaginatedRequestParams(cursor=cursor, _meta=meta)),
802748
# A cache hit skips session.list_tools, so the session re-absorbs the
803-
# served listing to rebuild its derived per-tool state (header maps,
804-
# output schemas) - idempotent on the already-filtered stored value.
749+
# served listing to rebuild its derived per-tool state.
805750
absorb=self.session._absorb_tool_listing, # pyright: ignore[reportPrivateUsage]
806751
)
807752

src/mcp/client/session.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,7 @@
5656

5757

5858
def _clamp_inbound_ttl(raw: dict[str, Any]) -> None:
59-
"""Floor a negative inbound `ttlMs` to 0, in place (2026-07-28 caching SHOULD).
60-
61-
Runs before the surface validation, whose `ge=0` would otherwise fail the
62-
whole call over one bad hint. Emit-side strictness is untouched — only a
63-
misbehaving peer reaches this. Floats are floored too; bools are not numbers
64-
here and are left for the validation to reject.
65-
"""
59+
"""Floor a negative inbound `ttlMs` to 0 before `ge=0` validation fails the call (2026-07-28 caching SHOULD)."""
6660
ttl = raw.get("ttlMs")
6761
if isinstance(ttl, int | float) and not isinstance(ttl, bool) and ttl < 0:
6862
raw["ttlMs"] = 0
@@ -473,10 +467,7 @@ async def send_discover(self, version: str) -> dict[str, Any]:
473467
"headers": {MCP_PROTOCOL_VERSION_HEADER: version, MCP_METHOD_HEADER: data["method"]},
474468
}
475469
raw = await self._dispatcher.send_raw_request(data["method"], data.get("params"), opts)
476-
# Clamping here (not in the callers) covers both discover() and the
477-
# mode='auto' probe — un-floored, a negative ttl fails DiscoverResult
478-
# validation in the probe, which reads as "not modern evidence" and
479-
# silently downgrades the connection to the legacy handshake.
470+
# Un-floored, a negative ttl fails the mode='auto' probe's validation and silently downgrades the handshake.
480471
_clamp_inbound_ttl(raw)
481472
return raw
482473

@@ -918,13 +909,9 @@ async def list_tools(self, *, params: types.PaginatedRequestParams | None = None
918909
return self._absorb_tool_listing(result)
919910

920911
def _absorb_tool_listing(self, result: types.ListToolsResult) -> types.ListToolsResult:
921-
"""Filter a tool listing per the 2026 x-mcp-header MUST and rebuild the derived
922-
per-tool state (arg→header maps, output schemas) from it.
912+
"""Filter the listing per the 2026 x-mcp-header MUST and rebuild derived per-tool state, in place.
923913
924-
Idempotent, so the client response cache can re-absorb a served listing: stored
925-
values are already post-filter, making the re-filter a no-op that rebuilds the
926-
maps and schemas from the served value. `result` is mutated in place (the cache
927-
only ever passes a private deep copy).
914+
Idempotent: cached values are already post-filter, so the response cache can re-absorb a served listing.
928915
"""
929916
if self._negotiated_version in MODERN_PROTOCOL_VERSIONS:
930917
# 2026-07-28: clients MUST drop tools whose x-mcp-header annotations are invalid.

src/mcp/server/caching.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ def validate_cache_hints(cache_hints: Mapping[Any, Any] | None) -> dict[str, Cac
7373
"""
7474
if cache_hints is None:
7575
return {}
76-
# Keys come from an untyped mapping, so format via repr: a non-string key
77-
# must produce this ValueError too, not a TypeError from sorted/join.
76+
# repr-format keys so a non-string key raises this ValueError, not a TypeError from sorted/join.
7877
unknown = sorted(repr(method) for method in cache_hints if method not in CACHEABLE_METHODS)
7978
if unknown:
8079
raise ValueError(f"cache_hints keys must be cacheable methods (see CacheableMethod); got: {', '.join(unknown)}")

src/mcp/server/runner.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,25 +200,20 @@ async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> HandlerResult:
200200
raise MCPError.from_error_data(result)
201201
# Fill cache hints on the handler result, before the serialize sieve
202202
# decides whether the negotiated version carries the fields at all.
203-
# `input_required` interim results are not `CacheableResult` models
204-
# and mapping results declaring that shape are skipped explicitly,
205-
# so the MRTR carve-out (no hints on them) holds on both paths.
203+
# MRTR carve-out: `input_required` interim results, typed or mapping, never get hints.
206204
if (hint := self.server.cache_hints.get(method)) is not None:
207205
if isinstance(result, CacheableResult):
208206
result = apply_cache_hint(result, hint)
209207
elif isinstance(result, Mapping) and result.get("resultType") != "input_required":
210-
# Same per-field precedence as `apply_cache_hint`: wire keys the
211-
# handler put in the mapping win. Fresh dict, so a mapping the
212-
# handler may still hold an alias to is never mutated.
208+
# Hint keys first so wire keys the handler set win, matching `apply_cache_hint` precedence.
213209
result = {"ttlMs": hint.ttl_ms, "cacheScope": hint.scope, **result}
214210
# Dump and serialize inside the chain so the OpenTelemetry span (the
215211
# outermost middleware) records a failing handler return shape too.
216212
return self._serialize(method, version, result)
217213

218214
call = self._compose_server_middleware(_inner)
219215
# `_inner` already produced the wire dict; a middleware that short-circuited
220-
# without `call_next` is trusted to return its own well-formed result,
221-
# configured cache hints included.
216+
# without `call_next` is trusted to return its own well-formed result.
222217
result = _dump_result(await call(ctx))
223218
if method == "initialize":
224219
# Commit only on chain success, so a middleware veto leaves no state.

0 commit comments

Comments
 (0)