Skip to content

Commit 0b3c2eb

Browse files
committed
fix: track main's optional serverInfo and empty-string server version
main now types Client.server_info as Implementation | None (serverInfo is optional at 2026-07-28) and defaults server version to "" instead of None. - ServerCard.from_server treats a derived empty version as unset, so a server without a version still fails card validation - reconcile_server_card accepts server_info=None: an anonymous server makes no identity claim, so only the protocol version check applies
1 parent 63fe92d commit 0b3c2eb

3 files changed

Lines changed: 32 additions & 8 deletions

File tree

src/mcp/client/experimental/server_card.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def parse_ai_catalog_response(response: httpx2.Response) -> AICatalog:
436436

437437
def reconcile_server_card(
438438
card: ServerCard,
439-
server_info: Implementation,
439+
server_info: Implementation | None,
440440
*,
441441
protocol_version: str | None = None,
442442
) -> list[CardMismatch]:
@@ -446,14 +446,17 @@ def reconcile_server_card(
446446
discrepancies for logging or UI. Runtime values MUST win, and cards MUST
447447
NOT drive security or access-control decisions. The card name matches
448448
when `server_info.name` equals either the full namespaced name or its
449-
post-slash local part.
449+
post-slash local part. A `None` `server_info` (a server that did not
450+
identify itself) makes no identity claim, so only the protocol version
451+
check can apply.
450452
"""
451453
mismatches: list[CardMismatch] = []
452-
local_name = card.name.split("/", 1)[1]
453-
if server_info.name not in (card.name, local_name):
454-
mismatches.append(CardMismatch(field="name", card_value=card.name, runtime_value=server_info.name))
455-
if server_info.version != card.version:
456-
mismatches.append(CardMismatch(field="version", card_value=card.version, runtime_value=server_info.version))
454+
if server_info is not None:
455+
local_name = card.name.split("/", 1)[1]
456+
if server_info.name not in (card.name, local_name):
457+
mismatches.append(CardMismatch(field="name", card_value=card.name, runtime_value=server_info.name))
458+
if server_info.version != card.version:
459+
mismatches.append(CardMismatch(field="version", card_value=card.version, runtime_value=server_info.version))
457460
if protocol_version is not None:
458461
declared = {version for remote in card.remotes or [] for version in remote.supported_protocol_versions or []}
459462
if declared and protocol_version not in declared:

src/mcp/shared/experimental/server_card.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ def from_server(
179179
resolved_icons = list(icons) if icons is not None else server.icons
180180
fields: dict[str, Any] = {
181181
"name": name,
182-
"version": version if version is not None else server.version,
182+
# A server signals "no version" as None or "" depending on its
183+
# class; both must fail card validation rather than derive.
184+
"version": version if version is not None else (server.version or None),
183185
"description": description if description is not None else server.description,
184186
"title": title if title is not None else server.title,
185187
"website_url": website_url if website_url is not None else server.website_url,

tests/client/experimental/test_server_card.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,25 @@ def test_reconcile_reports_name_and_version_mismatches() -> None:
922922
)
923923

924924

925+
def test_reconcile_without_server_info_checks_only_the_protocol_version() -> None:
926+
"""SDK-defined: `Client.server_info` is `None` when the server did not identify
927+
itself. An anonymous server makes no identity claim, so name and version cannot
928+
mismatch, while a declared protocol version union still applies."""
929+
assert reconcile_server_card(_card(), None) == []
930+
card = ServerCard(
931+
name="com.example/weather",
932+
version="1.4.0",
933+
description="Hourly forecasts.",
934+
remotes=[
935+
Remote(type="streamable-http", url="https://a.example.com/mcp", supported_protocol_versions=["2025-06-18"])
936+
],
937+
)
938+
mismatches = reconcile_server_card(card, None, protocol_version="2026-07-28")
939+
assert [(m.field, m.card_value, m.runtime_value) for m in mismatches] == snapshot(
940+
[("protocol_versions", "2025-06-18", "2026-07-28")]
941+
)
942+
943+
925944
def test_reconcile_checks_the_protocol_version_against_declared_unions() -> None:
926945
"""SDK-defined: with `protocol_version=` given, the union of every remote's declared
927946
`supportedProtocolVersions` is consulted. A member passes, a stranger mismatches."""

0 commit comments

Comments
 (0)