UNPKG

qiniu-mcp

Version:

A Model Context Protocol server for Qiniu Cloud Storage services with optimized local file upload support

189 lines 7.49 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { readFileSync, existsSync } from 'fs'; import { basename } from 'path'; import { ConfigManager } from './config.js'; import { RemoteClient } from './remote-client.js'; import { QINIU_TOOLS } from './tools.js'; class QiniuMCPServer { server; configManager; remoteClient; constructor() { this.server = new Server({ name: 'qiniu-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.configManager = ConfigManager.getInstance(); this.remoteClient = new RemoteClient(this.configManager.getRemoteConfig()); this.setupHandlers(); } setupHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: QINIU_TOOLS, }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Check if we have valid configuration const qiniuConfig = this.configManager.loadConfig(); if (!qiniuConfig) { return this.createErrorResult('Qiniu configuration not found. Please set up your API keys using environment variables or config file.\n\n' + 'Required environment variables:\n' + '- QINIU_ACCESS_KEY: Your Qiniu access key\n' + '- QINIU_SECRET_KEY: Your Qiniu secret key\n\n' + 'Optional environment variables:\n' + '- QINIU_BUCKET: Default bucket name\n' + '- QINIU_REGION: Default region (e.g., z0)\n' + '- QINIU_DOMAIN: Custom domain\n\n' + 'Or create a config file at ~/.qiniu-mcp.json with:\n' + this.configManager.generateConfigTemplate()); } // Add default bucket if not provided const toolArgs = args || {}; if (!toolArgs.bucket && qiniuConfig.bucket) { toolArgs.bucket = qiniuConfig.bucket; } // Handle local file upload specially if (name === 'qiniu_upload_local_file') { return await this.handleLocalFileUpload(toolArgs, qiniuConfig); } // Send request to remote server const response = await this.remoteClient.sendMCPRequest('tools/call', { name, arguments: toolArgs }, qiniuConfig); if (response.result) { return this.createSuccessResult(response.result); } else if (response.error) { return this.createErrorResult(response.error.message || 'Unknown error occurred'); } else { return this.createErrorResult('Unknown error occurred'); } } catch (error) { return this.createErrorResult(`Tool execution failed: ${error}`); } }); } /** * 处理本地文件上传 */ async handleLocalFileUpload(args, qiniuConfig) { try { let filePath = args.filePath; // 处理 file:// URL if (filePath.startsWith('file://')) { filePath = filePath.replace('file://', ''); // 在Windows上处理路径 if (process.platform === 'win32' && filePath.startsWith('/')) { filePath = filePath.substring(1); } } // 检查文件是否存在 if (!existsSync(filePath)) { return this.createErrorResult(`File not found: ${filePath}`); } // 读取文件内容 const fileBuffer = readFileSync(filePath); const base64Content = fileBuffer.toString('base64'); // 获取文件名 const key = args.key || basename(filePath); // 检测文件类型 const extension = filePath.split('.').pop()?.toLowerCase(); let contentType = 'application/octet-stream'; const mimeTypes = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'pdf': 'application/pdf', 'txt': 'text/plain', 'json': 'application/json', 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript' }; if (extension && mimeTypes[extension]) { contentType = mimeTypes[extension]; } // 使用base64上传 const response = await this.remoteClient.sendMCPRequest('tools/call', { name: 'qiniu_upload_base64', arguments: { base64Content, bucket: args.bucket, key, contentType, expires: args.expires } }, qiniuConfig); if (response.result) { return this.createSuccessResult(response.result); } else if (response.error) { return this.createErrorResult(response.error.message || 'Upload failed'); } else { return this.createErrorResult('Upload failed'); } } catch (error) { return this.createErrorResult(`Local file upload failed: ${error}`); } } createSuccessResult(data) { return { content: [ { type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2), }, ], }; } createErrorResult(error) { return { content: [ { type: 'text', text: `Error: ${error}`, }, ], isError: true, }; } async run() { // Test remote server connection const qiniuConfig = this.configManager.loadConfig(); if (qiniuConfig) { const isConnected = await this.remoteClient.testConnection(qiniuConfig); if (!isConnected) { console.error('Warning: Cannot connect to remote Qiniu server. Please check your configuration.'); } else { console.error('Successfully connected to Qiniu MCP server'); } } const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Qiniu MCP client running on stdio'); } } // Start the server const server = new QiniuMCPServer(); server.run().catch((error) => { console.error('Failed to start server:', error); process.exit(1); }); //# sourceMappingURL=index.js.map