gogcli-mcp-docs 2.6.0 → 2.7.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 +241 -9
- package/manifest.json +57 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +254 -7
- package/tests/tools/docs-extra.test.ts +220 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.7.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli (Docs)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
18
|
-
"version": "2.
|
|
18
|
+
"version": "2.7.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31276,11 +31276,13 @@ function registerDocsTools(server2) {
|
|
|
31276
31276
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31277
31277
|
text: external_exports.string().describe("Text content to write"),
|
|
31278
31278
|
append: external_exports.boolean().optional().describe("Append to existing content instead of replacing (default: false)"),
|
|
31279
|
+
checkOrphans: external_exports.boolean().optional().describe("Block the write (exit code 11) if it would orphan an open comment \u2014 i.e. remove the text the comment is anchored to. Recommended for replacement writes on commented docs."),
|
|
31279
31280
|
account: accountParam
|
|
31280
31281
|
}
|
|
31281
|
-
}, async ({ docId, text, append, account }) => {
|
|
31282
|
+
}, async ({ docId, text, append, checkOrphans, account }) => {
|
|
31282
31283
|
const args = ["docs", "write", docId, `--text=${text}`];
|
|
31283
31284
|
if (append) args.push("--append");
|
|
31285
|
+
if (checkOrphans) args.push("--check-orphans");
|
|
31284
31286
|
return runOrDiagnose(args, { account });
|
|
31285
31287
|
});
|
|
31286
31288
|
server2.registerTool("gog_docs_find_replace", {
|
|
@@ -31318,7 +31320,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31318
31320
|
);
|
|
31319
31321
|
|
|
31320
31322
|
// ../gogcli-mcp/src/server.ts
|
|
31321
|
-
var VERSION = true ? "2.
|
|
31323
|
+
var VERSION = true ? "2.7.0" : "0.0.0";
|
|
31322
31324
|
function createServer(options) {
|
|
31323
31325
|
return new McpServer({
|
|
31324
31326
|
name: options?.name ?? "gogcli",
|
|
@@ -31396,14 +31398,17 @@ function registerExtraDocsTools(server2) {
|
|
|
31396
31398
|
inputSchema: {
|
|
31397
31399
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31398
31400
|
format: external_exports.enum(["text", "json"]).optional().describe("Output format (default: text)"),
|
|
31399
|
-
tab: external_exports.string().optional().describe("Target tab title or ID
|
|
31400
|
-
allTabs: external_exports.boolean().optional().describe("Show all tabs with
|
|
31401
|
+
tab: external_exports.string().optional().describe("Target tab title or ID. In json mode, returns that one tab in the legacy top-level Document shape."),
|
|
31402
|
+
allTabs: external_exports.boolean().optional().describe("Show all tabs. In json mode, returns the canonical Document response with all tab content populated."),
|
|
31401
31403
|
maxBytes: external_exports.number().optional().describe("Max bytes to read in text mode (0 = unlimited; default 2000000)"),
|
|
31402
31404
|
account: accountParam
|
|
31403
31405
|
}
|
|
31404
31406
|
}, async ({ docId, format, tab, allTabs, maxBytes, account }) => {
|
|
31405
31407
|
if (format === "json") {
|
|
31406
|
-
|
|
31408
|
+
const args2 = ["docs", "raw", docId, "--pretty"];
|
|
31409
|
+
if (tab) args2.push(`--tab=${tab}`);
|
|
31410
|
+
if (allTabs) args2.push("--all-tabs");
|
|
31411
|
+
return runOrDiagnose(args2, { account });
|
|
31407
31412
|
}
|
|
31408
31413
|
const args = ["docs", "cat", docId];
|
|
31409
31414
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -31743,6 +31748,230 @@ function registerExtraDocsTools(server2) {
|
|
|
31743
31748
|
if (tab) args.push(`--tab=${tab}`);
|
|
31744
31749
|
return runOrDiagnose(args, { account });
|
|
31745
31750
|
});
|
|
31751
|
+
const tableSelectorParam = external_exports.string().optional().describe("Table selector: 1-based index (negative from the end), exact first-cell text, * for the only table, or text:VALUE for numeric/syntax-looking first-cell text (default: 1)");
|
|
31752
|
+
server2.registerTool("gog_docs_table_row_insert", {
|
|
31753
|
+
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 \u2014 the default).',
|
|
31754
|
+
inputSchema: {
|
|
31755
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31756
|
+
table: tableSelectorParam,
|
|
31757
|
+
at: external_exports.string().optional().describe('Insert before this 1-based row, a negative index from the end, or "end" to append (default: end)'),
|
|
31758
|
+
valuesJson: external_exports.string().optional().describe(`JSON string array with the new row's cell values, e.g. ["Name","Qty"]`),
|
|
31759
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31760
|
+
account: accountParam
|
|
31761
|
+
}
|
|
31762
|
+
}, async ({ docId, table, at, valuesJson, tab, account }) => {
|
|
31763
|
+
const args = ["docs", "table-row", "insert", docId];
|
|
31764
|
+
if (table) args.push(`--table=${table}`);
|
|
31765
|
+
if (at) args.push(`--at=${at}`);
|
|
31766
|
+
if (valuesJson) args.push(`--values-json=${valuesJson}`);
|
|
31767
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31768
|
+
return runOrDiagnose(args, { account });
|
|
31769
|
+
});
|
|
31770
|
+
server2.registerTool("gog_docs_table_row_delete", {
|
|
31771
|
+
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.",
|
|
31772
|
+
annotations: { destructiveHint: true },
|
|
31773
|
+
inputSchema: {
|
|
31774
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31775
|
+
row: external_exports.number().int().describe("1-based row number; negative indexes count from the end"),
|
|
31776
|
+
table: tableSelectorParam,
|
|
31777
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31778
|
+
account: accountParam
|
|
31779
|
+
}
|
|
31780
|
+
}, async ({ docId, row, table, tab, account }) => {
|
|
31781
|
+
const args = ["docs", "table-row", "delete", docId, `--row=${row}`];
|
|
31782
|
+
if (table) args.push(`--table=${table}`);
|
|
31783
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31784
|
+
return runOrDiagnose(args, { account });
|
|
31785
|
+
});
|
|
31786
|
+
server2.registerTool("gog_docs_table_column_insert", {
|
|
31787
|
+
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 \u2014 the default).',
|
|
31788
|
+
inputSchema: {
|
|
31789
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31790
|
+
table: tableSelectorParam,
|
|
31791
|
+
at: external_exports.string().optional().describe('Insert before this 1-based column, a negative index from the end, or "end" to append (default: end)'),
|
|
31792
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31793
|
+
account: accountParam
|
|
31794
|
+
}
|
|
31795
|
+
}, async ({ docId, table, at, tab, account }) => {
|
|
31796
|
+
const args = ["docs", "table-column", "insert", docId];
|
|
31797
|
+
if (at) args.push(`--at=${at}`);
|
|
31798
|
+
if (table) args.push(`--table=${table}`);
|
|
31799
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31800
|
+
return runOrDiagnose(args, { account });
|
|
31801
|
+
});
|
|
31802
|
+
server2.registerTool("gog_docs_table_column_delete", {
|
|
31803
|
+
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.",
|
|
31804
|
+
annotations: { destructiveHint: true },
|
|
31805
|
+
inputSchema: {
|
|
31806
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31807
|
+
col: external_exports.number().int().describe("1-based column number; negative indexes count from the end"),
|
|
31808
|
+
table: tableSelectorParam,
|
|
31809
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31810
|
+
account: accountParam
|
|
31811
|
+
}
|
|
31812
|
+
}, async ({ docId, col, table, tab, account }) => {
|
|
31813
|
+
const args = ["docs", "table-column", "delete", docId, `--col=${col}`];
|
|
31814
|
+
if (table) args.push(`--table=${table}`);
|
|
31815
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31816
|
+
return runOrDiagnose(args, { account });
|
|
31817
|
+
});
|
|
31818
|
+
server2.registerTool("gog_docs_table_merge", {
|
|
31819
|
+
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 \u2014 use gog_docs_table_unmerge to split back.",
|
|
31820
|
+
annotations: { destructiveHint: true },
|
|
31821
|
+
inputSchema: {
|
|
31822
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31823
|
+
range: external_exports.string().describe('1-based cell range r1,c1:r2,c2 (e.g. "1,1:2,3")'),
|
|
31824
|
+
table: tableSelectorParam,
|
|
31825
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31826
|
+
account: accountParam
|
|
31827
|
+
}
|
|
31828
|
+
}, async ({ docId, range, table, tab, account }) => {
|
|
31829
|
+
const args = ["docs", "table-merge", docId, `--range=${range}`];
|
|
31830
|
+
if (table) args.push(`--table=${table}`);
|
|
31831
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31832
|
+
return runOrDiagnose(args, { account });
|
|
31833
|
+
});
|
|
31834
|
+
server2.registerTool("gog_docs_table_unmerge", {
|
|
31835
|
+
description: "Unmerge (split) the merged region containing a given cell in a native Google Docs table.",
|
|
31836
|
+
inputSchema: {
|
|
31837
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31838
|
+
cell: external_exports.string().describe('1-based cell r,c inside the merged region (e.g. "1,1")'),
|
|
31839
|
+
table: tableSelectorParam,
|
|
31840
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31841
|
+
account: accountParam
|
|
31842
|
+
}
|
|
31843
|
+
}, async ({ docId, cell, table, tab, account }) => {
|
|
31844
|
+
const args = ["docs", "table-unmerge", docId, `--cell=${cell}`];
|
|
31845
|
+
if (table) args.push(`--table=${table}`);
|
|
31846
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31847
|
+
return runOrDiagnose(args, { account });
|
|
31848
|
+
});
|
|
31849
|
+
server2.registerTool("gog_docs_named_range_create", {
|
|
31850
|
+
description: "Create a named range \u2014 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.",
|
|
31851
|
+
inputSchema: {
|
|
31852
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31853
|
+
name: external_exports.string().describe("Unique named range name"),
|
|
31854
|
+
at: external_exports.string().optional().describe("Create the range around this literal matched text, instead of start/end indices"),
|
|
31855
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31856
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31857
|
+
start: external_exports.number().int().optional().describe("Range start UTF-16 index (inclusive). Required with end unless `at` is set."),
|
|
31858
|
+
end: external_exports.number().int().optional().describe("Range end UTF-16 index (exclusive). Required with start unless `at` is set."),
|
|
31859
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31860
|
+
account: accountParam
|
|
31861
|
+
}
|
|
31862
|
+
}, async ({ docId, name, at, occurrence, matchCase, start, end, tab, account }) => {
|
|
31863
|
+
const args = ["docs", "named-range", "create", docId, `--name=${name}`];
|
|
31864
|
+
if (at) args.push(`--at=${at}`);
|
|
31865
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31866
|
+
if (matchCase) args.push("--match-case");
|
|
31867
|
+
if (start !== void 0) args.push(`--start=${start}`);
|
|
31868
|
+
if (end !== void 0) args.push(`--end=${end}`);
|
|
31869
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31870
|
+
return runOrDiagnose(args, { account });
|
|
31871
|
+
});
|
|
31872
|
+
server2.registerTool("gog_docs_named_range_list", {
|
|
31873
|
+
description: "List named ranges in a Google Doc, optionally filtered by name.",
|
|
31874
|
+
annotations: { readOnlyHint: true },
|
|
31875
|
+
inputSchema: {
|
|
31876
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31877
|
+
name: external_exports.string().optional().describe("Only return ranges with this name"),
|
|
31878
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31879
|
+
account: accountParam
|
|
31880
|
+
}
|
|
31881
|
+
}, async ({ docId, name, tab, account }) => {
|
|
31882
|
+
const args = ["docs", "named-range", "list", docId];
|
|
31883
|
+
if (name) args.push(`--name=${name}`);
|
|
31884
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31885
|
+
return runOrDiagnose(args, { account });
|
|
31886
|
+
});
|
|
31887
|
+
server2.registerTool("gog_docs_named_range_delete", {
|
|
31888
|
+
description: "Delete a named range (the anchor only \u2014 the underlying document text is untouched).",
|
|
31889
|
+
annotations: { destructiveHint: true },
|
|
31890
|
+
inputSchema: {
|
|
31891
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31892
|
+
nameOrId: external_exports.string().describe("Named range name or ID"),
|
|
31893
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31894
|
+
account: accountParam
|
|
31895
|
+
}
|
|
31896
|
+
}, async ({ docId, nameOrId, tab, account }) => {
|
|
31897
|
+
const args = ["docs", "named-range", "delete", docId, nameOrId];
|
|
31898
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31899
|
+
return runOrDiagnose(args, { account });
|
|
31900
|
+
});
|
|
31901
|
+
server2.registerTool("gog_docs_named_range_replace", {
|
|
31902
|
+
description: "Replace the text inside a named range with new content (inline or from a file), keeping the anchor for future updates \u2014 the templated-update workhorse.",
|
|
31903
|
+
annotations: { destructiveHint: true },
|
|
31904
|
+
inputSchema: {
|
|
31905
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31906
|
+
nameOrId: external_exports.string().describe("Named range name or ID"),
|
|
31907
|
+
text: external_exports.string().optional().describe("Replacement text"),
|
|
31908
|
+
file: external_exports.string().optional().describe("Path to a file with the replacement text"),
|
|
31909
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31910
|
+
account: accountParam
|
|
31911
|
+
}
|
|
31912
|
+
}, async ({ docId, nameOrId, text, file: file2, tab, account }) => {
|
|
31913
|
+
const args = ["docs", "named-range", "replace", docId, nameOrId];
|
|
31914
|
+
if (text !== void 0) args.push(`--text=${text}`);
|
|
31915
|
+
if (file2) args.push(`--file=${file2}`);
|
|
31916
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31917
|
+
return runOrDiagnose(args, { account });
|
|
31918
|
+
});
|
|
31919
|
+
server2.registerTool("gog_docs_tables_list", {
|
|
31920
|
+
description: "Enumerate the native tables in a Google Doc (dimensions, position) \u2014 the index/first-cell-text it reports feeds the `table` selector on the table-row/column/merge tools.",
|
|
31921
|
+
annotations: { readOnlyHint: true },
|
|
31922
|
+
inputSchema: {
|
|
31923
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31924
|
+
tab: external_exports.string().optional().describe("Tab title or ID (omit for default)"),
|
|
31925
|
+
account: accountParam
|
|
31926
|
+
}
|
|
31927
|
+
}, async ({ docId, tab, account }) => {
|
|
31928
|
+
const args = ["docs", "tables", "list", docId];
|
|
31929
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31930
|
+
return runOrDiagnose(args, { account });
|
|
31931
|
+
});
|
|
31932
|
+
server2.registerTool("gog_docs_images_list", {
|
|
31933
|
+
description: "Enumerate inline/positioned images in a Google Doc.",
|
|
31934
|
+
annotations: { readOnlyHint: true },
|
|
31935
|
+
inputSchema: {
|
|
31936
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31937
|
+
tab: external_exports.string().optional().describe("Tab title or ID (omit for default)"),
|
|
31938
|
+
account: accountParam
|
|
31939
|
+
}
|
|
31940
|
+
}, async ({ docId, tab, account }) => {
|
|
31941
|
+
const args = ["docs", "images", "list", docId];
|
|
31942
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31943
|
+
return runOrDiagnose(args, { account });
|
|
31944
|
+
});
|
|
31945
|
+
server2.registerTool("gog_docs_headings_list", {
|
|
31946
|
+
description: "Enumerate headings in a Google Doc (a lightweight outline view), optionally filtered to one heading level.",
|
|
31947
|
+
annotations: { readOnlyHint: true },
|
|
31948
|
+
inputSchema: {
|
|
31949
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31950
|
+
level: external_exports.number().int().optional().describe("Only return this heading level (1-6)"),
|
|
31951
|
+
tab: external_exports.string().optional().describe("Tab title or ID (omit for default)"),
|
|
31952
|
+
account: accountParam
|
|
31953
|
+
}
|
|
31954
|
+
}, async ({ docId, level, tab, account }) => {
|
|
31955
|
+
const args = ["docs", "headings", "list", docId];
|
|
31956
|
+
if (level !== void 0) args.push(`--level=${level}`);
|
|
31957
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31958
|
+
return runOrDiagnose(args, { account });
|
|
31959
|
+
});
|
|
31960
|
+
server2.registerTool("gog_docs_paragraphs_list", {
|
|
31961
|
+
description: "Enumerate paragraphs in a Google Doc with emptiness, text-run ranges, styles, and links \u2014 richer than gog_docs_structure when you need per-run detail. Optionally filter to one named style.",
|
|
31962
|
+
annotations: { readOnlyHint: true },
|
|
31963
|
+
inputSchema: {
|
|
31964
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31965
|
+
style: external_exports.string().optional().describe("Only return this named style (e.g. NORMAL_TEXT or HEADING_2)"),
|
|
31966
|
+
tab: external_exports.string().optional().describe("Tab title or ID (omit for default)"),
|
|
31967
|
+
account: accountParam
|
|
31968
|
+
}
|
|
31969
|
+
}, async ({ docId, style, tab, account }) => {
|
|
31970
|
+
const args = ["docs", "paragraphs", "list", docId];
|
|
31971
|
+
if (style) args.push(`--style=${style}`);
|
|
31972
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31973
|
+
return runOrDiagnose(args, { account });
|
|
31974
|
+
});
|
|
31746
31975
|
server2.registerTool("gog_docs_insert_page_break", {
|
|
31747
31976
|
description: "Insert a Google Docs page break via InsertPageBreakRequest \u2014 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.",
|
|
31748
31977
|
annotations: { destructiveHint: true },
|
|
@@ -31871,11 +32100,12 @@ function registerExtraDocsTools(server2) {
|
|
|
31871
32100
|
return runOrDiagnose(args, { account });
|
|
31872
32101
|
});
|
|
31873
32102
|
server2.registerTool("gog_docs_insert_image", {
|
|
31874
|
-
description: "
|
|
32103
|
+
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.",
|
|
31875
32104
|
annotations: { destructiveHint: true },
|
|
31876
32105
|
inputSchema: {
|
|
31877
32106
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31878
|
-
file: external_exports.string().describe("Local PNG, JPEG, or GIF image to upload and insert"),
|
|
32107
|
+
file: external_exports.string().optional().describe("Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)"),
|
|
32108
|
+
url: external_exports.string().optional().describe("Public HTTPS image URL to insert directly \u2014 no Drive upload or temporary public sharing (exactly one of file or url)"),
|
|
31879
32109
|
at: external_exports.string().optional().describe('Placeholder text to replace, or "end" to append (default: end)'),
|
|
31880
32110
|
width: external_exports.number().optional().describe("Image width in points (default: 468)"),
|
|
31881
32111
|
height: external_exports.number().optional().describe("Image height in points (optional; width-only preserves aspect ratio)"),
|
|
@@ -31885,8 +32115,10 @@ function registerExtraDocsTools(server2) {
|
|
|
31885
32115
|
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31886
32116
|
account: accountParam
|
|
31887
32117
|
}
|
|
31888
|
-
}, async ({ docId, file: file2, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
31889
|
-
const args = ["docs", "insert-image", docId
|
|
32118
|
+
}, async ({ docId, file: file2, url: url2, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
32119
|
+
const args = ["docs", "insert-image", docId];
|
|
32120
|
+
if (file2) args.push(`--file=${file2}`);
|
|
32121
|
+
if (url2) args.push(`--url=${url2}`);
|
|
31890
32122
|
if (at) args.push(`--at=${at}`);
|
|
31891
32123
|
if (width !== void 0) args.push(`--width=${width}`);
|
|
31892
32124
|
if (height !== void 0) args.push(`--height=${height}`);
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-docs",
|
|
5
5
|
"display_name": "gogcli (Docs)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.7.0",
|
|
7
7
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -207,6 +207,62 @@
|
|
|
207
207
|
"name": "gog_docs_table_column_width",
|
|
208
208
|
"description": "Set a fixed table column width in points, or reset columns to Docs-managed even distribution."
|
|
209
209
|
},
|
|
210
|
+
{
|
|
211
|
+
"name": "gog_docs_table_row_insert",
|
|
212
|
+
"description": "Insert a table row, optionally populated from a JSON string array."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "gog_docs_table_row_delete",
|
|
216
|
+
"description": "Delete a table row by 1-based number."
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "gog_docs_table_column_insert",
|
|
220
|
+
"description": "Insert a table column before a 1-based position or at the end."
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "gog_docs_table_column_delete",
|
|
224
|
+
"description": "Delete a table column by 1-based number."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "gog_docs_table_merge",
|
|
228
|
+
"description": "Merge a rectangular cell range in a table."
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"name": "gog_docs_table_unmerge",
|
|
232
|
+
"description": "Unmerge (split) the merged region containing a cell."
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "gog_docs_named_range_create",
|
|
236
|
+
"description": "Create a named range — a durable anchor over document text, by matched text or indices."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"name": "gog_docs_named_range_list",
|
|
240
|
+
"description": "List named ranges, optionally filtered by name."
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "gog_docs_named_range_delete",
|
|
244
|
+
"description": "Delete a named range anchor (the text is untouched)."
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"name": "gog_docs_named_range_replace",
|
|
248
|
+
"description": "Replace the text inside a named range, keeping the anchor for future updates."
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "gog_docs_tables_list",
|
|
252
|
+
"description": "Enumerate native tables (dimensions, position) — feeds the table selector on the table tools."
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "gog_docs_images_list",
|
|
256
|
+
"description": "Enumerate inline/positioned images."
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"name": "gog_docs_headings_list",
|
|
260
|
+
"description": "Enumerate headings (outline view), optionally one level."
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"name": "gog_docs_paragraphs_list",
|
|
264
|
+
"description": "Enumerate paragraphs with text-run ranges, styles, and links."
|
|
265
|
+
},
|
|
210
266
|
{
|
|
211
267
|
"name": "gog_docs_insert_page_break",
|
|
212
268
|
"description": "Insert a Google Docs page break at a given index or at end-of-doc — the only path for multi-page deliverables."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-docs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-docs",
|
|
5
5
|
"description": "Extended Google Docs MCP server via gogcli — all base tools plus full Docs support",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp-docs"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.7.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-docs",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.7.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/tools/docs-extra.ts
CHANGED
|
@@ -75,14 +75,17 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
75
75
|
inputSchema: {
|
|
76
76
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
77
77
|
format: z.enum(['text', 'json']).optional().describe('Output format (default: text)'),
|
|
78
|
-
tab: z.string().optional().describe('Target tab title or ID
|
|
79
|
-
allTabs: z.boolean().optional().describe('Show all tabs with
|
|
78
|
+
tab: z.string().optional().describe('Target tab title or ID. In json mode, returns that one tab in the legacy top-level Document shape.'),
|
|
79
|
+
allTabs: z.boolean().optional().describe('Show all tabs. In json mode, returns the canonical Document response with all tab content populated.'),
|
|
80
80
|
maxBytes: z.number().optional().describe('Max bytes to read in text mode (0 = unlimited; default 2000000)'),
|
|
81
81
|
account: accountParam,
|
|
82
82
|
},
|
|
83
83
|
}, async ({ docId, format, tab, allTabs, maxBytes, account }) => {
|
|
84
84
|
if (format === 'json') {
|
|
85
|
-
|
|
85
|
+
const args = ['docs', 'raw', docId, '--pretty'];
|
|
86
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
87
|
+
if (allTabs) args.push('--all-tabs');
|
|
88
|
+
return runOrDiagnose(args, { account });
|
|
86
89
|
}
|
|
87
90
|
const args = ['docs', 'cat', docId];
|
|
88
91
|
if (tab) args.push(`--tab=${tab}`);
|
|
@@ -464,6 +467,247 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
464
467
|
return runOrDiagnose(args, { account });
|
|
465
468
|
});
|
|
466
469
|
|
|
470
|
+
// Shared describe for the --table selector used by the table-row/column/merge
|
|
471
|
+
// tools (a richer addressing scheme than the older tableIndex param).
|
|
472
|
+
const tableSelectorParam = z.string().optional().describe('Table selector: 1-based index (negative from the end), exact first-cell text, * for the only table, or text:VALUE for numeric/syntax-looking first-cell text (default: 1)');
|
|
473
|
+
|
|
474
|
+
server.registerTool('gog_docs_table_row_insert', {
|
|
475
|
+
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).',
|
|
476
|
+
inputSchema: {
|
|
477
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
478
|
+
table: tableSelectorParam,
|
|
479
|
+
at: z.string().optional().describe('Insert before this 1-based row, a negative index from the end, or "end" to append (default: end)'),
|
|
480
|
+
valuesJson: z.string().optional().describe('JSON string array with the new row\'s cell values, e.g. ["Name","Qty"]'),
|
|
481
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
482
|
+
account: accountParam,
|
|
483
|
+
},
|
|
484
|
+
}, async ({ docId, table, at, valuesJson, tab, account }) => {
|
|
485
|
+
const args = ['docs', 'table-row', 'insert', docId];
|
|
486
|
+
if (table) args.push(`--table=${table}`);
|
|
487
|
+
if (at) args.push(`--at=${at}`);
|
|
488
|
+
if (valuesJson) args.push(`--values-json=${valuesJson}`);
|
|
489
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
490
|
+
return runOrDiagnose(args, { account });
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
server.registerTool('gog_docs_table_row_delete', {
|
|
494
|
+
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.',
|
|
495
|
+
annotations: { destructiveHint: true },
|
|
496
|
+
inputSchema: {
|
|
497
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
498
|
+
row: z.number().int().describe('1-based row number; negative indexes count from the end'),
|
|
499
|
+
table: tableSelectorParam,
|
|
500
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
501
|
+
account: accountParam,
|
|
502
|
+
},
|
|
503
|
+
}, async ({ docId, row, table, tab, account }) => {
|
|
504
|
+
const args = ['docs', 'table-row', 'delete', docId, `--row=${row}`];
|
|
505
|
+
if (table) args.push(`--table=${table}`);
|
|
506
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
507
|
+
return runOrDiagnose(args, { account });
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
server.registerTool('gog_docs_table_column_insert', {
|
|
511
|
+
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).',
|
|
512
|
+
inputSchema: {
|
|
513
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
514
|
+
table: tableSelectorParam,
|
|
515
|
+
at: z.string().optional().describe('Insert before this 1-based column, a negative index from the end, or "end" to append (default: end)'),
|
|
516
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
517
|
+
account: accountParam,
|
|
518
|
+
},
|
|
519
|
+
}, async ({ docId, table, at, tab, account }) => {
|
|
520
|
+
const args = ['docs', 'table-column', 'insert', docId];
|
|
521
|
+
if (at) args.push(`--at=${at}`);
|
|
522
|
+
if (table) args.push(`--table=${table}`);
|
|
523
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
524
|
+
return runOrDiagnose(args, { account });
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
server.registerTool('gog_docs_table_column_delete', {
|
|
528
|
+
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.',
|
|
529
|
+
annotations: { destructiveHint: true },
|
|
530
|
+
inputSchema: {
|
|
531
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
532
|
+
col: z.number().int().describe('1-based column number; negative indexes count from the end'),
|
|
533
|
+
table: tableSelectorParam,
|
|
534
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
535
|
+
account: accountParam,
|
|
536
|
+
},
|
|
537
|
+
}, async ({ docId, col, table, tab, account }) => {
|
|
538
|
+
const args = ['docs', 'table-column', 'delete', docId, `--col=${col}`];
|
|
539
|
+
if (table) args.push(`--table=${table}`);
|
|
540
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
541
|
+
return runOrDiagnose(args, { account });
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
server.registerTool('gog_docs_table_merge', {
|
|
545
|
+
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.',
|
|
546
|
+
annotations: { destructiveHint: true },
|
|
547
|
+
inputSchema: {
|
|
548
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
549
|
+
range: z.string().describe('1-based cell range r1,c1:r2,c2 (e.g. "1,1:2,3")'),
|
|
550
|
+
table: tableSelectorParam,
|
|
551
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
552
|
+
account: accountParam,
|
|
553
|
+
},
|
|
554
|
+
}, async ({ docId, range, table, tab, account }) => {
|
|
555
|
+
const args = ['docs', 'table-merge', docId, `--range=${range}`];
|
|
556
|
+
if (table) args.push(`--table=${table}`);
|
|
557
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
558
|
+
return runOrDiagnose(args, { account });
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
server.registerTool('gog_docs_table_unmerge', {
|
|
562
|
+
description: 'Unmerge (split) the merged region containing a given cell in a native Google Docs table.',
|
|
563
|
+
inputSchema: {
|
|
564
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
565
|
+
cell: z.string().describe('1-based cell r,c inside the merged region (e.g. "1,1")'),
|
|
566
|
+
table: tableSelectorParam,
|
|
567
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
568
|
+
account: accountParam,
|
|
569
|
+
},
|
|
570
|
+
}, async ({ docId, cell, table, tab, account }) => {
|
|
571
|
+
const args = ['docs', 'table-unmerge', docId, `--cell=${cell}`];
|
|
572
|
+
if (table) args.push(`--table=${table}`);
|
|
573
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
574
|
+
return runOrDiagnose(args, { account });
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
server.registerTool('gog_docs_named_range_create', {
|
|
578
|
+
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.',
|
|
579
|
+
inputSchema: {
|
|
580
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
581
|
+
name: z.string().describe('Unique named range name'),
|
|
582
|
+
at: z.string().optional().describe('Create the range around this literal matched text, instead of start/end indices'),
|
|
583
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
584
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
585
|
+
start: z.number().int().optional().describe('Range start UTF-16 index (inclusive). Required with end unless `at` is set.'),
|
|
586
|
+
end: z.number().int().optional().describe('Range end UTF-16 index (exclusive). Required with start unless `at` is set.'),
|
|
587
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
588
|
+
account: accountParam,
|
|
589
|
+
},
|
|
590
|
+
}, async ({ docId, name, at, occurrence, matchCase, start, end, tab, account }) => {
|
|
591
|
+
const args = ['docs', 'named-range', 'create', docId, `--name=${name}`];
|
|
592
|
+
if (at) args.push(`--at=${at}`);
|
|
593
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
594
|
+
if (matchCase) args.push('--match-case');
|
|
595
|
+
if (start !== undefined) args.push(`--start=${start}`);
|
|
596
|
+
if (end !== undefined) args.push(`--end=${end}`);
|
|
597
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
598
|
+
return runOrDiagnose(args, { account });
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
server.registerTool('gog_docs_named_range_list', {
|
|
602
|
+
description: 'List named ranges in a Google Doc, optionally filtered by name.',
|
|
603
|
+
annotations: { readOnlyHint: true },
|
|
604
|
+
inputSchema: {
|
|
605
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
606
|
+
name: z.string().optional().describe('Only return ranges with this name'),
|
|
607
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
608
|
+
account: accountParam,
|
|
609
|
+
},
|
|
610
|
+
}, async ({ docId, name, tab, account }) => {
|
|
611
|
+
const args = ['docs', 'named-range', 'list', docId];
|
|
612
|
+
if (name) args.push(`--name=${name}`);
|
|
613
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
614
|
+
return runOrDiagnose(args, { account });
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
server.registerTool('gog_docs_named_range_delete', {
|
|
618
|
+
description: 'Delete a named range (the anchor only — the underlying document text is untouched).',
|
|
619
|
+
annotations: { destructiveHint: true },
|
|
620
|
+
inputSchema: {
|
|
621
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
622
|
+
nameOrId: z.string().describe('Named range name or ID'),
|
|
623
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
624
|
+
account: accountParam,
|
|
625
|
+
},
|
|
626
|
+
}, async ({ docId, nameOrId, tab, account }) => {
|
|
627
|
+
const args = ['docs', 'named-range', 'delete', docId, nameOrId];
|
|
628
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
629
|
+
return runOrDiagnose(args, { account });
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
server.registerTool('gog_docs_named_range_replace', {
|
|
633
|
+
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.',
|
|
634
|
+
annotations: { destructiveHint: true },
|
|
635
|
+
inputSchema: {
|
|
636
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
637
|
+
nameOrId: z.string().describe('Named range name or ID'),
|
|
638
|
+
text: z.string().optional().describe('Replacement text'),
|
|
639
|
+
file: z.string().optional().describe('Path to a file with the replacement text'),
|
|
640
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
641
|
+
account: accountParam,
|
|
642
|
+
},
|
|
643
|
+
}, async ({ docId, nameOrId, text, file, tab, account }) => {
|
|
644
|
+
const args = ['docs', 'named-range', 'replace', docId, nameOrId];
|
|
645
|
+
if (text !== undefined) args.push(`--text=${text}`);
|
|
646
|
+
if (file) args.push(`--file=${file}`);
|
|
647
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
648
|
+
return runOrDiagnose(args, { account });
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
server.registerTool('gog_docs_tables_list', {
|
|
652
|
+
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.',
|
|
653
|
+
annotations: { readOnlyHint: true },
|
|
654
|
+
inputSchema: {
|
|
655
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
656
|
+
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
657
|
+
account: accountParam,
|
|
658
|
+
},
|
|
659
|
+
}, async ({ docId, tab, account }) => {
|
|
660
|
+
const args = ['docs', 'tables', 'list', docId];
|
|
661
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
662
|
+
return runOrDiagnose(args, { account });
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
server.registerTool('gog_docs_images_list', {
|
|
666
|
+
description: 'Enumerate inline/positioned images in a Google Doc.',
|
|
667
|
+
annotations: { readOnlyHint: true },
|
|
668
|
+
inputSchema: {
|
|
669
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
670
|
+
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
671
|
+
account: accountParam,
|
|
672
|
+
},
|
|
673
|
+
}, async ({ docId, tab, account }) => {
|
|
674
|
+
const args = ['docs', 'images', 'list', docId];
|
|
675
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
676
|
+
return runOrDiagnose(args, { account });
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
server.registerTool('gog_docs_headings_list', {
|
|
680
|
+
description: 'Enumerate headings in a Google Doc (a lightweight outline view), optionally filtered to one heading level.',
|
|
681
|
+
annotations: { readOnlyHint: true },
|
|
682
|
+
inputSchema: {
|
|
683
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
684
|
+
level: z.number().int().optional().describe('Only return this heading level (1-6)'),
|
|
685
|
+
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
686
|
+
account: accountParam,
|
|
687
|
+
},
|
|
688
|
+
}, async ({ docId, level, tab, account }) => {
|
|
689
|
+
const args = ['docs', 'headings', 'list', docId];
|
|
690
|
+
if (level !== undefined) args.push(`--level=${level}`);
|
|
691
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
692
|
+
return runOrDiagnose(args, { account });
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
server.registerTool('gog_docs_paragraphs_list', {
|
|
696
|
+
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.',
|
|
697
|
+
annotations: { readOnlyHint: true },
|
|
698
|
+
inputSchema: {
|
|
699
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
700
|
+
style: z.string().optional().describe('Only return this named style (e.g. NORMAL_TEXT or HEADING_2)'),
|
|
701
|
+
tab: z.string().optional().describe('Tab title or ID (omit for default)'),
|
|
702
|
+
account: accountParam,
|
|
703
|
+
},
|
|
704
|
+
}, async ({ docId, style, tab, account }) => {
|
|
705
|
+
const args = ['docs', 'paragraphs', 'list', docId];
|
|
706
|
+
if (style) args.push(`--style=${style}`);
|
|
707
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
708
|
+
return runOrDiagnose(args, { account });
|
|
709
|
+
});
|
|
710
|
+
|
|
467
711
|
server.registerTool('gog_docs_insert_page_break', {
|
|
468
712
|
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.',
|
|
469
713
|
annotations: { destructiveHint: true },
|
|
@@ -597,11 +841,12 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
597
841
|
});
|
|
598
842
|
|
|
599
843
|
server.registerTool('gog_docs_insert_image', {
|
|
600
|
-
description: '
|
|
844
|
+
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.',
|
|
601
845
|
annotations: { destructiveHint: true },
|
|
602
846
|
inputSchema: {
|
|
603
847
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
604
|
-
file: z.string().describe('Local PNG, JPEG, or GIF image to upload and insert'),
|
|
848
|
+
file: z.string().optional().describe('Local PNG, JPEG, or GIF image to upload and insert (exactly one of file or url)'),
|
|
849
|
+
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)'),
|
|
605
850
|
at: z.string().optional().describe('Placeholder text to replace, or "end" to append (default: end)'),
|
|
606
851
|
width: z.number().optional().describe('Image width in points (default: 468)'),
|
|
607
852
|
height: z.number().optional().describe('Image height in points (optional; width-only preserves aspect ratio)'),
|
|
@@ -611,8 +856,10 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
611
856
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
612
857
|
account: accountParam,
|
|
613
858
|
},
|
|
614
|
-
}, async ({ docId, file, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
615
|
-
const args = ['docs', 'insert-image', docId
|
|
859
|
+
}, async ({ docId, file, url, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
860
|
+
const args = ['docs', 'insert-image', docId];
|
|
861
|
+
if (file) args.push(`--file=${file}`);
|
|
862
|
+
if (url) args.push(`--url=${url}`);
|
|
616
863
|
if (at) args.push(`--at=${at}`);
|
|
617
864
|
if (width !== undefined) args.push(`--width=${width}`);
|
|
618
865
|
if (height !== undefined) args.push(`--height=${height}`);
|
|
@@ -1262,3 +1262,223 @@ describe('gog_docs_clear', () => {
|
|
|
1262
1262
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'clear', 'abc'], { account: 'a@b.com' });
|
|
1263
1263
|
});
|
|
1264
1264
|
});
|
|
1265
|
+
|
|
1266
|
+
// --- gog 0.24.0 ---
|
|
1267
|
+
|
|
1268
|
+
describe('gog_docs_table_row_insert', () => {
|
|
1269
|
+
it('appends a row with values to a selected table', async () => {
|
|
1270
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1271
|
+
const handlers = setupHandlers();
|
|
1272
|
+
await handlers.get('gog_docs_table_row_insert')!({
|
|
1273
|
+
docId: 'd1', table: '2', at: 'end', valuesJson: '["A","B"]', tab: 'T',
|
|
1274
|
+
});
|
|
1275
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1276
|
+
['docs', 'table-row', 'insert', 'd1', '--table=2', '--at=end', '--values-json=["A","B"]', '--tab=T'],
|
|
1277
|
+
{ account: undefined },
|
|
1278
|
+
);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
it('works with no optional flags', async () => {
|
|
1282
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1283
|
+
const handlers = setupHandlers();
|
|
1284
|
+
await handlers.get('gog_docs_table_row_insert')!({ docId: 'd1' });
|
|
1285
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'table-row', 'insert', 'd1'], { account: undefined });
|
|
1286
|
+
});
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
describe('gog_docs_table_row_delete', () => {
|
|
1290
|
+
it('deletes a row by 1-based number', async () => {
|
|
1291
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1292
|
+
const handlers = setupHandlers();
|
|
1293
|
+
await handlers.get('gog_docs_table_row_delete')!({ docId: 'd1', row: -1, table: 'Budget' });
|
|
1294
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1295
|
+
['docs', 'table-row', 'delete', 'd1', '--row=-1', '--table=Budget'],
|
|
1296
|
+
{ account: undefined },
|
|
1297
|
+
);
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
describe('gog_docs_table_column_insert', () => {
|
|
1302
|
+
it('inserts a column before a 1-based index', async () => {
|
|
1303
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1304
|
+
const handlers = setupHandlers();
|
|
1305
|
+
await handlers.get('gog_docs_table_column_insert')!({ docId: 'd1', at: '3', table: '*', tab: 'T' });
|
|
1306
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1307
|
+
['docs', 'table-column', 'insert', 'd1', '--at=3', '--table=*', '--tab=T'],
|
|
1308
|
+
{ account: undefined },
|
|
1309
|
+
);
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
describe('gog_docs_table_column_delete', () => {
|
|
1314
|
+
it('deletes a column by 1-based number', async () => {
|
|
1315
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1316
|
+
const handlers = setupHandlers();
|
|
1317
|
+
await handlers.get('gog_docs_table_column_delete')!({ docId: 'd1', col: 2 });
|
|
1318
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1319
|
+
['docs', 'table-column', 'delete', 'd1', '--col=2'],
|
|
1320
|
+
{ account: undefined },
|
|
1321
|
+
);
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
describe('gog_docs_table_merge', () => {
|
|
1326
|
+
it('merges a 1-based cell range', async () => {
|
|
1327
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1328
|
+
const handlers = setupHandlers();
|
|
1329
|
+
await handlers.get('gog_docs_table_merge')!({ docId: 'd1', range: '1,1:2,3', table: '1' });
|
|
1330
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1331
|
+
['docs', 'table-merge', 'd1', '--range=1,1:2,3', '--table=1'],
|
|
1332
|
+
{ account: undefined },
|
|
1333
|
+
);
|
|
1334
|
+
});
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
describe('gog_docs_table_unmerge', () => {
|
|
1338
|
+
it('unmerges the region containing a cell', async () => {
|
|
1339
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1340
|
+
const handlers = setupHandlers();
|
|
1341
|
+
await handlers.get('gog_docs_table_unmerge')!({ docId: 'd1', cell: '1,1', tab: 'T' });
|
|
1342
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1343
|
+
['docs', 'table-unmerge', 'd1', '--cell=1,1', '--tab=T'],
|
|
1344
|
+
{ account: undefined },
|
|
1345
|
+
);
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
describe('gog_docs_named_range_create', () => {
|
|
1350
|
+
it('creates a range around matched text', async () => {
|
|
1351
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1352
|
+
const handlers = setupHandlers();
|
|
1353
|
+
await handlers.get('gog_docs_named_range_create')!({
|
|
1354
|
+
docId: 'd1', name: 'intro', at: 'Introduction', occurrence: 1, matchCase: true,
|
|
1355
|
+
});
|
|
1356
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1357
|
+
['docs', 'named-range', 'create', 'd1', '--name=intro', '--at=Introduction', '--occurrence=1', '--match-case'],
|
|
1358
|
+
{ account: undefined },
|
|
1359
|
+
);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('creates a range from explicit indices', async () => {
|
|
1363
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1364
|
+
const handlers = setupHandlers();
|
|
1365
|
+
await handlers.get('gog_docs_named_range_create')!({ docId: 'd1', name: 'intro', start: 5, end: 20, tab: 'T' });
|
|
1366
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1367
|
+
['docs', 'named-range', 'create', 'd1', '--name=intro', '--start=5', '--end=20', '--tab=T'],
|
|
1368
|
+
{ account: undefined },
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
describe('gog_docs_named_range_list', () => {
|
|
1374
|
+
it('lists named ranges, optionally filtered by name', async () => {
|
|
1375
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1376
|
+
const handlers = setupHandlers();
|
|
1377
|
+
await handlers.get('gog_docs_named_range_list')!({ docId: 'd1', name: 'intro' });
|
|
1378
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1379
|
+
['docs', 'named-range', 'list', 'd1', '--name=intro'],
|
|
1380
|
+
{ account: undefined },
|
|
1381
|
+
);
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
describe('gog_docs_named_range_delete', () => {
|
|
1386
|
+
it('deletes by name or id', async () => {
|
|
1387
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1388
|
+
const handlers = setupHandlers();
|
|
1389
|
+
await handlers.get('gog_docs_named_range_delete')!({ docId: 'd1', nameOrId: 'intro' });
|
|
1390
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1391
|
+
['docs', 'named-range', 'delete', 'd1', 'intro'],
|
|
1392
|
+
{ account: undefined },
|
|
1393
|
+
);
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
describe('gog_docs_named_range_replace', () => {
|
|
1398
|
+
it('replaces range content with text', async () => {
|
|
1399
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1400
|
+
const handlers = setupHandlers();
|
|
1401
|
+
await handlers.get('gog_docs_named_range_replace')!({ docId: 'd1', nameOrId: 'intro', text: 'New intro', tab: 'T' });
|
|
1402
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1403
|
+
['docs', 'named-range', 'replace', 'd1', 'intro', '--text=New intro', '--tab=T'],
|
|
1404
|
+
{ account: undefined },
|
|
1405
|
+
);
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
it('replaces range content from a file', async () => {
|
|
1409
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1410
|
+
const handlers = setupHandlers();
|
|
1411
|
+
await handlers.get('gog_docs_named_range_replace')!({ docId: 'd1', nameOrId: 'kix.abc', file: '/tmp/intro.txt' });
|
|
1412
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1413
|
+
['docs', 'named-range', 'replace', 'd1', 'kix.abc', '--file=/tmp/intro.txt'],
|
|
1414
|
+
{ account: undefined },
|
|
1415
|
+
);
|
|
1416
|
+
});
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
describe('gog_docs_tables_list', () => {
|
|
1420
|
+
it('enumerates tables, tab-aware', async () => {
|
|
1421
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1422
|
+
const handlers = setupHandlers();
|
|
1423
|
+
await handlers.get('gog_docs_tables_list')!({ docId: 'd1', tab: 'T' });
|
|
1424
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'tables', 'list', 'd1', '--tab=T'], { account: undefined });
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
describe('gog_docs_images_list', () => {
|
|
1429
|
+
it('enumerates images', async () => {
|
|
1430
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1431
|
+
const handlers = setupHandlers();
|
|
1432
|
+
await handlers.get('gog_docs_images_list')!({ docId: 'd1' });
|
|
1433
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'images', 'list', 'd1'], { account: undefined });
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
describe('gog_docs_headings_list', () => {
|
|
1438
|
+
it('enumerates headings with a level filter', async () => {
|
|
1439
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1440
|
+
const handlers = setupHandlers();
|
|
1441
|
+
await handlers.get('gog_docs_headings_list')!({ docId: 'd1', level: 2 });
|
|
1442
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'headings', 'list', 'd1', '--level=2'], { account: undefined });
|
|
1443
|
+
});
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
describe('gog_docs_paragraphs_list', () => {
|
|
1447
|
+
it('enumerates paragraphs with a style filter', async () => {
|
|
1448
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1449
|
+
const handlers = setupHandlers();
|
|
1450
|
+
await handlers.get('gog_docs_paragraphs_list')!({ docId: 'd1', style: 'HEADING_2', tab: 'T' });
|
|
1451
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1452
|
+
['docs', 'paragraphs', 'list', 'd1', '--style=HEADING_2', '--tab=T'],
|
|
1453
|
+
{ account: undefined },
|
|
1454
|
+
);
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
describe('gog_docs_insert_image url mode', () => {
|
|
1459
|
+
it('inserts a public HTTPS image by url without file', async () => {
|
|
1460
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1461
|
+
const handlers = setupHandlers();
|
|
1462
|
+
await handlers.get('gog_docs_insert_image')!({ docId: 'd1', url: 'https://x.test/i.png', at: '{{logo}}' });
|
|
1463
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1464
|
+
['docs', 'insert-image', 'd1', '--url=https://x.test/i.png', '--at={{logo}}'],
|
|
1465
|
+
{ account: undefined },
|
|
1466
|
+
);
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
describe('gog_docs_read json tab targeting', () => {
|
|
1471
|
+
it('passes --tab through to docs raw in json mode', async () => {
|
|
1472
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1473
|
+
const handlers = setupHandlers();
|
|
1474
|
+
await handlers.get('gog_docs_read')!({ docId: 'd1', format: 'json', tab: 'T' });
|
|
1475
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--tab=T'], { account: undefined });
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
it('passes --all-tabs through to docs raw in json mode', async () => {
|
|
1479
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1480
|
+
const handlers = setupHandlers();
|
|
1481
|
+
await handlers.get('gog_docs_read')!({ docId: 'd1', format: 'json', allTabs: true });
|
|
1482
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'raw', 'd1', '--pretty', '--all-tabs'], { account: undefined });
|
|
1483
|
+
});
|
|
1484
|
+
});
|