diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index ec2365810..afa7a06e9 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -291,13 +291,16 @@ def run( if transport not in TRANSPORTS.__args__: # type: ignore # pragma: no cover raise ValueError(f"Unknown transport: {transport}") - match transport: - case "stdio": - anyio.run(self.run_stdio_async) - case "sse": # pragma: no cover - anyio.run(lambda: self.run_sse_async(**kwargs)) - case "streamable-http": # pragma: no cover - anyio.run(lambda: self.run_streamable_http_async(**kwargs)) + try: + match transport: + case "stdio": + anyio.run(self.run_stdio_async) + case "sse": # pragma: no cover + anyio.run(lambda: self.run_sse_async(**kwargs)) + case "streamable-http": # pragma: no cover + anyio.run(lambda: self.run_streamable_http_async(**kwargs)) + except KeyboardInterrupt: + return async def _handle_list_tools( self, ctx: ServerRequestContext[LifespanResultT], params: PaginatedRequestParams | None diff --git a/tests/server/mcpserver/test_server.py b/tests/server/mcpserver/test_server.py index 3457ec944..deb91255a 100644 --- a/tests/server/mcpserver/test_server.py +++ b/tests/server/mcpserver/test_server.py @@ -74,6 +74,14 @@ def test_dependencies(self): mcp_no_deps = MCPServer("test") assert mcp_no_deps.dependencies == [] + def test_stdio_keyboard_interrupt_exits_cleanly(self): + mcp = MCPServer("test") + + with patch("mcp.server.mcpserver.server.anyio.run", side_effect=KeyboardInterrupt) as run: + mcp.run("stdio") + + run.assert_called_once_with(mcp.run_stdio_async) + async def test_sse_app_returns_starlette_app(self): """Test that sse_app returns a Starlette application with correct routes.""" mcp = MCPServer("test")