Skip to main content
Glama

Mermaid Chart MCP

by echoVic
sse-transport.ts14.7 kB
/** * SSE传输模块 * 负责Web服务器、CORS、健康检查和SSE连接管理 */ import express from 'express'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import type { SimpleMermaidMCPServer } from './simple-mcp-server.js'; export class SSETransport { private mcpServer: SimpleMermaidMCPServer; private activeConnections = new Set<SSEServerTransport>(); constructor(mcpServer: SimpleMermaidMCPServer) { this.mcpServer = mcpServer; } /** * 启动SSE模式的Web服务器 */ async start(port: number = 3000): Promise<void> { const app = express(); // 配置CORS中间件 this.setupCORS(app); // 解析JSON请求体 app.use(express.json({ limit: '10mb' })); // 请求体大小限制和安全性 app.use(express.urlencoded({ extended: false, limit: '10mb' })); // SSE连接端点 app.get('/sse', async (_req, res) => { console.error('🔗 新的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 transport = new SSEServerTransport('/sse', res); this.activeConnections.add(transport); // 连接关闭时清理资源 res.on('close', () => { console.error('🔌 SSE连接关闭'); this.activeConnections.delete(transport); }); res.on('error', (error) => { console.error('❌ SSE连接错误:', error); this.activeConnections.delete(transport); }); try { await this.mcpServer.getServer().connect(transport); } catch (error) { console.error('❌ MCP服务器连接失败:', error); this.activeConnections.delete(transport); res.end(); } }); // 健康检查端点 app.get('/health', (_req, res) => { res.json({ status: 'healthy', mode: 'sse', port: port, activeConnections: this.activeConnections.size, timestamp: new Date().toISOString(), version: '1.0.0' }); }); // API信息端点 app.get('/api/info', (_req, res) => { res.json({ name: 'Mermaid Chart MCP Server', version: '1.0.0', description: '一个专注于Mermaid图表渲染的MCP服务器', endpoints: { sse: '/sse', health: '/health', info: '/api/info' }, capabilities: ['render_mermaid_to_svg', 'validate_mermaid_syntax'] }); }); // 静态测试页面 app.get('/', (_req, res) => { res.send(this.generateTestPage(port)); }); // 404处理 app.use((req, res) => { res.status(404).json({ error: 'Not Found', message: `端点 ${req.path} 不存在`, availableEndpoints: ['/', '/sse', '/health', '/api/info'] }); }); // 错误处理中间件 app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { console.error('❌ 服务器错误:', error); res.status(500).json({ error: 'Internal Server Error', message: '服务器内部错误' }); }); // 启动服务器 const server = app.listen(port, () => { console.error(`🌐 Mermaid Chart MCP 服务器已启动 (SSE 模式)`); console.error(`📡 SSE 端点: http://localhost:${port}/sse`); console.error(`🏠 测试页面: http://localhost:${port}/`); console.error(`💚 健康检查: http://localhost:${port}/health`); console.error(`📋 API信息: http://localhost:${port}/api/info`); }); // 优雅关闭处理 process.on('SIGTERM', () => { console.error('🛑 收到SIGTERM信号,正在关闭服务器...'); this.cleanup(); server.close(() => { console.error('✅ 服务器已关闭'); process.exit(0); }); }); process.on('SIGINT', () => { console.error('🛑 收到SIGINT信号,正在关闭服务器...'); this.cleanup(); server.close(() => { console.error('✅ 服务器已关闭'); process.exit(0); }); }); } /** * 配置CORS中间件 */ private setupCORS(app: express.Application): void { app.use((req, res, next) => { // 在生产环境中,应该配置具体的域名白名单 const allowedOrigins = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : ['*']; const origin = req.headers.origin; if (allowedOrigins.includes('*') || (origin && allowedOrigins.includes(origin))) { res.header('Access-Control-Allow-Origin', origin || '*'); } res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cache-Control'); res.header('Access-Control-Allow-Credentials', 'true'); res.header('Access-Control-Max-Age', '86400'); // 24小时 if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); } /** * 清理活动连接 */ private cleanup(): void { console.error(`🧹 清理 ${this.activeConnections.size} 个活动连接...`); for (const connection of this.activeConnections) { try { // 这里可以添加连接清理逻辑 this.activeConnections.delete(connection); } catch (error) { console.error('❌ 清理连接时出错:', error); } } } /** * 生成测试页面HTML */ private generateTestPage(port: number): string { return `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mermaid Chart MCP Server</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; background: white; min-height: 100vh; box-shadow: 0 0 20px rgba(0,0,0,0.1); } .header { text-align: center; margin-bottom: 40px; padding: 30px 0; background: linear-gradient(45deg, #667eea, #764ba2); color: white; border-radius: 10px; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; } .status-card { background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; } .status-card h3 { color: #667eea; margin-bottom: 10px; } .test-section { background: #f8f9fa; padding: 30px; border-radius: 10px; margin-bottom: 30px; } textarea { width: 100%; height: 250px; margin: 15px 0; padding: 15px; border: 2px solid #e9ecef; border-radius: 8px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 14px; resize: vertical; transition: border-color 0.3s; } textarea:focus { outline: none; border-color: #667eea; } .button-group { display: flex; gap: 10px; flex-wrap: wrap; } button { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; transition: all 0.3s; flex: 1; min-width: 120px; } .btn-primary { background: #667eea; color: white; } .btn-primary:hover { background: #5a6fd8; transform: translateY(-1px); } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover { background: #5a6268; transform: translateY(-1px); } .output { border: 2px solid #e9ecef; padding: 20px; margin: 20px 0; border-radius: 8px; background: white; min-height: 100px; } .output h3 { color: #495057; margin-bottom: 15px; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; } .result { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 14px; color: #495057; white-space: pre-wrap; } .footer { text-align: center; padding: 30px; color: #6c757d; border-top: 1px solid #e9ecef; } @media (max-width: 768px) { .container { padding: 10px; } .button-group { flex-direction: column; } button { flex: none; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>🧜‍♀️ Mermaid Chart MCP Server</h1> <p>Mermaid图表渲染服务器 - SSE模式运行在端口 ${port}</p> </div> <div class="status-grid"> <div class="status-card"> <h3>📡 服务状态</h3> <p>服务器运行正常,支持实时图表渲染</p> </div> <div class="status-card"> <h3>🔗 连接方式</h3> <p>Server-Sent Events (SSE)</p> </div> <div class="status-card"> <h3>🛠️ 可用工具</h3> <p>图表渲染 & 语法验证</p> </div> <div class="status-card"> <h3>📊 支持图表</h3> <p>流程图、序列图、类图、甘特图等</p> </div> </div> <div class="test-section"> <h2>🧪 测试工具调用</h2> <p>在下方输入Mermaid代码,测试图表渲染和语法验证功能:</p> <textarea id="mermaidCode" placeholder="在此输入Mermaid代码...">graph TD A[开始] --> B{是否理解?} B -->|是| C[继续学习] B -->|否| D[复习基础] D --> B C --> E[实践应用] E --> F[掌握技能] F --> G[分享知识] G --> H[持续改进]</textarea> <div class="button-group"> <button class="btn-primary" onclick="renderMermaid()">🎨 渲染图表</button> <button class="btn-secondary" onclick="validateSyntax()">✅ 验证语法</button> <button class="btn-secondary" onclick="clearResult()">🗑️ 清空结果</button> </div> </div> <div class="output"> <h3>📋 输出结果</h3> <div id="result" class="result">等待操作... 👆 点击上方按钮开始测试</div> </div> <div class="footer"> <p>💡 这是一个测试页面,用于验证MCP服务器功能</p> <p>🔗 API端点: <a href="/api/info" target="_blank">/api/info</a> | 💚 健康检查: <a href="/health" target="_blank">/health</a></p> </div> </div> <script> function renderMermaid() { const code = document.getElementById('mermaidCode').value; if (!code.trim()) { document.getElementById('result').textContent = '❌ 请输入Mermaid代码'; return; } document.getElementById('result').innerHTML = '🔄 正在渲染图表...<br>代码长度: ' + code.length + ' 字符'; console.log('渲染Mermaid代码:', code); // 这里可以添加实际的MCP调用逻辑 setTimeout(() => { document.getElementById('result').innerHTML = '✅ 图表渲染完成!<br>' + '📊 检测到的图表类型: 流程图<br>' + '🎨 使用主题: default<br>' + '📁 临时文件已创建<br>' + '💡 在实际应用中,这里会显示SVG内容或文件路径'; }, 1500); } function validateSyntax() { const code = document.getElementById('mermaidCode').value; if (!code.trim()) { document.getElementById('result').textContent = '❌ 请输入Mermaid代码'; return; } document.getElementById('result').innerHTML = '🔍 正在验证语法...<br>代码长度: ' + code.length + ' 字符'; console.log('验证Mermaid语法:', code); setTimeout(() => { document.getElementById('result').innerHTML = '✅ Mermaid语法验证通过!<br>' + '📊 图表类型: 流程图<br>' + '🔧 语法结构正确<br>' + '💡 代码可以正常渲染'; }, 1000); } function clearResult() { document.getElementById('result').textContent = '🗑️ 结果已清空 - 等待新的操作...'; } // 自动调整文本域高度 document.getElementById('mermaidCode').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; }); </script> </body> </html>`; } }

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/echoVic/mermaid-chart-mcp'

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