@piotr-agier/google-drive-mcp 1.7.5 → 1.7.6
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/README.md +14 -16
- package/dist/index.js +366 -29
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,21 +189,14 @@ npx @piotr-agier/google-drive-mcp auth
|
|
|
189
189
|
|
|
190
190
|
### Running the Docker Container
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
The `scripts/docker-mcp.sh` wrapper manages the container lifecycle — it creates, reuses, and replaces containers automatically. MCP clients invoke this script directly (see configuration below).
|
|
193
|
+
|
|
194
|
+
To verify the image works after a rebuild:
|
|
193
195
|
|
|
194
196
|
```bash
|
|
195
|
-
docker run -
|
|
196
|
-
-v /path/to/gcp-oauth.keys.json:/config/gcp-oauth.keys.json:ro \
|
|
197
|
-
-v "$HOME/.config/google-drive-mcp/tokens.json":/config/tokens.json \
|
|
198
|
-
google-drive-mcp
|
|
197
|
+
docker run --rm google-drive-mcp --help
|
|
199
198
|
```
|
|
200
199
|
|
|
201
|
-
**Important Notes:**
|
|
202
|
-
- Replace `/path/to/gcp-oauth.keys.json` with the actual path to your OAuth credentials
|
|
203
|
-
- The `:ro` flag mounts the credentials as read-only for security
|
|
204
|
-
- Tokens are mounted read-write to allow automatic refresh
|
|
205
|
-
- The container runs as non-root user for security
|
|
206
|
-
|
|
207
200
|
### Docker Configuration for Claude Desktop
|
|
208
201
|
|
|
209
202
|
#### Option A: Reusable container (recommended)
|
|
@@ -228,6 +221,7 @@ The script will:
|
|
|
228
221
|
- Create the container on first run
|
|
229
222
|
- Reuse the existing container on subsequent runs
|
|
230
223
|
- Automatically restart it if it was stopped
|
|
224
|
+
- Replace the container when the image has been rebuilt
|
|
231
225
|
|
|
232
226
|
**Note:** The container stays running in the background until explicitly stopped.
|
|
233
227
|
To stop it: `docker stop google-drive-mcp`
|
|
@@ -525,6 +519,12 @@ Add the server to your Claude Desktop configuration:
|
|
|
525
519
|
- **readSmartChips** - Read smart chip-like elements (person mentions, rich links, date chips) from the default tab of a document. Only the default tab is scanned; other tabs are not included.
|
|
526
520
|
- `documentId`: Document ID
|
|
527
521
|
|
|
522
|
+
- **createFootnote** - Create a footnote in a Google Doc. Footnotes cannot be inserted inside equations, headers, footers, or other footnotes.
|
|
523
|
+
- `documentId`: Document ID
|
|
524
|
+
- `index`: 1-based character index where the footnote reference should be inserted (optional — provide this or `endOfSegment`)
|
|
525
|
+
- `endOfSegment`: If true, insert footnote at the end of the document body (optional — provide this or `index`)
|
|
526
|
+
- `content`: Optional text content for the footnote body
|
|
527
|
+
|
|
528
528
|
- **listGoogleDocs** - List Google Documents with optional filtering
|
|
529
529
|
- `query`: Search query to filter by name or content (optional)
|
|
530
530
|
- `maxResults`: Maximum documents to return, 1-100 (optional, default: 20)
|
|
@@ -1081,11 +1081,9 @@ npx @piotr-agier/google-drive-mcp auth
|
|
|
1081
1081
|
# 2. Verify tokens exist
|
|
1082
1082
|
ls -la ~/.config/google-drive-mcp/tokens.json
|
|
1083
1083
|
|
|
1084
|
-
# 3.
|
|
1085
|
-
docker
|
|
1086
|
-
|
|
1087
|
-
-v "$HOME/.config/google-drive-mcp/tokens.json":/config/tokens.json \
|
|
1088
|
-
google-drive-mcp
|
|
1084
|
+
# 3. Rebuild the image and restart the client
|
|
1085
|
+
docker build -t google-drive-mcp .
|
|
1086
|
+
# The client will invoke scripts/docker-mcp.sh, which auto-replaces the stale container
|
|
1089
1087
|
```
|
|
1090
1088
|
|
|
1091
1089
|
#### "npm ci failed" during Docker build
|
package/dist/index.js
CHANGED
|
@@ -892,6 +892,7 @@ async function downloadDriveFile(drive, args, log2) {
|
|
|
892
892
|
|
|
893
893
|
// src/tools/drive.ts
|
|
894
894
|
var FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
|
|
895
|
+
var SHORTCUT_MIME_TYPE = "application/vnd.google-apps.shortcut";
|
|
895
896
|
var BINARY_MIME_TYPES = {
|
|
896
897
|
jpg: "image/jpeg",
|
|
897
898
|
jpeg: "image/jpeg",
|
|
@@ -976,6 +977,19 @@ var CopyFileSchema = z.object({
|
|
|
976
977
|
newName: z.string().optional(),
|
|
977
978
|
parentFolderId: z.string().optional()
|
|
978
979
|
});
|
|
980
|
+
var CreateShortcutSchema = z.object({
|
|
981
|
+
targetFileId: z.string().min(1, "Target file ID is required"),
|
|
982
|
+
parentFolderId: z.string().optional(),
|
|
983
|
+
shortcutName: z.string().optional()
|
|
984
|
+
});
|
|
985
|
+
var LockFileSchema = z.object({
|
|
986
|
+
fileId: z.string().min(1, "File ID is required"),
|
|
987
|
+
reason: z.string().optional(),
|
|
988
|
+
ownerRestricted: z.boolean().optional()
|
|
989
|
+
});
|
|
990
|
+
var UnlockFileSchema = z.object({
|
|
991
|
+
fileId: z.string().min(1, "File ID is required")
|
|
992
|
+
});
|
|
979
993
|
var UploadFileSchema = z.object({
|
|
980
994
|
localPath: z.string().min(1, "Local file path is required"),
|
|
981
995
|
name: z.string().optional(),
|
|
@@ -1200,7 +1214,7 @@ var toolDefinitions = [
|
|
|
1200
1214
|
properties: {
|
|
1201
1215
|
fileId: { type: "string", description: "ID of the file to copy" },
|
|
1202
1216
|
newName: { type: "string", description: "Name for the copied file. If not provided, will use 'Copy of [original name]'" },
|
|
1203
|
-
parentFolderId: { type: "string", description: "ID
|
|
1217
|
+
parentFolderId: { type: "string", description: "ID or path of the destination folder (defaults to same folder as original)" }
|
|
1204
1218
|
},
|
|
1205
1219
|
required: ["fileId"]
|
|
1206
1220
|
}
|
|
@@ -1392,6 +1406,64 @@ var toolDefinitions = [
|
|
|
1392
1406
|
fileId: { type: "string", description: "Optional file ID for targeted access check" }
|
|
1393
1407
|
}
|
|
1394
1408
|
}
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
name: "createShortcut",
|
|
1412
|
+
description: "Create a shortcut (link) to a file or folder in Google Drive. Useful for referencing the same document from multiple locations without duplicating it.",
|
|
1413
|
+
inputSchema: {
|
|
1414
|
+
type: "object",
|
|
1415
|
+
properties: {
|
|
1416
|
+
targetFileId: {
|
|
1417
|
+
type: "string",
|
|
1418
|
+
description: "The file or folder ID (not a path) to create a shortcut to"
|
|
1419
|
+
},
|
|
1420
|
+
parentFolderId: {
|
|
1421
|
+
type: "string",
|
|
1422
|
+
description: "ID or path of the folder where the shortcut will be created"
|
|
1423
|
+
},
|
|
1424
|
+
shortcutName: {
|
|
1425
|
+
type: "string",
|
|
1426
|
+
description: "Custom name for the shortcut (defaults to original file name)"
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
required: ["targetFileId"]
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
name: "lockFile",
|
|
1434
|
+
description: "Lock a file to prevent editing by setting content restrictions. The file remains readable but cannot be modified until unlocked.",
|
|
1435
|
+
inputSchema: {
|
|
1436
|
+
type: "object",
|
|
1437
|
+
properties: {
|
|
1438
|
+
fileId: {
|
|
1439
|
+
type: "string",
|
|
1440
|
+
description: "ID of the file to lock"
|
|
1441
|
+
},
|
|
1442
|
+
reason: {
|
|
1443
|
+
type: "string",
|
|
1444
|
+
description: "Reason for locking the file (shown to users who try to edit)"
|
|
1445
|
+
},
|
|
1446
|
+
ownerRestricted: {
|
|
1447
|
+
type: "boolean",
|
|
1448
|
+
description: "If true, only the file owner can unlock the file (default: false)"
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
required: ["fileId"]
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
name: "unlockFile",
|
|
1456
|
+
description: "Unlock a previously locked file by removing content restrictions, restoring full edit access.",
|
|
1457
|
+
inputSchema: {
|
|
1458
|
+
type: "object",
|
|
1459
|
+
properties: {
|
|
1460
|
+
fileId: {
|
|
1461
|
+
type: "string",
|
|
1462
|
+
description: "ID of the file to unlock"
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
required: ["fileId"]
|
|
1466
|
+
}
|
|
1395
1467
|
}
|
|
1396
1468
|
];
|
|
1397
1469
|
async function handleTool(toolName, args, ctx) {
|
|
@@ -1729,7 +1801,8 @@ More results available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
1729
1801
|
name: data.newName || `Copy of ${originalFile.data.name}`
|
|
1730
1802
|
};
|
|
1731
1803
|
if (data.parentFolderId) {
|
|
1732
|
-
|
|
1804
|
+
const resolvedParentId = await ctx.resolveFolderId(data.parentFolderId);
|
|
1805
|
+
copyMetadata.parents = [resolvedParentId];
|
|
1733
1806
|
} else if (originalFile.data.parents) {
|
|
1734
1807
|
copyMetadata.parents = originalFile.data.parents;
|
|
1735
1808
|
}
|
|
@@ -1746,6 +1819,136 @@ Link: ${response.data.webViewLink}` }],
|
|
|
1746
1819
|
isError: false
|
|
1747
1820
|
};
|
|
1748
1821
|
}
|
|
1822
|
+
case "createShortcut": {
|
|
1823
|
+
const validation = CreateShortcutSchema.safeParse(args);
|
|
1824
|
+
if (!validation.success) {
|
|
1825
|
+
return errorResponse(validation.error.errors[0].message);
|
|
1826
|
+
}
|
|
1827
|
+
const data = validation.data;
|
|
1828
|
+
const parentId = await ctx.resolveFolderId(data.parentFolderId);
|
|
1829
|
+
const targetFile = await ctx.getDrive().files.get({
|
|
1830
|
+
fileId: data.targetFileId,
|
|
1831
|
+
fields: "id, name, mimeType",
|
|
1832
|
+
supportsAllDrives: true
|
|
1833
|
+
});
|
|
1834
|
+
const shortcutName = data.shortcutName || targetFile.data.name || "Shortcut";
|
|
1835
|
+
const shortcut = await ctx.getDrive().files.create({
|
|
1836
|
+
requestBody: {
|
|
1837
|
+
name: shortcutName,
|
|
1838
|
+
mimeType: SHORTCUT_MIME_TYPE,
|
|
1839
|
+
shortcutDetails: {
|
|
1840
|
+
targetId: data.targetFileId
|
|
1841
|
+
},
|
|
1842
|
+
parents: [parentId]
|
|
1843
|
+
},
|
|
1844
|
+
fields: "id, name, webViewLink, shortcutDetails",
|
|
1845
|
+
supportsAllDrives: true
|
|
1846
|
+
});
|
|
1847
|
+
ctx.log("Shortcut created", {
|
|
1848
|
+
shortcutId: shortcut.data.id,
|
|
1849
|
+
targetId: data.targetFileId,
|
|
1850
|
+
name: shortcutName
|
|
1851
|
+
});
|
|
1852
|
+
return {
|
|
1853
|
+
content: [{
|
|
1854
|
+
type: "text",
|
|
1855
|
+
text: `Shortcut created successfully!
|
|
1856
|
+
|
|
1857
|
+
Shortcut: ${shortcut.data.name} (${shortcut.data.id})
|
|
1858
|
+
Target: ${targetFile.data.name} (${data.targetFileId})
|
|
1859
|
+
Location: folder ${parentId}
|
|
1860
|
+
Link: ${shortcut.data.webViewLink || "N/A"}`
|
|
1861
|
+
}],
|
|
1862
|
+
isError: false
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
case "lockFile": {
|
|
1866
|
+
const validation = LockFileSchema.safeParse(args);
|
|
1867
|
+
if (!validation.success) {
|
|
1868
|
+
return errorResponse(validation.error.errors[0].message);
|
|
1869
|
+
}
|
|
1870
|
+
const data = validation.data;
|
|
1871
|
+
const fileInfo = await ctx.getDrive().files.get({
|
|
1872
|
+
fileId: data.fileId,
|
|
1873
|
+
fields: "id, name, contentRestrictions",
|
|
1874
|
+
supportsAllDrives: true
|
|
1875
|
+
});
|
|
1876
|
+
const existingRestrictions = fileInfo.data.contentRestrictions || [];
|
|
1877
|
+
if (existingRestrictions.some((r) => r.readOnly)) {
|
|
1878
|
+
return {
|
|
1879
|
+
content: [{
|
|
1880
|
+
type: "text",
|
|
1881
|
+
text: `File "${fileInfo.data.name}" is already locked.`
|
|
1882
|
+
}],
|
|
1883
|
+
isError: false
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
await ctx.getDrive().files.update({
|
|
1887
|
+
fileId: data.fileId,
|
|
1888
|
+
requestBody: {
|
|
1889
|
+
contentRestrictions: [{
|
|
1890
|
+
readOnly: true,
|
|
1891
|
+
reason: data.reason || "Locked via MCP",
|
|
1892
|
+
ownerRestricted: data.ownerRestricted ?? false
|
|
1893
|
+
}]
|
|
1894
|
+
},
|
|
1895
|
+
supportsAllDrives: true
|
|
1896
|
+
});
|
|
1897
|
+
ctx.log("File locked", { fileId: data.fileId, name: fileInfo.data.name, reason: data.reason });
|
|
1898
|
+
return {
|
|
1899
|
+
content: [{
|
|
1900
|
+
type: "text",
|
|
1901
|
+
text: `File locked successfully!
|
|
1902
|
+
|
|
1903
|
+
File: ${fileInfo.data.name}
|
|
1904
|
+
Reason: ${data.reason || "Locked via MCP"}${data.ownerRestricted ? "\nOwner-restricted: only the file owner can unlock" : ""}
|
|
1905
|
+
|
|
1906
|
+
The file is now read-only and cannot be edited or deleted.`
|
|
1907
|
+
}],
|
|
1908
|
+
isError: false
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
case "unlockFile": {
|
|
1912
|
+
const validation = UnlockFileSchema.safeParse(args);
|
|
1913
|
+
if (!validation.success) {
|
|
1914
|
+
return errorResponse(validation.error.errors[0].message);
|
|
1915
|
+
}
|
|
1916
|
+
const data = validation.data;
|
|
1917
|
+
const fileInfo = await ctx.getDrive().files.get({
|
|
1918
|
+
fileId: data.fileId,
|
|
1919
|
+
fields: "id, name, contentRestrictions",
|
|
1920
|
+
supportsAllDrives: true
|
|
1921
|
+
});
|
|
1922
|
+
const existingRestrictions = fileInfo.data.contentRestrictions || [];
|
|
1923
|
+
if (!existingRestrictions.some((r) => r.readOnly)) {
|
|
1924
|
+
return {
|
|
1925
|
+
content: [{
|
|
1926
|
+
type: "text",
|
|
1927
|
+
text: `File "${fileInfo.data.name}" is not locked.`
|
|
1928
|
+
}],
|
|
1929
|
+
isError: false
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
await ctx.getDrive().files.update({
|
|
1933
|
+
fileId: data.fileId,
|
|
1934
|
+
requestBody: {
|
|
1935
|
+
contentRestrictions: [{ readOnly: false }]
|
|
1936
|
+
},
|
|
1937
|
+
supportsAllDrives: true
|
|
1938
|
+
});
|
|
1939
|
+
ctx.log("File unlocked", { fileId: data.fileId, name: fileInfo.data.name });
|
|
1940
|
+
return {
|
|
1941
|
+
content: [{
|
|
1942
|
+
type: "text",
|
|
1943
|
+
text: `File unlocked successfully!
|
|
1944
|
+
|
|
1945
|
+
File: ${fileInfo.data.name}
|
|
1946
|
+
|
|
1947
|
+
The file can now be edited and deleted.`
|
|
1948
|
+
}],
|
|
1949
|
+
isError: false
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1749
1952
|
case "uploadFile": {
|
|
1750
1953
|
const validation = UploadFileSchema.safeParse(args);
|
|
1751
1954
|
if (!validation.success) {
|
|
@@ -3046,6 +3249,14 @@ var InsertSmartChipSchema = z2.object({
|
|
|
3046
3249
|
var ReadSmartChipsSchema = z2.object({
|
|
3047
3250
|
documentId: z2.string().min(1, "Document ID is required")
|
|
3048
3251
|
});
|
|
3252
|
+
var CreateFootnoteSchema = z2.object({
|
|
3253
|
+
documentId: z2.string().min(1, "Document ID is required"),
|
|
3254
|
+
index: z2.number().int().min(1, "Index must be at least 1").optional(),
|
|
3255
|
+
endOfSegment: z2.boolean().optional(),
|
|
3256
|
+
content: z2.string().optional()
|
|
3257
|
+
}).refine((data) => data.index !== void 0 || data.endOfSegment === true, {
|
|
3258
|
+
message: "Either 'index' or 'endOfSegment: true' must be provided"
|
|
3259
|
+
});
|
|
3049
3260
|
var toolDefinitions2 = [
|
|
3050
3261
|
{
|
|
3051
3262
|
name: "createGoogleDoc",
|
|
@@ -3465,6 +3676,20 @@ var toolDefinitions2 = [
|
|
|
3465
3676
|
},
|
|
3466
3677
|
required: ["documentId"]
|
|
3467
3678
|
}
|
|
3679
|
+
},
|
|
3680
|
+
{
|
|
3681
|
+
name: "createFootnote",
|
|
3682
|
+
description: "Create a footnote in a Google Doc. Footnotes cannot be inserted inside equations, headers, footers, or other footnotes.",
|
|
3683
|
+
inputSchema: {
|
|
3684
|
+
type: "object",
|
|
3685
|
+
properties: {
|
|
3686
|
+
documentId: { type: "string", description: "Document ID" },
|
|
3687
|
+
index: { type: "number", description: "1-based character index where the footnote reference should be inserted" },
|
|
3688
|
+
endOfSegment: { type: "boolean", description: "If true, insert footnote at the end of the document body (use instead of index)" },
|
|
3689
|
+
content: { type: "string", description: "Optional text content for the footnote body" }
|
|
3690
|
+
},
|
|
3691
|
+
required: ["documentId"]
|
|
3692
|
+
}
|
|
3468
3693
|
}
|
|
3469
3694
|
];
|
|
3470
3695
|
async function handleTool2(toolName, args, ctx) {
|
|
@@ -3590,37 +3815,106 @@ Link: ${doc.webViewLink}` }],
|
|
|
3590
3815
|
// DOC CONTENT
|
|
3591
3816
|
// =========================================================================
|
|
3592
3817
|
case "getGoogleDocContent": {
|
|
3593
|
-
let
|
|
3818
|
+
let resolveInlineElementText2 = function(el, inlineObjects) {
|
|
3819
|
+
if (el.person?.personProperties) {
|
|
3820
|
+
const p = el.person.personProperties;
|
|
3821
|
+
if (p.name && p.email) return `@${p.name} (${p.email})`;
|
|
3822
|
+
return `@${p.name || p.email || ""}`;
|
|
3823
|
+
}
|
|
3824
|
+
if (el.richLink?.richLinkProperties) {
|
|
3825
|
+
const rl = el.richLink.richLinkProperties;
|
|
3826
|
+
const title = (rl.title || rl.uri || "").replace(/[\[\]]/g, "\\$&");
|
|
3827
|
+
const uri = rl.uri;
|
|
3828
|
+
return title && uri ? `[${title}](${uri})` : title || null;
|
|
3829
|
+
}
|
|
3830
|
+
if (el.inlineObjectElement?.inlineObjectId) {
|
|
3831
|
+
if (inlineObjects) {
|
|
3832
|
+
const obj = inlineObjects[el.inlineObjectElement.inlineObjectId];
|
|
3833
|
+
const desc = obj?.inlineObjectProperties?.embeddedObject?.description || obj?.inlineObjectProperties?.embeddedObject?.title;
|
|
3834
|
+
return desc ? `[image: ${desc}]` : "[image]";
|
|
3835
|
+
}
|
|
3836
|
+
return "[image]";
|
|
3837
|
+
}
|
|
3838
|
+
if (el.footnoteReference) {
|
|
3839
|
+
return `[^${el.footnoteReference.footnoteNumber || ""}]`;
|
|
3840
|
+
}
|
|
3841
|
+
if (el.horizontalRule) {
|
|
3842
|
+
return "---\n";
|
|
3843
|
+
}
|
|
3844
|
+
return null;
|
|
3845
|
+
}, extractSegments2 = function(bodyContent, inlineObjects) {
|
|
3594
3846
|
const segments = [];
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3847
|
+
function getCellText(cellContent) {
|
|
3848
|
+
const before = segments.length;
|
|
3849
|
+
processContent(cellContent);
|
|
3850
|
+
const cellSegs = segments.splice(before);
|
|
3851
|
+
return cellSegs.map((s) => s.text.replace(/\n$/g, "")).join(" ").replace(/\|/g, "\\|").trim();
|
|
3852
|
+
}
|
|
3853
|
+
function processContent(content) {
|
|
3854
|
+
for (const element of content) {
|
|
3855
|
+
if (element.paragraph?.elements) {
|
|
3856
|
+
for (const textElement of element.paragraph.elements) {
|
|
3857
|
+
if (textElement.textRun?.content && textElement.startIndex != null && textElement.endIndex != null) {
|
|
3858
|
+
const seg = {
|
|
3859
|
+
text: textElement.textRun.content,
|
|
3860
|
+
startIndex: textElement.startIndex,
|
|
3861
|
+
endIndex: textElement.endIndex
|
|
3862
|
+
};
|
|
3863
|
+
if (withFormatting) {
|
|
3864
|
+
const ts = textElement.textRun.textStyle;
|
|
3865
|
+
if (ts) {
|
|
3866
|
+
if (ts.weightedFontFamily?.fontFamily) seg.fontFamily = ts.weightedFontFamily.fontFamily;
|
|
3867
|
+
if (ts.fontSize?.magnitude != null) seg.fontSize = ts.fontSize.magnitude;
|
|
3868
|
+
if (ts.bold) seg.bold = true;
|
|
3869
|
+
if (ts.italic) seg.italic = true;
|
|
3870
|
+
if (ts.underline) seg.underline = true;
|
|
3871
|
+
if (ts.strikethrough) seg.strikethrough = true;
|
|
3872
|
+
const fg = rgbColorToHex(ts.foregroundColor);
|
|
3873
|
+
const bg = rgbColorToHex(ts.backgroundColor);
|
|
3874
|
+
if (fg) seg.foregroundColor = fg;
|
|
3875
|
+
if (bg) seg.backgroundColor = bg;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
segments.push(seg);
|
|
3879
|
+
} else {
|
|
3880
|
+
const inlineText = resolveInlineElementText2(textElement, inlineObjects);
|
|
3881
|
+
if (inlineText && textElement.startIndex != null && textElement.endIndex != null) {
|
|
3882
|
+
segments.push({
|
|
3883
|
+
text: inlineText,
|
|
3884
|
+
startIndex: textElement.startIndex,
|
|
3885
|
+
endIndex: textElement.endIndex
|
|
3886
|
+
});
|
|
3617
3887
|
}
|
|
3618
3888
|
}
|
|
3619
|
-
segments.push(seg);
|
|
3620
3889
|
}
|
|
3890
|
+
} else if (element.table?.tableRows) {
|
|
3891
|
+
const rows = [];
|
|
3892
|
+
for (let rowIdx = 0; rowIdx < element.table.tableRows.length; rowIdx++) {
|
|
3893
|
+
const row = element.table.tableRows[rowIdx];
|
|
3894
|
+
if (!row.tableCells) continue;
|
|
3895
|
+
const cellTexts = [];
|
|
3896
|
+
for (const cell of row.tableCells) {
|
|
3897
|
+
cellTexts.push(cell.content ? getCellText(cell.content) : "");
|
|
3898
|
+
}
|
|
3899
|
+
rows.push("| " + cellTexts.join(" | ") + " |");
|
|
3900
|
+
if (rowIdx === 0) {
|
|
3901
|
+
rows.push("| " + cellTexts.map(() => "---").join(" | ") + " |");
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
const md = rows.join("\n") + "\n\n";
|
|
3905
|
+
if (element.startIndex != null && element.endIndex != null) {
|
|
3906
|
+
segments.push({
|
|
3907
|
+
text: md,
|
|
3908
|
+
startIndex: element.startIndex,
|
|
3909
|
+
endIndex: element.endIndex
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
} else if (element.tableOfContents?.content) {
|
|
3913
|
+
processContent(element.tableOfContents.content);
|
|
3621
3914
|
}
|
|
3622
3915
|
}
|
|
3623
3916
|
}
|
|
3917
|
+
processContent(bodyContent);
|
|
3624
3918
|
return segments;
|
|
3625
3919
|
}, formatSegments2 = function(segments) {
|
|
3626
3920
|
let result = "";
|
|
@@ -3677,7 +3971,7 @@ Link: ${doc.webViewLink}` }],
|
|
|
3677
3971
|
}
|
|
3678
3972
|
}
|
|
3679
3973
|
};
|
|
3680
|
-
var extractSegments = extractSegments2, formatSegments = formatSegments2, hasFormattingInfo = hasFormattingInfo2, buildMetaLine = buildMetaLine2, trackFonts = trackFonts2;
|
|
3974
|
+
var resolveInlineElementText = resolveInlineElementText2, extractSegments = extractSegments2, formatSegments = formatSegments2, hasFormattingInfo = hasFormattingInfo2, buildMetaLine = buildMetaLine2, trackFonts = trackFonts2;
|
|
3681
3975
|
const validation = GetGoogleDocContentSchema.safeParse(args);
|
|
3682
3976
|
if (!validation.success) {
|
|
3683
3977
|
return errorResponse(validation.error.errors[0].message);
|
|
@@ -3705,7 +3999,8 @@ Link: ${doc.webViewLink}` }],
|
|
|
3705
3999
|
`;
|
|
3706
4000
|
}
|
|
3707
4001
|
if (bodyContent) {
|
|
3708
|
-
const
|
|
4002
|
+
const tabInlineObjects = tab.documentTab?.inlineObjects;
|
|
4003
|
+
const segments = extractSegments2(bodyContent, tabInlineObjects);
|
|
3709
4004
|
trackFonts2(segments);
|
|
3710
4005
|
formattedContent += formatSegments2(segments);
|
|
3711
4006
|
if (segments.length > 0) {
|
|
@@ -3719,7 +4014,8 @@ Link: ${doc.webViewLink}` }],
|
|
|
3719
4014
|
} else {
|
|
3720
4015
|
const bodyContent = document.data.body?.content;
|
|
3721
4016
|
if (bodyContent) {
|
|
3722
|
-
const
|
|
4017
|
+
const legacyInlineObjects = document.data.inlineObjects;
|
|
4018
|
+
const segments = extractSegments2(bodyContent, legacyInlineObjects);
|
|
3723
4019
|
trackFonts2(segments);
|
|
3724
4020
|
formattedContent += formatSegments2(segments);
|
|
3725
4021
|
totalLength = segments.length > 0 ? segments[segments.length - 1].endIndex : 0;
|
|
@@ -4803,6 +5099,47 @@ Image URL: ${imageUrl}` }],
|
|
|
4803
5099
|
}
|
|
4804
5100
|
return { content: [{ type: "text", text: hits.length ? hits.join("\n") : "No smart chips detected (note: only the default tab is scanned)." }], isError: false };
|
|
4805
5101
|
}
|
|
5102
|
+
case "createFootnote": {
|
|
5103
|
+
const validation = CreateFootnoteSchema.safeParse(args);
|
|
5104
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
5105
|
+
const a = validation.data;
|
|
5106
|
+
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
5107
|
+
const createFootnoteReq = {};
|
|
5108
|
+
if (a.index !== void 0) {
|
|
5109
|
+
createFootnoteReq.location = { index: a.index };
|
|
5110
|
+
} else {
|
|
5111
|
+
createFootnoteReq.endOfSegmentLocation = { segmentId: "" };
|
|
5112
|
+
}
|
|
5113
|
+
const res = await docs.documents.batchUpdate({
|
|
5114
|
+
documentId: a.documentId,
|
|
5115
|
+
requestBody: {
|
|
5116
|
+
requests: [{ createFootnote: createFootnoteReq }]
|
|
5117
|
+
}
|
|
5118
|
+
});
|
|
5119
|
+
const footnoteId = res.data.replies?.[0]?.createFootnote?.footnoteId;
|
|
5120
|
+
if (!footnoteId) {
|
|
5121
|
+
return errorResponse("Failed to create footnote \u2014 no footnoteId in response.");
|
|
5122
|
+
}
|
|
5123
|
+
const locationDesc = a.index !== void 0 ? `at index ${a.index}` : "at end of document";
|
|
5124
|
+
if (a.content) {
|
|
5125
|
+
try {
|
|
5126
|
+
await docs.documents.batchUpdate({
|
|
5127
|
+
documentId: a.documentId,
|
|
5128
|
+
requestBody: {
|
|
5129
|
+
requests: [{
|
|
5130
|
+
insertText: {
|
|
5131
|
+
location: { segmentId: footnoteId, index: 0 },
|
|
5132
|
+
text: a.content
|
|
5133
|
+
}
|
|
5134
|
+
}]
|
|
5135
|
+
}
|
|
5136
|
+
});
|
|
5137
|
+
} catch (err) {
|
|
5138
|
+
return { content: [{ type: "text", text: `Created footnote ${footnoteId} ${locationDesc}, but failed to insert content: ${err.message}` }], isError: true };
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
return { content: [{ type: "text", text: `Created footnote ${footnoteId} ${locationDesc}.${a.content ? " Content inserted." : ""}` }], isError: false };
|
|
5142
|
+
}
|
|
4806
5143
|
default:
|
|
4807
5144
|
return null;
|
|
4808
5145
|
}
|