Skip to content

Commit 1797d9a

Browse files
committed
Move CacheableMethod to mcp_types.methods and derive CACHEABLE_METHODS from the method registry
1 parent 8d0f928 commit 1797d9a

4 files changed

Lines changed: 38 additions & 34 deletions

File tree

‎src/mcp-types/mcp_types/methods.py‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from collections.abc import Mapping
1414
from functools import cache
1515
from types import MappingProxyType, UnionType
16-
from typing import Any, Final, TypeVar
16+
from typing import Any, Final, Literal, TypeVar, get_args
1717

1818
from pydantic import BaseModel, TypeAdapter
1919

@@ -23,9 +23,11 @@
2323
from mcp_types.version import KNOWN_PROTOCOL_VERSIONS
2424

2525
__all__ = [
26+
"CACHEABLE_METHODS",
2627
"CLIENT_NOTIFICATIONS",
2728
"CLIENT_REQUESTS",
2829
"CLIENT_RESULTS",
30+
"CacheableMethod",
2931
"MONOLITH_NOTIFICATIONS",
3032
"MONOLITH_REQUESTS",
3133
"MONOLITH_RESULTS",
@@ -404,6 +406,29 @@
404406
"""Monolith result model (or two-arm union) per request method."""
405407

406408

409+
# --- Cacheable methods ---
410+
411+
CacheableMethod = Literal[
412+
"prompts/list",
413+
"resources/list",
414+
"resources/read",
415+
"resources/templates/list",
416+
"server/discover",
417+
"tools/list",
418+
]
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`."""
422+
423+
CACHEABLE_METHODS: Final[frozenset[str]] = frozenset(
424+
method
425+
for method, row in MONOLITH_RESULTS.items()
426+
if any(issubclass(arm, types.CacheableResult) for arm in (get_args(row) if isinstance(row, UnionType) else (row,)))
427+
)
428+
"""Runtime mirror of `CacheableMethod`, derived from `MONOLITH_RESULTS`: a
429+
method is cacheable iff its result row has a `CacheableResult` arm."""
430+
431+
407432
# --- Parse functions ---
408433

409434
# Envelope stubs merged into bodies for surface validation (surface classes are full frames).

‎src/mcp/server/caching.py‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,13 @@
1111

1212
from collections.abc import Mapping
1313
from dataclasses import dataclass
14-
from typing import Any, Final, Literal, TypeVar, get_args
14+
from typing import Any, Literal, TypeVar
1515

1616
import mcp_types as types
17+
from mcp_types.methods import CACHEABLE_METHODS, CacheableMethod
1718

1819
__all__ = ["CACHEABLE_METHODS", "CacheHint", "CacheableMethod", "apply_cache_hint", "validate_cache_hints"]
1920

20-
CacheableMethod = Literal[
21-
"prompts/list",
22-
"resources/list",
23-
"resources/read",
24-
"resources/templates/list",
25-
"server/discover",
26-
"tools/list",
27-
]
28-
"""The methods whose results carry `ttlMs`/`cacheScope`. Closed set: the spec
29-
defines caching hints on exactly these six (tests pin it to which result models
30-
mix in `CacheableResult`)."""
31-
32-
CACHEABLE_METHODS: Final[frozenset[str]] = frozenset(get_args(CacheableMethod))
33-
"""Runtime mirror of `CacheableMethod`, for callers the type checker can't see."""
34-
3521

3622
@dataclass(frozen=True, slots=True)
3723
class CacheHint:

‎tests/server/test_caching.py‎

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,25 @@
11
"""`mcp.server.caching`: `CacheHint` validation, per-field fills, and the
22
`cache_hints` constructor map reaching the wire on both server tiers."""
33

4-
from types import UnionType
5-
from typing import Any, cast, get_args
4+
from typing import Any, cast
65

76
import pytest
87
from inline_snapshot import snapshot
98
from mcp_types import (
10-
CacheableResult,
119
ListResourcesResult,
1210
ListToolsResult,
1311
PaginatedRequestParams,
1412
Resource,
1513
Tool,
16-
methods,
1714
)
1815

1916
from mcp import Client
2017
from mcp.server import CacheHint, MCPServer, Server, ServerRequestContext
21-
from mcp.server.caching import CACHEABLE_METHODS, apply_cache_hint
18+
from mcp.server.caching import apply_cache_hint
2219

2320
pytestmark = pytest.mark.anyio
2421

2522

26-
def test_cacheable_methods_match_the_result_models() -> None:
27-
"""Spec-mandated set (SEP-2549): `CACHEABLE_METHODS` mirrors exactly the
28-
methods whose monolith result models mix in `CacheableResult` - if the
29-
schema gains or loses a cacheable result, this weld breaks."""
30-
derived: set[str] = set()
31-
for method, model in methods.MONOLITH_RESULTS.items():
32-
arms = get_args(model) if isinstance(model, UnionType) else (model,)
33-
if any(isinstance(arm, type) and issubclass(arm, CacheableResult) for arm in arms):
34-
derived.add(method)
35-
assert CACHEABLE_METHODS == derived
36-
37-
3823
def test_cache_hint_defaults_match_the_conservative_model_defaults() -> None:
3924
"""SDK-defined: an unconfigured hint fills the same values the result models
4025
already default to - immediately stale, not shared - so stamping it is

‎tests/types/test_methods.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,14 @@ def test_built_in_maps_are_immutable():
548548
_assign_item(built_in)
549549

550550

551+
def test_cacheable_methods_mirror_the_cacheable_method_literal():
552+
"""Spec-mandated set (SEP-2549): the hand-written `CacheableMethod` Literal and
553+
`CACHEABLE_METHODS` (derived from which `MONOLITH_RESULTS` rows have a
554+
`CacheableResult` arm) name the same methods - if the schema gains or loses a
555+
cacheable result, this weld breaks."""
556+
assert methods.CACHEABLE_METHODS == frozenset(get_args(methods.CacheableMethod))
557+
558+
551559
def test_minimal_request_bodies_parse_through_every_request_row():
552560
for (method, version), surface_type in methods.CLIENT_REQUESTS.items():
553561
parsed = methods.parse_client_request(method, version, REQUEST_PARAMS_FIXTURES[surface_type])

0 commit comments

Comments
 (0)