Skip to content

[time] Cross-platform support for device timezones via tzlocal #925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
121 changes: 63 additions & 58 deletions src/time/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

A Model Context Protocol server that provides time and timezone conversion capabilities. This server enables LLMs to get current time information and perform timezone conversions using IANA timezone names, with automatic system timezone detection.

### Available Tools
## Available Tools

- `get_current_time` - Get current time in a specific timezone or system timezone.
- Required arguments:
- `timezone` (string): IANA timezone name (e.g., 'America/New_York', 'Europe/London')

- `convert_time` - Convert time between timezones.
- Required arguments:
- `source_timezone` (string): Source IANA timezone name
Expand All @@ -16,10 +15,10 @@ A Model Context Protocol server that provides time and timezone conversion capab

## Installation

### Using uv (recommended)
### Using `uv` (recommended)

When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-server-time*.
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run `mcp-server-time*.

### Using PIP

Expand All @@ -42,7 +41,7 @@ python -m mcp_server_time
Add to your Claude settings:

<details>
<summary>Using uvx</summary>
<summary>Using <code>uvx</code></summary>

```json
"mcpServers": {
Expand All @@ -55,7 +54,7 @@ Add to your Claude settings:
</details>

<details>
<summary>Using docker</summary>
<summary>Using <code>Docker</code></summary>

```json
"mcpServers": {
Expand All @@ -68,7 +67,7 @@ Add to your Claude settings:
</details>

<details>
<summary>Using pip installation</summary>
<summary>Using <code>pip</code></summary>

```json
"mcpServers": {
Expand All @@ -82,10 +81,10 @@ Add to your Claude settings:

### Configure for Zed

Add to your Zed settings.json:
Add to your Zed `settings.json`:

<details>
<summary>Using uvx</summary>
<summary>Using <code>uvx</code></summary>

```json
"context_servers": [
Expand All @@ -98,7 +97,7 @@ Add to your Zed settings.json:
</details>

<details>
<summary>Using pip installation</summary>
<summary>Using <code>pip</code></summary>

```json
"context_servers": {
Expand All @@ -115,6 +114,7 @@ Add to your Zed settings.json:
By default, the server automatically detects your system's timezone. You can override this by adding the argument `--local-timezone` to the `args` list in the configuration.

Example:

```json
{
"command": "python",
Expand All @@ -125,54 +125,60 @@ Example:
## Example Interactions

1. Get current time:
```json
{
"name": "get_current_time",
"arguments": {
"timezone": "Europe/Warsaw"
}
}
```
Response:
```json
{
"timezone": "Europe/Warsaw",
"datetime": "2024-01-01T13:00:00+01:00",
"is_dst": false
}
```

```json
{
"name": "get_current_time",
"arguments": {
"timezone": "Europe/Warsaw"
}
}
```

Response:

```json
{
"timezone": "Europe/Warsaw",
"datetime": "2024-01-01T13:00:00+01:00",
"is_dst": false
}
```

2. Convert time between timezones:
```json
{
"name": "convert_time",
"arguments": {
"source_timezone": "America/New_York",
"time": "16:30",
"target_timezone": "Asia/Tokyo"
}
}
```
Response:
```json
{
"source": {
"timezone": "America/New_York",
"datetime": "2024-01-01T12:30:00-05:00",
"is_dst": false
},
"target": {
"timezone": "Asia/Tokyo",
"datetime": "2024-01-01T12:30:00+09:00",
"is_dst": false
},
"time_difference": "+13.0h",
}
```

```json
{
"name": "convert_time",
"arguments": {
"source_timezone": "America/New_York",
"time": "16:30",
"target_timezone": "Asia/Tokyo"
}
}
```

Response:

```json
{
"source": {
"timezone": "America/New_York",
"datetime": "2024-01-01T12:30:00-05:00",
"is_dst": false
},
"target": {
"timezone": "Asia/Tokyo",
"datetime": "2024-01-01T12:30:00+09:00",
"is_dst": false
},
"time_difference": "+13.0h",
}
```

## Debugging

You can use the MCP inspector to debug the server. For uvx installations:
You can use the MCP inspector to debug the server. For `uvx` installations:

```bash
npx @modelcontextprotocol/inspector uvx mcp-server-time
Expand Down Expand Up @@ -203,13 +209,12 @@ docker build -t mcp/time .

## Contributing

We encourage contributions to help expand and improve mcp-server-time. Whether you want to add new time-related tools, enhance existing functionality, or improve documentation, your input is valuable.
We encourage contributions to help expand and improve `mcp-server-time`. Whether you want to add new time-related tools, enhance existing functionality, or improve documentation, your input is valuable.

For examples of other MCP servers and implementation patterns, see:
https://github.com/modelcontextprotocol/servers
For examples of other MCP servers and implementation patterns, see [https://github.com/modelcontextprotocol/servers]

Pull requests are welcome! Feel free to contribute new ideas, bug fixes, or enhancements to make mcp-server-time even more powerful and useful.
Pull requests are welcome! Feel free to contribute new ideas, bug fixes, or enhancements to make `mcp-server-time` even more powerful and useful.

## License

mcp-server-time is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
`mcp-server-time` is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
1 change: 1 addition & 0 deletions src/time/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies = [
"mcp>=1.0.0",
"pydantic>=2.0.0",
"tzdata>=2024.2",
"tzlocal>=5.3.1",
]

[project.scripts]
Expand Down
12 changes: 10 additions & 2 deletions src/time/src/mcp_server_time/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from enum import Enum
import json
from typing import Sequence

from tzlocal import get_localzone
from zoneinfo import ZoneInfo
from mcp.server import Server
from mcp.server.stdio import stdio_server
Expand Down Expand Up @@ -39,10 +39,18 @@ def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo:
if local_tz_override:
return ZoneInfo(local_tz_override)
Comment on lines 39 to 40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to add try/catch if the local override results in an exception, plus it looks like McpError expects an object with a message and not a string:

Suggested change
if local_tz_override:
return ZoneInfo(local_tz_override)
if local_tz_override:
try:
return ZoneInfo(local_tz_override)
except Exception as e:
error_data = ErrorData(
code=INVALID_PARAMS,
message=f"Invalid timezone: {str(e)}"
)
raise McpError(error_data)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will also need to add ErrorData and INVALID_PARAMS fom mcp.types, for example:

from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource, ErrorData, INVALID_PARAMS


# Get local timezone from datetime.now()
# First, try to get local timezone from tzlocal
try:
local_tz = get_localzone()
return ZoneInfo(str(local_tz))
except Exception as e:
pass

# If that fails, try getting local timezone from datetime.now()
tzinfo = datetime.now().astimezone(tz=None).tzinfo
if tzinfo is not None:
return ZoneInfo(str(tzinfo))

raise McpError("Could not determine local timezone - tzinfo is None")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise McpError("Could not determine local timezone - tzinfo is None")
error_data = ErrorData(
code=INVALID_PARAMS,
message="Could not determine local timezone - tzinfo is None"
)
raise McpError(error_data)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't leave comments or suggestions on unchanged lines, but I think you also want to change this in the get_zoneinfo function:

    except Exception as e:
        error_data = ErrorData(
            code=INVALID_PARAMS,
            message=f"Invalid timezone: {str(e)}"
        )
        raise McpError(error_data)



Expand Down
1 change: 0 additions & 1 deletion src/time/test/time_server_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from freezegun import freeze_time
from mcp.shared.exceptions import McpError
import pytest
Expand Down
17 changes: 16 additions & 1 deletion src/time/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.