gogcli-mcp-docs 2.7.1 → 2.18.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +20396 -19616
- package/manifest.json +94 -2
- package/package.json +3 -2
- package/server.json +2 -2
- package/src/index.ts +7 -9
- package/src/tools/docs-extra.ts +513 -20
- package/tests/tools/docs-extra.test.ts +1078 -404
- package/tsconfig.json +1 -1
package/src/tools/docs-extra.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags } from '../../../gogcli-mcp/src/lib.js';
|
|
3
|
+
import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags, payloadArg } from '../../../gogcli-mcp/src/lib.js';
|
|
4
|
+
import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
|
|
4
5
|
|
|
5
6
|
export function registerExtraDocsTools(server: McpServer): void {
|
|
6
7
|
server.registerTool('gog_docs_copy', {
|
|
@@ -28,9 +29,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
28
29
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
29
30
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
30
31
|
tabId: z.string().optional().describe('Tab ID to delete content from (for multi-tab docs)'),
|
|
32
|
+
segment: z.string().optional().describe('Target an exact header, footer, or footnote segment ID (header/footer IDs come from gog_docs_header_list / gog_docs_footer_list; footnote segment IDs appear in gog_docs_read json mode) instead of the document body.'),
|
|
33
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
31
34
|
account: accountParam,
|
|
32
35
|
},
|
|
33
|
-
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, account }) => {
|
|
36
|
+
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
34
37
|
const args = ['docs', 'delete'];
|
|
35
38
|
if (start !== undefined) args.push(`--start=${start}`);
|
|
36
39
|
if (end !== undefined) args.push(`--end=${end}`);
|
|
@@ -39,6 +42,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
39
42
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
40
43
|
if (matchCase) args.push('--match-case');
|
|
41
44
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
45
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
46
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
42
47
|
return runOrDiagnose(args, { account });
|
|
43
48
|
});
|
|
44
49
|
|
|
@@ -50,7 +55,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
50
55
|
account: accountParam,
|
|
51
56
|
},
|
|
52
57
|
}, async ({ docId, account }) => {
|
|
53
|
-
return runOrDiagnose(['drive', 'delete', docId], { account });
|
|
58
|
+
return runOrDiagnose(['drive', 'delete', docId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
54
59
|
});
|
|
55
60
|
|
|
56
61
|
server.registerTool('gog_docs_edit', {
|
|
@@ -78,9 +83,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
78
83
|
tab: z.string().optional().describe('Target tab title or ID. In json mode, returns that one tab in the legacy top-level Document shape.'),
|
|
79
84
|
allTabs: z.boolean().optional().describe('Show all tabs. In json mode, returns the canonical Document response with all tab content populated.'),
|
|
80
85
|
maxBytes: z.number().optional().describe('Max bytes to read in text mode (0 = unlimited; default 2000000)'),
|
|
86
|
+
chips: z.boolean().optional().describe('Render Google Docs smart chips (people, dates, rich links) inline in text mode; ignored in json mode'),
|
|
81
87
|
account: accountParam,
|
|
82
88
|
},
|
|
83
|
-
}, async ({ docId, format, tab, allTabs, maxBytes, account }) => {
|
|
89
|
+
}, async ({ docId, format, tab, allTabs, maxBytes, chips, account }) => {
|
|
84
90
|
if (format === 'json') {
|
|
85
91
|
const args = ['docs', 'raw', docId, '--pretty'];
|
|
86
92
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -91,6 +97,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
91
97
|
if (tab) args.push(`--tab=${tab}`);
|
|
92
98
|
if (allTabs) args.push('--all-tabs');
|
|
93
99
|
if (maxBytes !== undefined) args.push(`--max-bytes=${maxBytes}`);
|
|
100
|
+
if (chips) args.push('--chips');
|
|
94
101
|
return runOrDiagnose(args, { account });
|
|
95
102
|
});
|
|
96
103
|
|
|
@@ -122,6 +129,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
122
129
|
lineSpacing: z.number().optional().describe('Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)'),
|
|
123
130
|
headingLevel: z.number().int().optional().describe('Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)'),
|
|
124
131
|
namedStyle: z.enum(['NORMAL_TEXT', 'TITLE', 'SUBTITLE', 'HEADING_1', 'HEADING_2', 'HEADING_3', 'HEADING_4', 'HEADING_5', 'HEADING_6']).optional().describe('Set paragraph named style explicitly'),
|
|
132
|
+
bullets: z.boolean().optional().describe('Turn the matched paragraphs into a bulleted list with the default disc preset'),
|
|
133
|
+
bulletPreset: z.string().optional().describe('Create a bulleted list with a specific Google Docs bullet glyph preset (e.g. BULLET_DISC_CIRCLE_SQUARE)'),
|
|
134
|
+
ordered: z.boolean().optional().describe('Turn the matched paragraphs into a numbered list with the default decimal preset'),
|
|
135
|
+
noBullets: z.boolean().optional().describe('Remove bullets or numbering from the matched paragraphs'),
|
|
136
|
+
indentStart: z.number().optional().describe('Paragraph start indentation in points'),
|
|
137
|
+
indentEnd: z.number().optional().describe('Paragraph end indentation in points'),
|
|
138
|
+
indentFirstLine: z.number().optional().describe('Paragraph first-line indentation in points'),
|
|
139
|
+
spaceAbove: z.number().optional().describe('Space above the paragraph in points'),
|
|
140
|
+
spaceBelow: z.number().optional().describe('Space below the paragraph in points'),
|
|
141
|
+
spacingMode: z.enum(['NEVER_COLLAPSE', 'COLLAPSE_LISTS']).optional().describe('Paragraph spacing collapse behavior: NEVER_COLLAPSE always keeps space-above/space-below; COLLAPSE_LISTS collapses spacing between list items'),
|
|
142
|
+
keepLinesTogether: z.boolean().optional().describe('Keep all lines of the paragraph on one page/column (true) or clear that setting (false)'),
|
|
143
|
+
keepWithNext: z.boolean().optional().describe('Keep the paragraph with the next paragraph (true) or clear that setting (false)'),
|
|
144
|
+
segment: z.string().optional().describe('Target an exact header, footer, or footnote segment ID (header/footer IDs come from gog_docs_header_list / gog_docs_footer_list; footnote segment IDs appear in gog_docs_read json mode) instead of the document body.'),
|
|
145
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
125
146
|
account: accountParam,
|
|
126
147
|
},
|
|
127
148
|
}, async (args) => {
|
|
@@ -142,9 +163,15 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
142
163
|
code?: boolean;
|
|
143
164
|
link?: string; noLink?: boolean;
|
|
144
165
|
alignment?: string;
|
|
166
|
+
batch?: string;
|
|
145
167
|
lineSpacing?: number;
|
|
146
168
|
headingLevel?: number;
|
|
147
169
|
namedStyle?: string;
|
|
170
|
+
bullets?: boolean; bulletPreset?: string; ordered?: boolean; noBullets?: boolean;
|
|
171
|
+
indentStart?: number; indentEnd?: number; indentFirstLine?: number;
|
|
172
|
+
spaceAbove?: number; spaceBelow?: number; spacingMode?: string;
|
|
173
|
+
keepLinesTogether?: boolean; keepWithNext?: boolean;
|
|
174
|
+
segment?: string;
|
|
148
175
|
account?: string;
|
|
149
176
|
};
|
|
150
177
|
const argv = ['docs', 'format', a.docId];
|
|
@@ -171,9 +198,37 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
171
198
|
if (a.lineSpacing !== undefined) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
172
199
|
if (a.headingLevel !== undefined) argv.push(`--heading-level=${a.headingLevel}`);
|
|
173
200
|
if (a.namedStyle) argv.push(`--named-style=${a.namedStyle}`);
|
|
201
|
+
if (a.bullets) argv.push('--bullets');
|
|
202
|
+
if (a.bulletPreset) argv.push(`--bullet-preset=${a.bulletPreset}`);
|
|
203
|
+
if (a.ordered) argv.push('--ordered');
|
|
204
|
+
if (a.noBullets) argv.push('--no-bullets');
|
|
205
|
+
if (a.indentStart !== undefined) argv.push(`--indent-start=${a.indentStart}`);
|
|
206
|
+
if (a.indentEnd !== undefined) argv.push(`--indent-end=${a.indentEnd}`);
|
|
207
|
+
if (a.indentFirstLine !== undefined) argv.push(`--indent-first-line=${a.indentFirstLine}`);
|
|
208
|
+
if (a.spaceAbove !== undefined) argv.push(`--space-above=${a.spaceAbove}`);
|
|
209
|
+
if (a.spaceBelow !== undefined) argv.push(`--space-below=${a.spaceBelow}`);
|
|
210
|
+
if (a.spacingMode) argv.push(`--spacing-mode=${a.spacingMode}`);
|
|
211
|
+
if (a.keepLinesTogether !== undefined) argv.push(a.keepLinesTogether ? '--keep-lines-together' : '--no-keep-lines-together');
|
|
212
|
+
if (a.keepWithNext !== undefined) argv.push(a.keepWithNext ? '--keep-with-next' : '--no-keep-with-next');
|
|
213
|
+
if (a.segment) argv.push(`--segment=${a.segment}`);
|
|
214
|
+
if (a.batch) argv.push(`--batch=${a.batch}`);
|
|
174
215
|
return runOrDiagnose(argv, { account: a.account });
|
|
175
216
|
});
|
|
176
217
|
|
|
218
|
+
server.registerTool('gog_docs_suggestions_list', {
|
|
219
|
+
description: 'List pending suggestions (suggested text insertions and deletions) in a Google Doc, with exact UTF-16 ranges and segment context. Read-only; does not accept or reject suggestions.',
|
|
220
|
+
annotations: { readOnlyHint: true },
|
|
221
|
+
inputSchema: {
|
|
222
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
223
|
+
tab: z.string().optional().describe('Tab title or ID (omit for the first tab)'),
|
|
224
|
+
account: accountParam,
|
|
225
|
+
},
|
|
226
|
+
}, async ({ docId, tab, account }) => {
|
|
227
|
+
const args = ['docs', 'suggestions', 'list', docId];
|
|
228
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
229
|
+
return runOrDiagnose(args, { account });
|
|
230
|
+
});
|
|
231
|
+
|
|
177
232
|
server.registerTool('gog_docs_export', {
|
|
178
233
|
description: 'Export a Google Doc as PDF, plain text, HTML, DOCX, or other format.',
|
|
179
234
|
annotations: { readOnlyHint: true },
|
|
@@ -181,12 +236,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
181
236
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
182
237
|
format: z.string().optional().describe('Export format: pdf, txt, html, docx, rtf, odt, epub (default: pdf)'),
|
|
183
238
|
out: z.string().optional().describe('Output file path'),
|
|
239
|
+
overwrite: z.boolean().optional().describe('Overwrite the output file if it already exists (gog refuses otherwise)'),
|
|
184
240
|
account: accountParam,
|
|
185
241
|
},
|
|
186
|
-
}, async ({ docId, format, out, account }) => {
|
|
242
|
+
}, async ({ docId, format, out, overwrite, account }) => {
|
|
187
243
|
const args = ['docs', 'export', docId];
|
|
188
244
|
if (format) args.push(`--format=${format}`);
|
|
189
245
|
if (out) args.push(`--out=${out}`);
|
|
246
|
+
if (overwrite) args.push('--overwrite');
|
|
190
247
|
return runOrDiagnose(args, { account });
|
|
191
248
|
});
|
|
192
249
|
|
|
@@ -202,9 +259,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
202
259
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
203
260
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
204
261
|
tabId: z.string().optional().describe('Tab ID to insert into (for multi-tab docs)'),
|
|
262
|
+
segment: z.string().optional().describe('Target an exact header, footer, or footnote segment ID (header/footer IDs come from gog_docs_header_list / gog_docs_footer_list; footnote segment IDs appear in gog_docs_read json mode) instead of the document body.'),
|
|
263
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
205
264
|
account: accountParam,
|
|
206
265
|
},
|
|
207
|
-
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, account }) => {
|
|
266
|
+
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
208
267
|
const args = ['docs', 'insert', docId];
|
|
209
268
|
if (content) args.push(content);
|
|
210
269
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -213,6 +272,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
213
272
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
214
273
|
if (matchCase) args.push('--match-case');
|
|
215
274
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
275
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
276
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
216
277
|
return runOrDiagnose(args, { account });
|
|
217
278
|
});
|
|
218
279
|
|
|
@@ -285,10 +346,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
285
346
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
286
347
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
287
348
|
tabId: z.string().optional().describe('Tab ID for multi-tab docs'),
|
|
349
|
+
segment: z.string().optional().describe('Target an exact header, footer, or footnote segment ID (header/footer IDs come from gog_docs_header_list / gog_docs_footer_list; footnote segment IDs appear in gog_docs_read json mode) instead of the document body.'),
|
|
288
350
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
351
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
289
352
|
account: accountParam,
|
|
290
353
|
},
|
|
291
|
-
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, pageless, account }) => {
|
|
354
|
+
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, segment, pageless, batch, account }) => {
|
|
292
355
|
const args = ['docs', 'update', docId];
|
|
293
356
|
if (text) args.push(`--text=${text}`);
|
|
294
357
|
if (file) args.push(`--file=${file}`);
|
|
@@ -299,7 +362,9 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
299
362
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
300
363
|
if (matchCase) args.push('--match-case');
|
|
301
364
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
365
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
302
366
|
if (pageless) args.push('--pageless');
|
|
367
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
303
368
|
return runOrDiagnose(args, { account });
|
|
304
369
|
});
|
|
305
370
|
|
|
@@ -386,7 +451,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
386
451
|
account: accountParam,
|
|
387
452
|
},
|
|
388
453
|
}, async ({ docId, commentId, account }) => {
|
|
389
|
-
|
|
454
|
+
// --force: gog refuses this delete under the runner's --no-input without it.
|
|
455
|
+
return runOrDiagnose(['docs', 'comments', 'delete', docId, commentId, '--force'], { account });
|
|
390
456
|
});
|
|
391
457
|
|
|
392
458
|
server.registerTool('gog_docs_comments_reopen', {
|
|
@@ -432,9 +498,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
432
498
|
all: z.boolean().optional().describe('Return all matches instead of just one'),
|
|
433
499
|
failEmpty: z.boolean().optional().describe('Treat no matches as an error instead of returning an empty result'),
|
|
434
500
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
501
|
+
segment: z.string().optional().describe('Target an exact header, footer, or footnote segment ID (header/footer IDs come from gog_docs_header_list / gog_docs_footer_list; footnote segment IDs appear in gog_docs_read json mode) instead of the document body.'),
|
|
435
502
|
account: accountParam,
|
|
436
503
|
},
|
|
437
|
-
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, account }) => {
|
|
504
|
+
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, segment, account }) => {
|
|
438
505
|
const args = ['docs', 'find-range', docId, text];
|
|
439
506
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
440
507
|
if (matchCase) args.push('--match-case');
|
|
@@ -442,6 +509,84 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
442
509
|
if (all) args.push('--all');
|
|
443
510
|
if (failEmpty) args.push('--fail-empty');
|
|
444
511
|
if (tab) args.push(`--tab=${tab}`);
|
|
512
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
513
|
+
return runOrDiagnose(args, { account });
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Persisted, revision-locked Docs request batches (gog 0.25): begin a batch,
|
|
517
|
+
// run docs mutation tools with batch=<id> to compose requests locally, then
|
|
518
|
+
// end to submit them atomically against the locked revision.
|
|
519
|
+
server.registerTool('gog_batch_begin', {
|
|
520
|
+
description: 'Open a persisted, revision-locked request batch for a Google Doc. Subsequent docs mutation tools called with batch=<batchId> append their requests locally instead of applying them; gog_batch_end submits everything atomically. Returns the batchId.',
|
|
521
|
+
inputSchema: {
|
|
522
|
+
docId: z.string().describe('Google Doc ID the batch is locked to'),
|
|
523
|
+
name: z.string().optional().describe('Optional batch label'),
|
|
524
|
+
account: accountParam,
|
|
525
|
+
},
|
|
526
|
+
}, async ({ docId, name, account }) => {
|
|
527
|
+
const args = ['batch', 'begin', `--doc=${docId}`];
|
|
528
|
+
if (name) args.push(`--name=${name}`);
|
|
529
|
+
return runOrDiagnose(args, { account });
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
server.registerTool('gog_batch_end', {
|
|
533
|
+
description: 'Submit a persisted batch: applies every composed request to the doc in one atomic batchUpdate against the locked revision. Atomic by default — set autoSplit to submit >500-request batches as ordered chunks (non-atomic), or continueOnError to retry individually after an atomic validation failure, retaining failures in the batch.',
|
|
534
|
+
annotations: { destructiveHint: true },
|
|
535
|
+
inputSchema: {
|
|
536
|
+
batchId: z.string().describe('Batch ID (from gog_batch_begin)'),
|
|
537
|
+
autoSplit: z.boolean().optional().describe('Submit batches over 500 requests as ordered chunks (non-atomic)'),
|
|
538
|
+
continueOnError: z.boolean().optional().describe('After an atomic validation failure, submit requests individually and retain failures'),
|
|
539
|
+
account: accountParam,
|
|
540
|
+
},
|
|
541
|
+
}, async ({ batchId, autoSplit, continueOnError, account }) => {
|
|
542
|
+
const args = ['batch', 'end', batchId];
|
|
543
|
+
if (autoSplit) args.push('--auto-split');
|
|
544
|
+
if (continueOnError) args.push('--continue-on-error');
|
|
545
|
+
return runOrDiagnose(args, { account });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
server.registerTool('gog_batch_abort', {
|
|
549
|
+
description: 'Discard a persisted batch and its composed requests without applying anything to the doc.',
|
|
550
|
+
annotations: { destructiveHint: true },
|
|
551
|
+
inputSchema: {
|
|
552
|
+
batchId: z.string().describe('Batch ID to discard'),
|
|
553
|
+
account: accountParam,
|
|
554
|
+
},
|
|
555
|
+
}, async ({ batchId, account }) => {
|
|
556
|
+
return runOrDiagnose(['batch', 'abort', batchId], { account });
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
server.registerTool('gog_batch_list', {
|
|
560
|
+
description: 'List persisted Docs request batches (id, doc, label, request count, status).',
|
|
561
|
+
annotations: { readOnlyHint: true },
|
|
562
|
+
inputSchema: {
|
|
563
|
+
account: accountParam,
|
|
564
|
+
},
|
|
565
|
+
}, async ({ account }) => {
|
|
566
|
+
return runOrDiagnose(['batch', 'list'], { account });
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
server.registerTool('gog_batch_show', {
|
|
570
|
+
description: 'Show one persisted batch: its doc, locked revision, and the composed requests awaiting submission.',
|
|
571
|
+
annotations: { readOnlyHint: true },
|
|
572
|
+
inputSchema: {
|
|
573
|
+
batchId: z.string().describe('Batch ID'),
|
|
574
|
+
account: accountParam,
|
|
575
|
+
},
|
|
576
|
+
}, async ({ batchId, account }) => {
|
|
577
|
+
return runOrDiagnose(['batch', 'show', batchId], { account });
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
server.registerTool('gog_batch_prune', {
|
|
581
|
+
description: 'Delete stale persisted batches (not updated within olderThan).',
|
|
582
|
+
annotations: { destructiveHint: true },
|
|
583
|
+
inputSchema: {
|
|
584
|
+
olderThan: z.string().optional().describe('Delete batches not updated within this duration (e.g. 72h, 7d)'),
|
|
585
|
+
account: accountParam,
|
|
586
|
+
},
|
|
587
|
+
}, async ({ olderThan, account }) => {
|
|
588
|
+
const args = ['batch', 'prune'];
|
|
589
|
+
if (olderThan) args.push(`--older-than=${olderThan}`);
|
|
445
590
|
return runOrDiagnose(args, { account });
|
|
446
591
|
});
|
|
447
592
|
|
|
@@ -455,15 +600,17 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
455
600
|
evenlyDistributed: z.boolean().optional().describe('Reset the selected column (or all columns when col is omitted) to Docs-managed equal width.'),
|
|
456
601
|
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
457
602
|
tab: z.string().optional().describe('Target tab title or ID'),
|
|
603
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
458
604
|
account: accountParam,
|
|
459
605
|
},
|
|
460
|
-
}, async ({ docId, col, width, evenlyDistributed, tableIndex, tab, account }) => {
|
|
606
|
+
}, async ({ docId, col, width, evenlyDistributed, tableIndex, tab, batch, account }) => {
|
|
461
607
|
const args = ['docs', 'table-column-width', docId];
|
|
462
608
|
if (col !== undefined) args.push(`--col=${col}`);
|
|
463
609
|
if (width !== undefined) args.push(`--width=${width}`);
|
|
464
610
|
if (evenlyDistributed) args.push('--evenly-distributed');
|
|
465
611
|
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
466
612
|
if (tab) args.push(`--tab=${tab}`);
|
|
613
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
467
614
|
return runOrDiagnose(args, { account });
|
|
468
615
|
});
|
|
469
616
|
|
|
@@ -719,9 +866,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
719
866
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
720
867
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
721
868
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
869
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
722
870
|
account: accountParam,
|
|
723
871
|
},
|
|
724
|
-
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
872
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
725
873
|
const args = ['docs', 'insert-page-break', docId];
|
|
726
874
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
727
875
|
if (atEnd) args.push('--at-end');
|
|
@@ -729,6 +877,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
729
877
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
730
878
|
if (matchCase) args.push('--match-case');
|
|
731
879
|
if (tab) args.push(`--tab=${tab}`);
|
|
880
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
732
881
|
return runOrDiagnose(args, { account });
|
|
733
882
|
});
|
|
734
883
|
|
|
@@ -789,8 +938,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
789
938
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
790
939
|
row: z.number().int().describe('1-based row number'),
|
|
791
940
|
col: z.number().int().describe('1-based column number'),
|
|
792
|
-
content: z.string().optional().describe('Replacement content (omit when using contentFile)'),
|
|
793
|
-
contentFile: z.string().optional().describe('Read replacement content from a file instead of content'),
|
|
941
|
+
content: z.string().optional().describe('Replacement content (omit when using contentFile). Pass an empty string to clear the cell. Large values are written to a temp file and passed to gog as --content-file automatically.'),
|
|
942
|
+
contentFile: z.string().optional().describe('Read replacement content from a file instead of content. Mutually exclusive with content — gog rejects both together.'),
|
|
794
943
|
append: z.boolean().optional().describe('Append inside the cell instead of replacing existing cell content'),
|
|
795
944
|
format: z.enum(['markdown', 'plain']).optional().describe('Content format (default: markdown)'),
|
|
796
945
|
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
@@ -798,8 +947,21 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
798
947
|
account: accountParam,
|
|
799
948
|
},
|
|
800
949
|
}, async ({ docId, row, col, content, contentFile, append, format, tableIndex, tab, account }) => {
|
|
801
|
-
|
|
802
|
-
|
|
950
|
+
// gog HARD-ERRORS on both forms ("cannot use both --content and
|
|
951
|
+
// --content-file"), so reject here with a clearer message before gog runs.
|
|
952
|
+
// (The slides commands are the opposite: they accept both and the file
|
|
953
|
+
// wins — see the note in slides-extra.ts.)
|
|
954
|
+
if (content !== undefined && contentFile) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
'gog_docs_cell_update accepts content or contentFile, not both — gog refuses the combination ' +
|
|
957
|
+
'("cannot use both --content and --content-file"). Drop one of them.',
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const args: GogArg[] = ['docs', 'cell-update', docId, `--row=${row}`, `--col=${col}`];
|
|
961
|
+
// `content !== undefined` (not truthiness) is deliberate: an empty string is
|
|
962
|
+
// a meaningful value that clears the cell, so it must reach gog as
|
|
963
|
+
// `--content=`. contentFile stays on truthiness — an empty path is nothing.
|
|
964
|
+
if (content !== undefined) args.push(payloadArg('content', 'content-file', content, 'md'));
|
|
803
965
|
if (contentFile) args.push(`--content-file=${contentFile}`);
|
|
804
966
|
if (append) args.push('--append');
|
|
805
967
|
if (format) args.push(`--format=${format}`);
|
|
@@ -822,11 +984,23 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
822
984
|
bold: z.boolean().optional().describe('Set cell text bold'),
|
|
823
985
|
italic: z.boolean().optional().describe('Set cell text italic'),
|
|
824
986
|
underline: z.boolean().optional().describe('Set cell text underline'),
|
|
987
|
+
borderAll: z.string().optional().describe('All borders as WIDTH[,COLOR[,SOLID|DOT|DASH]] (e.g. 1pt,#000,DASH)'),
|
|
988
|
+
borderTop: z.string().optional().describe('Top border (same format as borderAll); overrides borderAll'),
|
|
989
|
+
borderBottom: z.string().optional().describe('Bottom border (same format as borderAll); overrides borderAll'),
|
|
990
|
+
borderLeft: z.string().optional().describe('Left border (same format as borderAll); overrides borderAll'),
|
|
991
|
+
borderRight: z.string().optional().describe('Right border (same format as borderAll); overrides borderAll'),
|
|
992
|
+
paddingAll: z.string().optional().describe('All cell padding (points by default; supports pt, in, cm, mm)'),
|
|
993
|
+
paddingTop: z.string().optional().describe('Top cell padding; overrides paddingAll'),
|
|
994
|
+
paddingBottom: z.string().optional().describe('Bottom cell padding; overrides paddingAll'),
|
|
995
|
+
paddingLeft: z.string().optional().describe('Left cell padding; overrides paddingAll'),
|
|
996
|
+
paddingRight: z.string().optional().describe('Right cell padding; overrides paddingAll'),
|
|
997
|
+
contentAlign: z.enum(['top', 'middle', 'bottom']).optional().describe('Vertical content alignment within the cell'),
|
|
825
998
|
tableIndex: z.number().int().optional().describe('0-based table index in document order (default: 0)'),
|
|
826
999
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1000
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
827
1001
|
account: accountParam,
|
|
828
1002
|
},
|
|
829
|
-
}, async ({ docId, row, col, rowSpan, colSpan, backgroundColor, textColor, bold, italic, underline, tableIndex, tab, account }) => {
|
|
1003
|
+
}, async ({ docId, row, col, rowSpan, colSpan, backgroundColor, textColor, bold, italic, underline, borderAll, borderTop, borderBottom, borderLeft, borderRight, paddingAll, paddingTop, paddingBottom, paddingLeft, paddingRight, contentAlign, tableIndex, tab, batch, account }) => {
|
|
830
1004
|
const args = ['docs', 'cell-style', docId, `--row=${row}`, `--col=${col}`];
|
|
831
1005
|
if (rowSpan !== undefined) args.push(`--row-span=${rowSpan}`);
|
|
832
1006
|
if (colSpan !== undefined) args.push(`--col-span=${colSpan}`);
|
|
@@ -835,8 +1009,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
835
1009
|
if (bold) args.push('--bold');
|
|
836
1010
|
if (italic) args.push('--italic');
|
|
837
1011
|
if (underline) args.push('--underline');
|
|
1012
|
+
if (borderAll) args.push(`--border-all=${borderAll}`);
|
|
1013
|
+
if (borderTop) args.push(`--border-top=${borderTop}`);
|
|
1014
|
+
if (borderBottom) args.push(`--border-bottom=${borderBottom}`);
|
|
1015
|
+
if (borderLeft) args.push(`--border-left=${borderLeft}`);
|
|
1016
|
+
if (borderRight) args.push(`--border-right=${borderRight}`);
|
|
1017
|
+
if (paddingAll) args.push(`--padding-all=${paddingAll}`);
|
|
1018
|
+
if (paddingTop) args.push(`--padding-top=${paddingTop}`);
|
|
1019
|
+
if (paddingBottom) args.push(`--padding-bottom=${paddingBottom}`);
|
|
1020
|
+
if (paddingLeft) args.push(`--padding-left=${paddingLeft}`);
|
|
1021
|
+
if (paddingRight) args.push(`--padding-right=${paddingRight}`);
|
|
1022
|
+
if (contentAlign) args.push(`--content-align=${contentAlign}`);
|
|
838
1023
|
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
839
1024
|
if (tab) args.push(`--tab=${tab}`);
|
|
1025
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
840
1026
|
return runOrDiagnose(args, { account });
|
|
841
1027
|
});
|
|
842
1028
|
|
|
@@ -848,6 +1034,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
848
1034
|
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)'),
|
|
849
1035
|
url: z.string().optional().describe('Public HTTPS image URL to insert directly — no Drive upload or temporary public sharing (exactly one of file or url)'),
|
|
850
1036
|
at: z.string().optional().describe('Placeholder text to replace, or "end" to append (default: end)'),
|
|
1037
|
+
before: z.string().optional().describe('Insert the image immediately before the first literal text match (no text is deleted)'),
|
|
1038
|
+
after: z.string().optional().describe('Insert the image immediately after the first literal text match (no text is deleted)'),
|
|
851
1039
|
width: z.number().optional().describe('Image width in points (default: 468)'),
|
|
852
1040
|
height: z.number().optional().describe('Image height in points (optional; width-only preserves aspect ratio)'),
|
|
853
1041
|
name: z.string().optional().describe('Override the uploaded Drive filename'),
|
|
@@ -856,17 +1044,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
856
1044
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
857
1045
|
account: accountParam,
|
|
858
1046
|
},
|
|
859
|
-
}, async ({ docId, file, url, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
1047
|
+
}, async ({ docId, file, url, at, before, after, width, height, name, parent, onRestricted, tab, account }) => {
|
|
860
1048
|
const args = ['docs', 'insert-image', docId];
|
|
861
1049
|
if (file) args.push(`--file=${file}`);
|
|
862
1050
|
if (url) args.push(`--url=${url}`);
|
|
863
1051
|
if (at) args.push(`--at=${at}`);
|
|
1052
|
+
if (before) args.push(`--before=${before}`);
|
|
1053
|
+
if (after) args.push(`--after=${after}`);
|
|
864
1054
|
if (width !== undefined) args.push(`--width=${width}`);
|
|
865
1055
|
if (height !== undefined) args.push(`--height=${height}`);
|
|
866
1056
|
if (name) args.push(`--name=${name}`);
|
|
867
1057
|
if (parent) args.push(`--parent=${parent}`);
|
|
868
1058
|
if (onRestricted) args.push(`--on-restricted=${onRestricted}`);
|
|
869
1059
|
if (tab) args.push(`--tab=${tab}`);
|
|
1060
|
+
if (file) args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse (local files upload via a temporary public share)
|
|
870
1061
|
return runOrDiagnose(args, { account });
|
|
871
1062
|
});
|
|
872
1063
|
|
|
@@ -882,9 +1073,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
882
1073
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
883
1074
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
884
1075
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1076
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
885
1077
|
account: accountParam,
|
|
886
1078
|
},
|
|
887
|
-
}, async ({ docId, email, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1079
|
+
}, async ({ docId, email, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
888
1080
|
const args = ['docs', 'insert-person', docId, `--email=${email}`];
|
|
889
1081
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
890
1082
|
if (atEnd) args.push('--at-end');
|
|
@@ -892,6 +1084,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
892
1084
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
893
1085
|
if (matchCase) args.push('--match-case');
|
|
894
1086
|
if (tab) args.push(`--tab=${tab}`);
|
|
1087
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
895
1088
|
return runOrDiagnose(args, { account });
|
|
896
1089
|
});
|
|
897
1090
|
|
|
@@ -905,15 +1098,17 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
905
1098
|
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
906
1099
|
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
907
1100
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1101
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
908
1102
|
account: accountParam,
|
|
909
1103
|
},
|
|
910
|
-
}, async ({ docId, date, format, index, atEnd, tab, account }) => {
|
|
1104
|
+
}, async ({ docId, date, format, index, atEnd, tab, batch, account }) => {
|
|
911
1105
|
const args = ['docs', 'insert-date-chip', docId];
|
|
912
1106
|
if (date) args.push(`--date=${date}`);
|
|
913
1107
|
if (format) args.push(`--format=${format}`);
|
|
914
1108
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
915
1109
|
if (atEnd) args.push('--at-end');
|
|
916
1110
|
if (tab) args.push(`--tab=${tab}`);
|
|
1111
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
917
1112
|
return runOrDiagnose(args, { account });
|
|
918
1113
|
});
|
|
919
1114
|
|
|
@@ -958,7 +1153,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
958
1153
|
account: accountParam,
|
|
959
1154
|
},
|
|
960
1155
|
}, async ({ docId, tab, account }) => {
|
|
961
|
-
const args = ['docs', 'delete-tab', docId, `--tab=${tab}
|
|
1156
|
+
const args = ['docs', 'delete-tab', docId, `--tab=${tab}`, '--force']; // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
962
1157
|
return runOrDiagnose(args, { account });
|
|
963
1158
|
});
|
|
964
1159
|
|
|
@@ -972,4 +1167,302 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
972
1167
|
}, async ({ docId, account }) => {
|
|
973
1168
|
return runOrDiagnose(['docs', 'clear', docId], { account });
|
|
974
1169
|
});
|
|
1170
|
+
|
|
1171
|
+
// --- gog 0.30 structural authoring: footnotes, section breaks, rules, columns ---
|
|
1172
|
+
|
|
1173
|
+
server.registerTool('gog_docs_insert_footnote', {
|
|
1174
|
+
description: 'Insert a footnote at a character index (or end-of-doc) and populate its text. The footnote reference mark is placed in the body; the footnote content goes in the new footnote segment.',
|
|
1175
|
+
annotations: { destructiveHint: true },
|
|
1176
|
+
inputSchema: {
|
|
1177
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1178
|
+
text: z.string().optional().describe('Footnote text'),
|
|
1179
|
+
file: z.string().optional().describe('Read footnote text from a file ("-" for stdin)'),
|
|
1180
|
+
index: z.number().int().optional().describe('Character index to place the reference mark at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1181
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1182
|
+
at: z.string().optional().describe('Anchor by literal text and place the reference mark at the start of the matched range'),
|
|
1183
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1184
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1185
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1186
|
+
account: accountParam,
|
|
1187
|
+
},
|
|
1188
|
+
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1189
|
+
const args = ['docs', 'insert-footnote', docId];
|
|
1190
|
+
if (text !== undefined) args.push(`--text=${text}`);
|
|
1191
|
+
if (file) args.push(`--file=${file}`);
|
|
1192
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1193
|
+
if (atEnd) args.push('--at-end');
|
|
1194
|
+
if (at) args.push(`--at=${at}`);
|
|
1195
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1196
|
+
if (matchCase) args.push('--match-case');
|
|
1197
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1198
|
+
return runOrDiagnose(args, { account });
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
server.registerTool('gog_docs_insert_section_break', {
|
|
1202
|
+
description: 'Insert a continuous or next-page section break at a character index (or end-of-doc). Section breaks enable per-section layout such as column counts (see gog_docs_section_columns).',
|
|
1203
|
+
annotations: { destructiveHint: true },
|
|
1204
|
+
inputSchema: {
|
|
1205
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1206
|
+
type: z.enum(['next-page', 'continuous']).optional().describe('Section break type (default: next-page)'),
|
|
1207
|
+
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1208
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1209
|
+
at: z.string().optional().describe('Anchor by literal text and insert at the start of the matched range'),
|
|
1210
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1211
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1212
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1213
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
1214
|
+
account: accountParam,
|
|
1215
|
+
},
|
|
1216
|
+
}, async ({ docId, type, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1217
|
+
const args = ['docs', 'insert-section-break', docId];
|
|
1218
|
+
if (type) args.push(`--type=${type}`);
|
|
1219
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1220
|
+
if (atEnd) args.push('--at-end');
|
|
1221
|
+
if (at) args.push(`--at=${at}`);
|
|
1222
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1223
|
+
if (matchCase) args.push('--match-case');
|
|
1224
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1225
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
1226
|
+
return runOrDiagnose(args, { account });
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
server.registerTool('gog_docs_insert_horizontal_rule', {
|
|
1230
|
+
description: 'Insert a horizontal rule (paragraph border) at a character index (or end-of-doc).',
|
|
1231
|
+
annotations: { destructiveHint: true },
|
|
1232
|
+
inputSchema: {
|
|
1233
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1234
|
+
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1235
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1236
|
+
at: z.string().optional().describe('Anchor by literal text and insert at the start of the matched range'),
|
|
1237
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1238
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1239
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1240
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
1241
|
+
account: accountParam,
|
|
1242
|
+
},
|
|
1243
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1244
|
+
const args = ['docs', 'insert-horizontal-rule', docId];
|
|
1245
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1246
|
+
if (atEnd) args.push('--at-end');
|
|
1247
|
+
if (at) args.push(`--at=${at}`);
|
|
1248
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1249
|
+
if (matchCase) args.push('--match-case');
|
|
1250
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1251
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
1252
|
+
return runOrDiagnose(args, { account });
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
server.registerTool('gog_docs_section_columns', {
|
|
1256
|
+
description: 'Set the column count (1-3) for the document section containing the target position. Use count=1 to reset to a single column. Anchor by index, end-of-doc, or literal text.',
|
|
1257
|
+
annotations: { destructiveHint: true },
|
|
1258
|
+
inputSchema: {
|
|
1259
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1260
|
+
count: z.number().int().min(1).max(3).describe('Number of columns (1-3; 1 resets to one column)'),
|
|
1261
|
+
separator: z.enum(['none', 'between']).optional().describe('Column separator line: none or between'),
|
|
1262
|
+
index: z.number().int().optional().describe('Character index identifying the section (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1263
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1264
|
+
at: z.string().optional().describe('Anchor by literal text and target the section at the start of the matched range'),
|
|
1265
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1266
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1267
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1268
|
+
batch: z.string().optional().describe('Append this mutation to a persisted batch (from gog_batch_begin) instead of applying it — nothing changes in the doc until gog_batch_end submits the batch.'),
|
|
1269
|
+
account: accountParam,
|
|
1270
|
+
},
|
|
1271
|
+
}, async ({ docId, count, separator, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1272
|
+
const args = ['docs', 'section-columns', docId, `--count=${count}`];
|
|
1273
|
+
if (separator) args.push(`--separator=${separator}`);
|
|
1274
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1275
|
+
if (atEnd) args.push('--at-end');
|
|
1276
|
+
if (at) args.push(`--at=${at}`);
|
|
1277
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1278
|
+
if (matchCase) args.push('--match-case');
|
|
1279
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1280
|
+
if (batch) args.push(`--batch=${batch}`);
|
|
1281
|
+
return runOrDiagnose(args, { account });
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
// --- gog 0.30 header & footer lifecycle (segment IDs feed the segment param
|
|
1285
|
+
// on gog_docs_insert/update/delete/format/find-range) ---
|
|
1286
|
+
|
|
1287
|
+
server.registerTool('gog_docs_header_list', {
|
|
1288
|
+
description: 'List a Google Doc\'s headers and their segment IDs. Use a segment ID with the segment param on gog_docs_insert / gog_docs_update / gog_docs_delete / gog_docs_format / gog_docs_find_range to edit inside a header.',
|
|
1289
|
+
annotations: { readOnlyHint: true },
|
|
1290
|
+
inputSchema: {
|
|
1291
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1292
|
+
tab: z.string().optional().describe('Limit results to a tab title or ID'),
|
|
1293
|
+
account: accountParam,
|
|
1294
|
+
},
|
|
1295
|
+
}, async ({ docId, tab, account }) => {
|
|
1296
|
+
const args = ['docs', 'header', 'list', docId];
|
|
1297
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1298
|
+
return runOrDiagnose(args, { account });
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
server.registerTool('gog_docs_header_create', {
|
|
1302
|
+
description: 'Create a header in a Google Doc and optionally populate its initial text. Returns the new header segment ID.',
|
|
1303
|
+
inputSchema: {
|
|
1304
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1305
|
+
text: z.string().optional().describe('Initial header text'),
|
|
1306
|
+
file: z.string().optional().describe('Read initial header text from a file ("-" for stdin)'),
|
|
1307
|
+
index: z.number().int().optional().describe('Character index identifying the section the header belongs to (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1308
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1309
|
+
at: z.string().optional().describe('Anchor by literal text and target the section at the start of the matched range'),
|
|
1310
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1311
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1312
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1313
|
+
account: accountParam,
|
|
1314
|
+
},
|
|
1315
|
+
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1316
|
+
const args = ['docs', 'header', 'create', docId];
|
|
1317
|
+
if (text !== undefined) args.push(`--text=${text}`);
|
|
1318
|
+
if (file) args.push(`--file=${file}`);
|
|
1319
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1320
|
+
if (atEnd) args.push('--at-end');
|
|
1321
|
+
if (at) args.push(`--at=${at}`);
|
|
1322
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1323
|
+
if (matchCase) args.push('--match-case');
|
|
1324
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1325
|
+
return runOrDiagnose(args, { account });
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
server.registerTool('gog_docs_header_delete', {
|
|
1329
|
+
description: 'Delete a header from a Google Doc by its segment ID (from gog_docs_header_list).',
|
|
1330
|
+
annotations: { destructiveHint: true },
|
|
1331
|
+
inputSchema: {
|
|
1332
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1333
|
+
headerId: z.string().describe('Header segment ID (from gog_docs_header_list)'),
|
|
1334
|
+
tab: z.string().optional().describe('Tab title or ID containing the header'),
|
|
1335
|
+
account: accountParam,
|
|
1336
|
+
},
|
|
1337
|
+
}, async ({ docId, headerId, tab, account }) => {
|
|
1338
|
+
const args = ['docs', 'header', 'delete', docId, headerId];
|
|
1339
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1340
|
+
args.push('--force'); // gog refuses this delete under the runner's --no-input without it
|
|
1341
|
+
return runOrDiagnose(args, { account });
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
server.registerTool('gog_docs_footer_list', {
|
|
1345
|
+
description: 'List a Google Doc\'s footers and their segment IDs. Use a segment ID with the segment param on gog_docs_insert / gog_docs_update / gog_docs_delete / gog_docs_format / gog_docs_find_range to edit inside a footer.',
|
|
1346
|
+
annotations: { readOnlyHint: true },
|
|
1347
|
+
inputSchema: {
|
|
1348
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1349
|
+
tab: z.string().optional().describe('Limit results to a tab title or ID'),
|
|
1350
|
+
account: accountParam,
|
|
1351
|
+
},
|
|
1352
|
+
}, async ({ docId, tab, account }) => {
|
|
1353
|
+
const args = ['docs', 'footer', 'list', docId];
|
|
1354
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1355
|
+
return runOrDiagnose(args, { account });
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
server.registerTool('gog_docs_footer_create', {
|
|
1359
|
+
description: 'Create a footer in a Google Doc and optionally populate its initial text. Returns the new footer segment ID.',
|
|
1360
|
+
inputSchema: {
|
|
1361
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1362
|
+
text: z.string().optional().describe('Initial footer text'),
|
|
1363
|
+
file: z.string().optional().describe('Read initial footer text from a file ("-" for stdin)'),
|
|
1364
|
+
index: z.number().int().optional().describe('Character index identifying the section the footer belongs to (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1365
|
+
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
1366
|
+
at: z.string().optional().describe('Anchor by literal text and target the section at the start of the matched range'),
|
|
1367
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
1368
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1369
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1370
|
+
account: accountParam,
|
|
1371
|
+
},
|
|
1372
|
+
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1373
|
+
const args = ['docs', 'footer', 'create', docId];
|
|
1374
|
+
if (text !== undefined) args.push(`--text=${text}`);
|
|
1375
|
+
if (file) args.push(`--file=${file}`);
|
|
1376
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
1377
|
+
if (atEnd) args.push('--at-end');
|
|
1378
|
+
if (at) args.push(`--at=${at}`);
|
|
1379
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
1380
|
+
if (matchCase) args.push('--match-case');
|
|
1381
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1382
|
+
return runOrDiagnose(args, { account });
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
server.registerTool('gog_docs_footer_delete', {
|
|
1386
|
+
description: 'Delete a footer from a Google Doc by its segment ID (from gog_docs_footer_list).',
|
|
1387
|
+
annotations: { destructiveHint: true },
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1390
|
+
footerId: z.string().describe('Footer segment ID (from gog_docs_footer_list)'),
|
|
1391
|
+
tab: z.string().optional().describe('Tab title or ID containing the footer'),
|
|
1392
|
+
account: accountParam,
|
|
1393
|
+
},
|
|
1394
|
+
}, async ({ docId, footerId, tab, account }) => {
|
|
1395
|
+
const args = ['docs', 'footer', 'delete', docId, footerId];
|
|
1396
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1397
|
+
args.push('--force'); // gog refuses this delete under the runner's --no-input without it
|
|
1398
|
+
return runOrDiagnose(args, { account });
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
// --- gog 0.30 image replacement & table-row pin/style ---
|
|
1402
|
+
|
|
1403
|
+
server.registerTool('gog_docs_replace_image', {
|
|
1404
|
+
description: 'Replace an existing image in a Google Doc in place — keeping its position and bounds — with a new image from a local file or public HTTPS URL. Target the image by exact object ID (from gog_docs_images_list), by alt-text substring, or leave both off to replace the only image in the doc/tab.',
|
|
1405
|
+
annotations: { destructiveHint: true },
|
|
1406
|
+
inputSchema: {
|
|
1407
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1408
|
+
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and use (exactly one of file or url)'),
|
|
1409
|
+
url: z.string().optional().describe('Public HTTPS image URL to use directly — no Drive upload (exactly one of file or url)'),
|
|
1410
|
+
objectId: z.string().optional().describe('Exact image object ID to replace (from gog_docs_images_list)'),
|
|
1411
|
+
matchAlt: z.string().optional().describe('Select the image whose alt text contains this value (case-insensitive)'),
|
|
1412
|
+
name: z.string().optional().describe('Override the uploaded Drive filename (file mode)'),
|
|
1413
|
+
parent: z.string().optional().describe('Drive folder ID for an uploaded local image (file mode)'),
|
|
1414
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1415
|
+
account: accountParam,
|
|
1416
|
+
},
|
|
1417
|
+
}, async ({ docId, file, url, objectId, matchAlt, name, parent, tab, account }) => {
|
|
1418
|
+
const args = ['docs', 'replace-image', docId];
|
|
1419
|
+
if (file) args.push(`--file=${file}`);
|
|
1420
|
+
if (url) args.push(`--url=${url}`);
|
|
1421
|
+
if (objectId) args.push(`--object-id=${objectId}`);
|
|
1422
|
+
if (matchAlt) args.push(`--match-alt=${matchAlt}`);
|
|
1423
|
+
if (name) args.push(`--name=${name}`);
|
|
1424
|
+
if (parent) args.push(`--parent=${parent}`);
|
|
1425
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1426
|
+
if (file) args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse (local files upload via a temporary public share)
|
|
1427
|
+
return runOrDiagnose(args, { account });
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
server.registerTool('gog_docs_table_row_pin_header', {
|
|
1431
|
+
description: 'Pin (or unpin) leading rows of a native Google Docs table as repeating header rows. rows=N pins the first N rows; rows=0 unpins all header rows.',
|
|
1432
|
+
annotations: { destructiveHint: true },
|
|
1433
|
+
inputSchema: {
|
|
1434
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1435
|
+
rows: z.number().int().min(0).describe('Number of leading rows to pin as headers; 0 unpins all header rows'),
|
|
1436
|
+
table: tableSelectorParam,
|
|
1437
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1438
|
+
account: accountParam,
|
|
1439
|
+
},
|
|
1440
|
+
}, async ({ docId, rows, table, tab, account }) => {
|
|
1441
|
+
const args = ['docs', 'table-row', 'pin-header', docId, `--rows=${rows}`];
|
|
1442
|
+
if (table) args.push(`--table=${table}`);
|
|
1443
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1444
|
+
return runOrDiagnose(args, { account });
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
server.registerTool('gog_docs_table_row_style', {
|
|
1448
|
+
description: 'Set native table row height and page-overflow behaviour. Target a 1-based row (negative counts from the end) or omit row to style every row in the table.',
|
|
1449
|
+
annotations: { destructiveHint: true },
|
|
1450
|
+
inputSchema: {
|
|
1451
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1452
|
+
row: z.number().int().optional().describe('1-based row number; negative indexes count from the end; omit to style all rows'),
|
|
1453
|
+
minHeight: z.string().optional().describe('Minimum row height (points by default; supports pt, in, cm, mm)'),
|
|
1454
|
+
preventOverflow: z.boolean().optional().describe('Keep the row within one page or column (true) or clear that setting (false)'),
|
|
1455
|
+
table: tableSelectorParam,
|
|
1456
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1457
|
+
account: accountParam,
|
|
1458
|
+
},
|
|
1459
|
+
}, async ({ docId, row, minHeight, preventOverflow, table, tab, account }) => {
|
|
1460
|
+
const args = ['docs', 'table-row', 'style', docId];
|
|
1461
|
+
if (row !== undefined) args.push(`--row=${row}`);
|
|
1462
|
+
if (minHeight) args.push(`--min-height=${minHeight}`);
|
|
1463
|
+
if (preventOverflow !== undefined) args.push(preventOverflow ? '--prevent-overflow' : '--no-prevent-overflow');
|
|
1464
|
+
if (table) args.push(`--table=${table}`);
|
|
1465
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
1466
|
+
return runOrDiagnose(args, { account });
|
|
1467
|
+
});
|
|
975
1468
|
}
|