382060f022
Phase 2 of the MCP 2026-07-28 migration (#69931), on top of the SDK 2.x migration (#88180): - Protocol-era negotiation (_negotiate_session): per-server `protocol` config key — auto (default, handshake-first with server/discover fallback on -32022/-32601), stateless (discover-first), legacy (handshake only). Auto is handshake-first deliberately: zero extra round-trips and zero behavior change for the entire existing server fleet, while 2026-07-28-only servers now connect via the fallback. All four transport call sites (stdio, SSE, new HTTP, legacy HTTP) route through the one choke point, so the CLI/desktop probe path inherits it too. - SEP-2549 list caching: tools/list ttlMs/cacheScope hints are captured during discovery and bound to the lazy-startup schema cache — TTL'd entries expire and force a live re-probe; hint-less (pre-2026) servers keep the never-expires behavior. Pagination continuation now speaks both SDK generations (params= vs cursor=). - SEP-837: OAuth client metadata declares application_type=native (config-overridable), with a fallback for 1.x-era metadata models. (RFC 9207 iss validation and SEP-2352 issuer-keyed credentials are native to SDK 2.0's OAuthClientProvider — verified, no client-side gap.) - SEP-2577 deprecation posture: SamplingHandler docstring marks the Sampling feature as upstream-deprecated (12-month window) — kept fully functional, closed to new capability. - Docs: `protocol` key in the MCP config reference.
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""SEP-2549 schema-cache TTL expiry (tools/mcp_schema_cache.py)."""
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from tools import mcp_schema_cache as sc
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolated_cache(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(sc, "_cache_path", lambda: tmp_path / "cache.json")
|
|
yield
|
|
|
|
|
|
def test_entry_without_ttl_never_expires():
|
|
sc.write_cache_entry("srv", "fp", tools=[{"name": "t"}])
|
|
assert sc.get_cached_entry("srv", "fp") is not None
|
|
|
|
|
|
def test_entry_within_ttl_served():
|
|
sc.write_cache_entry("srv", "fp", tools=[{"name": "t"}], ttl_ms=60_000)
|
|
entry = sc.get_cached_entry("srv", "fp")
|
|
assert entry is not None
|
|
assert entry["ttl_ms"] == 60_000
|
|
assert "written_at" in entry
|
|
|
|
|
|
def test_entry_past_ttl_is_a_miss(monkeypatch):
|
|
sc.write_cache_entry("srv", "fp", tools=[{"name": "t"}], ttl_ms=1_000)
|
|
real_time = time.time
|
|
monkeypatch.setattr(sc.time, "time", lambda: real_time() + 2.0)
|
|
assert sc.get_cached_entry("srv", "fp") is None
|
|
|
|
|
|
def test_ttl_rewrite_advances_written_at():
|
|
sc.write_cache_entry("srv", "fp", tools=[{"name": "t"}], ttl_ms=60_000)
|
|
first = sc.get_cached_entry("srv", "fp")["written_at"]
|
|
time.sleep(0.01)
|
|
# Identical payload would previously short-circuit; TTL'd entries must
|
|
# rewrite so written_at advances on every live reconfirmation.
|
|
sc.write_cache_entry("srv", "fp", tools=[{"name": "t"}], ttl_ms=60_000)
|
|
second = sc.get_cached_entry("srv", "fp")["written_at"]
|
|
assert second > first
|
|
|
|
|
|
def test_cache_scope_round_trips():
|
|
sc.write_cache_entry(
|
|
"srv", "fp", tools=[{"name": "t"}], ttl_ms=60_000, cache_scope="private"
|
|
)
|
|
assert sc.get_cached_entry("srv", "fp")["cache_scope"] == "private"
|