Skip to main content
Glama
server.js51.4 kB
import express from 'express'; import cors from 'cors'; import { v4 as uuidv4 } from 'uuid'; import fetch from 'node-fetch'; import fs from 'fs'; import path from 'path'; import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, Table, TableRow, TableCell, WidthType, BorderStyle, LevelFormat, NumberFormat, convertInchesToTwip, convertMillimetersToTwip, SectionType, PageOrientation, PageNumber, Footer, Header, UnderlineType } from 'docx'; import mammoth from 'mammoth'; 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 { createServer } from 'http'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import crypto from 'crypto'; const app = express(); const PORT = process.env.PORT || 3500; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // 生成API Key function generateApiKey() { return 'mcp_' + crypto.randomBytes(32).toString('hex'); } // 存储API Key let currentApiKey = generateApiKey(); let apiKeyExpiry = Date.now() + (24 * 60 * 60 * 1000); // 24小时后过期 console.log('🔑 API Key 已生成:', currentApiKey); console.log('⏰ 过期时间:', new Date(apiKeyExpiry).toLocaleString()); // 启用CORS app.use(cors({ origin: true, credentials: true })); app.use(express.json()); // 静态文件服务 app.use(express.static(join(__dirname, '..', 'public'))); // 路由配置 app.get('/', (req, res) => { res.sendFile(join(__dirname, '..', 'public', 'chat.html')); }); app.get('/start', (req, res) => { res.sendFile(join(__dirname, '..', 'public', 'start.html')); }); app.get('/mcp', (req, res) => { res.sendFile(join(__dirname, '..', 'public', 'index.html')); }); // 存储SSE连接 const connections = new Map(); // 加载天气配置 function loadWeatherConfig() { try { const configPath = path.join(process.cwd(), 'config', 'weather.json'); const configData = fs.readFileSync(configPath, 'utf8'); return JSON.parse(configData); } catch (error) { console.warn('无法加载天气配置文件,使用默认配置:', error.message); return { openweathermap: { enabled: false }, mockData: { enabled: true, cities: {} } }; } } // 创建MCP服务器实例 const server = new Server( { name: "mcp-sse-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // 注册工具 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_docx", description: "创建新的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, content: { type: "string", description: "文档内容(支持换行符\\n)", } }, required: ["fileName", "title", "content"], }, }, { name: "read_docx", description: "读取Word文档内容", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Word文档文件路径(相对于documents目录)", }, format: { type: "string", description: "输出格式", enum: ["text", "html", "markdown"], default: "text" } }, required: ["filePath"], }, }, { name: "replace_text_in_docx", description: "替换Word文档中的文本", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Word文档文件路径", }, searchText: { type: "string", description: "要搜索替换的文本", }, replaceText: { type: "string", description: "替换后的文本", }, outputFileName: { type: "string", description: "输出文件名(不包含扩展名)", } }, required: ["filePath", "searchText", "replaceText", "outputFileName"], }, }, { name: "create_formatted_docx", description: "创建带格式的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, content: { type: "array", description: "格式化内容数组", items: { type: "object", properties: { text: { type: "string", description: "文本内容" }, fontFamily: { type: "string", description: "字体" }, fontSize: { type: "number", description: "字号" }, bold: { type: "boolean", description: "是否粗体" }, italic: { type: "boolean", description: "是否斜体" }, underline: { type: "boolean", description: "是否下划线" }, color: { type: "string", description: "文字颜色" }, alignment: { type: "string", description: "对齐方式" }, heading: { type: "number", description: "标题级别" }, }, required: ["text"], }, } }, required: ["fileName", "title", "content"], }, }, { name: "create_table_in_docx", description: "创建包含表格的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, tableData: { type: "object", description: "表格数据", properties: { headers: { type: "array", items: { type: "string" }, description: "表格标题行", }, rows: { type: "array", items: { type: "array", items: { type: "string" }, }, description: "表格数据行", }, style: { type: "object", description: "表格样式设置", }, }, required: ["headers", "rows"], } }, required: ["fileName", "title", "tableData"], }, } ], }; }); // 处理工具调用 server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case "create_docx": try { const { createDocument } = await import('./docxHandler.js'); const result = await createDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.content ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建文档失败: ${error.message}`, }, ], }; } case "read_docx": try { const { readDocument } = await import('./docxHandler.js'); const result = await readDocument( request.params.arguments.filePath, request.params.arguments.format || 'text' ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `读取文档失败: ${error.message}`, }, ], }; } case "replace_text_in_docx": try { const { replaceTextInDocument } = await import('./docxHandler.js'); const result = await replaceTextInDocument( request.params.arguments.filePath, request.params.arguments.searchText, request.params.arguments.replaceText, request.params.arguments.outputFileName ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `替换文本失败: ${error.message}`, }, ], }; } case "create_formatted_docx": try { const { createFormattedDocument } = await import('./docxHandler.js'); const result = await createFormattedDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.content ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建格式化文档失败: ${error.message}`, }, ], }; } case "create_table_in_docx": try { const { createTableDocument } = await import('./docxHandler.js'); const result = await createTableDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.tableData ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建表格文档失败: ${error.message}`, }, ], }; } default: throw new Error(`未知工具: ${request.params.name}`); } }); // SSE端点 app.get('/sse', (req, res) => { // 设置SSE响应头 res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control', }); const connectionId = uuidv4(); connections.set(connectionId, res); console.log(`新的SSE连接: ${connectionId}`); // 发送连接确认 res.write(`data: ${JSON.stringify({ type: 'connection', id: connectionId, message: 'SSE连接已建立' })}\n\n`); // 处理连接关闭 req.on('close', () => { connections.delete(connectionId); console.log(`SSE连接关闭: ${connectionId}`); }); req.on('error', (err) => { console.error('SSE连接错误:', err); connections.delete(connectionId); }); }); // MCP消息处理端点 app.post('/mcp', async (req, res) => { try { const mcpRequest = req.body; console.log('收到MCP请求:', JSON.stringify(mcpRequest, null, 2)); let response; if (mcpRequest.method === 'tools/list') { // 直接返回工具列表 response = { jsonrpc: '2.0', id: mcpRequest.id, result: { tools: [ { name: "create_docx", description: "创建新的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, content: { type: "string", description: "文档内容(支持换行符\\n)", } }, required: ["fileName", "title", "content"], }, }, { name: "read_docx", description: "读取Word文档内容", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Word文档文件路径(相对于documents目录)", }, format: { type: "string", description: "输出格式", enum: ["text", "html", "markdown"], default: "text" } }, required: ["filePath"], }, }, { name: "replace_text_in_docx", description: "替换Word文档中的文本", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Word文档文件路径", }, searchText: { type: "string", description: "要搜索替换的文本", }, replaceText: { type: "string", description: "替换后的文本", }, outputFileName: { type: "string", description: "输出文件名(不包含扩展名)", } }, required: ["filePath", "searchText", "replaceText", "outputFileName"], }, }, { name: "create_formatted_docx", description: "创建带格式的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, content: { type: "array", description: "格式化内容数组", items: { type: "object", properties: { text: { type: "string", description: "文本内容" }, fontFamily: { type: "string", description: "字体" }, fontSize: { type: "number", description: "字号" }, bold: { type: "boolean", description: "是否粗体" }, italic: { type: "boolean", description: "是否斜体" }, underline: { type: "boolean", description: "是否下划线" }, color: { type: "string", description: "文字颜色" }, alignment: { type: "string", description: "对齐方式" }, heading: { type: "number", description: "标题级别" }, }, required: ["text"], }, } }, required: ["fileName", "title", "content"], }, }, { name: "create_table_in_docx", description: "创建包含表格的Word文档", inputSchema: { type: "object", properties: { fileName: { type: "string", description: "文件名(不包含扩展名)", }, title: { type: "string", description: "文档标题", }, tableData: { type: "object", description: "表格数据", properties: { headers: { type: "array", items: { type: "string" }, description: "表格标题行", }, rows: { type: "array", items: { type: "array", items: { type: "string" }, }, description: "表格数据行", }, style: { type: "object", description: "表格样式设置", }, }, required: ["headers", "rows"], } }, required: ["fileName", "title", "tableData"], }, } ] } }; } else if (mcpRequest.method === 'tools/call') { // 处理工具调用 const toolName = mcpRequest.params.name; const args = mcpRequest.params.arguments; let content; switch (toolName) { case "echo": content = [{ type: "text", text: `回显: ${args.text}`, }]; break; case "get_current_time": content = [{ type: "text", text: `当前时间: ${new Date().toLocaleString('zh-CN')}`, }]; break; case "calculate": try { const expression = args.expression; const result = eval(expression.replace(/[^0-9+\-*/().\s]/g, '')); content = [{ type: "text", text: `计算结果: ${expression} = ${result}`, }]; } catch (error) { content = [{ type: "text", text: `计算错误: ${error.message}`, }]; } break; case "get_weather": try { const city = args.city; const units = args.units || 'metric'; const weatherConfig = loadWeatherConfig(); let weatherData; let isRealData = false; // 尝试使用真实的 API if (weatherConfig.openweathermap.enabled && weatherConfig.openweathermap.apiKey !== 'your_api_key_here') { try { const API_KEY = weatherConfig.openweathermap.apiKey; const url = `${weatherConfig.openweathermap.baseUrl}/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=${units}&lang=zh_cn`; const response = await fetch(url); if (response.ok) { weatherData = await response.json(); isRealData = true; } else { throw new Error(`API请求失败: ${response.status}`); } } catch (apiError) { console.warn('真实API调用失败,使用模拟数据:', apiError.message); } } // 如果真实API失败或未配置,使用模拟数据 if (!weatherData) { const mockCity = weatherConfig.mockData.cities[city]; if (mockCity) { // 根据单位转换温度 let temp = mockCity.temp; let feels_like = mockCity.feels_like; let wind_speed = mockCity.wind_speed; if (units === 'imperial') { temp = Math.round(temp * 9/5 + 32); feels_like = Math.round(feels_like * 9/5 + 32); wind_speed = Math.round(wind_speed * 2.237 * 100) / 100; } else if (units === 'kelvin') { temp = Math.round(temp + 273.15); feels_like = Math.round(feels_like + 273.15); } weatherData = { name: city, main: { temp: temp, feels_like: feels_like, humidity: mockCity.humidity, pressure: mockCity.pressure }, weather: [ { main: "Mock", description: mockCity.description, icon: "01d" } ], wind: { speed: wind_speed }, visibility: mockCity.visibility }; } else { // 默认数据 weatherData = { name: city, main: { temp: units === 'metric' ? 20 : units === 'imperial' ? 68 : 293, feels_like: units === 'metric' ? 22 : units === 'imperial' ? 72 : 295, humidity: 60, pressure: 1013 }, weather: [ { main: "Unknown", description: "未知天气", icon: "01d" } ], wind: { speed: units === 'metric' ? 3.0 : 6.7 }, visibility: 10000 }; } } const tempUnit = units === 'metric' ? '°C' : units === 'imperial' ? '°F' : 'K'; const speedUnit = units === 'metric' ? 'm/s' : 'mph'; const dataSource = isRealData ? '🌐 实时数据' : '🎭 模拟数据'; const weatherReport = `🌤️ ${weatherData.name} 天气信息 (${dataSource}): 📊 温度: ${weatherData.main.temp}${tempUnit} (体感 ${weatherData.main.feels_like}${tempUnit}) 🌤️ 天气: ${weatherData.weather[0].description} 💧 湿度: ${weatherData.main.humidity}% 🌬️ 风速: ${weatherData.wind.speed} ${speedUnit} 🔍 能见度: ${weatherData.visibility / 1000} km 📈 气压: ${weatherData.main.pressure} hPa ${isRealData ? '✅ 数据来源: OpenWeatherMap API' : '💡 提示: 这是模拟数据,要获取实时数据请在 config/weather.json 中配置 OpenWeatherMap API key'}`; content = [{ type: "text", text: weatherReport, }]; } catch (error) { content = [{ type: "text", text: `天气查询错误: ${error.message}`, }]; } break; case "read_docx": try { const filePath = args.filePath; const format = args.format || 'text'; const fullPath = path.join(process.cwd(), 'documents', filePath); if (!fs.existsSync(fullPath)) { throw new Error(`文件不存在: ${filePath}`); } const result = await mammoth.convertToHtml(fs.readFileSync(fullPath)); let output; if (format === 'html') { output = result.value; } else if (format === 'markdown') { // 简单的HTML到Markdown转换 output = result.value .replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, (match, level, text) => '#'.repeat(parseInt(level)) + ' ' + text + '\n') .replace(/<p>(.*?)<\/p>/g, '$1\n\n') .replace(/<strong>(.*?)<\/strong>/g, '**$1**') .replace(/<em>(.*?)<\/em>/g, '*$1*') .replace(/<br\s*\/?>/g, '\n') .replace(/<[^>]*>/g, ''); } else { output = result.value.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim(); } content = [{ type: "text", text: `📄 文档读取成功 (${format}格式):\n\n${output}`, }]; } catch (error) { content = [{ type: "text", text: `❌ 文档读取失败: ${error.message}`, }]; } break; case "create_docx": try { const fileName = args.fileName; const title = args.title; const docContent = args.content; // 创建文档段落 const paragraphs = [ new Paragraph({ children: [ new TextRun({ text: title, bold: true, size: 32, }), ], heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER, }), new Paragraph({ children: [new TextRun("")], }), ]; // 处理内容(支持换行) const contentLines = docContent.split('\n'); contentLines.forEach(line => { paragraphs.push(new Paragraph({ children: [new TextRun({ text: line, size: 24, })], })); }); const doc = new Document({ sections: [{ properties: {}, children: paragraphs, }], }); // 确保输出目录存在 const outputDir = path.join(process.cwd(), 'documents', 'output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 保存文档 const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`); const buffer = await Packer.toBuffer(doc); fs.writeFileSync(outputPath, buffer); content = [{ type: "text", text: `✅ Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📄 内容长度: ${docContent.length} 字符`, }]; } catch (error) { content = [{ type: "text", text: `❌ 文档创建失败: ${error.message}`, }]; } break; case "replace_text_in_docx": try { const filePath = args.filePath; const searchText = args.searchText; const replaceText = args.replaceText; const outputFileName = args.outputFileName; const fullPath = path.join(process.cwd(), 'documents', filePath); if (!fs.existsSync(fullPath)) { throw new Error(`文件不存在: ${filePath}`); } // 读取原文档内容 const result = await mammoth.convertToHtml(fs.readFileSync(fullPath)); let htmlContent = result.value; // 执行文本替换 const originalCount = (htmlContent.match(new RegExp(searchText, 'g')) || []).length; htmlContent = htmlContent.replace(new RegExp(searchText, 'g'), replaceText); // 简单转换回文档格式(这里简化处理) const textContent = htmlContent.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim(); const lines = textContent.split(/[.!?]+/).filter(line => line.trim()); const paragraphs = [ new Paragraph({ children: [ new TextRun({ text: "已处理的文档", bold: true, size: 28, }), ], heading: HeadingLevel.HEADING_1, }), ]; lines.forEach(line => { if (line.trim()) { paragraphs.push(new Paragraph({ children: [new TextRun({ text: line.trim() + '.', size: 24, })], })); } }); const doc = new Document({ sections: [{ properties: {}, children: paragraphs, }], }); // 确保输出目录存在 const outputDir = path.join(process.cwd(), 'documents', 'output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 保存文档 const outputPath = path.join(process.cwd(), 'documents', 'output', `${outputFileName}.docx`); const buffer = await Packer.toBuffer(doc); fs.writeFileSync(outputPath, buffer); content = [{ type: "text", text: `✅ 文本替换完成!\n🔍 查找: "${searchText}"\n✏️ 替换为: "${replaceText}"\n📊 替换次数: ${originalCount}\n📁 输出文件: documents/output/${outputFileName}.docx`, }]; } catch (error) { content = [{ type: "text", text: `❌ 文本替换失败: ${error.message}`, }]; } break; case "create_formatted_docx": try { const fileName = args.fileName; const title = args.title; const contentArray = args.content; // 创建文档段落 const paragraphs = [ new Paragraph({ children: [ new TextRun({ text: title, bold: true, size: 32, }), ], heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER, }), new Paragraph({ children: [new TextRun("")], }), ]; // 处理格式化内容 contentArray.forEach(item => { const textRun = new TextRun({ text: item.text, fontFamily: item.fontFamily, size: item.fontSize ? item.fontSize * 2 : 24, bold: item.bold || false, italics: item.italic || false, underline: item.underline ? { color: item.color || '000000', type: UnderlineType.SINGLE } : undefined, color: item.color || '000000', }); const paragraph = new Paragraph({ children: [textRun], alignment: item.alignment === 'center' ? AlignmentType.CENTER : item.alignment === 'right' ? AlignmentType.RIGHT : item.alignment === 'justify' ? AlignmentType.JUSTIFIED : AlignmentType.LEFT, heading: item.heading ? (item.heading === 1 ? HeadingLevel.HEADING_1 : item.heading === 2 ? HeadingLevel.HEADING_2 : item.heading === 3 ? HeadingLevel.HEADING_3 : item.heading === 4 ? HeadingLevel.HEADING_4 : item.heading === 5 ? HeadingLevel.HEADING_5 : item.heading === 6 ? HeadingLevel.HEADING_6 : undefined) : undefined, }); paragraphs.push(paragraph); }); const doc = new Document({ sections: [{ properties: {}, children: paragraphs, }], }); // 确保输出目录存在 const outputDir = path.join(process.cwd(), 'documents', 'output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 保存文档 const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`); const buffer = await Packer.toBuffer(doc); fs.writeFileSync(outputPath, buffer); content = [{ type: "text", text: `✅ 格式化Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📄 内容段落数: ${contentArray.length}`, }]; } catch (error) { content = [{ type: "text", text: `❌ 格式化文档创建失败: ${error.message}`, }]; } break; case "create_table_in_docx": try { const fileName = args.fileName; const title = args.title; const tableData = args.tableData; // 创建标题段落 const paragraphs = [ new Paragraph({ children: [ new TextRun({ text: title, bold: true, size: 32, }), ], heading: HeadingLevel.TITLE, alignment: AlignmentType.CENTER, }), new Paragraph({ children: [new TextRun("")], }), ]; // 创建表格行 const tableRows = []; // 添加标题行 const headerRow = new TableRow({ children: tableData.headers.map(header => new TableCell({ children: [ new Paragraph({ children: [ new TextRun({ text: header, bold: true, size: 24, }), ], alignment: AlignmentType.CENTER, }), ], shading: { fill: "e5f3ff", }, }) ), }); tableRows.push(headerRow); // 添加数据行 tableData.rows.forEach(row => { const tableRow = new TableRow({ children: row.map(cell => new TableCell({ children: [ new Paragraph({ children: [ new TextRun({ text: cell, size: 22, }), ], }), ], }) ), }); tableRows.push(tableRow); }); // 创建表格 const table = new Table({ rows: tableRows, width: { size: 100, type: WidthType.PERCENTAGE, }, borders: { top: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, bottom: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, left: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, right: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, insideHorizontal: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, insideVertical: { style: BorderStyle.SINGLE, size: 1, color: "000000" }, }, }); paragraphs.push(new Paragraph({ children: [new TextRun("")] })); const doc = new Document({ sections: [{ properties: {}, children: [...paragraphs, table], }], }); // 确保输出目录存在 const outputDir = path.join(process.cwd(), 'documents', 'output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 保存文档 const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`); const buffer = await Packer.toBuffer(doc); fs.writeFileSync(outputPath, buffer); content = [{ type: "text", text: `✅ 表格Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📊 表格大小: ${tableData.headers.length} 列 × ${tableData.rows.length + 1} 行`, }]; } catch (error) { content = [{ type: "text", text: `❌ 表格文档创建失败: ${error.message}`, }]; } break; default: throw new Error(`未知工具: ${toolName}`); } response = { jsonrpc: '2.0', id: mcpRequest.id, result: { content: content } }; } else { throw new Error(`不支持的方法: ${mcpRequest.method}`); } // 通过SSE广播响应 broadcastToConnections({ type: 'mcp-response', data: response }); res.json(response); } catch (error) { console.error('MCP请求处理错误:', error); const errorResponse = { jsonrpc: '2.0', id: req.body.id || null, error: { code: -32603, message: error.message } }; res.status(500).json(errorResponse); } }); // 广播消息到所有SSE连接 function broadcastToConnections(message) { const messageStr = `data: ${JSON.stringify(message)}\n\n`; for (const [connectionId, connection] of connections) { try { connection.write(messageStr); } catch (error) { console.error(`向连接 ${connectionId} 发送消息失败:`, error); connections.delete(connectionId); } } } // 健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), connections: connections.size }); }); // API Key管理路由 app.get('/api/key', (req, res) => { // 检查API Key是否过期 if (Date.now() > apiKeyExpiry) { currentApiKey = generateApiKey(); apiKeyExpiry = Date.now() + (24 * 60 * 60 * 1000); console.log('🔄 API Key 已更新:', currentApiKey); } res.json({ apiKey: currentApiKey, expiry: new Date(apiKeyExpiry).toISOString(), timeRemaining: Math.max(0, apiKeyExpiry - Date.now()) }); }); // 重新生成API Key app.post('/api/key/regenerate', (req, res) => { currentApiKey = generateApiKey(); apiKeyExpiry = Date.now() + (24 * 60 * 60 * 1000); console.log('🔄 API Key 已重新生成:', currentApiKey); res.json({ apiKey: currentApiKey, expiry: new Date(apiKeyExpiry).toISOString(), message: 'API Key已重新生成' }); }); // AI对话路由(简化版,仅用于内部测试) app.post('/api/chat', async (req, res) => { try { const { message, history = [] } = req.body; if (!message) { return res.status(400).json({ error: '消息不能为空' }); } // 简单的消息处理,主要用于测试MCP工具 const response = await processSimpleMessage(message, history); res.json({ response: response.text, toolCalls: response.toolCalls || [], timestamp: new Date().toISOString() }); } catch (error) { console.error('消息处理错误:', error); res.status(500).json({ error: error.message || '处理消息时发生错误' }); } }); // 简单的消息处理(用于内部测试) async function processSimpleMessage(message, history) { const lowerMessage = message.toLowerCase(); let response = { text: '', toolCalls: [] }; // 创建Word文档 if (lowerMessage.includes('创建文档') || lowerMessage.includes('生成文档') || lowerMessage.includes('写文档')) { const titleMatch = message.match(/(?:标题|题目|名称)[::]?\s*(.+?)(?:\s|$|,|。)/); const contentMatch = message.match(/(?:内容|正文)[::]?\s*(.+)/); const title = titleMatch ? titleMatch[1] : '文档标题'; const content = contentMatch ? contentMatch[1] : message; const fileName = `文档_${Date.now()}`; try { const toolResult = await callInternalTool('create_docx', { fileName, title, content }); response.toolCalls.push({ tool: 'create_docx', arguments: { fileName, title, content }, result: toolResult }); response.text = `已为您创建文档:${toolResult}`; } catch (error) { response.text = '创建文档时出现错误。'; } } // 读取Word文档 else if (lowerMessage.includes('读取文档') || lowerMessage.includes('打开文档') || lowerMessage.includes('查看文档')) { const fileMatch = message.match(/(?:文件|文档)[::]?\s*(.+?\.docx)/i); if (fileMatch) { const filePath = fileMatch[1]; try { const toolResult = await callInternalTool('read_docx', { filePath, format: 'text' }); response.toolCalls.push({ tool: 'read_docx', arguments: { filePath, format: 'text' }, result: toolResult }); response.text = `文档读取结果:${toolResult}`; } catch (error) { response.text = `读取文档"${filePath}"时出现错误。`; } } else { response.text = '请指定要读取的文档文件名,例如:"读取文档 test.docx"'; } } // 替换文档文本 else if (lowerMessage.includes('替换文本') || lowerMessage.includes('修改文档')) { const fileMatch = message.match(/(?:文件|文档)[::]?\s*(.+?\.docx)/i); const searchMatch = message.match(/(?:查找|替换|原文)[::]?\s*"([^"]+)"/); const replaceMatch = message.match(/(?:替换为|新文)[::]?\s*"([^"]+)"/); if (fileMatch && searchMatch && replaceMatch) { const filePath = fileMatch[1]; const searchText = searchMatch[1]; const replaceText = replaceMatch[1]; const outputFileName = `修改后_${Date.now()}`; try { const toolResult = await callInternalTool('replace_text_in_docx', { filePath, searchText, replaceText, outputFileName }); response.toolCalls.push({ tool: 'replace_text_in_docx', arguments: { filePath, searchText, replaceText, outputFileName }, result: toolResult }); response.text = `文本替换结果:${toolResult}`; } catch (error) { response.text = '替换文本时出现错误。'; } } else { response.text = '请按格式指定:替换文档 test.docx 查找"旧文本" 替换为"新文本"'; } } // 创建格式化文档 else if (lowerMessage.includes('格式化文档') || lowerMessage.includes('带格式文档')) { const title = '格式化文档示例'; const fileName = `格式化文档_${Date.now()}`; const content = [ { text: '一级标题', heading: 1, bold: true }, { text: '这是普通段落文本。', fontSize: 12 }, { text: '二级标题', heading: 2, bold: true }, { text: '这是加粗文本。', bold: true }, { text: '这是斜体文本。', italic: true }, { text: '这是下划线文本。', underline: true } ]; try { const toolResult = await callInternalTool('create_formatted_docx', { fileName, title, content }); response.toolCalls.push({ tool: 'create_formatted_docx', arguments: { fileName, title, content }, result: toolResult }); response.text = `已为您创建格式化文档:${toolResult}`; } catch (error) { response.text = '创建格式化文档时出现错误。'; } } // 创建表格文档 else if (lowerMessage.includes('创建表格') || lowerMessage.includes('生成表格') || lowerMessage.includes('表格文档')) { const title = '表格文档示例'; const fileName = `表格文档_${Date.now()}`; const tableData = { headers: ['姓名', '年龄', '职位'], rows: [ ['张三', '25', '工程师'], ['李四', '30', '设计师'], ['王五', '28', '产品经理'] ] }; try { const toolResult = await callInternalTool('create_table_in_docx', { fileName, title, tableData }); response.toolCalls.push({ tool: 'create_table_in_docx', arguments: { fileName, title, tableData }, result: toolResult }); response.text = `已为您创建表格文档:${toolResult}`; } catch (error) { response.text = '创建表格文档时出现错误。'; } } // 默认回复 else { response.text = `您好!这是专业的Word文档处理MCP工具服务器。我可以帮您: 📄 创建文档 - 说"创建文档,标题:会议记录,内容:..." 👀 读取文档 - 说"读取文档 example.docx" ✏️ 替换文本 - 说"替换文档 test.docx 查找"旧文本" 替换为"新文本"" 🎨 格式化文档 - 说"创建格式化文档" 📊 表格文档 - 说"创建表格文档" 专为Cursor等IDE提供Word文档处理的MCP工具调用服务。`; } return response; } // 启动服务器 app.listen(PORT, () => { console.log(`MCP SSE服务器运行在端口 ${PORT}`); console.log(`🔑 API Key: ${currentApiKey}`); console.log(`🌐 启动页面: http://localhost:${PORT}/start`); console.log(`💬 对话页面: http://localhost:${PORT}`); console.log(`📡 SSE端点: http://localhost:${PORT}/sse`); console.log(`🔧 MCP端点: http://localhost:${PORT}/mcp`); // 自动打开浏览器到启动页面 const open = import('open'); open.then(({ default: openBrowser }) => { openBrowser(`http://localhost:${PORT}/start`); }).catch(() => { console.log('无法自动打开浏览器,请手动访问 http://localhost:' + PORT + '/start'); }); }); // 优雅关闭 process.on('SIGTERM', () => { console.log('收到SIGTERM信号,正在关闭服务器...'); process.exit(0); }); process.on('SIGINT', () => { console.log('收到SIGINT信号,正在关闭服务器...'); process.exit(0); }); // 处理工具调用的统一函数 async function handleToolCall(request) { switch (request.params.name) { case "create_docx": try { const { createDocument } = await import('./docxHandler.js'); const result = await createDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.content ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建文档失败: ${error.message}`, }, ], }; } case "read_docx": try { const { readDocument } = await import('./docxHandler.js'); const result = await readDocument( request.params.arguments.filePath, request.params.arguments.format || 'text' ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `读取文档失败: ${error.message}`, }, ], }; } case "replace_text_in_docx": try { const { replaceTextInDocument } = await import('./docxHandler.js'); const result = await replaceTextInDocument( request.params.arguments.filePath, request.params.arguments.searchText, request.params.arguments.replaceText, request.params.arguments.outputFileName ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `替换文本失败: ${error.message}`, }, ], }; } case "create_formatted_docx": try { const { createFormattedDocument } = await import('./docxHandler.js'); const result = await createFormattedDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.content ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建格式化文档失败: ${error.message}`, }, ], }; } case "create_table_in_docx": try { const { createTableDocument } = await import('./docxHandler.js'); const result = await createTableDocument( request.params.arguments.fileName, request.params.arguments.title, request.params.arguments.tableData ); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建表格文档失败: ${error.message}`, }, ], }; } default: throw new Error(`未知工具: ${request.params.name}`); } } // 内部工具调用函数 async function callInternalTool(toolName, args) { const request = { params: { name: toolName, arguments: args } }; const result = await handleToolCall(request); return result.content[0].text; }

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/starzzzzzzzzzzzzzz/mcp-tools'

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