gogcli-mcp-docs 4.6.3 → 4.7.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.
@@ -32,7 +32,7 @@ export function docTitle(raw: string): string | undefined {
32
32
  export function registerExtraDocsTools(server: McpServer): void {
33
33
  server.registerTool('gog_docs_copy', {
34
34
  description: 'Copy a Google Doc to a new document with the given title.',
35
- annotations: { destructiveHint: true },
35
+ annotations: { destructiveHint: true, openWorldHint: true },
36
36
  inputSchema: z.object({
37
37
  docId: z.string().describe('Doc ID (from the URL)'),
38
38
  title: z.string().describe('Title for the new copy'),
@@ -47,7 +47,7 @@ export function registerExtraDocsTools(server: McpServer): void {
47
47
 
48
48
  server.registerTool('gog_docs_delete', {
49
49
  description: 'Delete content within a Google Doc by character index range. To remove the entire document (move to Drive trash), use gog_docs_trash.',
50
- annotations: { destructiveHint: true },
50
+ annotations: { destructiveHint: true, openWorldHint: true },
51
51
  inputSchema: z.object({
52
52
  docId: z.string().describe('Doc ID (from the URL)'),
53
53
  start: z.number().optional().describe('Start index (character position, 1-based). Required unless `at` is set.'),
@@ -76,7 +76,7 @@ export function registerExtraDocsTools(server: McpServer): void {
76
76
 
77
77
  server.registerTool('gog_docs_trash', {
78
78
  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.',
79
- annotations: { destructiveHint: true },
79
+ annotations: { destructiveHint: true, openWorldHint: true },
80
80
  inputSchema: z.object({
81
81
  docId: z.string().describe('Doc ID to move to trash'),
82
82
  account: accountParam,
@@ -87,7 +87,7 @@ export function registerExtraDocsTools(server: McpServer): void {
87
87
 
88
88
  server.registerTool('gog_docs_edit', {
89
89
  description: 'Edit a Google Doc by finding and replacing text (stream-edit style).',
90
- annotations: { destructiveHint: true },
90
+ annotations: { destructiveHint: true, openWorldHint: true },
91
91
  inputSchema: z.object({
92
92
  docId: z.string().describe('Doc ID (from the URL)'),
93
93
  find: z.string().describe('Text to find'),
@@ -103,7 +103,7 @@ export function registerExtraDocsTools(server: McpServer): void {
103
103
 
104
104
  server.registerTool('gog_docs_read', {
105
105
  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.',
106
- annotations: { readOnlyHint: true },
106
+ annotations: { readOnlyHint: true, openWorldHint: true },
107
107
  inputSchema: z.object({
108
108
  docId: z.string().describe('Doc ID (from the URL)'),
109
109
  format: z.enum(['text', 'json']).optional().describe('Output format (default: text)'),
@@ -131,7 +131,7 @@ export function registerExtraDocsTools(server: McpServer): void {
131
131
 
132
132
  server.registerTool('gog_docs_format', {
133
133
  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.',
134
- annotations: { destructiveHint: true },
134
+ annotations: { destructiveHint: true, openWorldHint: true },
135
135
  inputSchema: z.object({
136
136
  docId: z.string().describe('Doc ID (from the URL)'),
137
137
  match: z.string().optional().describe('Format only the first text match'),
@@ -245,7 +245,7 @@ export function registerExtraDocsTools(server: McpServer): void {
245
245
 
246
246
  server.registerTool('gog_docs_suggestions_list', {
247
247
  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.',
248
- annotations: { readOnlyHint: true },
248
+ annotations: { readOnlyHint: true, openWorldHint: true },
249
249
  inputSchema: z.object({
250
250
  docId: z.string().describe('Doc ID (from the URL)'),
251
251
  tab: z.string().optional().describe('Tab title or ID (omit for the first tab)'),
@@ -260,7 +260,7 @@ export function registerExtraDocsTools(server: McpServer): void {
260
260
  server.registerTool('gog_docs_export', {
261
261
  description: 'Export a Google Doc as PDF, plain text, HTML, DOCX, or other format.',
262
262
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
263
- annotations: { readOnlyHint: false, destructiveHint: true },
263
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
264
264
  inputSchema: z.object({
265
265
  docId: z.string().describe('Doc ID (from the URL)'),
266
266
  format: z.string().optional().describe('Export format: pdf, txt, html, docx, rtf, odt, epub (default: pdf)'),
@@ -279,7 +279,7 @@ export function registerExtraDocsTools(server: McpServer): void {
279
279
 
280
280
  server.registerTool('gog_docs_insert', {
281
281
  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.',
282
- annotations: { destructiveHint: true },
282
+ annotations: { destructiveHint: true, openWorldHint: true },
283
283
  inputSchema: z.object({
284
284
  docId: z.string().describe('Doc ID (from the URL)'),
285
285
  content: z.string().optional().describe('Text content to insert'),
@@ -310,7 +310,7 @@ export function registerExtraDocsTools(server: McpServer): void {
310
310
 
311
311
  server.registerTool('gog_docs_append', {
312
312
  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).',
313
- annotations: { destructiveHint: true },
313
+ annotations: { destructiveHint: true, openWorldHint: true },
314
314
  inputSchema: z.object({
315
315
  docId: z.string().describe('Doc ID (from the URL)'),
316
316
  text: z.string().optional().describe('Text content to append'),
@@ -331,7 +331,7 @@ export function registerExtraDocsTools(server: McpServer): void {
331
331
 
332
332
  server.registerTool('gog_docs_list_tabs', {
333
333
  description: 'List all tabs in a Google Doc.',
334
- annotations: { readOnlyHint: true },
334
+ annotations: { readOnlyHint: true, openWorldHint: true },
335
335
  inputSchema: z.object({
336
336
  docId: z.string().describe('Doc ID (from the URL)'),
337
337
  account: accountParam,
@@ -342,7 +342,7 @@ export function registerExtraDocsTools(server: McpServer): void {
342
342
 
343
343
  server.registerTool('gog_docs_sed', {
344
344
  description: 'Stream-edit a Google Doc with sed-like regex expressions (s/find/replace/ syntax).',
345
- annotations: { destructiveHint: true },
345
+ annotations: { destructiveHint: true, openWorldHint: true },
346
346
  inputSchema: z.object({
347
347
  docId: z.string().describe('Doc ID (from the URL)'),
348
348
  expression: z.string().optional().describe('Single sed expression (e.g. "s/old/new/g")'),
@@ -367,7 +367,7 @@ export function registerExtraDocsTools(server: McpServer): void {
367
367
 
368
368
  server.registerTool('gog_docs_update', {
369
369
  description: 'Update a Google Doc — insert or replace text at a specific position.',
370
- annotations: { destructiveHint: true },
370
+ annotations: { destructiveHint: true, openWorldHint: true },
371
371
  inputSchema: z.object({
372
372
  docId: z.string().describe('Doc ID (from the URL)'),
373
373
  text: z.string().optional().describe('Text content to write'),
@@ -411,7 +411,7 @@ export function registerExtraDocsTools(server: McpServer): void {
411
411
  'Either flag additionally requires the Docs scope; the plain listing needs only Drive. ' +
412
412
  'matches[] always spans every tab, so a quote appearing in two tabs is visible as an ambiguity rather than silently resolved. ' +
413
413
  'tab keeps only comments with a match in that tab, which drops orphaned comments and comments that quote nothing.',
414
- annotations: { readOnlyHint: true },
414
+ annotations: { readOnlyHint: true, openWorldHint: true },
415
415
  inputSchema: z.object({
416
416
  docId: z.string().describe('Doc ID (from the URL)'),
417
417
  includeResolved: z.boolean().optional().describe('Include resolved comments (default: false, open only)'),
@@ -433,7 +433,7 @@ export function registerExtraDocsTools(server: McpServer): void {
433
433
 
434
434
  server.registerTool('gog_docs_comments_get', {
435
435
  description: 'Get a single comment by ID, including its replies.',
436
- annotations: { readOnlyHint: true },
436
+ annotations: { readOnlyHint: true, openWorldHint: true },
437
437
  inputSchema: z.object({
438
438
  docId: z.string().describe('Doc ID (from the URL)'),
439
439
  commentId: z.string().describe('Comment ID'),
@@ -449,7 +449,7 @@ export function registerExtraDocsTools(server: McpServer): void {
449
449
  + 'The Doc\'s owner and everyone the text +mentions are notified, so this reads the Doc and asks the MCP host to show '
450
450
  + 'the user a confirmation prompt with the Doc, the text and who is mentioned first; nothing is posted unless they '
451
451
  + 'accept.' + CONFIRM_FALLBACK_DESCRIPTION,
452
- annotations: { destructiveHint: false },
452
+ annotations: { destructiveHint: false, openWorldHint: true },
453
453
  inputSchema: z.object({
454
454
  docId: z.string().describe('Doc ID (from the URL)'),
455
455
  content: z.string().describe('Comment text'),
@@ -472,6 +472,7 @@ export function registerExtraDocsTools(server: McpServer): void {
472
472
  details: { doc, textPreview: bodyPreview(content), quoted, mentions },
473
473
  unsupportedNote: 'Ask the user to comment from Google Docs.',
474
474
  fallback: {
475
+ args: { docId, content, quoted, account },
475
476
  tool: 'gog_docs_comments_add',
476
477
  account,
477
478
  confirmToken,
@@ -489,7 +490,7 @@ export function registerExtraDocsTools(server: McpServer): void {
489
490
  description: 'Reply to an existing comment on a Google Doc. Everyone on the thread and anyone the reply +mentions is '
490
491
  + 'notified, so this reads the Doc and the comment and asks the MCP host to show the user a confirmation prompt '
491
492
  + 'with both and the reply first; nothing is posted unless they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
492
- annotations: { destructiveHint: false },
493
+ annotations: { destructiveHint: false, openWorldHint: true },
493
494
  inputSchema: z.object({
494
495
  docId: z.string().describe('Doc ID (from the URL)'),
495
496
  commentId: z.string().describe('Comment ID to reply to'),
@@ -513,6 +514,7 @@ export function registerExtraDocsTools(server: McpServer): void {
513
514
  details: { doc, replyingTo, textPreview: bodyPreview(content), mentions },
514
515
  unsupportedNote: 'Ask the user to reply from Google Docs.',
515
516
  fallback: {
517
+ args: { docId, commentId, content, account },
516
518
  tool: 'gog_docs_comments_reply',
517
519
  account,
518
520
  confirmToken,
@@ -528,7 +530,7 @@ export function registerExtraDocsTools(server: McpServer): void {
528
530
 
529
531
  server.registerTool('gog_docs_comments_resolve', {
530
532
  description: 'Resolve a comment (mark as done). Optionally include a closing message.',
531
- annotations: { destructiveHint: true },
533
+ annotations: { destructiveHint: true, openWorldHint: true },
532
534
  inputSchema: z.object({
533
535
  docId: z.string().describe('Doc ID (from the URL)'),
534
536
  commentId: z.string().describe('Comment ID to resolve'),
@@ -543,7 +545,7 @@ export function registerExtraDocsTools(server: McpServer): void {
543
545
 
544
546
  server.registerTool('gog_docs_comments_delete', {
545
547
  description: 'Delete a comment from a Google Doc. This action is permanent.',
546
- annotations: { destructiveHint: true },
548
+ annotations: { destructiveHint: true, openWorldHint: true },
547
549
  inputSchema: z.object({
548
550
  docId: z.string().describe('Doc ID (from the URL)'),
549
551
  commentId: z.string().describe('Comment ID to delete'),
@@ -556,7 +558,7 @@ export function registerExtraDocsTools(server: McpServer): void {
556
558
 
557
559
  server.registerTool('gog_docs_comments_reopen', {
558
560
  description: 'Reopen a previously resolved comment (flip resolved → open).',
559
- annotations: { destructiveHint: true },
561
+ annotations: { destructiveHint: true, openWorldHint: true },
560
562
  inputSchema: z.object({
561
563
  docId: z.string().describe('Doc ID (from the URL)'),
562
564
  commentId: z.string().describe('Comment ID to reopen'),
@@ -568,7 +570,7 @@ export function registerExtraDocsTools(server: McpServer): void {
568
570
 
569
571
  server.registerTool('gog_docs_comments_locate', {
570
572
  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.',
571
- annotations: { readOnlyHint: true },
573
+ annotations: { readOnlyHint: true, openWorldHint: true },
572
574
  inputSchema: z.object({
573
575
  docId: z.string().describe('Doc ID (from the URL)'),
574
576
  commentId: z.string().describe('Comment ID to locate'),
@@ -587,7 +589,7 @@ export function registerExtraDocsTools(server: McpServer): void {
587
589
 
588
590
  server.registerTool('gog_docs_find_range', {
589
591
  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.',
590
- annotations: { readOnlyHint: true },
592
+ annotations: { readOnlyHint: true, openWorldHint: true },
591
593
  inputSchema: z.object({
592
594
  docId: z.string().describe('Doc ID (from the URL)'),
593
595
  text: z.string().describe('Literal text to locate'),
@@ -617,7 +619,7 @@ export function registerExtraDocsTools(server: McpServer): void {
617
619
  // end to submit them atomically against the locked revision.
618
620
  server.registerTool('gog_batch_begin', {
619
621
  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.',
620
- annotations: { destructiveHint: false },
622
+ annotations: { destructiveHint: false, openWorldHint: false },
621
623
  inputSchema: z.object({
622
624
  docId: z.string().describe('Google Doc ID the batch is locked to'),
623
625
  name: z.string().optional().describe('Optional batch label'),
@@ -631,7 +633,7 @@ export function registerExtraDocsTools(server: McpServer): void {
631
633
 
632
634
  server.registerTool('gog_batch_end', {
633
635
  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.',
634
- annotations: { destructiveHint: true },
636
+ annotations: { destructiveHint: true, openWorldHint: true },
635
637
  inputSchema: z.object({
636
638
  batchId: z.string().describe('Batch ID (from gog_batch_begin)'),
637
639
  autoSplit: z.boolean().optional().describe('Submit batches over 500 requests as ordered chunks (non-atomic)'),
@@ -647,7 +649,7 @@ export function registerExtraDocsTools(server: McpServer): void {
647
649
 
648
650
  server.registerTool('gog_batch_abort', {
649
651
  description: 'Discard a persisted batch and its composed requests without applying anything to the doc.',
650
- annotations: { destructiveHint: true },
652
+ annotations: { destructiveHint: true, openWorldHint: false },
651
653
  inputSchema: z.object({
652
654
  batchId: z.string().describe('Batch ID to discard'),
653
655
  account: accountParam,
@@ -658,7 +660,7 @@ export function registerExtraDocsTools(server: McpServer): void {
658
660
 
659
661
  server.registerTool('gog_batch_list', {
660
662
  description: 'List persisted Docs request batches (id, doc, label, request count, status).',
661
- annotations: { readOnlyHint: true },
663
+ annotations: { readOnlyHint: true, openWorldHint: false },
662
664
  inputSchema: z.object({
663
665
  account: accountParam,
664
666
  }),
@@ -668,7 +670,7 @@ export function registerExtraDocsTools(server: McpServer): void {
668
670
 
669
671
  server.registerTool('gog_batch_show', {
670
672
  description: 'Show one persisted batch: its doc, locked revision, and the composed requests awaiting submission.',
671
- annotations: { readOnlyHint: true },
673
+ annotations: { readOnlyHint: true, openWorldHint: false },
672
674
  inputSchema: z.object({
673
675
  batchId: z.string().describe('Batch ID'),
674
676
  account: accountParam,
@@ -679,7 +681,7 @@ export function registerExtraDocsTools(server: McpServer): void {
679
681
 
680
682
  server.registerTool('gog_batch_prune', {
681
683
  description: 'Delete stale persisted batches (not updated within olderThan).',
682
- annotations: { destructiveHint: true },
684
+ annotations: { destructiveHint: true, openWorldHint: false },
683
685
  inputSchema: z.object({
684
686
  olderThan: z.string().optional().describe('Delete batches not updated within this duration (e.g. 72h, 7d)'),
685
687
  account: accountParam,
@@ -692,7 +694,7 @@ export function registerExtraDocsTools(server: McpServer): void {
692
694
 
693
695
  server.registerTool('gog_docs_table_column_width', {
694
696
  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.',
695
- annotations: { destructiveHint: true },
697
+ annotations: { destructiveHint: true, openWorldHint: true },
696
698
  inputSchema: z.object({
697
699
  docId: z.string().describe('Doc ID (from the URL)'),
698
700
  col: z.number().int().optional().describe('1-based column number. Omit with evenlyDistributed to reset all columns.'),
@@ -720,7 +722,7 @@ export function registerExtraDocsTools(server: McpServer): void {
720
722
 
721
723
  server.registerTool('gog_docs_table_row_insert', {
722
724
  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).',
723
- annotations: { destructiveHint: true },
725
+ annotations: { destructiveHint: true, openWorldHint: true },
724
726
  inputSchema: z.object({
725
727
  docId: z.string().describe('Doc ID (from the URL)'),
726
728
  table: tableSelectorParam,
@@ -740,7 +742,7 @@ export function registerExtraDocsTools(server: McpServer): void {
740
742
 
741
743
  server.registerTool('gog_docs_table_row_delete', {
742
744
  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.',
743
- annotations: { destructiveHint: true },
745
+ annotations: { destructiveHint: true, openWorldHint: true },
744
746
  inputSchema: z.object({
745
747
  docId: z.string().describe('Doc ID (from the URL)'),
746
748
  row: z.number().int().describe('1-based row number; negative indexes count from the end'),
@@ -757,7 +759,7 @@ export function registerExtraDocsTools(server: McpServer): void {
757
759
 
758
760
  server.registerTool('gog_docs_table_column_insert', {
759
761
  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).',
760
- annotations: { destructiveHint: true },
762
+ annotations: { destructiveHint: true, openWorldHint: true },
761
763
  inputSchema: z.object({
762
764
  docId: z.string().describe('Doc ID (from the URL)'),
763
765
  table: tableSelectorParam,
@@ -775,7 +777,7 @@ export function registerExtraDocsTools(server: McpServer): void {
775
777
 
776
778
  server.registerTool('gog_docs_table_column_delete', {
777
779
  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.',
778
- annotations: { destructiveHint: true },
780
+ annotations: { destructiveHint: true, openWorldHint: true },
779
781
  inputSchema: z.object({
780
782
  docId: z.string().describe('Doc ID (from the URL)'),
781
783
  col: z.number().int().describe('1-based column number; negative indexes count from the end'),
@@ -792,7 +794,7 @@ export function registerExtraDocsTools(server: McpServer): void {
792
794
 
793
795
  server.registerTool('gog_docs_table_merge', {
794
796
  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.',
795
- annotations: { destructiveHint: true },
797
+ annotations: { destructiveHint: true, openWorldHint: true },
796
798
  inputSchema: z.object({
797
799
  docId: z.string().describe('Doc ID (from the URL)'),
798
800
  range: z.string().describe('1-based cell range r1,c1:r2,c2 (e.g. "1,1:2,3")'),
@@ -809,7 +811,7 @@ export function registerExtraDocsTools(server: McpServer): void {
809
811
 
810
812
  server.registerTool('gog_docs_table_unmerge', {
811
813
  description: 'Unmerge (split) the merged region containing a given cell in a native Google Docs table.',
812
- annotations: { destructiveHint: true },
814
+ annotations: { destructiveHint: true, openWorldHint: true },
813
815
  inputSchema: z.object({
814
816
  docId: z.string().describe('Doc ID (from the URL)'),
815
817
  cell: z.string().describe('1-based cell r,c inside the merged region (e.g. "1,1")'),
@@ -826,7 +828,7 @@ export function registerExtraDocsTools(server: McpServer): void {
826
828
 
827
829
  server.registerTool('gog_docs_named_range_create', {
828
830
  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.',
829
- annotations: { destructiveHint: true },
831
+ annotations: { destructiveHint: true, openWorldHint: true },
830
832
  inputSchema: z.object({
831
833
  docId: z.string().describe('Doc ID (from the URL)'),
832
834
  name: z.string().describe('Unique named range name'),
@@ -851,7 +853,7 @@ export function registerExtraDocsTools(server: McpServer): void {
851
853
 
852
854
  server.registerTool('gog_docs_named_range_list', {
853
855
  description: 'List named ranges in a Google Doc, optionally filtered by name.',
854
- annotations: { readOnlyHint: true },
856
+ annotations: { readOnlyHint: true, openWorldHint: true },
855
857
  inputSchema: z.object({
856
858
  docId: z.string().describe('Doc ID (from the URL)'),
857
859
  name: z.string().optional().describe('Only return ranges with this name'),
@@ -867,7 +869,7 @@ export function registerExtraDocsTools(server: McpServer): void {
867
869
 
868
870
  server.registerTool('gog_docs_named_range_delete', {
869
871
  description: 'Delete a named range (the anchor only — the underlying document text is untouched).',
870
- annotations: { destructiveHint: true },
872
+ annotations: { destructiveHint: true, openWorldHint: true },
871
873
  inputSchema: z.object({
872
874
  docId: z.string().describe('Doc ID (from the URL)'),
873
875
  nameOrId: z.string().describe('Named range name or ID'),
@@ -882,7 +884,7 @@ export function registerExtraDocsTools(server: McpServer): void {
882
884
 
883
885
  server.registerTool('gog_docs_named_range_replace', {
884
886
  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.',
885
- annotations: { destructiveHint: true },
887
+ annotations: { destructiveHint: true, openWorldHint: true },
886
888
  inputSchema: z.object({
887
889
  docId: z.string().describe('Doc ID (from the URL)'),
888
890
  nameOrId: z.string().describe('Named range name or ID'),
@@ -902,7 +904,7 @@ export function registerExtraDocsTools(server: McpServer): void {
902
904
 
903
905
  server.registerTool('gog_docs_tables_list', {
904
906
  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.',
905
- annotations: { readOnlyHint: true },
907
+ annotations: { readOnlyHint: true, openWorldHint: true },
906
908
  inputSchema: z.object({
907
909
  docId: z.string().describe('Doc ID (from the URL)'),
908
910
  tab: z.string().optional().describe('Tab title or ID (omit for default)'),
@@ -916,7 +918,7 @@ export function registerExtraDocsTools(server: McpServer): void {
916
918
 
917
919
  server.registerTool('gog_docs_images_list', {
918
920
  description: 'Enumerate inline/positioned images in a Google Doc.',
919
- annotations: { readOnlyHint: true },
921
+ annotations: { readOnlyHint: true, openWorldHint: true },
920
922
  inputSchema: z.object({
921
923
  docId: z.string().describe('Doc ID (from the URL)'),
922
924
  tab: z.string().optional().describe('Tab title or ID (omit for default)'),
@@ -930,7 +932,7 @@ export function registerExtraDocsTools(server: McpServer): void {
930
932
 
931
933
  server.registerTool('gog_docs_headings_list', {
932
934
  description: 'Enumerate headings in a Google Doc (a lightweight outline view), optionally filtered to one heading level.',
933
- annotations: { readOnlyHint: true },
935
+ annotations: { readOnlyHint: true, openWorldHint: true },
934
936
  inputSchema: z.object({
935
937
  docId: z.string().describe('Doc ID (from the URL)'),
936
938
  level: z.number().int().optional().describe('Only return this heading level (1-6)'),
@@ -946,7 +948,7 @@ export function registerExtraDocsTools(server: McpServer): void {
946
948
 
947
949
  server.registerTool('gog_docs_paragraphs_list', {
948
950
  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.',
949
- annotations: { readOnlyHint: true },
951
+ annotations: { readOnlyHint: true, openWorldHint: true },
950
952
  inputSchema: z.object({
951
953
  docId: z.string().describe('Doc ID (from the URL)'),
952
954
  style: z.string().optional().describe('Only return this named style (e.g. NORMAL_TEXT or HEADING_2)'),
@@ -962,7 +964,7 @@ export function registerExtraDocsTools(server: McpServer): void {
962
964
 
963
965
  server.registerTool('gog_docs_insert_page_break', {
964
966
  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.',
965
- annotations: { destructiveHint: true },
967
+ annotations: { destructiveHint: true, openWorldHint: true },
966
968
  inputSchema: z.object({
967
969
  docId: z.string().describe('Doc ID (from the URL)'),
968
970
  index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
@@ -988,7 +990,7 @@ export function registerExtraDocsTools(server: McpServer): void {
988
990
 
989
991
  server.registerTool('gog_docs_page_layout', {
990
992
  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.',
991
- annotations: { destructiveHint: true },
993
+ annotations: { destructiveHint: true, openWorldHint: true },
992
994
  inputSchema: z.object({
993
995
  docId: z.string().describe('Doc ID (from the URL)'),
994
996
  layout: z.enum(['pageless', 'pages']).optional().describe('Page layout (default: pageless)'),
@@ -1016,7 +1018,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1016
1018
 
1017
1019
  server.registerTool('gog_docs_insert_table', {
1018
1020
  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.',
1019
- annotations: { destructiveHint: true },
1021
+ annotations: { destructiveHint: true, openWorldHint: true },
1020
1022
  inputSchema: z.object({
1021
1023
  docId: z.string().describe('Doc ID (from the URL)'),
1022
1024
  rows: z.number().int().min(1).describe('Number of rows (>=1)'),
@@ -1038,7 +1040,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1038
1040
 
1039
1041
  server.registerTool('gog_docs_cell_update', {
1040
1042
  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.',
1041
- annotations: { destructiveHint: true },
1043
+ annotations: { destructiveHint: true, openWorldHint: true },
1042
1044
  inputSchema: z.object({
1043
1045
  docId: z.string().describe('Doc ID (from the URL)'),
1044
1046
  row: z.number().int().describe('1-based row number'),
@@ -1078,7 +1080,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1078
1080
 
1079
1081
  server.registerTool('gog_docs_cell_style', {
1080
1082
  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.',
1081
- annotations: { destructiveHint: true },
1083
+ annotations: { destructiveHint: true, openWorldHint: true },
1082
1084
  inputSchema: z.object({
1083
1085
  docId: z.string().describe('Doc ID (from the URL)'),
1084
1086
  row: z.number().int().describe('0-based row number'),
@@ -1134,7 +1136,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1134
1136
 
1135
1137
  server.registerTool('gog_docs_insert_image', {
1136
1138
  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.',
1137
- annotations: { destructiveHint: true },
1139
+ annotations: { destructiveHint: true, openWorldHint: true },
1138
1140
  inputSchema: z.object({
1139
1141
  docId: z.string().describe('Doc ID (from the URL)'),
1140
1142
  file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)'),
@@ -1170,7 +1172,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1170
1172
 
1171
1173
  server.registerTool('gog_docs_insert_person', {
1172
1174
  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.',
1173
- annotations: { destructiveHint: true },
1175
+ annotations: { destructiveHint: true, openWorldHint: true },
1174
1176
  inputSchema: z.object({
1175
1177
  docId: z.string().describe('Doc ID (from the URL)'),
1176
1178
  email: z.string().describe('Email address for the person chip'),
@@ -1197,7 +1199,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1197
1199
 
1198
1200
  server.registerTool('gog_docs_insert_date_chip', {
1199
1201
  description: 'Insert a native Google Docs date smart chip, at a character index or end-of-doc.',
1200
- annotations: { destructiveHint: true },
1202
+ annotations: { destructiveHint: true, openWorldHint: true },
1201
1203
  inputSchema: z.object({
1202
1204
  docId: z.string().describe('Doc ID (from the URL)'),
1203
1205
  date: z.string().optional().describe('Date to insert as YYYY-MM-DD (default: today)'),
@@ -1221,7 +1223,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1221
1223
 
1222
1224
  server.registerTool('gog_docs_add_tab', {
1223
1225
  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.',
1224
- annotations: { destructiveHint: false },
1226
+ annotations: { destructiveHint: false, openWorldHint: true },
1225
1227
  inputSchema: z.object({
1226
1228
  docId: z.string().describe('Doc ID (from the URL)'),
1227
1229
  title: z.string().optional().describe('User-visible tab title'),
@@ -1241,7 +1243,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1241
1243
 
1242
1244
  server.registerTool('gog_docs_rename_tab', {
1243
1245
  description: 'Rename a tab in a Google Doc. Identify the existing tab by title or ID and give it a new title.',
1244
- annotations: { destructiveHint: false },
1246
+ annotations: { destructiveHint: false, openWorldHint: true },
1245
1247
  inputSchema: z.object({
1246
1248
  docId: z.string().describe('Doc ID (from the URL)'),
1247
1249
  tab: z.string().describe('Existing tab title or ID to rename'),
@@ -1255,7 +1257,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1255
1257
 
1256
1258
  server.registerTool('gog_docs_delete_tab', {
1257
1259
  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.',
1258
- annotations: { destructiveHint: true },
1260
+ annotations: { destructiveHint: true, openWorldHint: true },
1259
1261
  inputSchema: z.object({
1260
1262
  docId: z.string().describe('Doc ID (from the URL)'),
1261
1263
  tab: z.string().describe('Existing tab title or ID to delete'),
@@ -1268,7 +1270,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1268
1270
 
1269
1271
  server.registerTool('gog_docs_clear', {
1270
1272
  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.',
1271
- annotations: { destructiveHint: true },
1273
+ annotations: { destructiveHint: true, openWorldHint: true },
1272
1274
  inputSchema: z.object({
1273
1275
  docId: z.string().describe('Doc ID (from the URL)'),
1274
1276
  account: accountParam,
@@ -1281,7 +1283,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1281
1283
 
1282
1284
  server.registerTool('gog_docs_insert_footnote', {
1283
1285
  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.',
1284
- annotations: { destructiveHint: true },
1286
+ annotations: { destructiveHint: true, openWorldHint: true },
1285
1287
  inputSchema: z.object({
1286
1288
  docId: z.string().describe('Doc ID (from the URL)'),
1287
1289
  text: z.string().optional().describe('Footnote text'),
@@ -1310,7 +1312,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1310
1312
 
1311
1313
  server.registerTool('gog_docs_insert_section_break', {
1312
1314
  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).',
1313
- annotations: { destructiveHint: true },
1315
+ annotations: { destructiveHint: true, openWorldHint: true },
1314
1316
  inputSchema: z.object({
1315
1317
  docId: z.string().describe('Doc ID (from the URL)'),
1316
1318
  type: z.enum(['next-page', 'continuous']).optional().describe('Section break type (default: next-page)'),
@@ -1338,7 +1340,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1338
1340
 
1339
1341
  server.registerTool('gog_docs_insert_horizontal_rule', {
1340
1342
  description: 'Insert a horizontal rule (paragraph border) at a character index (or end-of-doc).',
1341
- annotations: { destructiveHint: true },
1343
+ annotations: { destructiveHint: true, openWorldHint: true },
1342
1344
  inputSchema: z.object({
1343
1345
  docId: z.string().describe('Doc ID (from the URL)'),
1344
1346
  index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
@@ -1364,7 +1366,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1364
1366
 
1365
1367
  server.registerTool('gog_docs_section_columns', {
1366
1368
  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.',
1367
- annotations: { destructiveHint: true },
1369
+ annotations: { destructiveHint: true, openWorldHint: true },
1368
1370
  inputSchema: z.object({
1369
1371
  docId: z.string().describe('Doc ID (from the URL)'),
1370
1372
  count: z.number().int().min(1).max(3).describe('Number of columns (1-3; 1 resets to one column)'),
@@ -1396,7 +1398,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1396
1398
 
1397
1399
  server.registerTool('gog_docs_header_list', {
1398
1400
  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.',
1399
- annotations: { readOnlyHint: true },
1401
+ annotations: { readOnlyHint: true, openWorldHint: true },
1400
1402
  inputSchema: z.object({
1401
1403
  docId: z.string().describe('Doc ID (from the URL)'),
1402
1404
  tab: z.string().optional().describe('Limit results to a tab title or ID'),
@@ -1410,7 +1412,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1410
1412
 
1411
1413
  server.registerTool('gog_docs_header_create', {
1412
1414
  description: 'Create a header in a Google Doc and optionally populate its initial text. Returns the new header segment ID.',
1413
- annotations: { destructiveHint: false },
1415
+ annotations: { destructiveHint: false, openWorldHint: true },
1414
1416
  inputSchema: z.object({
1415
1417
  docId: z.string().describe('Doc ID (from the URL)'),
1416
1418
  text: z.string().optional().describe('Initial header text'),
@@ -1439,7 +1441,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1439
1441
 
1440
1442
  server.registerTool('gog_docs_header_delete', {
1441
1443
  description: 'Delete a header from a Google Doc by its segment ID (from gog_docs_header_list).',
1442
- annotations: { destructiveHint: true },
1444
+ annotations: { destructiveHint: true, openWorldHint: true },
1443
1445
  inputSchema: z.object({
1444
1446
  docId: z.string().describe('Doc ID (from the URL)'),
1445
1447
  headerId: z.string().describe('Header segment ID (from gog_docs_header_list)'),
@@ -1455,7 +1457,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1455
1457
 
1456
1458
  server.registerTool('gog_docs_footer_list', {
1457
1459
  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.',
1458
- annotations: { readOnlyHint: true },
1460
+ annotations: { readOnlyHint: true, openWorldHint: true },
1459
1461
  inputSchema: z.object({
1460
1462
  docId: z.string().describe('Doc ID (from the URL)'),
1461
1463
  tab: z.string().optional().describe('Limit results to a tab title or ID'),
@@ -1469,7 +1471,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1469
1471
 
1470
1472
  server.registerTool('gog_docs_footer_create', {
1471
1473
  description: 'Create a footer in a Google Doc and optionally populate its initial text. Returns the new footer segment ID.',
1472
- annotations: { destructiveHint: false },
1474
+ annotations: { destructiveHint: false, openWorldHint: true },
1473
1475
  inputSchema: z.object({
1474
1476
  docId: z.string().describe('Doc ID (from the URL)'),
1475
1477
  text: z.string().optional().describe('Initial footer text'),
@@ -1498,7 +1500,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1498
1500
 
1499
1501
  server.registerTool('gog_docs_footer_delete', {
1500
1502
  description: 'Delete a footer from a Google Doc by its segment ID (from gog_docs_footer_list).',
1501
- annotations: { destructiveHint: true },
1503
+ annotations: { destructiveHint: true, openWorldHint: true },
1502
1504
  inputSchema: z.object({
1503
1505
  docId: z.string().describe('Doc ID (from the URL)'),
1504
1506
  footerId: z.string().describe('Footer segment ID (from gog_docs_footer_list)'),
@@ -1516,7 +1518,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1516
1518
 
1517
1519
  server.registerTool('gog_docs_replace_image', {
1518
1520
  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.',
1519
- annotations: { destructiveHint: true },
1521
+ annotations: { destructiveHint: true, openWorldHint: true },
1520
1522
  inputSchema: z.object({
1521
1523
  docId: z.string().describe('Doc ID (from the URL)'),
1522
1524
  file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and use (exactly one of file or url)'),
@@ -1544,7 +1546,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1544
1546
 
1545
1547
  server.registerTool('gog_docs_table_row_pin_header', {
1546
1548
  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.',
1547
- annotations: { destructiveHint: true },
1549
+ annotations: { destructiveHint: true, openWorldHint: true },
1548
1550
  inputSchema: z.object({
1549
1551
  docId: z.string().describe('Doc ID (from the URL)'),
1550
1552
  rows: z.number().int().min(0).describe('Number of leading rows to pin as headers; 0 unpins all header rows'),
@@ -1561,7 +1563,7 @@ export function registerExtraDocsTools(server: McpServer): void {
1561
1563
 
1562
1564
  server.registerTool('gog_docs_table_row_style', {
1563
1565
  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.',
1564
- annotations: { destructiveHint: true },
1566
+ annotations: { destructiveHint: true, openWorldHint: true },
1565
1567
  inputSchema: z.object({
1566
1568
  docId: z.string().describe('Doc ID (from the URL)'),
1567
1569
  row: z.number().int().optional().describe('1-based row number; negative indexes count from the end; omit to style all rows'),
@@ -0,0 +1,10 @@
1
+ import { authToolsFor, registerDocsTools } from '../../gogcli-mcp/src/lib.js';
2
+ import { registerExtraDocsTools } from '../src/tools/docs-extra.js';
3
+ import { describeToolAnnotations, LOCAL_AUTH_TOOLS } from '../../gogcli-mcp/tests/support/tool-annotations.js';
4
+
5
+ // The same registrar list src/index.ts hands to runMcp.
6
+ describeToolAnnotations([authToolsFor('docs'), registerDocsTools, registerExtraDocsTools] as never, {
7
+ tools: 83,
8
+ destructive: 52,
9
+ local: [...LOCAL_AUTH_TOOLS, 'gog_batch_abort', 'gog_batch_begin', 'gog_batch_list', 'gog_batch_prune', 'gog_batch_show'],
10
+ });