MCP Time Server 实战指南:时间查询与时区转换工具的实现、配置与调试
【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers
在基于 Model Context Protocol(MCP)构建的服务器集合仓库中,Time MCP Server 是一个典型的"小而完整"的参考实现:它用不到 220 行核心代码,向大语言模型(LLM)暴露了获取当前时间与跨时区时间转换两个能力,并自动探测系统时区。读完本文,你将掌握mcp-server-time的两个工具的参数语义与返回结构、--local-timezone时区覆盖机制的底层实现,以及在 Claude.app、Zed、VS Code 等客户端中完整的配置方式,还能了解如何用 MCP Inspector 调试该服务器,以及其测试用例如何覆盖 DST、半小时时区偏移等边缘场景。
项目定位与总体架构
Time MCP Server 的包名为mcp-server-time(当前仓库中版本为 0.6.2,要求 Python >= 3.10,采用 MIT 许可证),源码位于 src/time 目录。从 pyproject.toml 可以看到它的全部运行时依赖:
dependencies = [ "mcp>=1.23.0", "pydantic>=2.0.0", "tzdata>=2024.2", "tzlocal>=5.3.1", ]这四个依赖各司其职:mcp提供 MCP 协议的Server抽象与 stdio 传输层;pydantic用于定义结构化的返回模型;tzdata提供 IANA 时区数据库(保证在无系统时区数据的容器/平台上也能解析时区);tzlocal用于探测宿主机的本地时区。
从源码结构看,整个服务器由三个文件组成:
- server.py:核心实现,包含时区解析、两个工具的业务逻辑与 MCP 路由分发;
- init.py:定义命令行入口
main(),解析--local-timezone参数后启动异步服务; - main.py:两行的模块启动器,使
python -m mcp_server_time可以运行。
传输层采用 stdio(标准输入输出):serve()函数在 server.py 末尾通过stdio_server()打开读写流并调用server.run(),客户端(Claude、Zed、VS Code 等)以子进程方式拉起服务器,经 stdin/stdout 交换 JSON-RPC 消息。
核心工具一:get_current_time
get_current_time用于获取指定时区的当前时间。必填参数只有一个:
timezone(string):IANA 时区名,例如America/New_York、Europe/London。
返回结果由 Pydantic 模型TimeResult(见 server.py)约束,包含四个字段:timezone(时区名)、datetime(ISO 8601 格式、精确到秒、带偏移量)、day_of_week(星期几英文名)、is_dst(是否处于夏令时)。
调用示例(来自 src/time/README.md):
{ "name": "get_current_time", "arguments": { "timezone": "Europe/Warsaw" } }响应:
{ "timezone": "Europe/Warsaw", "datetime": "2024-01-01T13:00:00+01:00", "is_dst": false }源码实现细节
业务逻辑在TimeServer.get_current_time()中(server.py):
def get_current_time(self, timezone_name: str) -> TimeResult: """Get current time in specified timezone""" timezone = get_zoneinfo(timezone_name) current_time = datetime.now(timezone) return TimeResult( timezone=timezone_name, datetime=current_time.isoformat(timespec="seconds"), day_of_week=current_time.strftime("%A"), is_dst=bool(current_time.dst()), )几个值得注意的实现点:
- 时区校验走
get_zoneinfo统一入口。该函数(server.py)捕获ZoneInfo构造失败并抛出 MCP 标准协议错误McpError(ErrorData(code=INVALID_PARAMS, ...)),错误信息形如Invalid timezone: 'No time zone found with key Invalid/Timezone'——这与 time_server_test.py 中的断言完全一致,说明非法时区不会导致进程崩溃,而是以协议级错误返回给客户端。 - 夏令时由
dst()直接计算:is_dst字段让 LLM 能明确知道目标时区当前是否处于夏令时,避免模型自行猜测。 - 星期几以英文全称返回(
%A→Monday等),对多语言模型而言是更无歧义的表示。
核心工具二:convert_time
convert_time将某一时间点从一个时区转换到另一个时区,必填参数三个:
source_timezone(string):源 IANA 时区名;time(string):24 小时制时间,格式必须为HH:MM;target_timezone(string):目标 IANA 时区名。
调用示例:
{ "name": "convert_time", "arguments": { "source_timezone": "America/New_York", "time": "16:30", "target_timezone": "Asia/Tokyo" } }响应结构由TimeConversionResult模型(server.py)定义,包含源、目标两个TimeResult,以及一个人类可读的时差字符串:
{ "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" }源码实现细节
TimeServer.convert_time()的实现(server.py)有几个关键的工程细节:
1. 以"今天的日期"补齐时间。用户只传入HH:MM,实现先用datetime.strptime(time_str, "%H:%M")严格解析(失败时抛出Invalid time format. Expected HH:MM [24-hour format]),再把当前日期与解析出的时分拼合成一个带时区信息的datetime,因此转换结果会附带完整日期——这对跨日期变更线(date line)的场景尤为重要:
now = datetime.now(source_timezone) source_time = datetime( now.year, now.month, now.day, parsed_time.hour, parsed_time.minute, tzinfo=source_timezone, ) target_time = source_time.astimezone(target_timezone)2. 时差支持非整数小时。时差通过两个时区的utcoffset()相减计算,并对半小时/45 分钟偏移做了特殊格式化:
if hours_difference.is_integer(): time_diff_str = f"{hours_difference:+.1f}h" else: # For fractional hours like Nepal's UTC+5:45 time_diff_str = f"{hours_difference:+.2f}".rstrip("0").rstrip(".") + "h"这样尼泊尔(Asia/Kathmandu,UTC+5:45)会得到+4.75h而非四舍五入的+5h,印度(UTC+5:30)、伊朗(UTC+3:30)等半小时偏移时区也不会失真。
3. 错误路径与测试一一对应。time_server_test.py 中专门参数化了三类失败场景:源时区非法、目标时区非法、时间格式非法(如25:00),分别断言抛出McpError或ValueError,与源码中的异常分支严格对应。
本地时区自动探测与 --local-timezone 覆盖
Time Server 的一个重要设计是:当用户提问"现在几点"而没指明时区时,模型应当使用服务器所在主机的本地时区。这一能力分两层实现:
第一层:时区探测函数get_local_tz(server.py):
def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo: if local_tz_override: return ZoneInfo(local_tz_override) # Get local timezone from datetime.now() local_tzname = get_localzone_name() if local_tzname is not None: return ZoneInfo(local_tzname) # Default to UTC if local timezone cannot be determined return ZoneInfo("UTC")优先级为:命令行显式覆盖 >tzlocal探测系统时区(如Europe/Paris)> 回退到UTC。这个回退链在 time_server_test.py 中被完整覆盖,包括:覆盖值有效/无效、tzlocal返回None时回退 UTC、Windows 平台时区名(如Pacific Standard Time)被tzlocal转换为 IANA 名等场景。
第二层:把探测结果注入工具描述,引导 LLM 行为。在serve()中,list_tools返回的get_current_time参数描述是动态拼接的(server.py):
"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.", },也就是说,如果服务器运行在America/Chicago的机器上,模型看到的 schema 描述里会直接写明"用户未提供时区时请使用America/Chicago"。convert_time的source_timezone与target_timezone描述同样嵌入了本地时区名。这是"用工具描述做提示工程"的一个典型范例:服务器无需额外代码,仅靠描述文本就能让 LLM 正确地默认使用本地时区。
命令行参数 --local-timezone
覆盖机制的入口在init.py:
parser = argparse.ArgumentParser( description="give a model the ability to handle time queries and timezone conversions" ) parser.add_argument("--local-timezone", type=str, help="Override local timezone") args = parser.parse_args() asyncio.run(serve(args.local_timezone))按 src/time/README.md 的说明,只需在客户端配置的args列表中加入--local-timezone即可强制指定"本地时区",例如:
{ "command": "python", "args": ["-m", "mcp_server_time", "--local-timezone=America/New_York"] }这对容器部署特别有用。查看 Dockerfile 可以看到镜像层的对应设计:镜像通过环境变量LOCAL_TIMEZONE(默认UTC)在 ENTRYPOINT 中拼装--local-timezone参数:
# Set the LOCAL_TIMEZONE environment variable ENV LOCAL_TIMEZONE=${LOCAL_TIMEZONE:-"UTC"} # when running the container, add --local-timezone and a bind mount to the host's db file ENTRYPOINT ["mcp-server-time", "--local-timezone", "${LOCAL_TIMEZONE}"]因此 Claude.app 中的 Docker 配置里才会出现-e LOCAL_TIMEZONE这样的写法(见下文配置章节)。值得注意的是:覆盖值若为非法时区名,ZoneInfo构造会直接抛异常——测试 test_get_local_tz_with_invalid_override 验证了这一点。
安装与运行
README 给出两种安装方式,二者最终运行的是同一个入口点。
方式一:uv(推荐)
无需显式安装,直接使用uvx以隔离环境运行:
uvx mcp-server-time方式二:pip
pip install mcp-server-time安装后可作为 Python 模块运行:
python -m mcp_server_time这里有个细节可以印证:pyproject.toml 声明了控制台脚本入口:
[project.scripts] mcp-server-time = "mcp_server_time:main"即uvx mcp-server-time执行的就是init.py 中定义的main();而python -m mcp_server_time走的是main.py(其内容仅from mcp_server_time import main; main())。两条路径汇聚到同一个argparse+asyncio.run(serve(...))流程,行为完全一致。
客户端配置
以下配置示例全部继承自 src/time/README.md,按客户端分别说明。
Claude.app
使用 uvx:
{ "mcpServers": { "time": { "command": "uvx", "args": ["mcp-server-time"] } } }使用 Docker(通过环境变量指定本地时区):
{ "mcpServers": { "time": { "command": "docker", "args": ["run", "-i", "--rm", "-e", "LOCAL_TIMEZONE", "mcp/time"] } } }使用 pip 安装:
{ "mcpServers": { "time": { "command": "python", "args": ["-m", "mcp_server_time"] } } }Zed
添加到 Zed 的settings.json中。使用 uvx:
"context_servers": [ "mcp-server-time": { "command": "uvx", "args": ["mcp-server-time"] } ],使用 pip 安装:
"context_servers": { "mcp-server-time": { "command": "python", "args": ["-m", "mcp_server_time"] } },VS Code
手动安装时,按下Ctrl + Shift + P并输入Preferences: Open User Settings (JSON),在用户设置(JSON)中加入下列配置块;也可以把配置放到工作区的.vscode/mcp.json文件中以便在团队间共享。注意:使用mcp.json文件时,顶层需要mcp键。
使用 uvx:
{ "mcp": { "servers": { "time": { "command": "uvx", "args": ["mcp-server-time"] } } } }使用 Docker:
{ "mcp": { "servers": { "time": { "command": "docker", "args": ["run", "-i", "--rm", "mcp/time"] } } } }Zencoder
按 README 步骤:打开 Zencoder 菜单(...)→ 从下拉菜单选择Agent Tools→ 点击Add Custom MCP→ 填入名称与下述服务器配置,并点击Install按钮:
{ "command": "uvx", "args": ["mcp-server-time"] }调试与本地构建
MCP Inspector 调试。对 uvx 安装:
npx @modelcontextprotocol/inspector uvx mcp-server-time如果在源码目录中开发(本仓库的src/time目录):
cd src/time npx @modelcontextprotocol/inspector uv run mcp-server-time构建 Docker 镜像:
cd src/time docker build -t mcp/time .该 Dockerfile 采用两阶段构建:第一阶段基于官方 uv 镜像,利用uv.lock锁定依赖(uv sync --locked)并借助 build cache 挂载加速;第二阶段切换到轻量的python:3.12-slim-bookworm,仅复制生成的虚拟环境,并把/app/.venv/bin置于PATH首位,最终由 ENTRYPOINT 以--local-timezone参数启动。
交互示例与典型问法
README 列出的四类典型自然语言问法,恰好覆盖了两个工具的能力边界:
- "What time is it now?"(未指定时区,模型将依据工具描述中使用本地时区);
- "What time is it in Tokyo?"(对应
get_current_time,timezone=Asia/Tokyo); - "When it's 4 PM in New York, what time is it in London?"(对应
convert_time); - "Convert 9:30 AM Tokyo time to New York time"(对应
convert_time,注意需把 12 小时制换算为 24 小时制09:30再传参)。
这两个工具在注册时还携带了ToolAnnotations(server.py):readOnlyHint=True、destructiveHint=False、idempotentHint=True、openWorldHint=False,即向客户端声明这些工具是只读、可重复执行、无副作用的,客户端可据此放心地自动调用而无需二次确认。
测试体系:用 freeze_time 锁住时间验证边缘场景
Time Server 的测试文件 time_server_test.py 是理解其边界行为的好入口。测试借助freezegun把系统时钟"冻结"在特定时刻,再断言转换结果,从而把"时间正确性"变成可回归验证的属性。覆盖的边缘场景包括:
- DST(夏令时)切换前后:如
2024-03-31欧洲/美国已入夏令时、2024-01-01为冬令时,验证is_dst与偏移量正确; - 欧洲与美国 DST 结束时间不同步:
2024-10-28欧洲已回到冬令时(UTC+1)而纽约仍在夏令时(UTC-4),时差为-5.0h;2024-11-04之后双方都回到冬令时,时差变为-6.0h; - 45 分钟偏移:尼泊尔
Asia/Kathmandu(UTC+5:45),双向转换分别得到+4.75h/-4.75h; - 半小时 DST 跳变:Lord Howe 岛夏令时仅 +30 分钟,冬令时为 UTC+10:30;
- 半小时/历史偏移时区:伊朗
Asia/Tehran(UTC+3:30)、委内瑞拉 2016 年切换前的America/Caracas(UTC-4:30); - 跨日期变更线:
23:00的华沙时间转太平洋阿皮亚(UTC+13)后日期进位到次日,基里巴斯 UTC+14 是"全球最早进入新一天"的时区,同样验证日期进位; - 时差为零的特殊案例:南极 Troll 站在夏季与欧洲 DST 时区同为 UTC+2,时差
+0.0h。
此外,test_convert_time_errors验证了三种参数错误路径(源/目标时区非法、25:00等非法时间格式),test_get_local_tz_*系列则验证了上文所述的本地时区探测回退链。整体来看,测试与源码中的异常分支、格式化逻辑一一对应,是维护时区类工具时值得借鉴的用例设计方式。
小结
Time MCP Server 展示了 MCP 参考实现的标准形态:以 stdio 为传输层,用Server抽象注册list_tools/call_tool两类处理器,工具参数全部通过 JSON Schema 描述,返回结构化 Pydantic 模型并序列化为文本。它对时区问题的处理有几个可复用的设计:
- IANA 时区名 +
zoneinfo作为时区表示与计算基础,非法值以INVALID_PARAMS协议错误返回而非崩溃; - tzlocal 自动探测 +
--local-timezone覆盖 + UTC 兜底的三级时区解析链,并覆盖到 Docker 的LOCAL_TIMEZONE环境变量; - 把探测到的本地时区动态写进工具描述,让 LLM 在用户未指定时区时自动选对默认值;
- 时差格式化区分整数与小数小时,正确处理 30/45 分钟偏移时区。
如需扩展,可以在此基础上增加新的时间相关工具(例如按日历规则计算下一个工作日的时区本地时间),并参照 time_server_test.py 的 freeze_time 参数化写法补充回归用例。
【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考