Skip to main content
Glama
Qihoo360

360 AI Cloud Drive MCP Server

by Qihoo360

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
NameRequiredDescriptionDefault
urlNo文件下载地址,url或content必传1个
contentNo文件内容(md格式),url或content必传1个,需要传用户指定的完整内容,不能省略任何部分
upload_pathNo云盘存储路径,必须以/开头和结尾。如不指定,默认为带有当前日期的'/AI为我下载/YYYYMMDD/'/AI为我下载/20260103/

Implementation Reference

  • 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}`,
            }
          ],
        };
      }
    },
  • 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: "路径必须以/开头"
      //   })
    },
  • 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}`,
                }
              ],
            };
          }
        },
      );
    }
  • Includes 'file-save' tool registration by calling registerFileSaveTool within the registerAllTools function.
    registerFileSaveTool(server);
  • Top-level call to registerAllTools on the McpServer instance in ECSMcpServer constructor, which chains to file-save registration.
    registerAllTools(this.server);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Qihoo360/ecs_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server