Skip to content

Commit 70700a2

Browse files
KarlLeencursoragent
andcommitted
fix(time): preserve MCPError through call_tool handlers
Re-raise MCPError instead of wrapping it as ValueError, extract create_tool_handlers for unit tests, and cover list/call paths. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e33f079 commit 70700a2

2 files changed

Lines changed: 70 additions & 2 deletions

File tree

src/time/src/mcp_server_time/server.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ def convert_time(
128128
)
129129

130130

131-
async def serve(local_timezone: str | None = None) -> None:
131+
def create_tool_handlers(local_timezone: str | None = None):
132+
"""Build list_tools / call_tool handlers for the time MCP server."""
132133
time_server = TimeServer()
133134
local_tz = str(get_local_tz(local_timezone))
134135

@@ -224,9 +225,16 @@ async def call_tool(
224225
]
225226
)
226227

228+
except MCPError:
229+
raise
227230
except Exception as e:
228231
raise ValueError(f"Error processing mcp-server-time query: {str(e)}")
229232

233+
return list_tools, call_tool
234+
235+
236+
async def serve(local_timezone: str | None = None) -> None:
237+
list_tools, call_tool = create_tool_handlers(local_timezone)
230238
server = Server(
231239
"mcp-time",
232240
on_list_tools=list_tools,

src/time/test/time_server_test.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11

2+
import asyncio
3+
import json
4+
25
from freezegun import freeze_time
36
from mcp.shared.exceptions import MCPError
7+
from mcp.types import CallToolRequestParams, INVALID_PARAMS
48
import pytest
59
from unittest.mock import patch
610
from zoneinfo import ZoneInfo
711

8-
from mcp_server_time.server import TimeServer, get_local_tz
12+
from mcp_server_time.server import TimeServer, create_tool_handlers, get_local_tz
913

1014

1115
@pytest.mark.parametrize(
@@ -526,3 +530,59 @@ def test_get_local_tz_various_timezones(mock_get_localzone, timezone_name):
526530
result = get_local_tz()
527531
assert str(result) == timezone_name
528532
assert isinstance(result, ZoneInfo)
533+
534+
535+
def test_list_tools_returns_expected_tools():
536+
list_tools, _ = create_tool_handlers("UTC")
537+
538+
async def _run():
539+
return await list_tools(None, None) # type: ignore[arg-type]
540+
541+
result = asyncio.run(_run())
542+
assert [tool.name for tool in result.tools] == [
543+
"get_current_time",
544+
"convert_time",
545+
]
546+
547+
548+
def test_call_tool_get_current_time():
549+
_, call_tool = create_tool_handlers("UTC")
550+
params = CallToolRequestParams(
551+
name="get_current_time",
552+
arguments={"timezone": "Europe/Warsaw"},
553+
)
554+
555+
async def _run():
556+
with freeze_time("2024-01-01 12:00:00+00:00"):
557+
return await call_tool(None, params) # type: ignore[arg-type]
558+
559+
result = asyncio.run(_run())
560+
payload = json.loads(result.content[0].text)
561+
assert payload["timezone"] == "Europe/Warsaw"
562+
assert payload["datetime"] == "2024-01-01T13:00:00+01:00"
563+
564+
565+
def test_call_tool_invalid_timezone_raises_mcp_error():
566+
_, call_tool = create_tool_handlers("UTC")
567+
params = CallToolRequestParams(
568+
name="get_current_time",
569+
arguments={"timezone": "Invalid/Timezone"},
570+
)
571+
572+
async def _run():
573+
await call_tool(None, params) # type: ignore[arg-type]
574+
575+
with pytest.raises(MCPError) as exc_info:
576+
asyncio.run(_run())
577+
assert exc_info.value.code == INVALID_PARAMS
578+
579+
580+
def test_call_tool_unknown_tool_raises_value_error():
581+
_, call_tool = create_tool_handlers("UTC")
582+
params = CallToolRequestParams(name="unknown_tool", arguments={})
583+
584+
async def _run():
585+
await call_tool(None, params) # type: ignore[arg-type]
586+
587+
with pytest.raises(ValueError, match="Error processing mcp-server-time query"):
588+
asyncio.run(_run())

0 commit comments

Comments
 (0)