qiniu-mcp
Version:
A Model Context Protocol server for Qiniu Cloud Storage services with optimized local file upload support
189 lines • 7.49 kB
JavaScript
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