@jaingxyz/personal-gmail-mcp 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/dist/server.js ADDED
@@ -0,0 +1,136 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 jaingxyz
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { z } from "zod";
5
+ import { config } from "./config.js";
6
+ import { getMe } from "./google.js";
7
+ import { listLabels, listLabelsSchema, listRecent, listRecentSchema, read, readSchema, search, searchSchema, } from "./tools/read.js";
8
+ import { deleteMessage, deleteSchema, markRead, markReadSchema, modifyLabels, modifyLabelsSchema, untrash, untrashSchema, } from "./tools/mutate.js";
9
+ import { createDraft, createDraftSchema, reply, replySchema, send, sendSchema, sendDraft, sendDraftSchema, } from "./tools/send.js";
10
+ import { cancelEvent, cancelEventSchema, createEvent, createEventSchema, listCalendars, listCalendarsSchema, listEvents, listEventsSchema, readEvent, readEventSchema, respondSchema, respondToInvite, updateEvent, updateEventSchema, } from "./tools/calendar.js";
11
+ const PAGINATION_NOTE = "Returns up to `limit` (max 100) plus a `nextCursor`; pass it back as `cursor` to page through more.";
12
+ export function buildServer() {
13
+ const server = new McpServer({
14
+ name: "personal-gmail-mcp",
15
+ version: config.version,
16
+ });
17
+ server.registerTool("gmail_whoami", {
18
+ title: "Signed-in Gmail account",
19
+ description: "Return the signed-in Google account's Gmail profile (email address, message/thread totals).",
20
+ inputSchema: z.object({}).shape,
21
+ }, async () => toolResult(await getMe()));
22
+ server.registerTool("gmail_list_labels", {
23
+ title: "List labels",
24
+ description: "List the mailbox's labels (system labels like INBOX/SENT/TRASH and custom labels) with id, name, type, and unread/total counts.",
25
+ inputSchema: listLabelsSchema.shape,
26
+ }, async (args) => toolResult(await listLabels(args)));
27
+ server.registerTool("gmail_list_recent", {
28
+ title: "Recent messages in a label",
29
+ description: `List the most recent messages carrying a label, newest first. Defaults to INBOX. Supports unreadOnly. ${PAGINATION_NOTE}`,
30
+ inputSchema: listRecentSchema.shape,
31
+ }, async (args) => toolResult(await listRecent(args)));
32
+ server.registerTool("gmail_search", {
33
+ title: "Search mail",
34
+ description: `Search messages with Gmail query syntax (from:, subject:, has:attachment, newer_than:7d, is:unread, etc.). ${PAGINATION_NOTE}`,
35
+ inputSchema: searchSchema.shape,
36
+ }, async (args) => toolResult(await search(args)));
37
+ server.registerTool("gmail_read", {
38
+ title: "Read message",
39
+ description: "Fetch a single message by id, including its decoded body (text preferred, html fallback) and threading headers.",
40
+ inputSchema: readSchema.shape,
41
+ }, async (args) => toolResult(await read(args)));
42
+ server.registerTool("gmail_mark_read", {
43
+ title: "Mark message read/unread",
44
+ description: "Mark a message read (remove UNREAD) or unread (add UNREAD). Pass isRead=false to mark unread.",
45
+ inputSchema: markReadSchema.shape,
46
+ annotations: { destructiveHint: false, idempotentHint: true },
47
+ }, async (args) => toolResult(await markRead(args)));
48
+ server.registerTool("gmail_modify_labels", {
49
+ title: "Add/remove labels",
50
+ description: "Add or remove labels on a message. Removing INBOX archives the message; adding STARRED stars it. Use gmail_list_labels for ids.",
51
+ inputSchema: modifyLabelsSchema.shape,
52
+ annotations: { destructiveHint: false },
53
+ }, async (args) => toolResult(await modifyLabels(args)));
54
+ server.registerTool("gmail_delete", {
55
+ title: "Delete message",
56
+ description: "Delete a message. By default moves to Trash (recoverable). Pass hardDelete=true to permanently delete — not recoverable.",
57
+ inputSchema: deleteSchema.shape,
58
+ annotations: { destructiveHint: true },
59
+ }, async (args) => toolResult(await deleteMessage(args)));
60
+ server.registerTool("gmail_untrash", {
61
+ title: "Restore from Trash",
62
+ description: "Restore a message from Trash back to the mailbox.",
63
+ inputSchema: untrashSchema.shape,
64
+ annotations: { destructiveHint: false },
65
+ }, async (args) => toolResult(await untrash(args)));
66
+ server.registerTool("gmail_send", {
67
+ title: "Send email",
68
+ description: "Send a new email immediately. Saved to Sent. Use gmail_create_draft to review before sending.",
69
+ inputSchema: sendSchema.shape,
70
+ annotations: { destructiveHint: true },
71
+ }, async (args) => toolResult(await send(args)));
72
+ server.registerTool("gmail_reply", {
73
+ title: "Reply to message",
74
+ description: "Reply to a message by id, keeping it in the same thread. Sends immediately. replyAll=true to include all original recipients.",
75
+ inputSchema: replySchema.shape,
76
+ annotations: { destructiveHint: true },
77
+ }, async (args) => toolResult(await reply(args)));
78
+ server.registerTool("gmail_create_draft", {
79
+ title: "Create draft",
80
+ description: "Create a draft without sending. Returns a draftId for gmail_send_draft.",
81
+ inputSchema: createDraftSchema.shape,
82
+ }, async (args) => toolResult(await createDraft(args)));
83
+ server.registerTool("gmail_send_draft", {
84
+ title: "Send draft",
85
+ description: "Send a previously created draft by id.",
86
+ inputSchema: sendDraftSchema.shape,
87
+ annotations: { destructiveHint: true },
88
+ }, async (args) => toolResult(await sendDraft(args)));
89
+ // ---------- Calendar ----------
90
+ server.registerTool("gmail_calendar_list_calendars", {
91
+ title: "List calendars",
92
+ description: "List the user's calendars (primary + subscribed) with id, name, default flag, and whether they're editable.",
93
+ inputSchema: listCalendarsSchema.shape,
94
+ }, async (args) => toolResult(await listCalendars(args)));
95
+ server.registerTool("gmail_calendar_list_events", {
96
+ title: "List events in range",
97
+ description: "List events in a time range. Recurring series are expanded into individual instances, ordered by start time. Defaults to the primary calendar.",
98
+ inputSchema: listEventsSchema.shape,
99
+ }, async (args) => toolResult(await listEvents(args)));
100
+ server.registerTool("gmail_calendar_read_event", {
101
+ title: "Read event",
102
+ description: "Read full details of a single event including description, attendees, and recurrence.",
103
+ inputSchema: readEventSchema.shape,
104
+ }, async (args) => toolResult(await readEvent(args)));
105
+ server.registerTool("gmail_calendar_create_event", {
106
+ title: "Create event",
107
+ description: "Create an event. Times use {dateTime, timeZone} where dateTime is local-form (no offset) and timeZone is an IANA name. If attendees are provided, invites are sent. isOnlineMeeting attaches a Google Meet link.",
108
+ inputSchema: createEventSchema.shape,
109
+ annotations: { destructiveHint: true },
110
+ }, async (args) => toolResult(await createEvent(args)));
111
+ server.registerTool("gmail_calendar_update_event", {
112
+ title: "Update event",
113
+ description: "Update an event's subject, time, location, description, or attendees. Works on single instances of a recurring series (each instance has its own id).",
114
+ inputSchema: updateEventSchema.shape,
115
+ annotations: { destructiveHint: true },
116
+ }, async (args) => toolResult(await updateEvent(args)));
117
+ server.registerTool("gmail_calendar_cancel_event", {
118
+ title: "Cancel/delete event",
119
+ description: "Delete an event. If you organize it and sendUpdates=all (default), attendees receive a cancellation. Use sendUpdates=none for solo events.",
120
+ inputSchema: cancelEventSchema.shape,
121
+ annotations: { destructiveHint: true },
122
+ }, async (args) => toolResult(await cancelEvent(args)));
123
+ server.registerTool("gmail_calendar_respond_to_invite", {
124
+ title: "Respond to invite",
125
+ description: "Respond to a meeting invite as accept, tentativelyAccept, or decline. Optionally include a comment and choose whether to notify the organizer.",
126
+ inputSchema: respondSchema.shape,
127
+ annotations: { destructiveHint: true },
128
+ }, async (args) => toolResult(await respondToInvite(args)));
129
+ return server;
130
+ }
131
+ function toolResult(payload) {
132
+ return {
133
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
134
+ };
135
+ }
136
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,8BAA8B;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,UAAU,EACV,MAAM,EACN,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,OAAO,EACP,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,UAAU,EACV,SAAS,EACT,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EACf,WAAW,EACX,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,eAAe,GACnB,qGAAqG,CAAC;AAExG,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,6FAA6F;QAC/F,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK;KAChC,EACD,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,KAAK,EAAE,CAAC,CACtC,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EACT,iIAAiI;QACnI,WAAW,EAAE,gBAAgB,CAAC,KAAK;KACpC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CACnD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,yGAAyG,eAAe,EAAE;QACvI,WAAW,EAAE,gBAAgB,CAAC,KAAK;KACpC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CACnD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,8GAA8G,eAAe,EAAE;QAC5I,WAAW,EAAE,YAAY,CAAC,KAAK;KAChC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,CAC/C,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,iHAAiH;QACnH,WAAW,EAAE,UAAU,CAAC,KAAK;KAC9B,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAC7C,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,+FAA+F;QACjG,WAAW,EAAE,cAAc,CAAC,KAAK;QACjC,WAAW,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE;KAC9D,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,CACjD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,iIAAiI;QACnI,WAAW,EAAE,kBAAkB,CAAC,KAAK;QACrC,WAAW,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE;KACxC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC,CACrD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,0HAA0H;QAC5H,WAAW,EAAE,YAAY,CAAC,KAAK;QAC/B,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CACtD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,mDAAmD;QAChE,WAAW,EAAE,aAAa,CAAC,KAAK;QAChC,WAAW,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE;KACxC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAChD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,+FAA+F;QACjG,WAAW,EAAE,UAAU,CAAC,KAAK;QAC7B,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAC7C,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,+HAA+H;QACjI,WAAW,EAAE,WAAW,CAAC,KAAK;QAC9B,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAC9C,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,yEAAyE;QAC3E,WAAW,EAAE,iBAAiB,CAAC,KAAK;KACrC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CACpD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,wCAAwC;QACrD,WAAW,EAAE,eAAe,CAAC,KAAK;QAClC,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC,CAClD,CAAC;IAEF,iCAAiC;IAEjC,MAAM,CAAC,YAAY,CACjB,+BAA+B,EAC/B;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,6GAA6G;QAC/G,WAAW,EAAE,mBAAmB,CAAC,KAAK;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CACtD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,4BAA4B,EAC5B;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,gJAAgJ;QAClJ,WAAW,EAAE,gBAAgB,CAAC,KAAK;KACpC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CACnD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,uFAAuF;QACzF,WAAW,EAAE,eAAe,CAAC,KAAK;KACnC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC,CAClD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,kNAAkN;QACpN,WAAW,EAAE,iBAAiB,CAAC,KAAK;QACpC,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CACpD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,uJAAuJ;QACzJ,WAAW,EAAE,iBAAiB,CAAC,KAAK;QACpC,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CACpD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,4IAA4I;QAC9I,WAAW,EAAE,iBAAiB,CAAC,KAAK;QACpC,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CACpD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kCAAkC,EAClC;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,gJAAgJ;QAClJ,WAAW,EAAE,aAAa,CAAC,KAAK;QAChC,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;KACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,CACxD,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,OAAgB;IAGlC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,296 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 jaingxyz
3
+ import { z } from "zod";
4
+ import { calendarClient } from "../google.js";
5
+ import { config } from "../config.js";
6
+ // Google Calendar event times: { dateTime, timeZone } where dateTime is
7
+ // RFC-3339 local form (no offset) when timeZone is supplied, OR { date } for
8
+ // all-day. We mirror the Outlook tool's input shape; a separate timeZone field
9
+ // keeps callers from having to bake offsets into the string.
10
+ const dateTimeWithTz = z.object({
11
+ dateTime: z
12
+ .string()
13
+ .min(1)
14
+ .describe("Local date-time, no offset, e.g. '2026-05-20T15:00:00'. The timeZone field controls interpretation."),
15
+ timeZone: z
16
+ .string()
17
+ .min(1)
18
+ .describe("IANA timezone, e.g. 'America/Los_Angeles' or 'UTC'."),
19
+ });
20
+ const attendeeSchema = z.object({
21
+ email: z.string().email(),
22
+ name: z.string().optional(),
23
+ optional: z
24
+ .boolean()
25
+ .default(false)
26
+ .describe("If true, the attendee is marked optional."),
27
+ });
28
+ function toGoogleAttendees(attendees) {
29
+ return attendees.map((a) => ({
30
+ email: a.email,
31
+ displayName: a.name,
32
+ optional: a.optional,
33
+ }));
34
+ }
35
+ function formatPerson(p) {
36
+ if (!p)
37
+ return null;
38
+ if (p.displayName && p.email)
39
+ return `${p.displayName} <${p.email}>`;
40
+ return p.email ?? p.displayName ?? null;
41
+ }
42
+ export function summarizeEvent(e) {
43
+ const attendees = e.attendees ?? [];
44
+ const self = attendees.find((a) => a.self);
45
+ return {
46
+ id: e.id,
47
+ subject: e.summary ?? null,
48
+ start: e.start, // { dateTime, timeZone } or { date }
49
+ end: e.end,
50
+ isAllDay: !!e.start?.date,
51
+ location: e.location ?? null,
52
+ organizer: formatPerson(e.organizer),
53
+ attendees: attendees.map((a) => ({
54
+ email: formatPerson(a),
55
+ optional: a.optional ?? false,
56
+ responseStatus: a.responseStatus, // needsAction|declined|tentative|accepted
57
+ })),
58
+ status: e.status, // confirmed | tentative | cancelled
59
+ myResponse: self?.responseStatus ?? null,
60
+ // Google: recurringEventId present => this is an instance of a series.
61
+ recurringEventId: e.recurringEventId ?? null,
62
+ htmlLink: e.htmlLink,
63
+ preview: (e.description ?? "").slice(0, 200) || null,
64
+ };
65
+ }
66
+ // ---------- list_calendars ----------
67
+ export const listCalendarsSchema = z.object({});
68
+ export async function listCalendars(_input) {
69
+ const cal = await calendarClient();
70
+ const res = await cal.calendarList.list();
71
+ return {
72
+ calendars: (res.data.items ?? []).map((c) => ({
73
+ id: c.id,
74
+ name: c.summary,
75
+ isDefault: !!c.primary,
76
+ // owner/writer/reader/freeBusyReader — editable if owner or writer.
77
+ canEdit: c.accessRole === "owner" || c.accessRole === "writer",
78
+ accessRole: c.accessRole,
79
+ })),
80
+ };
81
+ }
82
+ // ---------- list_events ----------
83
+ export const listEventsSchema = z.object({
84
+ start: z
85
+ .string()
86
+ .min(1)
87
+ .describe("Inclusive window start, RFC-3339, e.g. '2026-05-20T00:00:00Z' or with offset."),
88
+ end: z
89
+ .string()
90
+ .min(1)
91
+ .describe("Exclusive window end. Same format as start."),
92
+ calendarId: z
93
+ .string()
94
+ .default("primary")
95
+ .describe("Calendar id. Defaults to the primary calendar."),
96
+ limit: z.number().int().min(1).max(200).default(50),
97
+ });
98
+ export async function listEvents(input) {
99
+ const cal = await calendarClient();
100
+ // singleEvents + orderBy=startTime expands recurring series into individual
101
+ // instances ordered by time (the "what's on my calendar" question).
102
+ const res = await cal.events.list({
103
+ calendarId: input.calendarId,
104
+ timeMin: input.start,
105
+ timeMax: input.end,
106
+ singleEvents: true,
107
+ orderBy: "startTime",
108
+ maxResults: input.limit,
109
+ timeZone: config.defaultTimeZone,
110
+ });
111
+ return {
112
+ timeZone: config.defaultTimeZone,
113
+ events: (res.data.items ?? []).map(summarizeEvent),
114
+ nextCursor: res.data.nextPageToken ?? null,
115
+ };
116
+ }
117
+ // ---------- read_event ----------
118
+ export const readEventSchema = z.object({
119
+ eventId: z.string().min(1),
120
+ calendarId: z.string().default("primary"),
121
+ });
122
+ export async function readEvent(input) {
123
+ const cal = await calendarClient();
124
+ const res = await cal.events.get({
125
+ calendarId: input.calendarId,
126
+ eventId: input.eventId,
127
+ timeZone: config.defaultTimeZone,
128
+ });
129
+ const e = res.data;
130
+ return {
131
+ ...summarizeEvent(e),
132
+ body: { contentType: "text", content: e.description ?? "" },
133
+ recurrence: e.recurrence ?? null,
134
+ };
135
+ }
136
+ // ---------- create_event ----------
137
+ export const createEventSchema = z.object({
138
+ subject: z.string().min(1),
139
+ start: dateTimeWithTz,
140
+ end: dateTimeWithTz,
141
+ attendees: z.array(attendeeSchema).default([]),
142
+ location: z.string().optional(),
143
+ body: z.string().optional(),
144
+ isOnlineMeeting: z
145
+ .boolean()
146
+ .default(false)
147
+ .describe("If true, attaches a Google Meet conference link."),
148
+ calendarId: z.string().default("primary"),
149
+ sendUpdates: z
150
+ .enum(["all", "externalOnly", "none"])
151
+ .default("all")
152
+ .describe("Whether to send invites/notifications to attendees."),
153
+ });
154
+ export async function createEvent(input) {
155
+ const cal = await calendarClient();
156
+ const requestBody = {
157
+ summary: input.subject,
158
+ start: input.start,
159
+ end: input.end,
160
+ attendees: toGoogleAttendees(input.attendees),
161
+ };
162
+ if (input.location)
163
+ requestBody.location = input.location;
164
+ if (input.body)
165
+ requestBody.description = input.body;
166
+ if (input.isOnlineMeeting) {
167
+ requestBody.conferenceData = {
168
+ createRequest: {
169
+ // requestId must be unique per create; derive from subject + start.
170
+ requestId: `meet-${Buffer.from(input.subject + input.start.dateTime)
171
+ .toString("base64url")
172
+ .slice(0, 32)}`,
173
+ conferenceSolutionKey: { type: "hangoutsMeet" },
174
+ },
175
+ };
176
+ }
177
+ const res = await cal.events.insert({
178
+ calendarId: input.calendarId,
179
+ requestBody,
180
+ sendUpdates: input.sendUpdates,
181
+ conferenceDataVersion: input.isOnlineMeeting ? 1 : 0,
182
+ });
183
+ return summarizeEvent(res.data);
184
+ }
185
+ // ---------- update_event ----------
186
+ export const updateEventSchema = z.object({
187
+ eventId: z.string().min(1),
188
+ calendarId: z.string().default("primary"),
189
+ subject: z.string().optional(),
190
+ start: dateTimeWithTz.optional(),
191
+ end: dateTimeWithTz.optional(),
192
+ attendees: z.array(attendeeSchema).optional(),
193
+ location: z.string().optional(),
194
+ body: z.string().optional(),
195
+ sendUpdates: z.enum(["all", "externalOnly", "none"]).default("all"),
196
+ });
197
+ export async function updateEvent(input) {
198
+ const cal = await calendarClient();
199
+ const patch = {};
200
+ if (input.subject !== undefined)
201
+ patch.summary = input.subject;
202
+ if (input.start !== undefined)
203
+ patch.start = input.start;
204
+ if (input.end !== undefined)
205
+ patch.end = input.end;
206
+ if (input.attendees !== undefined) {
207
+ patch.attendees = toGoogleAttendees(input.attendees);
208
+ }
209
+ if (input.location !== undefined)
210
+ patch.location = input.location;
211
+ if (input.body !== undefined)
212
+ patch.description = input.body;
213
+ const changed = Object.keys(patch);
214
+ if (changed.length === 0) {
215
+ return { ok: true, eventId: input.eventId, changed: [] };
216
+ }
217
+ // events.patch does a partial update. Unlike Graph, Google handles single
218
+ // recurring instances fine (an instance has its own id), so we don't refuse.
219
+ const res = await cal.events.patch({
220
+ calendarId: input.calendarId,
221
+ eventId: input.eventId,
222
+ requestBody: patch,
223
+ sendUpdates: input.sendUpdates,
224
+ });
225
+ return {
226
+ ok: true,
227
+ eventId: input.eventId,
228
+ changed,
229
+ event: summarizeEvent(res.data),
230
+ };
231
+ }
232
+ // ---------- cancel_event ----------
233
+ export const cancelEventSchema = z.object({
234
+ eventId: z.string().min(1),
235
+ calendarId: z.string().default("primary"),
236
+ sendUpdates: z
237
+ .enum(["all", "externalOnly", "none"])
238
+ .default("all")
239
+ .describe("Whether to notify attendees of the cancellation. Use 'all' for meetings; 'none' for solo events."),
240
+ });
241
+ export async function cancelEvent(input) {
242
+ const cal = await calendarClient();
243
+ // Google has no separate cancel vs delete: deleting an event you organize
244
+ // sends cancellations to attendees when sendUpdates=all.
245
+ await cal.events.delete({
246
+ calendarId: input.calendarId,
247
+ eventId: input.eventId,
248
+ sendUpdates: input.sendUpdates,
249
+ });
250
+ return { ok: true, eventId: input.eventId, sendUpdates: input.sendUpdates };
251
+ }
252
+ // ---------- respond_to_invite ----------
253
+ const RESPONSE_MAP = {
254
+ accept: "accepted",
255
+ tentativelyAccept: "tentative",
256
+ decline: "declined",
257
+ };
258
+ export const respondSchema = z.object({
259
+ eventId: z.string().min(1),
260
+ calendarId: z.string().default("primary"),
261
+ response: z.enum(["accept", "tentativelyAccept", "decline"]),
262
+ comment: z.string().optional(),
263
+ sendResponse: z
264
+ .boolean()
265
+ .default(true)
266
+ .describe("Whether to notify the organizer of your response."),
267
+ });
268
+ export async function respondToInvite(input) {
269
+ const cal = await calendarClient();
270
+ // Google has no /accept endpoint: you patch your own attendee entry's
271
+ // responseStatus. Fetch the event, find the self attendee, update it.
272
+ const current = await cal.events.get({
273
+ calendarId: input.calendarId,
274
+ eventId: input.eventId,
275
+ });
276
+ const attendees = current.data.attendees ?? [];
277
+ const self = attendees.find((a) => a.self);
278
+ if (!self) {
279
+ throw new Error(`You are not an attendee of event ${input.eventId}, so there is nothing to respond to.`);
280
+ }
281
+ self.responseStatus = RESPONSE_MAP[input.response];
282
+ if (input.comment)
283
+ self.comment = input.comment;
284
+ await cal.events.patch({
285
+ calendarId: input.calendarId,
286
+ eventId: input.eventId,
287
+ requestBody: { attendees },
288
+ sendUpdates: input.sendResponse ? "all" : "none",
289
+ });
290
+ return {
291
+ ok: true,
292
+ eventId: input.eventId,
293
+ response: RESPONSE_MAP[input.response],
294
+ };
295
+ }
296
+ //# sourceMappingURL=calendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../src/tools/calendar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ /** Case-insensitive lookup of a header value from a Gmail message payload. */
2
+ export function header(msg, name) {
3
+ const headers = msg.payload?.headers ?? [];
4
+ const lower = name.toLowerCase();
5
+ return (headers.find((h) => (h.name ?? "").toLowerCase() === lower)?.value ??
6
+ undefined);
7
+ }
8
+ /**
9
+ * Flatten a message into a compact summary for list/search results. Pulls the
10
+ * common headers and the snippet; does not walk the body (use readMessage for
11
+ * that).
12
+ */
13
+ export function summarizeMessage(msg) {
14
+ return {
15
+ id: msg.id,
16
+ threadId: msg.threadId,
17
+ from: header(msg, "From") ?? null,
18
+ to: header(msg, "To") ?? null,
19
+ subject: header(msg, "Subject") ?? null,
20
+ date: header(msg, "Date") ?? null,
21
+ snippet: msg.snippet ?? null,
22
+ labelIds: msg.labelIds ?? [],
23
+ unread: (msg.labelIds ?? []).includes("UNREAD"),
24
+ hasAttachments: hasAttachments(msg),
25
+ };
26
+ }
27
+ /** True if any part of the message carries a filename (i.e. an attachment). */
28
+ export function hasAttachments(msg) {
29
+ const walk = (part) => {
30
+ if (!part)
31
+ return false;
32
+ if (part.filename && part.filename.length > 0)
33
+ return true;
34
+ return (part.parts ?? []).some(walk);
35
+ };
36
+ return walk(msg.payload);
37
+ }
38
+ /**
39
+ * Extract the best-effort body text from a message payload. Prefers text/plain;
40
+ * falls back to text/html (caller may strip). Walks multipart trees.
41
+ */
42
+ export function extractBody(msg) {
43
+ const decode = (data) => data ? Buffer.from(data, "base64url").toString("utf8") : "";
44
+ let plain;
45
+ let html;
46
+ const walk = (part) => {
47
+ if (!part)
48
+ return;
49
+ const mime = part.mimeType ?? "";
50
+ if (mime === "text/plain" && plain === undefined) {
51
+ plain = decode(part.body?.data);
52
+ }
53
+ else if (mime === "text/html" && html === undefined) {
54
+ html = decode(part.body?.data);
55
+ }
56
+ for (const child of part.parts ?? [])
57
+ walk(child);
58
+ };
59
+ walk(msg.payload);
60
+ if (plain !== undefined && plain.length > 0) {
61
+ return { contentType: "text", content: plain };
62
+ }
63
+ if (html !== undefined && html.length > 0) {
64
+ return { contentType: "html", content: html };
65
+ }
66
+ // Single-part message: body sits directly on the payload.
67
+ return { contentType: "text", content: decode(msg.payload?.body?.data) };
68
+ }
69
+ /**
70
+ * Build an RFC-2822 message and return it base64url-encoded for
71
+ * users.messages.send / drafts.create. Gmail wants the raw MIME, not JSON.
72
+ * `threadHeaders` lets replies set In-Reply-To / References.
73
+ */
74
+ export function buildRawMessage(opts) {
75
+ const lines = [];
76
+ lines.push(`To: ${opts.to.join(", ")}`);
77
+ if (opts.cc?.length)
78
+ lines.push(`Cc: ${opts.cc.join(", ")}`);
79
+ if (opts.bcc?.length)
80
+ lines.push(`Bcc: ${opts.bcc.join(", ")}`);
81
+ // Encode the subject as RFC-2047 if it has non-ASCII chars.
82
+ lines.push(`Subject: ${encodeHeaderValue(opts.subject ?? "")}`);
83
+ lines.push("MIME-Version: 1.0");
84
+ // inReplyTo/references are derived from a FETCHED message's headers, not
85
+ // from email-validated input, so strip CR/LF to prevent header injection
86
+ // (a crafted Message-ID could otherwise smuggle "\r\nBcc: ...").
87
+ if (opts.inReplyTo)
88
+ lines.push(`In-Reply-To: ${stripCrlf(opts.inReplyTo)}`);
89
+ if (opts.references)
90
+ lines.push(`References: ${stripCrlf(opts.references)}`);
91
+ const contentType = opts.bodyFormat === "html" ? "text/html" : "text/plain";
92
+ lines.push(`Content-Type: ${contentType}; charset="UTF-8"`);
93
+ lines.push("Content-Transfer-Encoding: base64");
94
+ lines.push("");
95
+ // Body as base64 so arbitrary UTF-8 survives the 7-bit transport.
96
+ lines.push(Buffer.from(opts.body, "utf8").toString("base64"));
97
+ return Buffer.from(lines.join("\r\n"), "utf8").toString("base64url");
98
+ }
99
+ /** Remove CR/LF so a value can't inject extra MIME headers. */
100
+ export function stripCrlf(value) {
101
+ return value.replace(/[\r\n]+/g, " ");
102
+ }
103
+ /**
104
+ * RFC-2047 encode a header value. CR/LF are stripped first so they can't
105
+ * smuggle additional headers (the plain-ASCII branch would otherwise pass
106
+ * them straight through, since the test below treats them as ASCII).
107
+ */
108
+ export function encodeHeaderValue(value) {
109
+ const safe = stripCrlf(value);
110
+ // eslint-disable-next-line no-control-regex
111
+ if (/^[\x00-\x7f]*$/.test(safe))
112
+ return safe;
113
+ return `=?UTF-8?B?${Buffer.from(safe, "utf8").toString("base64")}?=`;
114
+ }
115
+ /** Truncate text to a max length, appending a marker when cut. */
116
+ export function truncate(text, max) {
117
+ if (max === 0 || text.length <= max)
118
+ return { text, truncated: false };
119
+ let cut = text.slice(0, max);
120
+ const lastCode = cut.charCodeAt(cut.length - 1);
121
+ if (lastCode >= 0xd800 && lastCode <= 0xdbff)
122
+ cut = cut.slice(0, -1);
123
+ return { text: cut + "\n…[truncated]", truncated: true };
124
+ }
125
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"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"}