Keeper.sh
Open-source calendar sync tool & universal calendar MCP server. Aggregate, sync and control calendars on Google, Outlook, Office 365, iCloud, CalDAV or ICS.
Install / Use
/learn @ridafkih/Keeper.shQuality Score
Category
Development & EngineeringSupported Platforms
README

About
Keeper is a simple & open-source calendar syncing tool. It allows you to pull events from remotely hosted iCal or ICS links, and push them to one or many calendars so the time slots can align across them all.
Features
- Aggregating calendar events from remote sources
- Event content agnostic syncing engine
- Push aggregate events to one or more calendars
- MCP (Model Context Protocol) server for AI agent calendar access
- Open source under AGPL-3.0
- Easy to self-host
- Easy-to-purge remote events
Bug Reports & Feature Requests
If you encounter a bug or have an idea for a feature, you may open an issue on GitHub and it will be triaged and addressed as soon as possible.
Contributing
High-value and high-quality contributions are appreciated. Before working on large features you intend to see merged, please open an issue first to discuss beforehand.
Local Development
The dev environment runs behind HTTPS at https://keeper.localhost using a Caddy reverse proxy with automatic TLS. The .localhost TLD resolves to 127.0.0.1 automatically per RFC 6761 — no /etc/hosts entry is needed.
Prerequisites
Getting Started
bun install
Generate and Trust a Root CA
The dev environment runs behind HTTPS via Caddy. You need to generate a local root certificate authority and trust it so your browser accepts the certificate.
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"
Then trust it on your platform:
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
Start the Dev Environment
bun dev
This starts PostgreSQL, Redis, and a Caddy reverse proxy via Docker Compose, along with the API, web, MCP, and cron services locally. Once running, open https://keeper.localhost.
Architecture
| Service | Local Port | Accessed Via |
| -------- | ---------- | ------------------------------------ |
| Caddy | 443 | https://keeper.localhost |
| Web | 5173 | Proxied by Caddy |
| API | 3000 | Proxied by Web at /api |
| MCP | 3001 | Proxied by Web at /mcp |
| Postgres | 5432 | postgresql://postgres:postgres@localhost:5432/postgres |
| Redis | 6379 | redis://localhost:6379 |
Qs
Why does this exist?
Because I needed it. Ever since starting Sedna—the AI governance platform—I've had to work across three calendars. One for my business, one for work, and one for personal.
Meetings have landed on top of one-another a frustratingly high number of times.
Why not use this other service?
I've probably tried it. It was probably too finicky, ended up making me waste hours of my time having to delete stale events it didn't seem to want to track anymore, or just didn't sync reliably.
How does the syncing engine work?
- If we have a local event but no corresponding "source → destination" mapping for an event, we push the event to the destination calendar.
- If we have a mapping for an event, but the source ID is not present on the source any longer, we delete the event from the destination.
- Any events with markers of having been created by Keeper, but with no corresponding local tracking, we remove it. This is only done for backwards compatibility.
Events are flagged as having been created by Keeper either using a @keeper.sh suffix on the remote UID, or in the case of a platform like Outlook that doesn't support custom UIDs, we just put it in a "keeper.sh" category.
Considerations
- Keeper tracks timeslots, not event details, summaries, descriptions, etc., for now. If you need that I would recommend OneCal.
- Keeper only sources from remote and publicly available iCal/ICS URLs at the moment, so that means that if your security policy does not permit these, another solution may suit you better.
- The MCP server provides read-only access to calendar data. AI agents can list calendars and query events but cannot create, modify, or delete them.
Cloud Hosted
I've made Keeper easy to self-host, but whether you simply want to support the project or don't want to deal with the hassle or overhead of configuring and running your own infrastructure cloud hosting is always an option.
Head to keeper.sh to get started with the cloud-hosted version. Use code README for 25% off.
| | Free | Pro (Cloud-Hosted) | Pro (Self-Hosted) | | --------------------- | ---------- | ------------------ | ----------------- | | Monthly Price | $0 USD | $5 USD | $0 | | Annual Price | $0 USD | $42 USD (-30%) | $0 | | Refresh Interval | 30 minutes | 1 minute | 1 minute | | Source Limit | 2 | ∞ | ∞ | | Destination Limit | 1 | ∞ | ∞ |
Self Hosted
By hosting Keeper yourself, you get all premium features for free, can guarantee data governance and autonomy, and it's fun. If you'll be self-hosting, please consider supporting me and development of the project by sponsoring me on GitHub.
There are seven images currently available, two of them are designed for convenience, while the five are designed to serve the granular underlying services.
[!NOTE]
Migrating from a previous version? If you are upgrading from the older Next.js-based release, see the migration guide for environment variable changes. The new web server will also print a migration notice at startup if it detects old environment variables.
Environment Variables
| Name | Service(s) | Description |
| ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| DATABASE_URL | api, cron, worker, mcp | PostgreSQL connection URL.<br><br>e.g. postgres://user:pass@postgres:5432/keeper |
| REDIS_URL | api, cron, worker | Redis connection URL. Must be the same Redis instance across all services.<br><br>e.g. redis://redis:6379 |
| WORKER_JOB_QUEUE_ENABLED | cron | Required. Set to true to enqueue sync jobs to the worker queue, or false to disable. If unset, the cron service will exit with a migration notice. |
| BETTER_AUTH_URL | api, mcp | The base URL used for auth redirects.<br><br>e.g. http://localhost:3000 |
| BETTER_AUTH_SECRET | api, mcp | Secret key for session signing.<br><br>e.g. openssl rand -base64 32 |
| API_PORT | api | Port the Bun API listens on. Defaults to 3001 in container images. |
| ENV | web | Optional. Runtime environment. One of development, production, or test. Defaults to production. |
| PORT | web | Port the web server listens on. Defaults to 3000 in container images. |
| VITE_API_URL | web | The URL the web server uses to proxy requests to the Bun API.<br><br>e.g. http://api:3001 |
| COMMERCIAL_MODE | api, cron | Enable Polar billing flow. Set to true if using Polar for subscriptions. |
| POLAR_ACCESS_TOKEN | api, cron | Optional. Polar API token for subscription management. |
| POLAR_MODE | api, cron | Optional. Polar environment, sandbox or production. |
| POLAR_WEBHOOK_SECRET | api | Optional. Secret to verify Polar webhooks. |
| ENCRYPTION_KEY | api, cron, worker | Key for encrypting CalDAV credentials at rest.<br><br>e.g. openssl rand -base64 32 |
| RESEND_API_KEY | api | Optional. API key for sending emails via R
