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.
- package/dist/index.js +840 -0
- 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
|
+
}
|