gokuai-user-mcp 1.0.2

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 (2) hide show
  1. package/dist/index.js +840 -0
  2. package/package.json +39 -0
package/dist/index.js ADDED
@@ -0,0 +1,840 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/tools/library.ts
8
+ import { z } from "zod";
9
+
10
+ // src/client.ts
11
+ var GokuaiClient = class {
12
+ token;
13
+ authType;
14
+ apiHost;
15
+ constructor(apiHost2, authType2, token2) {
16
+ this.token = token2;
17
+ this.authType = authType2;
18
+ this.apiHost = apiHost2;
19
+ }
20
+ get authHeaders() {
21
+ return {
22
+ "Authorization": `Bearer ${this.token}`,
23
+ "X-Auth-Type": this.authType
24
+ };
25
+ }
26
+ buildUrl(path, params) {
27
+ const url = new URL(path, this.apiHost);
28
+ if (params) {
29
+ for (const [key, value] of Object.entries(params)) {
30
+ if (value !== void 0 && value !== null) {
31
+ url.searchParams.set(key, String(value));
32
+ }
33
+ }
34
+ }
35
+ return url.toString();
36
+ }
37
+ async get(path, params) {
38
+ const url = this.buildUrl(path, params);
39
+ const response = await fetch(url, {
40
+ headers: this.authHeaders
41
+ });
42
+ if (!response.ok) {
43
+ const text = await response.text();
44
+ throw new Error(`HTTP ${response.status}: ${text}`);
45
+ }
46
+ return response.json();
47
+ }
48
+ async post(path, body) {
49
+ const url = this.buildUrl(path);
50
+ const params = new URLSearchParams();
51
+ for (const [key, value] of Object.entries(body)) {
52
+ if (value !== void 0 && value !== null) {
53
+ params.set(key, String(value));
54
+ }
55
+ }
56
+ const response = await fetch(url, {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/x-www-form-urlencoded",
60
+ ...this.authHeaders
61
+ },
62
+ body: params.toString()
63
+ });
64
+ if (!response.ok) {
65
+ const text = await response.text();
66
+ throw new Error(`HTTP ${response.status}: ${text}`);
67
+ }
68
+ return response.json();
69
+ }
70
+ async getLibraries() {
71
+ const resp = await this.get("/m-api/1/account/mount");
72
+ return resp.list || [];
73
+ }
74
+ async listFiles(mountId, fullpath, start, size) {
75
+ return this.get("/m-api/1/file/ls", {
76
+ mount_id: mountId,
77
+ fullpath: fullpath || "",
78
+ start,
79
+ size
80
+ });
81
+ }
82
+ async getFileInfo(mountId, opts) {
83
+ return this.get("/m-api/2/file/info", {
84
+ mount_id: mountId,
85
+ fullpath: opts.fullpath,
86
+ hash: opts.hash
87
+ });
88
+ }
89
+ async searchFiles(keyword, mountId, opts) {
90
+ const params = {
91
+ keyword,
92
+ mount_id: mountId,
93
+ start: opts?.start,
94
+ size: opts?.size
95
+ };
96
+ if (opts?.scope && opts.scope.length > 0) {
97
+ params.scope = opts.scope.join("|");
98
+ }
99
+ return this.get("/m-api/2/file/search", params);
100
+ }
101
+ async getDownloadUrl(mountId, opts) {
102
+ return this.get("/m-api/2/file/open", {
103
+ mount_id: mountId,
104
+ fullpath: opts.fullpath,
105
+ hash: opts.hash
106
+ });
107
+ }
108
+ async getPreviewUrl(mountId, opts, watermark) {
109
+ return this.get("/m-api/2/file/preview_url", {
110
+ mount_id: mountId,
111
+ fullpath: opts.fullpath,
112
+ hash: opts.hash,
113
+ watermark
114
+ });
115
+ }
116
+ async getFolderAttribute(mountId, opts) {
117
+ return this.get("/m-api/2/file/attribute", {
118
+ mount_id: mountId,
119
+ fullpath: opts.fullpath,
120
+ hash: opts.hash
121
+ });
122
+ }
123
+ async createLibrary(name, entId, capacity, storagePoint) {
124
+ return this.post("/m-api/1/library/create", {
125
+ name,
126
+ ent_id: entId,
127
+ capacity,
128
+ storage_point: storagePoint
129
+ });
130
+ }
131
+ async createFolder(mountId, fullpath, overwrite) {
132
+ return this.post("/m-api/2/file/create_folder", {
133
+ mount_id: mountId,
134
+ fullpath,
135
+ overwrite
136
+ });
137
+ }
138
+ async copyFile(mountId, fullpath, targetFullpath, opts) {
139
+ return this.post("/m-api/1/file/copy", {
140
+ mount_id: mountId,
141
+ fullpath,
142
+ target_fullpath: targetFullpath,
143
+ target_mount_id: opts?.target_mount_id
144
+ });
145
+ }
146
+ async moveFile(mountId, fullpath, targetFullpath) {
147
+ return this.post("/m-api/1/file/move", {
148
+ mount_id: mountId,
149
+ fullpath,
150
+ target_fullpath: targetFullpath
151
+ });
152
+ }
153
+ async renameFile(mountId, fullpath, newname) {
154
+ return this.post("/m-api/1/file/rename", {
155
+ mount_id: mountId,
156
+ fullpath,
157
+ newname
158
+ });
159
+ }
160
+ async deleteFile(mountId, fullpath) {
161
+ return this.post("/m-api/1/file/del", {
162
+ mount_id: mountId,
163
+ fullpath
164
+ });
165
+ }
166
+ async listFavorites(favId, start, size) {
167
+ return this.get("/m-api/1/favorites/get_files", {
168
+ fav_id: favId,
169
+ start,
170
+ size
171
+ });
172
+ }
173
+ async addFavorite(mountId, fullpath, favId) {
174
+ return this.post("/m-api/1/favorites/add_file", {
175
+ mount_id: mountId,
176
+ fullpath,
177
+ fav_id: favId
178
+ });
179
+ }
180
+ async removeFavorite(mountId, fullpath, favId) {
181
+ return this.post("/m-api/1/favorites/del_file", {
182
+ mount_id: mountId,
183
+ fullpath,
184
+ fav_id: favId
185
+ });
186
+ }
187
+ };
188
+
189
+ // src/tools/library.ts
190
+ function registerLibraryTools(server2, apiHost2, authType2, token2) {
191
+ const client = new GokuaiClient(apiHost2, authType2, token2);
192
+ server2.tool(
193
+ "list_libraries",
194
+ "List all cloud libraries (\u4E91\u5E93) accessible to the current user. Returns library name, mount_id, storage usage, and member count.",
195
+ {},
196
+ async () => {
197
+ try {
198
+ const libraries = await client.getLibraries();
199
+ if (!libraries || libraries.length === 0) {
200
+ return {
201
+ content: [{ type: "text", text: "No libraries found." }]
202
+ };
203
+ }
204
+ const formatted = libraries.map((lib) => {
205
+ const usedGB = (lib.size_org_use / (1024 * 1024 * 1024)).toFixed(2);
206
+ const totalGB = (lib.size_org_total / (1024 * 1024 * 1024)).toFixed(2);
207
+ return [
208
+ `\u{1F4C1} ${lib.org_name}`,
209
+ ` Ent ID: ${lib.ent_id}`,
210
+ ` Org ID: ${lib.org_id}`,
211
+ ` Mount ID: ${lib.mount_id}`,
212
+ ` Type: ${lib.org_type}`,
213
+ ` Members: ${lib.member_count}`,
214
+ ` Owner Member ID: ${lib.owner_member_id}`,
215
+ ` Logo: ${lib.org_logo_url || "-"}`,
216
+ ` Storage: ${usedGB} GB / ${totalGB} GB`,
217
+ ` Storage Point: ${lib.storage_point || "-"}`,
218
+ ` External Access: ${lib.storage_ethernet}`
219
+ ].join("\n");
220
+ });
221
+ return {
222
+ content: [{ type: "text", text: formatted.join("\n\n") }]
223
+ };
224
+ } catch (error) {
225
+ return {
226
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
227
+ isError: true
228
+ };
229
+ }
230
+ }
231
+ );
232
+ server2.tool(
233
+ "create_library",
234
+ "Create a new cloud library (\u4E91\u5E93). User must have permission to create libraries. Returns org_id, mount_id, and storage_point.",
235
+ {
236
+ name: z.string().describe("Library name"),
237
+ ent_id: z.number().describe("Enterprise ID (\u4F01\u4E1AID)"),
238
+ capacity: z.number().optional().describe("Storage capacity limit in bytes, -1 for unlimited"),
239
+ storage_point: z.string().optional().describe("Storage point, defaults to system default if not specified")
240
+ },
241
+ async ({ name, ent_id, capacity, storage_point }) => {
242
+ try {
243
+ const result = await client.createLibrary(name, ent_id, capacity, storage_point);
244
+ const info = [
245
+ `\u2705 Library created successfully: ${name}`,
246
+ ` Org ID: ${result.org_id}`,
247
+ ` Mount ID: ${result.mount_id}`,
248
+ ` Storage Point: ${result.storage_point || "-"}`
249
+ ];
250
+ return {
251
+ content: [{ type: "text", text: info.join("\n") }]
252
+ };
253
+ } catch (error) {
254
+ return {
255
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
256
+ isError: true
257
+ };
258
+ }
259
+ }
260
+ );
261
+ }
262
+
263
+ // src/tools/files.ts
264
+ import { z as z2 } from "zod";
265
+
266
+ // ../shared/dist/utils.js
267
+ function formatFileSize(bytes) {
268
+ if (bytes === 0)
269
+ return "0 B";
270
+ const units = ["B", "KB", "MB", "GB", "TB"];
271
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
272
+ return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
273
+ }
274
+
275
+ // src/tools/files.ts
276
+ function registerFileTools(server2, apiHost2, authType2, token2) {
277
+ const client = new GokuaiClient(apiHost2, authType2, token2);
278
+ server2.tool(
279
+ "list_files",
280
+ "List files and folders in a specified path within a cloud library. Returns file names, sizes, types, and modification info.",
281
+ {
282
+ mount_id: z2.number().describe("The mount ID of the library"),
283
+ path: z2.string().optional().describe('The folder path to list, defaults to root ("")'),
284
+ start: z2.number().optional().describe("Pagination start index"),
285
+ size: z2.number().optional().describe("Number of items to return, defaults to 100")
286
+ },
287
+ async ({ mount_id, path, start, size }) => {
288
+ try {
289
+ const result = await client.listFiles(mount_id, path || "", start, size || 100);
290
+ if (!result.list || result.list.length === 0) {
291
+ return {
292
+ content: [{ type: "text", text: `No files found. Total count: ${result.count}` }]
293
+ };
294
+ }
295
+ const header = `Total: ${result.count} items
296
+ ${"\u2500".repeat(50)}`;
297
+ const formatted = result.list.map((file) => {
298
+ const isDir = file.dir === 1;
299
+ const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
300
+ const sizeStr = isDir ? "-" : formatFileSize(file.filesize);
301
+ const created = new Date(file.create_dateline * 1e3).toLocaleString("zh-CN");
302
+ const modified = new Date(file.last_dateline * 1e3).toLocaleString("zh-CN");
303
+ return [
304
+ `${icon} ${file.filename}`,
305
+ ` Hash: ${file.hash}`,
306
+ ` Mount ID: ${file.mount_id}`,
307
+ ` Type: ${isDir ? "Folder" : "File"}`,
308
+ ` Path: ${file.fullpath}`,
309
+ ` File Hash: ${file.filehash || "-"}`,
310
+ ` Size: ${sizeStr}`,
311
+ ` Created: ${created} by ${file.create_member_name} (ID: ${file.create_member_id})`,
312
+ ` Modified: ${modified} by ${file.last_member_name} (ID: ${file.last_member_id})`,
313
+ ` Thumbnail: ${file.thumbnail || "-"}`,
314
+ ` Lock: ${file.lock ? "Yes" : "No"}`
315
+ ].join("\n");
316
+ });
317
+ return {
318
+ content: [{ type: "text", text: `${header}
319
+ ${formatted.join("\n")}` }]
320
+ };
321
+ } catch (error) {
322
+ return {
323
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
324
+ isError: true
325
+ };
326
+ }
327
+ }
328
+ );
329
+ server2.tool(
330
+ "get_file_info",
331
+ "Get detailed information about a specific file or folder. Provide either fullpath or hash to identify the file.",
332
+ {
333
+ mount_id: z2.number().describe("The mount ID of the library"),
334
+ fullpath: z2.string().optional().describe("Full path of the file"),
335
+ hash: z2.string().optional().describe("Hash of the file")
336
+ },
337
+ async ({ mount_id, fullpath, hash }) => {
338
+ try {
339
+ if (!fullpath && !hash) {
340
+ return {
341
+ content: [{ type: "text", text: "Error: Either fullpath or hash must be provided" }],
342
+ isError: true
343
+ };
344
+ }
345
+ const result = await client.getFileInfo(mount_id, { fullpath, hash });
346
+ const isDir = result.dir === 1;
347
+ const info = [
348
+ `Name: ${result.filename}`,
349
+ `Path: ${result.fullpath}`,
350
+ `Hash: ${result.hash}`,
351
+ `Mount ID: ${result.mount_id}`,
352
+ `Type: ${isDir ? "Folder" : "File"}`,
353
+ `Size: ${isDir ? "-" : formatFileSize(result.filesize)}`,
354
+ `File Hash: ${result.filehash || "-"}`,
355
+ `Created: ${new Date(result.create_dateline * 1e3).toLocaleString("zh-CN")} by ${result.create_member_name} (ID: ${result.create_member_id})`,
356
+ `Modified: ${new Date(result.last_dateline * 1e3).toLocaleString("zh-CN")} by ${result.last_member_name} (ID: ${result.last_member_id})`,
357
+ `Lock: ${result.lock ? "Yes" : "No"}`
358
+ ];
359
+ if (result.preview) {
360
+ info.push(`Preview: ${result.preview}`);
361
+ }
362
+ if (result.thumbnail) {
363
+ info.push(`Thumbnail: ${result.thumbnail}`);
364
+ }
365
+ return {
366
+ content: [{ type: "text", text: info.join("\n") }]
367
+ };
368
+ } catch (error) {
369
+ return {
370
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
371
+ isError: true
372
+ };
373
+ }
374
+ }
375
+ );
376
+ server2.tool(
377
+ "search_files",
378
+ "Search for files by keyword across a library or globally. Use mount_id=0 for global search.",
379
+ {
380
+ keyword: z2.string().describe("Search keyword"),
381
+ mount_id: z2.number().describe("The mount ID of the library, use 0 for global search"),
382
+ scope: z2.array(z2.string()).optional().describe("Search scope paths, separated by |"),
383
+ start: z2.number().optional().describe("Pagination start index"),
384
+ size: z2.number().optional().describe("Number of results to return")
385
+ },
386
+ async ({ keyword, mount_id, scope, start, size }) => {
387
+ try {
388
+ const result = await client.searchFiles(keyword, mount_id, { scope, start, size });
389
+ if (!result.list || result.list.length === 0) {
390
+ return {
391
+ content: [{ type: "text", text: `No files found matching "${keyword}". Total: ${result.count}` }]
392
+ };
393
+ }
394
+ const header = `Search results for "${keyword}" - Total: ${result.count}
395
+ ${"\u2500".repeat(50)}`;
396
+ const formatted = result.list.map((file) => {
397
+ const isDir = file.dir === 1;
398
+ const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
399
+ const sizeStr = isDir ? "-" : formatFileSize(file.filesize);
400
+ const created = new Date(file.create_dateline * 1e3).toLocaleString("zh-CN");
401
+ const modified = new Date(file.last_dateline * 1e3).toLocaleString("zh-CN");
402
+ return [
403
+ `${icon} ${file.filename}`,
404
+ ` Hash: ${file.hash}`,
405
+ ` Mount ID: ${file.mount_id}`,
406
+ ` Type: ${isDir ? "Folder" : "File"}`,
407
+ ` Path: ${file.fullpath}`,
408
+ ` File Hash: ${file.filehash || "-"}`,
409
+ ` Size: ${sizeStr}`,
410
+ ` Created: ${created} by ${file.create_member_name} (ID: ${file.create_member_id})`,
411
+ ` Modified: ${modified} by ${file.last_member_name} (ID: ${file.last_member_id})`,
412
+ ` Thumbnail: ${file.thumbnail || "-"}`,
413
+ ` Lock: ${file.lock ? "Yes" : "No"}`
414
+ ].join("\n");
415
+ });
416
+ return {
417
+ content: [{ type: "text", text: `${header}
418
+ ${formatted.join("\n")}` }]
419
+ };
420
+ } catch (error) {
421
+ return {
422
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
423
+ isError: true
424
+ };
425
+ }
426
+ }
427
+ );
428
+ server2.tool(
429
+ "get_folder_count",
430
+ "Get the number of files and sub-folders inside a folder. The count includes all descendants recursively. Provide either fullpath or hash.",
431
+ {
432
+ mount_id: z2.number().describe("The mount ID of the library"),
433
+ fullpath: z2.string().optional().describe("Full path of the folder"),
434
+ hash: z2.string().optional().describe("Hash of the folder")
435
+ },
436
+ async ({ mount_id, fullpath, hash }) => {
437
+ try {
438
+ if (!fullpath && !hash) {
439
+ return {
440
+ content: [{ type: "text", text: "Error: Either fullpath or hash must be provided" }],
441
+ isError: true
442
+ };
443
+ }
444
+ const result = await client.getFolderAttribute(mount_id, { fullpath, hash });
445
+ const info = [
446
+ `\u{1F4CA} Folder Statistics:`,
447
+ ` Files: ${result.file_count}`,
448
+ ` Sub-folders: ${result.folder_count}`,
449
+ ` Total: ${result.file_count + result.folder_count}`
450
+ ];
451
+ return {
452
+ content: [{ type: "text", text: info.join("\n") }]
453
+ };
454
+ } catch (error) {
455
+ return {
456
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
457
+ isError: true
458
+ };
459
+ }
460
+ }
461
+ );
462
+ }
463
+
464
+ // src/tools/download.ts
465
+ import { z as z3 } from "zod";
466
+ function registerDownloadTools(server2, apiHost2, authType2, token2) {
467
+ const client = new GokuaiClient(apiHost2, authType2, token2);
468
+ server2.tool(
469
+ "get_download_url",
470
+ "Get a temporary download URL for a file (valid for 10 minutes). Provide either fullpath or hash.",
471
+ {
472
+ mount_id: z3.number().describe("The mount ID of the library"),
473
+ fullpath: z3.string().optional().describe("Full path of the file"),
474
+ hash: z3.string().optional().describe("Hash of the file")
475
+ },
476
+ async ({ mount_id, fullpath, hash }) => {
477
+ try {
478
+ if (!fullpath && !hash) {
479
+ return {
480
+ content: [{ type: "text", text: "Error: Either fullpath or hash must be provided" }],
481
+ isError: true
482
+ };
483
+ }
484
+ const result = await client.getDownloadUrl(mount_id, { fullpath, hash });
485
+ const info = [
486
+ `Download URLs (valid for 10 minutes):`,
487
+ ...result.uris.map((uri, i) => ` ${i + 1}. ${uri}`),
488
+ ``,
489
+ `File Hash: ${result.filehash}`,
490
+ `File Size: ${formatFileSize(result.filesize)}`
491
+ ];
492
+ return {
493
+ content: [{ type: "text", text: info.join("\n") }]
494
+ };
495
+ } catch (error) {
496
+ return {
497
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
498
+ isError: true
499
+ };
500
+ }
501
+ }
502
+ );
503
+ server2.tool(
504
+ "read_file_content",
505
+ "Read the text content of a file from the cloud library. Downloads and returns file content as text. Best for text files (code, documents, configs). Provide either fullpath or hash.",
506
+ {
507
+ mount_id: z3.number().describe("The mount ID of the library"),
508
+ fullpath: z3.string().optional().describe("Full path of the file"),
509
+ hash: z3.string().optional().describe("Hash of the file"),
510
+ max_size: z3.number().optional().describe("Maximum bytes to read, defaults to 100000")
511
+ },
512
+ async ({ mount_id, fullpath, hash, max_size }) => {
513
+ try {
514
+ if (!fullpath && !hash) {
515
+ return {
516
+ content: [{ type: "text", text: "Error: Either fullpath or hash must be provided" }],
517
+ isError: true
518
+ };
519
+ }
520
+ const maxBytes = max_size || 1e5;
521
+ const downloadInfo = await client.getDownloadUrl(mount_id, { fullpath, hash });
522
+ if (!downloadInfo.uris || downloadInfo.uris.length === 0) {
523
+ return {
524
+ content: [{ type: "text", text: "Error: No download URL available for this file" }],
525
+ isError: true
526
+ };
527
+ }
528
+ if (downloadInfo.filesize > maxBytes * 10) {
529
+ return {
530
+ content: [{
531
+ type: "text",
532
+ text: `Warning: File is very large (${formatFileSize(downloadInfo.filesize)}). Consider using get_download_url instead to download it manually.`
533
+ }]
534
+ };
535
+ }
536
+ const response = await fetch(downloadInfo.uris[0]);
537
+ if (!response.ok) {
538
+ throw new Error(`Failed to download file: HTTP ${response.status}`);
539
+ }
540
+ const buffer = await response.arrayBuffer();
541
+ const bytes = new Uint8Array(buffer);
542
+ let content;
543
+ let truncated = false;
544
+ if (bytes.length > maxBytes) {
545
+ content = new TextDecoder("utf-8", { fatal: false }).decode(bytes.slice(0, maxBytes));
546
+ truncated = true;
547
+ } else {
548
+ content = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
549
+ }
550
+ let result = content;
551
+ if (truncated) {
552
+ result += `
553
+
554
+ --- Content truncated at ${formatFileSize(maxBytes)} (total file size: ${formatFileSize(downloadInfo.filesize)}) ---`;
555
+ }
556
+ return {
557
+ content: [{ type: "text", text: result }]
558
+ };
559
+ } catch (error) {
560
+ return {
561
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
562
+ isError: true
563
+ };
564
+ }
565
+ }
566
+ );
567
+ server2.tool(
568
+ "get_preview_url",
569
+ "Get a preview URL for a file (for documents, images, etc.). Provide either fullpath or hash.",
570
+ {
571
+ mount_id: z3.number().describe("The mount ID of the library"),
572
+ fullpath: z3.string().optional().describe("Full path of the file"),
573
+ hash: z3.string().optional().describe("Hash of the file"),
574
+ watermark: z3.string().optional().describe("Watermark text to add to the preview")
575
+ },
576
+ async ({ mount_id, fullpath, hash, watermark }) => {
577
+ try {
578
+ if (!fullpath && !hash) {
579
+ return {
580
+ content: [{ type: "text", text: "Error: Either fullpath or hash must be provided" }],
581
+ isError: true
582
+ };
583
+ }
584
+ const result = await client.getPreviewUrl(mount_id, { fullpath, hash }, watermark);
585
+ return {
586
+ content: [{ type: "text", text: `Preview URL: ${result.url}` }]
587
+ };
588
+ } catch (error) {
589
+ return {
590
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
591
+ isError: true
592
+ };
593
+ }
594
+ }
595
+ );
596
+ }
597
+
598
+ // src/tools/manage.ts
599
+ import { z as z4 } from "zod";
600
+ function registerManageTools(server2, apiHost2, authType2, token2) {
601
+ const client = new GokuaiClient(apiHost2, authType2, token2);
602
+ server2.tool(
603
+ "create_folder",
604
+ "Create a new folder in the cloud library at the specified path.",
605
+ {
606
+ mount_id: z4.number().describe("The mount ID of the library"),
607
+ fullpath: z4.string().describe("Full path of the folder to create, e.g. '/folder/subfolder'")
608
+ },
609
+ async ({ mount_id, fullpath }) => {
610
+ try {
611
+ const result = await client.createFolder(mount_id, fullpath);
612
+ return {
613
+ content: [{ type: "text", text: `Folder created successfully: ${fullpath}
614
+ ${JSON.stringify(result, null, 2)}` }]
615
+ };
616
+ } catch (error) {
617
+ return {
618
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
619
+ isError: true
620
+ };
621
+ }
622
+ }
623
+ );
624
+ server2.tool(
625
+ "copy_file",
626
+ "Copy file(s) to a target path. Use '|' to separate multiple source paths for batch copy.",
627
+ {
628
+ mount_id: z4.number().describe("The mount ID of the source library"),
629
+ fullpath: z4.string().describe("Full path of the source file(s), use '|' to separate multiple paths"),
630
+ target_mount_id: z4.number().optional().describe("The mount ID of the target library (if different from source)"),
631
+ target_fullpath: z4.string().describe("Full path of the target location")
632
+ },
633
+ async ({ mount_id, fullpath, target_mount_id, target_fullpath }) => {
634
+ try {
635
+ const opts = target_mount_id ? { target_mount_id } : void 0;
636
+ const result = await client.copyFile(mount_id, fullpath, target_fullpath, opts);
637
+ return {
638
+ content: [{ type: "text", text: `File copied successfully.
639
+ From: ${fullpath}
640
+ To: ${target_fullpath}
641
+ ${JSON.stringify(result, null, 2)}` }]
642
+ };
643
+ } catch (error) {
644
+ return {
645
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
646
+ isError: true
647
+ };
648
+ }
649
+ }
650
+ );
651
+ server2.tool(
652
+ "move_file",
653
+ "Move file(s) to a target path. Use '|' to separate multiple source paths for batch move.",
654
+ {
655
+ mount_id: z4.number().describe("The mount ID of the library"),
656
+ fullpath: z4.string().describe("Full path of the source file(s), use '|' to separate multiple paths"),
657
+ target_fullpath: z4.string().describe("Full path of the target location")
658
+ },
659
+ async ({ mount_id, fullpath, target_fullpath }) => {
660
+ try {
661
+ const result = await client.moveFile(mount_id, fullpath, target_fullpath);
662
+ return {
663
+ content: [{ type: "text", text: `File moved successfully.
664
+ From: ${fullpath}
665
+ To: ${target_fullpath}
666
+ ${JSON.stringify(result, null, 2)}` }]
667
+ };
668
+ } catch (error) {
669
+ return {
670
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
671
+ isError: true
672
+ };
673
+ }
674
+ }
675
+ );
676
+ server2.tool(
677
+ "rename_file",
678
+ "Rename a file or folder in the cloud library.",
679
+ {
680
+ mount_id: z4.number().describe("The mount ID of the library"),
681
+ fullpath: z4.string().describe("Full path of the file to rename"),
682
+ newname: z4.string().describe("New name for the file")
683
+ },
684
+ async ({ mount_id, fullpath, newname }) => {
685
+ try {
686
+ const result = await client.renameFile(mount_id, fullpath, newname);
687
+ return {
688
+ content: [{ type: "text", text: `File renamed successfully.
689
+ Path: ${fullpath}
690
+ New name: ${newname}
691
+ ${JSON.stringify(result, null, 2)}` }]
692
+ };
693
+ } catch (error) {
694
+ return {
695
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
696
+ isError: true
697
+ };
698
+ }
699
+ }
700
+ );
701
+ server2.tool(
702
+ "delete_file",
703
+ "Delete a file or folder from the cloud library. This action cannot be undone.",
704
+ {
705
+ mount_id: z4.number().describe("The mount ID of the library"),
706
+ fullpath: z4.string().describe("Full path of the file or folder to delete")
707
+ },
708
+ async ({ mount_id, fullpath }) => {
709
+ try {
710
+ const result = await client.deleteFile(mount_id, fullpath);
711
+ return {
712
+ content: [{ type: "text", text: `File deleted successfully: ${fullpath}
713
+ ${JSON.stringify(result, null, 2)}` }]
714
+ };
715
+ } catch (error) {
716
+ return {
717
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
718
+ isError: true
719
+ };
720
+ }
721
+ }
722
+ );
723
+ }
724
+
725
+ // src/tools/favorites.ts
726
+ import { z as z5 } from "zod";
727
+ function registerFavoritesTools(server2, apiHost2, authType2, token2) {
728
+ const client = new GokuaiClient(apiHost2, authType2, token2);
729
+ server2.tool(
730
+ "list_favorites",
731
+ "List files in the user's favorites collection.",
732
+ {
733
+ start: z5.number().optional().describe("Pagination start index"),
734
+ size: z5.number().optional().describe("Number of items to return")
735
+ },
736
+ async ({ start, size }) => {
737
+ try {
738
+ const result = await client.listFavorites(void 0, start, size);
739
+ if (!result.list || result.list.length === 0) {
740
+ return {
741
+ content: [{ type: "text", text: "No favorites found." }]
742
+ };
743
+ }
744
+ const header = `Favorites - Total: ${result.count}
745
+ ${"\u2500".repeat(50)}`;
746
+ const formatted = result.list.map((file) => {
747
+ const isDir = file.dir === 1;
748
+ const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
749
+ const sizeStr = isDir ? "-" : formatFileSize(file.filesize);
750
+ const created = new Date(file.create_dateline * 1e3).toLocaleString("zh-CN");
751
+ const modified = new Date(file.last_dateline * 1e3).toLocaleString("zh-CN");
752
+ return [
753
+ `${icon} ${file.filename}`,
754
+ ` Hash: ${file.hash}`,
755
+ ` Mount ID: ${file.mount_id}`,
756
+ ` Type: ${isDir ? "Folder" : "File"}`,
757
+ ` Path: ${file.fullpath}`,
758
+ ` File Hash: ${file.filehash || "-"}`,
759
+ ` Size: ${sizeStr}`,
760
+ ` Created: ${created} by ${file.create_member_name} (ID: ${file.create_member_id})`,
761
+ ` Modified: ${modified} by ${file.last_member_name} (ID: ${file.last_member_id})`
762
+ ].join("\n");
763
+ });
764
+ return {
765
+ content: [{ type: "text", text: `${header}
766
+ ${formatted.join("\n")}` }]
767
+ };
768
+ } catch (error) {
769
+ return {
770
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
771
+ isError: true
772
+ };
773
+ }
774
+ }
775
+ );
776
+ server2.tool(
777
+ "add_favorite",
778
+ "Add a file or folder to the user's favorites.",
779
+ {
780
+ mount_id: z5.number().describe("The mount ID of the library"),
781
+ fullpath: z5.string().describe("Full path of the file to add to favorites")
782
+ },
783
+ async ({ mount_id, fullpath }) => {
784
+ try {
785
+ const result = await client.addFavorite(mount_id, fullpath);
786
+ return {
787
+ content: [{ type: "text", text: `Added to favorites: ${fullpath}
788
+ ${JSON.stringify(result, null, 2)}` }]
789
+ };
790
+ } catch (error) {
791
+ return {
792
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
793
+ isError: true
794
+ };
795
+ }
796
+ }
797
+ );
798
+ server2.tool(
799
+ "remove_favorite",
800
+ "Remove a file or folder from the user's favorites.",
801
+ {
802
+ mount_id: z5.number().describe("The mount ID of the library"),
803
+ fullpath: z5.string().describe("Full path of the file to remove from favorites")
804
+ },
805
+ async ({ mount_id, fullpath }) => {
806
+ try {
807
+ const result = await client.removeFavorite(mount_id, fullpath);
808
+ return {
809
+ content: [{ type: "text", text: `Removed from favorites: ${fullpath}
810
+ ${JSON.stringify(result, null, 2)}` }]
811
+ };
812
+ } catch (error) {
813
+ return {
814
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
815
+ isError: true
816
+ };
817
+ }
818
+ }
819
+ );
820
+ }
821
+
822
+ // src/index.ts
823
+ var token = process.env.GOKUAI_TOKEN;
824
+ if (!token) {
825
+ console.error("Error: GOKUAI_TOKEN environment variable is required");
826
+ process.exit(1);
827
+ }
828
+ var authType = process.env.GOKUAI_AUTH_TYPE || "mcp";
829
+ var apiHost = process.env.GOKUAI_API_HOST || "https://yk3.gokuai.com";
830
+ var server = new McpServer({
831
+ name: "gokuai-user-mcp",
832
+ version: "1.0.0"
833
+ });
834
+ registerLibraryTools(server, apiHost, authType, token);
835
+ registerFileTools(server, apiHost, authType, token);
836
+ registerDownloadTools(server, apiHost, authType, token);
837
+ registerManageTools(server, apiHost, authType, token);
838
+ registerFavoritesTools(server, apiHost, authType, token);
839
+ var transport = new StdioServerTransport();
840
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "gokuai-user-mcp",
3
+ "version": "1.0.2",
4
+ "description": "MCP Server for Gokuai Cloud Storage (User API)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "gokuai-user-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "start": "node dist/index.js",
17
+ "inspector": "npx @modelcontextprotocol/inspector node dist/index.js"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "gokuai",
22
+ "cloud-storage",
23
+ "model-context-protocol"
24
+ ],
25
+ "license": "MIT",
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.12.0",
31
+ "zod": "^3.22.0"
32
+ },
33
+ "devDependencies": {
34
+ "@gokuai/shared": "*",
35
+ "@types/node": "^20.10.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.3.0"
38
+ }
39
+ }