🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度
1. Claude Code 加时间 MCP:这次要跑通的最小闭环
Claude Code 本身没有时钟,问它现在几点,多半会给你一个基于训练数据的日期,连年份都可能是旧的。要拿到真实系统时间,得给它挂一个 MCP 工具服务器,把「读时间」做成一次真正的函数调用,而不是让模型凭记忆猜。这次用 TaoToken 做 Claude Code 的模型出口,把「对话里问时间 → 客户端发 tools/call → 工具返回真实时间 → 模型转述」这条链路完整跑一遍。全程只动三个地方:一个从控制台创建的 Key、一份 ~/.claude/settings.json 里的 env、一份项目根目录的 .mcp.json。
先说清楚 MCP 在这里扮演什么角色。MCP 是 Model Context Protocol,它把「模型能用哪些工具」从提示词里拆出来,变成客户端能读的一份结构化清单。Claude Code 作为 MCP client,启动时会去读配置里的 server 列表,然后按配置拉起进程。本地工具用 stdio 传输最省事:客户端 fork 一个子进程,双方用标准输入输出按行传 JSON-RPC,不需要监听端口,不需要处理跨域,也不用在网络层做任何额外设置。时间查询这种「一问一答、读完就结束」的工具,stdin/stdout 完全够用,换来的是配置极短、出错面极小。
这次的任务被拆成三个互相独立的环节,混在一起排查是新手最容易浪费时间的点。第一个环节是模型出口:Claude Code 把 Anthropic 格式的请求发到 ANTHROPIC_BASE_URL,这里填 TaoToken 的统一 API 地址 https://taotoken.net/api,鉴权用 ANTHROPIC_AUTH_TOKEN。第二个环节是工具出口:MCP server 是本地子进程,一次网络请求都不发,它只读本机时间然后把结果写回 stdout。第三个环节是 Key 的来源:从落地页注册后进控制台新建,复制出来的那串就是 YOUR_API_KEY。三件事分别独立,任何一个环节配错,症状都不一样,后面排障章节会按这个顺序对号入座。
准备工作也就三样东西。Node 18 以上加 npm,用来装 Claude Code 和可选的 taoToken CLI;Python 3.9 以上,用来跑下面那个时间服务器,选 Python 是因为标准库自带 zoneinfo 和 datetime,不用装任何第三方包;再就是一个能登录控制台的账号。不需要 Docker,不需要额外开终端窗口常驻,Claude Code 会在会话启动时自己把 server 拉起来。
跑完之后的产出很具体:一份可以直接复制的 .mcp.json、几条本地启动和自测命令、一次真实会话记录,以及一份只针对本篇配置的报错对照。目标只有一个,在对话里打「现在几点」,看到的不是模型编的时间,而是本机系统时钟在当前时区的真实读数。
2. 把 Claude Code 的请求出口指向 TaoToken
Key 的获取路径很短。打开 TaoToken 登录后进控制台,在 API Keys 页面新建一个,复制出来的那串就是本文里所有 YOUR_API_KEY 的位置。这里有个习惯值得养成:Key 只填进本地配置文件和终端命令,不要贴进聊天记录,不要提交进 git,也尽量不要在共享屏幕时展开。如果怀疑泄露,直接在控制台删掉重建一个,比到处找哪里泄露快得多。
写进 Claude Code 的方式有三种,按持久程度排序。第一种是 ~/.claude/settings.json 的 env 字段,开一次终端就自动生效,推荐长期用:
{
"env": {
"ANTHROPIC_BASE_URL": "https://taotoken.net/api",
"ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY",
"ANTHROPIC_MODEL": "YOUR_MODEL_ID"
}
}
第二种是临时环境变量,适合只想在某个终端窗口里试一次,关掉窗口就失效:
export ANTHROPIC_BASE_URL="https://taotoken.net/api"
export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"
export ANTHROPIC_MODEL="YOUR_MODEL_ID"
第三种是命令行工具一把配好,适合已经在用 CLI 工作流的人:
npm install -g @taotoken/taotoken
taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m YOUR_MODEL_ID
三个变量里最容易写错的是地址。ANTHROPIC_BASE_URL 严格写 https://taotoken.net/api,末尾不要补 /v1,也不要补斜杠,客户端会自己在后面拼路径。模型 ID 从模型广场当前展示的列表里抄,不要凭印象拼写,也不要用别人文章里的旧 ID,广场上有哪个就用哪个。这两条看起来像废话,但 404 和「模型不存在」这两类报错里,八成都是它们引起的。
验证方式不复杂。新开一个终端,cd 到任意项目目录,敲 claude 进交互界面,随便问一句「回复 ok 两个字」看有没有正常返回。如果这一步就失败,先别急着碰 MCP,模型出口没通的话后面所有现象都会被污染。也可以在同一目录下用 claude -p "说一句话" 走一次非交互模式,输出更干净,适合脚本化确认。
还有一个容易被忽略的冲突点:如果环境里同时存在 ANTHROPIC_API_KEY 和 ANTHROPIC_AUTH_TOKEN,两个值的优先级在不同版本里表现不一样,可能出现「明明改了配置却还是 401」的假象。最省事的做法是只保留 ANTHROPIC_AUTH_TOKEN,把另一个从 shell 配置和 settings 里都清掉,然后重开终端再试。这一步做完,模型出口就算稳定了,接下来所有精力都可以放在 MCP 上。
3. 手写 60 行 stdio 时间 MCP 服务器(Python 标准库)
现成的时间服务器是有的,社区里也有若干实现,用 uvx 一行就能跑起来。但作为可复现的基线,我倾向于自己写一个最小版本,原因有三个:逻辑只有「取当前时间并格式化」这一件事,代码短到能整段读完;不依赖任何第三方包,不会因为上游版本更新导致参数或返回格式变化;出问题时能直接改代码打日志,不用去翻别人的仓库。下面这个文件大约 60 行,只用 Python 标准库,放在你自己建的目录里,文件名 server.py。
先说协议,理解了这四步,代码就没什么神秘感。MCP 的 stdio 传输是 JSON-RPC 2.0,一行一条消息,用换行分隔。客户端先发 initialize 请求,服务端回一个包含 protocolVersion、capabilities、serverInfo 的结果;客户端再发一条 notifications/initialized 通知,这条没有 id,服务端不需要回复;接着客户端发 tools/list,服务端返回工具数组,每项包含 name、description、inputSchema;最后客户端发 tools/call,服务端把结果放进 result.content 数组返回。整个生命周期就这些,没有握手加密,没有心跳协商。
#!/usr/bin/env python3
# server.py —— 最小 MCP stdio 时间服务器
import json
import sys
from datetime import datetime
from zoneinfo import ZoneInfo
DEFAULT_TZ = "Asia/Shanghai"
TOOL = {
"name": "get_current_time",
"description": "返回指定时区的当前时间。timezone 使用 IANA 名称,例如 Asia/Shanghai、UTC、America/New_York。",
"inputSchema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "IANA 时区名,不传则使用 Asia/Shanghai"
}
},
"required": []
}
}
def send(msg):
sys.stdout.write(json.dumps(msg, ensure_ascii=False) + "\n")
sys.stdout.flush()
def read_time(tz_name):
try:
zone = ZoneInfo(tz_name)
except Exception:
tz_name = DEFAULT_TZ
zone = ZoneInfo(tz_name)
now = datetime.now(zone)
text = now.strftime("%Y-%m-%d %H:%M:%S")
return tz_name, f"{text} ({tz_name}, UTC{now.strftime('%z')})"
def handle(req):
method = req.get("method")
rid = req.get("id")
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": rid,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "localtime", "version": "0.1.0"}
}
}
if method == "ping":
return {"jsonrpc": "2.0", "id": rid, "result": {}}
if method == "tools/list":
return {"jsonrpc": "2.0", "id": rid, "result": {"tools": [TOOL]}}
if method == "tools/call":
params = req.get("params") or {}
args = params.get("arguments") or {}
_, text = read_time(args.get("timezone") or DEFAULT_TZ)
return {
"jsonrpc": "2.0",
"id": rid,
"result": {"content": [{"type": "text", "text": text}]}
}
if rid is None:
return None
return {
"jsonrpc": "2.0",
"id": rid,
"error": {"code": -32601, "message": f"method not found: {method}"}
}
def main():
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
except json.JSONDecodeError:
continue
resp = handle(req)
if resp is not None:
send(resp)
if __name__ == "__main__":
main()
写完先别急着接 Claude Code,直接拿管道喂它四条消息自测,这一步能把九成的手写错误挡在门外:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0.1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_current_time","arguments":{"timezone":"Asia/Shanghai"}}}' \
| python3 /绝对路径/mcp-time/server.py
预期看到三行输出:第一行是 initialize 的结果,第二行是工具清单,里面能看到 get_current_time 和它的 inputSchema,第三行是 content 数组,文本形如 2025-09-18 15:42:07 (Asia/Shanghai, UTC+0800)。如果第三行的时间和本机时钟对得上,服务端逻辑就没问题了。
有两个环境细节值得提前处理。Windows 上 zoneinfo 依赖 tzdata 数据包,如果报 ZoneInfoNotFoundError,执行 pip install tzdata 即可;macOS 和主流 Linux 发行版一般自带时区数据库。另外在 Windows 上解释器命令通常是 python 而不是 python3,这一点在写 MCP 配置时要对应上,否则会出现「手动能跑、客户端拉不起来」的错觉。
4. 完整 mcp 配置 JSON 与本地启动命令
Claude Code 读 MCP 配置有三个层级,本篇用项目级最直观。在项目根目录新建 .mcp.json,内容如下,把 args 里的路径换成你机器上的绝对路径:
{
"mcpServers": {
"localtime": {
"command": "python3",
"args": ["/绝对路径/mcp-time/server.py"],
"env": {}
}
}
}
字段含义很直白。localtime 是你在客户端里看到的服务器名,随便起,但要和后面 /mcp 列表里的名字对得上;command 是可执行文件,建议写成解释器的绝对路径,例如 /usr/bin/python3,这样不受 shell PATH 影响;args 是参数数组,第一个元素是脚本绝对路径;env 是传给子进程的额外环境变量,时间服务器用不到,留空对象即可。如果你的脚本需要读某个变量,就在这个对象里补上,不要指望它继承你终端里临时 export 的东西。
不想手写文件的话,命令行添加同样可以,效果等价:
cd /你的项目目录
claude mcp add localtime -- python3 /绝对路径/mcp-time/server.py
想验证服务端单独启动的行为,可以直接跑一次:
python3 /绝对路径/mcp-time/server.py
这条命令执行后会看起来「卡住」,因为它在等 stdin 输入,这是 stdio 型 server 的正常表现,按 Ctrl+C 退出即可,不代表进程有问题。真正判断可用性靠的是上一步那段管道自测。
配置落盘后,重启 Claude Code,在交互界面里输入 /mcp,应该能看到 localtime 处于已连接状态,展开后列出 get_current_time 这个工具。项目级配置第一次使用时,客户端可能会提示你是否信任这个目录下的 MCP server,确认一次即可,之后同一项目不再重复询问。如果列表里显示连接失败,优先看路径是不是写成了相对路径,以及 command 指向的解释器在非登录 shell 环境下是否真的存在。
顺带给出用官方实现替代的写法,方便你在两套方案之间切换比较。社区里有一个基于 Python 的时间服务器,可以用 uvx 直接拉起:
{
"mcpServers": {
"time": {
"command": "uvx",
"args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
}
}
}
两种写法的取舍很清楚:自己写的版本完全可控、零依赖,适合作为长期基线;uvx 版本胜在开箱即用,但参数和包版本会随上游变化,把配置复制给别人时最好连版本一起说明。无论用哪种,/mcp 里的工具名可能不同,提问时按实际名字来。
5. 会话记录:问「现在几点」并拿到正确返回
配置就位后,进入项目目录启动 Claude Code,模型出口走的是前面配好的统一 API 地址,MCP 工具由客户端在本地拉起,两条链路互不干扰。下面是这边跑通之后的一次会话回放,时间戳来自当次运行,你复现时会得到你自己机器的当前时间,读数不一致是正常的,关键是来源必须是系统时钟而不是模型生成的文本。
$ cd ~/work/mcp-demo
$ claude
> 现在几点?
localtime · get_current_time (timezone: "Asia/Shanghai")
2025-09-18 15:42:07 (Asia/Shanghai, UTC+0800)
现在是 2025 年 9 月 18 日 15:42,北京时间,UTC+8。
> 那纽约现在几点?
localtime · get_current_time (timezone: "America/New_York")
2025-09-18 03:42:11 (America/New_York, UTC-0400)
纽约当地时间是 9 月 18 日凌晨 3:42,和北京相差 12 小时。
> 刚才那个工具是谁提供的?
当前会话挂载的 MCP 服务器是 localtime,暴露一个工具 get_current_time,
接收可选参数 timezone,返回对应时区的当前时间。
这段回放里有三个值得注意的细节。第一,模型没有直接回答时间,而是先发起一次工具调用,界面里能看到工具名、参数和返回内容三部分,这说明走的是真实调用路径。第二,第二次提问换了一个时区,模型自己把参数从 Asia/Shanghai 换成了 America/New_York,说明它读懂了 inputSchema 里对 timezone 的描述,这也是工具描述要写清楚的原因。第三,第三次提问涉及会话状态,模型能准确说出当前挂载的服务器名和工具名,说明配置确实加载到了上下文里。
想再确认一层,可以要求模型返回原始格式,或者故意问一个不存在的时区看它怎么处理。传 Asia/Nowhere 时,我这边写的代码会回退到默认时区并在返回文本里明确标出实际使用的时区名,这样模型不会把错误时区的结果当成用户要的时区转述,比静默修正更容易发现问题。如果你换成官方实现,这部分行为可能不同,遇到返回内容和你预期不一致时,以实际输出为准。
对照一下没有 MCP 的情况,差异会很明显。同一个问题在没挂工具的会话里,模型只能给出一段含糊的表述,或者根据训练数据里的日期直接编一个时间点,而且语气通常同样笃定。这也是为什么「时间类问题」适合拿来当 MCP 的第一个练手任务:正确与否可以一眼验证,不需要任何领域知识,错了就是错了,没有模糊地带。
6. 401、404 与工具不出现:本篇配置的排障清单
401 是模型出口的问题,和 MCP 无关。常见来源有三个:ANTHROPIC_AUTH_TOKEN 里粘了多余的空格或换行;Key 在控制台被删掉或重建过,本地还在用旧的;环境里同时存在另一个凭据变量,覆盖了你写进 settings 的值。处理顺序是先清空 shell 里的相关变量,只留 settings 一份配置,重开终端,再用一次非交互请求确认,最后才去看 MCP。
404 和「模型不存在」是地址与模型 ID 的问题。先把 ANTHROPIC_BASE_URL 逐字符对一遍,正确值是 https://taotoken.net/api,没有 /v1,没有尾斜杠,也没有多余路径。再把 ANTHROPIC_MODEL 和模型广场当前列表比一遍,注意大小写和分隔符。这两步做完还是报错,就把完整请求的报错信息记下来,和配置一起看,通常能直接定位到是哪一项没生效。
工具不出现,先看 /mcp 的服务器状态。显示连接失败时,九成是路径问题:args 里写成相对路径、解释器路径在 GUI 环境和终端里不一致、脚本没有可执行权限。确认方式是复制配置里的 command 和 args,在干净的终端里拼成一条命令直接执行,能正常等待输入就说明启动没问题。如果脚本启动就崩,把 stderr 打到文件里看,Python 语法错误和缺 tzdata 都会在这里暴露。
服务端连上了但模型不用工具,属于描述问题而不是配置问题。工具的 description 要写清楚它做什么、参数是什么格式,含糊的描述会让模型选择不调用。提问时也可以更明确一点,比如直接说「用 localtime 工具查一下」,第一次跑通之后再换回自然提问。另外,客户端可能会为工具调用请求一次确认,如果误点了拒绝,本次会话里该工具会被跳过,重新提问或者重开会话即可。
最后是一条边界原则,和这个时间工具关系不大,但值得写进习惯里。MCP 让模型能调用本机能力,挂载前先想清楚这个工具能做什么,只读类的查询风险最低,涉及写文件、改数据库、动线上环境的工具不要直接挂给模型。真需要模型帮忙生成命令或 SQL,也应该是它把命令写出来,你在本地执行,再把结果贴回对话让它解释。工具的能力边界,永远由挂载的人负责。
7. 跑通之后:用同一把 Key 对账这次工具调用
链路跑通后,有一件事值得顺手做掉:确认这次会话里用的模型和广场上展示的是同一个。打开 模型对话 用同一把 Key 发一条消息,对比模型 ID 是否与 settings.json 里填的一致。如果两边返回的质量或风格差异明显,先怀疑模型 ID 写错了,而不是怀疑工具调用出了问题。时间工具只是把系统时钟读出来,返回什么时间完全由本机决定,和模型无关。
接下来如果要把这套配置长期用下去,有两件事可以提前安排。一个是把 .mcp.json 提交进项目仓库,让同组的人拉下来就能用,但注意里面不要有绝对路径之外的敏感信息,Key 始终放在各自的 settings.json 或环境变量里,不进仓库。另一个是把工具列表当成项目资产维护,时间只是第一个,后面加只读的日志查询、配置读取、文档检索都很自然,每加一个都先在终端里用管道自测一遍,再接进客户端。
要看这次调用是否按预期计费和入账,去控制台对一下用量记录,时间工具的返回本身不产生额外请求,只有模型侧的对话轮次会计入。日常如果主要在 Claude Code 里工作,可以看一下 Coding Plan 的额度形态是否匹配你的使用节奏;想重新建一把 Key 做隔离测试,创建 Key 在控制台里一分钟能搞定。
换机器或者换项目时,最容易漏的是解释器路径和时区数据这两个环境差异。把这份配置带到新环境,先跑一遍管道自测,再启动客户端,比直接开 Claude Code 再回头找问题省时间。Claude Code 侧的完整变量说明和 settings.json 字段,对照 接入文档 核对一遍即可。下一步想验证别的工具,把 tools/list 里的数组改掉,其余流程完全复用。
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度




被折叠的 条评论
为什么被折叠?



