Skip to content

rocktakey/caldav-mcp

unversioned · 74dda5c4e623AGPL-3.0-or-later

Multi-account CalDAV calendars, events, tasks and sharing over MCP.

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 に公開済みタグを指定します。同名の配布物は上書きしません。