mcp-google-multi 6.0.0-alpha.5 → 6.0.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import type { ToolRegistry } from '../registry.js';
2
2
  export declare function isTextualMime(mimeType: string): boolean;
3
+ export declare function resolveConvertTarget(convertTo: string | undefined): string | undefined;
3
4
  export declare const DRIVE_QUERY_HINT: string;
4
5
  export declare function normalizeDriveQuery(raw: string): string;
5
6
  export declare function isDriveInvalidQuery(error: any): boolean;
@@ -48,6 +48,29 @@ export function isTextualMime(mimeType) {
48
48
  return TEXTUAL_EXACT.has(bare);
49
49
  }
50
50
  const BINARY_READ_HINT = 'Binary content cannot be inlined. Use drive_download to save the file to disk, or drive_export for Google Workspace files.';
51
+ // Accepted alongside the full application/vnd.google-apps.* ids so the obvious
52
+ // short spelling ("document") works; the enum advertises both.
53
+ const CONVERT_SHORTHANDS = {
54
+ document: 'application/vnd.google-apps.document',
55
+ spreadsheet: 'application/vnd.google-apps.spreadsheet',
56
+ presentation: 'application/vnd.google-apps.presentation',
57
+ drawing: 'application/vnd.google-apps.drawing',
58
+ };
59
+ export function resolveConvertTarget(convertTo) {
60
+ if (!convertTo)
61
+ return undefined;
62
+ return CONVERT_SHORTHANDS[convertTo] ?? convertTo;
63
+ }
64
+ const CONVERT_TO_VALUES = [
65
+ 'document',
66
+ 'spreadsheet',
67
+ 'presentation',
68
+ 'drawing',
69
+ 'application/vnd.google-apps.document',
70
+ 'application/vnd.google-apps.spreadsheet',
71
+ 'application/vnd.google-apps.presentation',
72
+ 'application/vnd.google-apps.drawing',
73
+ ];
51
74
  // Comment/Reply fields list — Drive API requires explicit `fields` on every call.
52
75
  const COMMENT_BASE_FIELDS = 'id,kind,content,htmlContent,createdTime,modifiedTime,resolved,anchor,author,deleted,quotedFileContent';
53
76
  const REPLY_SUBFIELDS = 'id,content,action,createdTime,modifiedTime,author,deleted';
@@ -279,15 +302,10 @@ export function registerDriveTools(server) {
279
302
  description: 'Upload a local file to Google Drive. Pass `convertTo` to import it as a native, editable Google Doc/Sheet/Slides/Drawing instead of storing the raw bytes.',
280
303
  inputSchema: {
281
304
  account: accountEnum.describe('Google account alias'),
282
- localPath: z.string().describe('Absolute path to file on disk'),
305
+ localPath: z.string().describe('Absolute path of the SOURCE file on disk to upload (on the machine running the server; this is not savePath)'),
283
306
  filename: z.string().describe('Name as it appears in Drive'),
284
307
  mimeType: z.string().optional().describe('Source MIME type of the local file (inferred from extension if omitted). With `convertTo`, this is the format Drive imports from.'),
285
- convertTo: z.enum([
286
- 'application/vnd.google-apps.document',
287
- 'application/vnd.google-apps.spreadsheet',
288
- 'application/vnd.google-apps.presentation',
289
- 'application/vnd.google-apps.drawing',
290
- ]).optional().describe('Convert the upload into this native Google Workspace type on import (e.g. upload .md/.html/.docx/.txt with convertTo=...google-apps.document to get a real Google Doc). Source must be an importable format. Omit to store the file as-is.'),
308
+ convertTo: z.enum(CONVERT_TO_VALUES).optional().describe('Convert the upload into this native Google Workspace type on import: "document" | "spreadsheet" | "presentation" | "drawing" (full application/vnd.google-apps.* ids also accepted). E.g. upload .md/.html/.docx/.txt with convertTo=document to get a real Google Doc. Source must be an importable format. Omit to store the file as-is.'),
291
309
  parentFolderId: z.string().optional().describe('Parent folder ID (defaults to My Drive root)'),
292
310
  },
293
311
  }, async ({ account, localPath, filename, mimeType: mimeTypeArg, convertTo, parentFolderId }) => {
@@ -301,7 +319,7 @@ export function registerDriveTools(server) {
301
319
  name: filename,
302
320
  parents: parentFolderId ? [parentFolderId] : undefined,
303
321
  // Setting a google-apps target type makes Drive convert the media on import.
304
- ...(convertTo ? { mimeType: convertTo } : {}),
322
+ ...(convertTo ? { mimeType: resolveConvertTarget(convertTo) } : {}),
305
323
  },
306
324
  media: {
307
325
  mimeType: resolvedMime,
@@ -323,14 +341,17 @@ export function registerDriveTools(server) {
323
341
  inputSchema: {
324
342
  account: accountEnum.describe('Google account alias'),
325
343
  fileId: z.string().describe('Google Drive file ID'),
326
- savePath: z.string().describe('Absolute directory path to save into'),
327
- filename: z.string().describe('Filename to save as'),
344
+ savePath: z.string().describe('Absolute DIRECTORY path to save into (created if missing, on the machine running the server); the file name comes from `filename`'),
345
+ filename: z.string().optional().describe('Filename to save as (defaults to the file name in Drive)'),
328
346
  },
329
347
  }, async ({ account, fileId, savePath, filename }) => {
330
348
  try {
331
349
  const auth = await getClient(account);
332
350
  const drive = driveClient({ version: 'v3', auth });
333
- const dest = prepareLocalDest(savePath, filename);
351
+ const name = filename
352
+ ?? (await drive.files.get({ fileId, fields: 'name', supportsAllDrives: true })).data.name
353
+ ?? fileId;
354
+ const dest = prepareLocalDest(savePath, name);
334
355
  const res = await drive.files.get({ fileId, alt: 'media', supportsAllDrives: true }, { responseType: 'stream' });
335
356
  // pipeline destroys both streams on source/sink error; raw .pipe leaks the partial file.
336
357
  await pipeline(res.data, fs.createWriteStream(dest, { mode: 0o600 }));
@@ -349,14 +370,20 @@ export function registerDriveTools(server) {
349
370
  account: accountEnum.describe('Google account alias'),
350
371
  fileId: z.string().describe('Google Drive file ID'),
351
372
  mimeType: z.string().describe('Target export MIME type (e.g. "application/pdf", "text/markdown", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")'),
352
- savePath: z.string().describe('Absolute directory path to save into'),
353
- filename: z.string().describe('Filename to save as'),
373
+ savePath: z.string().describe('Absolute DIRECTORY path to save into (created if missing, on the machine running the server); the file name comes from `filename`'),
374
+ filename: z.string().optional().describe('Filename to save as (defaults to the Drive name plus the extension implied by mimeType)'),
354
375
  },
355
376
  }, async ({ account, fileId, mimeType: exportMime, savePath, filename }) => {
356
377
  try {
357
378
  const auth = await getClient(account);
358
379
  const drive = driveClient({ version: 'v3', auth });
359
- const dest = prepareLocalDest(savePath, filename);
380
+ let name = filename;
381
+ if (!name) {
382
+ const meta = await drive.files.get({ fileId, fields: 'name', supportsAllDrives: true });
383
+ const ext = mime.extension(exportMime);
384
+ name = `${meta.data.name ?? fileId}${ext ? `.${ext}` : ''}`;
385
+ }
386
+ const dest = prepareLocalDest(savePath, name);
360
387
  const res = await drive.files.export({ fileId, mimeType: exportMime }, { responseType: 'stream' });
361
388
  // pipeline destroys both streams on source/sink error; raw .pipe leaks the partial file.
362
389
  await pipeline(res.data, fs.createWriteStream(dest, { mode: 0o600 }));
@@ -404,14 +431,9 @@ export function registerDriveTools(server) {
404
431
  fileId: z.string().describe('Google Drive file ID'),
405
432
  newName: z.string().optional().describe('New filename'),
406
433
  newParentFolderId: z.string().optional().describe('Move to this folder'),
407
- localPath: z.string().optional().describe('Replace file content with this local file'),
434
+ localPath: z.string().optional().describe('Replace file content with this local file (path on the machine running the server)'),
408
435
  mimeType: z.string().optional().describe('MIME type of the replacement file (required if localPath is provided)'),
409
- convertTo: z.enum([
410
- 'application/vnd.google-apps.document',
411
- 'application/vnd.google-apps.spreadsheet',
412
- 'application/vnd.google-apps.presentation',
413
- 'application/vnd.google-apps.drawing',
414
- ]).optional().describe('When replacing content via localPath, convert the new content into this native Google Workspace type on import (e.g. replace a Google Doc body from a local .docx). Source must be an importable format.'),
436
+ convertTo: z.enum(CONVERT_TO_VALUES).optional().describe('When replacing content via localPath, convert the new content into this native Google Workspace type on import: "document" | "spreadsheet" | "presentation" | "drawing" (full application/vnd.google-apps.* ids also accepted).'),
415
437
  },
416
438
  }, async ({ account, fileId, newName, newParentFolderId, localPath: localPathArg, mimeType: mimeTypeArg, convertTo }) => {
417
439
  try {
@@ -437,7 +459,7 @@ export function registerDriveTools(server) {
437
459
  body: await openLocalReadStream(localPathArg),
438
460
  };
439
461
  if (convertTo)
440
- requestBody.mimeType = convertTo;
462
+ requestBody.mimeType = resolveConvertTarget(convertTo);
441
463
  }
442
464
  const res = await drive.files.update(params);
443
465
  return {
@@ -665,7 +665,7 @@ export function registerGmailTools(server) {
665
665
  messageId: z.string().describe('The Gmail message ID'),
666
666
  attachmentId: z.string().describe('The attachment ID from gmail_read response'),
667
667
  filename: z.string().describe('Filename to save as (e.g. report.xlsx)'),
668
- savePath: z.string().describe('Absolute directory path to save into, e.g. /home/user/Downloads'),
668
+ savePath: z.string().describe('Absolute DIRECTORY path to save into (created if missing, on the machine running the server), e.g. /home/user/Downloads; the file name comes from `filename`'),
669
669
  },
670
670
  }, async ({ account, messageId, attachmentId, filename, savePath }) => {
671
671
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-google-multi",
3
- "version": "6.0.0-alpha.5",
3
+ "version": "6.0.0-alpha.6",
4
4
  "description": "Local MCP server for Google Workspace (Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console, +Forms/Chat/Admin) across multiple accounts — OAuth-only, encrypted token storage, deny-by-default writes.",
5
5
  "type": "module",
6
6
  "license": "MIT",