@piotr-agier/google-drive-mcp 2.0.2 → 2.2.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/README.md +16 -2
- package/dist/index.js +460 -102
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -427,7 +427,14 @@ var AuthServer = class {
|
|
|
427
427
|
this.baseOAuth2Client = oauth2Client;
|
|
428
428
|
this.tokenManager = new TokenManager(oauth2Client);
|
|
429
429
|
this.app = express();
|
|
430
|
-
|
|
430
|
+
const raw = process.env.GOOGLE_DRIVE_MCP_AUTH_PORT;
|
|
431
|
+
const portStart = raw ? Number(raw) : 3e3;
|
|
432
|
+
if (!Number.isInteger(portStart) || portStart < 1 || portStart > 65531) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`Invalid GOOGLE_DRIVE_MCP_AUTH_PORT: "${raw}". Must be an integer between 1 and 65531.`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
this.portRange = { start: portStart, end: portStart + 4 };
|
|
431
438
|
this.setupRoutes();
|
|
432
439
|
}
|
|
433
440
|
setupRoutes() {
|
|
@@ -1085,7 +1092,8 @@ var AddPermissionSchema = z.object({
|
|
|
1085
1092
|
emailAddress: z.string().email("Valid email is required"),
|
|
1086
1093
|
role: z.enum(["owner", "organizer", "fileOrganizer", "writer", "commenter", "reader"]).default("reader"),
|
|
1087
1094
|
type: z.enum(["user", "group", "domain", "anyone"]).default("user"),
|
|
1088
|
-
sendNotificationEmail: z.boolean().optional().default(false)
|
|
1095
|
+
sendNotificationEmail: z.boolean().optional().default(false),
|
|
1096
|
+
emailMessage: z.string().optional()
|
|
1089
1097
|
});
|
|
1090
1098
|
var UpdatePermissionSchema = z.object({
|
|
1091
1099
|
fileId: z.string().min(1, "File ID is required"),
|
|
@@ -1105,7 +1113,8 @@ var ShareFileSchema = z.object({
|
|
|
1105
1113
|
fileId: z.string().min(1, "File ID is required"),
|
|
1106
1114
|
emailAddress: z.string().email("Valid email is required"),
|
|
1107
1115
|
role: z.enum(["writer", "commenter", "reader"]).default("reader"),
|
|
1108
|
-
sendNotificationEmail: z.boolean().optional().default(true)
|
|
1116
|
+
sendNotificationEmail: z.boolean().optional().default(true),
|
|
1117
|
+
emailMessage: z.string().optional()
|
|
1109
1118
|
});
|
|
1110
1119
|
var ConvertPdfToGoogleDocSchema = z.object({
|
|
1111
1120
|
fileId: z.string().min(1, "File ID is required"),
|
|
@@ -1349,7 +1358,8 @@ var toolDefinitions = [
|
|
|
1349
1358
|
emailAddress: { type: "string", description: "Target user/group email" },
|
|
1350
1359
|
role: { type: "string", enum: ["owner", "organizer", "fileOrganizer", "writer", "commenter", "reader"], description: "Permission role" },
|
|
1351
1360
|
type: { type: "string", enum: ["user", "group", "domain", "anyone"], description: "Principal type" },
|
|
1352
|
-
sendNotificationEmail: { type: "boolean", description: "Send notification email" }
|
|
1361
|
+
sendNotificationEmail: { type: "boolean", description: "Send notification email" },
|
|
1362
|
+
emailMessage: { type: "string", description: "Custom message to include in the notification email. Ignored unless sendNotificationEmail is true." }
|
|
1353
1363
|
},
|
|
1354
1364
|
required: ["fileId", "emailAddress"]
|
|
1355
1365
|
}
|
|
@@ -1389,7 +1399,8 @@ var toolDefinitions = [
|
|
|
1389
1399
|
fileId: { type: "string", description: "Google Drive file ID" },
|
|
1390
1400
|
emailAddress: { type: "string", description: "User email" },
|
|
1391
1401
|
role: { type: "string", enum: ["writer", "commenter", "reader"], description: "Access role" },
|
|
1392
|
-
sendNotificationEmail: { type: "boolean", description: "Send notification email" }
|
|
1402
|
+
sendNotificationEmail: { type: "boolean", description: "Send notification email" },
|
|
1403
|
+
emailMessage: { type: "string", description: "Custom message to include in the notification email. Ignored unless sendNotificationEmail is true." }
|
|
1393
1404
|
},
|
|
1394
1405
|
required: ["fileId", "emailAddress"]
|
|
1395
1406
|
}
|
|
@@ -2139,6 +2150,7 @@ ${lines.join("\n")}` }], isError: false };
|
|
|
2139
2150
|
emailAddress: data.emailAddress
|
|
2140
2151
|
},
|
|
2141
2152
|
sendNotificationEmail: data.sendNotificationEmail,
|
|
2153
|
+
...data.emailMessage && { emailMessage: data.emailMessage },
|
|
2142
2154
|
fields: "id,type,role,emailAddress",
|
|
2143
2155
|
supportsAllDrives: true
|
|
2144
2156
|
});
|
|
@@ -2225,6 +2237,7 @@ ${lines.join("\n")}` }], isError: false };
|
|
|
2225
2237
|
emailAddress: data.emailAddress
|
|
2226
2238
|
},
|
|
2227
2239
|
sendNotificationEmail: data.sendNotificationEmail,
|
|
2240
|
+
...data.emailMessage && { emailMessage: data.emailMessage },
|
|
2228
2241
|
fields: "id,type,role,emailAddress",
|
|
2229
2242
|
supportsAllDrives: true
|
|
2230
2243
|
});
|
|
@@ -2555,9 +2568,62 @@ __export(docs_exports, {
|
|
|
2555
2568
|
toolDefinitions: () => toolDefinitions2
|
|
2556
2569
|
});
|
|
2557
2570
|
import { z as z2 } from "zod";
|
|
2558
|
-
import { createReadStream as createReadStream2, existsSync as existsSync3 } from "fs";
|
|
2559
|
-
import { basename as basename3, extname as extname3 } from "path";
|
|
2560
2571
|
import JSZip from "jszip";
|
|
2572
|
+
|
|
2573
|
+
// src/utils/driveImageUpload.ts
|
|
2574
|
+
import { existsSync as existsSync3, createReadStream as createReadStream2 } from "fs";
|
|
2575
|
+
import { basename as basename3, extname as extname3 } from "path";
|
|
2576
|
+
var MIME_BY_EXT = {
|
|
2577
|
+
".jpg": "image/jpeg",
|
|
2578
|
+
".jpeg": "image/jpeg",
|
|
2579
|
+
".png": "image/png",
|
|
2580
|
+
".gif": "image/gif",
|
|
2581
|
+
".bmp": "image/bmp",
|
|
2582
|
+
".webp": "image/webp",
|
|
2583
|
+
".svg": "image/svg+xml"
|
|
2584
|
+
};
|
|
2585
|
+
async function uploadImageToDrive(ctx, localFilePath, options = {}) {
|
|
2586
|
+
const { parentFolderId, makePublic = false } = options;
|
|
2587
|
+
if (!existsSync3(localFilePath)) {
|
|
2588
|
+
throw new Error(`Image file not found: ${localFilePath}`);
|
|
2589
|
+
}
|
|
2590
|
+
const fileName = basename3(localFilePath);
|
|
2591
|
+
const ext = extname3(localFilePath).toLowerCase();
|
|
2592
|
+
const mimeType = MIME_BY_EXT[ext] || "application/octet-stream";
|
|
2593
|
+
const requestBody = {
|
|
2594
|
+
name: fileName,
|
|
2595
|
+
mimeType
|
|
2596
|
+
};
|
|
2597
|
+
if (parentFolderId) requestBody.parents = [parentFolderId];
|
|
2598
|
+
const drive = ctx.getDrive();
|
|
2599
|
+
const uploadResponse = await drive.files.create({
|
|
2600
|
+
requestBody,
|
|
2601
|
+
media: { mimeType, body: createReadStream2(localFilePath) },
|
|
2602
|
+
fields: "id,webViewLink,webContentLink",
|
|
2603
|
+
supportsAllDrives: true
|
|
2604
|
+
});
|
|
2605
|
+
const fileId = uploadResponse.data.id;
|
|
2606
|
+
if (!fileId) throw new Error("Failed to upload image to Drive - no file ID returned");
|
|
2607
|
+
if (makePublic) {
|
|
2608
|
+
await drive.permissions.create({
|
|
2609
|
+
fileId,
|
|
2610
|
+
requestBody: { role: "reader", type: "anyone" }
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
const fileInfo = await drive.files.get({
|
|
2614
|
+
fileId,
|
|
2615
|
+
fields: "webContentLink",
|
|
2616
|
+
supportsAllDrives: true
|
|
2617
|
+
});
|
|
2618
|
+
const webContentLink = fileInfo.data.webContentLink;
|
|
2619
|
+
if (!webContentLink) throw new Error("Failed to get web content link for uploaded image");
|
|
2620
|
+
return { fileId, webContentLink };
|
|
2621
|
+
}
|
|
2622
|
+
async function deleteDriveFile(ctx, fileId) {
|
|
2623
|
+
await ctx.getDrive().files.delete({ fileId, supportsAllDrives: true });
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/tools/docs.ts
|
|
2561
2627
|
function hexToRgbColor(hex) {
|
|
2562
2628
|
if (!hex) return null;
|
|
2563
2629
|
let hexClean = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
@@ -2853,64 +2919,6 @@ async function insertInlineImageHelper(ctx, documentId, imageUrl, index, width,
|
|
|
2853
2919
|
}
|
|
2854
2920
|
return executeBatchUpdate(ctx, documentId, [request]);
|
|
2855
2921
|
}
|
|
2856
|
-
async function uploadImageToDriveHelper(ctx, localFilePath, parentFolderId, makePublic = false) {
|
|
2857
|
-
if (!existsSync3(localFilePath)) {
|
|
2858
|
-
throw new Error(`Image file not found: ${localFilePath}`);
|
|
2859
|
-
}
|
|
2860
|
-
const fileName = basename3(localFilePath);
|
|
2861
|
-
const mimeTypeMap = {
|
|
2862
|
-
".jpg": "image/jpeg",
|
|
2863
|
-
".jpeg": "image/jpeg",
|
|
2864
|
-
".png": "image/png",
|
|
2865
|
-
".gif": "image/gif",
|
|
2866
|
-
".bmp": "image/bmp",
|
|
2867
|
-
".webp": "image/webp",
|
|
2868
|
-
".svg": "image/svg+xml"
|
|
2869
|
-
};
|
|
2870
|
-
const ext = extname3(localFilePath).toLowerCase();
|
|
2871
|
-
const mimeType = mimeTypeMap[ext] || "application/octet-stream";
|
|
2872
|
-
const fileMetadata = {
|
|
2873
|
-
name: fileName,
|
|
2874
|
-
mimeType
|
|
2875
|
-
};
|
|
2876
|
-
if (parentFolderId) {
|
|
2877
|
-
fileMetadata.parents = [parentFolderId];
|
|
2878
|
-
}
|
|
2879
|
-
const media = {
|
|
2880
|
-
mimeType,
|
|
2881
|
-
body: createReadStream2(localFilePath)
|
|
2882
|
-
};
|
|
2883
|
-
const drive = ctx.getDrive();
|
|
2884
|
-
const uploadResponse = await drive.files.create({
|
|
2885
|
-
requestBody: fileMetadata,
|
|
2886
|
-
media,
|
|
2887
|
-
fields: "id,webViewLink,webContentLink",
|
|
2888
|
-
supportsAllDrives: true
|
|
2889
|
-
});
|
|
2890
|
-
const fileId = uploadResponse.data.id;
|
|
2891
|
-
if (!fileId) {
|
|
2892
|
-
throw new Error("Failed to upload image to Drive - no file ID returned");
|
|
2893
|
-
}
|
|
2894
|
-
if (makePublic) {
|
|
2895
|
-
await drive.permissions.create({
|
|
2896
|
-
fileId,
|
|
2897
|
-
requestBody: {
|
|
2898
|
-
role: "reader",
|
|
2899
|
-
type: "anyone"
|
|
2900
|
-
}
|
|
2901
|
-
});
|
|
2902
|
-
}
|
|
2903
|
-
const fileInfo = await drive.files.get({
|
|
2904
|
-
fileId,
|
|
2905
|
-
fields: "webContentLink",
|
|
2906
|
-
supportsAllDrives: true
|
|
2907
|
-
});
|
|
2908
|
-
const webContentLink = fileInfo.data.webContentLink;
|
|
2909
|
-
if (!webContentLink) {
|
|
2910
|
-
throw new Error("Failed to get web content link for uploaded image");
|
|
2911
|
-
}
|
|
2912
|
-
return webContentLink;
|
|
2913
|
-
}
|
|
2914
2922
|
var MAX_ROW_XML_DISTANCE = 1e5;
|
|
2915
2923
|
var MAX_PARAGRAPH_XML_DISTANCE = 5e4;
|
|
2916
2924
|
var MAX_PARAGRAPH_CONTEXT_LENGTH = 300;
|
|
@@ -3148,7 +3156,8 @@ var CreateGoogleDocSchema = z2.object({
|
|
|
3148
3156
|
});
|
|
3149
3157
|
var UpdateGoogleDocSchema = z2.object({
|
|
3150
3158
|
documentId: z2.string().min(1, "Document ID is required"),
|
|
3151
|
-
content: z2.string()
|
|
3159
|
+
content: z2.string(),
|
|
3160
|
+
tabId: z2.string().optional()
|
|
3152
3161
|
});
|
|
3153
3162
|
var GetGoogleDocContentSchema = z2.object({
|
|
3154
3163
|
documentId: z2.string().min(1, "Document ID is required"),
|
|
@@ -3157,12 +3166,14 @@ var GetGoogleDocContentSchema = z2.object({
|
|
|
3157
3166
|
var InsertTextSchema = z2.object({
|
|
3158
3167
|
documentId: z2.string().min(1, "Document ID is required"),
|
|
3159
3168
|
text: z2.string().min(1, "Text to insert is required"),
|
|
3160
|
-
index: z2.number().int().min(1, "Index must be at least 1 (1-based)")
|
|
3169
|
+
index: z2.number().int().min(1, "Index must be at least 1 (1-based)"),
|
|
3170
|
+
tabId: z2.string().optional()
|
|
3161
3171
|
});
|
|
3162
3172
|
var DeleteRangeSchema = z2.object({
|
|
3163
3173
|
documentId: z2.string().min(1, "Document ID is required"),
|
|
3164
3174
|
startIndex: z2.number().int().min(1, "Start index must be at least 1"),
|
|
3165
|
-
endIndex: z2.number().int().min(1, "End index must be at least 1")
|
|
3175
|
+
endIndex: z2.number().int().min(1, "End index must be at least 1"),
|
|
3176
|
+
tabId: z2.string().optional()
|
|
3166
3177
|
}).refine((data) => data.endIndex > data.startIndex, {
|
|
3167
3178
|
message: "End index must be greater than start index",
|
|
3168
3179
|
path: ["endIndex"]
|
|
@@ -3303,7 +3314,8 @@ var FindAndReplaceInDocSchema = z2.object({
|
|
|
3303
3314
|
findText: z2.string().min(1, "findText is required"),
|
|
3304
3315
|
replaceText: z2.string(),
|
|
3305
3316
|
matchCase: z2.boolean().optional().default(false),
|
|
3306
|
-
dryRun: z2.boolean().optional().default(false)
|
|
3317
|
+
dryRun: z2.boolean().optional().default(false),
|
|
3318
|
+
tabId: z2.string().optional()
|
|
3307
3319
|
});
|
|
3308
3320
|
var AddDocumentTabSchema = z2.object({
|
|
3309
3321
|
documentId: z2.string().min(1, "Document ID is required"),
|
|
@@ -3347,38 +3359,41 @@ var toolDefinitions2 = [
|
|
|
3347
3359
|
},
|
|
3348
3360
|
{
|
|
3349
3361
|
name: "updateGoogleDoc",
|
|
3350
|
-
description: "Update an existing Google Doc",
|
|
3362
|
+
description: "Update an existing Google Doc (replaces all content). For multi-tab docs, specify tabId to replace a single tab's content atomically; leaves other tabs untouched.",
|
|
3351
3363
|
inputSchema: {
|
|
3352
3364
|
type: "object",
|
|
3353
3365
|
properties: {
|
|
3354
3366
|
documentId: { type: "string", description: "Doc ID" },
|
|
3355
|
-
content: { type: "string", description: "New content" }
|
|
3367
|
+
content: { type: "string", description: "New content" },
|
|
3368
|
+
tabId: { type: "string", description: "Optional. Tab ID to replace (from listDocumentTabs). If set, delete+insert run in a single atomic batchUpdate scoped to that tab." }
|
|
3356
3369
|
},
|
|
3357
3370
|
required: ["documentId", "content"]
|
|
3358
3371
|
}
|
|
3359
3372
|
},
|
|
3360
3373
|
{
|
|
3361
3374
|
name: "insertText",
|
|
3362
|
-
description: "Insert text at a specific index in a Google Doc (surgical edit, doesn't replace entire doc)",
|
|
3375
|
+
description: "Insert text at a specific index in a Google Doc (surgical edit, doesn't replace entire doc). For multi-tab docs, specify tabId to target a specific tab.",
|
|
3363
3376
|
inputSchema: {
|
|
3364
3377
|
type: "object",
|
|
3365
3378
|
properties: {
|
|
3366
3379
|
documentId: { type: "string", description: "The document ID" },
|
|
3367
3380
|
text: { type: "string", description: "Text to insert" },
|
|
3368
|
-
index: { type: "number", description: "Position to insert at (1-based)" }
|
|
3381
|
+
index: { type: "number", description: "Position to insert at (1-based)" },
|
|
3382
|
+
tabId: { type: "string", description: "Optional. Tab ID to insert into (from listDocumentTabs). If omitted, inserts into the first/default tab." }
|
|
3369
3383
|
},
|
|
3370
3384
|
required: ["documentId", "text", "index"]
|
|
3371
3385
|
}
|
|
3372
3386
|
},
|
|
3373
3387
|
{
|
|
3374
3388
|
name: "deleteRange",
|
|
3375
|
-
description: "Delete content between start and end indices in a Google Doc",
|
|
3389
|
+
description: "Delete content between start and end indices in a Google Doc. For multi-tab docs, specify tabId to target a specific tab.",
|
|
3376
3390
|
inputSchema: {
|
|
3377
3391
|
type: "object",
|
|
3378
3392
|
properties: {
|
|
3379
3393
|
documentId: { type: "string", description: "The document ID" },
|
|
3380
3394
|
startIndex: { type: "number", description: "Start index (1-based, inclusive)" },
|
|
3381
|
-
endIndex: { type: "number", description: "End index (exclusive)" }
|
|
3395
|
+
endIndex: { type: "number", description: "End index (exclusive)" },
|
|
3396
|
+
tabId: { type: "string", description: "Optional. Tab ID to delete from (from listDocumentTabs). If omitted, deletes from the first/default tab." }
|
|
3382
3397
|
},
|
|
3383
3398
|
required: ["documentId", "startIndex", "endIndex"]
|
|
3384
3399
|
}
|
|
@@ -3521,7 +3536,7 @@ var toolDefinitions2 = [
|
|
|
3521
3536
|
},
|
|
3522
3537
|
{
|
|
3523
3538
|
name: "findAndReplaceInDoc",
|
|
3524
|
-
description: "Find and replace text across a Google Document. Dry-run mode counts matches from paragraph text only (may differ from actual replacements which cover tables, headers, footers, etc.)",
|
|
3539
|
+
description: "Find and replace text across a Google Document. Dry-run mode counts matches from paragraph text only (may differ from actual replacements which cover tables, headers, footers, etc.). For multi-tab docs, specify tabId to scope replacements to a single tab.",
|
|
3525
3540
|
inputSchema: {
|
|
3526
3541
|
type: "object",
|
|
3527
3542
|
properties: {
|
|
@@ -3529,7 +3544,8 @@ var toolDefinitions2 = [
|
|
|
3529
3544
|
findText: { type: "string", description: "Text to find" },
|
|
3530
3545
|
replaceText: { type: "string", description: "Replacement text" },
|
|
3531
3546
|
matchCase: { type: "boolean", description: "Case-sensitive match (default: false)" },
|
|
3532
|
-
dryRun: { type: "boolean", description: "Only count approximate matches from paragraph text, do not modify document (default: false)" }
|
|
3547
|
+
dryRun: { type: "boolean", description: "Only count approximate matches from paragraph text, do not modify document (default: false). Ignores tabId \u2014 always scans the full document body." },
|
|
3548
|
+
tabId: { type: "string", description: "Optional. Tab ID to scope replacements to (from listDocumentTabs). If omitted, replaces across all tabs." }
|
|
3533
3549
|
},
|
|
3534
3550
|
required: ["documentId", "findText", "replaceText"]
|
|
3535
3551
|
}
|
|
@@ -3843,6 +3859,43 @@ Link: ${doc.webViewLink}` }],
|
|
|
3843
3859
|
}
|
|
3844
3860
|
const a = validation.data;
|
|
3845
3861
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
3862
|
+
if (a.tabId) {
|
|
3863
|
+
const document2 = await docs.documents.get({ documentId: a.documentId, includeTabsContent: true });
|
|
3864
|
+
const tabs = document2.data.tabs;
|
|
3865
|
+
const tab = tabs ? findTabById(tabs, a.tabId) : null;
|
|
3866
|
+
if (!tab) {
|
|
3867
|
+
return errorResponse(`Tab with ID "${a.tabId}" not found. Use listDocumentTabs to see available tabs.`);
|
|
3868
|
+
}
|
|
3869
|
+
const bodyContent = tab.documentTab?.body?.content;
|
|
3870
|
+
const lastEndIndex = bodyContent?.[bodyContent.length - 1]?.endIndex ?? 1;
|
|
3871
|
+
const deleteEndIndex2 = Math.max(1, lastEndIndex - 1);
|
|
3872
|
+
const requests = [];
|
|
3873
|
+
if (deleteEndIndex2 > 1) {
|
|
3874
|
+
requests.push({
|
|
3875
|
+
deleteContentRange: {
|
|
3876
|
+
range: { startIndex: 1, endIndex: deleteEndIndex2, tabId: a.tabId }
|
|
3877
|
+
}
|
|
3878
|
+
});
|
|
3879
|
+
}
|
|
3880
|
+
requests.push({
|
|
3881
|
+
insertText: { location: { index: 1, tabId: a.tabId }, text: a.content }
|
|
3882
|
+
});
|
|
3883
|
+
requests.push({
|
|
3884
|
+
updateParagraphStyle: {
|
|
3885
|
+
range: { startIndex: 1, endIndex: a.content.length + 1, tabId: a.tabId },
|
|
3886
|
+
paragraphStyle: { namedStyleType: "NORMAL_TEXT" },
|
|
3887
|
+
fields: "namedStyleType"
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
3890
|
+
await docs.documents.batchUpdate({
|
|
3891
|
+
documentId: a.documentId,
|
|
3892
|
+
requestBody: { requests }
|
|
3893
|
+
});
|
|
3894
|
+
return {
|
|
3895
|
+
content: [{ type: "text", text: `Updated Google Doc: ${document2.data.title} (tab: ${a.tabId})` }],
|
|
3896
|
+
isError: false
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3846
3899
|
const document = await docs.documents.get({ documentId: a.documentId });
|
|
3847
3900
|
const endIndex = document.data.body?.content?.[document.data.body.content.length - 1]?.endIndex || 1;
|
|
3848
3901
|
const deleteEndIndex = Math.max(1, endIndex - 1);
|
|
@@ -4123,20 +4176,22 @@ Total length: ${totalLength} characters`
|
|
|
4123
4176
|
return errorResponse(validation.error.errors[0].message);
|
|
4124
4177
|
}
|
|
4125
4178
|
const a = validation.data;
|
|
4179
|
+
const location = { index: a.index };
|
|
4180
|
+
if (a.tabId) location.tabId = a.tabId;
|
|
4126
4181
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
4127
4182
|
await docs.documents.batchUpdate({
|
|
4128
4183
|
documentId: a.documentId,
|
|
4129
4184
|
requestBody: {
|
|
4130
4185
|
requests: [{
|
|
4131
4186
|
insertText: {
|
|
4132
|
-
location
|
|
4187
|
+
location,
|
|
4133
4188
|
text: a.text
|
|
4134
4189
|
}
|
|
4135
4190
|
}]
|
|
4136
4191
|
}
|
|
4137
4192
|
});
|
|
4138
4193
|
return {
|
|
4139
|
-
content: [{ type: "text", text: `Successfully inserted ${a.text.length} characters at index ${a.index}` }],
|
|
4194
|
+
content: [{ type: "text", text: `Successfully inserted ${a.text.length} characters at index ${a.index}${a.tabId ? ` in tab ${a.tabId}` : ""}` }],
|
|
4140
4195
|
isError: false
|
|
4141
4196
|
};
|
|
4142
4197
|
}
|
|
@@ -4149,22 +4204,22 @@ Total length: ${totalLength} characters`
|
|
|
4149
4204
|
if (a.endIndex <= a.startIndex) {
|
|
4150
4205
|
return errorResponse("endIndex must be greater than startIndex");
|
|
4151
4206
|
}
|
|
4207
|
+
const range = {
|
|
4208
|
+
startIndex: a.startIndex,
|
|
4209
|
+
endIndex: a.endIndex
|
|
4210
|
+
};
|
|
4211
|
+
if (a.tabId) range.tabId = a.tabId;
|
|
4152
4212
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
4153
4213
|
await docs.documents.batchUpdate({
|
|
4154
4214
|
documentId: a.documentId,
|
|
4155
4215
|
requestBody: {
|
|
4156
4216
|
requests: [{
|
|
4157
|
-
deleteContentRange: {
|
|
4158
|
-
range: {
|
|
4159
|
-
startIndex: a.startIndex,
|
|
4160
|
-
endIndex: a.endIndex
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4217
|
+
deleteContentRange: { range }
|
|
4163
4218
|
}]
|
|
4164
4219
|
}
|
|
4165
4220
|
});
|
|
4166
4221
|
return {
|
|
4167
|
-
content: [{ type: "text", text: `Successfully deleted content from index ${a.startIndex} to ${a.endIndex}` }],
|
|
4222
|
+
content: [{ type: "text", text: `Successfully deleted content from index ${a.startIndex} to ${a.endIndex}${a.tabId ? ` in tab ${a.tabId}` : ""}` }],
|
|
4168
4223
|
isError: false
|
|
4169
4224
|
};
|
|
4170
4225
|
}
|
|
@@ -4546,25 +4601,20 @@ ${text}`;
|
|
|
4546
4601
|
isError: false
|
|
4547
4602
|
};
|
|
4548
4603
|
}
|
|
4604
|
+
const replaceAllText = {
|
|
4605
|
+
containsText: { text: a.findText, matchCase: a.matchCase },
|
|
4606
|
+
replaceText: a.replaceText
|
|
4607
|
+
};
|
|
4608
|
+
if (a.tabId) replaceAllText.tabsCriteria = { tabIds: [a.tabId] };
|
|
4549
4609
|
const response = await docs.documents.batchUpdate({
|
|
4550
4610
|
documentId: a.documentId,
|
|
4551
4611
|
requestBody: {
|
|
4552
|
-
requests: [
|
|
4553
|
-
{
|
|
4554
|
-
replaceAllText: {
|
|
4555
|
-
containsText: {
|
|
4556
|
-
text: a.findText,
|
|
4557
|
-
matchCase: a.matchCase
|
|
4558
|
-
},
|
|
4559
|
-
replaceText: a.replaceText
|
|
4560
|
-
}
|
|
4561
|
-
}
|
|
4562
|
-
]
|
|
4612
|
+
requests: [{ replaceAllText }]
|
|
4563
4613
|
}
|
|
4564
4614
|
});
|
|
4565
4615
|
const occurrences = response.data.replies?.[0]?.replaceAllText?.occurrencesChanged ?? 0;
|
|
4566
4616
|
return {
|
|
4567
|
-
content: [{ type: "text", text: `Replaced ${occurrences} occurrence(s) of "${a.findText}".` }],
|
|
4617
|
+
content: [{ type: "text", text: `Replaced ${occurrences} occurrence(s) of "${a.findText}"${a.tabId ? ` in tab ${a.tabId}` : ""}.` }],
|
|
4568
4618
|
isError: false
|
|
4569
4619
|
};
|
|
4570
4620
|
}
|
|
@@ -5008,7 +5058,10 @@ ${index + 1}. ${replyAuthor} (${replyDate})
|
|
|
5008
5058
|
});
|
|
5009
5059
|
parentFolderId = fileInfo.data.parents?.[0];
|
|
5010
5060
|
}
|
|
5011
|
-
const imageUrl = await
|
|
5061
|
+
const { webContentLink: imageUrl } = await uploadImageToDrive(ctx, a.localImagePath, {
|
|
5062
|
+
parentFolderId,
|
|
5063
|
+
makePublic: a.makePublic
|
|
5064
|
+
});
|
|
5012
5065
|
await insertInlineImageHelper(ctx, a.documentId, imageUrl, a.index, a.width, a.height);
|
|
5013
5066
|
return {
|
|
5014
5067
|
content: [{ type: "text", text: `Successfully uploaded and inserted local image at index ${a.index}
|
|
@@ -5133,8 +5186,10 @@ Image URL: ${imageUrl}` }],
|
|
|
5133
5186
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
5134
5187
|
await docs.documents.batchUpdate({
|
|
5135
5188
|
documentId: a.documentId,
|
|
5136
|
-
// updateDocumentTabProperties is not yet in the googleapis TypeScript types — cast required
|
|
5137
|
-
|
|
5189
|
+
// updateDocumentTabProperties is not yet in the googleapis TypeScript types — cast required.
|
|
5190
|
+
// Per Google Docs API spec: tabId lives INSIDE tabProperties (it's the tab identifier),
|
|
5191
|
+
// and `fields` is a FieldMask for which properties to update (excludes tabId).
|
|
5192
|
+
requestBody: { requests: [{ updateDocumentTabProperties: { tabProperties: { tabId: a.tabId, title: a.title }, fields: "title" } }] }
|
|
5138
5193
|
});
|
|
5139
5194
|
return { content: [{ type: "text", text: `Renamed tab ${a.tabId} to "${a.title}".` }], isError: false };
|
|
5140
5195
|
}
|
|
@@ -6629,6 +6684,72 @@ var ExportSlideThumbnailSchema = z4.object({
|
|
|
6629
6684
|
mimeType: z4.enum(["PNG", "JPEG"]).optional().default("PNG"),
|
|
6630
6685
|
size: z4.enum(["SMALL", "MEDIUM", "LARGE"]).optional().default("LARGE")
|
|
6631
6686
|
});
|
|
6687
|
+
var InsertSlidesImageFromUrlSchema = z4.object({
|
|
6688
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6689
|
+
pageObjectId: z4.string().min(1, "Slide/page object ID is required"),
|
|
6690
|
+
imageUrl: z4.string().url("A valid image URL is required"),
|
|
6691
|
+
x: z4.number().optional().default(0),
|
|
6692
|
+
y: z4.number().optional().default(0),
|
|
6693
|
+
width: z4.number().optional(),
|
|
6694
|
+
height: z4.number().optional()
|
|
6695
|
+
});
|
|
6696
|
+
var MoveSlideElementSchema = z4.object({
|
|
6697
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6698
|
+
objectId: z4.string().min(1, "Element object ID is required"),
|
|
6699
|
+
x: z4.number().optional(),
|
|
6700
|
+
y: z4.number().optional(),
|
|
6701
|
+
width: z4.number().optional(),
|
|
6702
|
+
height: z4.number().optional()
|
|
6703
|
+
});
|
|
6704
|
+
var DeleteSlideElementSchema = z4.object({
|
|
6705
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6706
|
+
objectId: z4.string().min(1, "Element object ID is required")
|
|
6707
|
+
});
|
|
6708
|
+
var GetSlideElementInfoSchema = z4.object({
|
|
6709
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6710
|
+
slideObjectId: z4.string().optional()
|
|
6711
|
+
});
|
|
6712
|
+
var InsertSlidesLocalImageSchema = z4.object({
|
|
6713
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6714
|
+
pageObjectId: z4.string().min(1, "Slide/page object ID is required"),
|
|
6715
|
+
localImagePath: z4.string().min(1, "Local image path is required"),
|
|
6716
|
+
x: z4.number().optional().default(0),
|
|
6717
|
+
y: z4.number().optional().default(0),
|
|
6718
|
+
width: z4.number().optional(),
|
|
6719
|
+
height: z4.number().optional()
|
|
6720
|
+
});
|
|
6721
|
+
async function insertImageIntoSlide(ctx, presentationId, pageObjectId, imageUrl, x, y, width, height) {
|
|
6722
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
6723
|
+
const objectId = `img_${uuidv4().substring(0, 8)}`;
|
|
6724
|
+
const elementProperties = {
|
|
6725
|
+
pageObjectId,
|
|
6726
|
+
transform: {
|
|
6727
|
+
scaleX: 1,
|
|
6728
|
+
scaleY: 1,
|
|
6729
|
+
translateX: x,
|
|
6730
|
+
translateY: y,
|
|
6731
|
+
unit: "EMU"
|
|
6732
|
+
}
|
|
6733
|
+
};
|
|
6734
|
+
if (width != null && height != null) {
|
|
6735
|
+
elementProperties.size = {
|
|
6736
|
+
width: { magnitude: width, unit: "EMU" },
|
|
6737
|
+
height: { magnitude: height, unit: "EMU" }
|
|
6738
|
+
};
|
|
6739
|
+
}
|
|
6740
|
+
await slidesService.presentations.batchUpdate({
|
|
6741
|
+
presentationId,
|
|
6742
|
+
requestBody: {
|
|
6743
|
+
requests: [{
|
|
6744
|
+
createImage: { objectId, url: imageUrl, elementProperties }
|
|
6745
|
+
}]
|
|
6746
|
+
}
|
|
6747
|
+
});
|
|
6748
|
+
return {
|
|
6749
|
+
content: [{ type: "text", text: `Inserted image into slide ${pageObjectId} (objectId: ${objectId})` }],
|
|
6750
|
+
isError: false
|
|
6751
|
+
};
|
|
6752
|
+
}
|
|
6632
6753
|
var toolDefinitions4 = [
|
|
6633
6754
|
{
|
|
6634
6755
|
name: "createGoogleSlides",
|
|
@@ -6942,6 +7063,80 @@ var toolDefinitions4 = [
|
|
|
6942
7063
|
},
|
|
6943
7064
|
required: ["presentationId", "slideObjectId"]
|
|
6944
7065
|
}
|
|
7066
|
+
},
|
|
7067
|
+
{
|
|
7068
|
+
name: "insertSlidesImageFromUrl",
|
|
7069
|
+
description: "Insert an image into a Google Slides slide from a publicly accessible URL",
|
|
7070
|
+
inputSchema: {
|
|
7071
|
+
type: "object",
|
|
7072
|
+
properties: {
|
|
7073
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7074
|
+
pageObjectId: { type: "string", description: "Slide/page object ID to insert the image into" },
|
|
7075
|
+
imageUrl: { type: "string", description: "Publicly accessible URL of the image" },
|
|
7076
|
+
x: { type: "number", description: "X position in EMU (default: 0)" },
|
|
7077
|
+
y: { type: "number", description: "Y position in EMU (default: 0)" },
|
|
7078
|
+
width: { type: "number", description: "Width in EMU (omit to auto-size)" },
|
|
7079
|
+
height: { type: "number", description: "Height in EMU (omit to auto-size)" }
|
|
7080
|
+
},
|
|
7081
|
+
required: ["presentationId", "pageObjectId", "imageUrl"]
|
|
7082
|
+
}
|
|
7083
|
+
},
|
|
7084
|
+
{
|
|
7085
|
+
name: "getSlideElementInfo",
|
|
7086
|
+
description: "Get position, size, and transform of all elements on a slide. Returns actual rendered bounds.",
|
|
7087
|
+
inputSchema: {
|
|
7088
|
+
type: "object",
|
|
7089
|
+
properties: {
|
|
7090
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7091
|
+
slideObjectId: { type: "string", description: "Slide object ID (omit to get all slides)" }
|
|
7092
|
+
},
|
|
7093
|
+
required: ["presentationId"]
|
|
7094
|
+
}
|
|
7095
|
+
},
|
|
7096
|
+
{
|
|
7097
|
+
name: "moveSlideElement",
|
|
7098
|
+
description: "Move and/or resize an element (image, text box, shape) on a Google Slides slide",
|
|
7099
|
+
inputSchema: {
|
|
7100
|
+
type: "object",
|
|
7101
|
+
properties: {
|
|
7102
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7103
|
+
objectId: { type: "string", description: "Element object ID to move/resize" },
|
|
7104
|
+
x: { type: "number", description: "New X position in EMU" },
|
|
7105
|
+
y: { type: "number", description: "New Y position in EMU" },
|
|
7106
|
+
width: { type: "number", description: "New width in EMU" },
|
|
7107
|
+
height: { type: "number", description: "New height in EMU" }
|
|
7108
|
+
},
|
|
7109
|
+
required: ["presentationId", "objectId"]
|
|
7110
|
+
}
|
|
7111
|
+
},
|
|
7112
|
+
{
|
|
7113
|
+
name: "deleteSlideElement",
|
|
7114
|
+
description: "Delete an element (image, text box, shape) from a Google Slides slide",
|
|
7115
|
+
inputSchema: {
|
|
7116
|
+
type: "object",
|
|
7117
|
+
properties: {
|
|
7118
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7119
|
+
objectId: { type: "string", description: "Element object ID to delete" }
|
|
7120
|
+
},
|
|
7121
|
+
required: ["presentationId", "objectId"]
|
|
7122
|
+
}
|
|
7123
|
+
},
|
|
7124
|
+
{
|
|
7125
|
+
name: "insertSlidesLocalImage",
|
|
7126
|
+
description: "Upload a local image file to Google Drive and insert it into a Google Slides slide",
|
|
7127
|
+
inputSchema: {
|
|
7128
|
+
type: "object",
|
|
7129
|
+
properties: {
|
|
7130
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7131
|
+
pageObjectId: { type: "string", description: "Slide/page object ID to insert the image into" },
|
|
7132
|
+
localImagePath: { type: "string", description: "Absolute path to the local image file" },
|
|
7133
|
+
x: { type: "number", description: "X position in EMU (default: 0)" },
|
|
7134
|
+
y: { type: "number", description: "Y position in EMU (default: 0)" },
|
|
7135
|
+
width: { type: "number", description: "Width in EMU (omit to auto-size)" },
|
|
7136
|
+
height: { type: "number", description: "Height in EMU (omit to auto-size)" }
|
|
7137
|
+
},
|
|
7138
|
+
required: ["presentationId", "pageObjectId", "localImagePath"]
|
|
7139
|
+
}
|
|
6945
7140
|
}
|
|
6946
7141
|
];
|
|
6947
7142
|
async function handleTool4(toolName, args, ctx) {
|
|
@@ -7779,6 +7974,167 @@ Slide ${a.slideIndex ?? index} (ID: ${slide.objectId}):
|
|
|
7779
7974
|
isError: false
|
|
7780
7975
|
};
|
|
7781
7976
|
}
|
|
7977
|
+
case "getSlideElementInfo": {
|
|
7978
|
+
const validation = GetSlideElementInfoSchema.safeParse(args);
|
|
7979
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
7980
|
+
const a = validation.data;
|
|
7981
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
7982
|
+
const sizeOnly = await slidesService.presentations.get({
|
|
7983
|
+
presentationId: a.presentationId,
|
|
7984
|
+
fields: "pageSize"
|
|
7985
|
+
});
|
|
7986
|
+
const slideWidth = sizeOnly.data.pageSize?.width?.magnitude || 9144e3;
|
|
7987
|
+
const slideHeight = sizeOnly.data.pageSize?.height?.magnitude || 6858e3;
|
|
7988
|
+
let slides = [];
|
|
7989
|
+
if (a.slideObjectId) {
|
|
7990
|
+
const page = await slidesService.presentations.pages.get({
|
|
7991
|
+
presentationId: a.presentationId,
|
|
7992
|
+
pageObjectId: a.slideObjectId,
|
|
7993
|
+
fields: "objectId,pageElements(objectId,transform,size,shape/shapeType,image)"
|
|
7994
|
+
});
|
|
7995
|
+
slides = [page.data];
|
|
7996
|
+
} else {
|
|
7997
|
+
const withSlides = await slidesService.presentations.get({
|
|
7998
|
+
presentationId: a.presentationId,
|
|
7999
|
+
fields: "slides(objectId,pageElements(objectId,transform,size,shape/shapeType,image))"
|
|
8000
|
+
});
|
|
8001
|
+
slides = withSlides.data.slides || [];
|
|
8002
|
+
}
|
|
8003
|
+
const lines = [`Slide dimensions: ${slideWidth} x ${slideHeight} EMU`];
|
|
8004
|
+
for (const slide of slides) {
|
|
8005
|
+
lines.push(`
|
|
8006
|
+
--- Slide: ${slide.objectId} ---`);
|
|
8007
|
+
for (const el of slide.pageElements || []) {
|
|
8008
|
+
const t = el.transform || {};
|
|
8009
|
+
const s = el.size || {};
|
|
8010
|
+
const intrW = s.width?.magnitude || 0;
|
|
8011
|
+
const intrH = s.height?.magnitude || 0;
|
|
8012
|
+
const scX = t.scaleX || 1;
|
|
8013
|
+
const scY = t.scaleY || 1;
|
|
8014
|
+
const tx = t.translateX || 0;
|
|
8015
|
+
const ty = t.translateY || 0;
|
|
8016
|
+
const renderedW = intrW * scX;
|
|
8017
|
+
const renderedH = intrH * scY;
|
|
8018
|
+
const right = tx + renderedW;
|
|
8019
|
+
const bottom = ty + renderedH;
|
|
8020
|
+
const offPage = tx < 0 || ty < 0 || right > slideWidth || bottom > slideHeight ? " *** OFF PAGE ***" : "";
|
|
8021
|
+
lines.push(` ${el.objectId} (${el.shape ? "shape:" + el.shape.shapeType : el.image ? "image" : "other"})`);
|
|
8022
|
+
lines.push(` intrinsic: ${intrW} x ${intrH}, scale: ${scX} x ${scY}`);
|
|
8023
|
+
lines.push(` rendered: ${Math.round(renderedW)} x ${Math.round(renderedH)} at (${tx}, ${ty})`);
|
|
8024
|
+
lines.push(` bounds: right=${Math.round(right)}, bottom=${Math.round(bottom)}${offPage}`);
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
8028
|
+
}
|
|
8029
|
+
case "moveSlideElement": {
|
|
8030
|
+
const validation = MoveSlideElementSchema.safeParse(args);
|
|
8031
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8032
|
+
const a = validation.data;
|
|
8033
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
8034
|
+
const pres = await slidesService.presentations.get({
|
|
8035
|
+
presentationId: a.presentationId,
|
|
8036
|
+
fields: "slides(pageElements(objectId,transform,size))"
|
|
8037
|
+
});
|
|
8038
|
+
let currentTransform = null;
|
|
8039
|
+
let currentSize = null;
|
|
8040
|
+
for (const slide of pres.data.slides || []) {
|
|
8041
|
+
for (const el of slide.pageElements || []) {
|
|
8042
|
+
if (el.objectId === a.objectId) {
|
|
8043
|
+
currentTransform = el.transform || null;
|
|
8044
|
+
currentSize = el.size || null;
|
|
8045
|
+
break;
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
if (currentTransform) break;
|
|
8049
|
+
}
|
|
8050
|
+
if (!currentTransform) {
|
|
8051
|
+
return errorResponse(`Element ${a.objectId} not found in presentation`);
|
|
8052
|
+
}
|
|
8053
|
+
const origWidth = currentSize?.width?.magnitude || 3e6;
|
|
8054
|
+
const origHeight = currentSize?.height?.magnitude || 3e6;
|
|
8055
|
+
const curScaleX = currentTransform.scaleX || 1;
|
|
8056
|
+
const curScaleY = currentTransform.scaleY || 1;
|
|
8057
|
+
const newScaleX = a.width !== void 0 ? a.width / origWidth : curScaleX;
|
|
8058
|
+
const newScaleY = a.height !== void 0 ? a.height / origHeight : curScaleY;
|
|
8059
|
+
const newX = a.x ?? (currentTransform.translateX || 0);
|
|
8060
|
+
const newY = a.y ?? (currentTransform.translateY || 0);
|
|
8061
|
+
const newTransform = {
|
|
8062
|
+
scaleX: newScaleX,
|
|
8063
|
+
scaleY: newScaleY,
|
|
8064
|
+
translateX: newX,
|
|
8065
|
+
translateY: newY,
|
|
8066
|
+
shearX: currentTransform.shearX || 0,
|
|
8067
|
+
shearY: currentTransform.shearY || 0,
|
|
8068
|
+
unit: "EMU"
|
|
8069
|
+
};
|
|
8070
|
+
const requests = [{
|
|
8071
|
+
updatePageElementTransform: {
|
|
8072
|
+
objectId: a.objectId,
|
|
8073
|
+
applyMode: "ABSOLUTE",
|
|
8074
|
+
transform: newTransform
|
|
8075
|
+
}
|
|
8076
|
+
}];
|
|
8077
|
+
await slidesService.presentations.batchUpdate({
|
|
8078
|
+
presentationId: a.presentationId,
|
|
8079
|
+
requestBody: { requests }
|
|
8080
|
+
});
|
|
8081
|
+
const didResize = a.width !== void 0 || a.height !== void 0;
|
|
8082
|
+
const action = didResize ? "Moved/resized" : "Moved";
|
|
8083
|
+
return {
|
|
8084
|
+
content: [{ type: "text", text: `${action} element ${a.objectId} to (${newX}, ${newY})` }],
|
|
8085
|
+
isError: false
|
|
8086
|
+
};
|
|
8087
|
+
}
|
|
8088
|
+
case "deleteSlideElement": {
|
|
8089
|
+
const validation = DeleteSlideElementSchema.safeParse(args);
|
|
8090
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8091
|
+
const a = validation.data;
|
|
8092
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
8093
|
+
await slidesService.presentations.batchUpdate({
|
|
8094
|
+
presentationId: a.presentationId,
|
|
8095
|
+
requestBody: {
|
|
8096
|
+
requests: [{ deleteObject: { objectId: a.objectId } }]
|
|
8097
|
+
}
|
|
8098
|
+
});
|
|
8099
|
+
return {
|
|
8100
|
+
content: [{ type: "text", text: `Deleted element ${a.objectId}` }],
|
|
8101
|
+
isError: false
|
|
8102
|
+
};
|
|
8103
|
+
}
|
|
8104
|
+
case "insertSlidesImageFromUrl": {
|
|
8105
|
+
const validation = InsertSlidesImageFromUrlSchema.safeParse(args);
|
|
8106
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8107
|
+
const a = validation.data;
|
|
8108
|
+
return insertImageIntoSlide(ctx, a.presentationId, a.pageObjectId, a.imageUrl, a.x, a.y, a.width, a.height);
|
|
8109
|
+
}
|
|
8110
|
+
case "insertSlidesLocalImage": {
|
|
8111
|
+
const validation = InsertSlidesLocalImageSchema.safeParse(args);
|
|
8112
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8113
|
+
const a = validation.data;
|
|
8114
|
+
const { fileId, webContentLink } = await uploadImageToDrive(ctx, a.localImagePath, {
|
|
8115
|
+
makePublic: true
|
|
8116
|
+
});
|
|
8117
|
+
try {
|
|
8118
|
+
const result = await insertImageIntoSlide(
|
|
8119
|
+
ctx,
|
|
8120
|
+
a.presentationId,
|
|
8121
|
+
a.pageObjectId,
|
|
8122
|
+
webContentLink,
|
|
8123
|
+
a.x,
|
|
8124
|
+
a.y,
|
|
8125
|
+
a.width,
|
|
8126
|
+
a.height
|
|
8127
|
+
);
|
|
8128
|
+
await deleteDriveFile(ctx, fileId).catch(
|
|
8129
|
+
(err) => ctx.log(`insertSlidesLocalImage: failed to delete intermediary Drive file ${fileId}`, err)
|
|
8130
|
+
);
|
|
8131
|
+
return result;
|
|
8132
|
+
} catch (err) {
|
|
8133
|
+
await deleteDriveFile(ctx, fileId).catch(() => {
|
|
8134
|
+
});
|
|
8135
|
+
throw err;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
7782
8138
|
default:
|
|
7783
8139
|
return null;
|
|
7784
8140
|
}
|
|
@@ -8513,6 +8869,7 @@ Examples:
|
|
|
8513
8869
|
Environment Variables:
|
|
8514
8870
|
GOOGLE_DRIVE_OAUTH_CREDENTIALS Path to OAuth credentials file
|
|
8515
8871
|
GOOGLE_DRIVE_MCP_TOKEN_PATH Path to store authentication tokens
|
|
8872
|
+
GOOGLE_DRIVE_MCP_AUTH_PORT Starting port for OAuth callback server (default: 3000, uses 5 consecutive ports)
|
|
8516
8873
|
|
|
8517
8874
|
Transport Configuration:
|
|
8518
8875
|
MCP_TRANSPORT Transport mode: stdio or http (default: stdio)
|
|
@@ -8538,8 +8895,9 @@ async function runAuthServer() {
|
|
|
8538
8895
|
const authServerInstance = new AuthServer(oauth2Client);
|
|
8539
8896
|
const success = await authServerInstance.start(true);
|
|
8540
8897
|
if (!success && !authServerInstance.authCompletedSuccessfully) {
|
|
8898
|
+
const { start, end } = authServerInstance.portRange;
|
|
8541
8899
|
console.error(
|
|
8542
|
-
|
|
8900
|
+
`Authentication failed. Could not start server or validate existing tokens. Check port availability (${start}-${end}) and try again.`
|
|
8543
8901
|
);
|
|
8544
8902
|
process.exit(1);
|
|
8545
8903
|
} else if (authServerInstance.authCompletedSuccessfully) {
|