gogcli-mcp-docs 2.29.1 → 3.0.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 +16776 -18754
- package/manifest.json +1 -1
- package/mint.yaml +2 -2
- package/package.json +4 -4
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +137 -137
- package/tests/tools/docs-extra.test.ts +6 -11
package/src/tools/docs-extra.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags, payloadArg } from '../../../gogcli-mcp/src/lib.js';
|
|
4
4
|
import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
|
|
@@ -6,12 +6,12 @@ import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
|
|
|
6
6
|
export function registerExtraDocsTools(server: McpServer): void {
|
|
7
7
|
server.registerTool('gog_docs_copy', {
|
|
8
8
|
description: 'Copy a Google Doc to a new document with the given title.',
|
|
9
|
-
inputSchema: {
|
|
9
|
+
inputSchema: z.object({
|
|
10
10
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
11
11
|
title: z.string().describe('Title for the new copy'),
|
|
12
12
|
parent: z.string().optional().describe('Parent folder ID to place the copy in'),
|
|
13
13
|
account: accountParam,
|
|
14
|
-
},
|
|
14
|
+
}),
|
|
15
15
|
}, async ({ docId, title, parent, account }) => {
|
|
16
16
|
const args = ['docs', 'copy', docId, title];
|
|
17
17
|
if (parent) args.push(`--parent=${parent}`);
|
|
@@ -21,7 +21,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
21
21
|
server.registerTool('gog_docs_delete', {
|
|
22
22
|
description: 'Delete content within a Google Doc by character index range. To remove the entire document (move to Drive trash), use gog_docs_trash.',
|
|
23
23
|
annotations: { destructiveHint: true },
|
|
24
|
-
inputSchema: {
|
|
24
|
+
inputSchema: z.object({
|
|
25
25
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
26
26
|
start: z.number().optional().describe('Start index (character position, 1-based). Required unless `at` is set.'),
|
|
27
27
|
end: z.number().optional().describe('End index (character position, exclusive). Required unless `at` is set.'),
|
|
@@ -32,7 +32,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
32
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
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.'),
|
|
34
34
|
account: accountParam,
|
|
35
|
-
},
|
|
35
|
+
}),
|
|
36
36
|
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
37
37
|
const args = ['docs', 'delete'];
|
|
38
38
|
if (start !== undefined) args.push(`--start=${start}`);
|
|
@@ -50,10 +50,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
50
50
|
server.registerTool('gog_docs_trash', {
|
|
51
51
|
description: 'Move an entire Google Doc to Drive trash. Convenience wrapper around `gog drive delete` so docs-only users can clean up without installing gogcli-mcp-drive. The doc remains recoverable from Drive trash for ~30 days.',
|
|
52
52
|
annotations: { destructiveHint: true },
|
|
53
|
-
inputSchema: {
|
|
53
|
+
inputSchema: z.object({
|
|
54
54
|
docId: z.string().describe('Doc ID to move to trash'),
|
|
55
55
|
account: accountParam,
|
|
56
|
-
},
|
|
56
|
+
}),
|
|
57
57
|
}, async ({ docId, account }) => {
|
|
58
58
|
return runOrDiagnose(['drive', 'delete', docId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
59
59
|
});
|
|
@@ -61,13 +61,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
61
61
|
server.registerTool('gog_docs_edit', {
|
|
62
62
|
description: 'Edit a Google Doc by finding and replacing text (stream-edit style).',
|
|
63
63
|
annotations: { destructiveHint: true },
|
|
64
|
-
inputSchema: {
|
|
64
|
+
inputSchema: z.object({
|
|
65
65
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
66
66
|
find: z.string().describe('Text to find'),
|
|
67
67
|
replace: z.string().describe('Replacement text'),
|
|
68
68
|
matchCase: z.boolean().optional().describe('Case-sensitive matching'),
|
|
69
69
|
account: accountParam,
|
|
70
|
-
},
|
|
70
|
+
}),
|
|
71
71
|
}, async ({ docId, find, replace, matchCase, account }) => {
|
|
72
72
|
const args = ['docs', 'edit', docId, find, replace];
|
|
73
73
|
if (matchCase) args.push('--match-case');
|
|
@@ -77,7 +77,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
77
77
|
server.registerTool('gog_docs_read', {
|
|
78
78
|
description: 'Read the content of a Google Doc. Default: plain text body. Use format="json" for the raw Google Docs API response (lossless, includes character indices needed for index-based gog_docs_insert / gog_docs_delete calls). For markdown output, use gog_docs_export with format="md" — it writes to a file. Use gog_docs_structure to see paragraph-by-paragraph layout with indices.',
|
|
79
79
|
annotations: { readOnlyHint: true },
|
|
80
|
-
inputSchema: {
|
|
80
|
+
inputSchema: z.object({
|
|
81
81
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
82
82
|
format: z.enum(['text', 'json']).optional().describe('Output format (default: text)'),
|
|
83
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.'),
|
|
@@ -85,7 +85,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
85
85
|
maxBytes: z.number().optional().describe('Max bytes to read in text mode (0 = unlimited; default 2000000)'),
|
|
86
86
|
chips: z.boolean().optional().describe('Render Google Docs smart chips (people, dates, rich links) inline in text mode; ignored in json mode'),
|
|
87
87
|
account: accountParam,
|
|
88
|
-
},
|
|
88
|
+
}),
|
|
89
89
|
}, async ({ docId, format, tab, allTabs, maxBytes, chips, account }) => {
|
|
90
90
|
if (format === 'json') {
|
|
91
91
|
const args = ['docs', 'raw', docId, '--pretty'];
|
|
@@ -105,7 +105,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
105
105
|
server.registerTool('gog_docs_format', {
|
|
106
106
|
description: 'Apply text or paragraph formatting to a Google Doc. Use `match` to format a specific text occurrence, `matchAll` to format every occurrence, or omit both to format the whole doc. Boolean flags (bold/italic/etc.) set the attribute; negated flags (noBold/noItalic/etc.) clear it.',
|
|
107
107
|
annotations: { destructiveHint: true },
|
|
108
|
-
inputSchema: {
|
|
108
|
+
inputSchema: z.object({
|
|
109
109
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
110
110
|
match: z.string().optional().describe('Format only the first text match'),
|
|
111
111
|
matchAll: z.boolean().optional().describe('Format all matches instead of only the first'),
|
|
@@ -145,7 +145,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
145
145
|
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.'),
|
|
146
146
|
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.'),
|
|
147
147
|
account: accountParam,
|
|
148
|
-
},
|
|
148
|
+
}),
|
|
149
149
|
}, async (args) => {
|
|
150
150
|
const a = args as {
|
|
151
151
|
docId: string;
|
|
@@ -219,11 +219,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
219
219
|
server.registerTool('gog_docs_suggestions_list', {
|
|
220
220
|
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.',
|
|
221
221
|
annotations: { readOnlyHint: true },
|
|
222
|
-
inputSchema: {
|
|
222
|
+
inputSchema: z.object({
|
|
223
223
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
224
224
|
tab: z.string().optional().describe('Tab title or ID (omit for the first tab)'),
|
|
225
225
|
account: accountParam,
|
|
226
|
-
},
|
|
226
|
+
}),
|
|
227
227
|
}, async ({ docId, tab, account }) => {
|
|
228
228
|
const args = ['docs', 'suggestions', 'list', docId];
|
|
229
229
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -233,13 +233,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
233
233
|
server.registerTool('gog_docs_export', {
|
|
234
234
|
description: 'Export a Google Doc as PDF, plain text, HTML, DOCX, or other format.',
|
|
235
235
|
annotations: { readOnlyHint: true },
|
|
236
|
-
inputSchema: {
|
|
236
|
+
inputSchema: z.object({
|
|
237
237
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
238
238
|
format: z.string().optional().describe('Export format: pdf, txt, html, docx, rtf, odt, epub (default: pdf)'),
|
|
239
239
|
out: z.string().optional().describe('Output file path'),
|
|
240
240
|
overwrite: z.boolean().optional().describe('Overwrite the output file if it already exists (gog refuses otherwise)'),
|
|
241
241
|
account: accountParam,
|
|
242
|
-
},
|
|
242
|
+
}),
|
|
243
243
|
}, async ({ docId, format, out, overwrite, account }) => {
|
|
244
244
|
const args = ['docs', 'export', docId];
|
|
245
245
|
if (format) args.push(`--format=${format}`);
|
|
@@ -251,7 +251,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
251
251
|
server.registerTool('gog_docs_insert', {
|
|
252
252
|
description: 'Insert text at a specific character index in a Google Doc. When `index` is omitted, gog defaults to 1 (the very beginning), NOT the end — sequential inserts without an explicit index produce reversed output. To append at the end of the doc, use gog_docs_append (which uses `gog docs write --append` and is the right tool for iterative document construction). To find a valid index for mid-document inserts, call gog_docs_structure or gog_docs_read first.',
|
|
253
253
|
annotations: { destructiveHint: true },
|
|
254
|
-
inputSchema: {
|
|
254
|
+
inputSchema: z.object({
|
|
255
255
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
256
256
|
content: z.string().optional().describe('Text content to insert'),
|
|
257
257
|
index: z.number().optional().describe('Character index to insert at (1-based; default: 1 = start of doc). Prefer gog_docs_append when you want to add at the end.'),
|
|
@@ -263,7 +263,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
263
263
|
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.'),
|
|
264
264
|
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.'),
|
|
265
265
|
account: accountParam,
|
|
266
|
-
},
|
|
266
|
+
}),
|
|
267
267
|
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, segment, batch, account }) => {
|
|
268
268
|
const args = ['docs', 'insert', docId];
|
|
269
269
|
if (content) args.push(content);
|
|
@@ -281,14 +281,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
281
281
|
server.registerTool('gog_docs_append', {
|
|
282
282
|
description: 'Append text to the end of a Google Doc. This is the right tool for iterative document construction — multiple sequential calls produce content in the order they were called. Use gog_docs_insert only when you need to insert at a specific character position. Known markdown=true limitations (tracked upstream): (a) 3+ tables in one call reorders the trailing punctuation of the paragraph before the 3rd table — split into multiple calls with ≤2 tables each (openclaw/gogcli#607); (b) inline **bold** / *italic* / `code` inside table cells renders as literal characters — pre-format cell text separately or apply formatting after the append via gog_docs_format (openclaw/gogcli#608); (c) tables with an empty header row leak the last data row as literal pipe text — always supply a non-empty header (openclaw/gogcli#609).',
|
|
283
283
|
annotations: { destructiveHint: true },
|
|
284
|
-
inputSchema: {
|
|
284
|
+
inputSchema: z.object({
|
|
285
285
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
286
286
|
text: z.string().optional().describe('Text content to append'),
|
|
287
287
|
file: z.string().optional().describe('Path to a text file to append, read on the gog server. gog also accepts "-" for stdin, but this server never writes to gog\'s stdin, so "-" would hang until the call times out.'),
|
|
288
288
|
markdown: z.boolean().optional().describe('Convert markdown to Google Docs formatting (headings, bold, lists, etc.). See the tool description for known upstream limitations around tables.'),
|
|
289
289
|
tab: z.string().optional().describe('Target tab title or ID (for multi-tab docs)'),
|
|
290
290
|
account: accountParam,
|
|
291
|
-
},
|
|
291
|
+
}),
|
|
292
292
|
}, async ({ docId, text, file, markdown, tab, account }) => {
|
|
293
293
|
const args = ['docs', 'write', docId, '--append'];
|
|
294
294
|
if (text) args.push(`--text=${text}`);
|
|
@@ -301,10 +301,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
301
301
|
server.registerTool('gog_docs_list_tabs', {
|
|
302
302
|
description: 'List all tabs in a Google Doc.',
|
|
303
303
|
annotations: { readOnlyHint: true },
|
|
304
|
-
inputSchema: {
|
|
304
|
+
inputSchema: z.object({
|
|
305
305
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
306
306
|
account: accountParam,
|
|
307
|
-
},
|
|
307
|
+
}),
|
|
308
308
|
}, async ({ docId, account }) => {
|
|
309
309
|
return runOrDiagnose(['docs', 'list-tabs', docId], { account });
|
|
310
310
|
});
|
|
@@ -312,14 +312,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
312
312
|
server.registerTool('gog_docs_sed', {
|
|
313
313
|
description: 'Stream-edit a Google Doc with sed-like regex expressions (s/find/replace/ syntax).',
|
|
314
314
|
annotations: { destructiveHint: true },
|
|
315
|
-
inputSchema: {
|
|
315
|
+
inputSchema: z.object({
|
|
316
316
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
317
317
|
expression: z.string().optional().describe('Single sed expression (e.g. "s/old/new/g")'),
|
|
318
318
|
expressions: z.array(z.string()).optional().describe('Multiple sed expressions'),
|
|
319
319
|
file: z.string().optional().describe('Path to a sed script file'),
|
|
320
320
|
tab: z.string().optional().describe('Tab name to edit'),
|
|
321
321
|
account: accountParam,
|
|
322
|
-
},
|
|
322
|
+
}),
|
|
323
323
|
}, async ({ docId, expression, expressions, file, tab, account }) => {
|
|
324
324
|
const args = ['docs', 'sed', docId];
|
|
325
325
|
if (expression) args.push(expression);
|
|
@@ -336,7 +336,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
336
336
|
server.registerTool('gog_docs_update', {
|
|
337
337
|
description: 'Update a Google Doc — insert or replace text at a specific position.',
|
|
338
338
|
annotations: { destructiveHint: true },
|
|
339
|
-
inputSchema: {
|
|
339
|
+
inputSchema: z.object({
|
|
340
340
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
341
341
|
text: z.string().optional().describe('Text content to write'),
|
|
342
342
|
file: z.string().optional().describe('Path to a file whose content to use'),
|
|
@@ -351,7 +351,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
351
351
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
352
352
|
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.'),
|
|
353
353
|
account: accountParam,
|
|
354
|
-
},
|
|
354
|
+
}),
|
|
355
355
|
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, segment, pageless, batch, account }) => {
|
|
356
356
|
const args = ['docs', 'update', docId];
|
|
357
357
|
if (text) args.push(`--text=${text}`);
|
|
@@ -379,7 +379,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
379
379
|
'matches[] always spans every tab, so a quote appearing in two tabs is visible as an ambiguity rather than silently resolved. ' +
|
|
380
380
|
'tab keeps only comments with a match in that tab, which drops orphaned comments and comments that quote nothing.',
|
|
381
381
|
annotations: { readOnlyHint: true },
|
|
382
|
-
inputSchema: {
|
|
382
|
+
inputSchema: z.object({
|
|
383
383
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
384
384
|
includeResolved: z.boolean().optional().describe('Include resolved comments (default: false, open only)'),
|
|
385
385
|
since: z.string().optional().describe('Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)'),
|
|
@@ -387,7 +387,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
387
387
|
tab: z.string().optional().describe('Only return comments whose quoted text resolves inside this tab (title or ID). Implies locate, and drops orphaned and unquoted comments.'),
|
|
388
388
|
...paginationParams,
|
|
389
389
|
account: accountParam,
|
|
390
|
-
},
|
|
390
|
+
}),
|
|
391
391
|
}, async ({ docId, includeResolved, since, locate, tab, max, page, all, account }) => {
|
|
392
392
|
const args = ['docs', 'comments', 'list', docId];
|
|
393
393
|
if (includeResolved) args.push('--include-resolved');
|
|
@@ -401,11 +401,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
401
401
|
server.registerTool('gog_docs_comments_get', {
|
|
402
402
|
description: 'Get a single comment by ID, including its replies.',
|
|
403
403
|
annotations: { readOnlyHint: true },
|
|
404
|
-
inputSchema: {
|
|
404
|
+
inputSchema: z.object({
|
|
405
405
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
406
406
|
commentId: z.string().describe('Comment ID'),
|
|
407
407
|
account: accountParam,
|
|
408
|
-
},
|
|
408
|
+
}),
|
|
409
409
|
}, async ({ docId, commentId, account }) => {
|
|
410
410
|
return runOrDiagnose(['docs', 'comments', 'get', docId, commentId], { account });
|
|
411
411
|
});
|
|
@@ -413,12 +413,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
413
413
|
server.registerTool('gog_docs_comments_add', {
|
|
414
414
|
description:
|
|
415
415
|
'Add a comment to a Google Doc. Optionally attach quoted text that appears as the highlighted passage in the Google Docs UI.',
|
|
416
|
-
inputSchema: {
|
|
416
|
+
inputSchema: z.object({
|
|
417
417
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
418
418
|
content: z.string().describe('Comment text'),
|
|
419
419
|
quoted: z.string().optional().describe('Quoted text to attach to the comment (shown in UIs when available)'),
|
|
420
420
|
account: accountParam,
|
|
421
|
-
},
|
|
421
|
+
}),
|
|
422
422
|
}, async ({ docId, content, quoted, account }) => {
|
|
423
423
|
const args = ['docs', 'comments', 'add', docId, content];
|
|
424
424
|
if (quoted) args.push(`--quoted=${quoted}`);
|
|
@@ -427,12 +427,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
427
427
|
|
|
428
428
|
server.registerTool('gog_docs_comments_reply', {
|
|
429
429
|
description: 'Reply to an existing comment on a Google Doc.',
|
|
430
|
-
inputSchema: {
|
|
430
|
+
inputSchema: z.object({
|
|
431
431
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
432
432
|
commentId: z.string().describe('Comment ID to reply to'),
|
|
433
433
|
content: z.string().describe('Reply text'),
|
|
434
434
|
account: accountParam,
|
|
435
|
-
},
|
|
435
|
+
}),
|
|
436
436
|
}, async ({ docId, commentId, content, account }) => {
|
|
437
437
|
return runOrDiagnose(['docs', 'comments', 'reply', docId, commentId, content], { account });
|
|
438
438
|
});
|
|
@@ -440,12 +440,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
440
440
|
server.registerTool('gog_docs_comments_resolve', {
|
|
441
441
|
description: 'Resolve a comment (mark as done). Optionally include a closing message.',
|
|
442
442
|
annotations: { destructiveHint: true },
|
|
443
|
-
inputSchema: {
|
|
443
|
+
inputSchema: z.object({
|
|
444
444
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
445
445
|
commentId: z.string().describe('Comment ID to resolve'),
|
|
446
446
|
message: z.string().optional().describe('Optional message to include when resolving'),
|
|
447
447
|
account: accountParam,
|
|
448
|
-
},
|
|
448
|
+
}),
|
|
449
449
|
}, async ({ docId, commentId, message, account }) => {
|
|
450
450
|
const args = ['docs', 'comments', 'resolve', docId, commentId];
|
|
451
451
|
if (message) args.push(`--message=${message}`);
|
|
@@ -455,11 +455,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
455
455
|
server.registerTool('gog_docs_comments_delete', {
|
|
456
456
|
description: 'Delete a comment from a Google Doc. This action is permanent.',
|
|
457
457
|
annotations: { destructiveHint: true },
|
|
458
|
-
inputSchema: {
|
|
458
|
+
inputSchema: z.object({
|
|
459
459
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
460
460
|
commentId: z.string().describe('Comment ID to delete'),
|
|
461
461
|
account: accountParam,
|
|
462
|
-
},
|
|
462
|
+
}),
|
|
463
463
|
}, async ({ docId, commentId, account }) => {
|
|
464
464
|
// --force: gog refuses this delete under the runner's --no-input without it.
|
|
465
465
|
return runOrDiagnose(['docs', 'comments', 'delete', docId, commentId, '--force'], { account });
|
|
@@ -468,11 +468,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
468
468
|
server.registerTool('gog_docs_comments_reopen', {
|
|
469
469
|
description: 'Reopen a previously resolved comment (flip resolved → open).',
|
|
470
470
|
annotations: { destructiveHint: true },
|
|
471
|
-
inputSchema: {
|
|
471
|
+
inputSchema: z.object({
|
|
472
472
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
473
473
|
commentId: z.string().describe('Comment ID to reopen'),
|
|
474
474
|
account: accountParam,
|
|
475
|
-
},
|
|
475
|
+
}),
|
|
476
476
|
}, async ({ docId, commentId, account }) => {
|
|
477
477
|
return runOrDiagnose(['docs', 'comments', 'reopen', docId, commentId], { account });
|
|
478
478
|
});
|
|
@@ -480,14 +480,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
480
480
|
server.registerTool('gog_docs_comments_locate', {
|
|
481
481
|
description: 'Locate a comment\'s anchor in a Google Doc — resolves the comment\'s quoted text to its current Docs API index range, or reports the comment as orphaned if the quote can no longer be found (e.g. the anchored text was edited away). Read-only; useful before an index-based edit near a comment.',
|
|
482
482
|
annotations: { readOnlyHint: true },
|
|
483
|
-
inputSchema: {
|
|
483
|
+
inputSchema: z.object({
|
|
484
484
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
485
485
|
commentId: z.string().describe('Comment ID to locate'),
|
|
486
486
|
matchCase: z.boolean().optional().describe('Case-sensitive matching of the comment quote'),
|
|
487
487
|
normalizeWhitespace: z.boolean().optional().describe('Collapse whitespace while matching the comment quote'),
|
|
488
488
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
489
489
|
account: accountParam,
|
|
490
|
-
},
|
|
490
|
+
}),
|
|
491
491
|
}, async ({ docId, commentId, matchCase, normalizeWhitespace, tab, account }) => {
|
|
492
492
|
const args = ['docs', 'comments', 'locate', docId, commentId];
|
|
493
493
|
if (matchCase) args.push('--match-case');
|
|
@@ -499,7 +499,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
499
499
|
server.registerTool('gog_docs_find_range', {
|
|
500
500
|
description: 'Map literal text in a Google Doc to its Docs API UTF-16 index range(s). Read-only helper for computing the start/end indices that index-based tools (gog_docs_delete, gog_docs_update --replace-range) need. Returns the first match by default; use occurrence to pick a specific one or all to return every match.',
|
|
501
501
|
annotations: { readOnlyHint: true },
|
|
502
|
-
inputSchema: {
|
|
502
|
+
inputSchema: z.object({
|
|
503
503
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
504
504
|
text: z.string().describe('Literal text to locate'),
|
|
505
505
|
occurrence: z.number().int().optional().describe('Return the Nth occurrence (1-based; default: first)'),
|
|
@@ -510,7 +510,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
510
510
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
511
511
|
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.'),
|
|
512
512
|
account: accountParam,
|
|
513
|
-
},
|
|
513
|
+
}),
|
|
514
514
|
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, segment, account }) => {
|
|
515
515
|
const args = ['docs', 'find-range', docId, text];
|
|
516
516
|
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
@@ -528,11 +528,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
528
528
|
// end to submit them atomically against the locked revision.
|
|
529
529
|
server.registerTool('gog_batch_begin', {
|
|
530
530
|
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.',
|
|
531
|
-
inputSchema: {
|
|
531
|
+
inputSchema: z.object({
|
|
532
532
|
docId: z.string().describe('Google Doc ID the batch is locked to'),
|
|
533
533
|
name: z.string().optional().describe('Optional batch label'),
|
|
534
534
|
account: accountParam,
|
|
535
|
-
},
|
|
535
|
+
}),
|
|
536
536
|
}, async ({ docId, name, account }) => {
|
|
537
537
|
const args = ['batch', 'begin', `--doc=${docId}`];
|
|
538
538
|
if (name) args.push(`--name=${name}`);
|
|
@@ -542,12 +542,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
542
542
|
server.registerTool('gog_batch_end', {
|
|
543
543
|
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.',
|
|
544
544
|
annotations: { destructiveHint: true },
|
|
545
|
-
inputSchema: {
|
|
545
|
+
inputSchema: z.object({
|
|
546
546
|
batchId: z.string().describe('Batch ID (from gog_batch_begin)'),
|
|
547
547
|
autoSplit: z.boolean().optional().describe('Submit batches over 500 requests as ordered chunks (non-atomic)'),
|
|
548
548
|
continueOnError: z.boolean().optional().describe('After an atomic validation failure, submit requests individually and retain failures'),
|
|
549
549
|
account: accountParam,
|
|
550
|
-
},
|
|
550
|
+
}),
|
|
551
551
|
}, async ({ batchId, autoSplit, continueOnError, account }) => {
|
|
552
552
|
const args = ['batch', 'end', batchId];
|
|
553
553
|
if (autoSplit) args.push('--auto-split');
|
|
@@ -558,10 +558,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
558
558
|
server.registerTool('gog_batch_abort', {
|
|
559
559
|
description: 'Discard a persisted batch and its composed requests without applying anything to the doc.',
|
|
560
560
|
annotations: { destructiveHint: true },
|
|
561
|
-
inputSchema: {
|
|
561
|
+
inputSchema: z.object({
|
|
562
562
|
batchId: z.string().describe('Batch ID to discard'),
|
|
563
563
|
account: accountParam,
|
|
564
|
-
},
|
|
564
|
+
}),
|
|
565
565
|
}, async ({ batchId, account }) => {
|
|
566
566
|
return runOrDiagnose(['batch', 'abort', batchId], { account });
|
|
567
567
|
});
|
|
@@ -569,9 +569,9 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
569
569
|
server.registerTool('gog_batch_list', {
|
|
570
570
|
description: 'List persisted Docs request batches (id, doc, label, request count, status).',
|
|
571
571
|
annotations: { readOnlyHint: true },
|
|
572
|
-
inputSchema: {
|
|
572
|
+
inputSchema: z.object({
|
|
573
573
|
account: accountParam,
|
|
574
|
-
},
|
|
574
|
+
}),
|
|
575
575
|
}, async ({ account }) => {
|
|
576
576
|
return runOrDiagnose(['batch', 'list'], { account });
|
|
577
577
|
});
|
|
@@ -579,10 +579,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
579
579
|
server.registerTool('gog_batch_show', {
|
|
580
580
|
description: 'Show one persisted batch: its doc, locked revision, and the composed requests awaiting submission.',
|
|
581
581
|
annotations: { readOnlyHint: true },
|
|
582
|
-
inputSchema: {
|
|
582
|
+
inputSchema: z.object({
|
|
583
583
|
batchId: z.string().describe('Batch ID'),
|
|
584
584
|
account: accountParam,
|
|
585
|
-
},
|
|
585
|
+
}),
|
|
586
586
|
}, async ({ batchId, account }) => {
|
|
587
587
|
return runOrDiagnose(['batch', 'show', batchId], { account });
|
|
588
588
|
});
|
|
@@ -590,10 +590,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
590
590
|
server.registerTool('gog_batch_prune', {
|
|
591
591
|
description: 'Delete stale persisted batches (not updated within olderThan).',
|
|
592
592
|
annotations: { destructiveHint: true },
|
|
593
|
-
inputSchema: {
|
|
593
|
+
inputSchema: z.object({
|
|
594
594
|
olderThan: z.string().optional().describe('Delete batches not updated within this duration (e.g. 72h, 7d)'),
|
|
595
595
|
account: accountParam,
|
|
596
|
-
},
|
|
596
|
+
}),
|
|
597
597
|
}, async ({ olderThan, account }) => {
|
|
598
598
|
const args = ['batch', 'prune'];
|
|
599
599
|
if (olderThan) args.push(`--older-than=${olderThan}`);
|
|
@@ -603,7 +603,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
603
603
|
server.registerTool('gog_docs_table_column_width', {
|
|
604
604
|
description: 'Set a fixed width (in points) for a table column, or reset columns to Docs-managed even distribution. Target the table by 1-based index in document order (negative counts from the end) and the column by 1-based number. Pass evenlyDistributed without col to reset every column in the table.',
|
|
605
605
|
annotations: { destructiveHint: true },
|
|
606
|
-
inputSchema: {
|
|
606
|
+
inputSchema: z.object({
|
|
607
607
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
608
608
|
col: z.number().int().optional().describe('1-based column number. Omit with evenlyDistributed to reset all columns.'),
|
|
609
609
|
width: z.number().optional().describe('Fixed column width in points (minimum 5pt). Mutually exclusive with evenlyDistributed.'),
|
|
@@ -612,7 +612,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
612
612
|
tab: z.string().optional().describe('Target tab title or ID'),
|
|
613
613
|
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.'),
|
|
614
614
|
account: accountParam,
|
|
615
|
-
},
|
|
615
|
+
}),
|
|
616
616
|
}, async ({ docId, col, width, evenlyDistributed, tableIndex, tab, batch, account }) => {
|
|
617
617
|
const args = ['docs', 'table-column-width', docId];
|
|
618
618
|
if (col !== undefined) args.push(`--col=${col}`);
|
|
@@ -630,14 +630,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
630
630
|
|
|
631
631
|
server.registerTool('gog_docs_table_row_insert', {
|
|
632
632
|
description: 'Insert a row into a native Google Docs table, optionally populated from a JSON string array. Inserts before the 1-based position given by `at` (negative counts from the end; "end" appends — the default).',
|
|
633
|
-
inputSchema: {
|
|
633
|
+
inputSchema: z.object({
|
|
634
634
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
635
635
|
table: tableSelectorParam,
|
|
636
636
|
at: z.string().optional().describe('Insert before this 1-based row, a negative index from the end, or "end" to append (default: end)'),
|
|
637
637
|
valuesJson: z.string().optional().describe('JSON string array with the new row\'s cell values, e.g. ["Name","Qty"]'),
|
|
638
638
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
639
639
|
account: accountParam,
|
|
640
|
-
},
|
|
640
|
+
}),
|
|
641
641
|
}, async ({ docId, table, at, valuesJson, tab, account }) => {
|
|
642
642
|
const args = ['docs', 'table-row', 'insert', docId];
|
|
643
643
|
if (table) args.push(`--table=${table}`);
|
|
@@ -650,13 +650,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
650
650
|
server.registerTool('gog_docs_table_row_delete', {
|
|
651
651
|
description: 'Delete a row from a native Google Docs table by 1-based row number (negative counts from the end). The row\'s cell content is lost.',
|
|
652
652
|
annotations: { destructiveHint: true },
|
|
653
|
-
inputSchema: {
|
|
653
|
+
inputSchema: z.object({
|
|
654
654
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
655
655
|
row: z.number().int().describe('1-based row number; negative indexes count from the end'),
|
|
656
656
|
table: tableSelectorParam,
|
|
657
657
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
658
658
|
account: accountParam,
|
|
659
|
-
},
|
|
659
|
+
}),
|
|
660
660
|
}, async ({ docId, row, table, tab, account }) => {
|
|
661
661
|
const args = ['docs', 'table-row', 'delete', docId, `--row=${row}`];
|
|
662
662
|
if (table) args.push(`--table=${table}`);
|
|
@@ -666,13 +666,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
666
666
|
|
|
667
667
|
server.registerTool('gog_docs_table_column_insert', {
|
|
668
668
|
description: 'Insert a column into a native Google Docs table. Inserts before the 1-based position given by `at` (negative counts from the end; "end" appends — the default).',
|
|
669
|
-
inputSchema: {
|
|
669
|
+
inputSchema: z.object({
|
|
670
670
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
671
671
|
table: tableSelectorParam,
|
|
672
672
|
at: z.string().optional().describe('Insert before this 1-based column, a negative index from the end, or "end" to append (default: end)'),
|
|
673
673
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
674
674
|
account: accountParam,
|
|
675
|
-
},
|
|
675
|
+
}),
|
|
676
676
|
}, async ({ docId, table, at, tab, account }) => {
|
|
677
677
|
const args = ['docs', 'table-column', 'insert', docId];
|
|
678
678
|
if (at) args.push(`--at=${at}`);
|
|
@@ -684,13 +684,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
684
684
|
server.registerTool('gog_docs_table_column_delete', {
|
|
685
685
|
description: 'Delete a column from a native Google Docs table by 1-based column number (negative counts from the end). The column\'s cell content is lost.',
|
|
686
686
|
annotations: { destructiveHint: true },
|
|
687
|
-
inputSchema: {
|
|
687
|
+
inputSchema: z.object({
|
|
688
688
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
689
689
|
col: z.number().int().describe('1-based column number; negative indexes count from the end'),
|
|
690
690
|
table: tableSelectorParam,
|
|
691
691
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
692
692
|
account: accountParam,
|
|
693
|
-
},
|
|
693
|
+
}),
|
|
694
694
|
}, async ({ docId, col, table, tab, account }) => {
|
|
695
695
|
const args = ['docs', 'table-column', 'delete', docId, `--col=${col}`];
|
|
696
696
|
if (table) args.push(`--table=${table}`);
|
|
@@ -701,13 +701,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
701
701
|
server.registerTool('gog_docs_table_merge', {
|
|
702
702
|
description: 'Merge a rectangular cell range in a native Google Docs table. Content of non-first cells in the range is absorbed/discarded by the merge — use gog_docs_table_unmerge to split back.',
|
|
703
703
|
annotations: { destructiveHint: true },
|
|
704
|
-
inputSchema: {
|
|
704
|
+
inputSchema: z.object({
|
|
705
705
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
706
706
|
range: z.string().describe('1-based cell range r1,c1:r2,c2 (e.g. "1,1:2,3")'),
|
|
707
707
|
table: tableSelectorParam,
|
|
708
708
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
709
709
|
account: accountParam,
|
|
710
|
-
},
|
|
710
|
+
}),
|
|
711
711
|
}, async ({ docId, range, table, tab, account }) => {
|
|
712
712
|
const args = ['docs', 'table-merge', docId, `--range=${range}`];
|
|
713
713
|
if (table) args.push(`--table=${table}`);
|
|
@@ -717,13 +717,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
717
717
|
|
|
718
718
|
server.registerTool('gog_docs_table_unmerge', {
|
|
719
719
|
description: 'Unmerge (split) the merged region containing a given cell in a native Google Docs table.',
|
|
720
|
-
inputSchema: {
|
|
720
|
+
inputSchema: z.object({
|
|
721
721
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
722
722
|
cell: z.string().describe('1-based cell r,c inside the merged region (e.g. "1,1")'),
|
|
723
723
|
table: tableSelectorParam,
|
|
724
724
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
725
725
|
account: accountParam,
|
|
726
|
-
},
|
|
726
|
+
}),
|
|
727
727
|
}, async ({ docId, cell, table, tab, account }) => {
|
|
728
728
|
const args = ['docs', 'table-unmerge', docId, `--cell=${cell}`];
|
|
729
729
|
if (table) args.push(`--table=${table}`);
|
|
@@ -733,7 +733,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
733
733
|
|
|
734
734
|
server.registerTool('gog_docs_named_range_create', {
|
|
735
735
|
description: 'Create a named range — a durable, tab-aware anchor over a span of document text that survives subsequent edits (unlike raw indices). Anchor by literal text (`at`) or explicit UTF-16 start/end indices. Pair with gog_docs_named_range_replace for repeatable templated updates.',
|
|
736
|
-
inputSchema: {
|
|
736
|
+
inputSchema: z.object({
|
|
737
737
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
738
738
|
name: z.string().describe('Unique named range name'),
|
|
739
739
|
at: z.string().optional().describe('Create the range around this literal matched text, instead of start/end indices'),
|
|
@@ -743,7 +743,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
743
743
|
end: z.number().int().optional().describe('Range end UTF-16 index (exclusive). Required with start unless `at` is set.'),
|
|
744
744
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
745
745
|
account: accountParam,
|
|
746
|
-
},
|
|
746
|
+
}),
|
|
747
747
|
}, async ({ docId, name, at, occurrence, matchCase, start, end, tab, account }) => {
|
|
748
748
|
const args = ['docs', 'named-range', 'create', docId, `--name=${name}`];
|
|
749
749
|
if (at) args.push(`--at=${at}`);
|
|
@@ -758,12 +758,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
758
758
|
server.registerTool('gog_docs_named_range_list', {
|
|
759
759
|
description: 'List named ranges in a Google Doc, optionally filtered by name.',
|
|
760
760
|
annotations: { readOnlyHint: true },
|
|
761
|
-
inputSchema: {
|
|
761
|
+
inputSchema: z.object({
|
|
762
762
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
763
763
|
name: z.string().optional().describe('Only return ranges with this name'),
|
|
764
764
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
765
765
|
account: accountParam,
|
|
766
|
-
},
|
|
766
|
+
}),
|
|
767
767
|
}, async ({ docId, name, tab, account }) => {
|
|
768
768
|
const args = ['docs', 'named-range', 'list', docId];
|
|
769
769
|
if (name) args.push(`--name=${name}`);
|
|
@@ -774,12 +774,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
774
774
|
server.registerTool('gog_docs_named_range_delete', {
|
|
775
775
|
description: 'Delete a named range (the anchor only — the underlying document text is untouched).',
|
|
776
776
|
annotations: { destructiveHint: true },
|
|
777
|
-
inputSchema: {
|
|
777
|
+
inputSchema: z.object({
|
|
778
778
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
779
779
|
nameOrId: z.string().describe('Named range name or ID'),
|
|
780
780
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
781
781
|
account: accountParam,
|
|
782
|
-
},
|
|
782
|
+
}),
|
|
783
783
|
}, async ({ docId, nameOrId, tab, account }) => {
|
|
784
784
|
const args = ['docs', 'named-range', 'delete', docId, nameOrId];
|
|
785
785
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -789,14 +789,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
789
789
|
server.registerTool('gog_docs_named_range_replace', {
|
|
790
790
|
description: 'Replace the text inside a named range with new content (inline or from a file), keeping the anchor for future updates — the templated-update workhorse.',
|
|
791
791
|
annotations: { destructiveHint: true },
|
|
792
|
-
inputSchema: {
|
|
792
|
+
inputSchema: z.object({
|
|
793
793
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
794
794
|
nameOrId: z.string().describe('Named range name or ID'),
|
|
795
795
|
text: z.string().optional().describe('Replacement text'),
|
|
796
796
|
file: z.string().optional().describe('Path to a file with the replacement text'),
|
|
797
797
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
798
798
|
account: accountParam,
|
|
799
|
-
},
|
|
799
|
+
}),
|
|
800
800
|
}, async ({ docId, nameOrId, text, file, tab, account }) => {
|
|
801
801
|
const args = ['docs', 'named-range', 'replace', docId, nameOrId];
|
|
802
802
|
if (text !== undefined) args.push(`--text=${text}`);
|
|
@@ -808,11 +808,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
808
808
|
server.registerTool('gog_docs_tables_list', {
|
|
809
809
|
description: 'Enumerate the native tables in a Google Doc (dimensions, position) — the index/first-cell-text it reports feeds the `table` selector on the table-row/column/merge tools.',
|
|
810
810
|
annotations: { readOnlyHint: true },
|
|
811
|
-
inputSchema: {
|
|
811
|
+
inputSchema: z.object({
|
|
812
812
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
813
813
|
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
814
814
|
account: accountParam,
|
|
815
|
-
},
|
|
815
|
+
}),
|
|
816
816
|
}, async ({ docId, tab, account }) => {
|
|
817
817
|
const args = ['docs', 'tables', 'list', docId];
|
|
818
818
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -822,11 +822,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
822
822
|
server.registerTool('gog_docs_images_list', {
|
|
823
823
|
description: 'Enumerate inline/positioned images in a Google Doc.',
|
|
824
824
|
annotations: { readOnlyHint: true },
|
|
825
|
-
inputSchema: {
|
|
825
|
+
inputSchema: z.object({
|
|
826
826
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
827
827
|
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
828
828
|
account: accountParam,
|
|
829
|
-
},
|
|
829
|
+
}),
|
|
830
830
|
}, async ({ docId, tab, account }) => {
|
|
831
831
|
const args = ['docs', 'images', 'list', docId];
|
|
832
832
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -836,12 +836,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
836
836
|
server.registerTool('gog_docs_headings_list', {
|
|
837
837
|
description: 'Enumerate headings in a Google Doc (a lightweight outline view), optionally filtered to one heading level.',
|
|
838
838
|
annotations: { readOnlyHint: true },
|
|
839
|
-
inputSchema: {
|
|
839
|
+
inputSchema: z.object({
|
|
840
840
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
841
841
|
level: z.number().int().optional().describe('Only return this heading level (1-6)'),
|
|
842
842
|
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
843
843
|
account: accountParam,
|
|
844
|
-
},
|
|
844
|
+
}),
|
|
845
845
|
}, async ({ docId, level, tab, account }) => {
|
|
846
846
|
const args = ['docs', 'headings', 'list', docId];
|
|
847
847
|
if (level !== undefined) args.push(`--level=${level}`);
|
|
@@ -852,12 +852,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
852
852
|
server.registerTool('gog_docs_paragraphs_list', {
|
|
853
853
|
description: 'Enumerate paragraphs in a Google Doc with emptiness, text-run ranges, styles, and links — richer than gog_docs_structure when you need per-run detail. Optionally filter to one named style.',
|
|
854
854
|
annotations: { readOnlyHint: true },
|
|
855
|
-
inputSchema: {
|
|
855
|
+
inputSchema: z.object({
|
|
856
856
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
857
857
|
style: z.string().optional().describe('Only return this named style (e.g. NORMAL_TEXT or HEADING_2)'),
|
|
858
858
|
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
859
859
|
account: accountParam,
|
|
860
|
-
},
|
|
860
|
+
}),
|
|
861
861
|
}, async ({ docId, style, tab, account }) => {
|
|
862
862
|
const args = ['docs', 'paragraphs', 'list', docId];
|
|
863
863
|
if (style) args.push(`--style=${style}`);
|
|
@@ -868,7 +868,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
868
868
|
server.registerTool('gog_docs_insert_page_break', {
|
|
869
869
|
description: 'Insert a Google Docs page break via InsertPageBreakRequest — the only path for multi-page deliverables (markdown has no page-break construct). Specify `index` for a precise character position, or `atEnd` for end-of-doc.',
|
|
870
870
|
annotations: { destructiveHint: true },
|
|
871
|
-
inputSchema: {
|
|
871
|
+
inputSchema: z.object({
|
|
872
872
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
873
873
|
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
874
874
|
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
@@ -878,7 +878,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
878
878
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
879
879
|
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.'),
|
|
880
880
|
account: accountParam,
|
|
881
|
-
},
|
|
881
|
+
}),
|
|
882
882
|
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
883
883
|
const args = ['docs', 'insert-page-break', docId];
|
|
884
884
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -894,7 +894,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
894
894
|
server.registerTool('gog_docs_page_layout', {
|
|
895
895
|
description: 'Toggle the page layout (pageless | pages) of an existing Google Doc. Sibling to the --pageless flag on docs create/write/update for docs that were already created (e.g. by Drive markdown conversion) without the desired layout.',
|
|
896
896
|
annotations: { destructiveHint: true },
|
|
897
|
-
inputSchema: {
|
|
897
|
+
inputSchema: z.object({
|
|
898
898
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
899
899
|
layout: z.enum(['pageless', 'pages']).optional().describe('Page layout (default: pageless)'),
|
|
900
900
|
pageSize: z.enum(['A3', 'A4', 'A5', 'Letter', 'Legal', 'Tabloid']).optional().describe('Named page size preset (only applies in paged layout)'),
|
|
@@ -905,7 +905,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
905
905
|
marginLeft: z.string().optional().describe('Left page margin (points by default; supports pt, in, cm, mm)'),
|
|
906
906
|
marginRight: z.string().optional().describe('Right page margin (points by default; supports pt, in, cm, mm)'),
|
|
907
907
|
account: accountParam,
|
|
908
|
-
},
|
|
908
|
+
}),
|
|
909
909
|
}, async ({ docId, layout, pageSize, pageWidth, pageHeight, marginTop, marginBottom, marginLeft, marginRight, account }) => {
|
|
910
910
|
const args = ['docs', 'page-layout', docId];
|
|
911
911
|
if (layout) args.push(`--layout=${layout}`);
|
|
@@ -922,7 +922,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
922
922
|
server.registerTool('gog_docs_insert_table', {
|
|
923
923
|
description: 'Insert a native Google Docs table via InsertTableRequest, bypassing the markdown writer. Use this instead of writing a markdown table when you need precise dimensions or to avoid the markdown writer\'s table limitations. `valuesJson` is a JSON 2D string array whose dimensions must match rows x cols.',
|
|
924
924
|
annotations: { destructiveHint: true },
|
|
925
|
-
inputSchema: {
|
|
925
|
+
inputSchema: z.object({
|
|
926
926
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
927
927
|
rows: z.number().int().min(1).describe('Number of rows (>=1)'),
|
|
928
928
|
cols: z.number().int().min(1).describe('Number of columns (>=1)'),
|
|
@@ -931,7 +931,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
931
931
|
valuesJson: z.string().optional().describe('Cell values as a JSON 2D string array; dimensions must match rows x cols when supplied'),
|
|
932
932
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
933
933
|
account: accountParam,
|
|
934
|
-
},
|
|
934
|
+
}),
|
|
935
935
|
}, async ({ docId, rows, cols, index, atEnd, valuesJson, tab, account }) => {
|
|
936
936
|
const args = ['docs', 'insert-table', docId, `--rows=${rows}`, `--cols=${cols}`];
|
|
937
937
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -944,7 +944,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
944
944
|
server.registerTool('gog_docs_cell_update', {
|
|
945
945
|
description: 'Replace (or append to) the content of a single table cell, addressed by table / row / column — non-destructive to the rest of the table, unlike index-based edits that shift on every change. Provide content inline or read it from contentFile. Note: row/col/tableIndex are 1-based here; the sibling gog_docs_cell_style uses 0-based addressing.',
|
|
946
946
|
annotations: { destructiveHint: true },
|
|
947
|
-
inputSchema: {
|
|
947
|
+
inputSchema: z.object({
|
|
948
948
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
949
949
|
row: z.number().int().describe('1-based row number'),
|
|
950
950
|
col: z.number().int().describe('1-based column number'),
|
|
@@ -955,7 +955,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
955
955
|
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
956
956
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
957
957
|
account: accountParam,
|
|
958
|
-
},
|
|
958
|
+
}),
|
|
959
959
|
}, async ({ docId, row, col, content, contentFile, append, format, tableIndex, tab, account }) => {
|
|
960
960
|
// gog HARD-ERRORS on both forms ("cannot use both --content and
|
|
961
961
|
// --content-file"), so reject here with a clearer message before gog runs.
|
|
@@ -983,7 +983,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
983
983
|
server.registerTool('gog_docs_cell_style', {
|
|
984
984
|
description: 'Apply background color and/or inline text styling (bold, italic, underline, colors) to one or more table cells, addressed by 0-based row/column with optional spans. Sibling to gog_docs_cell_update, which changes cell content rather than styling. Note: row/col/tableIndex are 0-based here; gog_docs_cell_update uses 1-based addressing.',
|
|
985
985
|
annotations: { destructiveHint: true },
|
|
986
|
-
inputSchema: {
|
|
986
|
+
inputSchema: z.object({
|
|
987
987
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
988
988
|
row: z.number().int().describe('0-based row number'),
|
|
989
989
|
col: z.number().int().describe('0-based column number'),
|
|
@@ -1009,7 +1009,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1009
1009
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1010
1010
|
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.'),
|
|
1011
1011
|
account: accountParam,
|
|
1012
|
-
},
|
|
1012
|
+
}),
|
|
1013
1013
|
}, 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 }) => {
|
|
1014
1014
|
const args = ['docs', 'cell-style', docId, `--row=${row}`, `--col=${col}`];
|
|
1015
1015
|
if (rowSpan !== undefined) args.push(`--row-span=${rowSpan}`);
|
|
@@ -1039,7 +1039,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1039
1039
|
server.registerTool('gog_docs_insert_image', {
|
|
1040
1040
|
description: 'Insert an image into a Google Doc from a local file or a public HTTPS URL. file: uploaded to Drive, temporarily shared so Docs can fetch it, inserted, then the public permission is revoked. url: inserted directly with no Drive upload or temporary sharing. Replaces placeholder text (at) or appends at end-of-doc.',
|
|
1041
1041
|
annotations: { destructiveHint: true },
|
|
1042
|
-
inputSchema: {
|
|
1042
|
+
inputSchema: z.object({
|
|
1043
1043
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1044
1044
|
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)'),
|
|
1045
1045
|
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)'),
|
|
@@ -1053,7 +1053,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1053
1053
|
onRestricted: z.enum(['error', 'link']).optional().describe('If public sharing is blocked: error out, or fall back to a linked image (default: error)'),
|
|
1054
1054
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1055
1055
|
account: accountParam,
|
|
1056
|
-
},
|
|
1056
|
+
}),
|
|
1057
1057
|
}, async ({ docId, file, url, at, before, after, width, height, name, parent, onRestricted, tab, account }) => {
|
|
1058
1058
|
const args = ['docs', 'insert-image', docId];
|
|
1059
1059
|
if (file) args.push(`--file=${file}`);
|
|
@@ -1074,7 +1074,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1074
1074
|
server.registerTool('gog_docs_insert_person', {
|
|
1075
1075
|
description: 'Insert a native Google Docs person smart chip (the interactive @-mention chip) for an email address, at a character index or end-of-doc.',
|
|
1076
1076
|
annotations: { destructiveHint: true },
|
|
1077
|
-
inputSchema: {
|
|
1077
|
+
inputSchema: z.object({
|
|
1078
1078
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1079
1079
|
email: z.string().describe('Email address for the person chip'),
|
|
1080
1080
|
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
@@ -1085,7 +1085,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1085
1085
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1086
1086
|
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.'),
|
|
1087
1087
|
account: accountParam,
|
|
1088
|
-
},
|
|
1088
|
+
}),
|
|
1089
1089
|
}, async ({ docId, email, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1090
1090
|
const args = ['docs', 'insert-person', docId, `--email=${email}`];
|
|
1091
1091
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -1101,7 +1101,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1101
1101
|
server.registerTool('gog_docs_insert_date_chip', {
|
|
1102
1102
|
description: 'Insert a native Google Docs date smart chip, at a character index or end-of-doc.',
|
|
1103
1103
|
annotations: { destructiveHint: true },
|
|
1104
|
-
inputSchema: {
|
|
1104
|
+
inputSchema: z.object({
|
|
1105
1105
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1106
1106
|
date: z.string().optional().describe('Date to insert as YYYY-MM-DD (default: today)'),
|
|
1107
1107
|
format: z.enum(['abbreviated', 'full', 'iso']).optional().describe('Date display format (default: abbreviated)'),
|
|
@@ -1110,7 +1110,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1110
1110
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1111
1111
|
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.'),
|
|
1112
1112
|
account: accountParam,
|
|
1113
|
-
},
|
|
1113
|
+
}),
|
|
1114
1114
|
}, async ({ docId, date, format, index, atEnd, tab, batch, account }) => {
|
|
1115
1115
|
const args = ['docs', 'insert-date-chip', docId];
|
|
1116
1116
|
if (date) args.push(`--date=${date}`);
|
|
@@ -1124,14 +1124,14 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1124
1124
|
|
|
1125
1125
|
server.registerTool('gog_docs_add_tab', {
|
|
1126
1126
|
description: 'Add a tab to a Google Doc. Tabs partition a doc into independently-addressable sections (multi-tab docs). Optionally set the title, zero-based position, parent tab (for nesting), and an emoji icon.',
|
|
1127
|
-
inputSchema: {
|
|
1127
|
+
inputSchema: z.object({
|
|
1128
1128
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1129
1129
|
title: z.string().optional().describe('User-visible tab title'),
|
|
1130
1130
|
index: z.number().int().optional().describe('Zero-based tab position within the parent'),
|
|
1131
1131
|
parentTab: z.string().optional().describe('Parent tab title or ID to nest this tab under'),
|
|
1132
1132
|
iconEmoji: z.string().optional().describe('Emoji icon for the tab'),
|
|
1133
1133
|
account: accountParam,
|
|
1134
|
-
},
|
|
1134
|
+
}),
|
|
1135
1135
|
}, async ({ docId, title, index, parentTab, iconEmoji, account }) => {
|
|
1136
1136
|
const args = ['docs', 'add-tab', docId];
|
|
1137
1137
|
if (title) args.push(`--title=${title}`);
|
|
@@ -1143,12 +1143,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1143
1143
|
|
|
1144
1144
|
server.registerTool('gog_docs_rename_tab', {
|
|
1145
1145
|
description: 'Rename a tab in a Google Doc. Identify the existing tab by title or ID and give it a new title.',
|
|
1146
|
-
inputSchema: {
|
|
1146
|
+
inputSchema: z.object({
|
|
1147
1147
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1148
1148
|
tab: z.string().describe('Existing tab title or ID to rename'),
|
|
1149
1149
|
title: z.string().describe('New user-visible tab title'),
|
|
1150
1150
|
account: accountParam,
|
|
1151
|
-
},
|
|
1151
|
+
}),
|
|
1152
1152
|
}, async ({ docId, tab, title, account }) => {
|
|
1153
1153
|
const args = ['docs', 'rename-tab', docId, `--tab=${tab}`, `--title=${title}`];
|
|
1154
1154
|
return runOrDiagnose(args, { account });
|
|
@@ -1157,11 +1157,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1157
1157
|
server.registerTool('gog_docs_delete_tab', {
|
|
1158
1158
|
description: 'Delete a tab (and its content) from a Google Doc. Identify the tab by title or ID. This permanently removes the tab and everything in it.',
|
|
1159
1159
|
annotations: { destructiveHint: true },
|
|
1160
|
-
inputSchema: {
|
|
1160
|
+
inputSchema: z.object({
|
|
1161
1161
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1162
1162
|
tab: z.string().describe('Existing tab title or ID to delete'),
|
|
1163
1163
|
account: accountParam,
|
|
1164
|
-
},
|
|
1164
|
+
}),
|
|
1165
1165
|
}, async ({ docId, tab, account }) => {
|
|
1166
1166
|
const args = ['docs', 'delete-tab', docId, `--tab=${tab}`, '--force']; // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
1167
1167
|
return runOrDiagnose(args, { account });
|
|
@@ -1170,10 +1170,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1170
1170
|
server.registerTool('gog_docs_clear', {
|
|
1171
1171
|
description: 'Clear all content from a Google Doc, leaving an empty document. The doc itself is preserved (same ID/URL); only its body content is removed. To delete the whole doc instead, use gog_docs_trash.',
|
|
1172
1172
|
annotations: { destructiveHint: true },
|
|
1173
|
-
inputSchema: {
|
|
1173
|
+
inputSchema: z.object({
|
|
1174
1174
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1175
1175
|
account: accountParam,
|
|
1176
|
-
},
|
|
1176
|
+
}),
|
|
1177
1177
|
}, async ({ docId, account }) => {
|
|
1178
1178
|
return runOrDiagnose(['docs', 'clear', docId], { account });
|
|
1179
1179
|
});
|
|
@@ -1183,7 +1183,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1183
1183
|
server.registerTool('gog_docs_insert_footnote', {
|
|
1184
1184
|
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.',
|
|
1185
1185
|
annotations: { destructiveHint: true },
|
|
1186
|
-
inputSchema: {
|
|
1186
|
+
inputSchema: z.object({
|
|
1187
1187
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1188
1188
|
text: z.string().optional().describe('Footnote text'),
|
|
1189
1189
|
file: z.string().optional().describe('Read footnote text from a file on the gog server. gog also accepts "-" for stdin, but this server never writes to gog\'s stdin, so "-" would hang until the call times out.'),
|
|
@@ -1194,7 +1194,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1194
1194
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1195
1195
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1196
1196
|
account: accountParam,
|
|
1197
|
-
},
|
|
1197
|
+
}),
|
|
1198
1198
|
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1199
1199
|
const args = ['docs', 'insert-footnote', docId];
|
|
1200
1200
|
if (text !== undefined) args.push(`--text=${text}`);
|
|
@@ -1211,7 +1211,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1211
1211
|
server.registerTool('gog_docs_insert_section_break', {
|
|
1212
1212
|
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).',
|
|
1213
1213
|
annotations: { destructiveHint: true },
|
|
1214
|
-
inputSchema: {
|
|
1214
|
+
inputSchema: z.object({
|
|
1215
1215
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1216
1216
|
type: z.enum(['next-page', 'continuous']).optional().describe('Section break type (default: next-page)'),
|
|
1217
1217
|
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
@@ -1222,7 +1222,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1222
1222
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1223
1223
|
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.'),
|
|
1224
1224
|
account: accountParam,
|
|
1225
|
-
},
|
|
1225
|
+
}),
|
|
1226
1226
|
}, async ({ docId, type, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1227
1227
|
const args = ['docs', 'insert-section-break', docId];
|
|
1228
1228
|
if (type) args.push(`--type=${type}`);
|
|
@@ -1239,7 +1239,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1239
1239
|
server.registerTool('gog_docs_insert_horizontal_rule', {
|
|
1240
1240
|
description: 'Insert a horizontal rule (paragraph border) at a character index (or end-of-doc).',
|
|
1241
1241
|
annotations: { destructiveHint: true },
|
|
1242
|
-
inputSchema: {
|
|
1242
|
+
inputSchema: z.object({
|
|
1243
1243
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1244
1244
|
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
1245
1245
|
atEnd: z.boolean().optional().describe('Target end-of-doc/tab (mutually exclusive with index and at)'),
|
|
@@ -1249,7 +1249,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1249
1249
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1250
1250
|
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.'),
|
|
1251
1251
|
account: accountParam,
|
|
1252
|
-
},
|
|
1252
|
+
}),
|
|
1253
1253
|
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1254
1254
|
const args = ['docs', 'insert-horizontal-rule', docId];
|
|
1255
1255
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
@@ -1265,7 +1265,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1265
1265
|
server.registerTool('gog_docs_section_columns', {
|
|
1266
1266
|
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.',
|
|
1267
1267
|
annotations: { destructiveHint: true },
|
|
1268
|
-
inputSchema: {
|
|
1268
|
+
inputSchema: z.object({
|
|
1269
1269
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1270
1270
|
count: z.number().int().min(1).max(3).describe('Number of columns (1-3; 1 resets to one column)'),
|
|
1271
1271
|
separator: z.enum(['none', 'between']).optional().describe('Column separator line: none or between'),
|
|
@@ -1277,7 +1277,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1277
1277
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1278
1278
|
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.'),
|
|
1279
1279
|
account: accountParam,
|
|
1280
|
-
},
|
|
1280
|
+
}),
|
|
1281
1281
|
}, async ({ docId, count, separator, index, atEnd, at, occurrence, matchCase, tab, batch, account }) => {
|
|
1282
1282
|
const args = ['docs', 'section-columns', docId, `--count=${count}`];
|
|
1283
1283
|
if (separator) args.push(`--separator=${separator}`);
|
|
@@ -1297,11 +1297,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1297
1297
|
server.registerTool('gog_docs_header_list', {
|
|
1298
1298
|
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.',
|
|
1299
1299
|
annotations: { readOnlyHint: true },
|
|
1300
|
-
inputSchema: {
|
|
1300
|
+
inputSchema: z.object({
|
|
1301
1301
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1302
1302
|
tab: z.string().optional().describe('Limit results to a tab title or ID'),
|
|
1303
1303
|
account: accountParam,
|
|
1304
|
-
},
|
|
1304
|
+
}),
|
|
1305
1305
|
}, async ({ docId, tab, account }) => {
|
|
1306
1306
|
const args = ['docs', 'header', 'list', docId];
|
|
1307
1307
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -1310,7 +1310,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1310
1310
|
|
|
1311
1311
|
server.registerTool('gog_docs_header_create', {
|
|
1312
1312
|
description: 'Create a header in a Google Doc and optionally populate its initial text. Returns the new header segment ID.',
|
|
1313
|
-
inputSchema: {
|
|
1313
|
+
inputSchema: z.object({
|
|
1314
1314
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1315
1315
|
text: z.string().optional().describe('Initial header text'),
|
|
1316
1316
|
file: z.string().optional().describe('Read initial header text from a file on the gog server. gog also accepts "-" for stdin, but this server never writes to gog\'s stdin, so "-" would hang until the call times out.'),
|
|
@@ -1321,7 +1321,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1321
1321
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1322
1322
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1323
1323
|
account: accountParam,
|
|
1324
|
-
},
|
|
1324
|
+
}),
|
|
1325
1325
|
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1326
1326
|
const args = ['docs', 'header', 'create', docId];
|
|
1327
1327
|
if (text !== undefined) args.push(`--text=${text}`);
|
|
@@ -1338,12 +1338,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1338
1338
|
server.registerTool('gog_docs_header_delete', {
|
|
1339
1339
|
description: 'Delete a header from a Google Doc by its segment ID (from gog_docs_header_list).',
|
|
1340
1340
|
annotations: { destructiveHint: true },
|
|
1341
|
-
inputSchema: {
|
|
1341
|
+
inputSchema: z.object({
|
|
1342
1342
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1343
1343
|
headerId: z.string().describe('Header segment ID (from gog_docs_header_list)'),
|
|
1344
1344
|
tab: z.string().optional().describe('Tab title or ID containing the header'),
|
|
1345
1345
|
account: accountParam,
|
|
1346
|
-
},
|
|
1346
|
+
}),
|
|
1347
1347
|
}, async ({ docId, headerId, tab, account }) => {
|
|
1348
1348
|
const args = ['docs', 'header', 'delete', docId, headerId];
|
|
1349
1349
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -1354,11 +1354,11 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1354
1354
|
server.registerTool('gog_docs_footer_list', {
|
|
1355
1355
|
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.',
|
|
1356
1356
|
annotations: { readOnlyHint: true },
|
|
1357
|
-
inputSchema: {
|
|
1357
|
+
inputSchema: z.object({
|
|
1358
1358
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1359
1359
|
tab: z.string().optional().describe('Limit results to a tab title or ID'),
|
|
1360
1360
|
account: accountParam,
|
|
1361
|
-
},
|
|
1361
|
+
}),
|
|
1362
1362
|
}, async ({ docId, tab, account }) => {
|
|
1363
1363
|
const args = ['docs', 'footer', 'list', docId];
|
|
1364
1364
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -1367,7 +1367,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1367
1367
|
|
|
1368
1368
|
server.registerTool('gog_docs_footer_create', {
|
|
1369
1369
|
description: 'Create a footer in a Google Doc and optionally populate its initial text. Returns the new footer segment ID.',
|
|
1370
|
-
inputSchema: {
|
|
1370
|
+
inputSchema: z.object({
|
|
1371
1371
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1372
1372
|
text: z.string().optional().describe('Initial footer text'),
|
|
1373
1373
|
file: z.string().optional().describe('Read initial footer text from a file on the gog server. gog also accepts "-" for stdin, but this server never writes to gog\'s stdin, so "-" would hang until the call times out.'),
|
|
@@ -1378,7 +1378,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1378
1378
|
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
1379
1379
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1380
1380
|
account: accountParam,
|
|
1381
|
-
},
|
|
1381
|
+
}),
|
|
1382
1382
|
}, async ({ docId, text, file, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
1383
1383
|
const args = ['docs', 'footer', 'create', docId];
|
|
1384
1384
|
if (text !== undefined) args.push(`--text=${text}`);
|
|
@@ -1395,12 +1395,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1395
1395
|
server.registerTool('gog_docs_footer_delete', {
|
|
1396
1396
|
description: 'Delete a footer from a Google Doc by its segment ID (from gog_docs_footer_list).',
|
|
1397
1397
|
annotations: { destructiveHint: true },
|
|
1398
|
-
inputSchema: {
|
|
1398
|
+
inputSchema: z.object({
|
|
1399
1399
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1400
1400
|
footerId: z.string().describe('Footer segment ID (from gog_docs_footer_list)'),
|
|
1401
1401
|
tab: z.string().optional().describe('Tab title or ID containing the footer'),
|
|
1402
1402
|
account: accountParam,
|
|
1403
|
-
},
|
|
1403
|
+
}),
|
|
1404
1404
|
}, async ({ docId, footerId, tab, account }) => {
|
|
1405
1405
|
const args = ['docs', 'footer', 'delete', docId, footerId];
|
|
1406
1406
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -1413,7 +1413,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1413
1413
|
server.registerTool('gog_docs_replace_image', {
|
|
1414
1414
|
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.',
|
|
1415
1415
|
annotations: { destructiveHint: true },
|
|
1416
|
-
inputSchema: {
|
|
1416
|
+
inputSchema: z.object({
|
|
1417
1417
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1418
1418
|
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and use (exactly one of file or url)'),
|
|
1419
1419
|
url: z.string().optional().describe('Public HTTPS image URL to use directly — no Drive upload (exactly one of file or url)'),
|
|
@@ -1423,7 +1423,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1423
1423
|
parent: z.string().optional().describe('Drive folder ID for an uploaded local image (file mode)'),
|
|
1424
1424
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1425
1425
|
account: accountParam,
|
|
1426
|
-
},
|
|
1426
|
+
}),
|
|
1427
1427
|
}, async ({ docId, file, url, objectId, matchAlt, name, parent, tab, account }) => {
|
|
1428
1428
|
const args = ['docs', 'replace-image', docId];
|
|
1429
1429
|
if (file) args.push(`--file=${file}`);
|
|
@@ -1440,13 +1440,13 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1440
1440
|
server.registerTool('gog_docs_table_row_pin_header', {
|
|
1441
1441
|
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.',
|
|
1442
1442
|
annotations: { destructiveHint: true },
|
|
1443
|
-
inputSchema: {
|
|
1443
|
+
inputSchema: z.object({
|
|
1444
1444
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1445
1445
|
rows: z.number().int().min(0).describe('Number of leading rows to pin as headers; 0 unpins all header rows'),
|
|
1446
1446
|
table: tableSelectorParam,
|
|
1447
1447
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1448
1448
|
account: accountParam,
|
|
1449
|
-
},
|
|
1449
|
+
}),
|
|
1450
1450
|
}, async ({ docId, rows, table, tab, account }) => {
|
|
1451
1451
|
const args = ['docs', 'table-row', 'pin-header', docId, `--rows=${rows}`];
|
|
1452
1452
|
if (table) args.push(`--table=${table}`);
|
|
@@ -1457,7 +1457,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1457
1457
|
server.registerTool('gog_docs_table_row_style', {
|
|
1458
1458
|
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.',
|
|
1459
1459
|
annotations: { destructiveHint: true },
|
|
1460
|
-
inputSchema: {
|
|
1460
|
+
inputSchema: z.object({
|
|
1461
1461
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
1462
1462
|
row: z.number().int().optional().describe('1-based row number; negative indexes count from the end; omit to style all rows'),
|
|
1463
1463
|
minHeight: z.string().optional().describe('Minimum row height (points by default; supports pt, in, cm, mm)'),
|
|
@@ -1465,7 +1465,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
1465
1465
|
table: tableSelectorParam,
|
|
1466
1466
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
1467
1467
|
account: accountParam,
|
|
1468
|
-
},
|
|
1468
|
+
}),
|
|
1469
1469
|
}, async ({ docId, row, minHeight, preventOverflow, table, tab, account }) => {
|
|
1470
1470
|
const args = ['docs', 'table-row', 'style', docId];
|
|
1471
1471
|
if (row !== undefined) args.push(`--row=${row}`);
|