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); } }); }

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