Skip to main content
Glama
Qihoo360

360 AI Cloud Drive MCP Server

by Qihoo360

file-upload-stdio

Upload local files to specified paths in 360 AI Cloud Drive. Supports batch upload of multiple files for efficient cloud storage management.

Instructions

将本地文件上传到云盘指定路径。支持批量上传多个文件。

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filePathsYes本地文件路径数组,例如:['/本地/文件1.txt', '/本地/文件2.jpg']
uploadPathNo云盘上传目标路径,默认为根目录'/'/

Implementation Reference

  • The registration function that calls server.tool to register the 'file-upload-stdio' tool, including name, description, input schema, and handler.
    export function registerUploadFileStdioTool(server: McpServer) {
      server.tool(
        "file-upload-stdio",
        "将本地文件上传到云盘指定路径。支持批量上传多个文件。",
        {
          filePaths: z.array(z.string()).describe("本地文件路径数组,例如:['/本地/文件1.txt', '/本地/文件2.jpg']"),
          uploadPath: z.string().optional().default('/').describe("云盘上传目标路径,默认为根目录'/'")
        },
        async ({ filePaths, uploadPath }, mcpReq: any) => {
          const httpContext = gethttpContext(mcpReq, server);
                
          // 使用transport中的authInfo
          const transportAuthInfo = httpContext.authInfo;
          try {
            let authInfo: AuthInfo;
            
            try {
              // 获取鉴权信息
              authInfo = await getAuthInfo({}, transportAuthInfo);
    
            } catch (authError) {
              console.error("自动获取鉴权信息失败:", authError);
              throw new Error("获取鉴权信息失败,请提供有效的API_KEY");
            }
            
            try {
              // 检查必填参数
              if (!filePaths || filePaths.length === 0) {
                throw new Error("filePaths为必填参数且不能为空");
              }
              
              // 使用文件路径方式上传
              const uploadResult = await uploadFilesByPath(authInfo, filePaths, uploadPath);
              
              const uploadResults = uploadResult.uploadResults || [];
              const totalUploadTime = uploadResult.totalUploadTime || '未知';
              const duplicateFiles = uploadResult.duplicateFiles || [];
              const progressInfo = uploadResult.progressInfo || {};
              const uploadErrors = uploadResult.uploadErrors || [];
              
              // 处理上传结果,为每个文件创建详细信息
              const fileDetails: FileUploadDetail[] = uploadResults.map((result: any) => {
                const uploadRes = result.uploadRes || result || {};
                const fileId = result.fid || uploadRes.nid || '未知';
                const fileName = uploadRes.name || result.name || '未知';
                
                // 计算单个文件上传耗时
                const fileUploadTime = result.uploadStartTime && result.uploadEndTime
                  ? ((result.uploadEndTime - result.uploadStartTime) / 1000).toFixed(2)
                  : '未知';
                
                // 获取文件大小和计算上传速度
                let fileSize = '未知';
                let uploadSpeed = '未知';
                
                if (progressInfo[fileId]) {
                  fileSize = formatSize(progressInfo[fileId].total);
                  if (fileUploadTime !== '未知') {
                    uploadSpeed = formatSize(progressInfo[fileId].total / parseFloat(fileUploadTime)) + '/s';
                  }
                } else if (uploadRes.count_size) {
                  fileSize = formatSize(parseInt(uploadRes.count_size));
                  if (fileUploadTime !== '未知') {
                    uploadSpeed = formatSize(parseInt(uploadRes.count_size) / parseFloat(fileUploadTime)) + '/s';
                  }
                }
                
                return {
                  fileName: fileName,
                  fileId: fileId,
                  fileSize: fileSize,
                  fileType: getFileCategoryName(uploadRes.file_category || '0'),
                  createTime: uploadRes.create_time ? formatTimestamp(parseInt(uploadRes.create_time)) : '未知',
                  modifyTime: uploadRes.modify_time ? formatTimestamp(parseInt(uploadRes.modify_time)) : '未知',
                  filePath: uploadPath,
                  fileHash: uploadRes.file_hash || undefined,
                  uploadSpeed: uploadSpeed
                };
              });
              
              // 构建上传摘要
              const uploadSummary = [
                `总计上传文件数: ${fileDetails.length}/${filePaths.length}`,
                `总上传耗时: ${totalUploadTime}秒`,
                `上传路径: ${uploadPath}`
              ].join('\n');
              
              // 构建详细的文件信息文本
              const fileDetailsText = fileDetails.map((detail, index) => {
                return [
                  `文件 ${index + 1}:`,
                  `  文件名: ${detail.fileName}`,
                  `  文件ID: ${detail.fileId}`,
                  `  文件大小: ${detail.fileSize}`,
                  `  文件类型: ${detail.fileType}`,
                  `  创建时间: ${detail.createTime}`,
                  `  修改时间: ${detail.modifyTime}`,
                  `  存储路径: ${detail.filePath}`,
                  detail.fileHash ? `  文件哈希: ${detail.fileHash}` : '',
                  `  上传速度: ${detail.uploadSpeed || '未知'}`
                ].filter(line => line).join('\n');
              }).join('\n\n');
              
              // 构建重名文件提示信息
              let duplicateFilesText = '';
              if (duplicateFiles.length > 0) {
                duplicateFilesText = '\n\n检测到以下文件已存在(重名文件):\n' + 
                  duplicateFiles.map((file: any, index: number) => 
                    `${index + 1}. 文件名: ${file.fileName}\n   路径: ${file.filePath}\n   大小: ${file.fileSize}\n   创建时间: ${file.createTime}`
                  ).join('\n\n');
              }
              
              // 构建错误文件提示信息
              let errorFilesText = '';
              if (uploadErrors.length > 0) {
                errorFilesText = '\n\n以下文件上传失败:\n' + 
                  uploadErrors.map((errorInfo: FileUploadError, index: number) => {
                    // 获取API错误详情
                    const apiError = errorInfo.originalError;
                    let detailText = '';
                    
                    if (apiError && typeof apiError === 'object' && 'errno' in apiError) {
                      // 标准API错误
                      detailText = [
                        `   错误码: ${apiError.errno}`,
                        `   错误信息: ${apiError.errmsg || '未知'}`,
                        apiError.trace_id ? `   追踪ID: ${apiError.trace_id}` : '',
                        apiError.consume ? `   处理耗时: ${apiError.consume}ms` : ''
                      ].filter(line => line).join('\n');
                    } else {
                      // 普通错误
                      detailText = `   错误信息: ${errorInfo.formattedMessage}`;
                    }
                    
                    return `${index + 1}. 文件名: ${errorInfo.fileName}\n${detailText}`;
                  }).join('\n\n');
              }
              
              // 返回上传成功信息
              return {
                content: [
                  {
                    type: "text",
                    text: `文件上传完成!\n\n${uploadSummary}\n\n${fileDetailsText}${duplicateFilesText}${errorFilesText}`,
                  },
                  {
                    type: "text",
                    text: TOOL_LIMIT_NOTE,
                  },
                ],
              };
            } catch (uploadError: any) {
              throw uploadError;
            }
          } catch (error: any) {
            console.error("上传文件出错:", error);
            return {
              content: [
                {
                  type: "text",
                  text: `上传文件时发生错误: ${error.message}`,
                },
                {
                  type: "text",
                  text: TOOL_LIMIT_NOTE,
                },
              ],
            };
          }
        },
      );
    }
  • The main tool handler that authenticates, validates inputs, uploads files to cloud storage using SDK, handles errors/duplicates/progress, and returns formatted text response with file details.
    async ({ filePaths, uploadPath }, mcpReq: any) => {
      const httpContext = gethttpContext(mcpReq, server);
            
      // 使用transport中的authInfo
      const transportAuthInfo = httpContext.authInfo;
      try {
        let authInfo: AuthInfo;
        
        try {
          // 获取鉴权信息
          authInfo = await getAuthInfo({}, transportAuthInfo);
    
        } catch (authError) {
          console.error("自动获取鉴权信息失败:", authError);
          throw new Error("获取鉴权信息失败,请提供有效的API_KEY");
        }
        
        try {
          // 检查必填参数
          if (!filePaths || filePaths.length === 0) {
            throw new Error("filePaths为必填参数且不能为空");
          }
          
          // 使用文件路径方式上传
          const uploadResult = await uploadFilesByPath(authInfo, filePaths, uploadPath);
          
          const uploadResults = uploadResult.uploadResults || [];
          const totalUploadTime = uploadResult.totalUploadTime || '未知';
          const duplicateFiles = uploadResult.duplicateFiles || [];
          const progressInfo = uploadResult.progressInfo || {};
          const uploadErrors = uploadResult.uploadErrors || [];
          
          // 处理上传结果,为每个文件创建详细信息
          const fileDetails: FileUploadDetail[] = uploadResults.map((result: any) => {
            const uploadRes = result.uploadRes || result || {};
            const fileId = result.fid || uploadRes.nid || '未知';
            const fileName = uploadRes.name || result.name || '未知';
            
            // 计算单个文件上传耗时
            const fileUploadTime = result.uploadStartTime && result.uploadEndTime
              ? ((result.uploadEndTime - result.uploadStartTime) / 1000).toFixed(2)
              : '未知';
            
            // 获取文件大小和计算上传速度
            let fileSize = '未知';
            let uploadSpeed = '未知';
            
            if (progressInfo[fileId]) {
              fileSize = formatSize(progressInfo[fileId].total);
              if (fileUploadTime !== '未知') {
                uploadSpeed = formatSize(progressInfo[fileId].total / parseFloat(fileUploadTime)) + '/s';
              }
            } else if (uploadRes.count_size) {
              fileSize = formatSize(parseInt(uploadRes.count_size));
              if (fileUploadTime !== '未知') {
                uploadSpeed = formatSize(parseInt(uploadRes.count_size) / parseFloat(fileUploadTime)) + '/s';
              }
            }
            
            return {
              fileName: fileName,
              fileId: fileId,
              fileSize: fileSize,
              fileType: getFileCategoryName(uploadRes.file_category || '0'),
              createTime: uploadRes.create_time ? formatTimestamp(parseInt(uploadRes.create_time)) : '未知',
              modifyTime: uploadRes.modify_time ? formatTimestamp(parseInt(uploadRes.modify_time)) : '未知',
              filePath: uploadPath,
              fileHash: uploadRes.file_hash || undefined,
              uploadSpeed: uploadSpeed
            };
          });
          
          // 构建上传摘要
          const uploadSummary = [
            `总计上传文件数: ${fileDetails.length}/${filePaths.length}`,
            `总上传耗时: ${totalUploadTime}秒`,
            `上传路径: ${uploadPath}`
          ].join('\n');
          
          // 构建详细的文件信息文本
          const fileDetailsText = fileDetails.map((detail, index) => {
            return [
              `文件 ${index + 1}:`,
              `  文件名: ${detail.fileName}`,
              `  文件ID: ${detail.fileId}`,
              `  文件大小: ${detail.fileSize}`,
              `  文件类型: ${detail.fileType}`,
              `  创建时间: ${detail.createTime}`,
              `  修改时间: ${detail.modifyTime}`,
              `  存储路径: ${detail.filePath}`,
              detail.fileHash ? `  文件哈希: ${detail.fileHash}` : '',
              `  上传速度: ${detail.uploadSpeed || '未知'}`
            ].filter(line => line).join('\n');
          }).join('\n\n');
          
          // 构建重名文件提示信息
          let duplicateFilesText = '';
          if (duplicateFiles.length > 0) {
            duplicateFilesText = '\n\n检测到以下文件已存在(重名文件):\n' + 
              duplicateFiles.map((file: any, index: number) => 
                `${index + 1}. 文件名: ${file.fileName}\n   路径: ${file.filePath}\n   大小: ${file.fileSize}\n   创建时间: ${file.createTime}`
              ).join('\n\n');
          }
          
          // 构建错误文件提示信息
          let errorFilesText = '';
          if (uploadErrors.length > 0) {
            errorFilesText = '\n\n以下文件上传失败:\n' + 
              uploadErrors.map((errorInfo: FileUploadError, index: number) => {
                // 获取API错误详情
                const apiError = errorInfo.originalError;
                let detailText = '';
                
                if (apiError && typeof apiError === 'object' && 'errno' in apiError) {
                  // 标准API错误
                  detailText = [
                    `   错误码: ${apiError.errno}`,
                    `   错误信息: ${apiError.errmsg || '未知'}`,
                    apiError.trace_id ? `   追踪ID: ${apiError.trace_id}` : '',
                    apiError.consume ? `   处理耗时: ${apiError.consume}ms` : ''
                  ].filter(line => line).join('\n');
                } else {
                  // 普通错误
                  detailText = `   错误信息: ${errorInfo.formattedMessage}`;
                }
                
                return `${index + 1}. 文件名: ${errorInfo.fileName}\n${detailText}`;
              }).join('\n\n');
          }
          
          // 返回上传成功信息
          return {
            content: [
              {
                type: "text",
                text: `文件上传完成!\n\n${uploadSummary}\n\n${fileDetailsText}${duplicateFilesText}${errorFilesText}`,
              },
              {
                type: "text",
                text: TOOL_LIMIT_NOTE,
              },
            ],
          };
        } catch (uploadError: any) {
          throw uploadError;
        }
      } catch (error: any) {
        console.error("上传文件出错:", error);
        return {
          content: [
            {
              type: "text",
              text: `上传文件时发生错误: ${error.message}`,
            },
            {
              type: "text",
              text: TOOL_LIMIT_NOTE,
            },
          ],
        };
      }
    },
  • Zod input schema defining parameters: filePaths (array of strings, required), uploadPath (string, optional default '/') .
      filePaths: z.array(z.string()).describe("本地文件路径数组,例如:['/本地/文件1.txt', '/本地/文件2.jpg']"),
      uploadPath: z.string().optional().default('/').describe("云盘上传目标路径,默认为根目录'/'")
    },
  • Core helper function that performs the actual file upload using @aicloud360/sec-sdk-node UploadNode, managing config, callbacks for progress/success/error/duplicates, and returns detailed results.
    async function uploadFilesByPath(authInfo: AuthInfo, filePaths: string[], uploadPath: string): Promise<any> {
      return new Promise(async (resolve, reject) => {
        try {
          // 动态导入SDK
          const UploadNode = await importUploadSDK();
          
          // 检查所有文件是否存在
          for (const filePath of filePaths) {
            if (!fs.existsSync(filePath)) {
              reject(new Error(`文件不存在: ${filePath}`));
              return;
            }
          }
          
          // 创建上传配置
          const config = {
            qid: process.env.qid || authInfo.qid || '0',
            token: process.env.ECS_TOKEN || authInfo.token || '',
            access_token: process.env.ECS_ACCESS_TOKEN || authInfo.access_token || '',
            env: process.env.ECS_ENV || 'prod',
            path: uploadPath || '/'
          };
          
          // 上传错误数组
          let uploadErrors: FileUploadError[] = [];
          
          // 记录开始时间
          const startTime = Date.now();
          
          // 存储重名文件列表
          let duplicateFiles: any[] = [];
          
          // 存储上传结果集合
          let uploadResults: any[] = [];
          
          // 记录文件上传开始时间 (fileId -> timestamp)
          const fileStartTimes: Record<string, number> = {};
          
          // 记录文件上传进度信息 (fileId -> {loaded, total, name})
          const fileProgressInfo: Record<string, {loaded: number, total: number, name: string}> = {};
          
          // 格式化API错误
          const formatApiError = (error: any, file: any): FileUploadError => {
            let formattedMessage = '';
            let apiError: ApiError = {};
            
            // 尝试解析API错误
            if (error && typeof error === 'object') {
              // 如果错误包含errno和errmsg,则是标准API错误
              if ('errno' in error && 'errmsg' in error) {
                apiError = error as ApiError;
                formattedMessage = `错误码: ${apiError.errno}, 错误信息: ${apiError.errmsg}`;
                if (apiError.trace_id) {
                  formattedMessage += `, 追踪ID: ${apiError.trace_id}`;
                }
              } else if (error instanceof Error) {
                // 标准Error对象
                apiError = {
                  message: error.message,
                  stack: error.stack
                };
                formattedMessage = error.message;
              } else if (error.message) {
                // 有message属性但不是标准Error
                apiError = {
                  message: error.message
                };
                formattedMessage = error.message;
              } else {
                // 其他情况
                apiError = error;
                formattedMessage = JSON.stringify(error);
              }
            } else {
              // 非对象类型错误
              formattedMessage = String(error);
              apiError = { message: formattedMessage };
            }
            
            return {
              fileName: file?.name || '未知文件',
              filePath: file?.path || '',
              originalError: apiError,
              formattedMessage
            };
          };
          
          // 创建上传器实例
          const uploader = new UploadNode(config, {
            success: (result: any) => {
              
              // 文件ID
              const fileId = result.fid || result.nid;
              
              // 保存上传结果到结果集合
              uploadResults.push({
                ...result,
                uploadEndTime: Date.now(),
                uploadStartTime: fileStartTimes[fileId] || startTime
              });
            },
            progress: (fid: string, loaded: number, total: number, file: any) => {
              if (!loaded) {
                return;
              }
              
              // 记录文件开始上传的时间
              if (!fileStartTimes[fid]) {
                fileStartTimes[fid] = Date.now();
              }
              
              // 记录文件进度信息
              fileProgressInfo[fid] = {
                loaded,
                total,
                name: file.name
              };
              
              console.error(
                'id: ', fid, 
                '上传进度:' + Math.floor((loaded / total) * 100) + '%', 
                '文件:', file.name, 
                '已上传:', formatSize(loaded), 
                '总大小:', formatSize(total)
              );
            },
            error: (file: any, error: any) => {
              // 将错误添加到错误数组中
              uploadErrors.push(formatApiError(error, file));
            },
            duplicateList: (list: any, resData: any) => {
              // 存储重名文件信息
              duplicateFiles = list.map((item: any) => ({
                fileName: item.name || '未知',
                fileId: item.nid || '未知',
                fileSize: item.count_size ? formatSize(parseInt(item.count_size)) : '未知',
                fileType: getFileCategoryName(item.file_category || '0'),
                createTime: item.create_time ? formatTimestamp(parseInt(item.create_time)) : '未知',
                modifyTime: item.modify_time ? formatTimestamp(parseInt(item.modify_time)) : '未知',
                filePath: item.path || uploadPath
              }));
            },
            complete() {
              console.error('上传完成');
              
              // 处理上传错误
              if (uploadErrors.length > 0 && uploadResults.length === 0) {
                // 如果所有文件都上传失败,则拒绝promise
                const errorMessages = uploadErrors.map(err => 
                  `文件: ${err.fileName} - ${err.formattedMessage}`
                ).join('; ');
                reject(new Error(`所有文件上传失败: ${errorMessages}`));
                return;
              }
              
              // 计算总上传耗时
              const endTime = Date.now();
              const totalUploadTime = ((endTime - startTime) / 1000).toFixed(2);
              
              // 添加上传耗时信息
              const resultWithTiming = {
                uploadResults: uploadResults,
                totalUploadTime: totalUploadTime,
                startTime: startTime,
                endTime: endTime,
                duplicateFiles: duplicateFiles, // 添加重名文件列表到结果中
                fileCount: uploadResults.length,
                totalFileCount: filePaths.length,
                progressInfo: fileProgressInfo,
                uploadErrors: uploadErrors // 添加上传错误列表
              };
              
              // 解析结果并返回
              resolve(resultWithTiming);
            }
          });
          
          // 使用addWaitFile添加文件路径,开始上传
          console.error(`准备上传 ${filePaths.length} 个文件到 ${uploadPath}`);
          uploader.addWaitFile(filePaths);
          
        } catch (error) {
          console.error('上传文件过程中出错:', error);
          reject(error);
        }
      });
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It mentions support for batch uploads, which is useful, but lacks critical behavioral details such as authentication requirements, file size limits, overwrite behavior, error handling, or rate limits. For a mutation tool with no annotations, this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences with zero waste: the first states the core functionality, and the second adds the batch capability. It is front-loaded and appropriately sized for the tool's complexity, making it easy to grasp quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no annotations, no output schema, and involves mutation (uploading files), the description is incomplete. It lacks details on return values, error conditions, side effects, or security considerations. For a file upload tool with such complexity, the description should provide more context to guide safe and effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters thoroughly. The description adds minimal value by confirming the upload path is for the cloud drive, but does not provide additional semantics beyond what the schema states. With high schema coverage, the baseline is 3, but the description slightly enhances understanding, warranting a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('upload'), the resource ('local files'), and the destination ('to cloud drive specified path'). It distinguishes from siblings like file-download-stdio (download) and file-move (move within cloud), making the purpose specific and well-differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for uploading local files to cloud storage, but does not explicitly state when to use this tool versus alternatives like file-save (which might save content directly) or file-share (which shares existing cloud files). No exclusions or prerequisites are mentioned, leaving some ambiguity about appropriate contexts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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