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 bearer token 位于 $GOOGLE_CALENDAR_TOKEN;每次调用都需要使用 Authorization: Bearer $GOOGLE_CALENDAR_TOKEN。该 token 至少包含 calendar.readonly 和身份范围 (openid email profile);如果用户在安装时选择启用写入权限,还会包含范围更广的 calendar 范围(读写)。

Calendar API 返回标准 JSON;失败时会返回 {"error": {"code": 401|403|..., "message": "..."}},请原样显示该错误。401 表示 token 已过期(重新安装)。写入操作出现 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:

  • +agenda 会从 Settings.timezone 读取用户账号的时区(缓存 24 小时),并按该时区显示今天的事件,因此格式化时间前无需自行获取时区。
  • +insert 会为你构建创建事件所需的 JSON(与会者、sendUpdates、提醒),让你通过一行命令生成格式正确的请求。

其他操作(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 从 GOOGLE_WORKSPACE_CLI_TOKEN 环境变量读取其 OAuth bearer token。此技能使用的 Calendar token 位于 $GOOGLE_CALENDAR_TOKEN,因此每个调用 gws 的 shell 代码块开头都要重新导出一次:

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 拒绝请求,这两个辅助命令都会在 stderr 上输出结构化 JSON 错误,并以非零状态码退出——请原样显示错误。对包含与会者的事件使用 +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,或类似电子邮件地址的 ID,如 [email protected])都可用于后续的 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 会将重复会议展开为单个实例——这几乎总是查询日程时想要的结果。如果不使用该参数,你只会获取一次重复规则,并且必须在客户端展开。

本周会议(周一至周日)
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 会匹配摘要、说明、地点、与会者电子邮件地址以及创建者/组织者。

获取单个事件的完整详情(包括与会者、地点和链接)
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 获取该实例,然后 DELETE 特定的实例 ID(其格式更长,为 EVENT_ID_YYYYMMDDTHHMMSSZ)。

常见错误代码

HTTP含义告诉用户的内容
401 UNAUTHENTICATED令牌已过期 / 被撤销“请在 Connections 页面重新连接 Google Calendar 连接器。”
403 insufficientPermissions缺少写入作用域“此操作需要 Calendar 读写作用域,但你只授予了 calendar.readonly。请重新安装连接器,并勾选读写选项。”
403 forbidden此账号无法查看该日历 ID先检查 calendarList;如果这是共享日历,请所有者共享该日历。
404 notFound活动 / 日历 ID 错误仔细检查 ID,并尝试使用 calendarList 确认该日历是否存在。
409 conflict重复活动 ID 冲突在 requestId 后追加一个 UUID,然后重试。
429 quotaExceeded配额 / 请求限流等待约 5s,然后重试一次。

切勿记录或回显 $GOOGLE_CALENDAR_TOKEN — 请将其视为机密。

评论

支持 Markdown