import express from 'express'; import cors from 'cors'; import path from 'path' import { fileURLToPath } from 'url' import { streamText } from '@xsai/stream-text'; import { generateText } from '@xsai/generate-text'; import { extractReasoningStream } from '@xsai/utils-reasoning'; import { callMcpServer, discoverMcpServerTools, executeMcpTool } from './mcp.js'; import { getAvailableMcpServers, startMcpServer, stopMcpServer, getMcpServerStatus, stopAllMcpServers } from './mcp-manager.js'; const app = express(); const port = process.env.PORT || 7860; const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) app.use(cors()); app.use(express.json()); app.post('/api/summarize', async (req, res) => { const { content, apiEndpoint, apiKey, model } = req.body; console.log('Received summarize request with:', { apiEndpoint, model, contentLength: content.length }); try { let baseURL; if (apiEndpoint.endsWith('#')) { baseURL = apiEndpoint.slice(0, -1); } else if (apiEndpoint.endsWith('/')) { baseURL = `${apiEndpoint}v1`; } else { baseURL = `${apiEndpoint}/v1`; } console.log('Calling API baseURL:', baseURL); const { text } = await generateText({ apiKey: apiKey, baseURL: baseURL, model: model, messages: [{ role: 'user', content: `Summarize this conversation in 3-5 words: ${content}` }], temperature: 0.2, max_tokens: 20 }); const summary = text.trim(); res.json({ summary }); } catch (error) { console.error('Error:', error); res.status(500).json({ error: error.message }); } }); app.post('/api/chat', async (req, res) => { const { messages, apiEndpoint, apiKey, model } = req.body; console.log('Received chat request with:', { apiEndpoint, model, messageCount: messages.length }); try { let baseURL; if (apiEndpoint.endsWith('#')) { baseURL = apiEndpoint.slice(0, -1); } else if (apiEndpoint.endsWith('/')) { baseURL = `${apiEndpoint}v1`; } else { baseURL = `${apiEndpoint}/v1`; } console.log('Calling API baseURL:', baseURL); // Use xsai to stream text from the AI model const { textStream } = await streamText({ apiKey: apiKey, baseURL: baseURL, model: model, messages: messages }); // Extract reasoning and content streams const { reasoningStream, textStream: contentStream } = extractReasoningStream(textStream); // Set headers for streaming res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // Set SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); let reasoningText = ''; let contentText = ''; let isReasoningComplete = false; // Handle client disconnect req.on('close', () => { console.log('Client disconnected'); }); try { // First, collect all reasoning console.log('Collecting reasoning...'); for await (const chunk of reasoningStream) { reasoningText += chunk; } isReasoningComplete = true; console.log('Reasoning collection complete'); // If we have reasoning, send it first wrapped in think tags if (reasoningText.trim()) { const thinkingChunk = { choices: [{ delta: { content: `${reasoningText}` } }] }; res.write(`data: ${JSON.stringify(thinkingChunk)}\n\n`); } // Then stream the content console.log('Starting content stream...'); for await (const chunk of contentStream) { if (res.destroyed) break; const responseChunk = { choices: [{ delta: { content: chunk } }] }; res.write(`data: ${JSON.stringify(responseChunk)}\n\n`); contentText += chunk; } // Send completion marker res.write('data: [DONE]\n\n'); res.end(); console.log('Stream completed successfully'); } catch (streamError) { console.error('Streaming error:', streamError); if (!res.destroyed) { res.write(`data: ${JSON.stringify({ error: streamError.message })}\n\n`); res.end(); } } } catch (error) { console.error('Error:', error); if (!res.destroyed) { res.status(500).json({ error: error.message }); } } }); if (process.env.NODE_ENV === 'production') { // Serve static files from the dist directory app.use(express.static(path.join(__dirname, '../dist'))) // Handle all other routes by serving the index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../dist/index.html')) }) } // MCP endpoints app.post('/api/mcp/discover', async (req, res) => { const { server } = req.body; console.log('Received MCP discovery request for server:', server); if (!server || !server.endpoint) { console.log('Invalid server configuration'); return res.status(400).json({ error: 'Invalid server configuration' }); } try { console.log(`Discovering tools from MCP server: ${server.name} at ${server.endpoint}`); const toolsInfo = await discoverMcpServerTools(server); console.log('Discovered tools:', toolsInfo); res.json(toolsInfo); } catch (error) { console.error('Error discovering MCP tools:', error); res.status(500).json({ error: error.message }); } }); app.post('/api/mcp/execute', async (req, res) => { const { server, tool, parameters } = req.body; console.log(`Received MCP tool execution request: ${tool}`); console.log('Server:', server); console.log('Parameters:', parameters); if (!server || !server.endpoint || !tool) { console.log('Invalid request parameters'); return res.status(400).json({ error: 'Invalid request parameters' }); } try { console.log(`Executing MCP tool ${tool} on server ${server.name}`); const result = await executeMcpTool(server, tool, parameters); console.log('Tool execution result:', result); res.json(result); } catch (error) { console.error('Error executing MCP tool:', error); res.status(500).json({ error: error.message }); } }); // MCP 服务器管理端点 app.get('/api/mcp/servers/available', (req, res) => { console.log('Received request for available MCP servers'); try { const servers = getAvailableMcpServers(); console.log('Available MCP servers:', servers); res.json({ servers }); } catch (error) { console.error('Error getting available MCP servers:', error); res.status(500).json({ error: error.message }); } }); app.post('/api/mcp/servers/start', async (req, res) => { const { serverId } = req.body; if (!serverId) { return res.status(400).json({ error: 'Server ID is required' }); } try { const result = await startMcpServer(serverId); if (result.success) { res.json({ success: true, serverId, port: result.port, endpoint: `http://localhost:${result.port}` }); } else { res.status(500).json({ success: false, serverId, error: result.error }); } } catch (error) { console.error(`Error starting MCP server ${serverId}:`, error); res.status(500).json({ success: false, serverId, error: error.message }); } }); app.post('/api/mcp/servers/stop', async (req, res) => { const { serverId } = req.body; if (!serverId) { return res.status(400).json({ error: 'Server ID is required' }); } try { const result = await stopMcpServer(serverId); if (result.success) { res.json({ success: true, serverId }); } else { res.status(500).json({ success: false, serverId, error: result.error }); } } catch (error) { console.error(`Error stopping MCP server ${serverId}:`, error); res.status(500).json({ success: false, serverId, error: error.message }); } }); app.get('/api/mcp/servers/status/:serverId', (req, res) => { const { serverId } = req.params; try { const status = getMcpServerStatus(serverId); res.json({ serverId, ...status, endpoint: status.isRunning ? `http://localhost:${status.port}` : null }); } catch (error) { console.error(`Error getting MCP server status for ${serverId}:`, error); res.status(500).json({ error: error.message }); } }); // 测试 MCP 服务器连接 app.post('/api/mcp/servers/test-connection', async (req, res) => { const { endpoint, authToken } = req.body; if (!endpoint) { return res.status(400).json({ error: 'Endpoint is required' }); } try { // 创建一个临时服务器对象 const server = { id: 'test-connection', name: 'Test Connection', endpoint, authToken }; // 尝试发现工具 const toolsInfo = await discoverMcpServerTools(server); res.json({ success: true, tools: toolsInfo.tools || [], message: `Successfully connected to MCP server at ${endpoint}` }); } catch (error) { console.error(`Error testing connection to MCP server at ${endpoint}:`, error); res.status(500).json({ success: false, error: error.message, message: `Failed to connect to MCP server at ${endpoint}: ${error.message}` }); } }); console.log('Starting server...'); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); }); console.log('Server started!'); // 确保在进程退出时停止所有 MCP 服务器 process.on('exit', () => { stopAllMcpServers(); }); // 处理未捕获的异常 process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); stopAllMcpServers(); process.exit(1); }); // 处理未处理的 Promise 拒绝 process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason); });