关于 HammerTime
HammerTime 提供用于生成 Discord 时间戳和管理账户设置的 REST API 与无状态 MCP 服务。它以与机器人相同的选项和已保存设置提供时间戳命令。
主要功能
- 01生成多种格式的时间戳
- 02计算过去或未来的时间
- 03转换 Discord snowflake ID
- 04读取和更新机器人设置
- 05查看账户密钥和服务状态
适用场景
- 01为 Discord 活动创建时间戳
- 02计算相对当前时间的日期和时间
- 03根据 snowflake ID 查询时间戳
MCP 相关
无状态 Streamable HTTP MCP 服务位于 `/mcp`,使用与 REST API 相同的 API 密钥。其工具对应 HammerTime 机器人的命令。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.hammertime.cyou/mcpclaude mcp add --transport http wentthefox-hammertime https://api.hammertime.cyou/mcp{"mcpServers":{"wentthefox-hammertime":{"type":"http","url":"https://api.hammertime.cyou/mcp"}}}在 Cursor 中打开{"servers":{"wentthefox-hammertime":{"type":"http","url":"https://api.hammertime.cyou/mcp"}}}在 VS Code 中打开{"mcpServers":{"wentthefox-hammertime":{"type":"http","url":"https://api.hammertime.cyou/mcp"}}}{"mcpServers":{"wentthefox-hammertime":{"serverUrl":"https://api.hammertime.cyou/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://api.hammertime.cyou/mcp需要授权工具
详细介绍
HammerTime API
HammerTime 的公共 REST API 和 MCP 服务。每个端点和每个 MCP 工具都是同名的机器人命令,每个输入都是同名的命令选项,因此 POST /at 会接收 year、month、day、hour、minute、timezone、format 等参数,并像机器人一样,根据账号已保存的设置进行解析。逻辑由 @went.tf/hammertime-core 实现,机器人也使用它。
POST /at、/at12、/iso、/unix、/in、/ago、/add、/subtract、/snowflake | 命令,权限范围 timestamps:create |
GET/PATCH /settings | 账号的机器人设置,权限范围 settings:read / settings:write |
GET /me、GET /health | 正在使用的密钥和服务状态 |
POST /mcp | MCP 服务(Streamable HTTP,无状态),使用相同的 API 密钥 |
GET / 和 GET /generated/api-docs.json | Swagger UI 和 OpenAPI 文档 |
使用情况的记录方式与机器人相同(使用相同的计数器,来源标记为 api 或 mcp),除非账号关闭了遥测;回复中也会像机器人回复一样附带运行次数。
密钥
用户可在 HammerTime 网站的个人资料页面创建密钥,每个密钥对应一个关联的 Discord 账号。将密钥作为 Authorization: Bearer htk_... 发送。机器人有自己的服务密钥,会通过 X-Discord-User-Id 指定每个请求对应的账号;该密钥仅可用于命令的 REST 端点,不能用于 /mcp。
MCP 客户端需配置 /mcp 的 URL 和请求头,例如:
{ "mcpServers": { "hammertime": { "type": "http", "url": "https://<host>/mcp", "headers": { "Authorization": "Bearer htk_..." } } } }
将机器人作为客户端
机器人可以让此 API 处理其时间戳命令(在机器人的 .env 中配置 COMMANDS_API_URL 和 COMMANDS_API_KEY,参见 .env.example)。机器人会发送用户提供的选项,并使用带有 X-Discord-User-Id 的服务密钥,然后将答案转换为 Discord 回复。此时 API 已记录该次运行,回复中也附带了相关说明,因此机器人不会重复记录。当 API 停机、响应缓慢(COMMANDS_API_TIMEOUT_MS)、拒绝机器人请求或对其限流时,机器人会完全按照此前的方式自行处理命令,包括记录该次运行,因此此服务发生故障时,除了日志中的警告外不会造成任何影响。对于 API 拒绝的输入,机器人会照常措辞回复并记录。
速率限制
每个密钥平均每分钟可发起 RATE_LIMIT_PER_MINUTE(60)个请求,突发请求上限为该值的一半;REST 端点与 /mcp 共用限额。机器人的服务密钥会按其所服务的 Discord 账号分别限流,因此一个高频用户不会耗尽其他用户的配额。每个经过身份验证的请求的响应都会包含 RateLimit-Limit、RateLimit-Remaining 和 RateLimit-Reset;被拒绝的请求会返回带有 Retry-After(秒)的 429 响应。/health 和文档不受限流。
客户端地址每分钟身份验证失败 AUTH_FAILURES_PER_MINUTE(20)次后,在等待期结束前会被拒绝,且在此之前不会进行任何查询,因此大量伪造密钥不会各自导致 Laravel 应用执行一次查询。来自该地址的有效密钥也必须等待;这可能影响共用地址的无辜用户,因此应将该限额设得宽松一些。
在反向代理后方,所有客户端都会使用代理的地址,因此它们会被算作同一个客户端。将 CLIENT_IP_HEADER 设置为代理写入客户端地址的请求头,并确保代理会覆盖该请求头(nginx:proxy_set_header X-Real-IP $remote_addr;),否则任何人都能选择自己的计数地址。后台不会运行任何任务来清理限额记录:只有在使用时才会更新对应的计数桶,而 Redis 会在其再次被填满后自行清除该记录。
在 Redis 中计数
pm2.json 会运行两个工作进程(见下文)。如果不做处理,每个工作进程都会单独计数,因此一个密钥最多可以发起的请求数会达到工作进程数乘以
RATE_LIMIT_PER_MINUTE,某个地址的认证失败次数也是如此。配置 Redis,让它们统一计数:
可以配置 REDIS_URL,也可以配置 REDIS_HOST、REDIS_PORT、REDIS_USERNAME、REDIS_PASSWORD、REDIS_DB 和 REDIS_TLS(见
.env.example),这样所有工作进程共享限额。
- 一个 Lua 脚本会为令牌桶补充令牌、检查令牌并扣除令牌,因此两个工作进程不会同时取走最后一个令牌。键的格式为
<REDIS_KEY_PREFIX><kind>:<key or address>(hammertime-api:requests:key:42),并会自动过期。为 API 单独设置一个数据库(REDIS_DB);前缀可确保即使 Laravel 应用也共用该 Redis,其键也不会受到影响。 - 请求等待 Redis 的时间不会超过 150 ms。如果 Redis 没有响应,该请求会像未使用 Redis 时一样,按工作进程分别在内存中计数:限额稍微宽松一些,也好过限流器导致 API 宕机。日志会说明这一情况,每分钟最多记录一次;Redis 恢复后,计数会重新写入 Redis。
REDIS_TLS=true适用于连接另一台机器上的 Redis;在本机上无需启用。
以集群模式运行
pm2 会运行两个共享端口的服务工作进程(pm2.json 中的 exec_mode、instances、wait_ready 和 kill_timeout)。工作进程开始监听后会发送 ready,并在收到 SIGINT 时等待正在处理的请求完成后再停止,这样部署时就可以逐个替换工作进程,而不会中断请求(src/graceful-shutdown.ts,机器人也采用相同方式)。请求所需的数据不会从前一个工作进程保留。密钥缓存按工作进程分别维护,因此每个工作进程每隔 KEY_CACHE_TTL_SECONDS 会向 Laravel 应用验证一次密钥;除非配置了 Redis,否则限额也按工作进程分别计算。
数据存放位置
本服务不存储任何数据。Laravel 应用(SledgeHammerTime)负责管理密钥、设置和遥测数据;本服务使用 INTERNAL_API_SECRET 调用其 /internal 端点(见该仓库中的 docs/api.md)。已验证的密钥会缓存 KEY_CACHE_TTL_SECONDS,因此被撤销的密钥在这段时间内仍可使用。
生成的部分
src/generated/commands.ts(请求架构、选项类型和英文文本)由机器人的 src/commands.json 及其 en-US 语言区域生成,docs/openapi/api-docs.json(位于仓库根目录)则由路由生成。
两者都会提交到仓库;如果文件过期,测试会失败:
pnpm --filter @went.tf/hammertime-api run generate # after changing commands.json or the locale
pnpm --filter @went.tf/hammertime-api run generate -- --commands <path> # from another commands file
pnpm --filter @went.tf/hammertime-api run generate -- --check # write nothing, fail when out of date
pnpm --filter @went.tf/hammertime-api run openapi:sync [-- --check] # after changing routes or their descriptions
运行
pnpm run build:core && pnpm --filter @went.tf/hammertime-api run build
cp packages/api/.env.example packages/api/.env # fill it in, git ignores it
node packages/api/dist/src/server.js # reads packages/api/.env by itself
在服务器上,pm2 会运行本服务(pm2.json 中的 HammerTimeBot:Api),但前提是 packages/api/.env 文件存在,见 deploy.conf。(该文件以前是仓库根目录下的 .env.api,部署时会自动移动此类文件。)请先部署
Laravel 应用,因为本服务会调用其内部端点。本服务只监听 127.0.0.1:3010,因此 nginx 必须将其代理到一个主机名:虚拟主机配置文件为 nginx/api.hammertime.cyou.conf,其中的文件头包含一次性设置说明。
测试
在根目录运行 pnpm run test:packages(core 和 API)。测试会在进程内运行应用,并使用内存后端;MCP
测试使用真实的 MCP SDK 客户端。
在 MCP Registry 中上架
server.json 是 MCP Registry 中列出的内容:MCP 服务的端点,以及它需要以 bearer token 的形式提供 API 密钥。Registry 只保存元数据,因此无需发布软件包。
名称为 io.github.WentTheFox/hammertime。.github/workflows/publish-mcp.yml 中的工作流使用 GitHub Actions 的 OIDC 令牌进行身份验证,以证明该仓库属于相应所有者,因此无需在任何地方存储密钥。
发布一个版本:
- 提高
packages/api/package.json和packages/api/server.json中的version。两者不一致时,测试会失败。 - 合并后为其打标签:
git tag mcp-v0.2.0 && git push origin mcp-v0.2.0。
该工作流会测试软件包、检查标签是否与 server.json 一致、使用 mcp-publisher validate 验证,并发布。每个版本只能发布一次。若要在不发布的情况下试用该文件,请在 packages/api 中运行 mcp-publisher validate。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。