microsoft-calendar-sync について
microsoft-calendar-syncは、Microsoft 365またはOutlook.comのカレンダーを使うアプリの開発者向け連携スキルです。予定の空き時間を可用性データに同期し、予約をOutlookに反映します。Graph通知、購読更新、タイムゾーン、トークンローテーション、イベント識別も扱います。
主な機能
- 01Outlookの予定を空き時間データとして同期
- 02予約イベントをOutlookに作成・削除
- 03Graph通知を起点に空き時間を同期
- 04カレンダー購読の更新と期限切れ対応
- 05Teamsリンクとメールボックスのタイムゾーン対応
- 06FirestoreとSupabaseのストレージ実装
活用シーン
- 01ホストのOutlook予定を空き状況の判定に利用
- 02確定した予約をホストのカレンダーに同期
- 03予約キャンセル時に対応するイベントを削除
- 04Graph購読の期限切れや通知漏れを調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ timerise-ai/ microsoft-calendar-sync --skill microsoft-calendar-syncインストール後、Claude Code で/microsoft-calendar-syncを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitattributesLICENSECHANGELOG.mdCLAUDE.mdREADME.md.github/workflows/agent-eval.ymlevals/prompts.mdreferences/booking-events.mdbusy-sync.mdfirestore-backend.mdcalendars-and-settings.mdoperations.mddata-model.mdsupabase-backend.mdsubscriptions.mdprovenance.mdgraph-api.mdoauth-and-tokens.mdhttp-routes.mdtest-flows.mdtime-zones.mdtest-suite.mdtesting.md詳細
Microsoft Calendar Sync
2 つの方向に、それぞれ 1 つのルールがあります。受信:ホストのほかの予定については Outlook を正とするため、予定ありブロックは時間範囲を反映する破棄可能なミラーです。完全な再同期で再構築し、手動では編集しません。送信:予約については予約ストアを正とするため、Outlook に書き込むイベントには予約 ID を含むプライベートマーカーを付け、それを使って再度検索します。また、そのイベントを予定あり時間として読み戻すことはありません。Google と異なり、Graph はイベント ID を割り当て、3 秒以内の応答を求め、カレンダーごとにサブスクリプションを 1 つだけ許可します。
使用する場面
- ホストが Microsoft 365 または Outlook.com に接続し、空き状況をブロックするカレンダーを選択する場合。
- 予約がホストの Outlook カレンダーに表示され、オプションで Teams 会議として作成され、キャンセル時に消える場合。
- 既存の Graph 統合を監査する場合:1 時間後に停止する、サブスクリプションが通知を送らない、休暇が反映されない。
- Firestore と Supabase/Postgres の間で同期を移行する場合。
使用しない場面
- Google Calendar:
google-calendar-syncを使用してください。同じアーキテクチャとレコード形式ですが、API が異なります。 - 予約可能な時間枠の生成、予定あり時間の差し引き:
time-slot-generatorを使用してください。このスキルは、消費する予定ありブロックを生成します。 - **Microsoft でのユーザーサインイン(SSO)**は ID 管理の問題です。このスキルが求めるのは、すでにサインイン済みのユーザーのカレンダーへのアクセスです。
- 会議室やリソースのメールボックス、またはユーザーごとの同意なしでの組織全体へのアクセスには、管理者の同意を伴うアプリケーション権限が必要であり、設計も異なります。
- オンプレミスの Exchange Server が使用するのは Graph ではなく EWS です。
アーキテクチャ
connect (PKCE, single-use state) -> callback -> id token tid:oid + encrypted tokens + mailbox zone -> rows
|
inbound Graph -- POST {value[]} -> /notifications -> verify clientState -> 202 -> defer(syncBusy) -> calendarView pages
^ -> toBusyBlock -> diffBusy -> busy blocks
+-- subscription <-- cron: PATCH renew, 404 replace, 409 clear, resync every busy calendar
outbound booking confirmed -> find by marker -> unchanged? skip : PATCH - none? POST (transactionId) -> Teams link back
booking cancelled -> find by marker -> DELETE each (404 = done)
重要な事実
- すべてのトークン応答からリフレッシュトークンを保存してください。 Microsoft は更新のたびにリフレッシュトークンをローテーションします。Node 用 MSAL はトークンを自身のキャッシュ内に保持し、返すことはありません。そのため、MSAL の結果を保存する統合ではリフレッシュトークンがなく、最初のアクセストークンが期限切れになると停止します。これはおよそ 1 時間後です。
clientStateは最大 128 文字です。 これを超えると Graph はサブスクリプションを拒否します。Outlook のカレンダー ID だけでも約 150 文字あります。署名したサブスクリプション ID だけを入れてください。- 通知には 3 秒以内に応答してから同期してください。 エンドポイントの応答が遅いと、Graph はエンドポイントを低速と判定して遅延させ、その後、通知を完全に破棄します。検証して 202 を返し、応答後に同期を実行してください。
- カレンダーごとにサブスクリプションは 1 つだけです。既存のものを更新してください。 同じリソースへの 2 回目の POST は 409 になるため、Google の新規作成後に旧サブスクリプションを削除する方法は適用できません。有効期限を PATCH してください。404 は Graph がサブスクリプションを削除したことを意味し、その場合に限り新しいものを作成します。
- 終日イベントの時刻はタイムゾーンに依存します。 Graph は指定したタイムゾーンで深夜から翌日の深夜までとして返すため、UTC では日付は正しくても時刻が誤って表示されます。通常は Windows 名で報告されるメールボックスのタイムゾーンを基準にしてください。
- イベント ID は Graph が割り当てます。 予約のイベントは、単一値の拡張プロパティを使って再度検索します。作成前に検索し、重複を解消し、変更がない場合は書き込みを省略してください。出席者がいる会議への PATCH は、毎回出席者にメールを送信するためです。
- 出席者にはメールが送信され、無効にする切り替え手段はありません。 ゲストを招待すると、ホストから Outlook の招待メールが送信され、イベントを削除するとキャンセル通知が送信されます。
inviteAttendeesで、誰を一覧に含めるかを決めます。
厳守事項
OAuth コールバック URL からユーザー ID を取得しないでください。 state は、サーバー側に保存された 1 回限りの 10 分間有効なレコードを参照するキーです。そうしないと、誰でも別のユーザーのアカウントに自分のカレンダーを紐付けられてしまいます。
通知に応答する前に同期しないでください。
deferに処理を引き渡してください。cron はセーフティネットです。
clientState を検証し、通知が届いたサブスクリプションを特定するまでは、通知を信用しないでください。 エンドポイントは公開されています。署名済みの clientState だけが認証情報です。
トークンを平文で保存しないでください。 リフレッシュトークンは、カレンダーへの長期間有効な読み書きアクセス権です。
Outlook の障害によって予約を失敗させないでください。 送信書き込みはログに記録して戻り、予約はそのまま成立させます。
適応契約
この表は接合部の契約です。このスキルに adaptation.md はありません。名称変更の表は
data-model.md にあり、作業の順序は以下のクイックスタートに記載されています。
| 接合部 | スキルに含まれるもの | ホストが用意するもの |
|---|---|---|
| ドメインエンティティ | Connection、LinkedCalendar、WatchSubscription、BusyBlock、BookingForCalendar | 独自の名称(staff、resource、appointment) |
| テナントスコープ | ユーザーごとに、userId あたり Microsoft アカウントを1つ | ユーザー ID。組織のスコープはホストのガードで維持します |
| 認証ガード | authenticate(request)。userId | null を返します | Session、Clerk、NextAuth、Firebase、Supabase |
| データアクセス | CalendarStore と Firestore および Supabase の実装 | 独自の ORM または SDK |
| 予約 | BookingSource と pushBookingEvent / removeBookingEvent | 予約のステータスフローからこれらを呼び出します |
| 空き状況 | ユーザーごとのビジーブロック、listHostBusy クエリ | 独自のスロットエンジン(time-slot-generator) |
| タイムゾーン | メールボックスのタイムゾーン、hostTimeZone(userId)、defaultTimeZone | ホストのビジネスタイムゾーン |
| HTTP | Web の Request/Response ハンドラー | Next.js のルートハンドラー、Express アダプター |
| バックグラウンド処理 | 通知用の defer、12 時間ごとの renewSubscriptions | after()、waitUntil、Cloud Scheduler、Vercel Cron |
| 文字列 | describeBooking(イベントのタイトル/本文)、エラーコード | 独自の i18n |
| シークレット | クライアント ID/シークレット、clientState シークレット、32 バイトのトークンキー、マーカー GUID | 独自のシークレットマネージャー |
| テスト | node:test スイート、メモリストア、フェイク Graph | 独自のテストランナー |
クイックスタート
- Entra ID にアプリを登録し、マーカー GUID を生成します: oauth-and-tokens.md。
- 型をコピーし、名称変更を決めます:data-model.md。
- Graph クライアントをコピーします:graph-api.md。タイムゾーンマップもコピーします: time-zones.md。
- インバウンド同期をコピーします:busy-sync.md。アウトバウンドは booking-events.md、サブスクリプションは subscriptions.md、カレンダー一覧と切り替え機能は calendars-and-settings.md を参照してください。
- ストアを選びます:firestore-backend.md または supabase-backend.md。
- ルート、
defer、cron をマウントします:http-routes.md。 - テストスイートをコピーし、2 つの
TZ値を指定して実行します:testing.md、 test-suite.md、test-flows.md。 - 運用担当者にヘルスビューを提供します:operations.md。
- 既存の Graph 統合を修復しますか?修正の順序は provenance.md に記載されています。
リファレンス一覧
| シナリオ | トリガーキーワード | リファレンス |
|---|---|---|
| エンティティ、ストアの契約、名前変更 | LinkedCalendar, WatchSubscription, BusyBlock, CalendarStore, mutateCalendar, version | data-model.md |
| アカウントの接続 | Entra ID, app registration, redirect URI, tenant, organizations, common, scopes, PKCE, id_token, tid, oid, refresh token rotation, invalid_grant, interaction_required, admin consent, AES-256-GCM | oauth-and-tokens.md |
| Graph との通信 | fetch, calendarView, Prefer outlook.timezone, @odata.nextLink, $filter extended property, 429, Retry-After, subscriptions, MSAL | graph-api.md |
| メールボックスのタイムゾーン | mailboxSettings, Windows time zone, "W. Europe Standard Time", IANA, CLDR windowsZones | time-zones.md |
| 予定ありの時間の取り込み | toBusyBlock, showAs, free, workingElsewhere, oof, tentative, declined, isCancelled, isAllDay, diffBusy, lease, horizon | busy-sync.md |
| 予約の書き込み | singleValueExtendedProperties, marker GUID, transactionId, attendees, invitations, Teams, isOnlineMeeting, joinUrl, backfill | booking-events.md |
| サブスクリプション | clientState, 128 characters, expirationDateTime, 10080 minutes, PATCH renew, 404, 409 Conflict, lifecycle, subscriptionRemoved, missed | subscriptions.md |
| カレンダー一覧と切り替え | canEdit, isDefaultCalendar, shared calendar, syncBusy, syncBookings, planTransitions | calendars-and-settings.md |
| ルートと Webhook | validationToken, text/plain, 202, 3 seconds, defer, after, waitUntil, callback redirect, consent_required, cron secret, Express | http-routes.md |
| Firestore | firebase-admin, runTransaction, batch 500, composite index, security rules | firestore-backend.md |
| Supabase / Postgres | schema.sql, RLS, service role, optimistic concurrency, 23505, PostgREST 1000 rows | supabase-backend.md |
| 動作の検証 | node:test, memory store, fake Graph, TZ=, mutation check | testing.md, test-suite.md, test-flows.md |
| 実行と運用 | health, stale, subscription-expired, env vars, throttling, runbook, tunnel, localhost | operations.md |
| この設計の理由 | provenance, defects, kept deliberately, fix order | provenance.md |
兄弟スキルを掲載した Timerise Skills のインデックスに含まれています。