gogcli-mcp-gmail 2.4.0 → 2.5.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/index.js +16 -10
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/src/tools/gmail-extra.ts +15 -7
- package/tests/tools/gmail-extra.test.ts +45 -0
package/dist/index.js
CHANGED
|
@@ -31274,14 +31274,16 @@ function registerGmailTools(server2) {
|
|
|
31274
31274
|
bcc: external_exports.string().optional().describe("BCC recipients, comma-separated"),
|
|
31275
31275
|
replyToMessageId: external_exports.string().optional().describe("Message ID to reply to"),
|
|
31276
31276
|
threadId: external_exports.string().optional().describe("Thread ID to reply within"),
|
|
31277
|
+
attach: external_exports.array(external_exports.string()).optional().describe("Local file paths to attach (repeatable). Each file is read on the gog server (not this client), base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment. Keep the total under Gmail's ~35 MB inline-upload limit."),
|
|
31277
31278
|
account: accountParam
|
|
31278
31279
|
}
|
|
31279
|
-
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, account }) => {
|
|
31280
|
+
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, attach, account }) => {
|
|
31280
31281
|
const args = ["gmail", "send", `--to=${to}`, `--subject=${subject}`, `--body=${body}`];
|
|
31281
31282
|
if (cc) args.push(`--cc=${cc}`);
|
|
31282
31283
|
if (bcc) args.push(`--bcc=${bcc}`);
|
|
31283
31284
|
if (replyToMessageId) args.push(`--reply-to-message-id=${replyToMessageId}`);
|
|
31284
31285
|
if (threadId) args.push(`--thread-id=${threadId}`);
|
|
31286
|
+
if (attach) for (const path of attach) args.push(`--attach=${path}`);
|
|
31285
31287
|
return runOrDiagnose(args, { account });
|
|
31286
31288
|
});
|
|
31287
31289
|
registerRunTool(server2, { service: "gmail", examples: '"archive", "mark-read", "labels"' });
|
|
@@ -31297,7 +31299,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31297
31299
|
);
|
|
31298
31300
|
|
|
31299
31301
|
// ../gogcli-mcp/src/server.ts
|
|
31300
|
-
var VERSION = true ? "2.
|
|
31302
|
+
var VERSION = true ? "2.5.0" : "0.0.0";
|
|
31301
31303
|
function createServer(options) {
|
|
31302
31304
|
return new McpServer({
|
|
31303
31305
|
name: options?.name ?? "gogcli",
|
|
@@ -31478,7 +31480,7 @@ function registerExtraGmailTools(server2) {
|
|
|
31478
31480
|
return runOrDiagnose(args, { account });
|
|
31479
31481
|
});
|
|
31480
31482
|
server2.registerTool("gog_gmail_thread_get", {
|
|
31481
|
-
description: "Get a Gmail thread with all messages. For long threads that overflow context, use latestN to fetch only the most recent messages and/or snippetsOnly for a lightweight per-message headers+snippet view; sanitizeContent strips raw payloads/HTML and is the biggest size reducer when you do need bodies.",
|
|
31483
|
+
description: "Get a Gmail thread with all messages. For long threads that overflow context, use latestN to fetch only the most recent messages and/or snippetsOnly for a lightweight per-message headers+snippet view; sanitizeContent strips raw payloads/HTML and is the biggest size reducer when you do need bodies. Note each message carries two distinct id concepts: the top-level `id` (the Gmail short hex message id \u2014 pass THIS as replyToMessageId to reply) and the `Message-Id` header (the RFC822 `<\u2026@host>` value used in In-Reply-To/References) \u2014 don't confuse either with the `threadId`. To reply to the thread itself, pass the thread's id as replyToThreadId on gog_gmail_drafts_create.",
|
|
31482
31484
|
annotations: { readOnlyHint: true },
|
|
31483
31485
|
inputSchema: {
|
|
31484
31486
|
threadId: external_exports.string().describe("Gmail thread ID"),
|
|
@@ -31630,10 +31632,11 @@ function registerExtraGmailTools(server2) {
|
|
|
31630
31632
|
subject: external_exports.string().describe("Subject"),
|
|
31631
31633
|
body: external_exports.string().describe("Body (plain text)"),
|
|
31632
31634
|
bodyHtml: external_exports.string().optional().describe("Body (HTML; optional)"),
|
|
31633
|
-
replyToMessageId: external_exports.string().optional().describe("Reply to Gmail
|
|
31635
|
+
replyToMessageId: external_exports.string().optional().describe("Reply to a specific Gmail MESSAGE id \u2014 the short hex `id` field from gog_gmail_get / _search / _thread_get (e.g. 19e7593d77fd9636), NOT a thread id and NOT the RFC822 `<\u2026@host>` Message-Id header. Anchors In-Reply-To/References to that exact message. To reply to a thread when you don't know the latest message, use replyToThreadId instead. If both are given, replyToMessageId wins."),
|
|
31636
|
+
replyToThreadId: external_exports.string().optional().describe(`Reply to a Gmail THREAD id \u2014 passed to gog as --thread-id, which threads the draft using the thread's latest-message headers (In-Reply-To/References). This is what "reply to this thread" almost always means. Mutually exclusive with replyToMessageId (which wins if both are set). Thread ids and message ids are both 16-hex strings and easy to confuse \u2014 use this param, not replyToMessageId, when the id came from a thread.`),
|
|
31634
31637
|
replyTo: external_exports.string().optional().describe("Reply-To header address"),
|
|
31635
|
-
quote: external_exports.boolean().optional().describe("Include quoted original message in reply (requires replyToMessageId)"),
|
|
31636
|
-
attach: external_exports.array(external_exports.string()).optional().describe("
|
|
31638
|
+
quote: external_exports.boolean().optional().describe("Include quoted original message in reply (requires replyToMessageId or replyToThreadId)"),
|
|
31639
|
+
attach: external_exports.array(external_exports.string()).optional().describe("Local file paths to attach (repeatable). Read on the gog server, base64-encoded with a MIME type inferred from the extension. On gog_gmail_drafts_update, supplying attach REPLACES the draft's existing attachments; omitting it preserves them (use clearAttachments to remove all)."),
|
|
31637
31640
|
from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
|
|
31638
31641
|
omitRecipients: external_exports.boolean().optional().describe("Create the draft with no recipients even if to/cc/bcc are supplied \u2014 an accidental-send guard. Populate recipients in a later update before sending."),
|
|
31639
31642
|
returnFull: external_exports.boolean().optional().describe("After writing, re-fetch and return the full stored draft (subject, body, recipients) instead of just the write acknowledgement. Costs one extra read."),
|
|
@@ -31649,6 +31652,7 @@ function registerExtraGmailTools(server2) {
|
|
|
31649
31652
|
args.push(`--body=${f.body}`);
|
|
31650
31653
|
if (f.bodyHtml) args.push(`--body-html=${f.bodyHtml}`);
|
|
31651
31654
|
if (f.replyToMessageId) args.push(`--reply-to-message-id=${f.replyToMessageId}`);
|
|
31655
|
+
else if (f.replyToThreadId) args.push(`--thread-id=${f.replyToThreadId}`);
|
|
31652
31656
|
if (f.replyTo) args.push(`--reply-to=${f.replyTo}`);
|
|
31653
31657
|
if (f.quote) args.push("--quote");
|
|
31654
31658
|
if (f.attach) for (const path of f.attach) args.push(`--attach=${path}`);
|
|
@@ -31668,7 +31672,7 @@ function registerExtraGmailTools(server2) {
|
|
|
31668
31672
|
return runOrDiagnose(["gmail", "drafts", "get", draftId], { account });
|
|
31669
31673
|
}
|
|
31670
31674
|
server2.registerTool("gog_gmail_drafts_create", {
|
|
31671
|
-
description: "Create a new Gmail draft. Recipients (to/cc/bcc) are optional; omit them (or set omitRecipients) to create a recipient-less draft as an accidental-send guard.",
|
|
31675
|
+
description: "Create a new Gmail draft. Recipients (to/cc/bcc) are optional; omit them (or set omitRecipients) to create a recipient-less draft as an accidental-send guard. For replies, prefer replyToThreadId (anchors to the thread's latest message) or replyToMessageId (a specific message) \u2014 don't pass a thread id into replyToMessageId, which mis-threads silently.",
|
|
31672
31676
|
inputSchema: draftWriteSchema
|
|
31673
31677
|
}, async ({ account, returnFull, ...flags }) => {
|
|
31674
31678
|
const args = ["gmail", "drafts", "create"];
|
|
@@ -31676,15 +31680,17 @@ function registerExtraGmailTools(server2) {
|
|
|
31676
31680
|
return writeDraft(args, account, returnFull);
|
|
31677
31681
|
});
|
|
31678
31682
|
server2.registerTool("gog_gmail_drafts_update", {
|
|
31679
|
-
description: "Update an existing Gmail draft.",
|
|
31683
|
+
description: "Update an existing Gmail draft. For replies, prefer replyToThreadId (threads off the thread's latest message) or replyToMessageId (a specific message) over passing a thread id into replyToMessageId. Attachment semantics: supplying attach REPLACES the draft's existing attachments; omitting it preserves them; set clearAttachments to remove all.",
|
|
31680
31684
|
annotations: { destructiveHint: true },
|
|
31681
31685
|
inputSchema: {
|
|
31682
31686
|
draftId: external_exports.string().describe("Draft ID"),
|
|
31683
|
-
...draftWriteSchema
|
|
31687
|
+
...draftWriteSchema,
|
|
31688
|
+
clearAttachments: external_exports.boolean().optional().describe("Remove all attachments from the draft. By default, omitting attach preserves the draft's existing attachments; this intentionally clears them. Ignored if attach is also supplied (attach replaces).")
|
|
31684
31689
|
}
|
|
31685
|
-
}, async ({ draftId, account, returnFull, ...flags }) => {
|
|
31690
|
+
}, async ({ draftId, account, returnFull, clearAttachments, ...flags }) => {
|
|
31686
31691
|
const args = ["gmail", "drafts", "update", draftId];
|
|
31687
31692
|
appendDraftFlags(args, flags);
|
|
31693
|
+
if (clearAttachments) args.push("--clear-attachments");
|
|
31688
31694
|
return writeDraft(args, account, returnFull, draftId);
|
|
31689
31695
|
});
|
|
31690
31696
|
server2.registerTool("gog_gmail_drafts_delete", {
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-gmail",
|
|
5
5
|
"display_name": "gogcli (Gmail)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.5.0",
|
|
7
7
|
"description": "Extended Gmail for Claude via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-gmail",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-gmail",
|
|
5
5
|
"description": "Extended Gmail MCP server via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/src/tools/gmail-extra.ts
CHANGED
|
@@ -208,7 +208,7 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
server.registerTool('gog_gmail_thread_get', {
|
|
211
|
-
description: 'Get a Gmail thread with all messages. For long threads that overflow context, use latestN to fetch only the most recent messages and/or snippetsOnly for a lightweight per-message headers+snippet view; sanitizeContent strips raw payloads/HTML and is the biggest size reducer when you do need bodies.',
|
|
211
|
+
description: 'Get a Gmail thread with all messages. For long threads that overflow context, use latestN to fetch only the most recent messages and/or snippetsOnly for a lightweight per-message headers+snippet view; sanitizeContent strips raw payloads/HTML and is the biggest size reducer when you do need bodies. Note each message carries two distinct id concepts: the top-level `id` (the Gmail short hex message id — pass THIS as replyToMessageId to reply) and the `Message-Id` header (the RFC822 `<…@host>` value used in In-Reply-To/References) — don\'t confuse either with the `threadId`. To reply to the thread itself, pass the thread\'s id as replyToThreadId on gog_gmail_drafts_create.',
|
|
212
212
|
annotations: { readOnlyHint: true },
|
|
213
213
|
inputSchema: {
|
|
214
214
|
threadId: z.string().describe('Gmail thread ID'),
|
|
@@ -371,10 +371,11 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
371
371
|
subject: z.string().describe('Subject'),
|
|
372
372
|
body: z.string().describe('Body (plain text)'),
|
|
373
373
|
bodyHtml: z.string().optional().describe('Body (HTML; optional)'),
|
|
374
|
-
replyToMessageId: z.string().optional().describe('Reply to Gmail
|
|
374
|
+
replyToMessageId: z.string().optional().describe('Reply to a specific Gmail MESSAGE id — the short hex `id` field from gog_gmail_get / _search / _thread_get (e.g. 19e7593d77fd9636), NOT a thread id and NOT the RFC822 `<…@host>` Message-Id header. Anchors In-Reply-To/References to that exact message. To reply to a thread when you don\'t know the latest message, use replyToThreadId instead. If both are given, replyToMessageId wins.'),
|
|
375
|
+
replyToThreadId: z.string().optional().describe('Reply to a Gmail THREAD id — passed to gog as --thread-id, which threads the draft using the thread\'s latest-message headers (In-Reply-To/References). This is what "reply to this thread" almost always means. Mutually exclusive with replyToMessageId (which wins if both are set). Thread ids and message ids are both 16-hex strings and easy to confuse — use this param, not replyToMessageId, when the id came from a thread.'),
|
|
375
376
|
replyTo: z.string().optional().describe('Reply-To header address'),
|
|
376
|
-
quote: z.boolean().optional().describe('Include quoted original message in reply (requires replyToMessageId)'),
|
|
377
|
-
attach: z.array(z.string()).optional().describe('
|
|
377
|
+
quote: z.boolean().optional().describe('Include quoted original message in reply (requires replyToMessageId or replyToThreadId)'),
|
|
378
|
+
attach: z.array(z.string()).optional().describe('Local file paths to attach (repeatable). Read on the gog server, base64-encoded with a MIME type inferred from the extension. On gog_gmail_drafts_update, supplying attach REPLACES the draft\'s existing attachments; omitting it preserves them (use clearAttachments to remove all).'),
|
|
378
379
|
from: z.string().optional().describe('Send from this email address (must be a verified send-as alias)'),
|
|
379
380
|
omitRecipients: z.boolean().optional().describe('Create the draft with no recipients even if to/cc/bcc are supplied — an accidental-send guard. Populate recipients in a later update before sending.'),
|
|
380
381
|
returnFull: z.boolean().optional().describe('After writing, re-fetch and return the full stored draft (subject, body, recipients) instead of just the write acknowledgement. Costs one extra read.'),
|
|
@@ -389,6 +390,7 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
389
390
|
body: string;
|
|
390
391
|
bodyHtml?: string;
|
|
391
392
|
replyToMessageId?: string;
|
|
393
|
+
replyToThreadId?: string;
|
|
392
394
|
replyTo?: string;
|
|
393
395
|
quote?: boolean;
|
|
394
396
|
attach?: string[];
|
|
@@ -405,7 +407,11 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
405
407
|
args.push(`--subject=${f.subject}`);
|
|
406
408
|
args.push(`--body=${f.body}`);
|
|
407
409
|
if (f.bodyHtml) args.push(`--body-html=${f.bodyHtml}`);
|
|
410
|
+
// A draft can reply to a specific message (--reply-to-message-id) or thread
|
|
411
|
+
// off the latest message in a thread (--thread-id, which gog resolves
|
|
412
|
+
// server-side). replyToMessageId wins when both are supplied.
|
|
408
413
|
if (f.replyToMessageId) args.push(`--reply-to-message-id=${f.replyToMessageId}`);
|
|
414
|
+
else if (f.replyToThreadId) args.push(`--thread-id=${f.replyToThreadId}`);
|
|
409
415
|
if (f.replyTo) args.push(`--reply-to=${f.replyTo}`);
|
|
410
416
|
if (f.quote) args.push('--quote');
|
|
411
417
|
if (f.attach) for (const path of f.attach) args.push(`--attach=${path}`);
|
|
@@ -441,7 +447,7 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
441
447
|
}
|
|
442
448
|
|
|
443
449
|
server.registerTool('gog_gmail_drafts_create', {
|
|
444
|
-
description: 'Create a new Gmail draft. Recipients (to/cc/bcc) are optional; omit them (or set omitRecipients) to create a recipient-less draft as an accidental-send guard.',
|
|
450
|
+
description: 'Create a new Gmail draft. Recipients (to/cc/bcc) are optional; omit them (or set omitRecipients) to create a recipient-less draft as an accidental-send guard. For replies, prefer replyToThreadId (anchors to the thread\'s latest message) or replyToMessageId (a specific message) — don\'t pass a thread id into replyToMessageId, which mis-threads silently.',
|
|
445
451
|
inputSchema: draftWriteSchema,
|
|
446
452
|
}, async ({ account, returnFull, ...flags }) => {
|
|
447
453
|
const args = ['gmail', 'drafts', 'create'];
|
|
@@ -450,15 +456,17 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
450
456
|
});
|
|
451
457
|
|
|
452
458
|
server.registerTool('gog_gmail_drafts_update', {
|
|
453
|
-
description: 'Update an existing Gmail draft.',
|
|
459
|
+
description: 'Update an existing Gmail draft. For replies, prefer replyToThreadId (threads off the thread\'s latest message) or replyToMessageId (a specific message) over passing a thread id into replyToMessageId. Attachment semantics: supplying attach REPLACES the draft\'s existing attachments; omitting it preserves them; set clearAttachments to remove all.',
|
|
454
460
|
annotations: { destructiveHint: true },
|
|
455
461
|
inputSchema: {
|
|
456
462
|
draftId: z.string().describe('Draft ID'),
|
|
457
463
|
...draftWriteSchema,
|
|
464
|
+
clearAttachments: z.boolean().optional().describe('Remove all attachments from the draft. By default, omitting attach preserves the draft\'s existing attachments; this intentionally clears them. Ignored if attach is also supplied (attach replaces).'),
|
|
458
465
|
},
|
|
459
|
-
}, async ({ draftId, account, returnFull, ...flags }) => {
|
|
466
|
+
}, async ({ draftId, account, returnFull, clearAttachments, ...flags }) => {
|
|
460
467
|
const args = ['gmail', 'drafts', 'update', draftId];
|
|
461
468
|
appendDraftFlags(args, flags);
|
|
469
|
+
if (clearAttachments) args.push('--clear-attachments');
|
|
462
470
|
return writeDraft(args, account, returnFull, draftId);
|
|
463
471
|
});
|
|
464
472
|
|
|
@@ -545,6 +545,41 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
545
545
|
});
|
|
546
546
|
});
|
|
547
547
|
|
|
548
|
+
describe('gmail draft reply threading (native --thread-id)', () => {
|
|
549
|
+
it('passes replyToThreadId straight through as --thread-id on create (no thread fetch)', async () => {
|
|
550
|
+
await handlers.get('gog_gmail_drafts_create')!({
|
|
551
|
+
subject: 'Re: roof', body: 'Sounds good', replyToThreadId: '19dffe06f9668b28', account: 'me@x.com',
|
|
552
|
+
});
|
|
553
|
+
// gog resolves the thread's latest-message headers itself — no extra fetch.
|
|
554
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
|
|
555
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
556
|
+
['gmail', 'drafts', 'create', '--subject=Re: roof', '--body=Sounds good', '--thread-id=19dffe06f9668b28'],
|
|
557
|
+
{ account: 'me@x.com' },
|
|
558
|
+
);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('passes replyToThreadId as --thread-id on update', async () => {
|
|
562
|
+
await handlers.get('gog_gmail_drafts_update')!({
|
|
563
|
+
draftId: 'd1', subject: 'S', body: 'B', replyToThreadId: 't1',
|
|
564
|
+
});
|
|
565
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
566
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--thread-id=t1'],
|
|
567
|
+
{ account: undefined },
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('replyToMessageId wins when both ids are supplied (no --thread-id)', async () => {
|
|
572
|
+
await handlers.get('gog_gmail_drafts_create')!({
|
|
573
|
+
subject: 'S', body: 'B', replyToMessageId: 'mExplicit', replyToThreadId: 't1',
|
|
574
|
+
});
|
|
575
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
|
|
576
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
577
|
+
['gmail', 'drafts', 'create', '--subject=S', '--body=B', '--reply-to-message-id=mExplicit'],
|
|
578
|
+
{ account: undefined },
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
548
583
|
describe('gog_gmail_drafts_update', () => {
|
|
549
584
|
it('calls runOrDiagnose with draftId and updated fields', async () => {
|
|
550
585
|
await handlers.get('gog_gmail_drafts_update')!({
|
|
@@ -581,6 +616,16 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
581
616
|
);
|
|
582
617
|
});
|
|
583
618
|
|
|
619
|
+
it('passes --clear-attachments when clearAttachments is true', async () => {
|
|
620
|
+
await handlers.get('gog_gmail_drafts_update')!({
|
|
621
|
+
draftId: 'd1', subject: 'S', body: 'B', clearAttachments: true,
|
|
622
|
+
});
|
|
623
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
624
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--clear-attachments'],
|
|
625
|
+
{ account: undefined },
|
|
626
|
+
);
|
|
627
|
+
});
|
|
628
|
+
|
|
584
629
|
it('returnFull re-fetches the draft by its known id', async () => {
|
|
585
630
|
vi.mocked(lib.runOrDiagnose)
|
|
586
631
|
.mockResolvedValueOnce(toText('{"draftId":"d1"}'))
|