关于 Keeper.sh
Keeper.sh 是用于同步多个日历的开源工具,可连接 Google Calendar、Outlook、iCloud、Fastmail、CalDAV 及 iCal/ICS 来源。它面向需要跨日历查看和协调事件的个人与 Agent,并提供统一 API 和 MCP 服务。
主要功能
- 01连接多种日历来源与目标
- 02汇总事件并推送至多个日历
- 03查找并同步日历事件
- 04按来源移除事件详情
- 05提供 REST API 与 iCal 订阅源
适用场景
- 01跨工作与个人日历同步事件
- 02将一个日历的事件分发到多个目标
- 03清理已从来源删除的远程事件
MCP 相关
Keeper.sh 提供 MCP 服务,供 AI Agent 访问和管理日历。客户端可通过 MCP 读取、预订事件、回复邀请、查找空闲时间,并触发同步。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://www.keeper.sh/mcpclaude mcp add --transport http ridafkih-keeper-sh https://www.keeper.sh/mcp{"mcpServers":{"ridafkih-keeper-sh":{"type":"http","url":"https://www.keeper.sh/mcp"}}}在 Cursor 中打开{"servers":{"ridafkih-keeper-sh":{"type":"http","url":"https://www.keeper.sh/mcp"}}}在 VS Code 中打开{"mcpServers":{"ridafkih-keeper-sh":{"type":"http","url":"https://www.keeper.sh/mcp"}}}{"mcpServers":{"ridafkih-keeper-sh":{"serverUrl":"https://www.keeper.sh/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍

关于
Keeper.sh 是一款简单的开源日历同步工具。它可以从 Google Calendar、Outlook、iCloud、Fastmail、CalDAV 服务,或远程托管的 iCal 和 ICS 链接拉取事件,并将其推送到一个或多个日历,让所有日历中的时段保持一致。Google、Outlook、iCloud、Fastmail 和 CalDAV 均为一等集成,可分别用作来源或目标;iCal 和 ICS 链接则仅支持拉取。它还提供全局 MCP 服务和 API,让你或你的 Agent 可以通过一个便捷的界面管理所有日历。
推荐使用托管版本 keeper.sh:它运行的代码相同,但省去了服务器、域名、升级、备份,以及原本需要你自行注册的 Google 和 Microsoft 登录应用。可自托管也是一等选择,而且自托管时包含所有 Pro 功能——这不是试用版,也不会取消。你只需承担维护工作,而不用支付 5 美元。
功能
- 一等集成 Google Calendar、Outlook、iCloud、Fastmail 和 CalDAV,均可用作来源或目标
- 仅支持拉取远程托管的 iCal 和 ICS 链接
- 在 Google 和 Outlook 上使用服务商同步令牌进行增量同步,而非重新获取所有内容
- 与事件内容无关的同步引擎
- 将汇总事件推送到一个或多个日历
- 按来源设置隐私控制,可移除事件名称、描述和地点,并使用
{{calendar_name}}或{{event_name}}模板替换标题 /api/v1下通过 API 令牌进行身份验证的 REST API- 用于 AI Agent 访问日历的 MCP(Model Context Protocol)服务
- 可供任何日历应用订阅的合并 iCal 订阅源
- 基于 AGPL-3.0 开源
- 易于自托管
- 易于清除远程事件
错误报告和功能请求
如果你遇到错误或有功能创意,可以在 GitHub 上提交 issue,我们会尽快进行分类处理并解决。
贡献
欢迎高价值、高质量的贡献。在着手开发你希望合并的大型功能之前,请先提交 issue 进行讨论。
本地开发
开发环境通过带有自动 TLS 的 Caddy 反向代理,在 https://keeper.localhost 上运行。根据 RFC 6761,.localhost TLD 会自动解析到 127.0.0.1,因此无需添加 /etc/hosts 条目。
前置条件
开始使用
bun install
生成并信任根 CA
开发环境通过 Caddy 使用 HTTPS。你需要生成本地根证书颁发机构,并信任它,以便浏览器接受该证书。
mkdir -p .pki
openssl req -x509 -new -nodes \
-newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout .pki/root.key -out .pki/root.crt \
-days 3650 -subj "/CN=Keeper.sh CA"
然后在你的平台上信任它:
macOS
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain .pki/root.crt
Linux
sudo cp .pki/root.crt /usr/local/share/ca-certificates/keeper-dev-root.crt
sudo update-ca-certificates
启动开发环境
bun dev
这会通过 Docker Compose 启动 PostgreSQL、Redis 和 Caddy 反向代理,同时在本地启动 API、Web、MCP、cron 和 worker 服务。启动后,打开 https://keeper.localhost。
架构
| 服务 | 本地端口 | 访问方式 |
|---|---|---|
| Caddy | 443 | https://keeper.localhost |
| Web | 5173 | 由 Caddy 代理 |
| API | 3000 | 由 Web 在 /api 代理 |
| MCP | 3001 | 由 Web 在 /mcp 代理 |
| Postgres | 5432 | postgresql://postgres:postgres@localhost:5432/postgres |
| Redis | 6379 | redis://localhost:6379 |
常见问题
为什么会有这个项目?
因为我自己需要。自从创办 AI 治理平台 Sedna 后,我就必须同时处理三个日历:一个用于业务,一个用于工作,一个用于个人生活。
会议撞期的次数高得令人沮丧。
为什么不使用_另一个服务_?
如果它已经满足你的需求,那就用它。这个项目的存在,是因为我总是遇到两个问题:在源日历中删除的事件会永远留在目标日历中,而且我无法查看处理日历的代码。
这里的设计解决了这两个问题。删除操作会通过一条映射记录进行跟踪,该记录的生命周期长于它所指向的事件,因此后续同步时仍会删除远程副本;清理任务只会处理由 Keeper.sh 创建的事件,不会删除你自己创建的事件。引擎采用 AGPL-3.0 许可,因此你可以自行核实这一点,而不必盲目信任。
同步引擎如何工作?
- 如果我们有一个本地事件,但没有该事件对应的“源 → 目标”映射,就会将事件推送到目标日历。
- 如果我们有某个事件的映射,但源 ID 已不再存在于源日历中,就会从目标日历删除该事件。
- 对于带有 Keeper.sh 创建标记、但没有对应本地跟踪记录的事件,我们会将其删除。此操作仅用于向后兼容。
Keeper.sh 创建的事件会通过以下方式标记:远程 UID 使用 @keeper.sh 后缀;对于 Outlook 这类不支持自定义 UID 的平台,则会将事件归入 "keeper.sh" 类别。
同步如何拆分?
同步分为两部分,分别按不同的计划运行。
每分钟一次,无论使用哪种套餐,摄取流程都会将数据从源日历拉取到 Keeper.sh 自己的数据库中。Google 和 Outlook 会分别使用服务商提供的同步令牌和增量链接进行增量获取,因此每次运行只会请求自上次运行以来发生变化的内容。对于 CalDAV、iCloud 和 Fastmail,系统会重新获取数据,并与 Keeper.sh 已存储的事件状态进行比对;对于 iCal/ICS 链接,系统会重新获取数据,并与上次存储的快照进行比对。
定价表中的刷新间隔指的是向目标日历推送数据的频率。对于 Pro 套餐,cron 服务每分钟将每个目标日历的任务加入 Redis 支持的队列;免费套餐则每三十分钟加入一次。随后,工作进程会同步目标日历。我们采用轮询,而不是使用服务商的推送通知,因此无需从外部访问你的实例。
这两部分的运行并非只依赖计划任务。调用 POST /api/v1/sync 或通过 MCP 调用 trigger_sync,会清除摄取退避状态,使源日历在下一轮重新轮询,并立即将推送任务加入队列。为避免客户端向服务商发送过多请求,每位用户每分钟最多可触发一次。通过 pause_sync 暂停的日历会被这两部分跳过,直到恢复同步。
云托管
这是我最推荐大多数人使用的版本,包括完全有能力自行运行它的人。它使用的是运行在我维护的硬件上的同一套引擎,因此,你投入的时间会用在日程安排上,而不是基础设施上;而你支付的费用则用于支持这两个版本的开发。
前往 keeper.sh 开始使用云托管版本。
| 免费 | 专业版(云托管) | 专业版(自托管) | |
|---|---|---|---|
| 月费 | $0 USD | $5 USD | $0 |
| 年费 | $0 USD | $45 USD(-25%) | $0 |
| 刷新间隔 | 30 分钟 | 1 分钟 | 1 分钟 |
| 关联账户上限 | 2 | ∞ | ∞ |
| 同步映射上限 | 3 | ∞ | ∞ |
| 事件筛选器 | 否 | 是 | 是 |
| iCal 订阅源自定义 | 否 | 是 | 是 |
| API 请求数 | 每天 25 次 | ∞ | ∞ |
最先触及的两项上限分别计算。一个关联账户是指一个已连接的 Google、Outlook、iCloud、Fastmail 或 CalDAV 账户,或者一个 iCal/ICS 订阅源;免费版最多可关联两个,无论每个账户提供多少个日历。一个同步映射是指将一个源日历连接到一个目标日历;免费版最多可创建三个,因此,一个源日历分发到三个目标日历就会用完全部额度。刷新间隔指 Keeper.sh 向目标日历推送的频率;所有方案都会每分钟从源日历采集一次数据。
自托管
自行托管 Keeper.sh,默认即可使用专业版:无需订阅即可使用所有专业版功能;你还能确保数据治理和自主权,而且过程也很有趣。相应的代价是需要准备服务器和域名、进行升级和备份、创建自己的 Google 和 Microsoft OAuth 应用,并在服务停止时负责处理。如果你打算自托管,请考虑在 GitHub 上赞助我,以支持我和项目的开发。
目前提供七个镜像:两个以便于使用为设计目标,另外五个用于底层的细粒度服务。如果你没有特别的偏好,建议从反向代理后面的 keeper-standalone 开始;这是最不容易出错、涉及组件最少的方案。
说明
从旧版本迁移? 如果你要从基于 Next.js 的旧版本升级,请参阅迁移指南,了解环境变量的变更。如果新 Web 服务器检测到旧环境变量,也会在启动时打印迁移提示。
环境变量
| 名称 | 服务 | 描述 |
|---|---|---|
| DATABASE_URL | api, cron, worker, mcp | PostgreSQL 连接 URL。 例如: postgres://user:pass@postgres:5432/keeper |
| REDIS_URL | api, cron, worker | Redis 连接 URL。所有服务必须使用同一个 Redis 实例。 例如: redis://redis:6379 |
| DATABASE_POOL_MAX | api, cron, worker, mcp | 可选。服务连接池打开的 Postgres 连接数上限。默认每个服务为 10;keeper-standalone 和 keeper-services 镜像附带的每服务值较小,适配其所连接的 Postgres。每个共置服务都会添加自己的连接池,因此这些值与 CRON_FLUSH_POOL_MAX 的总和必须低于 Postgres 的 max_connections,并留出足够空间供你自行连接。例如: 15 |
| CRON_FLUSH_POOL_MAX | cron | 可选。cron 为写入导入数据而持有的第二个连接池大小,与 DATABASE_POOL_MAX 分开且额外计算。默认值为 16;附带的镜像为 8。例如: 16 |
| RUN_MIGRATIONS | api | 可选。设为 false,可在 API 容器启动时跳过数据库迁移,适用于在滚动部署服务前单独运行迁移的部署方式。默认值为 true。例如: false |
| WORKER_JOB_QUEUE_ENABLED | cron | 必需。设为 true,将同步任务加入 worker 队列。设为 false 会阻止 cron 服务将目标同步任务加入队列,因此仍会导入源事件,但不会将其推送到目标日历。若未设置,cron 服务将显示迁移提示后退出。 |
| WORKER_CONCURRENCY | worker | 可选。worker 并发处理的同步任务数。默认值为 25。 |
| BETTER_AUTH_URL | api, mcp | 用于身份验证重定向的基础 URL。 例如: http://localhost:3000 |
| BETTER_AUTH_SECRET | api, mcp | 用于会话签名的密钥。 例如: openssl rand -base64 32 |
| API_PORT | api | 必需。Bun API 监听的端口。在 keeper-standalone 和 keeper-services 镜像中预设为 3001。 |
| ENV | web | 可选。运行时环境。可选值为 development、production 或 test。默认值为 production。 |
| PORT | web | 必需。Web 服务器监听的端口。在 keeper-standalone 和 keeper-services 镜像中预设为 3000。 |
| VITE_API_URL | web | Web 服务器用于将请求代理到 Bun API 的 URL。 例如: http://api:3001 |
| COMMERCIAL_MODE | api, cron, mcp, web | 启用 Polar 计费流程。使用 Polar 处理订阅时设为 true。 |
| POLAR_ACCESS_TOKEN | api, cron | 可选。用于管理订阅的 Polar API 令牌。 |
| POLAR_MODE | api, cron | 可选。Polar 环境,可设为 sandbox 或 production。 |
| POLAR_WEBHOOK_SECRET | api | 可选。用于验证 Polar webhook 的密钥。 |
| ENCRYPTION_KEY | api, cron, worker | 用于加密静态存储的 CalDAV 凭据的密钥。 例如: openssl rand -base64 32 |
| RESEND_API_KEY | api | 可选。用于通过 Resend 发送电子邮件的 API 密钥。 |
| FEEDBACK_EMAIL | api | 可选。应用内反馈提交内容发送至的电子邮件地址。需要设置 RESEND_API_KEY。 |
| PASSKEY_RP_ID | api | 可选。通行密钥身份验证的信赖方 ID。 |
| PASSKEY_RP_NAME | api | 可选。通行密钥的信赖方显示名称。 |
| PASSKEY_ORIGIN | api | 可选。通行密钥流程允许的来源(例如:https://keeper.example.com)。 |
| GOOGLE_CLIENT_ID | api, cron, worker | 可选。Google 日历集成所必需。 |
| GOOGLE_CLIENT_SECRET | api, cron, worker | 可选。Google 日历集成所必需。 |
| MICROSOFT_CLIENT_ID | api, cron, worker | 可选。Microsoft Outlook 集成所必需。 |
| MICROSOFT_CLIENT_SECRET | api, cron, worker | 可选。Microsoft Outlook 集成所必需。 |
| OIDC_ISSUER_URL | api | 可选。符合 OIDC 标准的身份提供商(Authentik、Keycloak、Pocket ID 等)的颁发者 URL,必须与提供商公布的内容完全一致。发现信息从 <issuer>/.well-known/openid-configuration 读取。需要设置 OIDC_CLIENT_ID 和 OIDC_CLIENT_SECRET。例如: https://id.example.com 或 https://auth.example.com/realms/keeper |
| OIDC_CLIENT_ID | api | 可选。在 OIDC 颁发者处注册的 OAuth 客户端 ID。 |
| OIDC_CLIENT_SECRET | api | 可选。在 OIDC 颁发者处注册的 OAuth 客户端密钥。 |
| OIDC_PROVIDER_NAME | api | 可选。SSO 登录按钮上显示的名称。默认值为 SSO。例如: Pocket ID |
| OIDC_SCOPES | api | 可选。向 OIDC 颁发者请求的逗号分隔范围。默认值为 openid,profile,email。例如: openid,profile,email,groups |
| DISABLE_LOCAL_AUTH | api | 可选。设为 true,关闭用户名及电子邮件/密码登录和注册。只要设置了 Google 和 Microsoft 的客户端凭据,其登录方式仍可使用。仅当 OIDC_ISSUER_URL、OIDC_CLIENT_ID 和 OIDC_CLIENT_SECRET 均已设置时才生效。 |
| POSTGRES_PASSWORD | standalone | 可选。keeper-standalone 中内部 PostgreSQL 数据库的自定义密码。若未设置,默认值为 keeper。数据库不会暴露到容器外,因此风险较低,但仍可设置密码以增加纵深防御。 |
| BLOCK_PRIVATE_RESOLUTION | api, cron, worker | 可选。设为 true,阻止出站请求(ICS 订阅、CalDAV 服务器)解析到私有或保留网络地址。可防止 SSRF。为兼容使用本地 CalDAV/ICS 服务器的可自托管配置,默认值为 false。 |
| BLOCK_PRIVATE_RESOLUTION | api, cron | 可选。设为 true,阻止出站请求(ICS 订阅、CalDAV 服务器)解析到私有或保留网络地址。可防止 SSRF。为兼容使用本地 CalDAV/ICS 服务器的可自托管配置,默认值为 false。 |
| REGISTRATION_EMAIL_DOMAINS | api | 可选。允许创建账户的逗号分隔电子邮件域名列表。设置后,来自其他域名的注册和首次登录都会被拒绝;现有账户不受影响。未设置或为空时允许所有域名。 例如: keeper.sh,example.com |
| TRUSTED_ORIGINS | api | 可选。用于 CSRF 防护的额外受信任来源,以逗号分隔。 例如: http://192.168.1.100,http://keeper.local,https://keeper.example.com |
| WEBHOOK_PUBLIC_URL | api, cron | 可选。Google 日历和 Microsoft Graph 可访问你实例的公共 HTTPS 来源。设置后会启用实时推送,因此 Keeper.sh 能在几秒内获取日历变更,而不必等待下一次轮询。若不设置则一切保持不变:轮询照常进行,且不会向提供商注册任何订阅。所有套餐都会注册订阅;免费套餐中,五分钟内收到的通知会合并为一次读取,目标仍按免费套餐的 30 分钟周期更新。必须是没有查询字符串或片段的公共 https:// 来源;启动时会拒绝 localhost、私有范围和 .local 地址,因为提供商无法向这些地址投递通知。例如: https://keeper.example.com |
| CRON_JOBS_ENABLED | cron | 可选。设为 false,使此进程仅运行导入循环,适用于在运行定时任务的容器之外增加额外 cron 容器以提升导入能力。默认值为 true。 |
| INGEST_LOOP_ENABLED | cron | 可选。设为 false,阻止此进程读取源日历。默认值为 true。可同时运行任意数量的进程:每个进程都会认领到期的日历并持有租约,因此不会重复读取日历。 |
| INGEST_FLEET_CONCURRENCY | cron | 一个进程按常规计划同时读取的日历数。默认值为 64。 |
| INGEST_WAKE_CONCURRENCY | cron | 推送通知、同步请求或登录后,一个进程同时读取的日历数。与常规计划分开处理,因此此类积压永远不会造成延迟。默认值为 16。 |
| INGEST_CLAIM_BATCH | cron | 一个进程每次查询最多认领的日历数。默认值为 50。 |
| INGEST_FLEET_IDLE_MS | cron | 没有到期日历时,循环等待多久后再次检查。默认值为 2000。 |
| INGEST_WAKE_IDLE_MS | cron | 推送通知及其他唤醒事件的相同等待时长。默认值为 1000。 |
| INGEST_LEASE_SECONDS | cron | 被认领的日历由认领进程持有的时长。若进程退出,该日历会在租约到期后再次读取。默认值为 900。 |
| INGEST_PRO_POLL_SECONDS | cron | 没有正常推送订阅覆盖时,读取 Pro 日历的频率。默认值为 60。 |
| INGEST_FREE_POLL_SECONDS | cron | 免费日历的相同间隔。默认值为 600。 |
| INGEST_PRO_SAFETY_POLL_SECONDS | cron | 即使有正常推送订阅,也会读取 Pro 日历的频率,以捕获提供商丢失的通知。默认值为 300。 |
| INGEST_FREE_SAFETY_POLL_SECONDS | cron | 免费日历的相同安全轮询间隔。默认值为 3600。 |
| INGEST_DORMANT_POLL_SECONDS | cron | 读取休眠日历的频率:休眠日历指不为任何目标或订阅源提供数据,且其所有者在 INGEST_DORMANT_AFTER_DAYS 期间未登录或使用 API 的日历。登录后会立即读取。默认值为 86400。 |
| INGEST_DORMANT_AFTER_DAYS | cron | 日历被视为休眠前的未活跃天数。设为 0,则永不将日历视为休眠。默认值为 30。 |
| GOOGLE_PROJECT_REQUESTS_PER_MINUTE | api, cron, worker | 可选。整个 Google Cloud 项目每分钟最多发出的 Google 日历请求数。后台工作(读取源日历、写入目标、注册推送通道、重新发现日历)最多使用其 90%;其余部分留给用户正在等待的请求,例如连接账户或通过 API 创建事件,这类请求会优先于排队的后台工作。默认值为 10000,即 Google 公布的每项目限额;若项目配额不同,请设置为相应值。设为 0,则除 Google 的每用户限额外不设项目级限额。 |
| OUTLOOK_INGEST_REQUESTS_PER_MINUTE | cron | 可选。所有读取日历的进程每分钟最多发出的 Microsoft Graph 请求数。Microsoft Graph 对每个邮箱的 Outlook 请求设有限额,Keeper.sh 已会相应地控制请求速率,因此默认不设置此项,表示不设限。 |
| WEBSOCKET_URL | api | 可选。客户端应连接实时套接字的外部 URL。未设置时,客户端会连接 API 自身的 /api/socket 路径。例如: wss://socket.keeper.example.com |
| MCP_PUBLIC_URL | api, mcp | api 可选,mcp 必需。MCP 资源的公共 URL。启用 API 上的 OAuth,并向客户端标识 MCP 服务。在 keeper-standalone 中,默认值为在 BETTER_AUTH_URL 后附加 /mcp。例如: https://keeper.example.com/mcp |
| VITE_MCP_URL | web | 可选。Web 服务器用于将 /mcp 请求代理到 MCP 服务的内部 URL。例如: http://mcp:3002 |
| OPENAI_APPS_CHALLENGE_TOKEN | web | 可选。来自 OpenAI Platform 插件提交流程的域名验证令牌,通过 /.well-known/openai-apps-challenge 提供。仅在将 MCP 服务提交到 ChatGPT Plugins Directory 时需要。例如: abc123 |
| MCP_PORT | mcp | mcp 必需。MCP 服务监听的端口。在 keeper-standalone 镜像中预设为 3002。例如: 3002 |
| MCP_API_URL | api, mcp | 可选。提供 MCP 服务时用于访问 Keeper.sh API 的内部 URL,用于调用工具以及获取验证 MCP 令牌的签名密钥。默认值为 BETTER_AUTH_URL,这要求两个服务都能访问你实例的公共 URL。在 keeper-standalone 镜像中预设为捆绑的 API。例如: http://api:3001 |
| OTEL_EXPORTER_OTLP_ENDPOINT | api, cron, worker, mcp, web | 可选。设置后,启用将结构化日志转发到 OpenTelemetry 收集器。每个服务都通过 @keeper.sh/otelemetry 中的 keeper-otelemetry 二进制文件运行;该文件会将服务作为子进程启动、转发其输出和停止信号,且不会影响应用性能。例如: https://otel-collector.example.com:4318 |
| OTEL_EXPORTER_OTLP_PROTOCOL | api, cron, worker, mcp, web | 可选。OTLP 导出器使用的协议。根据 OpenTelemetry 规范,默认值为 http/protobuf。例如: http/protobuf、grpc、http/json |
| OTEL_EXPORTER_OTLP_HEADERS | api, cron, worker, mcp, web | 可选。每次 OTLP 导出请求发送的标头。可用于身份验证(例如 Basic 身份验证或 API 密钥)。 例如: Authorization=Basic dXNlcjpwYXNz |
web 服务器会在运行时读取以下环境变量,并将其序列化到页面中,作为公开的运行时配置。所有变量均为可选。 |
| 名称 | 描述 |
|---|---|
| POLAR_PRO_MONTHLY_PRODUCT_ID | 可选。Polar 月度产品 ID,用于启用应用内升级链接。 |
| POLAR_PRO_YEARLY_PRODUCT_ID | 可选。Polar 年度产品 ID,用于启用应用内升级链接。 |
| VITE_VISITORS_NOW_TOKEN | 可选。用于分析的 visitors.now 令牌 |
| VITE_GOOGLE_ADS_ID | 可选。Google Ads 转化跟踪 ID(例如,AW-123456789) |
| VITE_GOOGLE_ADS_CONVERSION_LABEL | 可选。用于购买跟踪的 Google Ads 转化标签 |
| VITE_GOOGLE_ADS_SIGNUP_CONVERSION_LABEL | 可选。用于注册跟踪的 Google Ads 转化标签 |
| VITE_GOOGLE_ANALYTICS_ID | 可选。Google Analytics 4 衡量 ID(例如,G-XXXXXXXXXX) |
说明
keeper-standalone会在内部自动配置所有内容——Web 服务器和 Bun API 均位于端口80上的同一个 Caddy 反向代理后。keeper-services会在一个容器中运行 Web、API、cron 和 worker 服务。Web 服务器会在内部代理/api请求,因此只需暴露端口3000。- 对于单独的镜像,只需暴露
web容器。API 通过VITE_API_URL在内部访问。
Images
| Tag | Description | Included Services |
|---|---|---|
keeper-standalone:2 | “standalone” 镜像包含使用 Keeper.sh 快速启动所需的一切,并尽可能减少配置。 | keeper-web、keeper-api、keeper-cron、keeper-worker、keeper-mcp、redis、postgresql、caddy |
keeper-services:2 | 如果你希望 Redis 和数据库运行在容器之外,可以使用 “services” 镜像启动,此镜像不包含它们。 | keeper-web、keeper-api、keeper-cron、keeper-worker |
keeper-web:2 | 包含 Vite SSR Web 界面的镜像。 | keeper-web |
keeper-api:2 | 包含 Bun API 服务的镜像。 | keeper-api |
keeper-cron:2 | 包含 Bun cron 服务的镜像。需要 keeper-worker 执行目标同步。 | keeper-cron |
keeper-worker:2 | 包含 BullMQ worker 的镜像,用于处理由 keeper-cron 加入队列的日历同步任务。 | keeper-worker |
keeper-mcp:2 | 包含用于 AI Agent 日历访问的 MCP 服务的镜像。可选——仅在使用 MCP 客户端时需要。 | keeper-mcp |
提示
请将镜像固定到 major.minor 版本标签(例如
2.13),而不是latest。这样可以避免拉取新镜像时自动应用破坏性变更。
前提条件
Docker 和 Docker Compose
如需安装 Docker Compose,请参阅 Docker 官方文档。
Google OAuth 凭据
提示
此项为可选,但如果不配置,你将无法将 Google Calendar 设置为目标。
请参阅 Google Cloud Platform 官方文档,以生成有效的 Google OAuth 凭据。你必须为同意屏幕授予 calendar.events、calendar.calendarlist.readonly 和 userinfo.email 范围。
完成配置后,在运行时将客户端 ID 和客户端密钥设置为 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET 环境变量。
Microsoft Azure 凭据
提示
再次提醒,这是可选项。如果不进行配置,就无法将 Microsoft Outlook 配置为目标位置。
这个社区讨论帖提供了最清晰的非旧版 OAuth 配置指南。所需的作用域为 Calendars.ReadWrite、User.Read 和 offline_access。Microsoft 的客户端 ID 和密钥分别填入环境变量 MICROSOFT_CLIENT_ID 和 MICROSOFT_CLIENT_SECRET。
如果用户将使用工作或学校(Entra ID)帐户登录,请在应用注册中添加 xms_edov 可选声明,路径为 令牌配置 → 添加可选声明 → ID → xms_edov。当租户验证了帐户电子邮件地址所属的域时,Entra 会设置此声明,Keeper.sh 会据此将该电子邮件记录为已验证。没有此声明时,工作或学校帐户的登录会被记录为未验证,并且无法自动关联到使用相同电子邮件地址的现有帐户;但用户登录后仍可在设置中进行关联。个人 Microsoft 帐户不需要此声明。
OIDC / SSO
自托管用户可以将登录委托给任何兼容 OIDC 的身份提供商(Authentik、Keycloak、Pocket ID、Zitadel 等)。在设置以下变量之前,不会有任何变化。
使用以下重定向 URI 向身份提供商注册一个机密客户端:
https://<your-keeper-origin>/api/auth/oauth2/callback/oidc
然后将客户端传递给运行 API 的容器(keeper-standalone、keeper-services 或 keeper-api):
environment:
OIDC_ISSUER_URL: https://id.example.com
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
OIDC_PROVIDER_NAME: Pocket ID
OIDC_ISSUER_URL 必须与身份提供商公布的 issuer 一致,包括任何路径。例如,Keycloak 的 https://auth.example.com/realms/keeper 或 Authentik 的 https://authentik.example.com/application/o/keeper/。OIDC_PROVIDER_NAME 用作登录按钮的名称,默认为 SSO。OIDC_SCOPES 是逗号分隔的列表,默认为 openid,profile,email。
首次使用身份提供商登录时,会创建一个新的 Keeper.sh 帐户,绝不会自动与现有帐户合并:如果身份提供商的电子邮件地址已属于某个 Keeper.sh 帐户,登录将被拒绝。若要将现有帐户改为使用 SSO,请按常规方式登录,然后在设置中选择 关联 SSO;之后,使用任一方式登录都会进入同一个帐户。
所有需要关联帐户的用户完成关联后,你可以关闭用户名和密码登录:
DISABLE_LOCAL_AUTH: "true"
此设置会在服务器端关闭用户名和密码登录及注册,而不仅仅是在界面上隐藏。Google 和 Microsoft 登录不受影响:只要设置了 GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET 或 MICROSOFT_CLIENT_ID/MICROSOFT_CLIENT_SECRET,就仍可使用;这些提供商的日历同步也需要相应配置。只有在 OIDC 客户端完成全部配置后,此设置才会生效,因此仅设置此项不会导致你无法登录。
独立容器
对于单实例部署,建议从 keeper-standalone:2 开始。此容器包含 cron、worker、web、api 服务,以及已配置的 redis、database 和 caddy 实例,所有服务都通过同一个端口提供访问。之后,如果需要独立扩展这些服务、运行自己的 Postgres 和 Redis,或将它们部署在不同主机上,再将服务拆分。
生成 keeper-standalone 环境变量
以下命令会生成一个 .env 文件,其中包含用于生成会话的密钥,以及用于静态加密 CalDAV 凭据的密钥。
重要
如果你计划通过 http://localhost 以外的 URL 访问 Keeper.sh, 则需要设置
TRUSTED_ORIGINS环境变量。其值应为你将使用的、以协议和主机名为完整内容的源,以逗号分隔。以下示例展示了如何通过局域网 IP 访问 Keeper.sh,以及如何通过反向代理将 Keeper.sh 部署在 https://keeper.example.com/
TRUSTED_ORIGINS=http://10.0.0.2,https://keeper.example.com如果不设置此变量,你将无法通过
better-auth软件包的 CSRF 检查。
cat > .env << EOF
# BETTER_AUTH_SECRET and ENCRYPTION_KEY are required.
# TRUSTED_ORIGINS is required if you plan on accessing Keeper.sh from an
# origin other than http://localhost/
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -base64 32)
TRUSTED_ORIGINS=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
EOF
使用 Docker 运行 keeper-standalone
如果你只想使用 Docker CLI 运行,可以使用以下命令。不过,我建议使用 compose.yaml 文件。
docker run -d \
-p 80:80 \
-v keeper-data:/var/lib/postgresql/data \
--env-file .env \
ghcr.io/ridafkih/keeper-standalone:2
使用 Docker Compose 运行 keeper-standalone
如果你更倾向于使用 compose.yaml 文件,以下是一个示例。记得先填好 .env 文件。
services:
keeper:
image: ghcr.io/ridafkih/keeper-standalone:2
ports:
- "80:80"
volumes:
- keeper-data:/var/lib/postgresql/data
environment:
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
TRUSTED_ORIGINS: ${TRUSTED_ORIGINS}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
volumes:
keeper-data:
配置完成后,你可以使用以下命令启动 Keeper.sh。
docker compose up -d
一切就绪后,你可以通过 http://localhost/ 访问 Keeper.sh。你可以使用 Nginx 或 Caddy 等反向代理,在网络中通过域名访问 Keeper.sh。
集合服务镜像
如果你想使用自己的 Redis 和 PostgreSQL,可以使用 keeper-services 镜像。该镜像将 cron、worker、web 和 api 服务整合在一起。
生成 keeper-services 环境变量
cat > .env << EOF
# DATABASE_URL and REDIS_URL are required.
# *_CLIENT_ID and *_CLIENT_SECRET are optional.
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -base64 32)
DATABASE_URL=postgres://keeper:keeper@postgres:5432/keeper
REDIS_URL=redis://redis:6379
BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
EOF
使用 Docker Compose 运行 keeper-services
填好环境变量后,你可以选择同时运行 redis 和 postgres,并搭配 keeper-services 镜像,以便快速启动。
services:
postgres:
image: postgres:17
command: ["postgres", "-c", "max_connections=200"]
environment:
POSTGRES_USER: keeper
POSTGRES_PASSWORD: keeper
POSTGRES_DB: keeper
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keeper -d keeper"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
keeper:
image: ghcr.io/ridafkih/keeper-services:2
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
postgres-data:
redis-data:
配置完成后,你可以使用以下命令启动 Keeper.sh。
docker compose up -d
单独的服务镜像
将每项服务放在各自的镜像中运行,可以最大程度地控制扩缩容和部署位置,但配置也会长得多。只有在 keeper-standalone 或 keeper-services 不再满足需求时,才应选择这种方式。
生成单独服务的环境变量
cat > .env << EOF
# The only optional variables are *_CLIENT_ID, *_CLIENT_SECRET
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -base64 32)
VITE_API_URL=http://api:3001
POSTGRES_USER=keeper
POSTGRES_PASSWORD=keeper
POSTGRES_DB=keeper
REDIS_URL=redis://redis:6379
BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
EOF
配置单独服务的 compose.yaml
services:
postgres:
image: postgres:17
command: ["postgres", "-c", "max_connections=200"]
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keeper -d keeper"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
api:
image: ghcr.io/ridafkih/keeper-api:2
environment:
API_PORT: 3001
DATABASE_URL: postgres://keeper:keeper@postgres:5432/keeper
REDIS_URL: redis://redis:6379
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
cron:
image: ghcr.io/ridafkih/keeper-cron:2
environment:
DATABASE_URL: postgres://keeper:keeper@postgres:5432/keeper
REDIS_URL: redis://redis:6379
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
WORKER_JOB_QUEUE_ENABLED: "true"
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
worker:
image: ghcr.io/ridafkih/keeper-worker:2
environment:
DATABASE_URL: postgres://keeper:keeper@postgres:5432/keeper
REDIS_URL: redis://redis:6379
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
web:
image: ghcr.io/ridafkih/keeper-web:2
environment:
VITE_API_URL: ${VITE_API_URL}
PORT: 3000
ports:
- "3000:3000"
depends_on:
api:
condition: service_started
volumes:
postgres-data:
redis-data:
配置完成后,你可以使用以下命令启动 Keeper.sh。
docker compose up -d
REST API
Keeper.sh 在 /api/v1 下提供 REST API。仪表板和 MCP 服务使用的也是同一套接口,因此 Agent 通过 MCP 能完成的操作,你都可以使用 curl 完成。
身份验证
在仪表板的设置 → API 令牌中创建 API 令牌。令牌以 kpr_ 为前缀,完整值只会在创建时返回一次。请将其作为 bearer token 传入。
curl https://keeper.example.com/api/v1/calendars \
-H "Authorization: Bearer kpr_..."
/api/v1 路由也接受已登录的浏览器会话或 MCP OAuth 访问令牌,因此这三类调用方都会使用相同的处理程序。令牌管理位于 /api/tokens,需要使用浏览器会话,而非 API 令牌。
端点
| Method | Path | Description |
|---|---|---|
GET | /api/v1/calendars | 列出已连接的日历。可选使用逗号分隔的 provider 筛选条件。 |
PATCH | /api/v1/calendars/{calendarId} | 暂停或恢复日历同步。将 paused 设为 true,即可在不取消连接的情况下暂停双向同步。 |
GET | /api/v1/calendars/{calendarId}/invites | 列出指定日期范围内尚未回复的日历邀请。 |
GET | /api/v1/accounts | 列出已连接的日历账户及各账户包含的日历数量。可选使用逗号分隔的 provider 筛选条件。 |
GET | /api/v1/events | 列出指定日期范围内的事件。支持 calendarId、availability 和 isAllDay 筛选条件,也可使用 count=true 仅返回数量。 |
POST | /api/v1/events | 创建事件。需要提供 calendarId、title、startTime 和 endTime。 |
GET | /api/v1/events/{id} | 获取单个事件。 |
PATCH | /api/v1/events/{id} | 更新事件字段,或发送 rsvpStatus 回复邀请。 |
DELETE | /api/v1/events/{id} | 删除事件。 |
GET | /api/v1/events/free-time | 在指定日期范围内查找时长至少为 durationMinutes 的空闲时段。需要提供 timezone,并支持工作时间选项。 |
POST | /api/v1/sync | 立即触发同步。每位用户每分钟最多请求一次。 |
GET | /api/v1/ical | 获取 iCal 订阅源的 URL。 |
范围参数 from 和 to 是 ISO 8601 日期时间。如果省略,from 默认为当前时间,to 默认为 from 一周后。范围不得超过 732 天。
/api/v1/events/free-time 将标记为有空或在其他地点工作的事件视为不占用时间,其他所有事件(包括全天事件)都视为繁忙;传入 ignoreAllDayEvents=true 可避免全天事件占用时间。workingHoursStart 和 workingHoursEnd 是 24 小时制的本地时间,例如 09:00;workingDays 是逗号分隔的列表,其中 0 表示星期日。三者都依据 timezone 解析,因此工作时间在夏令时切换期间也保持一致。
POST /api/v1/sync 会清除各数据源的摄取退避状态,使其在下一轮重新轮询,并将推送任务加入队列,发送到每个目标。超过频率限制时会返回带有 Retry-After 标头的 429,而不是将第二次运行加入队列。
说明
免费套餐的 API 每天最多可处理 25 个请求,超出后请求会返回
429。Pro 不限量运行,而未启用COMMERCIAL_MODE的自托管实例会被视为 Pro。
MCP(模型上下文协议)
Keeper.sh 包含一个可选的 MCP 服务,让 AI Agent(例如 Claude)通过标准化协议访问你的日历数据。MCP 服务通过 OAuth 2.1 进行身份验证,授权流程由 Web 应用托管。
可用工具
| 工具 | 描述 |
|---|---|
list_calendars | 列出所有已连接到 Keeper.sh 的日历,包括提供商名称和账户。 |
get_event_count | 获取日历事件数量。可通过 from 和 to ISO 8601 日期时间将范围限定在指定日期区间。 |
get_events | 获取指定日期范围内的日历事件。接受 ISO 8601 日期时间,以及用于本地化事件时间的 IANA 时区标识符。 |
get_event | 根据 ID 获取单个日历事件。 |
find_free_time | 在指定日期范围内,查找所有已同步日历中时长至少达到指定值的空闲时段。 |
create_event | 在已连接的日历上创建事件。需要提供日历 ID、标题、开始时间和结束时间。 |
update_event | 更新现有日历事件。只会更新你提供的字段。 |
delete_event | 根据 ID 删除日历事件。 |
get_pending_invites | 获取指定日期范围内尚未回复的日历邀请。 |
rsvp_event | 回复日历事件邀请,可选择 accepted、declined 或 tentative。 |
list_accounts | 列出所有已连接的日历账户及其提供商信息。 |
trigger_sync | 立即强制同步,无需等待下一次计划运行。每分钟最多接受一个请求。 |
pause_sync | 暂停或恢复单个日历的同步,而不将其断开连接。 |
get_ical_feed | 获取你的 iCal 订阅源 URL,以便在其他日历应用中订阅。 |
连接 MCP 客户端
要连接兼容 MCP 的客户端(例如 Claude Code、Claude Desktop),请将其指向你的 MCP 服务 URL。客户端会引导你完成 OAuth 授权流程,以授权其读取和写入日历数据——工具集不仅能读取事件,还能创建、更新、删除事件、回复邀请、查找空闲时段,以及暂停或强制同步。
Claude Code MCP 配置示例:
{
"mcpServers": {
"keeper": {
"type": "url",
"url": "https://keeper.example.com/mcp"
}
}
}
自托管 MCP 设置
说明
MCP 完全是可选功能。所有服务和镜像中的 MCP 相关环境变量均为可选项。如果未设置这些变量,Keeper.sh 会正常启动,但不提供 MCP 功能。现有的自托管部署不会受到影响。
MCP 服务通过 Web 服务的 /mcp 路径进行代理,方式与 API 通过 /api 进行代理相同。
keeper-standalone 内置 MCP 服务,并在你已发布的端口上通过 /mcp 提供服务,无需额外配置。它会从 BETTER_AUTH_URL 派生 MCP_PUBLIC_URL,因此只要 BETTER_AUTH_URL 是你的实例 URL,MCP 客户端就可以使用在同一 URL 后附加 /mcp 的地址。
keeper-services 和各个独立服务镜像不内置 MCP。要在其中启用 MCP:
- 运行
keeper-mcp容器,并设置MCP_PORT、MCP_PUBLIC_URL、DATABASE_URL、BETTER_AUTH_SECRET和BETTER_AUTH_URL。 - 在
api服务上将MCP_PUBLIC_URL设置为相同的值(例如,https://keeper.example.com/mcp)。 - 在
web服务上将VITE_MCP_URL设置为 MCP 容器的内部 URL(例如,http://mcp:3002)。
注册表清单
仓库根目录中的 server.json 描述了面向 官方 MCP 注册表 的托管服务。它包含一个指向 https://www.keeper.sh/mcp 的 streamable-http 远程服务,并采用 sh.keeper 命名空间;该命名空间通过证明对 keeper.sh 的所有权进行验证,而非通过 GitHub 账号验证。
发布是使用 mcp-publisher CLI 手动执行的步骤,特意不设为自动化流程:已发布的版本无法撤回,因此清单会先在此处审核,其中的 version 也永远不能重复使用。自托管实例不需要此文件,因为它只描述托管实例。
由于注册条目是永久的,只有在其中列出的所有 URL 都已在生产环境中可访问后才发布,包括 websiteUrl、两个 icons[].src 和 streamable-http 远程服务。若 websiteUrl 指向的页面尚未上线,该 URL 就会一直返回 404,直到下次提升版本号。
模块
应用程序
服务
模块
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。