Skip to content

Commit d4a3558

Browse files
committed
test: add URL elicitation, subscriptions, pagination, timeouts, and meta interaction tests
Covers URL-mode elicitation (including the elicitation/complete lifecycle and the -32042 rejection flow), resource subscriptions and update notifications, cursor pagination across all four list methods, request and session read timeouts, _meta round trips, and the MCPServer Context convenience methods. Removes the 'pragma: no cover' from the resource-updated send path now that it is covered.
1 parent 5216997 commit d4a3558

10 files changed

Lines changed: 1000 additions & 11 deletions

File tree

src/mcp/server/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ async def send_log_message(
223223
related_request_id,
224224
)
225225

226-
async def send_resource_updated(self, uri: str | AnyUrl) -> None: # pragma: no cover
226+
async def send_resource_updated(self, uri: str | AnyUrl) -> None:
227227
"""Send a resource updated notification."""
228228
await self.send_notification(
229229
types.ResourceUpdatedNotification(

tests/interaction/_helpers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Shared type aliases for the interaction suite.
2+
3+
Keep this module small: it exists only for types that every test would otherwise have to
4+
assemble from the SDK's internals to annotate a client callback. Server fixtures and assertion
5+
helpers belong in the test that uses them.
6+
"""
7+
8+
from mcp.shared.session import RequestResponder
9+
from mcp.types import ClientResult, ServerNotification, ServerRequest
10+
11+
# TODO: this union is the parameter type of every client message handler (MessageHandlerFnT),
12+
# but the SDK does not export a name for it -- writing a correctly-typed handler requires
13+
# importing RequestResponder from mcp.shared.session and assembling the union by hand. It
14+
# should be a named, exported alias next to MessageHandlerFnT (like ClientRequestContext is
15+
# for the request callbacks), at which point this module can be deleted.
16+
IncomingMessage = RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception
17+
"""Everything a client message handler can receive."""

tests/interaction/_requirements.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,71 @@ class Requirement:
151151
behavior="A progress notification sent by the client is delivered to the server's progress handler.",
152152
),
153153
# ═══════════════════════════════════════════════════════════════════════════
154+
# Timeouts
155+
# ═══════════════════════════════════════════════════════════════════════════
156+
"timeouts:per-request": Requirement(
157+
source=f"{SPEC_BASE_URL}/basic/lifecycle#timeouts",
158+
behavior=(
159+
"A request that exceeds its read timeout fails with a request-timeout error instead of "
160+
"waiting forever for the response."
161+
),
162+
divergence=Divergence(
163+
note=(
164+
"The spec says the requester SHOULD issue a cancellation notification for the timed-out "
165+
"request; the client only raises locally and sends nothing, so the server keeps running "
166+
"the handler."
167+
),
168+
),
169+
),
170+
"timeouts:session-survives": Requirement(
171+
source=f"{SPEC_BASE_URL}/basic/lifecycle#timeouts",
172+
behavior="The session continues to serve new requests after an earlier request timed out.",
173+
),
174+
"timeouts:session-default": Requirement(
175+
source=f"{SPEC_BASE_URL}/basic/lifecycle#timeouts",
176+
behavior="A session-level read timeout applies to every request that does not override it.",
177+
),
178+
# ═══════════════════════════════════════════════════════════════════════════
179+
# Pagination
180+
# ═══════════════════════════════════════════════════════════════════════════
181+
"pagination:cursor-round-trip": Requirement(
182+
source=f"{SPEC_BASE_URL}/server/utilities/pagination#response-format",
183+
behavior=(
184+
"The nextCursor returned by a list handler reaches the client, and the cursor the client "
185+
"sends back on the next call reaches the handler as an opaque string."
186+
),
187+
),
188+
"pagination:exhaustion": Requirement(
189+
source=f"{SPEC_BASE_URL}/server/utilities/pagination#response-format",
190+
behavior=(
191+
"Following nextCursor until it is absent yields every page exactly once; a result without "
192+
"nextCursor ends the sequence."
193+
),
194+
),
195+
"pagination:resources": Requirement(
196+
source=f"{SPEC_BASE_URL}/server/utilities/pagination#operations-supporting-pagination",
197+
behavior="resources/list supports cursor pagination.",
198+
),
199+
"pagination:resource-templates": Requirement(
200+
source=f"{SPEC_BASE_URL}/server/utilities/pagination#operations-supporting-pagination",
201+
behavior="resources/templates/list supports cursor pagination.",
202+
),
203+
"pagination:prompts": Requirement(
204+
source=f"{SPEC_BASE_URL}/server/utilities/pagination#operations-supporting-pagination",
205+
behavior="prompts/list supports cursor pagination.",
206+
),
207+
# ═══════════════════════════════════════════════════════════════════════════
208+
# Request metadata
209+
# ═══════════════════════════════════════════════════════════════════════════
210+
"meta:request-to-handler": Requirement(
211+
source=f"{SPEC_BASE_URL}/basic#meta",
212+
behavior="The _meta object the client attaches to a request is visible to the server handler.",
213+
),
214+
"meta:result-to-client": Requirement(
215+
source=f"{SPEC_BASE_URL}/basic#meta",
216+
behavior="The _meta object a handler attaches to its result is delivered to the client.",
217+
),
218+
# ═══════════════════════════════════════════════════════════════════════════
154219
# Ping
155220
# ═══════════════════════════════════════════════════════════════════════════
156221
"ping:client-to-server": Requirement(
@@ -283,6 +348,29 @@ class Requirement:
283348
source=f"{SPEC_BASE_URL}/server/resources#error-handling",
284349
behavior="resources/read for an unknown URI returns a JSON-RPC error; the spec reserves -32002 for it.",
285350
),
351+
"resources:templates:list": Requirement(
352+
source=f"{SPEC_BASE_URL}/server/resources#resource-templates",
353+
behavior=(
354+
"resources/templates/list returns the registered templates with their uriTemplate and descriptive fields."
355+
),
356+
),
357+
"resources:subscribe": Requirement(
358+
source=f"{SPEC_BASE_URL}/server/resources#subscriptions",
359+
behavior="resources/subscribe delivers the URI to the server's subscribe handler and returns an empty result.",
360+
),
361+
"resources:unsubscribe": Requirement(
362+
source=f"{SPEC_BASE_URL}/server/resources#subscriptions",
363+
behavior=(
364+
"resources/unsubscribe delivers the URI to the server's unsubscribe handler and returns an empty result."
365+
),
366+
),
367+
"resources:updated-notification": Requirement(
368+
source=f"{SPEC_BASE_URL}/server/resources#subscriptions",
369+
behavior=(
370+
"A resources/updated notification sent by the server reaches the client carrying the URI of "
371+
"the changed resource."
372+
),
373+
),
286374
# ═══════════════════════════════════════════════════════════════════════════
287375
# Notifications: list_changed (server → client)
288376
# ═══════════════════════════════════════════════════════════════════════════
@@ -367,6 +455,36 @@ class Requirement:
367455
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
368456
behavior="A form-mode elicitation answered with action 'cancel' returns no content to the handler.",
369457
),
458+
"elicitation:url:accept": Requirement(
459+
source=f"{SPEC_BASE_URL}/client/elicitation#url-mode-elicitation",
460+
behavior=(
461+
"A URL-mode elicitation delivers the message, URL, and elicitationId to the client; an accept "
462+
"response carries no content (accept means the user agreed to visit the URL, not that the "
463+
"interaction completed)."
464+
),
465+
),
466+
"elicitation:url:decline": Requirement(
467+
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
468+
behavior="A URL-mode elicitation answered with decline returns the action with no content.",
469+
),
470+
"elicitation:url:cancel": Requirement(
471+
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
472+
behavior="A URL-mode elicitation answered with cancel returns the action with no content.",
473+
),
474+
"elicitation:complete-notification": Requirement(
475+
source=f"{SPEC_BASE_URL}/client/elicitation#completion-notification",
476+
behavior=(
477+
"An elicitation/complete notification sent by the server after an out-of-band elicitation "
478+
"finishes reaches the client carrying the elicitationId."
479+
),
480+
),
481+
"elicitation:url:required-error": Requirement(
482+
source=f"{SPEC_BASE_URL}/client/elicitation#url-elicitation-required-error",
483+
behavior=(
484+
"A handler that cannot proceed without a URL elicitation rejects the request with error "
485+
"-32042, carrying the pending elicitations in the error data."
486+
),
487+
),
370488
"elicitation:form:not-supported": Requirement(
371489
source=f"{SPEC_BASE_URL}/client/elicitation#capabilities",
372490
behavior=(
@@ -457,6 +575,30 @@ class Requirement:
457575
),
458576
),
459577
),
578+
"mcpserver:context:logging": Requirement(
579+
source="sdk",
580+
behavior=(
581+
"The Context logging helpers (debug/info/warning/error) send log message notifications at the "
582+
"corresponding severity."
583+
),
584+
),
585+
"mcpserver:context:progress": Requirement(
586+
source="sdk",
587+
behavior=(
588+
"Context.report_progress sends a progress notification against the requesting client's progress token."
589+
),
590+
),
591+
"mcpserver:context:elicit": Requirement(
592+
source="sdk",
593+
behavior=(
594+
"Context.elicit sends a form elicitation built from a typed schema and returns a typed "
595+
"accepted/declined/cancelled result."
596+
),
597+
),
598+
"mcpserver:context:read-resource": Requirement(
599+
source="sdk",
600+
behavior="Context.read_resource reads a resource registered on the same server from inside a tool.",
601+
),
460602
"mcpserver:tools:handler-exception": Requirement(
461603
source="sdk",
462604
behavior=(

0 commit comments

Comments
 (0)