@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.
Files changed (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/bin/server.js +5 -0
  4. package/dist/cjs/constants.d.cts +7 -0
  5. package/dist/cjs/constants.d.ts +7 -0
  6. package/dist/cjs/constants.js +18 -0
  7. package/dist/cjs/constants.js.map +1 -0
  8. package/dist/cjs/index.d.cts +8 -0
  9. package/dist/cjs/index.d.ts +8 -0
  10. package/dist/cjs/index.js +314 -0
  11. package/dist/cjs/index.js.map +1 -0
  12. package/dist/cjs/lib/create-store.d.cts +2 -0
  13. package/dist/cjs/lib/create-store.d.ts +2 -0
  14. package/dist/cjs/lib/create-store.js +166 -0
  15. package/dist/cjs/lib/create-store.js.map +1 -0
  16. package/dist/cjs/lib/query-builder.d.cts +45 -0
  17. package/dist/cjs/lib/query-builder.d.ts +45 -0
  18. package/dist/cjs/lib/query-builder.js +219 -0
  19. package/dist/cjs/lib/query-builder.js.map +1 -0
  20. package/dist/cjs/mcp/index.d.cts +3 -0
  21. package/dist/cjs/mcp/index.d.ts +3 -0
  22. package/dist/cjs/mcp/index.js +66 -0
  23. package/dist/cjs/mcp/index.js.map +1 -0
  24. package/dist/cjs/mcp/prompts/index.d.cts +2 -0
  25. package/dist/cjs/mcp/prompts/index.d.ts +2 -0
  26. package/dist/cjs/mcp/prompts/index.js +26 -0
  27. package/dist/cjs/mcp/prompts/index.js.map +1 -0
  28. package/dist/cjs/mcp/prompts/organize-files.d.cts +16 -0
  29. package/dist/cjs/mcp/prompts/organize-files.d.ts +16 -0
  30. package/dist/cjs/mcp/prompts/organize-files.js +169 -0
  31. package/dist/cjs/mcp/prompts/organize-files.js.map +1 -0
  32. package/dist/cjs/mcp/prompts/query-syntax.d.cts +19 -0
  33. package/dist/cjs/mcp/prompts/query-syntax.d.ts +19 -0
  34. package/dist/cjs/mcp/prompts/query-syntax.js +169 -0
  35. package/dist/cjs/mcp/prompts/query-syntax.js.map +1 -0
  36. package/dist/cjs/mcp/resources/file.d.cts +9 -0
  37. package/dist/cjs/mcp/resources/file.d.ts +9 -0
  38. package/dist/cjs/mcp/resources/file.js +247 -0
  39. package/dist/cjs/mcp/resources/file.js.map +1 -0
  40. package/dist/cjs/mcp/resources/index.d.cts +1 -0
  41. package/dist/cjs/mcp/resources/index.d.ts +1 -0
  42. package/dist/cjs/mcp/resources/index.js +17 -0
  43. package/dist/cjs/mcp/resources/index.js.map +1 -0
  44. package/dist/cjs/mcp/tools/file-move-to-trash.d.cts +59 -0
  45. package/dist/cjs/mcp/tools/file-move-to-trash.d.ts +59 -0
  46. package/dist/cjs/mcp/tools/file-move-to-trash.js +334 -0
  47. package/dist/cjs/mcp/tools/file-move-to-trash.js.map +1 -0
  48. package/dist/cjs/mcp/tools/file-move.d.cts +73 -0
  49. package/dist/cjs/mcp/tools/file-move.d.ts +73 -0
  50. package/dist/cjs/mcp/tools/file-move.js +613 -0
  51. package/dist/cjs/mcp/tools/file-move.js.map +1 -0
  52. package/dist/cjs/mcp/tools/files-search.d.cts +135 -0
  53. package/dist/cjs/mcp/tools/files-search.d.ts +135 -0
  54. package/dist/cjs/mcp/tools/files-search.js +558 -0
  55. package/dist/cjs/mcp/tools/files-search.js.map +1 -0
  56. package/dist/cjs/mcp/tools/folder-contents.d.cts +139 -0
  57. package/dist/cjs/mcp/tools/folder-contents.d.ts +139 -0
  58. package/dist/cjs/mcp/tools/folder-contents.js +513 -0
  59. package/dist/cjs/mcp/tools/folder-contents.js.map +1 -0
  60. package/dist/cjs/mcp/tools/folder-create.d.cts +59 -0
  61. package/dist/cjs/mcp/tools/folder-create.d.ts +59 -0
  62. package/dist/cjs/mcp/tools/folder-create.js +368 -0
  63. package/dist/cjs/mcp/tools/folder-create.js.map +1 -0
  64. package/dist/cjs/mcp/tools/folder-path.d.cts +49 -0
  65. package/dist/cjs/mcp/tools/folder-path.d.ts +49 -0
  66. package/dist/cjs/mcp/tools/folder-path.js +367 -0
  67. package/dist/cjs/mcp/tools/folder-path.js.map +1 -0
  68. package/dist/cjs/mcp/tools/folder-search.d.cts +139 -0
  69. package/dist/cjs/mcp/tools/folder-search.d.ts +139 -0
  70. package/dist/cjs/mcp/tools/folder-search.js +760 -0
  71. package/dist/cjs/mcp/tools/folder-search.js.map +1 -0
  72. package/dist/cjs/mcp/tools/index.d.cts +7 -0
  73. package/dist/cjs/mcp/tools/index.d.ts +7 -0
  74. package/dist/cjs/mcp/tools/index.js +46 -0
  75. package/dist/cjs/mcp/tools/index.js.map +1 -0
  76. package/dist/cjs/package.json +1 -0
  77. package/dist/cjs/schemas/drive-query-schema.d.cts +40 -0
  78. package/dist/cjs/schemas/drive-query-schema.d.ts +40 -0
  79. package/dist/cjs/schemas/drive-query-schema.js +90 -0
  80. package/dist/cjs/schemas/drive-query-schema.js.map +1 -0
  81. package/dist/cjs/schemas/drive-validation.d.cts +48 -0
  82. package/dist/cjs/schemas/drive-validation.d.ts +48 -0
  83. package/dist/cjs/schemas/drive-validation.js +96 -0
  84. package/dist/cjs/schemas/drive-validation.js.map +1 -0
  85. package/dist/cjs/schemas/index.d.cts +2 -0
  86. package/dist/cjs/schemas/index.d.ts +2 -0
  87. package/dist/cjs/schemas/index.js +20 -0
  88. package/dist/cjs/schemas/index.js.map +1 -0
  89. package/dist/cjs/setup/config.d.cts +44 -0
  90. package/dist/cjs/setup/config.d.ts +44 -0
  91. package/dist/cjs/setup/config.js +201 -0
  92. package/dist/cjs/setup/config.js.map +1 -0
  93. package/dist/cjs/setup/http.d.cts +8 -0
  94. package/dist/cjs/setup/http.d.ts +8 -0
  95. package/dist/cjs/setup/http.js +260 -0
  96. package/dist/cjs/setup/http.js.map +1 -0
  97. package/dist/cjs/setup/index.d.cts +5 -0
  98. package/dist/cjs/setup/index.d.ts +5 -0
  99. package/dist/cjs/setup/index.js +46 -0
  100. package/dist/cjs/setup/index.js.map +1 -0
  101. package/dist/cjs/setup/oauth-google.d.cts +64 -0
  102. package/dist/cjs/setup/oauth-google.d.ts +64 -0
  103. package/dist/cjs/setup/oauth-google.js +347 -0
  104. package/dist/cjs/setup/oauth-google.js.map +1 -0
  105. package/dist/cjs/setup/runtime.d.cts +10 -0
  106. package/dist/cjs/setup/runtime.d.ts +10 -0
  107. package/dist/cjs/setup/runtime.js +353 -0
  108. package/dist/cjs/setup/runtime.js.map +1 -0
  109. package/dist/cjs/setup/stdio.d.cts +7 -0
  110. package/dist/cjs/setup/stdio.d.ts +7 -0
  111. package/dist/cjs/setup/stdio.js +239 -0
  112. package/dist/cjs/setup/stdio.js.map +1 -0
  113. package/dist/cjs/types.d.cts +45 -0
  114. package/dist/cjs/types.d.ts +45 -0
  115. package/dist/cjs/types.js +5 -0
  116. package/dist/cjs/types.js.map +1 -0
  117. package/dist/esm/constants.d.ts +7 -0
  118. package/dist/esm/constants.js +7 -0
  119. package/dist/esm/constants.js.map +1 -0
  120. package/dist/esm/index.d.ts +8 -0
  121. package/dist/esm/index.js +34 -0
  122. package/dist/esm/index.js.map +1 -0
  123. package/dist/esm/lib/create-store.d.ts +2 -0
  124. package/dist/esm/lib/create-store.js +6 -0
  125. package/dist/esm/lib/create-store.js.map +1 -0
  126. package/dist/esm/lib/query-builder.d.ts +45 -0
  127. package/dist/esm/lib/query-builder.js +184 -0
  128. package/dist/esm/lib/query-builder.js.map +1 -0
  129. package/dist/esm/mcp/index.d.ts +3 -0
  130. package/dist/esm/mcp/index.js +6 -0
  131. package/dist/esm/mcp/index.js.map +1 -0
  132. package/dist/esm/mcp/prompts/index.d.ts +2 -0
  133. package/dist/esm/mcp/prompts/index.js +2 -0
  134. package/dist/esm/mcp/prompts/index.js.map +1 -0
  135. package/dist/esm/mcp/prompts/organize-files.d.ts +16 -0
  136. package/dist/esm/mcp/prompts/organize-files.js +21 -0
  137. package/dist/esm/mcp/prompts/organize-files.js.map +1 -0
  138. package/dist/esm/mcp/prompts/query-syntax.d.ts +19 -0
  139. package/dist/esm/mcp/prompts/query-syntax.js +82 -0
  140. package/dist/esm/mcp/prompts/query-syntax.js.map +1 -0
  141. package/dist/esm/mcp/resources/file.d.ts +9 -0
  142. package/dist/esm/mcp/resources/file.js +77 -0
  143. package/dist/esm/mcp/resources/file.js.map +1 -0
  144. package/dist/esm/mcp/resources/index.d.ts +1 -0
  145. package/dist/esm/mcp/resources/index.js +1 -0
  146. package/dist/esm/mcp/resources/index.js.map +1 -0
  147. package/dist/esm/mcp/tools/file-move-to-trash.d.ts +59 -0
  148. package/dist/esm/mcp/tools/file-move-to-trash.js +118 -0
  149. package/dist/esm/mcp/tools/file-move-to-trash.js.map +1 -0
  150. package/dist/esm/mcp/tools/file-move.d.ts +73 -0
  151. package/dist/esm/mcp/tools/file-move.js +274 -0
  152. package/dist/esm/mcp/tools/file-move.js.map +1 -0
  153. package/dist/esm/mcp/tools/files-search.d.ts +135 -0
  154. package/dist/esm/mcp/tools/files-search.js +254 -0
  155. package/dist/esm/mcp/tools/files-search.js.map +1 -0
  156. package/dist/esm/mcp/tools/folder-contents.d.ts +139 -0
  157. package/dist/esm/mcp/tools/folder-contents.js +214 -0
  158. package/dist/esm/mcp/tools/folder-contents.js.map +1 -0
  159. package/dist/esm/mcp/tools/folder-create.d.ts +59 -0
  160. package/dist/esm/mcp/tools/folder-create.js +140 -0
  161. package/dist/esm/mcp/tools/folder-create.js.map +1 -0
  162. package/dist/esm/mcp/tools/folder-path.d.ts +49 -0
  163. package/dist/esm/mcp/tools/folder-path.js +147 -0
  164. package/dist/esm/mcp/tools/folder-path.js.map +1 -0
  165. package/dist/esm/mcp/tools/folder-search.d.ts +139 -0
  166. package/dist/esm/mcp/tools/folder-search.js +343 -0
  167. package/dist/esm/mcp/tools/folder-search.js.map +1 -0
  168. package/dist/esm/mcp/tools/index.d.ts +7 -0
  169. package/dist/esm/mcp/tools/index.js +7 -0
  170. package/dist/esm/mcp/tools/index.js.map +1 -0
  171. package/dist/esm/package.json +1 -0
  172. package/dist/esm/schemas/drive-query-schema.d.ts +40 -0
  173. package/dist/esm/schemas/drive-query-schema.js +84 -0
  174. package/dist/esm/schemas/drive-query-schema.js.map +1 -0
  175. package/dist/esm/schemas/drive-validation.d.ts +48 -0
  176. package/dist/esm/schemas/drive-validation.js +73 -0
  177. package/dist/esm/schemas/drive-validation.js.map +1 -0
  178. package/dist/esm/schemas/index.d.ts +2 -0
  179. package/dist/esm/schemas/index.js +2 -0
  180. package/dist/esm/schemas/index.js.map +1 -0
  181. package/dist/esm/setup/config.d.ts +44 -0
  182. package/dist/esm/setup/config.js +151 -0
  183. package/dist/esm/setup/config.js.map +1 -0
  184. package/dist/esm/setup/http.d.ts +8 -0
  185. package/dist/esm/setup/http.js +54 -0
  186. package/dist/esm/setup/http.js.map +1 -0
  187. package/dist/esm/setup/index.d.ts +5 -0
  188. package/dist/esm/setup/index.js +5 -0
  189. package/dist/esm/setup/index.js.map +1 -0
  190. package/dist/esm/setup/oauth-google.d.ts +64 -0
  191. package/dist/esm/setup/oauth-google.js +168 -0
  192. package/dist/esm/setup/oauth-google.js.map +1 -0
  193. package/dist/esm/setup/runtime.d.ts +10 -0
  194. package/dist/esm/setup/runtime.js +84 -0
  195. package/dist/esm/setup/runtime.js.map +1 -0
  196. package/dist/esm/setup/stdio.d.ts +7 -0
  197. package/dist/esm/setup/stdio.js +38 -0
  198. package/dist/esm/setup/stdio.js.map +1 -0
  199. package/dist/esm/types.d.ts +45 -0
  200. package/dist/esm/types.js +1 -0
  201. package/dist/esm/types.js.map +1 -0
  202. 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
+ }