mcpField
K
Keeper.sh

Keeper.sh

远程服务
keeper.sh其他1.4k今天更新

关于 Keeper.sh

Keeper.sh 是用于同步多个日历的开源工具,可连接 Google Calendar、Outlook、iCloud、Fastmail、CalDAV 及 iCal/ICS 来源。它面向需要跨日历查看和协调事件的个人与 Agent,并提供统一 API 和 MCP 服务。

主要功能

  1. 01连接多种日历来源与目标
  2. 02汇总事件并推送至多个日历
  3. 03查找并同步日历事件
  4. 04按来源移除事件详情
  5. 05提供 REST API 与 iCal 订阅源

适用场景

  1. 01跨工作与个人日历同步事件
  2. 02将一个日历的事件分发到多个目标
  3. 03清理已从来源删除的远程事件

MCP 相关

Keeper.sh 提供 MCP 服务,供 AI Agent 访问和管理日历。客户端可通过 MCP 读取、预订事件、回复邀请、查找空闲时间,并触发同步。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://www.keeper.sh/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

详细介绍

README 配图

关于

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(v1.3.11+)
  • Docker 和 Docker Compose
开始使用
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。

架构
服务本地端口访问方式
Caddy443https://keeper.localhost
Web5173由 Caddy 代理
API3000由 Web 在 /api 代理
MCP3001由 Web 在 /mcp 代理
Postgres5432postgresql://postgres:postgres@localhost:5432/postgres
Redis6379redis://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_URLapi, cron, worker, mcpPostgreSQL 连接 URL。

例如:postgres://user:pass@postgres:5432/keeper
REDIS_URLapi, cron, workerRedis 连接 URL。所有服务必须使用同一个 Redis 实例。

例如:redis://redis:6379
DATABASE_POOL_MAXapi, cron, worker, mcp可选。服务连接池打开的 Postgres 连接数上限。默认每个服务为 10;keeper-standalone 和 keeper-services 镜像附带的每服务值较小,适配其所连接的 Postgres。每个共置服务都会添加自己的连接池,因此这些值与 CRON_FLUSH_POOL_MAX 的总和必须低于 Postgres 的 max_connections,并留出足够空间供你自行连接。

例如:15
CRON_FLUSH_POOL_MAXcron可选。cron 为写入导入数据而持有的第二个连接池大小,与 DATABASE_POOL_MAX 分开且额外计算。默认值为 16;附带的镜像为 8。

例如:16
RUN_MIGRATIONSapi可选。设为 false,可在 API 容器启动时跳过数据库迁移,适用于在滚动部署服务前单独运行迁移的部署方式。默认值为 true。

例如:false
WORKER_JOB_QUEUE_ENABLEDcron必需。设为 true,将同步任务加入 worker 队列。设为 false 会阻止 cron 服务将目标同步任务加入队列,因此仍会导入源事件,但不会将其推送到目标日历。若未设置,cron 服务将显示迁移提示后退出。
WORKER_CONCURRENCYworker可选。worker 并发处理的同步任务数。默认值为 25。
BETTER_AUTH_URLapi, mcp用于身份验证重定向的基础 URL。

例如:http://localhost:3000
BETTER_AUTH_SECRETapi, mcp用于会话签名的密钥。

例如:openssl rand -base64 32
API_PORTapi必需。Bun API 监听的端口。在 keeper-standalone 和 keeper-services 镜像中预设为 3001。
ENVweb可选。运行时环境。可选值为 development、production 或 test。默认值为 production。
PORTweb必需。Web 服务器监听的端口。在 keeper-standalone 和 keeper-services 镜像中预设为 3000。
VITE_API_URLwebWeb 服务器用于将请求代理到 Bun API 的 URL。

例如:http://api:3001
COMMERCIAL_MODEapi, cron, mcp, web启用 Polar 计费流程。使用 Polar 处理订阅时设为 true。
POLAR_ACCESS_TOKENapi, cron可选。用于管理订阅的 Polar API 令牌。
POLAR_MODEapi, cron可选。Polar 环境,可设为 sandbox 或 production。
POLAR_WEBHOOK_SECRETapi可选。用于验证 Polar webhook 的密钥。
ENCRYPTION_KEYapi, cron, worker用于加密静态存储的 CalDAV 凭据的密钥。

例如:openssl rand -base64 32
RESEND_API_KEYapi可选。用于通过 Resend 发送电子邮件的 API 密钥。
FEEDBACK_EMAILapi可选。应用内反馈提交内容发送至的电子邮件地址。需要设置 RESEND_API_KEY。
PASSKEY_RP_IDapi可选。通行密钥身份验证的信赖方 ID。
PASSKEY_RP_NAMEapi可选。通行密钥的信赖方显示名称。
PASSKEY_ORIGINapi可选。通行密钥流程允许的来源(例如:https://keeper.example.com)。
GOOGLE_CLIENT_IDapi, cron, worker可选。Google 日历集成所必需。
GOOGLE_CLIENT_SECRETapi, cron, worker可选。Google 日历集成所必需。
MICROSOFT_CLIENT_IDapi, cron, worker可选。Microsoft Outlook 集成所必需。
MICROSOFT_CLIENT_SECRETapi, cron, worker可选。Microsoft Outlook 集成所必需。
OIDC_ISSUER_URLapi可选。符合 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_IDapi可选。在 OIDC 颁发者处注册的 OAuth 客户端 ID。
OIDC_CLIENT_SECRETapi可选。在 OIDC 颁发者处注册的 OAuth 客户端密钥。
OIDC_PROVIDER_NAMEapi可选。SSO 登录按钮上显示的名称。默认值为 SSO。

例如:Pocket ID
OIDC_SCOPESapi可选。向 OIDC 颁发者请求的逗号分隔范围。默认值为 openid,profile,email。

例如:openid,profile,email,groups
DISABLE_LOCAL_AUTHapi可选。设为 true,关闭用户名及电子邮件/密码登录和注册。只要设置了 Google 和 Microsoft 的客户端凭据,其登录方式仍可使用。仅当 OIDC_ISSUER_URL、OIDC_CLIENT_ID 和 OIDC_CLIENT_SECRET 均已设置时才生效。
POSTGRES_PASSWORDstandalone可选。keeper-standalone 中内部 PostgreSQL 数据库的自定义密码。若未设置,默认值为 keeper。数据库不会暴露到容器外,因此风险较低,但仍可设置密码以增加纵深防御。
BLOCK_PRIVATE_RESOLUTIONapi, cron, worker可选。设为 true,阻止出站请求(ICS 订阅、CalDAV 服务器)解析到私有或保留网络地址。可防止 SSRF。为兼容使用本地 CalDAV/ICS 服务器的可自托管配置,默认值为 false。
BLOCK_PRIVATE_RESOLUTIONapi, cron可选。设为 true,阻止出站请求(ICS 订阅、CalDAV 服务器)解析到私有或保留网络地址。可防止 SSRF。为兼容使用本地 CalDAV/ICS 服务器的可自托管配置,默认值为 false。
REGISTRATION_EMAIL_DOMAINSapi可选。允许创建账户的逗号分隔电子邮件域名列表。设置后,来自其他域名的注册和首次登录都会被拒绝;现有账户不受影响。未设置或为空时允许所有域名。

例如:keeper.sh,example.com
TRUSTED_ORIGINSapi可选。用于 CSRF 防护的额外受信任来源,以逗号分隔。

例如:http://192.168.1.100,http://keeper.local,https://keeper.example.com
WEBHOOK_PUBLIC_URLapi, cron可选。Google 日历和 Microsoft Graph 可访问你实例的公共 HTTPS 来源。设置后会启用实时推送,因此 Keeper.sh 能在几秒内获取日历变更,而不必等待下一次轮询。若不设置则一切保持不变:轮询照常进行,且不会向提供商注册任何订阅。所有套餐都会注册订阅;免费套餐中,五分钟内收到的通知会合并为一次读取,目标仍按免费套餐的 30 分钟周期更新。必须是没有查询字符串或片段的公共 https:// 来源;启动时会拒绝 localhost、私有范围和 .local 地址,因为提供商无法向这些地址投递通知。

例如:https://keeper.example.com
CRON_JOBS_ENABLEDcron可选。设为 false,使此进程仅运行导入循环,适用于在运行定时任务的容器之外增加额外 cron 容器以提升导入能力。默认值为 true。
INGEST_LOOP_ENABLEDcron可选。设为 false,阻止此进程读取源日历。默认值为 true。可同时运行任意数量的进程:每个进程都会认领到期的日历并持有租约,因此不会重复读取日历。
INGEST_FLEET_CONCURRENCYcron一个进程按常规计划同时读取的日历数。默认值为 64。
INGEST_WAKE_CONCURRENCYcron推送通知、同步请求或登录后,一个进程同时读取的日历数。与常规计划分开处理,因此此类积压永远不会造成延迟。默认值为 16。
INGEST_CLAIM_BATCHcron一个进程每次查询最多认领的日历数。默认值为 50。
INGEST_FLEET_IDLE_MScron没有到期日历时,循环等待多久后再次检查。默认值为 2000。
INGEST_WAKE_IDLE_MScron推送通知及其他唤醒事件的相同等待时长。默认值为 1000。
INGEST_LEASE_SECONDScron被认领的日历由认领进程持有的时长。若进程退出,该日历会在租约到期后再次读取。默认值为 900。
INGEST_PRO_POLL_SECONDScron没有正常推送订阅覆盖时,读取 Pro 日历的频率。默认值为 60。
INGEST_FREE_POLL_SECONDScron免费日历的相同间隔。默认值为 600。
INGEST_PRO_SAFETY_POLL_SECONDScron即使有正常推送订阅,也会读取 Pro 日历的频率,以捕获提供商丢失的通知。默认值为 300。
INGEST_FREE_SAFETY_POLL_SECONDScron免费日历的相同安全轮询间隔。默认值为 3600。
INGEST_DORMANT_POLL_SECONDScron读取休眠日历的频率:休眠日历指不为任何目标或订阅源提供数据,且其所有者在 INGEST_DORMANT_AFTER_DAYS 期间未登录或使用 API 的日历。登录后会立即读取。默认值为 86400。
INGEST_DORMANT_AFTER_DAYScron日历被视为休眠前的未活跃天数。设为 0,则永不将日历视为休眠。默认值为 30。
GOOGLE_PROJECT_REQUESTS_PER_MINUTEapi, cron, worker可选。整个 Google Cloud 项目每分钟最多发出的 Google 日历请求数。后台工作(读取源日历、写入目标、注册推送通道、重新发现日历)最多使用其 90%;其余部分留给用户正在等待的请求,例如连接账户或通过 API 创建事件,这类请求会优先于排队的后台工作。默认值为 10000,即 Google 公布的每项目限额;若项目配额不同,请设置为相应值。设为 0,则除 Google 的每用户限额外不设项目级限额。
OUTLOOK_INGEST_REQUESTS_PER_MINUTEcron可选。所有读取日历的进程每分钟最多发出的 Microsoft Graph 请求数。Microsoft Graph 对每个邮箱的 Outlook 请求设有限额,Keeper.sh 已会相应地控制请求速率,因此默认不设置此项,表示不设限。
WEBSOCKET_URLapi可选。客户端应连接实时套接字的外部 URL。未设置时,客户端会连接 API 自身的 /api/socket 路径。

例如:wss://socket.keeper.example.com
MCP_PUBLIC_URLapi, mcpapi 可选,mcp 必需。MCP 资源的公共 URL。启用 API 上的 OAuth,并向客户端标识 MCP 服务。在 keeper-standalone 中,默认值为在 BETTER_AUTH_URL 后附加 /mcp。

例如:https://keeper.example.com/mcp
VITE_MCP_URLweb可选。Web 服务器用于将 /mcp 请求代理到 MCP 服务的内部 URL。

例如:http://mcp:3002
OPENAI_APPS_CHALLENGE_TOKENweb可选。来自 OpenAI Platform 插件提交流程的域名验证令牌,通过 /.well-known/openai-apps-challenge 提供。仅在将 MCP 服务提交到 ChatGPT Plugins Directory 时需要。

例如:abc123
MCP_PORTmcpmcp 必需。MCP 服务监听的端口。在 keeper-standalone 镜像中预设为 3002。

例如:3002
MCP_API_URLapi, mcp可选。提供 MCP 服务时用于访问 Keeper.sh API 的内部 URL,用于调用工具以及获取验证 MCP 令牌的签名密钥。默认值为 BETTER_AUTH_URL,这要求两个服务都能访问你实例的公共 URL。在 keeper-standalone 镜像中预设为捆绑的 API。

例如:http://api:3001
OTEL_EXPORTER_OTLP_ENDPOINTapi, cron, worker, mcp, web可选。设置后,启用将结构化日志转发到 OpenTelemetry 收集器。每个服务都通过 @keeper.sh/otelemetry 中的 keeper-otelemetry 二进制文件运行;该文件会将服务作为子进程启动、转发其输出和停止信号,且不会影响应用性能。

例如:https://otel-collector.example.com:4318
OTEL_EXPORTER_OTLP_PROTOCOLapi, cron, worker, mcp, web可选。OTLP 导出器使用的协议。根据 OpenTelemetry 规范,默认值为 http/protobuf。

例如:http/protobuf、grpc、http/json
OTEL_EXPORTER_OTLP_HEADERSapi, 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

TagDescriptionIncluded 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 令牌。

端点

MethodPathDescription
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:

  1. 运行 keeper-mcp 容器,并设置 MCP_PORT、MCP_PUBLIC_URL、DATABASE_URL、BETTER_AUTH_SECRET 和 BETTER_AUTH_URL。
  2. 在 api 服务上将 MCP_PUBLIC_URL 设置为相同的值(例如,https://keeper.example.com/mcp)。
  3. 在 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,直到下次提升版本号。

模块

应用程序

  1. @keeper.sh/web

服务

  1. @keeper.sh/api
  2. @keeper.sh/cron
  3. @keeper.sh/mcp
  4. @keeper.sh/worker

模块

  1. @keeper.sh/auth
  2. @keeper.sh/broadcast
  3. @keeper.sh/calendar
  4. @keeper.sh/constants
  5. @keeper.sh/data-schemas
  6. @keeper.sh/database
  7. @keeper.sh/digest-fetch
  8. @keeper.sh/fixtures
  9. @keeper.sh/otelemetry
  10. @keeper.sh/premium
  11. @keeper.sh/queue
  12. @keeper.sh/sync
  13. @keeper.sh/typescript-config

评论

支持 Markdown

第一次接触 MCP?

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

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