caldav-mcp
ローカルLLMから、複数アカウントの予定・タスク・共有カレンダーを操作するMCPサーバです。CalDAVへ直接アクセスし、ローカルDB、キャッシュ、バックグラウンド同期を持ちません。接続方式はstdioです。
例えば、次のような依頼をMCPホスト上のLLMから実行できます。
「個人用と仕事用の来週の予定を調べて」
list_calendars → search_events
「買い物タスクを完了にして」
search_tasks → get_item → complete_task
「来週の定例会だけ、1時間遅らせて」
search_events → get_item → update_item(scope=occurrence)
「自分のカレンダーをbobに読み取り専用で共有して」
create_share → bobのアカウントでupdate_share(enabled=true, hidden=false)
導入
GitHub ReleasesからLinux用のビルド済みバイナリを取得できます。x86_64は caldav-mcp-x86_64-unknown-linux-musl.tar.gz、ARM64は caldav-mcp-aarch64-unknown-linux-musl.tar.gz を選びます。muslによる静的リンクのため、実行時にRust・Pythonや特定バージョンのglibcは必要ありません。
アーカイブと同じリリースの SHA256SUMS をダウンロードし、sha256sum --check --ignore-missing SHA256SUMS で検証してから展開します。同梱の config.example.toml を編集し、MCPホストのコマンドに展開した caldav-mcp の絶対パスを指定します。
Agent Plugins対応クライアントへ導入
このリポジトリはAgent Plugins v1.0.0形式のpluginです。ルートの plugin.json がpluginを識別し、mcp.json がstdio MCPサーバの起動方法を定義します。この形式のstdioに対応するクライアントと、PATHから実行できる uv が必要です。
mcp.json は uv run で plugin/launch.py を起動します。この補助スクリプトは設定と認証情報を読み、Rust製の caldav-mcp 本体へプロセスを置き換えます。Python 3.11以降と、TLS検証用のCA証明書を提供するcertifiはuvが用意します。起動用の依存関係は plugin/launch.py.lock で固定します。Linux x86_64/ARM64では、初回接続時に Cargo.toml と同じバージョンの公開バイナリと SHA256SUMS をGitHubから取得・検証し、以降は保存済みバイナリを使用します。
クライアントがplugin専用に割り当てる永続ディレクトリ PLUGIN_DATA に、次のファイルを配置します。実際の場所はクライアントの案内で確認してください。設定がない状態で起動すると、標準エラーにも config.toml の配置先が表示されます。
config.toml: 設定例をコピーし、アカウントやURLを編集します。credentials.json:password_envまたはbearer_token_envに指定した環境変数名をキーにした認証情報です。launcher.toml: 自前のバイナリを使う場合だけ作成する起動設定です。
credentials.json の例です。アカウント数や変数名は自由に設定できます。このファイルは本人だけが読み書きできる権限にします。
{
"CALDAV_PERSONAL_PASSWORD": "your-personal-password",
"CALDAV_WORK_PASSWORD": "your-work-password"
}
自前のバイナリを使う場合の launcher.toml です。指定時はダウンロードを行わず、Linux以外でもそのOS用のバイナリを使用できます。
binary = "/absolute/path/to/caldav-mcp"
PLUGIN_ROOT と PLUGIN_DATA はAgent Pluginsの標準変数です。設定・認証情報・取得済みバイナリは PLUGIN_DATA に置くため、plugin更新後も保持されます。認証情報は起動スクリプトから本体へ環境変数として渡します。設定ファイルの読み込みはこのpluginの起動処理が担当します。
Hermes Agentでの導入例
Agent Plugins対応のHermes Agentでは、次のコマンドでインストール・有効化します。
hermes plugins install ROCKTAKEY/caldav-mcp --no-enable
hermes plugins enable caldav-mcp
このpluginに割り当てられた PLUGIN_DATA に上記ファイルを設定し、gatewayを再起動して新しいセッションを開きます。config.yaml にも mcp_servers.caldav がある場合はそちらが優先されるため、pluginから起動する場合は重複した設定を取り除いてください。
更新は hermes plugins update caldav-mcp で行います。インストール・有効化のコマンドや保存先の管理はクライアント固有の機能です。
ソースからビルド
Rustのstable toolchainとCargoを使用します(Rust 1.98.1で検証)。リポジトリのルートで実行します。実行時にPythonは必要ありません。
cargo build --release --locked
cp examples/config.toml config.toml
設定例のURL、ユーザー名、アカウントIDを変更します。パスワードは、MCPホストから子プロセスへ引き継ぐ環境変数に設定します。password_env は値ではなく環境変数名です。認証情報をツールの引数としてLLMに渡す必要はありません。
./target/release/caldav-mcp --config /absolute/path/to/config.toml
これは対話式CLIではなく、標準入力からMCPメッセージを受け取るプロセスです。通常はMCPホストから起動します。--help で起動引数を確認できます。
MCPホストの設定例です。コマンドや設定ファイルのパスは実際の配置に合わせてください。
{
"mcpServers": {
"caldav": {
"command": "/absolute/path/to/caldav-mcp/target/release/caldav-mcp",
"args": ["--config", "/absolute/path/to/config.toml"]
}
}
}
アカウントとカレンダー
accounts に複数の接続設定を列挙します。同じRadicaleの別ユーザーと、別サーバのアカウントをどちらも登録できます。
idはMCP操作で使う一意の識別子です。urlはリダイレクトを必要としないCalDAVの接続URLです。通常は末尾を/にします。usernameとpassword_envはBasic認証用です。Bearer認証を使う場合は、代わりにbearer_token_envを指定します。calendarsには、自動検出されない共有カレンダーの絶対URLを追加できます。default_calendarはLLMに提示する既定の保存先候補です。書き込み時には常にcalendarを明示します。sharing_urlはRadicaleの共有APIのURLです。共有管理を使う場合に設定します。timeout_secondsはHTTPタイムアウトで、既定値は30秒です。
アカウント内のURLは同一オリジンに限定します。別ホストのカレンダーは別アカウントとして設定してください。TLS証明書検証は有効です。
timezone は利用者のタイムゾーンです。現在日時の表示、新規カレンダーの既定タイムゾーン、検索時に指定するfloating timeの解釈に使用します。個々の予定・タスクでは、日付のみ・UTC・タイムゾーン付き・floatingを明示的に区別します。
利用できる操作
23個のツールを公開します。引数のJSON Schemaは tools/list から取得できます。操作例と入力形式はツールの使い方を参照してください。
- アカウント一覧、カレンダー一覧・詳細・作成・属性変更・削除。
- 複数カレンダーを横断する予定・タスク・日誌の検索と詳細取得。
- 予定・タスク・日誌の作成・更新・削除、タスクの完了・再開。
- 終日・タイムゾーン・繰り返し規則・追加日・除外日・個別回の変更と削除。
- タスクの期限・進捗・優先度・分類・親子関係、アラーム、URI添付。
- サーバによる空き時間照会と、選択したカレンダーすべてで空いている時間の取得。
- Radicaleユーザー間の共有作成、受け入れ、権限変更、非表示・無効化、解除。
アラームはDISPLAY形式の相対時刻と絶対UTC時刻を設定できます。添付はURIを記録します。実通知の配信や添付ファイルのアップロードは行いません。既存の参加者、未編集のアラーム、独自プロパティは保持します。
Radicaleの共有設定
通常の読み書きはCalDAVを使います。共有相手の追加・権限変更・解除は、Radicale 3.7以降の共有専用APIを使います。サーバ側で利用者への共有作成を許可する必要があります。設定例は次のとおりです。
[sharing]
type = files
collection_by_map = True
permit_create_map = True
permit_create_map を全体で有効にせず、rights設定の M 権限で対象を限定することもできます。既存サーバの設定はこのMCPサーバから変更しません。Radicaleの共有設定を参照してください。
共有を作成しただけでは相手側で有効になりません。相手のアカウントから list_shares で確認し、update_share に enabled=true と hidden=false を渡すと、カレンダー一覧に現れます。共有先パスは /bob/team/ のようなRadicale内部のパスです。
共有APIがないバージョンや共有機能が無効な環境では、共有管理ツールは unsupported を返します。rightsファイルで既に許可されているカレンダーは、URLを設定して読み書きできます。トークンによる公開共有、グループ共有の作成、rightsファイルの編集は提供しません。
更新・検索の意味
更新・削除には、取得結果の ref と revision を使います。ETagが変わっていれば conflict となるため、改めて取得して変更内容を判断してください。作成に使うUIDは再試行時も同じ値を使います。
更新の引数を省略すると、その項目は保持されます。null は削除を意味します。配列項目を指定するとその項目全体を置換します。
series は基本コンポーネントを編集し、既存の個別例外を保持します。occurrence は元の recurrence_id で特定した1回を編集します。時間を移動するときは終了時刻・期限も明示的に調整してください。
検索・空き時間の期間評価は接続先サーバが行います。Radicale 3.9.0のfree-busy処理は日付のみ・floatingの値をUTCとして扱います。特定の地域の時刻に合わせた空き時間が必要な予定には、タイムゾーン付き日時を指定してください。
編集範囲は series と occurrence です。Radicale 3.9.0では、保存済みの RANGE=THISANDFUTURE がGETに残る一方、展開REPORTの後続回に変更時刻が反映されない不具合を確認しています。検索の expand=true はこのサーバ側の計算結果を返します。範囲例外のあるシリーズから新しい個別例外を作る操作は unsupported として扱います。
検索結果の complete、errors、truncated を確認してください。一部の接続失敗は空のカレンダーとは区別されます。空き時間照会で一部のカレンダーを取得できなければ、全体の free は null です。件数超過時は期間や検索条件を絞ってください。検索件数は最大500件、HTTP応答は展開後16 MiBまでです。
通信断で書き込み結果を確定できない場合は outcome_unknown、書き込み成功後の再取得だけに失敗した場合は readback_failed を返します。対象を読み直してから次の操作を判断してください。
対応確認と開発
Radicale 3.9.0を一時起動し、2ユーザーで共有・予定・タスク・空き時間照会を検証しています。MCPのstdio接続についても公式SDKのクライアントで初期化と操作を試験します。実際のローカルLLMによるツール選択精度と、利用者の稼働中サーバとの接続は別途確認が必要です。
試験用RadicaleのPython環境を、uvでリポジトリ内に用意します。
uv venv --python 3.15 .venv
uv pip install --python .venv/bin/python -r tests/requirements.txt
cargo test --locked --all-targets
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo build --release --locked
pluginの起動・認証情報の受け渡し・配布物検証は、uvでcertifiを用意してテストします。
uv run --no-project --python '>=3.11' --with certifi -m unittest discover -s tests -p test_plugin.py
既存のRadicale環境を使う場合は RADICALE_PYTHON にそのPython実行ファイルを指定します。Radicaleを使わない検証は cargo test --locked --lib --test mcp_stdio で実行できます。
Guix環境では manifest.scm と scripts/rust.sh を使えます。
scripts/rust.sh cargo test --locked --all-targets
scripts/rust.sh cargo clippy --locked --all-targets -- -D warnings
scripts/rust.sh cargo fmt --all -- --check
scripts/rust.sh cargo build --release --locked
Rustの依存関係は Cargo.lock で固定します。Guix用スクリプトはクレートのダウンロードを .guix-shell/cargo に保持します。これは開発用の依存ファイルであり、カレンダーのキャッシュではありません。
テストは一時ディレクトリ内のRadicaleのみを操作します。利用者の設定や実サーバは読み込みません。
CIとリリース
CIはpush・pull request・手動実行で、workflow依存の検証、fmt、Clippy、全テスト、リリースビルドを実行します。Ubuntu 26.04上で、独立した検査とビルドを並列実行します。Python 3.15と試験用の依存関係はuvで用意し、Radicale 3.9.0をCI内の一時サーバとして起動します。
Actionの実行コミットはGitHub Actions標準のactions.lockで固定します。workflowの uses を変更したときは、公式CLIでlockファイルを再生成して一緒にコミットしてください。Dependabotもworkflowとlockファイルをまとめて更新します。
gh extension install github/gh-actions-lock --pin v0.4.1
gh actions-lock
gh actions-lock --verify
tagprは main へのpushでリリースPRを作成・更新します。.tagprに従って Cargo.toml と Cargo.lock のバージョン、CHANGELOGを更新し、リリースPRのマージ後に v 接頭辞のタグとGitHub Releaseを作成します。GITHUB_TOKENが作ったPRでもCIを実行できるよう、tagprから明示的にCIの workflow_dispatch を呼び出します。リポジトリのActions設定では「Allow GitHub Actions to create and approve pull requests」を有効にします。
設計では、型付き入力、更新の状態、機能ごとの責務を説明しています。ライセンスはAGPL-3.0です。
バイナリ配布CDは、tagprのタグ公開後にLinux x86_64/ARM64を並列ビルドし、起動・バージョン・静的リンクを確認してからアーカイブと SHA256SUMS をGitHub Releaseに添付します。PRではビルドとパッケージ検証までを実行します。既存リリースへ添付する場合は、Release binaries ワークフローをmainから実行し、tag に公開済みタグを指定します。同名の配布物は上書きしません。