谷歌日历MCP服务器
用于Google日历的可流式HTTP MCP服务器——管理事件、检查可用性和安排会议。
作者 过度
\[!警告\] 您自行负责将此服务器连接到MCP客户端。语言模型可能会出错、误解指令或执行意外操作。审查工具输出,验证更改(例如 search_events),并且更喜欢小的增量写入。 HTTP/OAuth层是为开发过程中的便利性而设计的,而不是生产级的安全性。如果远程部署,请加强它:适当的令牌验证、安全存储、TLS终止、严格的CORS/源代码检查、速率限制、审计日志记录以及遵守谷歌的条款。通知
此repo以两种方式工作:
- 作为一个 节点/HONO服务器 用于本地工作流
- 作为一个 Cloudflare工作人员 用于远程交互
有关Cloudflare的生产部署,请参阅 远程模型上下文协议服务器(MCP).
特性
- ✅ 事件 --在所有日历中搜索、创建、更新、删除、回复邀请
- ✅ 日历 --发现可用日历
- ✅ 可用性 --调度前检查忙/闲状态
- ✅ 自然语言 --用“明天中午的午餐”这样的文字创建活动
- ✅ Google Meet --自动为活动创建会议链接
- ✅ OAuth 2.1 --使用RS令牌映射保护PKCE流
- ✅ 双运行时 --Node.js/Bun或Cloudflare Workers
设计原则
- LLM友好:工具是简化和统一的,而不是1:1 API镜像
- 智能默认值:搜索所有日历,无垃圾邮件通知,定期扩展
- 只是工作:
search_events默认情况下搜索所有日历--无需设置 - 清晰的反馈:每个响应都包括每个事件所属的日历
- 功能有限:由于模型的幻觉,不包括管理日历的工具。确保您使用的客户端允许您确认危险操作,如事件删除或更新。
______________________________________________________________________
安装
先决条件: 包子, , 谷歌云 项目。对于远程:a 云耀 账户
跑步方式(选一种)
- 本地+OAuth --使用Google OAuth的标准设置
- Cloudflare Worker(牧者开发) --本地工人测试
- Cloudflare Worker(部署) --远程生产
______________________________________________________________________
1.本地+OAuth——快速入门
- 设置谷歌云控制台:
创建项目并启用API:
- 首选 谷歌云控制台 - 创建新项目(或选择现有项目) - 导航至 API和服务>库 - 搜索“谷歌日历API”并点击 启用
配置OAuth同意屏幕 (凭证前必须填写):
- 首选 API和服务>OAuth同意屏幕 - 选择 外部 (或工作区内部) - 填写应用程序名称、用户支持电子邮件、开发人员电子邮件 - 添加作用域: ../auth/calendar.events, ../auth/calendar.readonly - 将您的电子邮件添加为 测试用户 (应用程序处于“测试”模式时需要) - 保存
创建凭据:
- 首选 API和服务>凭据 - 点击 创建凭据>OAuth客户端ID - 应用程序类型: Web应用程序 - 名称:任何东西(例如,“谷歌日历MCP”) - 授权重定向URI: http://127.0.0.1:3001/oauth/callback - 点击 创建 并复制 客户端ID 和 客户端密钥
- 配置环境:
git clone
cd google-calendar-mcp
bun install
cp env.example .env编辑 .env:
PORT=3000
AUTH_ENABLED=true
PROVIDER_CLIENT_ID=your_client_id
PROVIDER_CLIENT_SECRET=your_client_secret
OAUTH_SCOPES=https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly
OAUTH_REDIRECT_URI=http://127.0.0.1:3001/oauth/callback
OAUTH_REDIRECT_ALLOWLIST=alice://oauth/callback,http://127.0.0.1:3001/oauth/callback- 运行:
bun dev
# MCP: http://127.0.0.1:3000/mcp
# OAuth: http://127.0.0.1:3001提示: 授权服务器在PORT+1(默认为3001)上运行。
克劳德桌面/光标:
{
"mcpServers": {
"google-calendar": {
"command": "bunx",
"args": ["mcp-remote", "http://localhost:3000/mcp", "--transport", "http-only"],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}令牌加密(推荐)
为安全令牌存储生成加密密钥:
openssl rand -base64 32添加到 .env:
RS_TOKENS_ENC_KEY=your-32-byte-base64-key______________________________________________________________________
2.Cloudflare Worker(本地开发人员)
bun x wrangler dev --local | cat使用OAuth:
bun x wrangler secret put PROVIDER_CLIENT_ID
bun x wrangler secret put PROVIDER_CLIENT_SECRET
bun x wrangler dev --local | cat端点: http://127.0.0.1:8787/mcp
______________________________________________________________________
3.Cloudflare Worker(部署)
- 为令牌存储创建KV命名空间:
bun x wrangler kv:namespace create TOKENS输出将显示:
Add the following to your wrangler.toml:
[[kv_namespaces]]
binding = "TOKENS"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"- 更新
wrangler.toml使用您的KV命名空间ID:
[[kv_namespaces]]
binding = "TOKENS"
id = "your-kv-namespace-id-from-step-1"- 设置秘密:
bun x wrangler secret put PROVIDER_CLIENT_ID
bun x wrangler secret put PROVIDER_CLIENT_SECRET
# Generate encryption key (32-byte base64url):
openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'
bun x wrangler secret put TOKENS_ENC_KEY注: TOKENS_ENC_KEY 加密存储在KV中的OAuth令牌(AES-256-GCM)。没有它,令牌将以未加密的方式存储!- 更新重定向URI
wrangler.toml:
OAUTH_REDIRECT_URI = "https://your-worker.your-subdomain.workers.dev/oauth/callback"
OAUTH_REDIRECT_ALLOWLIST = "alice://oauth/callback,https://your-worker.your-subdomain.workers.dev/oauth/callback"- 将Workers URL添加到Google Cloud OAuth应用程序的重定向URI中
- 部署:
bun x wrangler deploy端点: https://..workers.dev/mcp
______________________________________________________________________
客户端配置
预认证(推荐)
Claude Desktop有短暂的超时,可能会在进程中途终止OAuth流。 首先手动进行预身份验证:
# Authenticate (complete Google sign-in when browser opens)
npx mcp-remote https://your-worker.workers.dev/mcp --transport http-only一旦您看到“身份验证成功!”,令牌将被缓存,Claude Desktop将使用它们。
克劳德桌面/光标(本地服务器)
{
"mcpServers": {
"google-calendar": {
"command": "npx",
"args": ["mcp-remote", "http://127.0.0.1:3000/mcp", "--transport", "http-only"],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}克劳德桌面/光标(Cloudflare Worker)
{
"mcpServers": {
"google-calendar": {
"command": "npx",
"args": ["mcp-remote", "https://your-worker.workers.dev/mcp", "--transport", "http-only"]
}
}
}节点版本问题(nvm用户)
如果你得到 ReadableStream is not defined Claude Desktop可能正在使用旧的Node版本。通过指定完整路径进行修复:
{
"mcpServers": {
"google-calendar": {
"command": "/Users/YOUR_USER/.nvm/versions/node/v22.0.0/bin/npx",
"args": ["mcp-remote", "https://your-worker.workers.dev/mcp", "--transport", "http-only"],
"env": {
"PATH": "/Users/YOUR_USER/.nvm/versions/node/v22.0.0/bin:/usr/local/bin:/usr/bin:/bin"
}
}
}
}使用以下命令查找节点路径: which node
MCP检查员(快速测试)
bunx @modelcontextprotocol/inspector
# Connect to: http://localhost:3000/mcp (local) or https://your-worker.workers.dev/mcp (remote)______________________________________________________________________
工具
list_calendars
发现可用日历及其ID。通常不需要,因为 search_events 默认情况下搜索所有日历。
// Input
{}
// Output
{
items: Array;
}search_events
搜索事件 默认情况下为所有日历。返回按开始时间排序的合并结果。
// Input
{
calendarId?: string | string[]; // Default: "all" (searches ALL calendars)
// Can be: "all", single ID, or array of IDs
timeMin?: string; // ISO 8601
timeMax?: string; // ISO 8601
query?: string; // Text search
maxResults?: number; // Default: 50 (total across all calendars)
eventTypes?: string[]; // default, birthday, focusTime, outOfOffice
orderBy?: "startTime" | "updated";
fields?: string[]; // Control output verbosity
pageToken?: string; // Pagination (single calendar only)
}
// Output
{
items: Array;
calendarsSearched: string[]; // List of calendars that were searched
nextPageToken?: string;
}注: 每个活动包括calendarId和calendarName所以你知道它属于哪个日历。使用这个calendarId通话时update_event或delete_event.
check_availability
在安排之前检查忙/闲状态。
// Input
{
timeMin: string; // ISO 8601 (required)
timeMax: string; // ISO 8601 (required)
calendarIds?: string[]; // Default: ["primary"]
}
// Output
{
calendars: {
[calendarId]: {
busy: Array;
}
}
}create_event
使用自然语言或结构化输入创建事件。
// Natural language mode
{
text: "Lunch with Anna tomorrow at noon for 1 hour";
calendarId?: string;
sendUpdates?: "all" | "externalOnly" | "none";
}
// Structured mode
{
summary: string; // Required
start: string; // ISO 8601 or YYYY-MM-DD
end: string; // ISO 8601 or YYYY-MM-DD
calendarId?: string;
description?: string;
location?: string;
attendees?: string[]; // Email addresses
addGoogleMeet?: boolean; // Auto-create Meet link
recurrence?: string[]; // RRULE array
visibility?: "default" | "public" | "private";
sendUpdates?: "all" | "externalOnly" | "none";
}update_event
更新或移动现有事件(PATCH语义)。
{
eventId: string; // Required
calendarId?: string;
targetCalendarId?: string; // Move to different calendar
summary?: string;
start?: string;
end?: string;
description?: string;
location?: string;
attendees?: string[];
addGoogleMeet?: boolean;
sendUpdates?: "all" | "externalOnly" | "none";
}delete_event
从日历中删除事件。
{
eventId: string; // Required
calendarId?: string;
sendUpdates?: "all" | "externalOnly" | "none";
}respond_to_event
接受、拒绝或暂时接受活动邀请。
// Input
{
eventId: string; // Required
calendarId?: string; // Default: "primary"
response: "accepted" | "declined" | "tentative"; // Required
sendUpdates?: "all" | "externalOnly" | "none"; // Default: "all"
}
// Output
{
ok: true,
response: "accepted", // Your response
event: { ... } // Updated event
}注: 仅适用于您被邀请参加的活动。对于您创建的活动,您是组织者,而不是参与者。
______________________________________________________________________
例子
1.列出今天的活动(搜索所有日历)
{
"name": "search_events",
"arguments": {
"timeMin": "2025-01-15T00:00:00Z",
"timeMax": "2025-01-15T23:59:59Z"
}
}无需致电list_calendars首先--search_events默认情况下搜索所有可访问的日历,并显示每个事件所属的日历。
2.使用Google Meet创建活动
{
"name": "create_event",
"arguments": {
"summary": "Team Standup",
"start": "2025-01-16T09:00:00+01:00",
"end": "2025-01-16T09:30:00+01:00",
"addGoogleMeet": true,
"attendees": ["alice@example.com", "bob@example.com"]
}
}3.自然语言事件
{
"name": "create_event",
"arguments": {
"text": "Coffee with Sarah next Monday at 3pm for 30 minutes"
}
}4.在安排之前检查可用性
{
"name": "check_availability",
"arguments": {
"timeMin": "2025-01-16T09:00:00Z",
"timeMax": "2025-01-16T18:00:00Z"
}
}______________________________________________________________________
HTTP端点
| 端点 | 方法 | 目的 |
|---|---|---|
/mcp | 发布 | MCP JSON-RPC 2.0 |
/mcp | GET | SSE流(仅限Node.js) |
/health | GET | 健康检查 |
/.well-known/oauth-authorization-server | GET | OAuth AS元数据 |
/.well-known/oauth-protected-resource | GET | OAuth RS元数据 |
OAuth(端口+1):
GET /authorize--启动OAuth流GET /oauth/callback--谷歌回拨POST /token--代币兑换POST /revoke--撤销代币
______________________________________________________________________
发展
bun dev # Start with hot reload
bun run typecheck # TypeScript check
bun run lint # Lint code
bun run build # Production build
bun start # Run production______________________________________________________________________
建筑
src/
├── shared/
│ ├── tools/
│ │ ├── list-calendars.ts
│ │ ├── search-events.ts
│ │ ├── check-availability.ts
│ │ ├── create-event.ts
│ │ ├── update-event.ts
│ │ └── delete-event.ts
│ ├── oauth/ # OAuth flow (PKCE, discovery)
│ └── storage/ # Token storage (file, KV, memory)
├── services/
│ └── google-calendar.ts # Google Calendar API client
├── config/
│ └── metadata.ts # Server & tool descriptions
├── index.ts # Node.js entry
└── worker.ts # Workers entry______________________________________________________________________
故障排除
| 问题 | 解决方案 |
|---|---|
| “需要身份验证” | 完成OAuth流程。跑 rm -rf ~/.mcp-auth 并重新进行身份验证。 |
| “redirect_uri_mismatch” | 谷歌对待 localhost 和 127.0.0.1 不同。使用 127.0.0.1 在.env和谷歌云控制台中保持一致。 |
| “unknown_txn”错误 | 失效的mcp远程进程。跑 pkill -9 -f mcp-remote && rm -rf ~/.mcp-auth 然后重试。 |
| “ReadableStream未定义” | Node.js版本太旧(需要18+)。使用配置中较新节点的完整路径。 |
| “spawn bunx ENOENT” | 克劳德桌面找不到 bunx.使用 npx 或者指定完整路径。 |
| “另一个处理auth的实例” | 杀死僵尸进程: pkill -9 -f mcp-remote && rm -rf ~/.mcp-auth |
| Claude中的OAuth超时 | Claude过快地终止了认证流。手动预认证(请参阅客户端配置)。 |
| 令牌过期 | Google令牌在1小时后过期。如果满足以下条件,则会自动使用刷新令牌 access_type=offline 设置。 |
| OAuth未启动(Worker) | curl -i -X POST https:///mcp 应返回 401 随着 WWW-Authenticate. |
| KV命名空间错误 | 运行 wrangler kv:namespace create TOKENS 并更新 wrangler.toml 与ID |
| Claude中的工具为空 | 确保Worker返回JSON模式 tools/list;使用 mcp-remote. |
调试
启用详细日志 --debug:
npx mcp-remote https://your-worker.workers.dev/mcp --transport http-only --debug日志被写入 ~/.mcp-auth/{hash}_debug.log.
独立测试身份验证流程:
npx -p mcp-remote@latest mcp-remote-client https://your-worker.workers.dev/mcp --transport http-only --debug______________________________________________________________________
环境变量
Node.js(.env)
| 变量 | 必填 | 描述 |
|---|---|---|
PROVIDER_CLIENT_ID | ✓ | 谷歌OAuth客户端ID |
PROVIDER_CLIENT_SECRET | ✓ | 谷歌OAuth客户端密码 |
RS_TOKENS_ENC_KEY | Prod | 32字节base64密钥用于令牌加密 |
PORT | MCP服务器端口(默认值:3000) | |
HOST | 服务器主机(默认值:127.0.0.1) | |
LOG_LEVEL | 调试、信息、警告、错误 | |
OAUTH_REDIRECT_URI | OAuth的回调URL | |
OAUTH_REDIRECT_ALLOWLIST | 逗号分隔的允许重定向URI |
Cloudflare Workers(wrangler.toml+机密)
牧马人。汤姆瓦尔斯:
AUTH_ENABLED = "true"
AUTH_STRATEGY = "oauth"
OAUTH_SCOPES = "https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly"
OAUTH_REDIRECT_URI = "https://your-worker.workers.dev/oauth/callback"秘密(通过设置 wrangler secret put):
PROVIDER_CLIENT_ID--谷歌OAuth客户端IDPROVIDER_CLIENT_SECRET--谷歌OAuth客户端密码TOKENS_ENC_KEY--32字节base64url加密密钥
KV命名空间:
[[kv_namespaces]]
binding = "TOKENS"
id = "your-kv-namespace-id"______________________________________________________________________
许可证
麻省理工学院
