mcpField
D
deltat

deltat

本地服务
delt.at生产力93 周前更新

关于 deltat

deltat 是供 Agent 使用的日历预订 MCP 服务,适合先提出时间、再等待人工确认的场景。它通过查找、暂存和提交流程临时保留时段,并以原子操作提交预订,也支持多个日历。

主要功能

  1. 01跨日历查找空闲时段
  2. 02确认前暂存时段
  3. 03提交或释放暂存
  4. 04原子预订多个日历
  5. 05冲突时返回备选时间
  6. 06查看和取消预订

适用场景

  1. 01等待确认期间暂留一个时段
  2. 02同时预订房间和设备
  3. 03时段被占用时提供备选
  4. 04管理预约和取消事项

MCP 相关

该服务通过 MCP 客户端提供日历工具,并由 deltat 数据库支持。预订流程为 find_slots、hold_slot、commit_hold;当前软件包提供 stdio 服务。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"open-deltat-deltat": {
"command": "npx",
"args": [
"-y",
"@open-deltat/mcp"
],
"env": {
"DELTAT_PASSWORD": "<DELTAT_PASSWORD>",
"DELTAT_HOST": "<DELTAT_HOST>",
"DELTAT_PORT": "<DELTAT_PORT>",
"DELTAT_DATABASE": "<DELTAT_DATABASE>",
"DELTAT_USER": "<DELTAT_USER>",
"DELTAT_TLS": "<DELTAT_TLS>",
"DELTAT_TLS_CA": "<DELTAT_TLS_CA>"
}
}
}
}
启动命令npx -y @open-deltat/mcp
需要配置的环境变量
DELTAT_PASSWORD密钥Password for the deltat server. Required; there is no default.
DELTAT_HOSTHostname of the deltat server.
DELTAT_PORTPort deltat listens on.
DELTAT_DATABASETenant to connect to. Each database name is an isolated tenant.
DELTAT_USERUsername presented at connect time.
DELTAT_TLSon to encrypt the connection and verify deltat's certificate. Unset means off unless DELTAT_TLS_CA is set; without TLS the password is sent in the clear, so use it for any deltat that is not on this machine.
DELTAT_TLS_CAPath to a PEM certificate to trust instead of the system's authorities, for a deltat with a self-signed certificate. Implies DELTAT_TLS=on.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@open-deltat/mcp0.2.0

工具

9

收录的工具列表 · 更新于 2 周前

参数类型必填说明
namestring是
hoursarray否每周开放时段;为空表示不可用
timezonestring否IANA 时区,例如 Europe/Berlin
slot_minutesinteger否

详细介绍

@open-deltat/mcp

为 Agent 提供一个真正可预约、绝不会出现重复预约的日历。

npx @open-deltat/mcp

大多数排程 API 只给 Agent 一个动词:预约。这对提交表单来说没问题,但对 Agent 来说行不通,因为 Agent 需要几秒钟才能做出决定。它看到 14:30 有空,于是说“14:30 怎么样?”,然后等人类回复;等到回复时,那个时段可能已被别人占用。查看和预约是两个独立的时刻,中间的每一刻都存在竞争。

这个服务没有一步完成预约的动词。预约流程是:

find_slots  →  hold_slot  →  commit_hold

关键在于暂留。它会将时段保留几分钟,因此从提出时间到确认期间,该时段属于你,其他人无法占用。暂留会自动过期,因此被放弃的对话会自行清理,暂留也不会产生任何成本。

底层是 deltat,这是一个数据库,其唯一不变量是两项预约绝不会占用同一时段。拒绝由存储引擎发出,而非由先行检查、寄希望于不会出错的应用代码发出。

工具

工具使用时机
find_slots向任何人提供时间之前。列出你指定的所有日历上真正空闲的时段。
hold_slot即将向人类报出某个具体时间时。返回 hold_ids。
commit_hold预约已确认时。以原子方式将每个暂留时段转为预约。
release_hold确定不需要已暂留的时段时。立即释放该时段。
create_calendar需要新的可预约日历时。返回 calendar_id。
set_availability开放时间有变,或 find_slots 没有返回任何结果时。替换整周的安排。
list_bookings有人询问已安排的事项,或你需要 booking_id 来取消预约时。
cancel_booking人类要求取消已确认的预约时。
book_slot永不使用。此工具仅用于拒绝请求,并引导你改用 hold_slot + commit_hold。

最后一行是刻意为之。其他所有日历 API 都有一步完成预约的动词,因此模型会尝试使用这里的同类动词,却发现无此功能。与其让它产生幻觉式工具调用或放弃对话,不如注册这个动词并在响应中解释实际的两个步骤。它绝不会预约任何内容。

时间以带必需偏移量的 RFC 3339 字符串传递,绝不会使用裸 epoch 毫秒数。没有时区的时间戳会被拒绝,而不会被默默解释为进程运行所在时区的时间。错误会带有可用于分支处理的类型前缀:CONFLICT(选择其他时段)、EXPIRED(重新暂留)、INVALID、NOT_FOUND。

拒绝时告知可用时间

当时段已被占用或时间不在开放时间内时,拒绝响应会附带原本可用的时间,这样 Agent 就能在同一轮中提出替代方案,而不必从头开始:

{
  "error": "CONFLICT",
  "booked": false, "held": false, "reserved": false,
  "retry_same_time": true,
  "alternatives": [
    { "start": "2026-06-01T12:00:00.000Z", "end": "2026-06-01T13:00:00.000Z",
      "start_local": "1 Jun 2026, 2:00 pm" }
  ],
  "next": "These times were free a moment ago but are NOT reserved. Offer one, then call hold_slot on it before you tell anyone it is theirs."
}

在实时通话中,这能省去整个模型轮次,否则就只能陷入沉默。

reserved 始终为 false。之所以明确声明,而不是让人自行推断,是因为只扫一眼 alternatives 的模型不能因此认定某个时段已被留出。替代时间是曾经空闲的时间;要使用其中任一时间,仍需调用 hold_slot。

如果没有可提供的替代时间,拒绝响应仍采用简单的 CODE: message 格式,因此禁用了替代方案的内核(DELTAT_COUNTER_OFFER=0)或早于此功能的内核,行为都与以前完全相同。

同时预约多个日历

相机机身、镜头和工作人员。外科医生和手术室。预约以及前往预约地点的车程。将所有日历传给同一组工具:

find_slots({ calendar_ids: [body, lens, crew], from, to })    times when ALL are free
hold_slot({ calendar_ids: [body, lens, crew], start, end })   holds every one, or none
commit_hold({ hold_ids, label })                              books every one, or none

只有一个日历时,也使用相同的调用方式,只需传入一个元素的列表。提交操作在 deltat 中是一条记录,因此无论是其他预约者抢先预约,还是发生崩溃,都不会导致机身已预约而镜头仍空闲。

当一组设备的预约被拒绝时,替代时间是所有日历都能在相同长度的时段内空闲的时间,绝不会是刚被拒绝的时间,格式与上文相同。

配置

你需要一个 deltat 服务,版本为 0.4.0 或更高。使用 Docker 自托管:

docker run -p 5433:5433 -e DELTAT_PASSWORD=<your-password> ghcr.io/open-deltat/deltat

然后添加 MCP 服务。在 Claude Code 中:

claude mcp add deltat \
  --env DELTAT_HOST=localhost \
  --env DELTAT_PORT=5433 \
  --env DELTAT_DATABASE=public \
  --env DELTAT_PASSWORD=<your-password> \
  -- npx -y @open-deltat/mcp

在 Claude Desktop 中,编辑 claude_desktop_config.json 并重启:

{
  "mcpServers": {
    "deltat": {
      "command": "npx",
      "args": ["-y", "@open-deltat/mcp"],
      "env": {
        "DELTAT_HOST": "localhost",
        "DELTAT_PORT": "5433",
        "DELTAT_DATABASE": "public",
        "DELTAT_PASSWORD": "<your-password>"
      }
    }
  }
}

DELTAT_PASSWORD 是必填项,没有默认值;如果未设置,服务会拒绝启动,而不是尝试使用容易猜到的密码。其他所有配置默认连接到 localhost:5433 上的本地 deltat,数据库为 public。每个数据库名称都是一个隔离租户。

然后只需询问:“创建一个名为 Haircuts 的日历,开放时间为工作日下午,然后帮我预约明天第一个空闲的 30 分钟时段。”

关于身份验证,实话实说

这个 stdio 服务没有登录功能。 配置中的 deltat 密码就是凭据,服务以你的用户权限运行。这就是单操作员自托管模式:你拥有 deltat,也拥有 Agent,因此无需 OAuth。

托管式多租户服务是另一个问题。 在这种情况下,Agent 使用 OAuth 2.1 进行身份验证:服务收到未经身份验证的调用时,会返回 401 和受保护资源元数据(RFC 9728);具备相应能力的客户端则会自行运行授权码 + PKCE 流程,并通过 Client ID Metadata Document 注册,且需获得一次性人工授权。本包中的 OidcAdapter 是资源服务器端的实现,可离线使用颁发者的 JWKS 验证 bearer JWT,无需针对每个请求进行网络调用。

这个包目前还不包含该托管式传输方式。上述 stdio 服务是当前可运行的方式。

嵌入

import { createDeltatMcpServer } from "@open-deltat/mcp";
import { DeltaT } from "@open-deltat/client";

const server = createDeltatMcpServer(new DeltaT({ /* ... */ }));
await server.connect(yourTransport);

端到端验证

在 deltat 运行时:

bun scripts/smoke.ts

会通过内存中的 MCP 客户端驱动完整流程,并验证整个往返过程 (创建 → 查询可用性 → 查找 → 暂留 → 确认 → 列出 → 取消)。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。