@piotr-agier/google-drive-mcp 1.1.2 → 1.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 +13 -0
- package/dist/index.js +305 -33
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ A Model Context Protocol (MCP) server that provides secure integration with Goog
|
|
|
7
7
|
- **Multi-format Support**: Work with Google Docs, Sheets, Slides, and regular files
|
|
8
8
|
- **File Management**: Create, update, delete, rename, and move files and folders
|
|
9
9
|
- **Advanced Search**: Search across your entire Google Drive
|
|
10
|
+
- **Shared Drives Support**: Full access to Google Shared Drives (formerly Team Drives) in addition to My Drive
|
|
10
11
|
- **Folder Navigation**: List and navigate through folder hierarchies with path support (e.g., `/Work/Projects`)
|
|
11
12
|
- **MCP Resource Protocol**: Files accessible as MCP resources for reading content
|
|
12
13
|
- **Secure Authentication**: OAuth 2.0 with automatic token refresh
|
|
@@ -496,6 +497,18 @@ Add the server to your Claude Desktop configuration:
|
|
|
496
497
|
- `x`, `y`, `width`, `height`: Position/size in EMU
|
|
497
498
|
- `backgroundColor`: Fill color (RGBA 0-1) (optional)
|
|
498
499
|
|
|
500
|
+
### Speaker Notes
|
|
501
|
+
|
|
502
|
+
- **getGoogleSlidesSpeakerNotes** - Get speaker notes from a slide
|
|
503
|
+
- `presentationId`: Presentation ID
|
|
504
|
+
- `slideIndex`: Slide index (0-based)
|
|
505
|
+
- Returns the speaker notes text or a message if no notes exist
|
|
506
|
+
|
|
507
|
+
- **updateGoogleSlidesSpeakerNotes** - Update or set speaker notes for a slide
|
|
508
|
+
- `presentationId`: Presentation ID
|
|
509
|
+
- `slideIndex`: Slide index (0-based)
|
|
510
|
+
- `notes`: The speaker notes content to set
|
|
511
|
+
|
|
499
512
|
## Authentication Flow
|
|
500
513
|
|
|
501
514
|
The server uses OAuth 2.0 for secure authentication:
|
package/dist/index.js
CHANGED
|
@@ -578,7 +578,7 @@ async function authenticate() {
|
|
|
578
578
|
// src/index.ts
|
|
579
579
|
import { z } from "zod";
|
|
580
580
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
581
|
-
import { readFileSync } from "fs";
|
|
581
|
+
import { readFileSync, createReadStream, existsSync, statSync } from "fs";
|
|
582
582
|
import { join as join2, dirname as dirname3 } from "path";
|
|
583
583
|
var drive = null;
|
|
584
584
|
function ensureDriveService() {
|
|
@@ -619,6 +619,45 @@ var TEXT_MIME_TYPES = {
|
|
|
619
619
|
txt: "text/plain",
|
|
620
620
|
md: "text/markdown"
|
|
621
621
|
};
|
|
622
|
+
var BINARY_MIME_TYPES = {
|
|
623
|
+
jpg: "image/jpeg",
|
|
624
|
+
jpeg: "image/jpeg",
|
|
625
|
+
png: "image/png",
|
|
626
|
+
gif: "image/gif",
|
|
627
|
+
webp: "image/webp",
|
|
628
|
+
svg: "image/svg+xml",
|
|
629
|
+
bmp: "image/bmp",
|
|
630
|
+
ico: "image/x-icon",
|
|
631
|
+
mp3: "audio/mpeg",
|
|
632
|
+
wav: "audio/wav",
|
|
633
|
+
ogg: "audio/ogg",
|
|
634
|
+
m4a: "audio/mp4",
|
|
635
|
+
aac: "audio/aac",
|
|
636
|
+
flac: "audio/flac",
|
|
637
|
+
opus: "audio/opus",
|
|
638
|
+
mp4: "video/mp4",
|
|
639
|
+
webm: "video/webm",
|
|
640
|
+
avi: "video/x-msvideo",
|
|
641
|
+
mov: "video/quicktime",
|
|
642
|
+
mkv: "video/x-matroska",
|
|
643
|
+
"3gp": "video/3gpp",
|
|
644
|
+
pdf: "application/pdf",
|
|
645
|
+
zip: "application/zip",
|
|
646
|
+
gz: "application/gzip",
|
|
647
|
+
tar: "application/x-tar",
|
|
648
|
+
json: "application/json",
|
|
649
|
+
xml: "application/xml",
|
|
650
|
+
csv: "text/csv",
|
|
651
|
+
html: "text/html",
|
|
652
|
+
css: "text/css",
|
|
653
|
+
js: "application/javascript",
|
|
654
|
+
doc: "application/msword",
|
|
655
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
656
|
+
xls: "application/vnd.ms-excel",
|
|
657
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
658
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
659
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
660
|
+
};
|
|
622
661
|
var authClient = null;
|
|
623
662
|
var authenticationPromise = null;
|
|
624
663
|
var __filename = fileURLToPath2(import.meta.url);
|
|
@@ -647,7 +686,9 @@ async function resolvePath(pathStr) {
|
|
|
647
686
|
let response = await drive.files.list({
|
|
648
687
|
q: `'${currentFolderId}' in parents and name = '${part}' and mimeType = '${FOLDER_MIME_TYPE}' and trashed = false`,
|
|
649
688
|
fields: "files(id)",
|
|
650
|
-
spaces: "drive"
|
|
689
|
+
spaces: "drive",
|
|
690
|
+
includeItemsFromAllDrives: true,
|
|
691
|
+
supportsAllDrives: true
|
|
651
692
|
});
|
|
652
693
|
if (!response.data.files?.length) {
|
|
653
694
|
const folderMetadata = {
|
|
@@ -657,7 +698,8 @@ async function resolvePath(pathStr) {
|
|
|
657
698
|
};
|
|
658
699
|
const folder = await drive.files.create({
|
|
659
700
|
requestBody: folderMetadata,
|
|
660
|
-
fields: "id"
|
|
701
|
+
fields: "id",
|
|
702
|
+
supportsAllDrives: true
|
|
661
703
|
});
|
|
662
704
|
if (!folder.data.id) {
|
|
663
705
|
throw new Error(`Failed to create intermediate folder: ${part}`);
|
|
@@ -719,7 +761,9 @@ async function checkFileExists(name, parentFolderId = "root") {
|
|
|
719
761
|
const res = await drive.files.list({
|
|
720
762
|
q: query,
|
|
721
763
|
fields: "files(id, name, mimeType)",
|
|
722
|
-
pageSize: 1
|
|
764
|
+
pageSize: 1,
|
|
765
|
+
includeItemsFromAllDrives: true,
|
|
766
|
+
supportsAllDrives: true
|
|
723
767
|
});
|
|
724
768
|
if (res.data.files && res.data.files.length > 0) {
|
|
725
769
|
return res.data.files[0].id || null;
|
|
@@ -777,12 +821,14 @@ var UpdateGoogleDocSchema = z.object({
|
|
|
777
821
|
var CreateGoogleSheetSchema = z.object({
|
|
778
822
|
name: z.string().min(1, "Sheet name is required"),
|
|
779
823
|
data: z.array(z.array(z.string())),
|
|
780
|
-
parentFolderId: z.string().optional()
|
|
824
|
+
parentFolderId: z.string().optional(),
|
|
825
|
+
valueInputOption: z.enum(["RAW", "USER_ENTERED"]).optional()
|
|
781
826
|
});
|
|
782
827
|
var UpdateGoogleSheetSchema = z.object({
|
|
783
828
|
spreadsheetId: z.string().min(1, "Spreadsheet ID is required"),
|
|
784
829
|
range: z.string().min(1, "Range is required"),
|
|
785
|
-
data: z.array(z.array(z.string()))
|
|
830
|
+
data: z.array(z.array(z.string())),
|
|
831
|
+
valueInputOption: z.enum(["RAW", "USER_ENTERED"]).optional()
|
|
786
832
|
});
|
|
787
833
|
var GetGoogleSheetContentSchema = z.object({
|
|
788
834
|
spreadsheetId: z.string().min(1, "Spreadsheet ID is required"),
|
|
@@ -991,6 +1037,21 @@ var CreateGoogleSlidesShapeSchema = z.object({
|
|
|
991
1037
|
alpha: z.number().min(0).max(1).optional()
|
|
992
1038
|
}).optional()
|
|
993
1039
|
});
|
|
1040
|
+
var GetGoogleSlidesSpeakerNotesSchema = z.object({
|
|
1041
|
+
presentationId: z.string().min(1, "Presentation ID is required"),
|
|
1042
|
+
slideIndex: z.number().min(0, "Slide index must be non-negative")
|
|
1043
|
+
});
|
|
1044
|
+
var UpdateGoogleSlidesSpeakerNotesSchema = z.object({
|
|
1045
|
+
presentationId: z.string().min(1, "Presentation ID is required"),
|
|
1046
|
+
slideIndex: z.number().min(0, "Slide index must be non-negative"),
|
|
1047
|
+
notes: z.string()
|
|
1048
|
+
});
|
|
1049
|
+
var UploadFileSchema = z.object({
|
|
1050
|
+
localPath: z.string().min(1, "Local file path is required"),
|
|
1051
|
+
name: z.string().optional(),
|
|
1052
|
+
parentFolderId: z.string().optional(),
|
|
1053
|
+
mimeType: z.string().optional()
|
|
1054
|
+
});
|
|
994
1055
|
var server = new Server(
|
|
995
1056
|
{
|
|
996
1057
|
name: "google-drive-mcp",
|
|
@@ -1033,7 +1094,9 @@ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|
|
1033
1094
|
const params = {
|
|
1034
1095
|
pageSize,
|
|
1035
1096
|
fields: "nextPageToken, files(id, name, mimeType)",
|
|
1036
|
-
q: `trashed = false
|
|
1097
|
+
q: `trashed = false`,
|
|
1098
|
+
includeItemsFromAllDrives: true,
|
|
1099
|
+
supportsAllDrives: true
|
|
1037
1100
|
};
|
|
1038
1101
|
if (request.params?.cursor) {
|
|
1039
1102
|
params.pageToken = request.params.cursor;
|
|
@@ -1056,7 +1119,8 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
1056
1119
|
const fileId = request.params.uri.replace("gdrive:///", "");
|
|
1057
1120
|
const file = await drive.files.get({
|
|
1058
1121
|
fileId,
|
|
1059
|
-
fields: "mimeType"
|
|
1122
|
+
fields: "mimeType",
|
|
1123
|
+
supportsAllDrives: true
|
|
1060
1124
|
});
|
|
1061
1125
|
const mimeType = file.data.mimeType;
|
|
1062
1126
|
if (!mimeType) {
|
|
@@ -1082,7 +1146,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
1082
1146
|
break;
|
|
1083
1147
|
}
|
|
1084
1148
|
const res = await drive.files.export(
|
|
1085
|
-
{ fileId, mimeType: exportMimeType },
|
|
1149
|
+
{ fileId, mimeType: exportMimeType, supportsAllDrives: true },
|
|
1086
1150
|
{ responseType: "text" }
|
|
1087
1151
|
);
|
|
1088
1152
|
log("Successfully read resource", { fileId, mimeType });
|
|
@@ -1097,7 +1161,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
1097
1161
|
};
|
|
1098
1162
|
} else {
|
|
1099
1163
|
const res = await drive.files.get(
|
|
1100
|
-
{ fileId, alt: "media" },
|
|
1164
|
+
{ fileId, alt: "media", supportsAllDrives: true },
|
|
1101
1165
|
{ responseType: "arraybuffer" }
|
|
1102
1166
|
);
|
|
1103
1167
|
const contentMime = mimeType || "application/octet-stream";
|
|
@@ -1252,7 +1316,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1252
1316
|
},
|
|
1253
1317
|
{
|
|
1254
1318
|
name: "createGoogleSheet",
|
|
1255
|
-
description: "Create a new Google Sheet",
|
|
1319
|
+
description: "Create a new Google Sheet. By default uses RAW mode which stores values as-is. Set valueInputOption to 'USER_ENTERED' only when you need formulas to be evaluated.",
|
|
1256
1320
|
inputSchema: {
|
|
1257
1321
|
type: "object",
|
|
1258
1322
|
properties: {
|
|
@@ -1262,22 +1326,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1262
1326
|
description: "Data as array of arrays",
|
|
1263
1327
|
items: { type: "array", items: { type: "string" } }
|
|
1264
1328
|
},
|
|
1265
|
-
parentFolderId: { type: "string", description: "Parent folder ID (defaults to root)", optional: true }
|
|
1329
|
+
parentFolderId: { type: "string", description: "Parent folder ID (defaults to root)", optional: true },
|
|
1330
|
+
valueInputOption: {
|
|
1331
|
+
type: "string",
|
|
1332
|
+
enum: ["RAW", "USER_ENTERED"],
|
|
1333
|
+
description: "RAW (default): Values stored exactly as provided - formulas stored as text strings. Safe for untrusted data. USER_ENTERED: Values parsed like spreadsheet UI - formulas (=SUM, =IF, etc.) are evaluated. SECURITY WARNING: USER_ENTERED can execute formulas, only use with trusted data, never with user-provided input that could contain malicious formulas like =IMPORTDATA() or =IMPORTRANGE()."
|
|
1334
|
+
}
|
|
1266
1335
|
},
|
|
1267
1336
|
required: ["name", "data"]
|
|
1268
1337
|
}
|
|
1269
1338
|
},
|
|
1270
1339
|
{
|
|
1271
1340
|
name: "updateGoogleSheet",
|
|
1272
|
-
description: "Update an existing Google Sheet",
|
|
1341
|
+
description: "Update an existing Google Sheet. By default uses RAW mode which stores values as-is. Set valueInputOption to 'USER_ENTERED' only when you need formulas to be evaluated.",
|
|
1273
1342
|
inputSchema: {
|
|
1274
1343
|
type: "object",
|
|
1275
1344
|
properties: {
|
|
1276
1345
|
spreadsheetId: { type: "string", description: "Sheet ID" },
|
|
1277
|
-
range: { type: "string", description: "Range to update" },
|
|
1346
|
+
range: { type: "string", description: "Range to update (e.g., 'Sheet1!A1:C10')" },
|
|
1278
1347
|
data: {
|
|
1279
1348
|
type: "array",
|
|
1349
|
+
description: "2D array of values to write",
|
|
1280
1350
|
items: { type: "array", items: { type: "string" } }
|
|
1351
|
+
},
|
|
1352
|
+
valueInputOption: {
|
|
1353
|
+
type: "string",
|
|
1354
|
+
enum: ["RAW", "USER_ENTERED"],
|
|
1355
|
+
description: "RAW (default): Values stored exactly as provided - formulas stored as text strings. Safe for untrusted data. USER_ENTERED: Values parsed like spreadsheet UI - formulas (=SUM, =IF, etc.) are evaluated. SECURITY WARNING: USER_ENTERED can execute formulas, only use with trusted data, never with user-provided input that could contain malicious formulas like =IMPORTDATA() or =IMPORTRANGE()."
|
|
1281
1356
|
}
|
|
1282
1357
|
},
|
|
1283
1358
|
required: ["spreadsheetId", "range", "data"]
|
|
@@ -1787,6 +1862,45 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1787
1862
|
},
|
|
1788
1863
|
required: ["presentationId", "pageObjectId", "shapeType", "x", "y", "width", "height"]
|
|
1789
1864
|
}
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
name: "getGoogleSlidesSpeakerNotes",
|
|
1868
|
+
description: "Get speaker notes from a specific slide in Google Slides",
|
|
1869
|
+
inputSchema: {
|
|
1870
|
+
type: "object",
|
|
1871
|
+
properties: {
|
|
1872
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
1873
|
+
slideIndex: { type: "number", description: "Slide index (0-based)" }
|
|
1874
|
+
},
|
|
1875
|
+
required: ["presentationId", "slideIndex"]
|
|
1876
|
+
}
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
name: "updateGoogleSlidesSpeakerNotes",
|
|
1880
|
+
description: "Update speaker notes for a specific slide in Google Slides",
|
|
1881
|
+
inputSchema: {
|
|
1882
|
+
type: "object",
|
|
1883
|
+
properties: {
|
|
1884
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
1885
|
+
slideIndex: { type: "number", description: "Slide index (0-based)" },
|
|
1886
|
+
notes: { type: "string", description: "Speaker notes content" }
|
|
1887
|
+
},
|
|
1888
|
+
required: ["presentationId", "slideIndex", "notes"]
|
|
1889
|
+
}
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
name: "uploadFile",
|
|
1893
|
+
description: "Upload a local file (any type: image, audio, video, PDF, etc.) to Google Drive",
|
|
1894
|
+
inputSchema: {
|
|
1895
|
+
type: "object",
|
|
1896
|
+
properties: {
|
|
1897
|
+
localPath: { type: "string", description: "Absolute path to the local file to upload" },
|
|
1898
|
+
name: { type: "string", description: "File name in Drive (defaults to local filename)", optional: true },
|
|
1899
|
+
parentFolderId: { type: "string", description: "Parent folder ID or path (e.g., '/Work/Projects'). Creates folders if needed. Defaults to root.", optional: true },
|
|
1900
|
+
mimeType: { type: "string", description: "MIME type (auto-detected from extension if omitted)", optional: true }
|
|
1901
|
+
},
|
|
1902
|
+
required: ["localPath"]
|
|
1903
|
+
}
|
|
1790
1904
|
}
|
|
1791
1905
|
]
|
|
1792
1906
|
};
|
|
@@ -1814,9 +1928,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1814
1928
|
q: formattedQuery,
|
|
1815
1929
|
pageSize: Math.min(pageSize || 50, 100),
|
|
1816
1930
|
pageToken,
|
|
1817
|
-
fields: "nextPageToken, files(id, name, mimeType, modifiedTime, size)"
|
|
1931
|
+
fields: "nextPageToken, files(id, name, mimeType, modifiedTime, size)",
|
|
1932
|
+
includeItemsFromAllDrives: true,
|
|
1933
|
+
supportsAllDrives: true
|
|
1818
1934
|
});
|
|
1819
|
-
const fileList = res.data.files?.map((f) => `${f.name} (${f.mimeType})`).join("\n") || "";
|
|
1935
|
+
const fileList = res.data.files?.map((f) => `${f.name} (ID: ${f.id}, ${f.mimeType})`).join("\n") || "";
|
|
1820
1936
|
log("Search results", { query: userQuery, resultCount: res.data.files?.length });
|
|
1821
1937
|
let response = `Found ${res.data.files?.length ?? 0} files:
|
|
1822
1938
|
${fileList}`;
|
|
@@ -1860,7 +1976,8 @@ More results available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
1860
1976
|
media: {
|
|
1861
1977
|
mimeType: fileMetadata.mimeType,
|
|
1862
1978
|
body: args.content
|
|
1863
|
-
}
|
|
1979
|
+
},
|
|
1980
|
+
supportsAllDrives: true
|
|
1864
1981
|
});
|
|
1865
1982
|
log("File created successfully", { fileId: file.data?.id });
|
|
1866
1983
|
return {
|
|
@@ -1880,7 +1997,8 @@ ID: ${file.data?.id || "unknown"}`
|
|
|
1880
1997
|
const args = validation.data;
|
|
1881
1998
|
const existingFile = await drive.files.get({
|
|
1882
1999
|
fileId: args.fileId,
|
|
1883
|
-
fields: "mimeType, name, parents"
|
|
2000
|
+
fields: "mimeType, name, parents",
|
|
2001
|
+
supportsAllDrives: true
|
|
1884
2002
|
});
|
|
1885
2003
|
const currentMimeType = existingFile.data.mimeType || "text/plain";
|
|
1886
2004
|
if (!Object.values(TEXT_MIME_TYPES).includes(currentMimeType)) {
|
|
@@ -1899,7 +2017,8 @@ ID: ${file.data?.id || "unknown"}`
|
|
|
1899
2017
|
mimeType: updateMetadata.mimeType || currentMimeType,
|
|
1900
2018
|
body: args.content
|
|
1901
2019
|
},
|
|
1902
|
-
fields: "id, name, modifiedTime, webViewLink"
|
|
2020
|
+
fields: "id, name, modifiedTime, webViewLink",
|
|
2021
|
+
supportsAllDrives: true
|
|
1903
2022
|
});
|
|
1904
2023
|
return {
|
|
1905
2024
|
content: [{
|
|
@@ -1930,7 +2049,8 @@ Modified: ${updatedFile.data.modifiedTime}`
|
|
|
1930
2049
|
};
|
|
1931
2050
|
const folder = await drive.files.create({
|
|
1932
2051
|
requestBody: folderMetadata,
|
|
1933
|
-
fields: "id, name, webViewLink"
|
|
2052
|
+
fields: "id, name, webViewLink",
|
|
2053
|
+
supportsAllDrives: true
|
|
1934
2054
|
});
|
|
1935
2055
|
log("Folder created successfully", { folderId: folder.data.id, name: folder.data.name });
|
|
1936
2056
|
return {
|
|
@@ -1954,7 +2074,9 @@ ID: ${folder.data.id}`
|
|
|
1954
2074
|
pageSize: Math.min(args.pageSize || 50, 100),
|
|
1955
2075
|
pageToken: args.pageToken,
|
|
1956
2076
|
fields: "nextPageToken, files(id, name, mimeType, modifiedTime, size)",
|
|
1957
|
-
orderBy: "name"
|
|
2077
|
+
orderBy: "name",
|
|
2078
|
+
includeItemsFromAllDrives: true,
|
|
2079
|
+
supportsAllDrives: true
|
|
1958
2080
|
});
|
|
1959
2081
|
const files = res.data.files || [];
|
|
1960
2082
|
const formattedFiles = files.map((file) => {
|
|
@@ -1980,12 +2102,13 @@ More items available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
1980
2102
|
return errorResponse(validation.error.errors[0].message);
|
|
1981
2103
|
}
|
|
1982
2104
|
const args = validation.data;
|
|
1983
|
-
const item = await drive.files.get({ fileId: args.itemId, fields: "name" });
|
|
2105
|
+
const item = await drive.files.get({ fileId: args.itemId, fields: "name", supportsAllDrives: true });
|
|
1984
2106
|
await drive.files.update({
|
|
1985
2107
|
fileId: args.itemId,
|
|
1986
2108
|
requestBody: {
|
|
1987
2109
|
trashed: true
|
|
1988
|
-
}
|
|
2110
|
+
},
|
|
2111
|
+
supportsAllDrives: true
|
|
1989
2112
|
});
|
|
1990
2113
|
log("Item moved to trash successfully", { itemId: args.itemId, name: item.data.name });
|
|
1991
2114
|
return {
|
|
@@ -1999,14 +2122,15 @@ More items available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
1999
2122
|
return errorResponse(validation.error.errors[0].message);
|
|
2000
2123
|
}
|
|
2001
2124
|
const args = validation.data;
|
|
2002
|
-
const item = await drive.files.get({ fileId: args.itemId, fields: "name, mimeType" });
|
|
2125
|
+
const item = await drive.files.get({ fileId: args.itemId, fields: "name, mimeType", supportsAllDrives: true });
|
|
2003
2126
|
if (Object.values(TEXT_MIME_TYPES).includes(item.data.mimeType || "")) {
|
|
2004
2127
|
validateTextFileExtension(args.newName);
|
|
2005
2128
|
}
|
|
2006
2129
|
const updatedItem = await drive.files.update({
|
|
2007
2130
|
fileId: args.itemId,
|
|
2008
2131
|
requestBody: { name: args.newName },
|
|
2009
|
-
fields: "id, name, modifiedTime"
|
|
2132
|
+
fields: "id, name, modifiedTime",
|
|
2133
|
+
supportsAllDrives: true
|
|
2010
2134
|
});
|
|
2011
2135
|
return {
|
|
2012
2136
|
content: [{
|
|
@@ -2026,16 +2150,18 @@ More items available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
2026
2150
|
if (args.destinationFolderId === args.itemId) {
|
|
2027
2151
|
return errorResponse("Cannot move a folder into itself.");
|
|
2028
2152
|
}
|
|
2029
|
-
const item = await drive.files.get({ fileId: args.itemId, fields: "name, parents" });
|
|
2153
|
+
const item = await drive.files.get({ fileId: args.itemId, fields: "name, parents", supportsAllDrives: true });
|
|
2030
2154
|
await drive.files.update({
|
|
2031
2155
|
fileId: args.itemId,
|
|
2032
2156
|
addParents: destinationFolderId,
|
|
2033
2157
|
removeParents: item.data.parents?.join(",") || "",
|
|
2034
|
-
fields: "id, name, parents"
|
|
2158
|
+
fields: "id, name, parents",
|
|
2159
|
+
supportsAllDrives: true
|
|
2035
2160
|
});
|
|
2036
2161
|
const destinationFolder = await drive.files.get({
|
|
2037
2162
|
fileId: destinationFolderId,
|
|
2038
|
-
fields: "name"
|
|
2163
|
+
fields: "name",
|
|
2164
|
+
supportsAllDrives: true
|
|
2039
2165
|
});
|
|
2040
2166
|
return {
|
|
2041
2167
|
content: [{
|
|
@@ -2079,7 +2205,8 @@ More items available. Use pageToken: ${res.data.nextPageToken}`;
|
|
|
2079
2205
|
mimeType: "application/vnd.google-apps.document",
|
|
2080
2206
|
parents: [parentFolderId]
|
|
2081
2207
|
},
|
|
2082
|
-
fields: "id, name, webViewLink"
|
|
2208
|
+
fields: "id, name, webViewLink",
|
|
2209
|
+
supportsAllDrives: true
|
|
2083
2210
|
});
|
|
2084
2211
|
} catch (createError) {
|
|
2085
2212
|
log("Drive files.create error details:", {
|
|
@@ -2204,12 +2331,14 @@ Link: ${doc.webViewLink}` }],
|
|
|
2204
2331
|
await drive.files.update({
|
|
2205
2332
|
fileId: spreadsheet.data.spreadsheetId || "",
|
|
2206
2333
|
addParents: parentFolderId,
|
|
2207
|
-
|
|
2334
|
+
removeParents: "root",
|
|
2335
|
+
fields: "id, name, webViewLink",
|
|
2336
|
+
supportsAllDrives: true
|
|
2208
2337
|
});
|
|
2209
2338
|
await sheets.spreadsheets.values.update({
|
|
2210
2339
|
spreadsheetId: spreadsheet.data.spreadsheetId,
|
|
2211
2340
|
range: "Sheet1!A1",
|
|
2212
|
-
valueInputOption: "RAW",
|
|
2341
|
+
valueInputOption: args.valueInputOption || "RAW",
|
|
2213
2342
|
requestBody: { values: args.data }
|
|
2214
2343
|
});
|
|
2215
2344
|
return {
|
|
@@ -2228,7 +2357,7 @@ ID: ${spreadsheet.data.spreadsheetId}` }],
|
|
|
2228
2357
|
await sheets.spreadsheets.values.update({
|
|
2229
2358
|
spreadsheetId: args.spreadsheetId,
|
|
2230
2359
|
range: args.range,
|
|
2231
|
-
valueInputOption: "RAW",
|
|
2360
|
+
valueInputOption: args.valueInputOption || "RAW",
|
|
2232
2361
|
requestBody: { values: args.data }
|
|
2233
2362
|
});
|
|
2234
2363
|
return {
|
|
@@ -2626,7 +2755,8 @@ ID: ${spreadsheet.data.spreadsheetId}` }],
|
|
|
2626
2755
|
await drive.files.update({
|
|
2627
2756
|
fileId: presentation.data.presentationId,
|
|
2628
2757
|
addParents: parentFolderId,
|
|
2629
|
-
removeParents: "root"
|
|
2758
|
+
removeParents: "root",
|
|
2759
|
+
supportsAllDrives: true
|
|
2630
2760
|
});
|
|
2631
2761
|
for (const slide of args.slides) {
|
|
2632
2762
|
const slideObjectId = `slide_${uuidv4().substring(0, 8)}`;
|
|
@@ -3421,6 +3551,148 @@ Slide ${args.slideIndex ?? index} (ID: ${slide.objectId}):
|
|
|
3421
3551
|
isError: false
|
|
3422
3552
|
};
|
|
3423
3553
|
}
|
|
3554
|
+
case "getGoogleSlidesSpeakerNotes": {
|
|
3555
|
+
const validation = GetGoogleSlidesSpeakerNotesSchema.safeParse(request.params.arguments);
|
|
3556
|
+
if (!validation.success) {
|
|
3557
|
+
return errorResponse(validation.error.errors[0].message);
|
|
3558
|
+
}
|
|
3559
|
+
const args = validation.data;
|
|
3560
|
+
const slidesService = google.slides({ version: "v1", auth: authClient });
|
|
3561
|
+
const presentation = await slidesService.presentations.get({
|
|
3562
|
+
presentationId: args.presentationId
|
|
3563
|
+
});
|
|
3564
|
+
if (!presentation.data.slides || args.slideIndex >= presentation.data.slides.length) {
|
|
3565
|
+
return errorResponse(`Slide index ${args.slideIndex} not found in presentation (has ${presentation.data.slides?.length ?? 0} slides)`);
|
|
3566
|
+
}
|
|
3567
|
+
const slide = presentation.data.slides[args.slideIndex];
|
|
3568
|
+
const notesObjectId = slide.slideProperties?.notesPage?.notesProperties?.speakerNotesObjectId;
|
|
3569
|
+
if (!notesObjectId) {
|
|
3570
|
+
return {
|
|
3571
|
+
content: [{ type: "text", text: "No speaker notes found for this slide" }],
|
|
3572
|
+
isError: false
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
const notesPageObjectId = slide.slideProperties?.notesPage?.objectId;
|
|
3576
|
+
if (!notesPageObjectId) {
|
|
3577
|
+
return {
|
|
3578
|
+
content: [{ type: "text", text: "No speaker notes found for this slide" }],
|
|
3579
|
+
isError: false
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
const notesPage = presentation.data.slides?.[args.slideIndex]?.slideProperties?.notesPage;
|
|
3583
|
+
if (!notesPage || !notesPage.pageElements) {
|
|
3584
|
+
return {
|
|
3585
|
+
content: [{ type: "text", text: "No speaker notes found for this slide" }],
|
|
3586
|
+
isError: false
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3589
|
+
const speakerNotesElement = notesPage.pageElements.find(
|
|
3590
|
+
(element) => element.objectId === notesObjectId
|
|
3591
|
+
);
|
|
3592
|
+
if (!speakerNotesElement || !speakerNotesElement.shape?.text) {
|
|
3593
|
+
return {
|
|
3594
|
+
content: [{ type: "text", text: "No speaker notes found for this slide" }],
|
|
3595
|
+
isError: false
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
let notesText = "";
|
|
3599
|
+
const textElements = speakerNotesElement.shape.text.textElements || [];
|
|
3600
|
+
textElements.forEach((textElement) => {
|
|
3601
|
+
if (textElement.textRun?.content) {
|
|
3602
|
+
notesText += textElement.textRun.content;
|
|
3603
|
+
}
|
|
3604
|
+
});
|
|
3605
|
+
return {
|
|
3606
|
+
content: [{ type: "text", text: notesText.trim() || "No speaker notes found for this slide" }],
|
|
3607
|
+
isError: false
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
case "updateGoogleSlidesSpeakerNotes": {
|
|
3611
|
+
const validation = UpdateGoogleSlidesSpeakerNotesSchema.safeParse(request.params.arguments);
|
|
3612
|
+
if (!validation.success) {
|
|
3613
|
+
return errorResponse(validation.error.errors[0].message);
|
|
3614
|
+
}
|
|
3615
|
+
const args = validation.data;
|
|
3616
|
+
const slidesService = google.slides({ version: "v1", auth: authClient });
|
|
3617
|
+
const presentation = await slidesService.presentations.get({
|
|
3618
|
+
presentationId: args.presentationId
|
|
3619
|
+
});
|
|
3620
|
+
if (!presentation.data.slides || args.slideIndex >= presentation.data.slides.length) {
|
|
3621
|
+
return errorResponse(`Slide index ${args.slideIndex} not found in presentation (has ${presentation.data.slides?.length ?? 0} slides)`);
|
|
3622
|
+
}
|
|
3623
|
+
const slide = presentation.data.slides[args.slideIndex];
|
|
3624
|
+
const notesObjectId = slide.slideProperties?.notesPage?.notesProperties?.speakerNotesObjectId;
|
|
3625
|
+
if (!notesObjectId) {
|
|
3626
|
+
return errorResponse("This slide does not have a speaker notes object. Speaker notes may need to be initialized manually in Google Slides first.");
|
|
3627
|
+
}
|
|
3628
|
+
const requests = [
|
|
3629
|
+
{
|
|
3630
|
+
deleteText: {
|
|
3631
|
+
objectId: notesObjectId,
|
|
3632
|
+
textRange: {
|
|
3633
|
+
type: "ALL"
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
},
|
|
3637
|
+
{
|
|
3638
|
+
insertText: {
|
|
3639
|
+
objectId: notesObjectId,
|
|
3640
|
+
text: args.notes,
|
|
3641
|
+
insertionIndex: 0
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
];
|
|
3645
|
+
await slidesService.presentations.batchUpdate({
|
|
3646
|
+
presentationId: args.presentationId,
|
|
3647
|
+
requestBody: { requests }
|
|
3648
|
+
});
|
|
3649
|
+
return {
|
|
3650
|
+
content: [{ type: "text", text: `Successfully updated speaker notes for slide ${args.slideIndex}` }],
|
|
3651
|
+
isError: false
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
case "uploadFile": {
|
|
3655
|
+
const validation = UploadFileSchema.safeParse(request.params.arguments);
|
|
3656
|
+
if (!validation.success) {
|
|
3657
|
+
return errorResponse(validation.error.errors[0].message);
|
|
3658
|
+
}
|
|
3659
|
+
const args = validation.data;
|
|
3660
|
+
if (!existsSync(args.localPath)) {
|
|
3661
|
+
return errorResponse(`File not found: ${args.localPath}`);
|
|
3662
|
+
}
|
|
3663
|
+
const stats = statSync(args.localPath);
|
|
3664
|
+
const fileName = args.name || args.localPath.split(/[\\/]/).pop() || "upload";
|
|
3665
|
+
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
3666
|
+
const detectedMime = args.mimeType || BINARY_MIME_TYPES[ext] || "application/octet-stream";
|
|
3667
|
+
const parentId = await resolveFolderId(args.parentFolderId);
|
|
3668
|
+
log("Uploading file", { localPath: args.localPath, name: fileName, mimeType: detectedMime, size: stats.size });
|
|
3669
|
+
const file = await drive.files.create({
|
|
3670
|
+
requestBody: {
|
|
3671
|
+
name: fileName,
|
|
3672
|
+
parents: [parentId]
|
|
3673
|
+
},
|
|
3674
|
+
media: {
|
|
3675
|
+
mimeType: detectedMime,
|
|
3676
|
+
body: createReadStream(args.localPath)
|
|
3677
|
+
},
|
|
3678
|
+
fields: "id, name, size, mimeType, webViewLink",
|
|
3679
|
+
supportsAllDrives: true
|
|
3680
|
+
});
|
|
3681
|
+
log("File uploaded successfully", { fileId: file.data?.id });
|
|
3682
|
+
return {
|
|
3683
|
+
content: [{
|
|
3684
|
+
type: "text",
|
|
3685
|
+
text: [
|
|
3686
|
+
`Uploaded: ${file.data?.name || fileName}`,
|
|
3687
|
+
`ID: ${file.data?.id || "unknown"}`,
|
|
3688
|
+
`Size: ${file.data?.size || stats.size} bytes`,
|
|
3689
|
+
`Type: ${file.data?.mimeType || detectedMime}`,
|
|
3690
|
+
file.data?.webViewLink ? `Link: ${file.data.webViewLink}` : ""
|
|
3691
|
+
].filter(Boolean).join("\n")
|
|
3692
|
+
}],
|
|
3693
|
+
isError: false
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3424
3696
|
default:
|
|
3425
3697
|
return errorResponse("Tool not found");
|
|
3426
3698
|
}
|