gogcli-mcp-docs 2.8.0 → 2.18.1
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 +20340 -19648
- package/manifest.json +70 -2
- package/package.json +3 -2
- package/server.json +2 -2
- package/src/index.ts +7 -9
- package/src/tools/docs-extra.ts +413 -16
- package/tests/tools/docs-extra.test.ts +952 -438
- 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,10 +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.'),
|
|
31
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.'),
|
|
32
34
|
account: accountParam,
|
|
33
35
|
},
|
|
34
|
-
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, batch, account }) => {
|
|
36
|
+
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
35
37
|
const args = ['docs', 'delete'];
|
|
36
38
|
if (start !== undefined) args.push(`--start=${start}`);
|
|
37
39
|
if (end !== undefined) args.push(`--end=${end}`);
|
|
@@ -40,6 +42,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
40
42
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
41
43
|
if (matchCase) args.push('--match-case');
|
|
42
44
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
45
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
43
46
|
if (batch) args.push(`--batch=${batch}`);
|
|
44
47
|
return runOrDiagnose(args, { account });
|
|
45
48
|
});
|
|
@@ -52,7 +55,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
52
55
|
account: accountParam,
|
|
53
56
|
},
|
|
54
57
|
}, async ({ docId, account }) => {
|
|
55
|
-
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
|
|
56
59
|
});
|
|
57
60
|
|
|
58
61
|
server.registerTool('gog_docs_edit', {
|
|
@@ -80,9 +83,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
80
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.'),
|
|
81
84
|
allTabs: z.boolean().optional().describe('Show all tabs. In json mode, returns the canonical Document response with all tab content populated.'),
|
|
82
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'),
|
|
83
87
|
account: accountParam,
|
|
84
88
|
},
|
|
85
|
-
}, async ({ docId, format, tab, allTabs, maxBytes, account }) => {
|
|
89
|
+
}, async ({ docId, format, tab, allTabs, maxBytes, chips, account }) => {
|
|
86
90
|
if (format === 'json') {
|
|
87
91
|
const args = ['docs', 'raw', docId, '--pretty'];
|
|
88
92
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -93,6 +97,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
93
97
|
if (tab) args.push(`--tab=${tab}`);
|
|
94
98
|
if (allTabs) args.push('--all-tabs');
|
|
95
99
|
if (maxBytes !== undefined) args.push(`--max-bytes=${maxBytes}`);
|
|
100
|
+
if (chips) args.push('--chips');
|
|
96
101
|
return runOrDiagnose(args, { account });
|
|
97
102
|
});
|
|
98
103
|
|
|
@@ -124,6 +129,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
124
129
|
lineSpacing: z.number().optional().describe('Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)'),
|
|
125
130
|
headingLevel: z.number().int().optional().describe('Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)'),
|
|
126
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.'),
|
|
127
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.'),
|
|
128
146
|
account: accountParam,
|
|
129
147
|
},
|
|
@@ -149,6 +167,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
149
167
|
lineSpacing?: number;
|
|
150
168
|
headingLevel?: number;
|
|
151
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;
|
|
152
175
|
account?: string;
|
|
153
176
|
};
|
|
154
177
|
const argv = ['docs', 'format', a.docId];
|
|
@@ -175,10 +198,37 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
175
198
|
if (a.lineSpacing !== undefined) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
176
199
|
if (a.headingLevel !== undefined) argv.push(`--heading-level=${a.headingLevel}`);
|
|
177
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}`);
|
|
178
214
|
if (a.batch) argv.push(`--batch=${a.batch}`);
|
|
179
215
|
return runOrDiagnose(argv, { account: a.account });
|
|
180
216
|
});
|
|
181
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
|
+
|
|
182
232
|
server.registerTool('gog_docs_export', {
|
|
183
233
|
description: 'Export a Google Doc as PDF, plain text, HTML, DOCX, or other format.',
|
|
184
234
|
annotations: { readOnlyHint: true },
|
|
@@ -186,12 +236,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
186
236
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
187
237
|
format: z.string().optional().describe('Export format: pdf, txt, html, docx, rtf, odt, epub (default: pdf)'),
|
|
188
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)'),
|
|
189
240
|
account: accountParam,
|
|
190
241
|
},
|
|
191
|
-
}, async ({ docId, format, out, account }) => {
|
|
242
|
+
}, async ({ docId, format, out, overwrite, account }) => {
|
|
192
243
|
const args = ['docs', 'export', docId];
|
|
193
244
|
if (format) args.push(`--format=${format}`);
|
|
194
245
|
if (out) args.push(`--out=${out}`);
|
|
246
|
+
if (overwrite) args.push('--overwrite');
|
|
195
247
|
return runOrDiagnose(args, { account });
|
|
196
248
|
});
|
|
197
249
|
|
|
@@ -207,10 +259,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
207
259
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
208
260
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
209
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.'),
|
|
210
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.'),
|
|
211
264
|
account: accountParam,
|
|
212
265
|
},
|
|
213
|
-
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, batch, account }) => {
|
|
266
|
+
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
214
267
|
const args = ['docs', 'insert', docId];
|
|
215
268
|
if (content) args.push(content);
|
|
216
269
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -219,6 +272,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
219
272
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
220
273
|
if (matchCase) args.push('--match-case');
|
|
221
274
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
275
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
222
276
|
if (batch) args.push(`--batch=${batch}`);
|
|
223
277
|
return runOrDiagnose(args, { account });
|
|
224
278
|
});
|
|
@@ -292,11 +346,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
292
346
|
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
293
347
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
294
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.'),
|
|
295
350
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
296
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.'),
|
|
297
352
|
account: accountParam,
|
|
298
353
|
},
|
|
299
|
-
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, pageless, batch, account }) => {
|
|
354
|
+
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, segment, pageless, batch, account }) => {
|
|
300
355
|
const args = ['docs', 'update', docId];
|
|
301
356
|
if (text) args.push(`--text=${text}`);
|
|
302
357
|
if (file) args.push(`--file=${file}`);
|
|
@@ -307,6 +362,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
307
362
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
308
363
|
if (matchCase) args.push('--match-case');
|
|
309
364
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
365
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
310
366
|
if (pageless) args.push('--pageless');
|
|
311
367
|
if (batch) args.push(`--batch=${batch}`);
|
|
312
368
|
return runOrDiagnose(args, { account });
|
|
@@ -395,7 +451,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
395
451
|
account: accountParam,
|
|
396
452
|
},
|
|
397
453
|
}, async ({ docId, commentId, account }) => {
|
|
398
|
-
|
|
454
|
+
// --force: gog refuses this delete under the runner's --no-input without it.
|
|
455
|
+
return runOrDiagnose(['docs', 'comments', 'delete', docId, commentId, '--force'], { account });
|
|
399
456
|
});
|
|
400
457
|
|
|
401
458
|
server.registerTool('gog_docs_comments_reopen', {
|
|
@@ -441,9 +498,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
441
498
|
all: z.boolean().optional().describe('Return all matches instead of just one'),
|
|
442
499
|
failEmpty: z.boolean().optional().describe('Treat no matches as an error instead of returning an empty result'),
|
|
443
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.'),
|
|
444
502
|
account: accountParam,
|
|
445
503
|
},
|
|
446
|
-
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, account }) => {
|
|
504
|
+
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, segment, account }) => {
|
|
447
505
|
const args = ['docs', 'find-range', docId, text];
|
|
448
506
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
449
507
|
if (matchCase) args.push('--match-case');
|
|
@@ -451,6 +509,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
451
509
|
if (all) args.push('--all');
|
|
452
510
|
if (failEmpty) args.push('--fail-empty');
|
|
453
511
|
if (tab) args.push(`--tab=${tab}`);
|
|
512
|
+
if (segment) args.push(`--segment=${segment}`);
|
|
454
513
|
return runOrDiagnose(args, { account });
|
|
455
514
|
});
|
|
456
515
|
|
|
@@ -879,8 +938,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
879
938
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
880
939
|
row: z.number().int().describe('1-based row number'),
|
|
881
940
|
col: z.number().int().describe('1-based column number'),
|
|
882
|
-
content: z.string().optional().describe('Replacement content (omit when using contentFile)'),
|
|
883
|
-
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.'),
|
|
884
943
|
append: z.boolean().optional().describe('Append inside the cell instead of replacing existing cell content'),
|
|
885
944
|
format: z.enum(['markdown', 'plain']).optional().describe('Content format (default: markdown)'),
|
|
886
945
|
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
@@ -888,8 +947,21 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
888
947
|
account: accountParam,
|
|
889
948
|
},
|
|
890
949
|
}, async ({ docId, row, col, content, contentFile, append, format, tableIndex, tab, account }) => {
|
|
891
|
-
|
|
892
|
-
|
|
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'));
|
|
893
965
|
if (contentFile) args.push(`--content-file=${contentFile}`);
|
|
894
966
|
if (append) args.push('--append');
|
|
895
967
|
if (format) args.push(`--format=${format}`);
|
|
@@ -912,12 +984,23 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
912
984
|
bold: z.boolean().optional().describe('Set cell text bold'),
|
|
913
985
|
italic: z.boolean().optional().describe('Set cell text italic'),
|
|
914
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'),
|
|
915
998
|
tableIndex: z.number().int().optional().describe('0-based table index in document order (default: 0)'),
|
|
916
999
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
917
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.'),
|
|
918
1001
|
account: accountParam,
|
|
919
1002
|
},
|
|
920
|
-
}, async ({ docId, row, col, rowSpan, colSpan, backgroundColor, textColor, bold, italic, underline, tableIndex, tab, batch, 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 }) => {
|
|
921
1004
|
const args = ['docs', 'cell-style', docId, `--row=${row}`, `--col=${col}`];
|
|
922
1005
|
if (rowSpan !== undefined) args.push(`--row-span=${rowSpan}`);
|
|
923
1006
|
if (colSpan !== undefined) args.push(`--col-span=${colSpan}`);
|
|
@@ -926,6 +1009,17 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
926
1009
|
if (bold) args.push('--bold');
|
|
927
1010
|
if (italic) args.push('--italic');
|
|
928
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}`);
|
|
929
1023
|
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
930
1024
|
if (tab) args.push(`--tab=${tab}`);
|
|
931
1025
|
if (batch) args.push(`--batch=${batch}`);
|
|
@@ -940,6 +1034,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
940
1034
|
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)'),
|
|
941
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)'),
|
|
942
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)'),
|
|
943
1039
|
width: z.number().optional().describe('Image width in points (default: 468)'),
|
|
944
1040
|
height: z.number().optional().describe('Image height in points (optional; width-only preserves aspect ratio)'),
|
|
945
1041
|
name: z.string().optional().describe('Override the uploaded Drive filename'),
|
|
@@ -948,17 +1044,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
948
1044
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
949
1045
|
account: accountParam,
|
|
950
1046
|
},
|
|
951
|
-
}, 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 }) => {
|
|
952
1048
|
const args = ['docs', 'insert-image', docId];
|
|
953
1049
|
if (file) args.push(`--file=${file}`);
|
|
954
1050
|
if (url) args.push(`--url=${url}`);
|
|
955
1051
|
if (at) args.push(`--at=${at}`);
|
|
1052
|
+
if (before) args.push(`--before=${before}`);
|
|
1053
|
+
if (after) args.push(`--after=${after}`);
|
|
956
1054
|
if (width !== undefined) args.push(`--width=${width}`);
|
|
957
1055
|
if (height !== undefined) args.push(`--height=${height}`);
|
|
958
1056
|
if (name) args.push(`--name=${name}`);
|
|
959
1057
|
if (parent) args.push(`--parent=${parent}`);
|
|
960
1058
|
if (onRestricted) args.push(`--on-restricted=${onRestricted}`);
|
|
961
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)
|
|
962
1061
|
return runOrDiagnose(args, { account });
|
|
963
1062
|
});
|
|
964
1063
|
|
|
@@ -1054,7 +1153,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1054
1153
|
account: accountParam,
|
|
1055
1154
|
},
|
|
1056
1155
|
}, async ({ docId, tab, account }) => {
|
|
1057
|
-
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
|
|
1058
1157
|
return runOrDiagnose(args, { account });
|
|
1059
1158
|
});
|
|
1060
1159
|
|
|
@@ -1068,4 +1167,302 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1068
1167
|
}, async ({ docId, account }) => {
|
|
1069
1168
|
return runOrDiagnose(['docs', 'clear', docId], { account });
|
|
1070
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
|
+
});
|
|
1071
1468
|
}
|