Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/mcp/shared/jsonrpc_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import contextvars
import logging
from collections.abc import Awaitable, Callable, Mapping
from collections.abc import Awaitable, Callable, Coroutine, Mapping
from dataclasses import dataclass, field
from functools import partial
from typing import Any, Generic, Literal, cast
Expand Down Expand Up @@ -209,7 +209,9 @@ async def _wrapped(progress: float, total: float | None, message: str | None) ->
return _wrapped


def _contained_notify(fn: OnNotify) -> OnNotify:
def _contained_notify(
fn: OnNotify,
) -> Callable[[DispatchContext[TransportContext], str, Mapping[str, Any] | None], Coroutine[Any, Any, None]]:
"""Wrap a notification handler so it can't crash the dispatcher (same boundary as `_shielded_progress`)."""

async def _wrapped(dctx: DispatchContext[TransportContext], method: str, params: Mapping[str, Any] | None) -> None:
Expand Down Expand Up @@ -670,7 +672,7 @@ def _resolve_pending(self, request_id: RequestId | None, outcome: dict[str, Any]

def _spawn(
self,
fn: Callable[..., Awaitable[Any]],
fn: Callable[..., Coroutine[Any, Any, Any]],
*args: object,
sender_ctx: contextvars.Context | None,
) -> None:
Expand Down
5 changes: 4 additions & 1 deletion tests/client/test_stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,10 @@ async def die_late() -> None:
process.exit(0)

# The grace wait starts when stdin closes; anchor the death there.
process.on_stdin_close = lambda: tg.start_soon(die_late)
def schedule_death() -> None:
tg.start_soon(die_late)

process.on_stdin_close = schedule_death
# no branch: the tracer drops this nested async-with's arcs under
# trio's MockClock even though the body runs.
async with stdio_client(FAKE_PARAMS): # pragma: no branch
Expand Down
13 changes: 8 additions & 5 deletions tests/shared/test_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1314,17 +1314,20 @@ async def _handle_context_list_tools(

async def _handle_context_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
assert params.name in ("echo_headers", "echo_context")
assert isinstance(ctx.request, Request)
assert ctx.request is not None
# Starlette's Request is generic over its state type (0.52+); pin the parameter so member access stays typed.
request: Request[Any] = ctx.request
assert isinstance(request, Request)

if params.name == "echo_headers":
return CallToolResult(content=[TextContent(type="text", text=json.dumps(dict(ctx.request.headers)))])
return CallToolResult(content=[TextContent(type="text", text=json.dumps(dict(request.headers)))])

assert params.arguments is not None
context_data: dict[str, Any] = {
"request_id": params.arguments.get("request_id"),
"headers": dict(ctx.request.headers),
"method": ctx.request.method,
"path": ctx.request.url.path,
"headers": dict(request.headers),
"method": request.method,
"path": request.url.path,
"protocol_version": ctx.protocol_version,
"session_protocol_version": ctx.session.protocol_version,
}
Expand Down
Loading
Loading