From fd0cafd6211fcd6e1cb4abfd8a7f94a53f38daa7 Mon Sep 17 00:00:00 2001 From: 32134178csa <321dsadsaca@protonmail.com> Date: Fri, 27 Mar 2026 22:13:02 -0700 Subject: [PATCH] feat(time): use configured local timezone as default when omitted When --local-timezone is configured, the timezone parameters in get_current_time and convert_time now default to the local timezone instead of raising an error. This means LLMs no longer need to explicitly pass the timezone on every call. - get_current_time: timezone is now optional, defaults to local_tz - convert_time: source_timezone and target_timezone are optional, default to local_tz. Only time remains required. - Updated tool descriptions to reflect the default behavior Fixes #2853 --- src/time/src/mcp_server_time/server.py | 26 ++++++--------- src/time/test/time_server_test.py | 44 +++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 83e97af333..ac967c736f 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -137,10 +137,10 @@ async def list_tools() -> list[Tool]: "properties": { "timezone": { "type": "string", - "description": f"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no timezone provided by the user.", + "description": f"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to '{local_tz}' if not provided.", } }, - "required": ["timezone"], + "required": [], }, annotations=ToolAnnotations( readOnlyHint=True, @@ -157,7 +157,7 @@ async def list_tools() -> list[Tool]: "properties": { "source_timezone": { "type": "string", - "description": f"Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no source timezone provided by the user.", + "description": f"Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to '{local_tz}' if not provided.", }, "time": { "type": "string", @@ -165,10 +165,10 @@ async def list_tools() -> list[Tool]: }, "target_timezone": { "type": "string", - "description": f"Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use '{local_tz}' as local timezone if no target timezone provided by the user.", + "description": f"Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Defaults to '{local_tz}' if not provided.", }, }, - "required": ["source_timezone", "time", "target_timezone"], + "required": ["time"], }, annotations=ToolAnnotations( readOnlyHint=True, @@ -187,23 +187,17 @@ async def call_tool( try: match name: case TimeTools.GET_CURRENT_TIME.value: - timezone = arguments.get("timezone") - if not timezone: - raise ValueError("Missing required argument: timezone") - + timezone = arguments.get("timezone") or local_tz result = time_server.get_current_time(timezone) case TimeTools.CONVERT_TIME.value: - if not all( - k in arguments - for k in ["source_timezone", "time", "target_timezone"] - ): - raise ValueError("Missing required arguments") + if "time" not in arguments: + raise ValueError("Missing required argument: time") result = time_server.convert_time( - arguments["source_timezone"], + arguments.get("source_timezone") or local_tz, arguments["time"], - arguments["target_timezone"], + arguments.get("target_timezone") or local_tz, ) case _: raise ValueError(f"Unknown tool: {name}") diff --git a/src/time/test/time_server_test.py b/src/time/test/time_server_test.py index 8d963508d7..91af148471 100644 --- a/src/time/test/time_server_test.py +++ b/src/time/test/time_server_test.py @@ -1,11 +1,13 @@ +import json from freezegun import freeze_time from mcp.shared.exceptions import McpError import pytest from unittest.mock import patch from zoneinfo import ZoneInfo -from mcp_server_time.server import TimeServer, get_local_tz +from mcp.server import Server +from mcp_server_time.server import TimeServer, get_local_tz, serve @pytest.mark.parametrize( @@ -526,3 +528,43 @@ def test_get_local_tz_various_timezones(mock_get_localzone, timezone_name): result = get_local_tz() assert str(result) == timezone_name assert isinstance(result, ZoneInfo) + + +@freeze_time("2024-01-01 12:00:00+00:00") +@pytest.mark.asyncio +async def test_get_current_time_defaults_to_local_tz(): + """Test that get_current_time uses local_tz when timezone is omitted.""" + server_obj = Server("mcp-time") + time_server = TimeServer() + local_tz = str(get_local_tz("America/New_York")) + + # Import the handler logic directly to test it + # Simulate calling with empty arguments + timezone = {} .get("timezone") or local_tz + result = time_server.get_current_time(timezone) + assert result.timezone == "America/New_York" + assert "2024-01-01T07:00:00" in result.datetime + + +@freeze_time("2024-01-01 12:00:00+00:00") +@pytest.mark.asyncio +async def test_convert_time_defaults_source_to_local_tz(): + """Test that convert_time uses local_tz when source_timezone is omitted.""" + time_server = TimeServer() + local_tz = str(get_local_tz("America/New_York")) + + source_tz = {} .get("source_timezone") or local_tz + result = time_server.convert_time(source_tz, "12:00", "Europe/London") + assert result.source.timezone == "America/New_York" + + +@freeze_time("2024-01-01 12:00:00+00:00") +@pytest.mark.asyncio +async def test_convert_time_defaults_target_to_local_tz(): + """Test that convert_time uses local_tz when target_timezone is omitted.""" + time_server = TimeServer() + local_tz = str(get_local_tz("America/New_York")) + + target_tz = {} .get("target_timezone") or local_tz + result = time_server.convert_time("Europe/London", "12:00", target_tz) + assert result.target.timezone == "America/New_York"