mcpField
G
google-calendar

google-calendar

github.com/acedatacloud生産性17214 回インストール今日更新
リポジトリ

google-calendar について

curl と jq で Google Calendar を操作するスキルです。予定確認やイベント作成には Google's gws コマンドラインも任意で使えます。カレンダーの確認、現地時刻での表示、イベント検索、空き状況確認、慎重な変更手順を扱います。

主な機能

  1. 01アクセス可能なカレンダーとタイムゾーンの確認
  2. 02イベントの一覧表示と検索
  3. 03複数カレンダーの空き状況確認
  4. 04イベントの作成、更新、移動、キャンセル
  5. 05参加者への通知と権限エラーへの対応

活用シーン

  1. 01今日または今週の予定を確認
  2. 02条件に合うイベントを検索
  3. 03複数カレンダーの空き時間を確認
  4. 04確認を取って予定を変更またはキャンセル

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/acedatacloud/skills --skill google-calendar

インストール後、Claude Code で/google-calendarを入力して読み込まれたか確認します。

詳細

curl + jq を使って Google Calendar を操作します。ユーザーの OAuth ベアラートークンは $GOOGLE_CALENDAR_TOKEN に格納されています。すべての呼び出しで Authorization: Bearer $GOOGLE_CALENDAR_TOKEN として指定する必要があります。トークンには最低限、 calendar.readonly と ID スコープ (openid email profile)が含まれます。インストール時にユーザーが書き込みを許可した場合は、 より広範な calendar スコープ(読み取り+書き込み)も含まれます。

Calendar API は標準の JSON を返します。エラーは {"error": {"code": 401|403|..., "message": "..."}} の形式で返されるため、 そのエラーをそのまま表示してください。401 はトークンの期限切れを意味します(再インストールしてください)。 書き込み時の 403 insufficientPermissions は、ユーザーが calendar.readonly のみを許可したことを意味します。 読み取り+書き込みのチェックボックスをオンにしてコネクターを再インストールするよう依頼してください。

必ず最初に users/me/calendarList を呼び出して、アカウントから参照できるカレンダー (ユーザーのメインカレンダーと、登録済みまたは共有されたカレンダー)を確認し、 さらに users/me/settings/timezone を呼び出して、UTC ではなくユーザーのローカルタイムゾーンで時刻を表示してください。

破壊的な書き込みを行う前に(参加者がいる予定の作成、移動、キャンセル)、予定の詳細を正確に表示し、ユーザーに確認してください。参加者がいる場合は、Google から参加者にメールを送信するかどうかも確認してください。これは sendUpdates クエリパラメーターで制御されます。

オプション:議題の確認と作成に使う Google Workspace CLI(gws)

gws は Google 公式の CLI です (公式サポート対象ではなく、コミュニティが googleworkspace 組織で管理しています)。Google の Discovery Document からコマンド体系を動的に構築し、API エラーが発生するとゼロ以外の終了コードを返します。また、時刻を考慮したワークフロー向けに、独自に作成されたヘルパーコマンド(+ で始まるコマンド)も提供します。

gws は次の 2 つのケースで使用してください。

  • +agenda は Settings.timezone からユーザーのアカウントのタイムゾーンを取得し (24 時間キャッシュ)、そのタイムゾーンで今日の予定を表示します。そのため、時刻を整形する前にタイムゾーンを自分で取得する必要はありません。
  • +insert は予定作成用の JSON(参加者、sendUpdates、リマインダー)を整形するため、1 行のコマンドで正しい形式のリクエストを作成できます。

それ以外のすべて(events.list / patch / move / delete、freebusy、 calendarList)については、以下の curl の例と同等で、より短いため、こちらを使用してください。

インストール
npm install -g @googleworkspace/cli   # or: brew install googleworkspace-cli
# Pre-built binaries also at https://github.com/googleworkspace/cli/releases
gws --version
認証

gws は OAuth ベアラートークンを環境変数 GOOGLE_WORKSPACE_CLI_TOKEN から読み込みます。このスキルで使用する Calendar トークンは $GOOGLE_CALENDAR_TOKEN にあるため、gws を呼び出す各シェルブロックの先頭で、一度再エクスポートしてください。

export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_CALENDAR_TOKEN"
議題の確認と作成
# Today on the primary calendar, in the account's own timezone
gws calendar +agenda

# Today / week, with explicit overrides
gws calendar +agenda --today --tz America/New_York
gws calendar +agenda --range week

# Create an event (auto-shapes attendees + sendUpdates JSON)
gws calendar +insert --calendar primary \
  --json '{
    "summary":"Standup",
    "start":{"dateTime":"2026-05-06T10:00:00-04:00"},
    "end":  {"dateTime":"2026-05-06T10:30:00-04:00"},
    "attendees":[{"email":"[email protected]"}]
  }' \
  --params '{"sendUpdates":"all"}'

どちらのヘルパーも、Google がリクエストを拒否すると、構造化された JSON エラーを stderr に出力してゼロ以外の終了コードで終了します。そのエラーをそのまま表示してください。参加者がいる予定に対する +insert には、より広範な calendar スコープが必要です。403 insufficientPermissions が返された場合は、読み取り+書き込みをオンにして再インストールするようユーザーに依頼してください。

レシピ

認証を確認し、カレンダーを検出する(必ず最初に実行)
# Account confirmation + calendars the user can read
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/calendarList" \
  | jq '.items[] | {id, summary, primary, accessRole, timeZone}'

# User's preferred display zone (use this when formatting times)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" \
  | jq -r .value

各カレンダーの id(primary、または [email protected] のようなメールアドレス形式の ID)が、後続の calendars/{id}/events 呼び出しで使用する値です。

メインカレンダーの今日の議題
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
TODAY=$(TZ=$TZ date +%Y-%m-%d)
START="${TODAY}T00:00:00Z"
END="${TODAY}T23:59:59Z"

curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
  --data-urlencode "timeMin=$START" \
  --data-urlencode "timeMax=$END" \
  --data-urlencode 'singleEvents=true' \
  --data-urlencode 'orderBy=startTime' \
  --data-urlencode "timeZone=$TZ" \
  | jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}'

singleEvents=true を指定すると、繰り返しの予定が個別の予定に展開されます。議題を表示する場合、ほとんどの場合これが適しています。指定しない場合は、繰り返しルールが 1 回だけ返されるため、クライアント側で展開する必要があります。

今週の会議(月〜日)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
# Bash date math: Monday-of-this-week
MON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null \
  || TZ=$TZ date -v-mondayw +%Y-%m-%d)  # macOS fallback
SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null \
  || TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)

curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
  --data-urlencode "timeMin=${MON}T00:00:00Z" \
  --data-urlencode "timeMax=${SUN}T23:59:59Z" \
  --data-urlencode 'singleEvents=true' \
  --data-urlencode 'orderBy=startTime' \
  | jq -r '.items[] | "\(.start.dateTime // .start.date)\t\(.summary)\t\((.attendees // []) | length) attendees"'
クエリで予定を検索
Q='quarterly review'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
  --data-urlencode "q=$Q" \
  --data-urlencode 'singleEvents=true' \
  --data-urlencode 'maxResults=20' \
  | jq '.items[] | {start: .start.dateTime, summary, htmlLink}'

q は、概要、説明、場所、参加者のメールアドレス、作成者/主催者を対象に検索します。

予定 1 件の詳細をすべて取得(参加者、場所、リンクを含む)
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \
  | jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}'
複数のカレンダーの空き時間/予定ありの時間(今後 7 日間)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ)
NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
  || TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ)

cat > /tmp/freebusy.json <<JSON
{
  "timeMin": "$NOW",
  "timeMax": "$NEXT_WEEK",
  "timeZone": "$TZ",
  "items": [
    {"id": "primary"},
    {"id": "[email protected]"}
  ]
}
JSON

curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data @/tmp/freebusy.json \
  "https://www.googleapis.com/calendar/v3/freeBusy" \
  | jq '.calendars'

各カレンダーの応答は {"busy": [{"start": "...", "end": "..."}]} です。 時間帯の間の空きが、予定のない時間です。

メイン以外のカレンダーの予定を一覧表示
CAL_ID='[email protected]'
# URL-encode the @ in the path
CAL_ENCODED=$(printf %s "$CAL_ID" | jq -sRr @uri)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  --get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events" \
  --data-urlencode 'singleEvents=true' \
  --data-urlencode 'orderBy=startTime' \
  --data-urlencode 'maxResults=20' \
  | jq '.items[] | {start: .start.dateTime, summary}'
ページネーション
PAGE_TOKEN=''
while : ; do
  RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
    --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
    --data-urlencode 'singleEvents=true' \
    --data-urlencode 'orderBy=startTime' \
    --data-urlencode 'maxResults=250' \
    ${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
  echo "$RESP" | jq -c '.items[]?'
  PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
  [ -z "$PAGE_TOKEN" ] && break
done

書き込みのレシピ

いずれも、より広い calendar スコープが必要です。ユーザーが calendar.readonly だけを許可した場合は、403 insufficientPermissions が返されます。 その旨を伝え、読み取り+書き込みのチェックボックスをオンにして 再インストールするよう依頼してください。イベントを作成またはキャンセルする前に、必ずイベントの概要、日時、参加者リストをユーザーに復唱してください。

単一のイベントを作成する(参加者と Google Meet のリンクは任意)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)

cat > /tmp/_cal_event.json <<JSON
{
  "summary": "Sync — Q2 OKR review",
  "location": "Online",
  "description": "Drafted by AceDataCloud.",
  "start": {"dateTime": "2026-05-12T10:00:00", "timeZone": "$TZ"},
  "end":   {"dateTime": "2026-05-12T10:30:00", "timeZone": "$TZ"},
  "attendees": [
    {"email": "[email protected]"},
    {"email": "[email protected]"}
  ],
  "reminders": {"useDefault": true},
  "conferenceData": {
    "createRequest": {
      "requestId": "meet-$(date +%s)",
      "conferenceSolutionKey": {"type": "hangoutsMeet"}
    }
  }
}
JSON

# sendUpdates: 'all' = email all attendees; 'externalOnly' = only non-org; 'none' = silent
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data @/tmp/_cal_event.json \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all" \
  | jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'

ユーザーが Meet のリンクを求めていない場合は、conferenceData ブロックを省略してください。 通常のイベントとして作成されます。

繰り返しイベントを作成する
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_recur.json <<JSON
{
  "summary": "Weekly 1:1",
  "start": {"dateTime": "2026-05-12T15:00:00", "timeZone": "$TZ"},
  "end":   {"dateTime": "2026-05-12T15:30:00", "timeZone": "$TZ"},
  "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data @/tmp/_cal_recur.json \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
  | jq '{id, recurrence, summary}'

RRULE は RFC 5545 に準拠しています。よく使われるパターンは、FREQ=DAILY、FREQ=WEEKLY;BYDAY=MO,WE,FR、 FREQ=MONTHLY;BYMONTHDAY=15 です。終了日を指定するには UNTIL=20261231T235959Z、または COUNT=12 を追加します。

既存のイベントを更新する(PATCH — 部分更新)
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"location":"Conference Room 4","description":"Now in-person."}' \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
  | jq '{id, summary, location, description}'

PATCH は送信したフィールドのみを変更します。PUT はイベントのペイロード全体を置き換えます。 PATCH を優先してください。

イベントの日時を変更する
EVENT_ID='abc123def4567890ghijklmnop'
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_resched.json <<JSON
{
  "start": {"dateTime": "2026-05-12T14:00:00", "timeZone": "$TZ"},
  "end":   {"dateTime": "2026-05-12T14:30:00", "timeZone": "$TZ"}
}
JSON
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data @/tmp/_cal_resched.json \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
  | jq '{id, summary, start, end}'
参加者を追加または変更する

参加者をパッチする際、Google では参加者リスト全体を送信する必要があります。現在のリストを取得して 変更し、そのリストを送り返してください。

EVENT_ID='abc123def4567890ghijklmnop'
CURRENT=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?fields=attendees" \
  | jq '.attendees // []')
NEW=$(echo "$CURRENT" | jq '. + [{"email":"[email protected]"}]')
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  -H 'Content-Type: application/json' \
  --data "{\"attendees\": $NEW}" \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
  | jq '{id, attendees}'
イベントをキャンセル/削除する
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
  "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
  -o /dev/null -w 'HTTP %{http_code}\n'

204 = 成功です。繰り返しイベントの特定の回のみをキャンセルするには、まず events.instances でその回を取得し、その後、特定のインスタンス ID(より長い EVENT_ID_YYYYMMDDTHHMMSSZ の形式)を DELETE します。

よくあるエラーコード

HTTP意味ユーザーへの説明
401 UNAUTHENTICATEDトークンの有効期限切れ/取り消し「Connections ページで Google Calendar コネクターを再接続してください。」
403 insufficientPermissions書き込みスコープがない「この操作には Calendar の読み取り+書き込みスコープが必要ですが、calendar.readonly しか許可されていません。読み取り+書き込みのチェックボックスをオンにして、コネクターを再インストールしてください。」
403 forbiddenこのアカウントからカレンダー ID が見えないまず calendarList を確認してください。共有カレンダーの場合は、所有者に共有してもらう必要があります。
404 notFoundイベント ID またはカレンダー ID が間違っているID を再確認し、calendarList でカレンダーが存在することを確認してください。
409 conflict繰り返しイベント ID の衝突requestId に UUID を追加して、再試行してください。
429 quotaExceededクォータ超過/スロットリング約 5 秒待ってから、一度だけ再試行してください。

$GOOGLE_CALENDAR_TOKEN をログに記録したり、ユーザーに伝えたりしないでください。秘密情報として扱ってください。

コメント