@@ -633,10 +633,6 @@ def __post_init__(self) -> None:
633633 "client:output-schema:skip-on-error" : Requirement (
634634 source = "sdk" ,
635635 behavior = "The client skips structured-content validation when the tool result has isError true." ,
636- deferred = (
637- "Not yet covered here: planned gap test (an isError result with mismatching structuredContent "
638- "is returned to the caller rather than rejected)."
639- ),
640636 ),
641637 "client:output-schema:validate" : Requirement (
642638 source = f"{ SPEC_BASE_URL } /server/tools#output-schema" ,
@@ -645,28 +641,49 @@ def __post_init__(self) -> None:
645641 "is rejected by the client: the call raises instead of returning the invalid result."
646642 ),
647643 ),
644+ "client:output-schema:missing-structured" : Requirement (
645+ source = "sdk" ,
646+ behavior = "A tool that declares an output schema but returns no structuredContent fails client-side validation." ,
647+ ),
648+ "client:output-schema:auto-list" : Requirement (
649+ source = "sdk" ,
650+ behavior = (
651+ "Calling a tool whose output schema is not yet cached issues an implicit tools/list to "
652+ "populate the cache; subsequent calls of the same tool do not."
653+ ),
654+ divergence = Divergence (
655+ note = (
656+ "Design concern rather than spec violation: the implicit request is invisible to the "
657+ "caller, and against a server that registers only on_call_tool a successful call surfaces "
658+ "as METHOD_NOT_FOUND from a tools/list the caller never asked for."
659+ ),
660+ ),
661+ ),
648662 "mcpserver:output-schema:missing-structured" : Requirement (
649663 source = f"{ SPEC_BASE_URL } /server/tools#output-schema" ,
650664 behavior = "A tool with an output schema whose function returns no structured content produces a server error." ,
651- deferred = "Not yet covered here: planned gap test (output schema declared but no structured content returned)." ,
652665 ),
653666 "mcpserver:output-schema:server-validate" : Requirement (
654667 source = f"{ SPEC_BASE_URL } /server/tools#output-schema" ,
655668 behavior = (
656669 "MCPServer validates structured content against the tool's output schema before returning; a "
657670 "mismatch produces a server error."
658671 ),
659- deferred = "Not yet covered here: planned gap test (server-side output schema validation failure)." ,
660672 ),
661673 "mcpserver:output-schema:skip-on-error" : Requirement (
662674 source = "sdk" ,
663675 behavior = "Server-side output schema validation is skipped when the tool returns an isError result." ,
664- deferred = "Not yet covered here: planned gap test (isError results bypass server-side schema validation)." ,
665676 ),
666677 "mcpserver:tool:duplicate-name" : Requirement (
667678 source = f"{ SPEC_BASE_URL } /server/tools#tool-names" ,
668679 behavior = "Registering a tool with a name already in use is rejected at registration time." ,
669- deferred = "Not yet covered here: planned gap test (duplicate tool registration)." ,
680+ divergence = Divergence (
681+ note = (
682+ "MCPServer logs a warning and keeps the first registration instead of rejecting; "
683+ "warn_on_duplicate_tools defaults to True and warning is the only effect -- there is "
684+ "no rejection mode."
685+ ),
686+ ),
670687 ),
671688 "mcpserver:tool:extra" : Requirement (
672689 source = "sdk" ,
@@ -693,7 +710,10 @@ def __post_init__(self) -> None:
693710 "mcpserver:tool:naming-validation" : Requirement (
694711 source = "sdk" ,
695712 behavior = "Tool names that violate the spec's naming rules are rejected at registration time." ,
696- deferred = "Not yet covered here: tool-name validation at registration has not been pinned yet." ,
713+ deferred = (
714+ "Not implemented in the SDK: MCPServer accepts any string as a tool name; there is no "
715+ "spec-naming-rules check at registration time."
716+ ),
697717 ),
698718 "mcpserver:tool:output-schema:model" : Requirement (
699719 source = "sdk" ,
@@ -737,10 +757,6 @@ def __post_init__(self) -> None:
737757 "A tool function that raises the URL-elicitation-required error surfaces to the caller as "
738758 "error -32042 with the elicitation parameters intact."
739759 ),
740- deferred = (
741- "Not yet covered here: the low-level equivalent is pinned by elicitation:url:required-error; "
742- "the MCPServer-decorated path is a planned gap test."
743- ),
744760 ),
745761 # ═══════════════════════════════════════════════════════════════════════════
746762 # MCPServer: Context helpers (SDK)
@@ -882,12 +898,20 @@ def __post_init__(self) -> None:
882898 "mcpserver:resource:duplicate-name" : Requirement (
883899 source = "sdk" ,
884900 behavior = "Registering a resource or template with a duplicate identifier is rejected at registration time." ,
885- deferred = "Not yet covered here: planned gap test (duplicate resource registration)." ,
901+ divergence = Divergence (
902+ note = (
903+ "MCPServer logs a warning and keeps the first registration instead of rejecting; same "
904+ "warn-and-ignore behaviour as duplicate tool names (mcpserver:tool:duplicate-name)."
905+ ),
906+ ),
907+ deferred = (
908+ "Not yet covered here: mechanical sibling of mcpserver:tool:duplicate-name (same "
909+ "warn-and-ignore behaviour); planned as a small follow-on to that test."
910+ ),
886911 ),
887912 "mcpserver:resource:read-throws-surfaced" : Requirement (
888913 source = "sdk" ,
889914 behavior = "A resource function that raises is surfaced to the caller as a JSON-RPC error response." ,
890- deferred = "Not yet covered here: planned gap test (resource function raising during read)." ,
891915 ),
892916 "mcpserver:resource:static" : Requirement (
893917 source = "sdk" ,
@@ -983,7 +1007,6 @@ def __post_init__(self) -> None:
9831007 "mcpserver:prompt:args-validation" : Requirement (
9841008 source = f"{ SPEC_BASE_URL } /server/prompts#implementation-considerations" ,
9851009 behavior = "prompts/get arguments that fail the prompt's argument schema are rejected before the function runs." ,
986- deferred = "Not yet covered here: planned gap test (argument validation on decorated prompts)." ,
9871010 ),
9881011 "mcpserver:prompt:decorated" : Requirement (
9891012 source = "sdk" ,
@@ -995,12 +1018,20 @@ def __post_init__(self) -> None:
9951018 "mcpserver:prompt:duplicate-name" : Requirement (
9961019 source = "sdk" ,
9971020 behavior = "Registering a duplicate prompt name is rejected at registration time." ,
998- deferred = "Not yet covered here: planned gap test (duplicate prompt registration)." ,
1021+ divergence = Divergence (
1022+ note = (
1023+ "MCPServer logs a warning and keeps the first registration instead of rejecting; same "
1024+ "warn-and-ignore behaviour as duplicate tool names (mcpserver:tool:duplicate-name)."
1025+ ),
1026+ ),
1027+ deferred = (
1028+ "Not yet covered here: mechanical sibling of mcpserver:tool:duplicate-name (same "
1029+ "warn-and-ignore behaviour); planned as a small follow-on to that test."
1030+ ),
9991031 ),
10001032 "mcpserver:prompt:optional-args" : Requirement (
10011033 source = "sdk" ,
10021034 behavior = "A prompt with optional arguments can be fetched without supplying them." ,
1003- deferred = "Not yet covered here: planned gap test (optional prompt arguments omitted)." ,
10041035 ),
10051036 "mcpserver:prompt:unknown-name" : Requirement (
10061037 source = f"{ SPEC_BASE_URL } /server/prompts#error-handling" ,
@@ -1056,7 +1087,6 @@ def __post_init__(self) -> None:
10561087 "MCPServer advertises the completions capability when at least one completion source is "
10571088 "registered, and omits it otherwise."
10581089 ),
1059- deferred = "Not yet covered here: planned gap test (automatic completions capability derivation)." ,
10601090 ),
10611091 # ═══════════════════════════════════════════════════════════════════════════
10621092 # Logging
@@ -1112,7 +1142,6 @@ def __post_init__(self) -> None:
11121142 behavior = (
11131143 "A client that handles sampling requests advertises the sampling capability in its initialize request."
11141144 ),
1115- deferred = "Not yet covered here: planned gap test (positive sampling capability declaration)." ,
11161145 ),
11171146 "sampling:create:basic" : Requirement (
11181147 source = f"{ SPEC_BASE_URL } /client/sampling#creating-messages" ,
@@ -1137,10 +1166,6 @@ def __post_init__(self) -> None:
11371166 "capability; the server-side validator only checks tools/tool_choice."
11381167 ),
11391168 ),
1140- deferred = (
1141- "Not implemented in the SDK: include_context is forwarded regardless of the client's declared "
1142- "sampling.context capability (unlike tools, which are gated by the server-side validator)."
1143- ),
11441169 ),
11451170 "sampling:create:model-preferences" : Requirement (
11461171 source = f"{ SPEC_BASE_URL } /client/sampling#model-preferences" ,
@@ -1168,7 +1193,6 @@ def __post_init__(self) -> None:
11681193 "sampling:create-message:audio-content" : Requirement (
11691194 source = f"{ SPEC_BASE_URL } /client/sampling#audio-content" ,
11701195 behavior = "Sampling messages can carry audio content: base64 data with a mimeType." ,
1171- deferred = "Not yet covered here: planned gap test (audio content in sampling messages, both directions)." ,
11721196 ),
11731197 "sampling:create-message:image-content" : Requirement (
11741198 source = f"{ SPEC_BASE_URL } /client/sampling#image-content" ,
@@ -1191,15 +1215,20 @@ def __post_init__(self) -> None:
11911215 "sampling:message:content-cardinality" : Requirement (
11921216 source = f"{ SPEC_BASE_URL } /client/sampling" ,
11931217 behavior = "A sampling message's content may be a single block or an array of blocks." ,
1194- deferred = "Not yet covered here: planned gap test (list-valued sampling message content)." ,
11951218 ),
11961219 "sampling:result:no-tools-single-content" : Requirement (
11971220 source = "sdk" ,
11981221 behavior = (
11991222 "When the request carries no tools, a sampling callback result whose content is an array is "
12001223 "rejected by the client."
12011224 ),
1202- deferred = "Not yet covered here: planned gap test (array content rejected for tool-free sampling)." ,
1225+ divergence = Divergence (
1226+ note = (
1227+ "The client does not validate the callback result against the request shape; an array-content "
1228+ "result for a tool-free request is accepted client-side and surfaces as a raw "
1229+ "pydantic.ValidationError from the server's response parsing (send_request) instead."
1230+ ),
1231+ ),
12031232 ),
12041233 "sampling:result:with-tools-array-content" : Requirement (
12051234 source = "sdk" ,
@@ -1225,7 +1254,6 @@ def __post_init__(self) -> None:
12251254 "Every assistant tool_use block in a sampling request must be matched by a tool_result with "
12261255 "the same id in the following user message; an unmatched tool_use is rejected with Invalid params."
12271256 ),
1228- deferred = "Not yet covered here: planned gap test (unmatched tool_use rejected by the validator)." ,
12291257 ),
12301258 "sampling:tools:server-gated-by-capability" : Requirement (
12311259 source = f"{ SPEC_BASE_URL } /client/sampling#tools-in-sampling" ,
@@ -1433,9 +1461,10 @@ def __post_init__(self) -> None:
14331461 "of roots changes."
14341462 ),
14351463 deferred = (
1436- "Not implemented in the SDK: the client keeps no managed roots store, so nothing fires "
1437- "automatically when the configured roots change; emission is an explicit "
1438- "send_roots_list_changed() call (pinned by roots:list-changed)."
1464+ "Not implemented in the SDK: the client does not own the root set (it calls back to the host "
1465+ "via list_roots_callback), so there is no mutation it could observe to auto-emit on; the SDK "
1466+ "provides send_roots_list_changed() for the host to call when its roots change, and that "
1467+ "emission path is covered by roots:list-changed."
14391468 ),
14401469 ),
14411470 "roots:list:basic" : Requirement (
@@ -1467,8 +1496,9 @@ def __post_init__(self) -> None:
14671496 source = f"{ SPEC_BASE_URL } /client/roots#root" ,
14681497 behavior = "Every root returned by the client identifies itself with a file:// URI." ,
14691498 deferred = (
1470- "Not yet covered here: planned gap test (the SDK's Root type enforces the file:// scheme; pin "
1471- "it end-to-end through roots/list)."
1499+ "Schema-level validation: the FileUrl type on Root.uri rejects any non-file:// scheme at "
1500+ "construction and at parse, so a non-conforming root cannot reach the wire from either side; "
1501+ "type-level coverage belongs in tests/test_types.py rather than this interaction suite."
14721502 ),
14731503 ),
14741504 # ═══════════════════════════════════════════════════════════════════════════
0 commit comments