diff --git a/src/time/README.md b/src/time/README.md index 109ecddc..8f80e415 100644 --- a/src/time/README.md +++ b/src/time/README.md @@ -5,8 +5,8 @@ A Model Context Protocol server that provides time and timezone conversion capab ### Available Tools - `get_current_time` - Get current time in a specific timezone or system timezone. - - Optional argument: `timezone` (string): IANA timezone name (e.g., 'America/New_York', 'Europe/London') - - If timezone is not provided, returns time in system timezone + - Required arguments: + - `timezone` (string): IANA timezone name (e.g., 'America/New_York', 'Europe/London') - `convert_time` - Convert time between timezones. - Required arguments: @@ -111,45 +111,25 @@ Example: ## Example Interactions -1. Get current time (using system timezone): -```json -{ - "name": "get_current_time", - "arguments": {} -} -``` -Response: -```json -{ - "timezone": "Europe/London", - "time": "14:30 BST", - "date": "2024-11-25", - "full_datetime": "2024-11-25 14:30:00 BST", - "is_dst": true -} -``` - -2. Get current time in specific timezone: +1. Get current time: ```json { "name": "get_current_time", "arguments": { - "timezone": "America/New_York" + "timezone": "Europe/Warsaw" } } ``` Response: ```json { - "timezone": "America/New_York", - "time": "09:30 EDT", - "date": "2024-11-25", - "full_datetime": "2024-11-25 09:30:00 EDT", - "is_dst": true + "timezone": "Europe/Warsaw", + "datetime": "2024-01-01T13:00:00+01:00", + "is_dst": false } ``` -3. Convert time between timezones: +2. Convert time between timezones: ```json { "name": "convert_time", @@ -165,31 +145,18 @@ Response: { "source": { "timezone": "America/New_York", - "time": "16:30 EDT", - "date": "2024-11-25" + "datetime": "2024-01-01T12:30:00-05:00", + "is_dst": false }, "target": { "timezone": "Asia/Tokyo", - "time": "05:30 JST", - "date": "2024-11-26" + "datetime": "2024-01-01T12:30:00+09:00", + "is_dst": false }, "time_difference": "+13.0h", - "date_changed": true, - "day_relation": "next day" } ``` -## Tips for Using IANA Timezone Names - -Common timezone formats: -- North America: `America/New_York`, `America/Los_Angeles`, `America/Chicago` -- Europe: `Europe/London`, `Europe/Paris`, `Europe/Berlin` -- Asia: `Asia/Tokyo`, `Asia/Shanghai`, `Asia/Dubai` -- Pacific: `Pacific/Auckland`, `Pacific/Honolulu` -- Australia: `Australia/Sydney`, `Australia/Melbourne` - -The server will automatically detect and use your system timezone if no specific timezone is provided. - ## Debugging You can use the MCP inspector to debug the server. For uvx installations: diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index d0700778..c9c7ff67 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-time" -version = "0.5.1.pre3" +version = "0.5.1" description = "A Model Context Protocol server providing tools for time queries and timezone conversions for LLMs" readme = "README.md" requires-python = ">=3.10" @@ -33,5 +33,4 @@ dev-dependencies = [ "freezegun>=1.5.1", "pyright>=1.1.389", "pytest>=8.3.3", - "ruff>=0.7.3", ] diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index b3e5e795..d4a302ca 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import datetime from enum import Enum import json from typing import Sequence @@ -35,13 +34,10 @@ class TimeConversionInput(BaseModel): time: str target_tz_list: list[str] +def get_local_tz(local_tz_override: str | None = None) -> pytz.timezone: + return pytz.timezone(local_tz_override) if local_tz_override else get_localzone() class TimeServer: - def __init__(self, local_tz_override: str | None = None): - self.local_tz = ( - pytz.timezone(local_tz_override) if local_tz_override else get_localzone() - ) - def get_current_time(self, timezone_name: str) -> TimeResult: """Get current time in specified timezone""" try: @@ -109,8 +105,8 @@ def convert_time( async def serve(local_timezone: str | None = None) -> None: server = Server("mcp-time") - time_server = TimeServer(local_timezone) - local_tz = str(time_server.local_tz) + time_server = TimeServer() + local_tz = str(get_local_tz(local_timezone)) @server.list_tools() async def list_tools() -> list[Tool]: @@ -183,7 +179,7 @@ async def call_tool( case _: raise ValueError(f"Unknown tool: {name}") - return [TextContent(type="text", text=json.dumps(result, indent=2))] + return [TextContent(type="text", text=json.dumps(result.model_dump(), indent=2))] except Exception as e: raise ValueError(f"Error processing mcp-server-time query: {str(e)}") diff --git a/src/time/test/time_server_test.py b/src/time/test/time_server_test.py index 74313c78..09c49eb7 100644 --- a/src/time/test/time_server_test.py +++ b/src/time/test/time_server_test.py @@ -3,7 +3,7 @@ from freezegun import freeze_time import pytest -from mcp_server_time.server import TimeServer +from mcp_server_time.server import TimeServer, serve @pytest.mark.parametrize( @@ -455,41 +455,3 @@ def test_convert_time(test_time, source_tz, time_str, target_tz, expected): assert result.source.is_dst == expected["source"]["is_dst"] assert result.target.is_dst == expected["target"]["is_dst"] assert result.time_difference == expected["time_difference"] - - -# @pytest.mark.anyio -# async def test_call_tool(mock_forecast_response): -# class Response(): -# def raise_for_status(self): -# pass - -# def json(self): -# return mock_forecast_response - -# class AsyncClient(): -# def __aenter__(self): -# return self - -# async def __aexit__(self, *exc_info): -# pass - -# async def get(self, *args, **kwargs): -# return Response() - -# with patch('httpx.AsyncClient', new=AsyncClient) as mock_client: -# result = await call_tool("get_forecast", {"city": "London", "days": 2}) - -# assert len(result) == 1 -# assert result[0].type == "text" -# forecast_data = json.loads(result[0].text) -# assert len(forecast_data) == 1 -# assert forecast_data[0]["temperature"] == 18.5 -# assert forecast_data[0]["conditions"] == "sunny" - - -# @pytest.mark.anyio -# async def test_list_tools(): -# tools = await list_tools() -# assert len(tools) == 1 -# assert tools[0].name == "get_forecast" -# assert "city" in tools[0].inputSchema["properties"] diff --git a/src/time/uv.lock b/src/time/uv.lock index 54eb3ccc..a7b0aa11 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -160,7 +160,7 @@ wheels = [ [[package]] name = "mcp-server-time" -version = "0.5.1rc3" +version = "0.5.1" source = { editable = "." } dependencies = [ { name = "mcp" }, @@ -174,7 +174,6 @@ dev = [ { name = "freezegun" }, { name = "pyright" }, { name = "pytest" }, - { name = "ruff" }, ] [package.metadata] @@ -190,7 +189,6 @@ dev = [ { name = "freezegun", specifier = ">=1.5.1" }, { name = "pyright", specifier = ">=1.1.389" }, { name = "pytest", specifier = ">=8.3.3" }, - { name = "ruff", specifier = ">=0.7.3" }, ] [[package]] @@ -360,31 +358,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] -[[package]] -name = "ruff" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, - { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, - { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, - { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, - { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, - { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, - { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, - { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, - { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, - { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, - { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, - { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, - { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, - { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, - { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, - { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, - { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, -] - [[package]] name = "six" version = "1.16.0"