google-tools-mcp 1.2.12 → 2.0.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/LICENSE +21 -21
- package/README.md +537 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +128 -0
- package/dist/markdown-transformer/index.js +17 -7
- package/dist/markdown-transformer/markdownToDocs.js +185 -27
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +12 -0
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +197 -196
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +30 -6
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +71 -8
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -81
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
import * as fs from 'fs/promises';
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import { Readable } from 'stream';
|
|
7
|
-
import { pipeline } from 'stream/promises';
|
|
8
|
-
import { createWriteStream } from 'fs';
|
|
9
|
-
|
|
10
|
-
// Google Workspace MIME types and their export formats
|
|
11
|
-
const EXPORT_FORMATS = {
|
|
12
|
-
'application/vnd.google-apps.document': {
|
|
13
|
-
label: 'Google Doc',
|
|
14
|
-
formats: {
|
|
15
|
-
pdf: 'application/pdf',
|
|
16
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
17
|
-
md: 'text/markdown',
|
|
18
|
-
txt: 'text/plain',
|
|
19
|
-
html: 'text/html',
|
|
20
|
-
rtf: 'application/rtf',
|
|
21
|
-
odt: 'application/vnd.oasis.opendocument.text',
|
|
22
|
-
epub: 'application/epub+zip',
|
|
23
|
-
},
|
|
24
|
-
defaultFormat: 'pdf',
|
|
25
|
-
extensions: {
|
|
26
|
-
pdf: '.pdf',
|
|
27
|
-
docx: '.docx',
|
|
28
|
-
md: '.md',
|
|
29
|
-
txt: '.txt',
|
|
30
|
-
html: '.html',
|
|
31
|
-
rtf: '.rtf',
|
|
32
|
-
odt: '.odt',
|
|
33
|
-
epub: '.epub',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
'application/vnd.google-apps.spreadsheet': {
|
|
37
|
-
label: 'Google Sheet',
|
|
38
|
-
formats: {
|
|
39
|
-
pdf: 'application/pdf',
|
|
40
|
-
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
41
|
-
csv: 'text/csv',
|
|
42
|
-
tsv: 'text/tab-separated-values',
|
|
43
|
-
ods: 'application/vnd.oasis.opendocument.spreadsheet',
|
|
44
|
-
},
|
|
45
|
-
defaultFormat: 'xlsx',
|
|
46
|
-
extensions: {
|
|
47
|
-
pdf: '.pdf',
|
|
48
|
-
xlsx: '.xlsx',
|
|
49
|
-
csv: '.csv',
|
|
50
|
-
tsv: '.tsv',
|
|
51
|
-
ods: '.ods',
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
'application/vnd.google-apps.presentation': {
|
|
55
|
-
label: 'Google Slides',
|
|
56
|
-
formats: {
|
|
57
|
-
pdf: 'application/pdf',
|
|
58
|
-
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
59
|
-
odp: 'application/vnd.oasis.opendocument.presentation',
|
|
60
|
-
txt: 'text/plain',
|
|
61
|
-
},
|
|
62
|
-
defaultFormat: 'pdf',
|
|
63
|
-
extensions: {
|
|
64
|
-
pdf: '.pdf',
|
|
65
|
-
pptx: '.pptx',
|
|
66
|
-
odp: '.odp',
|
|
67
|
-
txt: '.txt',
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
'application/vnd.google-apps.drawing': {
|
|
71
|
-
label: 'Google Drawing',
|
|
72
|
-
formats: {
|
|
73
|
-
pdf: 'application/pdf',
|
|
74
|
-
png: 'image/png',
|
|
75
|
-
jpg: 'image/jpeg',
|
|
76
|
-
svg: 'image/svg+xml',
|
|
77
|
-
},
|
|
78
|
-
defaultFormat: 'pdf',
|
|
79
|
-
extensions: {
|
|
80
|
-
pdf: '.pdf',
|
|
81
|
-
png: '.png',
|
|
82
|
-
jpg: '.jpg',
|
|
83
|
-
svg: '.svg',
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// All supported export format keys
|
|
89
|
-
const ALL_FORMAT_KEYS = [
|
|
90
|
-
...new Set(
|
|
91
|
-
Object.values(EXPORT_FORMATS).flatMap((t) => Object.keys(t.formats))
|
|
92
|
-
),
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
export function register(server) {
|
|
96
|
-
server.addTool({
|
|
97
|
-
name: 'downloadFile',
|
|
98
|
-
description:
|
|
99
|
-
'Downloads a file from Google Drive to the local filesystem. ' +
|
|
100
|
-
'For Google Workspace files (Docs, Sheets, Slides, Drawings), specify an export format ' +
|
|
101
|
-
'(e.g. pdf, docx, md, xlsx, csv, pptx). For regular files (images, PDFs, etc.), ' +
|
|
102
|
-
'downloads the original file directly.',
|
|
103
|
-
parameters: z.object({
|
|
104
|
-
fileId: z.string().describe('The ID of the file to download.'),
|
|
105
|
-
localPath: z
|
|
106
|
-
.string()
|
|
107
|
-
.describe(
|
|
108
|
-
'The local file path to save the downloaded file to. ' +
|
|
109
|
-
'Can be an absolute path or relative to the current working directory. ' +
|
|
110
|
-
'If a directory is provided, the file will be saved with its Drive name.'
|
|
111
|
-
),
|
|
112
|
-
exportFormat: z
|
|
113
|
-
.enum(ALL_FORMAT_KEYS)
|
|
114
|
-
.optional()
|
|
115
|
-
.describe(
|
|
116
|
-
'Export format for Google Workspace files. ' +
|
|
117
|
-
'Docs: pdf, docx, md, txt, html, rtf, odt, epub. ' +
|
|
118
|
-
'Sheets: pdf, xlsx, csv, tsv, ods. ' +
|
|
119
|
-
'Slides: pdf, pptx, odp, txt. ' +
|
|
120
|
-
'Drawings: pdf, png, jpg, svg. ' +
|
|
121
|
-
'Ignored for non-Google files (they download as-is).'
|
|
122
|
-
),
|
|
123
|
-
}),
|
|
124
|
-
execute: async (args, { log }) => {
|
|
125
|
-
const drive = await getDriveClient();
|
|
126
|
-
log.info(`Downloading file: ${args.fileId}`);
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
// Get file metadata first
|
|
130
|
-
const metaResponse = await drive.files.get({
|
|
131
|
-
fileId: args.fileId,
|
|
132
|
-
fields: 'id,name,mimeType,size',
|
|
133
|
-
supportsAllDrives: true,
|
|
134
|
-
});
|
|
135
|
-
const file = metaResponse.data;
|
|
136
|
-
if (!file) {
|
|
137
|
-
throw new UserError(
|
|
138
|
-
`File with ID ${args.fileId} not found.`
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const isGoogleWorkspace =
|
|
143
|
-
file.mimeType in EXPORT_FORMATS;
|
|
144
|
-
|
|
145
|
-
// Determine export MIME type and file extension
|
|
146
|
-
let exportMimeType = null;
|
|
147
|
-
let fileExtension = '';
|
|
148
|
-
|
|
149
|
-
if (isGoogleWorkspace) {
|
|
150
|
-
const typeInfo = EXPORT_FORMATS[file.mimeType];
|
|
151
|
-
const format =
|
|
152
|
-
args.exportFormat || typeInfo.defaultFormat;
|
|
153
|
-
|
|
154
|
-
if (!typeInfo.formats[format]) {
|
|
155
|
-
const available = Object.keys(
|
|
156
|
-
typeInfo.formats
|
|
157
|
-
).join(', ');
|
|
158
|
-
throw new UserError(
|
|
159
|
-
`Format '${format}' is not supported for ${typeInfo.label}. ` +
|
|
160
|
-
`Available formats: ${available}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
exportMimeType = typeInfo.formats[format];
|
|
165
|
-
fileExtension = typeInfo.extensions[format];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Resolve the local path
|
|
169
|
-
let destPath = path.resolve(args.localPath);
|
|
170
|
-
|
|
171
|
-
// Check if destPath is a directory
|
|
172
|
-
let isDir = false;
|
|
173
|
-
try {
|
|
174
|
-
const stat = await fs.stat(destPath);
|
|
175
|
-
isDir = stat.isDirectory();
|
|
176
|
-
} catch {
|
|
177
|
-
// Path doesn't exist yet — treat as a file path
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (isDir) {
|
|
181
|
-
// Build filename from Drive name + appropriate extension
|
|
182
|
-
let fileName = file.name;
|
|
183
|
-
if (isGoogleWorkspace && fileExtension) {
|
|
184
|
-
// Google Workspace files don't have extensions in their name
|
|
185
|
-
fileName = file.name + fileExtension;
|
|
186
|
-
}
|
|
187
|
-
destPath = path.join(destPath, fileName);
|
|
188
|
-
} else if (
|
|
189
|
-
isGoogleWorkspace &&
|
|
190
|
-
fileExtension &&
|
|
191
|
-
!destPath.endsWith(fileExtension)
|
|
192
|
-
) {
|
|
193
|
-
// Append extension if not already present
|
|
194
|
-
destPath = destPath + fileExtension;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Ensure parent directory exists
|
|
198
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
199
|
-
|
|
200
|
-
// Download the file
|
|
201
|
-
if (isGoogleWorkspace) {
|
|
202
|
-
log.info(
|
|
203
|
-
`Exporting ${EXPORT_FORMATS[file.mimeType].label} as ${args.exportFormat || EXPORT_FORMATS[file.mimeType].defaultFormat}`
|
|
204
|
-
);
|
|
205
|
-
const exportResponse = await drive.files.export(
|
|
206
|
-
{ fileId: args.fileId, mimeType: exportMimeType },
|
|
207
|
-
{ responseType: 'stream' }
|
|
208
|
-
);
|
|
209
|
-
await pipeline(
|
|
210
|
-
exportResponse.data,
|
|
211
|
-
createWriteStream(destPath)
|
|
212
|
-
);
|
|
213
|
-
} else {
|
|
214
|
-
log.info(`Downloading binary file: ${file.name}`);
|
|
215
|
-
const downloadResponse = await drive.files.get(
|
|
216
|
-
{
|
|
217
|
-
fileId: args.fileId,
|
|
218
|
-
alt: 'media',
|
|
219
|
-
supportsAllDrives: true,
|
|
220
|
-
},
|
|
221
|
-
{ responseType: 'stream' }
|
|
222
|
-
);
|
|
223
|
-
await pipeline(
|
|
224
|
-
downloadResponse.data,
|
|
225
|
-
createWriteStream(destPath)
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Get the final file size
|
|
230
|
-
const stat = await fs.stat(destPath);
|
|
231
|
-
|
|
232
|
-
const result = {
|
|
233
|
-
success: true,
|
|
234
|
-
fileName: file.name,
|
|
235
|
-
savedTo: destPath,
|
|
236
|
-
fileSize: stat.size,
|
|
237
|
-
mimeType: isGoogleWorkspace
|
|
238
|
-
? exportMimeType
|
|
239
|
-
: file.mimeType,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
if (isGoogleWorkspace) {
|
|
243
|
-
result.exportedAs =
|
|
244
|
-
args.exportFormat ||
|
|
245
|
-
EXPORT_FORMATS[file.mimeType].defaultFormat;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return JSON.stringify(result, null, 2);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
if (error instanceof UserError) throw error;
|
|
251
|
-
log.error(
|
|
252
|
-
`Error downloading file: ${error.message || error}`
|
|
253
|
-
);
|
|
254
|
-
if (error.code === 404)
|
|
255
|
-
throw new UserError(
|
|
256
|
-
`File not found (ID: ${args.fileId}).`
|
|
257
|
-
);
|
|
258
|
-
if (error.code === 403)
|
|
259
|
-
throw new UserError(
|
|
260
|
-
'Permission denied. Make sure you have access to this file.'
|
|
261
|
-
);
|
|
262
|
-
throw new UserError(
|
|
263
|
-
`Failed to download file: ${error.message || 'Unknown error'}`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { Readable } from 'stream';
|
|
7
|
+
import { pipeline } from 'stream/promises';
|
|
8
|
+
import { createWriteStream } from 'fs';
|
|
9
|
+
|
|
10
|
+
// Google Workspace MIME types and their export formats
|
|
11
|
+
const EXPORT_FORMATS = {
|
|
12
|
+
'application/vnd.google-apps.document': {
|
|
13
|
+
label: 'Google Doc',
|
|
14
|
+
formats: {
|
|
15
|
+
pdf: 'application/pdf',
|
|
16
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
17
|
+
md: 'text/markdown',
|
|
18
|
+
txt: 'text/plain',
|
|
19
|
+
html: 'text/html',
|
|
20
|
+
rtf: 'application/rtf',
|
|
21
|
+
odt: 'application/vnd.oasis.opendocument.text',
|
|
22
|
+
epub: 'application/epub+zip',
|
|
23
|
+
},
|
|
24
|
+
defaultFormat: 'pdf',
|
|
25
|
+
extensions: {
|
|
26
|
+
pdf: '.pdf',
|
|
27
|
+
docx: '.docx',
|
|
28
|
+
md: '.md',
|
|
29
|
+
txt: '.txt',
|
|
30
|
+
html: '.html',
|
|
31
|
+
rtf: '.rtf',
|
|
32
|
+
odt: '.odt',
|
|
33
|
+
epub: '.epub',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
'application/vnd.google-apps.spreadsheet': {
|
|
37
|
+
label: 'Google Sheet',
|
|
38
|
+
formats: {
|
|
39
|
+
pdf: 'application/pdf',
|
|
40
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
41
|
+
csv: 'text/csv',
|
|
42
|
+
tsv: 'text/tab-separated-values',
|
|
43
|
+
ods: 'application/vnd.oasis.opendocument.spreadsheet',
|
|
44
|
+
},
|
|
45
|
+
defaultFormat: 'xlsx',
|
|
46
|
+
extensions: {
|
|
47
|
+
pdf: '.pdf',
|
|
48
|
+
xlsx: '.xlsx',
|
|
49
|
+
csv: '.csv',
|
|
50
|
+
tsv: '.tsv',
|
|
51
|
+
ods: '.ods',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
'application/vnd.google-apps.presentation': {
|
|
55
|
+
label: 'Google Slides',
|
|
56
|
+
formats: {
|
|
57
|
+
pdf: 'application/pdf',
|
|
58
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
59
|
+
odp: 'application/vnd.oasis.opendocument.presentation',
|
|
60
|
+
txt: 'text/plain',
|
|
61
|
+
},
|
|
62
|
+
defaultFormat: 'pdf',
|
|
63
|
+
extensions: {
|
|
64
|
+
pdf: '.pdf',
|
|
65
|
+
pptx: '.pptx',
|
|
66
|
+
odp: '.odp',
|
|
67
|
+
txt: '.txt',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
'application/vnd.google-apps.drawing': {
|
|
71
|
+
label: 'Google Drawing',
|
|
72
|
+
formats: {
|
|
73
|
+
pdf: 'application/pdf',
|
|
74
|
+
png: 'image/png',
|
|
75
|
+
jpg: 'image/jpeg',
|
|
76
|
+
svg: 'image/svg+xml',
|
|
77
|
+
},
|
|
78
|
+
defaultFormat: 'pdf',
|
|
79
|
+
extensions: {
|
|
80
|
+
pdf: '.pdf',
|
|
81
|
+
png: '.png',
|
|
82
|
+
jpg: '.jpg',
|
|
83
|
+
svg: '.svg',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// All supported export format keys
|
|
89
|
+
const ALL_FORMAT_KEYS = [
|
|
90
|
+
...new Set(
|
|
91
|
+
Object.values(EXPORT_FORMATS).flatMap((t) => Object.keys(t.formats))
|
|
92
|
+
),
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export function register(server) {
|
|
96
|
+
server.addTool({
|
|
97
|
+
name: 'downloadFile',
|
|
98
|
+
description:
|
|
99
|
+
'Downloads a file from Google Drive to the local filesystem. ' +
|
|
100
|
+
'For Google Workspace files (Docs, Sheets, Slides, Drawings), specify an export format ' +
|
|
101
|
+
'(e.g. pdf, docx, md, xlsx, csv, pptx). For regular files (images, PDFs, etc.), ' +
|
|
102
|
+
'downloads the original file directly.',
|
|
103
|
+
parameters: z.object({
|
|
104
|
+
fileId: z.string().describe('The ID of the file to download.'),
|
|
105
|
+
localPath: z
|
|
106
|
+
.string()
|
|
107
|
+
.describe(
|
|
108
|
+
'The local file path to save the downloaded file to. ' +
|
|
109
|
+
'Can be an absolute path or relative to the current working directory. ' +
|
|
110
|
+
'If a directory is provided, the file will be saved with its Drive name.'
|
|
111
|
+
),
|
|
112
|
+
exportFormat: z
|
|
113
|
+
.enum(ALL_FORMAT_KEYS)
|
|
114
|
+
.optional()
|
|
115
|
+
.describe(
|
|
116
|
+
'Export format for Google Workspace files. ' +
|
|
117
|
+
'Docs: pdf, docx, md, txt, html, rtf, odt, epub. ' +
|
|
118
|
+
'Sheets: pdf, xlsx, csv, tsv, ods. ' +
|
|
119
|
+
'Slides: pdf, pptx, odp, txt. ' +
|
|
120
|
+
'Drawings: pdf, png, jpg, svg. ' +
|
|
121
|
+
'Ignored for non-Google files (they download as-is).'
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
execute: async (args, { log }) => {
|
|
125
|
+
const drive = await getDriveClient();
|
|
126
|
+
log.info(`Downloading file: ${args.fileId}`);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Get file metadata first
|
|
130
|
+
const metaResponse = await drive.files.get({
|
|
131
|
+
fileId: args.fileId,
|
|
132
|
+
fields: 'id,name,mimeType,size',
|
|
133
|
+
supportsAllDrives: true,
|
|
134
|
+
});
|
|
135
|
+
const file = metaResponse.data;
|
|
136
|
+
if (!file) {
|
|
137
|
+
throw new UserError(
|
|
138
|
+
`File with ID ${args.fileId} not found.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const isGoogleWorkspace =
|
|
143
|
+
file.mimeType in EXPORT_FORMATS;
|
|
144
|
+
|
|
145
|
+
// Determine export MIME type and file extension
|
|
146
|
+
let exportMimeType = null;
|
|
147
|
+
let fileExtension = '';
|
|
148
|
+
|
|
149
|
+
if (isGoogleWorkspace) {
|
|
150
|
+
const typeInfo = EXPORT_FORMATS[file.mimeType];
|
|
151
|
+
const format =
|
|
152
|
+
args.exportFormat || typeInfo.defaultFormat;
|
|
153
|
+
|
|
154
|
+
if (!typeInfo.formats[format]) {
|
|
155
|
+
const available = Object.keys(
|
|
156
|
+
typeInfo.formats
|
|
157
|
+
).join(', ');
|
|
158
|
+
throw new UserError(
|
|
159
|
+
`Format '${format}' is not supported for ${typeInfo.label}. ` +
|
|
160
|
+
`Available formats: ${available}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
exportMimeType = typeInfo.formats[format];
|
|
165
|
+
fileExtension = typeInfo.extensions[format];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Resolve the local path
|
|
169
|
+
let destPath = path.resolve(args.localPath);
|
|
170
|
+
|
|
171
|
+
// Check if destPath is a directory
|
|
172
|
+
let isDir = false;
|
|
173
|
+
try {
|
|
174
|
+
const stat = await fs.stat(destPath);
|
|
175
|
+
isDir = stat.isDirectory();
|
|
176
|
+
} catch {
|
|
177
|
+
// Path doesn't exist yet — treat as a file path
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isDir) {
|
|
181
|
+
// Build filename from Drive name + appropriate extension
|
|
182
|
+
let fileName = file.name;
|
|
183
|
+
if (isGoogleWorkspace && fileExtension) {
|
|
184
|
+
// Google Workspace files don't have extensions in their name
|
|
185
|
+
fileName = file.name + fileExtension;
|
|
186
|
+
}
|
|
187
|
+
destPath = path.join(destPath, fileName);
|
|
188
|
+
} else if (
|
|
189
|
+
isGoogleWorkspace &&
|
|
190
|
+
fileExtension &&
|
|
191
|
+
!destPath.endsWith(fileExtension)
|
|
192
|
+
) {
|
|
193
|
+
// Append extension if not already present
|
|
194
|
+
destPath = destPath + fileExtension;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Ensure parent directory exists
|
|
198
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
199
|
+
|
|
200
|
+
// Download the file
|
|
201
|
+
if (isGoogleWorkspace) {
|
|
202
|
+
log.info(
|
|
203
|
+
`Exporting ${EXPORT_FORMATS[file.mimeType].label} as ${args.exportFormat || EXPORT_FORMATS[file.mimeType].defaultFormat}`
|
|
204
|
+
);
|
|
205
|
+
const exportResponse = await drive.files.export(
|
|
206
|
+
{ fileId: args.fileId, mimeType: exportMimeType },
|
|
207
|
+
{ responseType: 'stream' }
|
|
208
|
+
);
|
|
209
|
+
await pipeline(
|
|
210
|
+
exportResponse.data,
|
|
211
|
+
createWriteStream(destPath)
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
log.info(`Downloading binary file: ${file.name}`);
|
|
215
|
+
const downloadResponse = await drive.files.get(
|
|
216
|
+
{
|
|
217
|
+
fileId: args.fileId,
|
|
218
|
+
alt: 'media',
|
|
219
|
+
supportsAllDrives: true,
|
|
220
|
+
},
|
|
221
|
+
{ responseType: 'stream' }
|
|
222
|
+
);
|
|
223
|
+
await pipeline(
|
|
224
|
+
downloadResponse.data,
|
|
225
|
+
createWriteStream(destPath)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Get the final file size
|
|
230
|
+
const stat = await fs.stat(destPath);
|
|
231
|
+
|
|
232
|
+
const result = {
|
|
233
|
+
success: true,
|
|
234
|
+
fileName: file.name,
|
|
235
|
+
savedTo: destPath,
|
|
236
|
+
fileSize: stat.size,
|
|
237
|
+
mimeType: isGoogleWorkspace
|
|
238
|
+
? exportMimeType
|
|
239
|
+
: file.mimeType,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
if (isGoogleWorkspace) {
|
|
243
|
+
result.exportedAs =
|
|
244
|
+
args.exportFormat ||
|
|
245
|
+
EXPORT_FORMATS[file.mimeType].defaultFormat;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return JSON.stringify(result, null, 2);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof UserError) throw error;
|
|
251
|
+
log.error(
|
|
252
|
+
`Error downloading file: ${error.message || error}`
|
|
253
|
+
);
|
|
254
|
+
if (error.code === 404)
|
|
255
|
+
throw new UserError(
|
|
256
|
+
`File not found (ID: ${args.fileId}).`
|
|
257
|
+
);
|
|
258
|
+
if (error.code === 403)
|
|
259
|
+
throw new UserError(
|
|
260
|
+
'Permission denied. Make sure you have access to this file.'
|
|
261
|
+
);
|
|
262
|
+
throw new UserError(
|
|
263
|
+
`Failed to download file: ${error.message || 'Unknown error'}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
export function register(server) {
|
|
5
|
-
server.addTool({
|
|
6
|
-
name: 'getFileInfo',
|
|
7
|
-
description: 'Gets metadata about any Drive file (document, spreadsheet, PDF, etc.) including its name, type, owner, sharing status, and modification history.',
|
|
8
|
-
parameters: z.object({
|
|
9
|
-
fileId: z.string().describe('The ID of the file to get information about.'),
|
|
10
|
-
}),
|
|
11
|
-
execute: async (args, { log }) => {
|
|
12
|
-
const drive = await getDriveClient();
|
|
13
|
-
log.info(`Getting info for file: ${args.fileId}`);
|
|
14
|
-
try {
|
|
15
|
-
const response = await drive.files.get({
|
|
16
|
-
fileId: args.fileId,
|
|
17
|
-
fields: 'id,name,description,mimeType,size,createdTime,modifiedTime,webViewLink,owners(displayName,emailAddress),lastModifyingUser(displayName,emailAddress),shared,parents,version',
|
|
18
|
-
supportsAllDrives: true,
|
|
19
|
-
});
|
|
20
|
-
const file = response.data;
|
|
21
|
-
if (!file) {
|
|
22
|
-
throw new UserError(`File with ID ${args.fileId} not found.`);
|
|
23
|
-
}
|
|
24
|
-
const info = {
|
|
25
|
-
id: file.id,
|
|
26
|
-
name: file.name,
|
|
27
|
-
mimeType: file.mimeType,
|
|
28
|
-
createdTime: file.createdTime,
|
|
29
|
-
modifiedTime: file.modifiedTime,
|
|
30
|
-
owner: file.owners?.[0]?.displayName || null,
|
|
31
|
-
lastModifyingUser: file.lastModifyingUser?.displayName || null,
|
|
32
|
-
shared: file.shared || false,
|
|
33
|
-
url: file.webViewLink,
|
|
34
|
-
description: file.description || null,
|
|
35
|
-
};
|
|
36
|
-
return JSON.stringify(info, null, 2);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
log.error(`Error getting file info: ${error.message || error}`);
|
|
40
|
-
if (error.code === 404)
|
|
41
|
-
throw new UserError(`File not found (ID: ${args.fileId}).`);
|
|
42
|
-
if (error.code === 403)
|
|
43
|
-
throw new UserError('Permission denied. Make sure you have access to this file.');
|
|
44
|
-
throw new UserError(`Failed to get file info: ${error.message || 'Unknown error'}`);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
export function register(server) {
|
|
5
|
+
server.addTool({
|
|
6
|
+
name: 'getFileInfo',
|
|
7
|
+
description: 'Gets metadata about any Drive file (document, spreadsheet, PDF, etc.) including its name, type, owner, sharing status, and modification history.',
|
|
8
|
+
parameters: z.object({
|
|
9
|
+
fileId: z.string().describe('The ID of the file to get information about.'),
|
|
10
|
+
}),
|
|
11
|
+
execute: async (args, { log }) => {
|
|
12
|
+
const drive = await getDriveClient();
|
|
13
|
+
log.info(`Getting info for file: ${args.fileId}`);
|
|
14
|
+
try {
|
|
15
|
+
const response = await drive.files.get({
|
|
16
|
+
fileId: args.fileId,
|
|
17
|
+
fields: 'id,name,description,mimeType,size,createdTime,modifiedTime,webViewLink,owners(displayName,emailAddress),lastModifyingUser(displayName,emailAddress),shared,parents,version',
|
|
18
|
+
supportsAllDrives: true,
|
|
19
|
+
});
|
|
20
|
+
const file = response.data;
|
|
21
|
+
if (!file) {
|
|
22
|
+
throw new UserError(`File with ID ${args.fileId} not found.`);
|
|
23
|
+
}
|
|
24
|
+
const info = {
|
|
25
|
+
id: file.id,
|
|
26
|
+
name: file.name,
|
|
27
|
+
mimeType: file.mimeType,
|
|
28
|
+
createdTime: file.createdTime,
|
|
29
|
+
modifiedTime: file.modifiedTime,
|
|
30
|
+
owner: file.owners?.[0]?.displayName || null,
|
|
31
|
+
lastModifyingUser: file.lastModifyingUser?.displayName || null,
|
|
32
|
+
shared: file.shared || false,
|
|
33
|
+
url: file.webViewLink,
|
|
34
|
+
description: file.description || null,
|
|
35
|
+
};
|
|
36
|
+
return JSON.stringify(info, null, 2);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
log.error(`Error getting file info: ${error.message || error}`);
|
|
40
|
+
if (error.code === 404)
|
|
41
|
+
throw new UserError(`File not found (ID: ${args.fileId}).`);
|
|
42
|
+
if (error.code === 403)
|
|
43
|
+
throw new UserError('Permission denied. Make sure you have access to this file.');
|
|
44
|
+
throw new UserError(`Failed to get file info: ${error.message || 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|