@mcp-z/mcp-drive 1.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 -0
- package/README.md +163 -0
- package/bin/server.js +5 -0
- package/dist/cjs/constants.d.cts +7 -0
- package/dist/cjs/constants.d.ts +7 -0
- package/dist/cjs/constants.js +18 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/index.d.cts +8 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.js +314 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/create-store.d.cts +2 -0
- package/dist/cjs/lib/create-store.d.ts +2 -0
- package/dist/cjs/lib/create-store.js +166 -0
- package/dist/cjs/lib/create-store.js.map +1 -0
- package/dist/cjs/lib/query-builder.d.cts +45 -0
- package/dist/cjs/lib/query-builder.d.ts +45 -0
- package/dist/cjs/lib/query-builder.js +219 -0
- package/dist/cjs/lib/query-builder.js.map +1 -0
- package/dist/cjs/mcp/index.d.cts +3 -0
- package/dist/cjs/mcp/index.d.ts +3 -0
- package/dist/cjs/mcp/index.js +66 -0
- package/dist/cjs/mcp/index.js.map +1 -0
- package/dist/cjs/mcp/prompts/index.d.cts +2 -0
- package/dist/cjs/mcp/prompts/index.d.ts +2 -0
- package/dist/cjs/mcp/prompts/index.js +26 -0
- package/dist/cjs/mcp/prompts/index.js.map +1 -0
- package/dist/cjs/mcp/prompts/organize-files.d.cts +16 -0
- package/dist/cjs/mcp/prompts/organize-files.d.ts +16 -0
- package/dist/cjs/mcp/prompts/organize-files.js +169 -0
- package/dist/cjs/mcp/prompts/organize-files.js.map +1 -0
- package/dist/cjs/mcp/prompts/query-syntax.d.cts +19 -0
- package/dist/cjs/mcp/prompts/query-syntax.d.ts +19 -0
- package/dist/cjs/mcp/prompts/query-syntax.js +169 -0
- package/dist/cjs/mcp/prompts/query-syntax.js.map +1 -0
- package/dist/cjs/mcp/resources/file.d.cts +9 -0
- package/dist/cjs/mcp/resources/file.d.ts +9 -0
- package/dist/cjs/mcp/resources/file.js +247 -0
- package/dist/cjs/mcp/resources/file.js.map +1 -0
- package/dist/cjs/mcp/resources/index.d.cts +1 -0
- package/dist/cjs/mcp/resources/index.d.ts +1 -0
- package/dist/cjs/mcp/resources/index.js +17 -0
- package/dist/cjs/mcp/resources/index.js.map +1 -0
- package/dist/cjs/mcp/tools/file-move-to-trash.d.cts +59 -0
- package/dist/cjs/mcp/tools/file-move-to-trash.d.ts +59 -0
- package/dist/cjs/mcp/tools/file-move-to-trash.js +334 -0
- package/dist/cjs/mcp/tools/file-move-to-trash.js.map +1 -0
- package/dist/cjs/mcp/tools/file-move.d.cts +73 -0
- package/dist/cjs/mcp/tools/file-move.d.ts +73 -0
- package/dist/cjs/mcp/tools/file-move.js +613 -0
- package/dist/cjs/mcp/tools/file-move.js.map +1 -0
- package/dist/cjs/mcp/tools/files-search.d.cts +135 -0
- package/dist/cjs/mcp/tools/files-search.d.ts +135 -0
- package/dist/cjs/mcp/tools/files-search.js +558 -0
- package/dist/cjs/mcp/tools/files-search.js.map +1 -0
- package/dist/cjs/mcp/tools/folder-contents.d.cts +139 -0
- package/dist/cjs/mcp/tools/folder-contents.d.ts +139 -0
- package/dist/cjs/mcp/tools/folder-contents.js +513 -0
- package/dist/cjs/mcp/tools/folder-contents.js.map +1 -0
- package/dist/cjs/mcp/tools/folder-create.d.cts +59 -0
- package/dist/cjs/mcp/tools/folder-create.d.ts +59 -0
- package/dist/cjs/mcp/tools/folder-create.js +368 -0
- package/dist/cjs/mcp/tools/folder-create.js.map +1 -0
- package/dist/cjs/mcp/tools/folder-path.d.cts +49 -0
- package/dist/cjs/mcp/tools/folder-path.d.ts +49 -0
- package/dist/cjs/mcp/tools/folder-path.js +367 -0
- package/dist/cjs/mcp/tools/folder-path.js.map +1 -0
- package/dist/cjs/mcp/tools/folder-search.d.cts +139 -0
- package/dist/cjs/mcp/tools/folder-search.d.ts +139 -0
- package/dist/cjs/mcp/tools/folder-search.js +760 -0
- package/dist/cjs/mcp/tools/folder-search.js.map +1 -0
- package/dist/cjs/mcp/tools/index.d.cts +7 -0
- package/dist/cjs/mcp/tools/index.d.ts +7 -0
- package/dist/cjs/mcp/tools/index.js +46 -0
- package/dist/cjs/mcp/tools/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/schemas/drive-query-schema.d.cts +40 -0
- package/dist/cjs/schemas/drive-query-schema.d.ts +40 -0
- package/dist/cjs/schemas/drive-query-schema.js +90 -0
- package/dist/cjs/schemas/drive-query-schema.js.map +1 -0
- package/dist/cjs/schemas/drive-validation.d.cts +48 -0
- package/dist/cjs/schemas/drive-validation.d.ts +48 -0
- package/dist/cjs/schemas/drive-validation.js +96 -0
- package/dist/cjs/schemas/drive-validation.js.map +1 -0
- package/dist/cjs/schemas/index.d.cts +2 -0
- package/dist/cjs/schemas/index.d.ts +2 -0
- package/dist/cjs/schemas/index.js +20 -0
- package/dist/cjs/schemas/index.js.map +1 -0
- package/dist/cjs/setup/config.d.cts +44 -0
- package/dist/cjs/setup/config.d.ts +44 -0
- package/dist/cjs/setup/config.js +201 -0
- package/dist/cjs/setup/config.js.map +1 -0
- package/dist/cjs/setup/http.d.cts +8 -0
- package/dist/cjs/setup/http.d.ts +8 -0
- package/dist/cjs/setup/http.js +260 -0
- package/dist/cjs/setup/http.js.map +1 -0
- package/dist/cjs/setup/index.d.cts +5 -0
- package/dist/cjs/setup/index.d.ts +5 -0
- package/dist/cjs/setup/index.js +46 -0
- package/dist/cjs/setup/index.js.map +1 -0
- package/dist/cjs/setup/oauth-google.d.cts +64 -0
- package/dist/cjs/setup/oauth-google.d.ts +64 -0
- package/dist/cjs/setup/oauth-google.js +347 -0
- package/dist/cjs/setup/oauth-google.js.map +1 -0
- package/dist/cjs/setup/runtime.d.cts +10 -0
- package/dist/cjs/setup/runtime.d.ts +10 -0
- package/dist/cjs/setup/runtime.js +353 -0
- package/dist/cjs/setup/runtime.js.map +1 -0
- package/dist/cjs/setup/stdio.d.cts +7 -0
- package/dist/cjs/setup/stdio.d.ts +7 -0
- package/dist/cjs/setup/stdio.js +239 -0
- package/dist/cjs/setup/stdio.js.map +1 -0
- package/dist/cjs/types.d.cts +45 -0
- package/dist/cjs/types.d.ts +45 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/constants.d.ts +7 -0
- package/dist/esm/constants.js +7 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +34 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/create-store.d.ts +2 -0
- package/dist/esm/lib/create-store.js +6 -0
- package/dist/esm/lib/create-store.js.map +1 -0
- package/dist/esm/lib/query-builder.d.ts +45 -0
- package/dist/esm/lib/query-builder.js +184 -0
- package/dist/esm/lib/query-builder.js.map +1 -0
- package/dist/esm/mcp/index.d.ts +3 -0
- package/dist/esm/mcp/index.js +6 -0
- package/dist/esm/mcp/index.js.map +1 -0
- package/dist/esm/mcp/prompts/index.d.ts +2 -0
- package/dist/esm/mcp/prompts/index.js +2 -0
- package/dist/esm/mcp/prompts/index.js.map +1 -0
- package/dist/esm/mcp/prompts/organize-files.d.ts +16 -0
- package/dist/esm/mcp/prompts/organize-files.js +21 -0
- package/dist/esm/mcp/prompts/organize-files.js.map +1 -0
- package/dist/esm/mcp/prompts/query-syntax.d.ts +19 -0
- package/dist/esm/mcp/prompts/query-syntax.js +82 -0
- package/dist/esm/mcp/prompts/query-syntax.js.map +1 -0
- package/dist/esm/mcp/resources/file.d.ts +9 -0
- package/dist/esm/mcp/resources/file.js +77 -0
- package/dist/esm/mcp/resources/file.js.map +1 -0
- package/dist/esm/mcp/resources/index.d.ts +1 -0
- package/dist/esm/mcp/resources/index.js +1 -0
- package/dist/esm/mcp/resources/index.js.map +1 -0
- package/dist/esm/mcp/tools/file-move-to-trash.d.ts +59 -0
- package/dist/esm/mcp/tools/file-move-to-trash.js +118 -0
- package/dist/esm/mcp/tools/file-move-to-trash.js.map +1 -0
- package/dist/esm/mcp/tools/file-move.d.ts +73 -0
- package/dist/esm/mcp/tools/file-move.js +274 -0
- package/dist/esm/mcp/tools/file-move.js.map +1 -0
- package/dist/esm/mcp/tools/files-search.d.ts +135 -0
- package/dist/esm/mcp/tools/files-search.js +254 -0
- package/dist/esm/mcp/tools/files-search.js.map +1 -0
- package/dist/esm/mcp/tools/folder-contents.d.ts +139 -0
- package/dist/esm/mcp/tools/folder-contents.js +214 -0
- package/dist/esm/mcp/tools/folder-contents.js.map +1 -0
- package/dist/esm/mcp/tools/folder-create.d.ts +59 -0
- package/dist/esm/mcp/tools/folder-create.js +140 -0
- package/dist/esm/mcp/tools/folder-create.js.map +1 -0
- package/dist/esm/mcp/tools/folder-path.d.ts +49 -0
- package/dist/esm/mcp/tools/folder-path.js +147 -0
- package/dist/esm/mcp/tools/folder-path.js.map +1 -0
- package/dist/esm/mcp/tools/folder-search.d.ts +139 -0
- package/dist/esm/mcp/tools/folder-search.js +343 -0
- package/dist/esm/mcp/tools/folder-search.js.map +1 -0
- package/dist/esm/mcp/tools/index.d.ts +7 -0
- package/dist/esm/mcp/tools/index.js +7 -0
- package/dist/esm/mcp/tools/index.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/schemas/drive-query-schema.d.ts +40 -0
- package/dist/esm/schemas/drive-query-schema.js +84 -0
- package/dist/esm/schemas/drive-query-schema.js.map +1 -0
- package/dist/esm/schemas/drive-validation.d.ts +48 -0
- package/dist/esm/schemas/drive-validation.js +73 -0
- package/dist/esm/schemas/drive-validation.js.map +1 -0
- package/dist/esm/schemas/index.d.ts +2 -0
- package/dist/esm/schemas/index.js +2 -0
- package/dist/esm/schemas/index.js.map +1 -0
- package/dist/esm/setup/config.d.ts +44 -0
- package/dist/esm/setup/config.js +151 -0
- package/dist/esm/setup/config.js.map +1 -0
- package/dist/esm/setup/http.d.ts +8 -0
- package/dist/esm/setup/http.js +54 -0
- package/dist/esm/setup/http.js.map +1 -0
- package/dist/esm/setup/index.d.ts +5 -0
- package/dist/esm/setup/index.js +5 -0
- package/dist/esm/setup/index.js.map +1 -0
- package/dist/esm/setup/oauth-google.d.ts +64 -0
- package/dist/esm/setup/oauth-google.js +168 -0
- package/dist/esm/setup/oauth-google.js.map +1 -0
- package/dist/esm/setup/runtime.d.ts +10 -0
- package/dist/esm/setup/runtime.js +84 -0
- package/dist/esm/setup/runtime.js.map +1 -0
- package/dist/esm/setup/stdio.d.ts +7 -0
- package/dist/esm/setup/stdio.js +38 -0
- package/dist/esm/setup/stdio.js.map +1 -0
- package/dist/esm/types.d.ts +45 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { google } from 'googleapis';
|
|
3
|
+
export default function createResource() {
|
|
4
|
+
const template = new ResourceTemplate('drive://files/{fileId}', {
|
|
5
|
+
list: undefined
|
|
6
|
+
});
|
|
7
|
+
const config = {
|
|
8
|
+
description: 'Drive file resource',
|
|
9
|
+
mimeType: 'application/json'
|
|
10
|
+
};
|
|
11
|
+
const handler = async (uri, variables, extra)=>{
|
|
12
|
+
// Extract fileId and handle both string and string[] cases
|
|
13
|
+
const fileId = Array.isArray(variables.fileId) ? variables.fileId[0] : variables.fileId;
|
|
14
|
+
try {
|
|
15
|
+
var _logger_debug;
|
|
16
|
+
// Validate fileId exists and is a string
|
|
17
|
+
if (!fileId || typeof fileId !== 'string') {
|
|
18
|
+
return {
|
|
19
|
+
contents: [
|
|
20
|
+
{
|
|
21
|
+
uri: uri.href,
|
|
22
|
+
mimeType: 'application/json',
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
error: 'Missing or invalid fileId in resource URI'
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Safe type guard to access middleware-enriched extra
|
|
31
|
+
const { logger, authContext } = extra;
|
|
32
|
+
const drive = google.drive({
|
|
33
|
+
version: 'v3',
|
|
34
|
+
auth: authContext.auth
|
|
35
|
+
});
|
|
36
|
+
const resp = await drive.files.get({
|
|
37
|
+
fileId,
|
|
38
|
+
fields: 'id,name,mimeType,size,modifiedTime,owners,webViewLink'
|
|
39
|
+
});
|
|
40
|
+
const data = resp.data;
|
|
41
|
+
(_logger_debug = logger.debug) === null || _logger_debug === void 0 ? void 0 : _logger_debug.call(logger, {
|
|
42
|
+
fileId,
|
|
43
|
+
fileName: data === null || data === void 0 ? void 0 : data.name
|
|
44
|
+
}, 'drive-file resource fetch success');
|
|
45
|
+
return {
|
|
46
|
+
contents: [
|
|
47
|
+
{
|
|
48
|
+
uri: uri.href,
|
|
49
|
+
mimeType: 'application/json',
|
|
50
|
+
text: JSON.stringify(data || {})
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
var _logger_debug1;
|
|
56
|
+
const { logger } = extra;
|
|
57
|
+
(_logger_debug1 = logger.debug) === null || _logger_debug1 === void 0 ? void 0 : _logger_debug1.call(logger, error, 'drive resource fetch failed');
|
|
58
|
+
return {
|
|
59
|
+
contents: [
|
|
60
|
+
{
|
|
61
|
+
uri: uri.href,
|
|
62
|
+
mimeType: 'application/json',
|
|
63
|
+
text: JSON.stringify({
|
|
64
|
+
error: error.message
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
name: 'file',
|
|
73
|
+
template,
|
|
74
|
+
config,
|
|
75
|
+
handler
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/servers/mcp-drive/src/mcp/resources/file.ts"],"sourcesContent":["import type { EnrichedExtra } from '@mcp-z/oauth-google';\nimport type { ResourceConfig, ResourceModule } from '@mcp-z/server';\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { ReadResourceResult, ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js';\nimport { google } from 'googleapis';\n\nexport default function createResource() {\n const template = new ResourceTemplate('drive://files/{fileId}', {\n list: undefined,\n });\n const config: ResourceConfig = {\n description: 'Drive file resource',\n mimeType: 'application/json',\n };\n\n const handler = async (uri: URL, variables: Record<string, string | string[]>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>): Promise<ReadResourceResult> => {\n // Extract fileId and handle both string and string[] cases\n const fileId = Array.isArray(variables.fileId) ? variables.fileId[0] : variables.fileId;\n\n try {\n // Validate fileId exists and is a string\n if (!fileId || typeof fileId !== 'string') {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify({\n error: 'Missing or invalid fileId in resource URI',\n }),\n },\n ],\n };\n }\n\n // Safe type guard to access middleware-enriched extra\n const { logger, authContext } = extra as unknown as EnrichedExtra;\n const drive = google.drive({ version: 'v3', auth: authContext.auth });\n const resp = await drive.files.get({\n fileId,\n fields: 'id,name,mimeType,size,modifiedTime,owners,webViewLink',\n });\n const data = resp.data;\n logger.debug?.({ fileId, fileName: data?.name }, 'drive-file resource fetch success');\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(data || {}),\n },\n ],\n };\n } catch (error) {\n const { logger } = extra as unknown as EnrichedExtra;\n logger.debug?.(error as Record<string, unknown>, 'drive resource fetch failed');\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify({ error: (error as Error).message }),\n },\n ],\n };\n }\n };\n\n return {\n name: 'file',\n template,\n config,\n handler,\n } satisfies ResourceModule;\n}\n"],"names":["ResourceTemplate","google","createResource","template","list","undefined","config","description","mimeType","handler","uri","variables","extra","fileId","Array","isArray","logger","contents","href","text","JSON","stringify","error","authContext","drive","version","auth","resp","files","get","fields","data","debug","fileName","name","message"],"mappings":"AAEA,SAASA,gBAAgB,QAAQ,0CAA0C;AAG3E,SAASC,MAAM,QAAQ,aAAa;AAEpC,eAAe,SAASC;IACtB,MAAMC,WAAW,IAAIH,iBAAiB,0BAA0B;QAC9DI,MAAMC;IACR;IACA,MAAMC,SAAyB;QAC7BC,aAAa;QACbC,UAAU;IACZ;IAEA,MAAMC,UAAU,OAAOC,KAAUC,WAA8CC;QAC7E,2DAA2D;QAC3D,MAAMC,SAASC,MAAMC,OAAO,CAACJ,UAAUE,MAAM,IAAIF,UAAUE,MAAM,CAAC,EAAE,GAAGF,UAAUE,MAAM;QAEvF,IAAI;gBAwBFG;YAvBA,yCAAyC;YACzC,IAAI,CAACH,UAAU,OAAOA,WAAW,UAAU;gBACzC,OAAO;oBACLI,UAAU;wBACR;4BACEP,KAAKA,IAAIQ,IAAI;4BACbV,UAAU;4BACVW,MAAMC,KAAKC,SAAS,CAAC;gCACnBC,OAAO;4BACT;wBACF;qBACD;gBACH;YACF;YAEA,sDAAsD;YACtD,MAAM,EAAEN,MAAM,EAAEO,WAAW,EAAE,GAAGX;YAChC,MAAMY,QAAQvB,OAAOuB,KAAK,CAAC;gBAAEC,SAAS;gBAAMC,MAAMH,YAAYG,IAAI;YAAC;YACnE,MAAMC,OAAO,MAAMH,MAAMI,KAAK,CAACC,GAAG,CAAC;gBACjChB;gBACAiB,QAAQ;YACV;YACA,MAAMC,OAAOJ,KAAKI,IAAI;aACtBf,gBAAAA,OAAOgB,KAAK,cAAZhB,oCAAAA,mBAAAA,QAAe;gBAAEH;gBAAQoB,QAAQ,EAAEF,iBAAAA,2BAAAA,KAAMG,IAAI;YAAC,GAAG;YACjD,OAAO;gBACLjB,UAAU;oBACR;wBACEP,KAAKA,IAAIQ,IAAI;wBACbV,UAAU;wBACVW,MAAMC,KAAKC,SAAS,CAACU,QAAQ,CAAC;oBAChC;iBACD;YACH;QACF,EAAE,OAAOT,OAAO;gBAEdN;YADA,MAAM,EAAEA,MAAM,EAAE,GAAGJ;aACnBI,iBAAAA,OAAOgB,KAAK,cAAZhB,qCAAAA,oBAAAA,QAAeM,OAAkC;YACjD,OAAO;gBACLL,UAAU;oBACR;wBACEP,KAAKA,IAAIQ,IAAI;wBACbV,UAAU;wBACVW,MAAMC,KAAKC,SAAS,CAAC;4BAAEC,OAAO,AAACA,MAAgBa,OAAO;wBAAC;oBACzD;iBACD;YACH;QACF;IACF;IAEA,OAAO;QACLD,MAAM;QACN/B;QACAG;QACAG;IACF;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as file } from './file.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as file } from './file.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/servers/mcp-drive/src/mcp/resources/index.ts"],"sourcesContent":["export { default as file } from './file.js';\n"],"names":["default","file"],"mappings":"AAAA,SAASA,WAAWC,IAAI,QAAQ,YAAY"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { EnrichedExtra } from '@mcp-z/oauth-google';
|
|
2
|
+
import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
declare const inputSchema: z.ZodObject<{
|
|
5
|
+
ids: z.ZodArray<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
declare const outputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
8
|
+
type: z.ZodLiteral<"success">;
|
|
9
|
+
operationSummary: z.ZodString;
|
|
10
|
+
totalCount: z.ZodNumber;
|
|
11
|
+
successCount: z.ZodNumber;
|
|
12
|
+
failureCount: z.ZodNumber;
|
|
13
|
+
recoverable: z.ZodBoolean;
|
|
14
|
+
recoverableDays: z.ZodNumber;
|
|
15
|
+
failures: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
error: z.ZodString;
|
|
18
|
+
}, z.core.$strip>>>;
|
|
19
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
20
|
+
type: z.ZodLiteral<"auth_required">;
|
|
21
|
+
provider: z.ZodString;
|
|
22
|
+
message: z.ZodString;
|
|
23
|
+
url: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>], "type">;
|
|
25
|
+
export type Input = z.infer<typeof inputSchema>;
|
|
26
|
+
export type Output = z.infer<typeof outputSchema>;
|
|
27
|
+
declare function handler({ ids }: Input, extra: EnrichedExtra): Promise<CallToolResult>;
|
|
28
|
+
export default function createTool(): {
|
|
29
|
+
name: "file-move-to-trash";
|
|
30
|
+
config: {
|
|
31
|
+
readonly title: "Move Files to Trash";
|
|
32
|
+
readonly description: "Move files to trash (recoverable for 30 days).";
|
|
33
|
+
readonly inputSchema: z.ZodObject<{
|
|
34
|
+
ids: z.ZodArray<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
readonly outputSchema: z.ZodObject<{
|
|
37
|
+
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
38
|
+
type: z.ZodLiteral<"success">;
|
|
39
|
+
operationSummary: z.ZodString;
|
|
40
|
+
totalCount: z.ZodNumber;
|
|
41
|
+
successCount: z.ZodNumber;
|
|
42
|
+
failureCount: z.ZodNumber;
|
|
43
|
+
recoverable: z.ZodBoolean;
|
|
44
|
+
recoverableDays: z.ZodNumber;
|
|
45
|
+
failures: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
error: z.ZodString;
|
|
48
|
+
}, z.core.$strip>>>;
|
|
49
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
50
|
+
type: z.ZodLiteral<"auth_required">;
|
|
51
|
+
provider: z.ZodString;
|
|
52
|
+
message: z.ZodString;
|
|
53
|
+
url: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>], "type">;
|
|
55
|
+
}, z.core.$strip>;
|
|
56
|
+
};
|
|
57
|
+
handler: typeof handler;
|
|
58
|
+
};
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { schemas } from '@mcp-z/oauth-google';
|
|
2
|
+
const { AuthRequiredBranchSchema } = schemas;
|
|
3
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { google } from 'googleapis';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const MAX_BATCH_SIZE = 1000;
|
|
7
|
+
const inputSchema = z.object({
|
|
8
|
+
ids: z.array(z.string().min(1)).min(1).max(MAX_BATCH_SIZE).describe('File IDs to soft delete (move to trash)')
|
|
9
|
+
});
|
|
10
|
+
// Success branch schema
|
|
11
|
+
const successBranchSchema = z.object({
|
|
12
|
+
type: z.literal('success'),
|
|
13
|
+
operationSummary: z.string().describe('Human-readable summary of the operation'),
|
|
14
|
+
totalCount: z.number().describe('Total number of files requested to trash'),
|
|
15
|
+
successCount: z.number().describe('Number of files successfully moved to trash'),
|
|
16
|
+
failureCount: z.number().describe('Number of files that failed to move'),
|
|
17
|
+
recoverable: z.boolean().describe('Whether trashed files can be restored'),
|
|
18
|
+
recoverableDays: z.number().describe('Days until permanent deletion'),
|
|
19
|
+
failures: z.array(z.object({
|
|
20
|
+
id: z.string().describe('ID of the file that failed'),
|
|
21
|
+
error: z.string().describe('Error message explaining the failure')
|
|
22
|
+
})).optional().describe('Details of any files that failed to trash')
|
|
23
|
+
});
|
|
24
|
+
// Output schema with auth_required support
|
|
25
|
+
const outputSchema = z.discriminatedUnion('type', [
|
|
26
|
+
successBranchSchema,
|
|
27
|
+
AuthRequiredBranchSchema
|
|
28
|
+
]);
|
|
29
|
+
const config = {
|
|
30
|
+
title: 'Move Files to Trash',
|
|
31
|
+
description: 'Move files to trash (recoverable for 30 days).',
|
|
32
|
+
inputSchema: inputSchema,
|
|
33
|
+
outputSchema: z.object({
|
|
34
|
+
result: outputSchema
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
async function handler({ ids }, extra) {
|
|
38
|
+
const logger = extra.logger;
|
|
39
|
+
logger.info('drive.file.moveToTrash called', {
|
|
40
|
+
count: ids.length
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const drive = google.drive({
|
|
44
|
+
version: 'v3',
|
|
45
|
+
auth: extra.authContext.auth
|
|
46
|
+
});
|
|
47
|
+
const results = await Promise.allSettled(ids.map(async (id)=>{
|
|
48
|
+
await drive.files.update({
|
|
49
|
+
fileId: id,
|
|
50
|
+
requestBody: {
|
|
51
|
+
trashed: true
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return id;
|
|
55
|
+
}));
|
|
56
|
+
// Separate successes and failures
|
|
57
|
+
const failures = [];
|
|
58
|
+
results.forEach((result, index)=>{
|
|
59
|
+
const id = ids[index];
|
|
60
|
+
if (!id) return;
|
|
61
|
+
if (result.status === 'rejected') {
|
|
62
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
63
|
+
failures.push({
|
|
64
|
+
id,
|
|
65
|
+
error: errorMessage
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const successCount = ids.length - failures.length;
|
|
70
|
+
const failureCount = failures.length;
|
|
71
|
+
const totalCount = ids.length;
|
|
72
|
+
logger.info('drive.file.moveToTrash completed', {
|
|
73
|
+
totalCount,
|
|
74
|
+
successCount,
|
|
75
|
+
failureCount
|
|
76
|
+
});
|
|
77
|
+
const operationSummary = failureCount === 0 ? `Moved ${successCount} file${successCount === 1 ? '' : 's'} to trash (recoverable for 30 days)` : `Moved ${successCount} of ${totalCount} file${totalCount === 1 ? '' : 's'} to trash (${failureCount} failed, recoverable for 30 days)`;
|
|
78
|
+
const result = {
|
|
79
|
+
type: 'success',
|
|
80
|
+
operationSummary,
|
|
81
|
+
totalCount,
|
|
82
|
+
successCount,
|
|
83
|
+
failureCount,
|
|
84
|
+
recoverable: true,
|
|
85
|
+
recoverableDays: 30,
|
|
86
|
+
...failures.length > 0 && {
|
|
87
|
+
failures
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: JSON.stringify(result)
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
structuredContent: {
|
|
98
|
+
result
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
+
logger.error('drive.file.moveToTrash error', {
|
|
104
|
+
error: message
|
|
105
|
+
});
|
|
106
|
+
// Throw McpError
|
|
107
|
+
throw new McpError(ErrorCode.InternalError, `Error moving files to trash: ${message}`, {
|
|
108
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export default function createTool() {
|
|
113
|
+
return {
|
|
114
|
+
name: 'file-move-to-trash',
|
|
115
|
+
config,
|
|
116
|
+
handler
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/servers/mcp-drive/src/mcp/tools/file-move-to-trash.ts"],"sourcesContent":["import type { EnrichedExtra } from '@mcp-z/oauth-google';\nimport { schemas } from '@mcp-z/oauth-google';\n\nconst { AuthRequiredBranchSchema } = schemas;\n\nimport { type CallToolResult, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';\nimport { google } from 'googleapis';\nimport { z } from 'zod';\n\nconst MAX_BATCH_SIZE = 1000;\n\nconst inputSchema = z.object({\n ids: z.array(z.string().min(1)).min(1).max(MAX_BATCH_SIZE).describe('File IDs to soft delete (move to trash)'),\n});\n\n// Success branch schema\nconst successBranchSchema = z.object({\n type: z.literal('success'),\n operationSummary: z.string().describe('Human-readable summary of the operation'),\n totalCount: z.number().describe('Total number of files requested to trash'),\n successCount: z.number().describe('Number of files successfully moved to trash'),\n failureCount: z.number().describe('Number of files that failed to move'),\n recoverable: z.boolean().describe('Whether trashed files can be restored'),\n recoverableDays: z.number().describe('Days until permanent deletion'),\n failures: z\n .array(\n z.object({\n id: z.string().describe('ID of the file that failed'),\n error: z.string().describe('Error message explaining the failure'),\n })\n )\n .optional()\n .describe('Details of any files that failed to trash'),\n});\n\n// Output schema with auth_required support\nconst outputSchema = z.discriminatedUnion('type', [successBranchSchema, AuthRequiredBranchSchema]);\n\nconst config = {\n title: 'Move Files to Trash',\n description: 'Move files to trash (recoverable for 30 days).',\n inputSchema: inputSchema,\n outputSchema: z.object({\n result: outputSchema,\n }),\n} as const;\n\nexport type Input = z.infer<typeof inputSchema>;\nexport type Output = z.infer<typeof outputSchema>;\n\nasync function handler({ ids }: Input, extra: EnrichedExtra): Promise<CallToolResult> {\n const logger = extra.logger;\n logger.info('drive.file.moveToTrash called', { count: ids.length });\n\n try {\n const drive = google.drive({ version: 'v3', auth: extra.authContext.auth });\n\n const results = await Promise.allSettled(\n ids.map(async (id) => {\n await drive.files.update({\n fileId: id,\n requestBody: { trashed: true },\n });\n return id;\n })\n );\n\n // Separate successes and failures\n const failures: Array<{ id: string; error: string }> = [];\n\n results.forEach((result, index) => {\n const id = ids[index];\n if (!id) return;\n\n if (result.status === 'rejected') {\n const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);\n failures.push({ id, error: errorMessage });\n }\n });\n\n const successCount = ids.length - failures.length;\n const failureCount = failures.length;\n const totalCount = ids.length;\n\n logger.info('drive.file.moveToTrash completed', {\n totalCount,\n successCount,\n failureCount,\n });\n\n const operationSummary = failureCount === 0 ? `Moved ${successCount} file${successCount === 1 ? '' : 's'} to trash (recoverable for 30 days)` : `Moved ${successCount} of ${totalCount} file${totalCount === 1 ? '' : 's'} to trash (${failureCount} failed, recoverable for 30 days)`;\n\n const result: Output = {\n type: 'success' as const,\n operationSummary,\n totalCount,\n successCount,\n failureCount,\n recoverable: true,\n recoverableDays: 30,\n ...(failures.length > 0 && { failures }),\n };\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result),\n },\n ],\n structuredContent: { result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error('drive.file.moveToTrash error', { error: message });\n\n // Throw McpError\n throw new McpError(ErrorCode.InternalError, `Error moving files to trash: ${message}`, {\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n}\n\nexport default function createTool() {\n return {\n name: 'file-move-to-trash' as const,\n config,\n handler,\n };\n}\n"],"names":["schemas","AuthRequiredBranchSchema","ErrorCode","McpError","google","z","MAX_BATCH_SIZE","inputSchema","object","ids","array","string","min","max","describe","successBranchSchema","type","literal","operationSummary","totalCount","number","successCount","failureCount","recoverable","boolean","recoverableDays","failures","id","error","optional","outputSchema","discriminatedUnion","config","title","description","result","handler","extra","logger","info","count","length","drive","version","auth","authContext","results","Promise","allSettled","map","files","update","fileId","requestBody","trashed","forEach","index","status","errorMessage","reason","Error","message","String","push","content","text","JSON","stringify","structuredContent","InternalError","stack","undefined","createTool","name"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { EnrichedExtra } from '@mcp-z/oauth-google';
|
|
2
|
+
import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
declare const inputSchema: z.ZodObject<{
|
|
5
|
+
fileIds: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
6
|
+
destinationFolderId: z.ZodString;
|
|
7
|
+
returnOldParents: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
declare const outputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
10
|
+
type: z.ZodLiteral<"success">;
|
|
11
|
+
moved: z.ZodArray<z.ZodObject<{
|
|
12
|
+
fileId: z.ZodString;
|
|
13
|
+
fileName: z.ZodString;
|
|
14
|
+
oldParents: z.ZodArray<z.ZodString>;
|
|
15
|
+
newParent: z.ZodString;
|
|
16
|
+
webViewLink: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, z.core.$strip>>;
|
|
18
|
+
failed: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
19
|
+
fileId: z.ZodString;
|
|
20
|
+
error: z.ZodString;
|
|
21
|
+
code: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strip>>>;
|
|
23
|
+
totalRequested: z.ZodNumber;
|
|
24
|
+
totalMoved: z.ZodNumber;
|
|
25
|
+
totalFailed: z.ZodNumber;
|
|
26
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
27
|
+
type: z.ZodLiteral<"auth_required">;
|
|
28
|
+
provider: z.ZodString;
|
|
29
|
+
message: z.ZodString;
|
|
30
|
+
url: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>], "type">;
|
|
32
|
+
export type Input = z.infer<typeof inputSchema>;
|
|
33
|
+
export type Output = z.infer<typeof outputSchema>;
|
|
34
|
+
declare function handler({ fileIds, destinationFolderId, returnOldParents }: Input, extra: EnrichedExtra): Promise<CallToolResult>;
|
|
35
|
+
export default function createTool(): {
|
|
36
|
+
name: "file-move";
|
|
37
|
+
config: {
|
|
38
|
+
readonly title: "Move Files";
|
|
39
|
+
readonly description: "Move files/folders to destination folder. Returns oldParents for undo. Use \"root\" for My Drive root.";
|
|
40
|
+
readonly inputSchema: z.ZodObject<{
|
|
41
|
+
fileIds: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
|
|
42
|
+
destinationFolderId: z.ZodString;
|
|
43
|
+
returnOldParents: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
readonly outputSchema: z.ZodObject<{
|
|
46
|
+
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
47
|
+
type: z.ZodLiteral<"success">;
|
|
48
|
+
moved: z.ZodArray<z.ZodObject<{
|
|
49
|
+
fileId: z.ZodString;
|
|
50
|
+
fileName: z.ZodString;
|
|
51
|
+
oldParents: z.ZodArray<z.ZodString>;
|
|
52
|
+
newParent: z.ZodString;
|
|
53
|
+
webViewLink: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>>;
|
|
55
|
+
failed: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
56
|
+
fileId: z.ZodString;
|
|
57
|
+
error: z.ZodString;
|
|
58
|
+
code: z.ZodOptional<z.ZodString>;
|
|
59
|
+
}, z.core.$strip>>>;
|
|
60
|
+
totalRequested: z.ZodNumber;
|
|
61
|
+
totalMoved: z.ZodNumber;
|
|
62
|
+
totalFailed: z.ZodNumber;
|
|
63
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
64
|
+
type: z.ZodLiteral<"auth_required">;
|
|
65
|
+
provider: z.ZodString;
|
|
66
|
+
message: z.ZodString;
|
|
67
|
+
url: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, z.core.$strip>], "type">;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
};
|
|
71
|
+
handler: typeof handler;
|
|
72
|
+
};
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { schemas } from '@mcp-z/oauth-google';
|
|
2
|
+
const { AuthRequiredBranchSchema } = schemas;
|
|
3
|
+
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { google } from 'googleapis';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
function isDriveApiError(error) {
|
|
7
|
+
return typeof error === 'object' && error !== null && ('message' in error || 'code' in error);
|
|
8
|
+
}
|
|
9
|
+
const inputSchema = z.object({
|
|
10
|
+
fileIds: z.union([
|
|
11
|
+
z.string().min(1),
|
|
12
|
+
z.array(z.string().min(1)).min(1).max(100)
|
|
13
|
+
]).describe('File or folder ID(s) to move. Single ID or array (max 100 for batch)'),
|
|
14
|
+
destinationFolderId: z.string().min(1).describe('Destination folder ID (use "root" for My Drive root)'),
|
|
15
|
+
returnOldParents: z.boolean().optional().describe('Include old parent IDs in response for manual undo (recommended: true)')
|
|
16
|
+
});
|
|
17
|
+
// Success branch schema
|
|
18
|
+
const successBranchSchema = z.object({
|
|
19
|
+
type: z.literal('success'),
|
|
20
|
+
moved: z.array(z.object({
|
|
21
|
+
fileId: z.string().describe('ID of the moved file'),
|
|
22
|
+
fileName: z.string().describe('Name of the moved file'),
|
|
23
|
+
oldParents: z.array(z.string()).describe('Previous parent folder IDs (for undo)'),
|
|
24
|
+
newParent: z.string().describe('New parent folder ID'),
|
|
25
|
+
webViewLink: z.string().optional().describe('URL to view the file')
|
|
26
|
+
})).describe('Successfully moved files'),
|
|
27
|
+
failed: z.array(z.object({
|
|
28
|
+
fileId: z.string().describe('ID of the file that failed to move'),
|
|
29
|
+
error: z.string().describe('Error message explaining the failure'),
|
|
30
|
+
code: z.string().optional().describe('API error code if available')
|
|
31
|
+
})).optional().describe('Files that failed to move'),
|
|
32
|
+
totalRequested: z.number().describe('Total number of files requested to move'),
|
|
33
|
+
totalMoved: z.number().describe('Number of files successfully moved'),
|
|
34
|
+
totalFailed: z.number().describe('Number of files that failed to move')
|
|
35
|
+
});
|
|
36
|
+
// Output schema with auth_required support
|
|
37
|
+
const outputSchema = z.discriminatedUnion('type', [
|
|
38
|
+
successBranchSchema,
|
|
39
|
+
AuthRequiredBranchSchema
|
|
40
|
+
]);
|
|
41
|
+
const config = {
|
|
42
|
+
title: 'Move Files',
|
|
43
|
+
description: 'Move files/folders to destination folder. Returns oldParents for undo. Use "root" for My Drive root.',
|
|
44
|
+
inputSchema: inputSchema,
|
|
45
|
+
outputSchema: z.object({
|
|
46
|
+
result: outputSchema
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Move a single file to a new parent folder
|
|
51
|
+
*/ async function moveSingleFile(drive, fileId, destinationFolderId, returnOldParents, logger) {
|
|
52
|
+
try {
|
|
53
|
+
// Get current file metadata to get old parents
|
|
54
|
+
const fileMetadata = await drive.files.get({
|
|
55
|
+
fileId: fileId,
|
|
56
|
+
fields: 'id,name,parents,webViewLink'
|
|
57
|
+
});
|
|
58
|
+
const oldParents = fileMetadata.data.parents || [];
|
|
59
|
+
const fileName = fileMetadata.data.name || fileId;
|
|
60
|
+
const webViewLink = fileMetadata.data.webViewLink;
|
|
61
|
+
// Move file using addParents and removeParents
|
|
62
|
+
await drive.files.update({
|
|
63
|
+
fileId: fileId,
|
|
64
|
+
addParents: destinationFolderId,
|
|
65
|
+
removeParents: oldParents.join(','),
|
|
66
|
+
fields: 'id,name,parents,webViewLink'
|
|
67
|
+
});
|
|
68
|
+
const result = {
|
|
69
|
+
fileId: fileId,
|
|
70
|
+
fileName: fileName,
|
|
71
|
+
oldParents: returnOldParents ? oldParents : [],
|
|
72
|
+
newParent: destinationFolderId,
|
|
73
|
+
...webViewLink && {
|
|
74
|
+
webViewLink
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
result
|
|
80
|
+
};
|
|
81
|
+
} catch (e) {
|
|
82
|
+
const errorMessage = isDriveApiError(e) && e.message ? e.message : 'Unknown error';
|
|
83
|
+
const errorCode = isDriveApiError(e) && e.code ? String(e.code) : undefined;
|
|
84
|
+
logger.info('Failed to move file', {
|
|
85
|
+
fileId,
|
|
86
|
+
error: errorMessage
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: {
|
|
91
|
+
fileId: fileId,
|
|
92
|
+
error: errorMessage,
|
|
93
|
+
...errorCode && {
|
|
94
|
+
code: errorCode
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Move multiple files using batch requests
|
|
102
|
+
*/ async function moveBatchFiles(drive, fileIds, destinationFolderId, returnOldParents, _logger) {
|
|
103
|
+
const moved = [];
|
|
104
|
+
const failed = [];
|
|
105
|
+
// First, fetch metadata for all files in batch
|
|
106
|
+
const metadataResults = await Promise.allSettled(fileIds.map(async (fileId)=>{
|
|
107
|
+
try {
|
|
108
|
+
const response = await drive.files.get({
|
|
109
|
+
fileId: fileId,
|
|
110
|
+
fields: 'id,name,parents,webViewLink'
|
|
111
|
+
});
|
|
112
|
+
const webViewLink = response.data.webViewLink;
|
|
113
|
+
return {
|
|
114
|
+
fileId: fileId,
|
|
115
|
+
name: response.data.name || fileId,
|
|
116
|
+
parents: response.data.parents || [],
|
|
117
|
+
...webViewLink && {
|
|
118
|
+
webViewLink
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const message = isDriveApiError(e) && e.message ? e.message : String(e);
|
|
123
|
+
throw new Error(`Failed to fetch metadata: ${message}`);
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
// Process metadata results
|
|
127
|
+
const filesToMove = [];
|
|
128
|
+
for(let i = 0; i < metadataResults.length; i++){
|
|
129
|
+
const result = metadataResults[i];
|
|
130
|
+
if (result && result.status === 'fulfilled') {
|
|
131
|
+
filesToMove.push(result.value);
|
|
132
|
+
} else if (result && result.status === 'rejected') {
|
|
133
|
+
const fileId = fileIds[i];
|
|
134
|
+
if (fileId) {
|
|
135
|
+
var _result_reason, _result_reason1;
|
|
136
|
+
const errorCode = ((_result_reason = result.reason) === null || _result_reason === void 0 ? void 0 : _result_reason.code) ? String(result.reason.code) : undefined;
|
|
137
|
+
failed.push({
|
|
138
|
+
fileId: fileId,
|
|
139
|
+
error: ((_result_reason1 = result.reason) === null || _result_reason1 === void 0 ? void 0 : _result_reason1.message) || 'Failed to fetch file metadata',
|
|
140
|
+
...errorCode && {
|
|
141
|
+
code: errorCode
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Now move files in batch
|
|
148
|
+
const moveResults = await Promise.allSettled(filesToMove.map(async (file)=>{
|
|
149
|
+
try {
|
|
150
|
+
await drive.files.update({
|
|
151
|
+
fileId: file.fileId,
|
|
152
|
+
addParents: destinationFolderId,
|
|
153
|
+
removeParents: file.parents.join(','),
|
|
154
|
+
fields: 'id'
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
fileId: file.fileId,
|
|
158
|
+
fileName: file.name,
|
|
159
|
+
oldParents: returnOldParents ? file.parents : [],
|
|
160
|
+
newParent: destinationFolderId,
|
|
161
|
+
...file.webViewLink && {
|
|
162
|
+
webViewLink: file.webViewLink
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
} catch (e) {
|
|
166
|
+
const message = isDriveApiError(e) && e.message ? e.message : String(e);
|
|
167
|
+
throw new Error(`Failed to move: ${message}`);
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
170
|
+
// Process move results
|
|
171
|
+
for(let i = 0; i < moveResults.length; i++){
|
|
172
|
+
const result = moveResults[i];
|
|
173
|
+
if (result && result.status === 'fulfilled') {
|
|
174
|
+
moved.push(result.value);
|
|
175
|
+
} else if (result && result.status === 'rejected') {
|
|
176
|
+
const file = filesToMove[i];
|
|
177
|
+
if (file) {
|
|
178
|
+
var _result_reason2, _result_reason3;
|
|
179
|
+
const errorCode = ((_result_reason2 = result.reason) === null || _result_reason2 === void 0 ? void 0 : _result_reason2.code) ? String(result.reason.code) : undefined;
|
|
180
|
+
failed.push({
|
|
181
|
+
fileId: file.fileId,
|
|
182
|
+
error: ((_result_reason3 = result.reason) === null || _result_reason3 === void 0 ? void 0 : _result_reason3.message) || 'Failed to move file',
|
|
183
|
+
...errorCode && {
|
|
184
|
+
code: errorCode
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
moved,
|
|
192
|
+
failed
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function handler({ fileIds, destinationFolderId, returnOldParents = true }, extra) {
|
|
196
|
+
const logger = extra.logger;
|
|
197
|
+
const isBatch = Array.isArray(fileIds);
|
|
198
|
+
const fileIdArray = Array.isArray(fileIds) ? fileIds : [
|
|
199
|
+
fileIds
|
|
200
|
+
];
|
|
201
|
+
logger.info('drive.file.move called', {
|
|
202
|
+
fileCount: fileIdArray.length,
|
|
203
|
+
isBatch,
|
|
204
|
+
destinationFolderId,
|
|
205
|
+
returnOldParents
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
const drive = google.drive({
|
|
209
|
+
version: 'v3',
|
|
210
|
+
auth: extra.authContext.auth
|
|
211
|
+
});
|
|
212
|
+
let moved = [];
|
|
213
|
+
let failed = [];
|
|
214
|
+
if (isBatch && fileIdArray.length > 1) {
|
|
215
|
+
// Use batch API for multiple files
|
|
216
|
+
const batchResult = await moveBatchFiles(drive, fileIdArray, destinationFolderId, returnOldParents, logger);
|
|
217
|
+
moved = batchResult.moved;
|
|
218
|
+
failed = batchResult.failed;
|
|
219
|
+
} else {
|
|
220
|
+
// Single file operation
|
|
221
|
+
const fileId = fileIdArray[0];
|
|
222
|
+
if (fileId) {
|
|
223
|
+
const singleResult = await moveSingleFile(drive, fileId, destinationFolderId, returnOldParents, logger);
|
|
224
|
+
if (singleResult.success && singleResult.result) {
|
|
225
|
+
moved.push(singleResult.result);
|
|
226
|
+
} else if (singleResult.error) {
|
|
227
|
+
failed.push(singleResult.error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
logger.info('drive.file.move returning', {
|
|
232
|
+
totalRequested: fileIdArray.length,
|
|
233
|
+
totalMoved: moved.length,
|
|
234
|
+
totalFailed: failed.length
|
|
235
|
+
});
|
|
236
|
+
const result = {
|
|
237
|
+
type: 'success',
|
|
238
|
+
moved,
|
|
239
|
+
...failed.length > 0 && {
|
|
240
|
+
failed
|
|
241
|
+
},
|
|
242
|
+
totalRequested: fileIdArray.length,
|
|
243
|
+
totalMoved: moved.length,
|
|
244
|
+
totalFailed: failed.length
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: 'text',
|
|
250
|
+
text: JSON.stringify(result)
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
structuredContent: {
|
|
254
|
+
result
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
259
|
+
logger.error('drive.file.move error', {
|
|
260
|
+
error: message
|
|
261
|
+
});
|
|
262
|
+
// Throw McpError
|
|
263
|
+
throw new McpError(ErrorCode.InternalError, `Error moving files: ${message}`, {
|
|
264
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export default function createTool() {
|
|
269
|
+
return {
|
|
270
|
+
name: 'file-move',
|
|
271
|
+
config,
|
|
272
|
+
handler
|
|
273
|
+
};
|
|
274
|
+
}
|