file-save
Save files to cloud storage by providing a URL or text content, specifying the upload path for organized storage.
Instructions
通过URL或文本内容保存文件到云盘
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | 文件下载地址,url或content必传1个 | |
| content | No | 文件内容(md格式),url或content必传1个,需要传用户指定的完整内容,不能省略任何部分 | |
| upload_path | No | 云盘存储路径,必须以/开头和结尾。如不指定,默认为带有当前日期的'/AI为我下载/YYYYMMDD/' | /AI为我下载/20260103/ |
Implementation Reference
- src/tools/file-save.ts:160-296 (handler)Main execution logic for 'file-save' tool: validates input, authenticates, saves file via API, polls asynchronous task status, formats response with file details and cloud disk link.async ({ url, content }, mcpReq: any) => { // 参数验证 if (!url && !content) { return { content: [{ type: "text", text: "❌ 参数错误: 必须提供url或content参数" }] }; } const httpContext = gethttpContext(mcpReq, server); const transportAuthInfo = httpContext.authInfo; try { let authInfo: AuthInfo; try { // 获取鉴权信息 authInfo = await getAuthInfo({ method: 'MCP.saveFile' }, transportAuthInfo); authInfo.request_url = getConfig(transportAuthInfo?.ecsEnv).request_url; } catch (authError) { console.error("获取鉴权信息失败:", authError); throw new Error("获取鉴权信息失败"); } // 调用保存文件API const saveResult = await saveFile(authInfo, { url, content }); if (saveResult && saveResult.errno === 0) { const taskId = saveResult.data?.task_id; if (!taskId) { return { content: [{ type: "text", text: "❌ 保存文件失败: 未获取到任务ID" }] }; } // 轮询任务状态 try { const finalResult = await pollTaskStatus(authInfo, taskId); const resultData = finalResult.data; let resultText = `✅ 文件保存成功!\n\n`; resultText += `🆔 任务ID: ${taskId}\n`; const fileInfo: any = { taskId }; if (resultData) { const qid = resultData.qid || authInfo.qid; const path = resultData.file_path; const nid = resultData.nid; const size = resultData.file_size; fileInfo.path = path; fileInfo.nid = nid; fileInfo.size = size; fileInfo.qid = qid; if (path) { resultText += `📂 云盘路径: ${path}\n`; } if (size) { resultText += `📦 文件大小: ${formatBytes(size)}\n`; } if (path && nid && qid) { const dirPath = path.substring(0, path.lastIndexOf('/') + 1); const href = `https://www.yunpan.com/file/index#/fileManage/my/file/${encodeURIComponent(dirPath)}?focus_nid=${nid}&owner_qid=${qid}`; resultText += `🔗 [点击查看云盘文件](${href})\n`; fileInfo.href = href; } } return { content: [ { type: "text", name: "x-save-result-json", text: JSON.stringify({ status: 'success', file: fileInfo }) }, { type: "text", name: "x-save-result-display", text: resultText } ] }; } catch (pollError: any) { const isTimeout = pollError.message.includes('轮询超时'); const status = isTimeout ? 'timeout' : 'failed'; const resultText = isTimeout ? `⏳ 文件保存轮询超时\n\n- 任务ID: ${taskId}\n- 请稍后检查云盘或使用任务ID查询状态。` : `❌ 文件保存失败\n\n- 任务ID: ${taskId}\n- 错误信息: ${pollError.message}`; return { content: [ { type: "text", name: "x-save-result-json", text: JSON.stringify({ status: status, taskId: taskId, error: pollError.message }) }, { type: "text", name: "x-save-result-display", text: resultText } ] }; } } else { throw new Error(saveResult?.errmsg || "API请求失败"); } } catch (error: any) { return { content: [ { type: "text", text: `保存文件时发生错误: ${error.message}`, } ], }; } },
- src/tools/file-save.ts:147-159 (schema)Zod input schema defining optional 'url' (file download URL) or 'content' (file text content), one required. upload_path commented out.{ url: z.string().optional().describe("文件下载地址,url或content必传1个"), content: z.string().optional().describe("文件内容(md格式),url或content必传1个,需要传用户指定的完整内容,不能省略任何部分"), // upload_path: z.string() // .default('/来自mcp_server/') // .describe("云盘存储路径,必须以/开头和结尾。如不指定,默认为'/来自mcp_server/'。\n- 支持自动创建不存在的一级目录\n- 不支持不存在的多级目录") // .refine((path) => path.endsWith('/'), { // message: "路径必须以/结尾" // }) // .refine((path) => path.startsWith('/'), { // message: "路径必须以/开头" // }) },
- src/tools/file-save.ts:143-298 (registration)Registers the 'file-save' tool on the MCP server instance, specifying name, description, input schema, and handler function.export function registerFileSaveTool(server: McpServer) { server.tool( "file-save", "通过URL或文本内容保存文件到云盘", { url: z.string().optional().describe("文件下载地址,url或content必传1个"), content: z.string().optional().describe("文件内容(md格式),url或content必传1个,需要传用户指定的完整内容,不能省略任何部分"), // upload_path: z.string() // .default('/来自mcp_server/') // .describe("云盘存储路径,必须以/开头和结尾。如不指定,默认为'/来自mcp_server/'。\n- 支持自动创建不存在的一级目录\n- 不支持不存在的多级目录") // .refine((path) => path.endsWith('/'), { // message: "路径必须以/结尾" // }) // .refine((path) => path.startsWith('/'), { // message: "路径必须以/开头" // }) }, async ({ url, content }, mcpReq: any) => { // 参数验证 if (!url && !content) { return { content: [{ type: "text", text: "❌ 参数错误: 必须提供url或content参数" }] }; } const httpContext = gethttpContext(mcpReq, server); const transportAuthInfo = httpContext.authInfo; try { let authInfo: AuthInfo; try { // 获取鉴权信息 authInfo = await getAuthInfo({ method: 'MCP.saveFile' }, transportAuthInfo); authInfo.request_url = getConfig(transportAuthInfo?.ecsEnv).request_url; } catch (authError) { console.error("获取鉴权信息失败:", authError); throw new Error("获取鉴权信息失败"); } // 调用保存文件API const saveResult = await saveFile(authInfo, { url, content }); if (saveResult && saveResult.errno === 0) { const taskId = saveResult.data?.task_id; if (!taskId) { return { content: [{ type: "text", text: "❌ 保存文件失败: 未获取到任务ID" }] }; } // 轮询任务状态 try { const finalResult = await pollTaskStatus(authInfo, taskId); const resultData = finalResult.data; let resultText = `✅ 文件保存成功!\n\n`; resultText += `🆔 任务ID: ${taskId}\n`; const fileInfo: any = { taskId }; if (resultData) { const qid = resultData.qid || authInfo.qid; const path = resultData.file_path; const nid = resultData.nid; const size = resultData.file_size; fileInfo.path = path; fileInfo.nid = nid; fileInfo.size = size; fileInfo.qid = qid; if (path) { resultText += `📂 云盘路径: ${path}\n`; } if (size) { resultText += `📦 文件大小: ${formatBytes(size)}\n`; } if (path && nid && qid) { const dirPath = path.substring(0, path.lastIndexOf('/') + 1); const href = `https://www.yunpan.com/file/index#/fileManage/my/file/${encodeURIComponent(dirPath)}?focus_nid=${nid}&owner_qid=${qid}`; resultText += `🔗 [点击查看云盘文件](${href})\n`; fileInfo.href = href; } } return { content: [ { type: "text", name: "x-save-result-json", text: JSON.stringify({ status: 'success', file: fileInfo }) }, { type: "text", name: "x-save-result-display", text: resultText } ] }; } catch (pollError: any) { const isTimeout = pollError.message.includes('轮询超时'); const status = isTimeout ? 'timeout' : 'failed'; const resultText = isTimeout ? `⏳ 文件保存轮询超时\n\n- 任务ID: ${taskId}\n- 请稍后检查云盘或使用任务ID查询状态。` : `❌ 文件保存失败\n\n- 任务ID: ${taskId}\n- 错误信息: ${pollError.message}`; return { content: [ { type: "text", name: "x-save-result-json", text: JSON.stringify({ status: status, taskId: taskId, error: pollError.message }) }, { type: "text", name: "x-save-result-display", text: resultText } ] }; } } else { throw new Error(saveResult?.errmsg || "API请求失败"); } } catch (error: any) { return { content: [ { type: "text", text: `保存文件时发生错误: ${error.message}`, } ], }; } }, ); }
- src/tools/index.ts:36-36 (registration)Includes 'file-save' tool registration by calling registerFileSaveTool within the registerAllTools function.registerFileSaveTool(server);
- src/server/index.ts:34-34 (registration)Top-level call to registerAllTools on the McpServer instance in ECSMcpServer constructor, which chains to file-save registration.registerAllTools(this.server);