@mcp-wormhole/google-calendar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/dist/chunk-C2MOCEV4.js +239 -0
- package/dist/chunk-C2MOCEV4.js.map +1 -0
- package/dist/index.js +589 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @mcp-wormhole/google-calendar
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mcp-wormhole/google-calendar)
|
|
4
|
+
[](https://www.npmjs.com/package/@mcp-wormhole/google-calendar)
|
|
5
|
+
|
|
6
|
+
**Full-stack Google Calendar MCP server** — 12 tools, 6 prompt workflows, and browsable `gcal://` resources via the official Google Calendar API.
|
|
7
|
+
|
|
8
|
+
## Highlights
|
|
9
|
+
|
|
10
|
+
| | |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **Tools** | 12 across calendars, events, search, free/busy, quick-add, RSVP |
|
|
13
|
+
| **Prompts** | 6 MCP workflow templates (`today_agenda`, `find_meeting_time`, …) |
|
|
14
|
+
| **Resources** | Browse calendars → events at `gcal://` URIs |
|
|
15
|
+
| **Auth** | OAuth2 refresh token JSON or service account |
|
|
16
|
+
| **Transport** | stdio (npx) |
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx -y @mcp-wormhole/google-calendar
|
|
22
|
+
# set GOOGLE_CALENDAR_CREDENTIALS in env
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"google-calendar": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@mcp-wormhole/google-calendar"],
|
|
31
|
+
"env": {
|
|
32
|
+
"GOOGLE_CALENDAR_CREDENTIALS": "{\"client_id\":\"...\",\"client_secret\":\"...\",\"refresh_token\":\"...\"}"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Optional: `"GOOGLE_CALENDAR_ID": "primary"` to set a default calendar.
|
|
40
|
+
|
|
41
|
+
## Authentication
|
|
42
|
+
|
|
43
|
+
1. Create a Google Cloud project and enable the **Google Calendar API**
|
|
44
|
+
2. Create OAuth 2.0 credentials (Desktop app) or a service account
|
|
45
|
+
3. For OAuth user access, obtain a **refresh token** with calendar scope
|
|
46
|
+
4. Paste the JSON into `GOOGLE_CALENDAR_CREDENTIALS` (inline or file path)
|
|
47
|
+
|
|
48
|
+
Docs: [Google Calendar API auth](https://developers.google.com/calendar/api/guides/auth)
|
|
49
|
+
|
|
50
|
+
## MCP prompt workflows
|
|
51
|
+
|
|
52
|
+
| Prompt | Purpose |
|
|
53
|
+
|--------|---------|
|
|
54
|
+
| `today_agenda` | Chronological summary of today's events |
|
|
55
|
+
| `week_ahead_overview` | Next 7 days — conflicts and gaps |
|
|
56
|
+
| `meeting_prep_brief` | Prep brief for a specific event |
|
|
57
|
+
| `find_meeting_time` | Mutual free slots across calendars |
|
|
58
|
+
| `scheduling_conflict_scan` | Overlaps and tight turnarounds |
|
|
59
|
+
| `focus_time_planner` | Suggest deep-work blocks |
|
|
60
|
+
|
|
61
|
+
## Browsable resources
|
|
62
|
+
|
|
63
|
+
| URI | Content |
|
|
64
|
+
|-----|---------|
|
|
65
|
+
| `gcal://catalog` | Tool/prompt/resource index |
|
|
66
|
+
| `gcal://calendars` | All calendars |
|
|
67
|
+
| `gcal://calendar/{calendar_id}` | Calendar detail |
|
|
68
|
+
| `gcal://calendar/{calendar_id}/events` | Upcoming events |
|
|
69
|
+
|
|
70
|
+
## Tool list
|
|
71
|
+
|
|
72
|
+
- **Calendars** — `gcal_list_calendars`, `gcal_get_calendar`
|
|
73
|
+
- **Events** — list, get, create, update, delete, search, upcoming, quick-add
|
|
74
|
+
- **Scheduling** — `gcal_find_free_slots`, `gcal_rsvp_event`
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cp .env.example .env # add GOOGLE_CALENDAR_CREDENTIALS
|
|
80
|
+
pnpm install && pnpm build
|
|
81
|
+
pnpm verify
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { OAuth2Client } from "google-auth-library";
|
|
6
|
+
import { google } from "googleapis";
|
|
7
|
+
var CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar";
|
|
8
|
+
var GoogleCalendarError = class extends Error {
|
|
9
|
+
constructor(message, status, details) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.details = details;
|
|
13
|
+
this.name = "GoogleCalendarError";
|
|
14
|
+
}
|
|
15
|
+
status;
|
|
16
|
+
details;
|
|
17
|
+
};
|
|
18
|
+
function parseCredentials(raw) {
|
|
19
|
+
const trimmed = raw.trim();
|
|
20
|
+
if (!trimmed) {
|
|
21
|
+
throw new Error("GOOGLE_CALENDAR_CREDENTIALS is empty");
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.startsWith("{")) {
|
|
24
|
+
return JSON.parse(trimmed);
|
|
25
|
+
}
|
|
26
|
+
return JSON.parse(readFileSync(trimmed, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
function createAuth(credentials) {
|
|
29
|
+
if (credentials.type === "service_account") {
|
|
30
|
+
return new google.auth.GoogleAuth({
|
|
31
|
+
credentials,
|
|
32
|
+
scopes: [CALENDAR_SCOPE]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const clientId = String(credentials.client_id ?? "");
|
|
36
|
+
const clientSecret = String(credentials.client_secret ?? "");
|
|
37
|
+
const refreshToken = String(credentials.refresh_token ?? "");
|
|
38
|
+
if (!clientId || !clientSecret || !refreshToken) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"OAuth credentials require client_id, client_secret, and refresh_token (or a service_account JSON)"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const oauth2 = new OAuth2Client(clientId, clientSecret);
|
|
44
|
+
oauth2.setCredentials({ refresh_token: refreshToken });
|
|
45
|
+
return oauth2;
|
|
46
|
+
}
|
|
47
|
+
async function wrapApi(fn) {
|
|
48
|
+
try {
|
|
49
|
+
return await fn();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const err = error;
|
|
52
|
+
const details = err.response?.data !== void 0 ? JSON.stringify(err.response.data) : void 0;
|
|
53
|
+
throw new GoogleCalendarError(err.message ?? "Google Calendar API error", err.code, details);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var GoogleCalendarClient = class {
|
|
57
|
+
constructor(calendar, defaultCalendarId) {
|
|
58
|
+
this.defaultCalendarId = defaultCalendarId;
|
|
59
|
+
this.calendar = calendar;
|
|
60
|
+
}
|
|
61
|
+
defaultCalendarId;
|
|
62
|
+
calendar;
|
|
63
|
+
resolveCalendarId(calendarId) {
|
|
64
|
+
return calendarId?.trim() || this.defaultCalendarId;
|
|
65
|
+
}
|
|
66
|
+
async listCalendars(maxResults = 50) {
|
|
67
|
+
return wrapApi(async () => {
|
|
68
|
+
const result = await this.calendar.calendarList.list({ maxResults });
|
|
69
|
+
return (result.data.items ?? []).map(mapCalendar);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async getCalendar(calendarId) {
|
|
73
|
+
return wrapApi(async () => {
|
|
74
|
+
const result = await this.calendar.calendarList.get({ calendarId });
|
|
75
|
+
return mapCalendar(result.data);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async listEvents(options) {
|
|
79
|
+
const calendarId = this.resolveCalendarId(options.calendarId);
|
|
80
|
+
return wrapApi(async () => {
|
|
81
|
+
const result = await this.calendar.events.list({
|
|
82
|
+
calendarId,
|
|
83
|
+
timeMin: options.timeMin,
|
|
84
|
+
timeMax: options.timeMax,
|
|
85
|
+
maxResults: options.maxResults ?? 25,
|
|
86
|
+
q: options.q,
|
|
87
|
+
singleEvents: options.singleEvents ?? true,
|
|
88
|
+
orderBy: options.orderBy ?? (options.singleEvents !== false ? "startTime" : void 0)
|
|
89
|
+
});
|
|
90
|
+
return (result.data.items ?? []).map(mapEvent);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async getEvent(calendarId, eventId) {
|
|
94
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
95
|
+
return wrapApi(async () => {
|
|
96
|
+
const result = await this.calendar.events.get({ calendarId: calId, eventId });
|
|
97
|
+
return mapEvent(result.data);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async createEvent(calendarId, event) {
|
|
101
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
102
|
+
return wrapApi(async () => {
|
|
103
|
+
const result = await this.calendar.events.insert({
|
|
104
|
+
calendarId: calId,
|
|
105
|
+
requestBody: event
|
|
106
|
+
});
|
|
107
|
+
return mapEvent(result.data);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async updateEvent(calendarId, eventId, event) {
|
|
111
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
112
|
+
return wrapApi(async () => {
|
|
113
|
+
const result = await this.calendar.events.patch({
|
|
114
|
+
calendarId: calId,
|
|
115
|
+
eventId,
|
|
116
|
+
requestBody: event
|
|
117
|
+
});
|
|
118
|
+
return mapEvent(result.data);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async deleteEvent(calendarId, eventId) {
|
|
122
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
123
|
+
await wrapApi(async () => {
|
|
124
|
+
await this.calendar.events.delete({ calendarId: calId, eventId });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async searchEvents(calendarId, query, maxResults = 25) {
|
|
128
|
+
return this.listEvents({
|
|
129
|
+
calendarId,
|
|
130
|
+
q: query,
|
|
131
|
+
maxResults,
|
|
132
|
+
singleEvents: true,
|
|
133
|
+
orderBy: "startTime"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async quickAddEvent(calendarId, text) {
|
|
137
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
138
|
+
return wrapApi(async () => {
|
|
139
|
+
const result = await this.calendar.events.quickAdd({
|
|
140
|
+
calendarId: calId,
|
|
141
|
+
text
|
|
142
|
+
});
|
|
143
|
+
return mapEvent(result.data);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async findFreeSlots(options) {
|
|
147
|
+
return wrapApi(async () => {
|
|
148
|
+
const result = await this.calendar.freebusy.query({
|
|
149
|
+
requestBody: {
|
|
150
|
+
timeMin: options.timeMin,
|
|
151
|
+
timeMax: options.timeMax,
|
|
152
|
+
timeZone: options.timeZone,
|
|
153
|
+
items: options.calendarIds.map((id) => ({ id }))
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
const busy = {};
|
|
157
|
+
for (const [calendarId, data] of Object.entries(result.data.calendars ?? {})) {
|
|
158
|
+
busy[calendarId] = (data.busy ?? []).map((slot) => ({
|
|
159
|
+
start: slot.start ?? "",
|
|
160
|
+
end: slot.end ?? ""
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
return { busy, timeMin: options.timeMin, timeMax: options.timeMax };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async listUpcomingEvents(calendarId, maxResults = 10) {
|
|
167
|
+
return this.listEvents({
|
|
168
|
+
calendarId,
|
|
169
|
+
timeMin: (/* @__PURE__ */ new Date()).toISOString(),
|
|
170
|
+
maxResults,
|
|
171
|
+
singleEvents: true,
|
|
172
|
+
orderBy: "startTime"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async rsvpEvent(calendarId, eventId, responseStatus, attendeeEmail) {
|
|
176
|
+
const calId = this.resolveCalendarId(calendarId);
|
|
177
|
+
return wrapApi(async () => {
|
|
178
|
+
const existing = await this.calendar.events.get({ calendarId: calId, eventId });
|
|
179
|
+
const attendees = [...existing.data.attendees ?? []];
|
|
180
|
+
const selfEmail = attendeeEmail ?? existing.data.organizer?.email;
|
|
181
|
+
if (!selfEmail) {
|
|
182
|
+
throw new GoogleCalendarError("Could not determine attendee email for RSVP");
|
|
183
|
+
}
|
|
184
|
+
const index = attendees.findIndex((a) => a.email === selfEmail);
|
|
185
|
+
if (index >= 0) {
|
|
186
|
+
attendees[index] = { ...attendees[index], responseStatus };
|
|
187
|
+
} else {
|
|
188
|
+
attendees.push({ email: selfEmail, responseStatus });
|
|
189
|
+
}
|
|
190
|
+
const result = await this.calendar.events.patch({
|
|
191
|
+
calendarId: calId,
|
|
192
|
+
eventId,
|
|
193
|
+
requestBody: { attendees }
|
|
194
|
+
});
|
|
195
|
+
return mapEvent(result.data);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
function mapCalendar(item) {
|
|
200
|
+
return {
|
|
201
|
+
id: item.id ?? "",
|
|
202
|
+
summary: item.summary ?? item.id ?? "Untitled",
|
|
203
|
+
description: item.description,
|
|
204
|
+
timeZone: item.timeZone,
|
|
205
|
+
primary: item.primary,
|
|
206
|
+
accessRole: item.accessRole
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function mapEvent(item) {
|
|
210
|
+
return {
|
|
211
|
+
id: item.id ?? "",
|
|
212
|
+
summary: item.summary,
|
|
213
|
+
description: item.description,
|
|
214
|
+
location: item.location,
|
|
215
|
+
htmlLink: item.htmlLink,
|
|
216
|
+
status: item.status,
|
|
217
|
+
start: item.start,
|
|
218
|
+
end: item.end,
|
|
219
|
+
attendees: item.attendees,
|
|
220
|
+
organizer: item.organizer
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async function createClientFromEnv() {
|
|
224
|
+
const raw = process.env.GOOGLE_CALENDAR_CREDENTIALS;
|
|
225
|
+
if (!raw) {
|
|
226
|
+
throw new Error("Missing GOOGLE_CALENDAR_CREDENTIALS environment variable");
|
|
227
|
+
}
|
|
228
|
+
const credentials = parseCredentials(raw);
|
|
229
|
+
const auth = createAuth(credentials);
|
|
230
|
+
const calendar = google.calendar({ version: "v3", auth });
|
|
231
|
+
const defaultCalendarId = process.env.GOOGLE_CALENDAR_ID?.trim() || "primary";
|
|
232
|
+
return new GoogleCalendarClient(calendar, defaultCalendarId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export {
|
|
236
|
+
GoogleCalendarError,
|
|
237
|
+
createClientFromEnv
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=chunk-C2MOCEV4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { OAuth2Client } from \"google-auth-library\";\nimport { google, type calendar_v3 } from \"googleapis\";\n\nconst CALENDAR_SCOPE = \"https://www.googleapis.com/auth/calendar\";\n\nexport interface CalendarSummary {\n id: string;\n summary: string;\n description?: string | null;\n timeZone?: string | null;\n primary?: boolean | null;\n accessRole?: string | null;\n}\n\nexport interface EventSummary {\n id: string;\n summary?: string | null;\n description?: string | null;\n location?: string | null;\n htmlLink?: string | null;\n status?: string | null;\n start?: calendar_v3.Schema$EventDateTime;\n end?: calendar_v3.Schema$EventDateTime;\n attendees?: calendar_v3.Schema$EventAttendee[];\n organizer?: calendar_v3.Schema$Event[\"organizer\"];\n}\n\nexport class GoogleCalendarError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly details?: string,\n ) {\n super(message);\n this.name = \"GoogleCalendarError\";\n }\n}\n\nfunction parseCredentials(raw: string): Record<string, unknown> {\n const trimmed = raw.trim();\n if (!trimmed) {\n throw new Error(\"GOOGLE_CALENDAR_CREDENTIALS is empty\");\n }\n if (trimmed.startsWith(\"{\")) {\n return JSON.parse(trimmed) as Record<string, unknown>;\n }\n return JSON.parse(readFileSync(trimmed, \"utf8\")) as Record<string, unknown>;\n}\n\nfunction createAuth(credentials: Record<string, unknown>) {\n if (credentials.type === \"service_account\") {\n return new google.auth.GoogleAuth({\n credentials,\n scopes: [CALENDAR_SCOPE],\n });\n }\n\n const clientId = String(credentials.client_id ?? \"\");\n const clientSecret = String(credentials.client_secret ?? \"\");\n const refreshToken = String(credentials.refresh_token ?? \"\");\n\n if (!clientId || !clientSecret || !refreshToken) {\n throw new Error(\n \"OAuth credentials require client_id, client_secret, and refresh_token (or a service_account JSON)\",\n );\n }\n\n const oauth2 = new OAuth2Client(clientId, clientSecret);\n oauth2.setCredentials({ refresh_token: refreshToken });\n return oauth2;\n}\n\nasync function wrapApi<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n const err = error as { message?: string; code?: number; response?: { data?: unknown } };\n const details =\n err.response?.data !== undefined ? JSON.stringify(err.response.data) : undefined;\n throw new GoogleCalendarError(err.message ?? \"Google Calendar API error\", err.code, details);\n }\n}\n\nexport class GoogleCalendarClient {\n private readonly calendar: calendar_v3.Calendar;\n\n constructor(\n calendar: calendar_v3.Calendar,\n readonly defaultCalendarId: string,\n ) {\n this.calendar = calendar;\n }\n\n resolveCalendarId(calendarId?: string): string {\n return calendarId?.trim() || this.defaultCalendarId;\n }\n\n async listCalendars(maxResults = 50): Promise<CalendarSummary[]> {\n return wrapApi(async () => {\n const result = await this.calendar.calendarList.list({ maxResults });\n return (result.data.items ?? []).map(mapCalendar);\n });\n }\n\n async getCalendar(calendarId: string): Promise<CalendarSummary> {\n return wrapApi(async () => {\n const result = await this.calendar.calendarList.get({ calendarId });\n return mapCalendar(result.data);\n });\n }\n\n async listEvents(options: {\n calendarId?: string;\n timeMin?: string;\n timeMax?: string;\n maxResults?: number;\n q?: string;\n singleEvents?: boolean;\n orderBy?: \"startTime\" | \"updated\";\n }): Promise<EventSummary[]> {\n const calendarId = this.resolveCalendarId(options.calendarId);\n return wrapApi(async () => {\n const result = await this.calendar.events.list({\n calendarId,\n timeMin: options.timeMin,\n timeMax: options.timeMax,\n maxResults: options.maxResults ?? 25,\n q: options.q,\n singleEvents: options.singleEvents ?? true,\n orderBy: options.orderBy ?? (options.singleEvents !== false ? \"startTime\" : undefined),\n });\n return (result.data.items ?? []).map(mapEvent);\n });\n }\n\n async getEvent(calendarId: string | undefined, eventId: string): Promise<EventSummary> {\n const calId = this.resolveCalendarId(calendarId);\n return wrapApi(async () => {\n const result = await this.calendar.events.get({ calendarId: calId, eventId });\n return mapEvent(result.data);\n });\n }\n\n async createEvent(\n calendarId: string | undefined,\n event: calendar_v3.Schema$Event,\n ): Promise<EventSummary> {\n const calId = this.resolveCalendarId(calendarId);\n return wrapApi(async () => {\n const result = await this.calendar.events.insert({\n calendarId: calId,\n requestBody: event,\n });\n return mapEvent(result.data);\n });\n }\n\n async updateEvent(\n calendarId: string | undefined,\n eventId: string,\n event: calendar_v3.Schema$Event,\n ): Promise<EventSummary> {\n const calId = this.resolveCalendarId(calendarId);\n return wrapApi(async () => {\n const result = await this.calendar.events.patch({\n calendarId: calId,\n eventId,\n requestBody: event,\n });\n return mapEvent(result.data);\n });\n }\n\n async deleteEvent(calendarId: string | undefined, eventId: string): Promise<void> {\n const calId = this.resolveCalendarId(calendarId);\n await wrapApi(async () => {\n await this.calendar.events.delete({ calendarId: calId, eventId });\n });\n }\n\n async searchEvents(calendarId: string | undefined, query: string, maxResults = 25): Promise<EventSummary[]> {\n return this.listEvents({\n calendarId,\n q: query,\n maxResults,\n singleEvents: true,\n orderBy: \"startTime\",\n });\n }\n\n async quickAddEvent(calendarId: string | undefined, text: string): Promise<EventSummary> {\n const calId = this.resolveCalendarId(calendarId);\n return wrapApi(async () => {\n const result = await this.calendar.events.quickAdd({\n calendarId: calId,\n text,\n });\n return mapEvent(result.data);\n });\n }\n\n async findFreeSlots(options: {\n calendarIds: string[];\n timeMin: string;\n timeMax: string;\n timeZone?: string;\n }): Promise<{ busy: Record<string, { start: string; end: string }[]>; timeMin: string; timeMax: string }> {\n return wrapApi(async () => {\n const result = await this.calendar.freebusy.query({\n requestBody: {\n timeMin: options.timeMin,\n timeMax: options.timeMax,\n timeZone: options.timeZone,\n items: options.calendarIds.map((id) => ({ id })),\n },\n });\n\n const busy: Record<string, { start: string; end: string }[]> = {};\n for (const [calendarId, data] of Object.entries(result.data.calendars ?? {})) {\n busy[calendarId] = (data.busy ?? []).map((slot) => ({\n start: slot.start ?? \"\",\n end: slot.end ?? \"\",\n }));\n }\n\n return { busy, timeMin: options.timeMin, timeMax: options.timeMax };\n });\n }\n\n async listUpcomingEvents(calendarId: string | undefined, maxResults = 10): Promise<EventSummary[]> {\n return this.listEvents({\n calendarId,\n timeMin: new Date().toISOString(),\n maxResults,\n singleEvents: true,\n orderBy: \"startTime\",\n });\n }\n\n async rsvpEvent(\n calendarId: string | undefined,\n eventId: string,\n responseStatus: \"accepted\" | \"declined\" | \"tentative\",\n attendeeEmail?: string,\n ): Promise<EventSummary> {\n const calId = this.resolveCalendarId(calendarId);\n return wrapApi(async () => {\n const existing = await this.calendar.events.get({ calendarId: calId, eventId });\n const attendees = [...(existing.data.attendees ?? [])];\n const selfEmail = attendeeEmail ?? existing.data.organizer?.email;\n\n if (!selfEmail) {\n throw new GoogleCalendarError(\"Could not determine attendee email for RSVP\");\n }\n\n const index = attendees.findIndex((a) => a.email === selfEmail);\n if (index >= 0) {\n attendees[index] = { ...attendees[index], responseStatus };\n } else {\n attendees.push({ email: selfEmail, responseStatus });\n }\n\n const result = await this.calendar.events.patch({\n calendarId: calId,\n eventId,\n requestBody: { attendees },\n });\n return mapEvent(result.data);\n });\n }\n}\n\nfunction mapCalendar(item: calendar_v3.Schema$CalendarListEntry): CalendarSummary {\n return {\n id: item.id ?? \"\",\n summary: item.summary ?? item.id ?? \"Untitled\",\n description: item.description,\n timeZone: item.timeZone,\n primary: item.primary,\n accessRole: item.accessRole,\n };\n}\n\nfunction mapEvent(item: calendar_v3.Schema$Event): EventSummary {\n return {\n id: item.id ?? \"\",\n summary: item.summary,\n description: item.description,\n location: item.location,\n htmlLink: item.htmlLink,\n status: item.status,\n start: item.start,\n end: item.end,\n attendees: item.attendees,\n organizer: item.organizer,\n };\n}\n\nexport async function createClientFromEnv(): Promise<GoogleCalendarClient> {\n const raw = process.env.GOOGLE_CALENDAR_CREDENTIALS;\n if (!raw) {\n throw new Error(\"Missing GOOGLE_CALENDAR_CREDENTIALS environment variable\");\n }\n\n const credentials = parseCredentials(raw);\n const auth = createAuth(credentials);\n const calendar = google.calendar({ version: \"v3\", auth });\n const defaultCalendarId = process.env.GOOGLE_CALENDAR_ID?.trim() || \"primary\";\n\n return new GoogleCalendarClient(calendar, defaultCalendarId);\n}\n"],"mappings":";;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,cAAgC;AAEzC,IAAM,iBAAiB;AAwBhB,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YACE,SACS,QACA,SACT;AACA,UAAM,OAAO;AAHJ;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAAA,EACA;AAKb;AAEA,SAAS,iBAAiB,KAAsC;AAC9D,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,MAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B;AACA,SAAO,KAAK,MAAM,aAAa,SAAS,MAAM,CAAC;AACjD;AAEA,SAAS,WAAW,aAAsC;AACxD,MAAI,YAAY,SAAS,mBAAmB;AAC1C,WAAO,IAAI,OAAO,KAAK,WAAW;AAAA,MAChC;AAAA,MACA,QAAQ,CAAC,cAAc;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,OAAO,YAAY,aAAa,EAAE;AACnD,QAAM,eAAe,OAAO,YAAY,iBAAiB,EAAE;AAC3D,QAAM,eAAe,OAAO,YAAY,iBAAiB,EAAE;AAE3D,MAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,cAAc;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,aAAa,UAAU,YAAY;AACtD,SAAO,eAAe,EAAE,eAAe,aAAa,CAAC;AACrD,SAAO;AACT;AAEA,eAAe,QAAW,IAAkC;AAC1D,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,SAAS,OAAO;AACd,UAAM,MAAM;AACZ,UAAM,UACJ,IAAI,UAAU,SAAS,SAAY,KAAK,UAAU,IAAI,SAAS,IAAI,IAAI;AACzE,UAAM,IAAI,oBAAoB,IAAI,WAAW,6BAA6B,IAAI,MAAM,OAAO;AAAA,EAC7F;AACF;AAEO,IAAM,uBAAN,MAA2B;AAAA,EAGhC,YACE,UACS,mBACT;AADS;AAET,SAAK,WAAW;AAAA,EAClB;AAAA,EAHW;AAAA,EAJM;AAAA,EASjB,kBAAkB,YAA6B;AAC7C,WAAO,YAAY,KAAK,KAAK,KAAK;AAAA,EACpC;AAAA,EAEA,MAAM,cAAc,aAAa,IAAgC;AAC/D,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,aAAa,KAAK,EAAE,WAAW,CAAC;AACnE,cAAQ,OAAO,KAAK,SAAS,CAAC,GAAG,IAAI,WAAW;AAAA,IAClD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,YAA8C;AAC9D,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,aAAa,IAAI,EAAE,WAAW,CAAC;AAClE,aAAO,YAAY,OAAO,IAAI;AAAA,IAChC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,SAQW;AAC1B,UAAM,aAAa,KAAK,kBAAkB,QAAQ,UAAU;AAC5D,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,KAAK;AAAA,QAC7C;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,SAAS,QAAQ;AAAA,QACjB,YAAY,QAAQ,cAAc;AAAA,QAClC,GAAG,QAAQ;AAAA,QACX,cAAc,QAAQ,gBAAgB;AAAA,QACtC,SAAS,QAAQ,YAAY,QAAQ,iBAAiB,QAAQ,cAAc;AAAA,MAC9E,CAAC;AACD,cAAQ,OAAO,KAAK,SAAS,CAAC,GAAG,IAAI,QAAQ;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,YAAgC,SAAwC;AACrF,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,IAAI,EAAE,YAAY,OAAO,QAAQ,CAAC;AAC5E,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YACJ,YACA,OACuB;AACvB,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,OAAO;AAAA,QAC/C,YAAY;AAAA,QACZ,aAAa;AAAA,MACf,CAAC;AACD,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YACJ,YACA,SACA,OACuB;AACvB,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,MAAM;AAAA,QAC9C,YAAY;AAAA,QACZ;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AACD,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,YAAgC,SAAgC;AAChF,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,UAAM,QAAQ,YAAY;AACxB,YAAM,KAAK,SAAS,OAAO,OAAO,EAAE,YAAY,OAAO,QAAQ,CAAC;AAAA,IAClE,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,YAAgC,OAAe,aAAa,IAA6B;AAC1G,WAAO,KAAK,WAAW;AAAA,MACrB;AAAA,MACA,GAAG;AAAA,MACH;AAAA,MACA,cAAc;AAAA,MACd,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,YAAgC,MAAqC;AACvF,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,SAAS;AAAA,QACjD,YAAY;AAAA,QACZ;AAAA,MACF,CAAC;AACD,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,SAKsF;AACxG,WAAO,QAAQ,YAAY;AACzB,YAAM,SAAS,MAAM,KAAK,SAAS,SAAS,MAAM;AAAA,QAChD,aAAa;AAAA,UACX,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ;AAAA,UACjB,UAAU,QAAQ;AAAA,UAClB,OAAO,QAAQ,YAAY,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;AAAA,QACjD;AAAA,MACF,CAAC;AAED,YAAM,OAAyD,CAAC;AAChE,iBAAW,CAAC,YAAY,IAAI,KAAK,OAAO,QAAQ,OAAO,KAAK,aAAa,CAAC,CAAC,GAAG;AAC5E,aAAK,UAAU,KAAK,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU;AAAA,UAClD,OAAO,KAAK,SAAS;AAAA,UACrB,KAAK,KAAK,OAAO;AAAA,QACnB,EAAE;AAAA,MACJ;AAEA,aAAO,EAAE,MAAM,SAAS,QAAQ,SAAS,SAAS,QAAQ,QAAQ;AAAA,IACpE,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,mBAAmB,YAAgC,aAAa,IAA6B;AACjG,WAAO,KAAK,WAAW;AAAA,MACrB;AAAA,MACA,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,MAChC;AAAA,MACA,cAAc;AAAA,MACd,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UACJ,YACA,SACA,gBACA,eACuB;AACvB,UAAM,QAAQ,KAAK,kBAAkB,UAAU;AAC/C,WAAO,QAAQ,YAAY;AACzB,YAAM,WAAW,MAAM,KAAK,SAAS,OAAO,IAAI,EAAE,YAAY,OAAO,QAAQ,CAAC;AAC9E,YAAM,YAAY,CAAC,GAAI,SAAS,KAAK,aAAa,CAAC,CAAE;AACrD,YAAM,YAAY,iBAAiB,SAAS,KAAK,WAAW;AAE5D,UAAI,CAAC,WAAW;AACd,cAAM,IAAI,oBAAoB,6CAA6C;AAAA,MAC7E;AAEA,YAAM,QAAQ,UAAU,UAAU,CAAC,MAAM,EAAE,UAAU,SAAS;AAC9D,UAAI,SAAS,GAAG;AACd,kBAAU,KAAK,IAAI,EAAE,GAAG,UAAU,KAAK,GAAG,eAAe;AAAA,MAC3D,OAAO;AACL,kBAAU,KAAK,EAAE,OAAO,WAAW,eAAe,CAAC;AAAA,MACrD;AAEA,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,MAAM;AAAA,QAC9C,YAAY;AAAA,QACZ;AAAA,QACA,aAAa,EAAE,UAAU;AAAA,MAC3B,CAAC;AACD,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,YAAY,MAA6D;AAChF,SAAO;AAAA,IACL,IAAI,KAAK,MAAM;AAAA,IACf,SAAS,KAAK,WAAW,KAAK,MAAM;AAAA,IACpC,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,SAAS,KAAK;AAAA,IACd,YAAY,KAAK;AAAA,EACnB;AACF;AAEA,SAAS,SAAS,MAA8C;AAC9D,SAAO;AAAA,IACL,IAAI,KAAK,MAAM;AAAA,IACf,SAAS,KAAK;AAAA,IACd,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,UAAU,KAAK;AAAA,IACf,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ,KAAK,KAAK;AAAA,IACV,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAClB;AACF;AAEA,eAAsB,sBAAqD;AACzE,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AAEA,QAAM,cAAc,iBAAiB,GAAG;AACxC,QAAM,OAAO,WAAW,WAAW;AACnC,QAAM,WAAW,OAAO,SAAS,EAAE,SAAS,MAAM,KAAK,CAAC;AACxD,QAAM,oBAAoB,QAAQ,IAAI,oBAAoB,KAAK,KAAK;AAEpE,SAAO,IAAI,qBAAqB,UAAU,iBAAiB;AAC7D;","names":[]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
GoogleCalendarError,
|
|
4
|
+
createClientFromEnv
|
|
5
|
+
} from "./chunk-C2MOCEV4.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
|
|
11
|
+
// src/mcp/prompts.ts
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var calendarArg = {
|
|
14
|
+
calendar_id: z.string().optional().describe("Calendar ID (default: primary).")
|
|
15
|
+
};
|
|
16
|
+
function userMessage(text) {
|
|
17
|
+
return { role: "user", content: { type: "text", text } };
|
|
18
|
+
}
|
|
19
|
+
function registerGoogleCalendarPrompts(server2, _client) {
|
|
20
|
+
server2.registerPrompt(
|
|
21
|
+
"today_agenda",
|
|
22
|
+
{
|
|
23
|
+
title: "Today's agenda",
|
|
24
|
+
description: "Summarize today's calendar events with times and prep notes.",
|
|
25
|
+
argsSchema: calendarArg
|
|
26
|
+
},
|
|
27
|
+
async ({ calendar_id }) => {
|
|
28
|
+
const cal = calendar_id ?? "primary";
|
|
29
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
30
|
+
return {
|
|
31
|
+
messages: [
|
|
32
|
+
userMessage(
|
|
33
|
+
`For calendar ${cal}: call gcal_list_events with time_min ${today}T00:00:00Z and time_max ${today}T23:59:59Z. Output a chronological agenda with start time, title, location, and any prep actions.`
|
|
34
|
+
)
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
server2.registerPrompt(
|
|
40
|
+
"week_ahead_overview",
|
|
41
|
+
{
|
|
42
|
+
title: "Week ahead overview",
|
|
43
|
+
description: "Overview of the next 7 days \u2014 key meetings, conflicts, and focus blocks.",
|
|
44
|
+
argsSchema: calendarArg
|
|
45
|
+
},
|
|
46
|
+
async ({ calendar_id }) => {
|
|
47
|
+
const cal = calendar_id ?? "primary";
|
|
48
|
+
const start = (/* @__PURE__ */ new Date()).toISOString();
|
|
49
|
+
const end = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString();
|
|
50
|
+
return {
|
|
51
|
+
messages: [
|
|
52
|
+
userMessage(
|
|
53
|
+
`For calendar ${cal}: call gcal_list_events from ${start} to ${end}. Group by day. Flag back-to-back meetings, long gaps, and anything that looks like a conflict.`
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
server2.registerPrompt(
|
|
60
|
+
"meeting_prep_brief",
|
|
61
|
+
{
|
|
62
|
+
title: "Meeting prep brief",
|
|
63
|
+
description: "Brief for a specific upcoming event \u2014 attendees, agenda hints, logistics.",
|
|
64
|
+
argsSchema: {
|
|
65
|
+
...calendarArg,
|
|
66
|
+
event_id: z.string().describe("Event ID to brief.")
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async ({ calendar_id, event_id }) => {
|
|
70
|
+
return {
|
|
71
|
+
messages: [
|
|
72
|
+
userMessage(
|
|
73
|
+
`Call gcal_get_event for event ${event_id} on calendar ${calendar_id ?? "primary"}. Produce a prep brief: time, attendees, location/video link, description highlights, and suggested talking points.`
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
server2.registerPrompt(
|
|
80
|
+
"find_meeting_time",
|
|
81
|
+
{
|
|
82
|
+
title: "Find meeting time",
|
|
83
|
+
description: "Find mutual free slots across calendars for scheduling.",
|
|
84
|
+
argsSchema: {
|
|
85
|
+
calendar_ids: z.array(z.string()).min(1).describe("Calendars to check for availability."),
|
|
86
|
+
duration_minutes: z.number().int().min(15).max(480).optional()
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async ({ calendar_ids, duration_minutes }) => {
|
|
90
|
+
const start = (/* @__PURE__ */ new Date()).toISOString();
|
|
91
|
+
const end = new Date(Date.now() + 5 * 24 * 60 * 60 * 1e3).toISOString();
|
|
92
|
+
const duration = duration_minutes ?? 30;
|
|
93
|
+
return {
|
|
94
|
+
messages: [
|
|
95
|
+
userMessage(
|
|
96
|
+
`Call gcal_find_free_slots for calendars ${calendar_ids.join(", ")} between ${start} and ${end}. Suggest ${duration}-minute meeting slots during business hours, ranked by convenience.`
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
server2.registerPrompt(
|
|
103
|
+
"scheduling_conflict_scan",
|
|
104
|
+
{
|
|
105
|
+
title: "Scheduling conflict scan",
|
|
106
|
+
description: "Detect overlapping events and tight turnarounds in a date range.",
|
|
107
|
+
argsSchema: {
|
|
108
|
+
...calendarArg,
|
|
109
|
+
days: z.number().int().min(1).max(14).optional()
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async ({ calendar_id, days }) => {
|
|
113
|
+
const n = days ?? 3;
|
|
114
|
+
const start = (/* @__PURE__ */ new Date()).toISOString();
|
|
115
|
+
const end = new Date(Date.now() + n * 24 * 60 * 60 * 1e3).toISOString();
|
|
116
|
+
return {
|
|
117
|
+
messages: [
|
|
118
|
+
userMessage(
|
|
119
|
+
`For calendar ${calendar_id ?? "primary"}: list events from ${start} to ${end}. Identify overlaps, meetings with <15 min gaps, and events missing locations or video links.`
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
server2.registerPrompt(
|
|
126
|
+
"focus_time_planner",
|
|
127
|
+
{
|
|
128
|
+
title: "Focus time planner",
|
|
129
|
+
description: "Suggest open blocks for deep work based on free/busy data.",
|
|
130
|
+
argsSchema: calendarArg
|
|
131
|
+
},
|
|
132
|
+
async ({ calendar_id }) => {
|
|
133
|
+
const cal = calendar_id ?? "primary";
|
|
134
|
+
const start = (/* @__PURE__ */ new Date()).toISOString();
|
|
135
|
+
const end = new Date(Date.now() + 3 * 24 * 60 * 60 * 1e3).toISOString();
|
|
136
|
+
return {
|
|
137
|
+
messages: [
|
|
138
|
+
userMessage(
|
|
139
|
+
`Call gcal_find_free_slots for calendar ${cal} between ${start} and ${end}. Recommend 2\u20133 focus blocks of at least 90 minutes during weekday business hours. Avoid lunch hour.`
|
|
140
|
+
)
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/mcp/resources.ts
|
|
148
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
149
|
+
|
|
150
|
+
// src/mcp/catalog.ts
|
|
151
|
+
var GCAL_TOOL_NAMES = [
|
|
152
|
+
"gcal_list_calendars",
|
|
153
|
+
"gcal_get_calendar",
|
|
154
|
+
"gcal_list_events",
|
|
155
|
+
"gcal_get_event",
|
|
156
|
+
"gcal_create_event",
|
|
157
|
+
"gcal_update_event",
|
|
158
|
+
"gcal_delete_event",
|
|
159
|
+
"gcal_search_events",
|
|
160
|
+
"gcal_find_free_slots",
|
|
161
|
+
"gcal_quick_add_event",
|
|
162
|
+
"gcal_list_upcoming_events",
|
|
163
|
+
"gcal_rsvp_event"
|
|
164
|
+
];
|
|
165
|
+
var GCAL_PROMPT_NAMES = [
|
|
166
|
+
"today_agenda",
|
|
167
|
+
"week_ahead_overview",
|
|
168
|
+
"meeting_prep_brief",
|
|
169
|
+
"find_meeting_time",
|
|
170
|
+
"scheduling_conflict_scan",
|
|
171
|
+
"focus_time_planner"
|
|
172
|
+
];
|
|
173
|
+
var GCAL_RESOURCE_URIS = [
|
|
174
|
+
"gcal://catalog",
|
|
175
|
+
"gcal://calendars",
|
|
176
|
+
"gcal://calendar/{calendar_id}",
|
|
177
|
+
"gcal://calendar/{calendar_id}/events"
|
|
178
|
+
];
|
|
179
|
+
var GCAL_TOOL_COUNT = GCAL_TOOL_NAMES.length;
|
|
180
|
+
var GCAL_PROMPT_COUNT = GCAL_PROMPT_NAMES.length;
|
|
181
|
+
var GCAL_RESOURCE_TEMPLATE_COUNT = GCAL_RESOURCE_URIS.length;
|
|
182
|
+
|
|
183
|
+
// src/mcp/resources.ts
|
|
184
|
+
function textResource(uri, data) {
|
|
185
|
+
return {
|
|
186
|
+
contents: [
|
|
187
|
+
{
|
|
188
|
+
uri,
|
|
189
|
+
mimeType: "application/json",
|
|
190
|
+
text: JSON.stringify(data, null, 2)
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function param(value) {
|
|
196
|
+
return Array.isArray(value) ? value[0] : value;
|
|
197
|
+
}
|
|
198
|
+
function registerGoogleCalendarResources(server2, client2) {
|
|
199
|
+
server2.registerResource(
|
|
200
|
+
"gcal_catalog",
|
|
201
|
+
"gcal://catalog",
|
|
202
|
+
{
|
|
203
|
+
title: "Google Calendar MCP catalog",
|
|
204
|
+
description: "Tool, prompt, and resource index for mcp-wormhole Google Calendar.",
|
|
205
|
+
mimeType: "application/json"
|
|
206
|
+
},
|
|
207
|
+
async (uri) => textResource(uri.href, {
|
|
208
|
+
server: "mcp-wormhole-google-calendar",
|
|
209
|
+
tools: GCAL_TOOL_NAMES.length,
|
|
210
|
+
tool_names: GCAL_TOOL_NAMES,
|
|
211
|
+
prompts: GCAL_PROMPT_NAMES.length,
|
|
212
|
+
prompt_names: GCAL_PROMPT_NAMES,
|
|
213
|
+
resource_templates: GCAL_RESOURCE_URIS
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
server2.registerResource(
|
|
217
|
+
"gcal_calendars",
|
|
218
|
+
"gcal://calendars",
|
|
219
|
+
{
|
|
220
|
+
title: "All calendars",
|
|
221
|
+
description: "Browse calendars accessible to the authenticated account.",
|
|
222
|
+
mimeType: "application/json"
|
|
223
|
+
},
|
|
224
|
+
async (uri) => textResource(uri.href, { calendars: await client2.listCalendars(50) })
|
|
225
|
+
);
|
|
226
|
+
server2.registerResource(
|
|
227
|
+
"gcal_calendar",
|
|
228
|
+
new ResourceTemplate("gcal://calendar/{calendar_id}", {
|
|
229
|
+
list: async () => {
|
|
230
|
+
const calendars = await client2.listCalendars(50);
|
|
231
|
+
return {
|
|
232
|
+
resources: calendars.map((c) => ({
|
|
233
|
+
uri: `gcal://calendar/${c.id}`,
|
|
234
|
+
name: c.summary,
|
|
235
|
+
mimeType: "application/json"
|
|
236
|
+
}))
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
complete: {
|
|
240
|
+
calendar_id: async () => (await client2.listCalendars(50)).map((c) => c.id)
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
{
|
|
244
|
+
title: "Calendar",
|
|
245
|
+
description: "Calendar metadata and navigation links.",
|
|
246
|
+
mimeType: "application/json"
|
|
247
|
+
},
|
|
248
|
+
async (uri, { calendar_id }) => {
|
|
249
|
+
const id = param(calendar_id);
|
|
250
|
+
const calendar = await client2.getCalendar(id);
|
|
251
|
+
return textResource(uri.href, {
|
|
252
|
+
calendar_id: id,
|
|
253
|
+
calendar,
|
|
254
|
+
browse: {
|
|
255
|
+
events: `gcal://calendar/${id}/events`
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
server2.registerResource(
|
|
261
|
+
"gcal_calendar_events",
|
|
262
|
+
new ResourceTemplate("gcal://calendar/{calendar_id}/events", {
|
|
263
|
+
list: void 0,
|
|
264
|
+
complete: {
|
|
265
|
+
calendar_id: async () => (await client2.listCalendars(50)).map((c) => c.id)
|
|
266
|
+
}
|
|
267
|
+
}),
|
|
268
|
+
{
|
|
269
|
+
title: "Calendar events",
|
|
270
|
+
description: "Upcoming events for a calendar.",
|
|
271
|
+
mimeType: "application/json"
|
|
272
|
+
},
|
|
273
|
+
async (uri, { calendar_id }) => {
|
|
274
|
+
const id = param(calendar_id);
|
|
275
|
+
const events = await client2.listUpcomingEvents(id, 25);
|
|
276
|
+
return textResource(uri.href, { calendar_id: id, events });
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/mcp/tools.ts
|
|
282
|
+
import { z as z2 } from "zod";
|
|
283
|
+
|
|
284
|
+
// src/mcp/helpers.ts
|
|
285
|
+
function json(data) {
|
|
286
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
287
|
+
}
|
|
288
|
+
function toolError(error) {
|
|
289
|
+
if (error instanceof GoogleCalendarError) {
|
|
290
|
+
return json({
|
|
291
|
+
error: error.message,
|
|
292
|
+
status: error.status,
|
|
293
|
+
details: error.details
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return json({
|
|
297
|
+
error: error instanceof Error ? error.message : String(error)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function ok(message, data) {
|
|
301
|
+
return json(
|
|
302
|
+
data ? { message, ...typeof data === "object" && data ? data : { result: data } } : { message }
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/mcp/tools.ts
|
|
307
|
+
var calendarId = z2.string().optional().describe("Calendar ID (defaults to GOOGLE_CALENDAR_ID or primary).");
|
|
308
|
+
var eventId = z2.string().describe("Google Calendar event ID.");
|
|
309
|
+
var maxResults = z2.number().int().min(1).max(250).optional();
|
|
310
|
+
var isoDateTime = z2.string().describe("ISO 8601 datetime (e.g. 2026-06-23T09:00:00Z).");
|
|
311
|
+
function wrap(client2, fn) {
|
|
312
|
+
return async () => {
|
|
313
|
+
try {
|
|
314
|
+
return json(await fn());
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return toolError(error);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function registerGoogleCalendarTools(server2, client2) {
|
|
321
|
+
server2.registerTool(
|
|
322
|
+
"gcal_list_calendars",
|
|
323
|
+
{
|
|
324
|
+
title: "List calendars",
|
|
325
|
+
description: "All calendars accessible to the authenticated account.",
|
|
326
|
+
inputSchema: { max_results: maxResults.describe("Max calendars (default 50).") }
|
|
327
|
+
},
|
|
328
|
+
async ({ max_results }) => {
|
|
329
|
+
try {
|
|
330
|
+
return json({ calendars: await client2.listCalendars(max_results ?? 50) });
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return toolError(error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
server2.registerTool(
|
|
337
|
+
"gcal_get_calendar",
|
|
338
|
+
{
|
|
339
|
+
title: "Get calendar",
|
|
340
|
+
description: "Calendar metadata by ID.",
|
|
341
|
+
inputSchema: { calendar_id: z2.string().describe("Calendar ID.") }
|
|
342
|
+
},
|
|
343
|
+
async ({ calendar_id }) => wrap(client2, () => client2.getCalendar(calendar_id))()
|
|
344
|
+
);
|
|
345
|
+
server2.registerTool(
|
|
346
|
+
"gcal_list_events",
|
|
347
|
+
{
|
|
348
|
+
title: "List events",
|
|
349
|
+
description: "Events in a calendar, optionally filtered by time range or search query.",
|
|
350
|
+
inputSchema: {
|
|
351
|
+
calendar_id: calendarId,
|
|
352
|
+
time_min: isoDateTime.optional(),
|
|
353
|
+
time_max: isoDateTime.optional(),
|
|
354
|
+
max_results: maxResults.describe("Max events (default 25)."),
|
|
355
|
+
q: z2.string().optional().describe("Free-text search query.")
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
async ({ calendar_id, time_min, time_max, max_results, q }) => {
|
|
359
|
+
try {
|
|
360
|
+
return json({
|
|
361
|
+
events: await client2.listEvents({
|
|
362
|
+
calendarId: calendar_id,
|
|
363
|
+
timeMin: time_min,
|
|
364
|
+
timeMax: time_max,
|
|
365
|
+
maxResults: max_results ?? 25,
|
|
366
|
+
q
|
|
367
|
+
})
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return toolError(error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
server2.registerTool(
|
|
375
|
+
"gcal_get_event",
|
|
376
|
+
{
|
|
377
|
+
title: "Get event",
|
|
378
|
+
description: "Full event record by ID.",
|
|
379
|
+
inputSchema: { calendar_id: calendarId, event_id: eventId }
|
|
380
|
+
},
|
|
381
|
+
async ({ calendar_id, event_id }) => wrap(client2, () => client2.getEvent(calendar_id, event_id))()
|
|
382
|
+
);
|
|
383
|
+
server2.registerTool(
|
|
384
|
+
"gcal_create_event",
|
|
385
|
+
{
|
|
386
|
+
title: "Create event",
|
|
387
|
+
description: "Create a calendar event with summary, times, attendees, and location.",
|
|
388
|
+
inputSchema: {
|
|
389
|
+
calendar_id: calendarId,
|
|
390
|
+
summary: z2.string().describe("Event title."),
|
|
391
|
+
description: z2.string().optional(),
|
|
392
|
+
location: z2.string().optional(),
|
|
393
|
+
start: isoDateTime.describe("Start time (dateTime)."),
|
|
394
|
+
end: isoDateTime.describe("End time (dateTime)."),
|
|
395
|
+
time_zone: z2.string().optional().describe("IANA timezone (e.g. America/Los_Angeles)."),
|
|
396
|
+
attendees: z2.array(z2.string().email()).optional().describe("Attendee email addresses.")
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async ({ calendar_id, summary, description, location, start, end, time_zone, attendees }) => {
|
|
400
|
+
try {
|
|
401
|
+
const event = await client2.createEvent(calendar_id, {
|
|
402
|
+
summary,
|
|
403
|
+
description,
|
|
404
|
+
location,
|
|
405
|
+
start: { dateTime: start, timeZone: time_zone },
|
|
406
|
+
end: { dateTime: end, timeZone: time_zone },
|
|
407
|
+
attendees: attendees?.map((email) => ({ email }))
|
|
408
|
+
});
|
|
409
|
+
return ok("Event created", { event });
|
|
410
|
+
} catch (error) {
|
|
411
|
+
return toolError(error);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
server2.registerTool(
|
|
416
|
+
"gcal_update_event",
|
|
417
|
+
{
|
|
418
|
+
title: "Update event",
|
|
419
|
+
description: "Patch an existing event \u2014 only provided fields are updated.",
|
|
420
|
+
inputSchema: {
|
|
421
|
+
calendar_id: calendarId,
|
|
422
|
+
event_id: eventId,
|
|
423
|
+
summary: z2.string().optional(),
|
|
424
|
+
description: z2.string().optional(),
|
|
425
|
+
location: z2.string().optional(),
|
|
426
|
+
start: isoDateTime.optional(),
|
|
427
|
+
end: isoDateTime.optional(),
|
|
428
|
+
time_zone: z2.string().optional()
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
async ({ calendar_id, event_id, summary, description, location, start, end, time_zone }) => {
|
|
432
|
+
try {
|
|
433
|
+
const patch = {};
|
|
434
|
+
if (summary !== void 0) patch.summary = summary;
|
|
435
|
+
if (description !== void 0) patch.description = description;
|
|
436
|
+
if (location !== void 0) patch.location = location;
|
|
437
|
+
if (start !== void 0) patch.start = { dateTime: start, timeZone: time_zone };
|
|
438
|
+
if (end !== void 0) patch.end = { dateTime: end, timeZone: time_zone };
|
|
439
|
+
const event = await client2.updateEvent(calendar_id, event_id, patch);
|
|
440
|
+
return ok("Event updated", { event });
|
|
441
|
+
} catch (error) {
|
|
442
|
+
return toolError(error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
server2.registerTool(
|
|
447
|
+
"gcal_delete_event",
|
|
448
|
+
{
|
|
449
|
+
title: "Delete event",
|
|
450
|
+
description: "Permanently delete an event.",
|
|
451
|
+
inputSchema: { calendar_id: calendarId, event_id: eventId }
|
|
452
|
+
},
|
|
453
|
+
async ({ calendar_id, event_id }) => {
|
|
454
|
+
try {
|
|
455
|
+
await client2.deleteEvent(calendar_id, event_id);
|
|
456
|
+
return ok("Event deleted", { event_id });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
return toolError(error);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
server2.registerTool(
|
|
463
|
+
"gcal_search_events",
|
|
464
|
+
{
|
|
465
|
+
title: "Search events",
|
|
466
|
+
description: "Search events by free-text query across a calendar.",
|
|
467
|
+
inputSchema: {
|
|
468
|
+
calendar_id: calendarId,
|
|
469
|
+
query: z2.string().describe("Search query."),
|
|
470
|
+
max_results: maxResults.describe("Max results (default 25).")
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
async ({ calendar_id, query, max_results }) => {
|
|
474
|
+
try {
|
|
475
|
+
return json({
|
|
476
|
+
events: await client2.searchEvents(calendar_id, query, max_results ?? 25)
|
|
477
|
+
});
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return toolError(error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
server2.registerTool(
|
|
484
|
+
"gcal_find_free_slots",
|
|
485
|
+
{
|
|
486
|
+
title: "Find free/busy slots",
|
|
487
|
+
description: "Query free/busy information for one or more calendars in a time window.",
|
|
488
|
+
inputSchema: {
|
|
489
|
+
calendar_ids: z2.array(z2.string()).min(1).describe("Calendar IDs to check."),
|
|
490
|
+
time_min: isoDateTime,
|
|
491
|
+
time_max: isoDateTime,
|
|
492
|
+
time_zone: z2.string().optional()
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
async ({ calendar_ids, time_min, time_max, time_zone }) => wrap(
|
|
496
|
+
client2,
|
|
497
|
+
() => client2.findFreeSlots({
|
|
498
|
+
calendarIds: calendar_ids,
|
|
499
|
+
timeMin: time_min,
|
|
500
|
+
timeMax: time_max,
|
|
501
|
+
timeZone: time_zone
|
|
502
|
+
})
|
|
503
|
+
)()
|
|
504
|
+
);
|
|
505
|
+
server2.registerTool(
|
|
506
|
+
"gcal_quick_add_event",
|
|
507
|
+
{
|
|
508
|
+
title: "Quick add event",
|
|
509
|
+
description: "Create an event using natural language (Google quick-add syntax).",
|
|
510
|
+
inputSchema: {
|
|
511
|
+
calendar_id: calendarId,
|
|
512
|
+
text: z2.string().describe('Natural language event, e.g. "Lunch with Alex tomorrow at noon".')
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
async ({ calendar_id, text }) => {
|
|
516
|
+
try {
|
|
517
|
+
const event = await client2.quickAddEvent(calendar_id, text);
|
|
518
|
+
return ok("Event created via quick add", { event });
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return toolError(error);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
server2.registerTool(
|
|
525
|
+
"gcal_list_upcoming_events",
|
|
526
|
+
{
|
|
527
|
+
title: "List upcoming events",
|
|
528
|
+
description: "Next events starting from now on the default or specified calendar.",
|
|
529
|
+
inputSchema: {
|
|
530
|
+
calendar_id: calendarId,
|
|
531
|
+
max_results: maxResults.describe("Max events (default 10).")
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
async ({ calendar_id, max_results }) => {
|
|
535
|
+
try {
|
|
536
|
+
return json({
|
|
537
|
+
events: await client2.listUpcomingEvents(calendar_id, max_results ?? 10)
|
|
538
|
+
});
|
|
539
|
+
} catch (error) {
|
|
540
|
+
return toolError(error);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
server2.registerTool(
|
|
545
|
+
"gcal_rsvp_event",
|
|
546
|
+
{
|
|
547
|
+
title: "RSVP to event",
|
|
548
|
+
description: "Accept, decline, or mark tentative for a meeting invitation.",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
calendar_id: calendarId,
|
|
551
|
+
event_id: eventId,
|
|
552
|
+
response_status: z2.enum(["accepted", "declined", "tentative"]),
|
|
553
|
+
attendee_email: z2.string().email().optional().describe("Your email if not the organizer.")
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
async ({ calendar_id, event_id, response_status, attendee_email }) => {
|
|
557
|
+
try {
|
|
558
|
+
const event = await client2.rsvpEvent(
|
|
559
|
+
calendar_id,
|
|
560
|
+
event_id,
|
|
561
|
+
response_status,
|
|
562
|
+
attendee_email
|
|
563
|
+
);
|
|
564
|
+
return ok("RSVP updated", { event });
|
|
565
|
+
} catch (error) {
|
|
566
|
+
return toolError(error);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/index.ts
|
|
573
|
+
var server = new McpServer({
|
|
574
|
+
name: "mcp-wormhole-google-calendar",
|
|
575
|
+
version: "0.1.0"
|
|
576
|
+
});
|
|
577
|
+
var client = await createClientFromEnv();
|
|
578
|
+
registerGoogleCalendarTools(server, client);
|
|
579
|
+
registerGoogleCalendarPrompts(server, client);
|
|
580
|
+
registerGoogleCalendarResources(server, client);
|
|
581
|
+
async function main() {
|
|
582
|
+
const transport = new StdioServerTransport();
|
|
583
|
+
await server.connect(transport);
|
|
584
|
+
}
|
|
585
|
+
main().catch((err) => {
|
|
586
|
+
console.error(err);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
});
|
|
589
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/mcp/catalog.ts","../src/mcp/tools.ts","../src/mcp/helpers.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createClientFromEnv } from \"./client.js\";\nimport { registerGoogleCalendarPrompts } from \"./mcp/prompts.js\";\nimport { registerGoogleCalendarResources } from \"./mcp/resources.js\";\nimport { registerGoogleCalendarTools } from \"./mcp/tools.js\";\n\nconst server = new McpServer({\n name: \"mcp-wormhole-google-calendar\",\n version: \"0.1.0\",\n});\n\nconst client = await createClientFromEnv();\nregisterGoogleCalendarTools(server, client);\nregisterGoogleCalendarPrompts(server, client);\nregisterGoogleCalendarResources(server, client);\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { GoogleCalendarClient } from \"../client.js\";\n\nconst calendarArg = {\n calendar_id: z.string().optional().describe(\"Calendar ID (default: primary).\"),\n};\n\nfunction userMessage(text: string) {\n return { role: \"user\" as const, content: { type: \"text\" as const, text } };\n}\n\nexport function registerGoogleCalendarPrompts(server: McpServer, _client: GoogleCalendarClient) {\n server.registerPrompt(\n \"today_agenda\",\n {\n title: \"Today's agenda\",\n description: \"Summarize today's calendar events with times and prep notes.\",\n argsSchema: calendarArg,\n },\n async ({ calendar_id }) => {\n const cal = calendar_id ?? \"primary\";\n const today = new Date().toISOString().slice(0, 10);\n return {\n messages: [\n userMessage(\n `For calendar ${cal}: call gcal_list_events with time_min ${today}T00:00:00Z and time_max ${today}T23:59:59Z. Output a chronological agenda with start time, title, location, and any prep actions.`,\n ),\n ],\n };\n },\n );\n\n server.registerPrompt(\n \"week_ahead_overview\",\n {\n title: \"Week ahead overview\",\n description: \"Overview of the next 7 days — key meetings, conflicts, and focus blocks.\",\n argsSchema: calendarArg,\n },\n async ({ calendar_id }) => {\n const cal = calendar_id ?? \"primary\";\n const start = new Date().toISOString();\n const end = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();\n return {\n messages: [\n userMessage(\n `For calendar ${cal}: call gcal_list_events from ${start} to ${end}. Group by day. Flag back-to-back meetings, long gaps, and anything that looks like a conflict.`,\n ),\n ],\n };\n },\n );\n\n server.registerPrompt(\n \"meeting_prep_brief\",\n {\n title: \"Meeting prep brief\",\n description: \"Brief for a specific upcoming event — attendees, agenda hints, logistics.\",\n argsSchema: {\n ...calendarArg,\n event_id: z.string().describe(\"Event ID to brief.\"),\n },\n },\n async ({ calendar_id, event_id }) => {\n return {\n messages: [\n userMessage(\n `Call gcal_get_event for event ${event_id} on calendar ${calendar_id ?? \"primary\"}. Produce a prep brief: time, attendees, location/video link, description highlights, and suggested talking points.`,\n ),\n ],\n };\n },\n );\n\n server.registerPrompt(\n \"find_meeting_time\",\n {\n title: \"Find meeting time\",\n description: \"Find mutual free slots across calendars for scheduling.\",\n argsSchema: {\n calendar_ids: z.array(z.string()).min(1).describe(\"Calendars to check for availability.\"),\n duration_minutes: z.number().int().min(15).max(480).optional(),\n },\n },\n async ({ calendar_ids, duration_minutes }) => {\n const start = new Date().toISOString();\n const end = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toISOString();\n const duration = duration_minutes ?? 30;\n return {\n messages: [\n userMessage(\n `Call gcal_find_free_slots for calendars ${calendar_ids.join(\", \")} between ${start} and ${end}. Suggest ${duration}-minute meeting slots during business hours, ranked by convenience.`,\n ),\n ],\n };\n },\n );\n\n server.registerPrompt(\n \"scheduling_conflict_scan\",\n {\n title: \"Scheduling conflict scan\",\n description: \"Detect overlapping events and tight turnarounds in a date range.\",\n argsSchema: {\n ...calendarArg,\n days: z.number().int().min(1).max(14).optional(),\n },\n },\n async ({ calendar_id, days }) => {\n const n = days ?? 3;\n const start = new Date().toISOString();\n const end = new Date(Date.now() + n * 24 * 60 * 60 * 1000).toISOString();\n return {\n messages: [\n userMessage(\n `For calendar ${calendar_id ?? \"primary\"}: list events from ${start} to ${end}. Identify overlaps, meetings with <15 min gaps, and events missing locations or video links.`,\n ),\n ],\n };\n },\n );\n\n server.registerPrompt(\n \"focus_time_planner\",\n {\n title: \"Focus time planner\",\n description: \"Suggest open blocks for deep work based on free/busy data.\",\n argsSchema: calendarArg,\n },\n async ({ calendar_id }) => {\n const cal = calendar_id ?? \"primary\";\n const start = new Date().toISOString();\n const end = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString();\n return {\n messages: [\n userMessage(\n `Call gcal_find_free_slots for calendar ${cal} between ${start} and ${end}. Recommend 2–3 focus blocks of at least 90 minutes during weekday business hours. Avoid lunch hour.`,\n ),\n ],\n };\n },\n );\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { GoogleCalendarClient } from \"../client.js\";\nimport {\n GCAL_PROMPT_NAMES,\n GCAL_RESOURCE_URIS,\n GCAL_TOOL_NAMES,\n} from \"./catalog.js\";\n\nfunction textResource(uri: string, data: unknown) {\n return {\n contents: [\n {\n uri,\n mimeType: \"application/json\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n\nfunction param(value: string | string[]): string {\n return Array.isArray(value) ? value[0]! : value;\n}\n\nexport function registerGoogleCalendarResources(\n server: McpServer,\n client: GoogleCalendarClient,\n) {\n server.registerResource(\n \"gcal_catalog\",\n \"gcal://catalog\",\n {\n title: \"Google Calendar MCP catalog\",\n description: \"Tool, prompt, and resource index for mcp-wormhole Google Calendar.\",\n mimeType: \"application/json\",\n },\n async (uri) =>\n textResource(uri.href, {\n server: \"mcp-wormhole-google-calendar\",\n tools: GCAL_TOOL_NAMES.length,\n tool_names: GCAL_TOOL_NAMES,\n prompts: GCAL_PROMPT_NAMES.length,\n prompt_names: GCAL_PROMPT_NAMES,\n resource_templates: GCAL_RESOURCE_URIS,\n }),\n );\n\n server.registerResource(\n \"gcal_calendars\",\n \"gcal://calendars\",\n {\n title: \"All calendars\",\n description: \"Browse calendars accessible to the authenticated account.\",\n mimeType: \"application/json\",\n },\n async (uri) => textResource(uri.href, { calendars: await client.listCalendars(50) }),\n );\n\n server.registerResource(\n \"gcal_calendar\",\n new ResourceTemplate(\"gcal://calendar/{calendar_id}\", {\n list: async () => {\n const calendars = await client.listCalendars(50);\n return {\n resources: calendars.map((c) => ({\n uri: `gcal://calendar/${c.id}`,\n name: c.summary,\n mimeType: \"application/json\",\n })),\n };\n },\n complete: {\n calendar_id: async () => (await client.listCalendars(50)).map((c) => c.id),\n },\n }),\n {\n title: \"Calendar\",\n description: \"Calendar metadata and navigation links.\",\n mimeType: \"application/json\",\n },\n async (uri, { calendar_id }) => {\n const id = param(calendar_id);\n const calendar = await client.getCalendar(id);\n return textResource(uri.href, {\n calendar_id: id,\n calendar,\n browse: {\n events: `gcal://calendar/${id}/events`,\n },\n });\n },\n );\n\n server.registerResource(\n \"gcal_calendar_events\",\n new ResourceTemplate(\"gcal://calendar/{calendar_id}/events\", {\n list: undefined,\n complete: {\n calendar_id: async () => (await client.listCalendars(50)).map((c) => c.id),\n },\n }),\n {\n title: \"Calendar events\",\n description: \"Upcoming events for a calendar.\",\n mimeType: \"application/json\",\n },\n async (uri, { calendar_id }) => {\n const id = param(calendar_id);\n const events = await client.listUpcomingEvents(id, 25);\n return textResource(uri.href, { calendar_id: id, events });\n },\n );\n}\n","/** Canonical tool names — keep in sync with registerGoogleCalendarTools. */\nexport const GCAL_TOOL_NAMES = [\n \"gcal_list_calendars\",\n \"gcal_get_calendar\",\n \"gcal_list_events\",\n \"gcal_get_event\",\n \"gcal_create_event\",\n \"gcal_update_event\",\n \"gcal_delete_event\",\n \"gcal_search_events\",\n \"gcal_find_free_slots\",\n \"gcal_quick_add_event\",\n \"gcal_list_upcoming_events\",\n \"gcal_rsvp_event\",\n] as const;\n\nexport const GCAL_PROMPT_NAMES = [\n \"today_agenda\",\n \"week_ahead_overview\",\n \"meeting_prep_brief\",\n \"find_meeting_time\",\n \"scheduling_conflict_scan\",\n \"focus_time_planner\",\n] as const;\n\nexport const GCAL_RESOURCE_URIS = [\n \"gcal://catalog\",\n \"gcal://calendars\",\n \"gcal://calendar/{calendar_id}\",\n \"gcal://calendar/{calendar_id}/events\",\n] as const;\n\nexport const GCAL_TOOL_COUNT = GCAL_TOOL_NAMES.length;\nexport const GCAL_PROMPT_COUNT = GCAL_PROMPT_NAMES.length;\nexport const GCAL_RESOURCE_TEMPLATE_COUNT = GCAL_RESOURCE_URIS.length;\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { GoogleCalendarClient } from \"../client.js\";\nimport { json, ok, toolError } from \"./helpers.js\";\n\nconst calendarId = z.string().optional().describe(\"Calendar ID (defaults to GOOGLE_CALENDAR_ID or primary).\");\nconst eventId = z.string().describe(\"Google Calendar event ID.\");\nconst maxResults = z.number().int().min(1).max(250).optional();\nconst isoDateTime = z.string().describe(\"ISO 8601 datetime (e.g. 2026-06-23T09:00:00Z).\");\n\nfunction wrap(client: GoogleCalendarClient, fn: () => Promise<unknown>) {\n return async () => {\n try {\n return json(await fn());\n } catch (error) {\n return toolError(error);\n }\n };\n}\n\nexport function registerGoogleCalendarTools(server: McpServer, client: GoogleCalendarClient) {\n server.registerTool(\n \"gcal_list_calendars\",\n {\n title: \"List calendars\",\n description: \"All calendars accessible to the authenticated account.\",\n inputSchema: { max_results: maxResults.describe(\"Max calendars (default 50).\") },\n },\n async ({ max_results }) => {\n try {\n return json({ calendars: await client.listCalendars(max_results ?? 50) });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_get_calendar\",\n {\n title: \"Get calendar\",\n description: \"Calendar metadata by ID.\",\n inputSchema: { calendar_id: z.string().describe(\"Calendar ID.\") },\n },\n async ({ calendar_id }) => wrap(client, () => client.getCalendar(calendar_id))(),\n );\n\n server.registerTool(\n \"gcal_list_events\",\n {\n title: \"List events\",\n description: \"Events in a calendar, optionally filtered by time range or search query.\",\n inputSchema: {\n calendar_id: calendarId,\n time_min: isoDateTime.optional(),\n time_max: isoDateTime.optional(),\n max_results: maxResults.describe(\"Max events (default 25).\"),\n q: z.string().optional().describe(\"Free-text search query.\"),\n },\n },\n async ({ calendar_id, time_min, time_max, max_results, q }) => {\n try {\n return json({\n events: await client.listEvents({\n calendarId: calendar_id,\n timeMin: time_min,\n timeMax: time_max,\n maxResults: max_results ?? 25,\n q,\n }),\n });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_get_event\",\n {\n title: \"Get event\",\n description: \"Full event record by ID.\",\n inputSchema: { calendar_id: calendarId, event_id: eventId },\n },\n async ({ calendar_id, event_id }) => wrap(client, () => client.getEvent(calendar_id, event_id))(),\n );\n\n server.registerTool(\n \"gcal_create_event\",\n {\n title: \"Create event\",\n description: \"Create a calendar event with summary, times, attendees, and location.\",\n inputSchema: {\n calendar_id: calendarId,\n summary: z.string().describe(\"Event title.\"),\n description: z.string().optional(),\n location: z.string().optional(),\n start: isoDateTime.describe(\"Start time (dateTime).\"),\n end: isoDateTime.describe(\"End time (dateTime).\"),\n time_zone: z.string().optional().describe(\"IANA timezone (e.g. America/Los_Angeles).\"),\n attendees: z.array(z.string().email()).optional().describe(\"Attendee email addresses.\"),\n },\n },\n async ({ calendar_id, summary, description, location, start, end, time_zone, attendees }) => {\n try {\n const event = await client.createEvent(calendar_id, {\n summary,\n description,\n location,\n start: { dateTime: start, timeZone: time_zone },\n end: { dateTime: end, timeZone: time_zone },\n attendees: attendees?.map((email) => ({ email })),\n });\n return ok(\"Event created\", { event });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_update_event\",\n {\n title: \"Update event\",\n description: \"Patch an existing event — only provided fields are updated.\",\n inputSchema: {\n calendar_id: calendarId,\n event_id: eventId,\n summary: z.string().optional(),\n description: z.string().optional(),\n location: z.string().optional(),\n start: isoDateTime.optional(),\n end: isoDateTime.optional(),\n time_zone: z.string().optional(),\n },\n },\n async ({ calendar_id, event_id, summary, description, location, start, end, time_zone }) => {\n try {\n const patch: Record<string, unknown> = {};\n if (summary !== undefined) patch.summary = summary;\n if (description !== undefined) patch.description = description;\n if (location !== undefined) patch.location = location;\n if (start !== undefined) patch.start = { dateTime: start, timeZone: time_zone };\n if (end !== undefined) patch.end = { dateTime: end, timeZone: time_zone };\n\n const event = await client.updateEvent(calendar_id, event_id, patch);\n return ok(\"Event updated\", { event });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_delete_event\",\n {\n title: \"Delete event\",\n description: \"Permanently delete an event.\",\n inputSchema: { calendar_id: calendarId, event_id: eventId },\n },\n async ({ calendar_id, event_id }) => {\n try {\n await client.deleteEvent(calendar_id, event_id);\n return ok(\"Event deleted\", { event_id });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_search_events\",\n {\n title: \"Search events\",\n description: \"Search events by free-text query across a calendar.\",\n inputSchema: {\n calendar_id: calendarId,\n query: z.string().describe(\"Search query.\"),\n max_results: maxResults.describe(\"Max results (default 25).\"),\n },\n },\n async ({ calendar_id, query, max_results }) => {\n try {\n return json({\n events: await client.searchEvents(calendar_id, query, max_results ?? 25),\n });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_find_free_slots\",\n {\n title: \"Find free/busy slots\",\n description: \"Query free/busy information for one or more calendars in a time window.\",\n inputSchema: {\n calendar_ids: z.array(z.string()).min(1).describe(\"Calendar IDs to check.\"),\n time_min: isoDateTime,\n time_max: isoDateTime,\n time_zone: z.string().optional(),\n },\n },\n async ({ calendar_ids, time_min, time_max, time_zone }) =>\n wrap(client, () =>\n client.findFreeSlots({\n calendarIds: calendar_ids,\n timeMin: time_min,\n timeMax: time_max,\n timeZone: time_zone,\n }),\n )(),\n );\n\n server.registerTool(\n \"gcal_quick_add_event\",\n {\n title: \"Quick add event\",\n description: \"Create an event using natural language (Google quick-add syntax).\",\n inputSchema: {\n calendar_id: calendarId,\n text: z.string().describe('Natural language event, e.g. \"Lunch with Alex tomorrow at noon\".'),\n },\n },\n async ({ calendar_id, text }) => {\n try {\n const event = await client.quickAddEvent(calendar_id, text);\n return ok(\"Event created via quick add\", { event });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_list_upcoming_events\",\n {\n title: \"List upcoming events\",\n description: \"Next events starting from now on the default or specified calendar.\",\n inputSchema: {\n calendar_id: calendarId,\n max_results: maxResults.describe(\"Max events (default 10).\"),\n },\n },\n async ({ calendar_id, max_results }) => {\n try {\n return json({\n events: await client.listUpcomingEvents(calendar_id, max_results ?? 10),\n });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n\n server.registerTool(\n \"gcal_rsvp_event\",\n {\n title: \"RSVP to event\",\n description: \"Accept, decline, or mark tentative for a meeting invitation.\",\n inputSchema: {\n calendar_id: calendarId,\n event_id: eventId,\n response_status: z.enum([\"accepted\", \"declined\", \"tentative\"]),\n attendee_email: z.string().email().optional().describe(\"Your email if not the organizer.\"),\n },\n },\n async ({ calendar_id, event_id, response_status, attendee_email }) => {\n try {\n const event = await client.rsvpEvent(\n calendar_id,\n event_id,\n response_status,\n attendee_email,\n );\n return ok(\"RSVP updated\", { event });\n } catch (error) {\n return toolError(error);\n }\n },\n );\n}\n","import { GoogleCalendarError } from \"../client.js\";\n\nexport function json(data: unknown) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] };\n}\n\nexport function toolError(error: unknown) {\n if (error instanceof GoogleCalendarError) {\n return json({\n error: error.message,\n status: error.status,\n details: error.details,\n });\n }\n\n return json({\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\nexport function ok(message: string, data?: unknown) {\n return json(\n data\n ? { message, ...(typeof data === \"object\" && data ? (data as object) : { result: data }) }\n : { message },\n );\n}\n"],"mappings":"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","names":["server","server","client","z","z","client","server"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-wormhole/google-calendar",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Google Calendar — 12 tools, 6 prompt workflows, and browsable gcal:// resources via the Google Calendar API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-wormhole-google-calendar": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/index.js",
|
|
12
|
+
"dist/index.js.map",
|
|
13
|
+
"dist/chunk-*.js",
|
|
14
|
+
"dist/chunk-*.js.map",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"verify": "node --env-file=.env dist/verify.js",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"google-calendar",
|
|
28
|
+
"calendar",
|
|
29
|
+
"scheduling",
|
|
30
|
+
"model-context-protocol"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"author": "Ayush Kumar <ayushknj3@gmail.com>",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Ayush7614/mcp-wormhole.git",
|
|
37
|
+
"directory": "packages/google-calendar"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/Ayush7614/mcp-wormhole/tree/master/packages/google-calendar#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/Ayush7614/mcp-wormhole/issues"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
51
|
+
"google-auth-library": "^9.15.1",
|
|
52
|
+
"googleapis": "^144.0.0",
|
|
53
|
+
"zod": "^3.23.8"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"tsup": "^8.3.5",
|
|
57
|
+
"typescript": "^5.7.2"
|
|
58
|
+
}
|
|
59
|
+
}
|