/** * cesium-mcp-runtime demo server * * 仅启动 HTTP + WebSocket 服务器(不启动 MCP stdio transport), * 用于配合 demo/index.html 进行本地测试。 * * 用法:npx tsx src/demo-server.ts */ import { WebSocketServer, WebSocket, type RawData } from 'ws' import { createServer, type IncomingMessage, type ServerResponse } from 'node:http' const WS_PORT = parseInt(process.env.CESIUM_WS_PORT ?? '9100') const browserClients = new Map() let requestIdCounter = 0 function getDefaultBrowser(): WebSocket | null { if (browserClients.size === 0) return null return browserClients.values().next().value ?? null } function pushToBrowser(sessionId: string, command: { action: string; params: Record }): boolean { const ws = browserClients.get(sessionId) ?? getDefaultBrowser() if (!ws || ws.readyState !== WebSocket.OPEN) return false ws.send(JSON.stringify({ jsonrpc: '2.0', id: `push_${++requestIdCounter}`, method: command.action, params: command.params, })) return true } function handleHttpRequest(req: IncomingMessage, res: ServerResponse) { res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type') if (req.method === 'OPTIONS') { res.writeHead(204) res.end() return } if (req.method === 'POST' && req.url?.startsWith('/api/command')) { let body = '' req.on('data', (chunk: Buffer) => { body += chunk.toString() }) req.on('end', () => { try { const payload = JSON.parse(body) const sessionId: string = payload.sessionId ?? 'default' const commands: Array<{ action: string; params: Record }> = Array.isArray(payload.commands) ? payload.commands : [payload.command] let sent = 0 for (const cmd of commands) { if (cmd && pushToBrowser(sessionId, cmd)) sent++ } console.log(`[http] POST /api/command → sent ${sent}/${commands.length} to session=${sessionId}`) res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ ok: true, sent, total: commands.length })) } catch { res.writeHead(400, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ ok: false, error: 'Invalid JSON' })) } }) return } if (req.method === 'GET' && req.url?.startsWith('/api/status')) { const sessions = Array.from(browserClients.keys()) res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ ok: true, sessions, connections: sessions.length })) return } res.writeHead(404) res.end('Not Found') } const httpServer = createServer(handleHttpRequest) const wss = new WebSocketServer({ server: httpServer }) wss.on('connection', (ws: WebSocket, req: IncomingMessage) => { const sessionId = new URL(req.url ?? '/', 'http://localhost').searchParams.get('session') ?? 'default' console.log(`[ws] 浏览器连接: session=${sessionId}`) browserClients.set(sessionId, ws) ws.on('message', (raw: RawData) => { try { const msg = JSON.parse(raw.toString()) console.log('[ws] ← 浏览器响应:', JSON.stringify(msg).slice(0, 120)) } catch { /* ignore */ } }) ws.on('close', () => { console.log(`[ws] 浏览器断开: session=${sessionId}`) browserClients.delete(sessionId) }) }) httpServer.listen(WS_PORT, () => { console.log('\n ╔══════════════════════════════════════════════╗') console.log(' ║ cesium-mcp-runtime Demo Server ║') console.log(' ╠══════════════════════════════════════════════╣') console.log(` ║ HTTP + WS : http://localhost:${WS_PORT} ║`) console.log(' ║ POST : /api/command ║') console.log(' ║ GET : /api/status ║') console.log(` ║ WS : ws://localhost:${WS_PORT}?session=xxx ║`) console.log(' ╚══════════════════════════════════════════════╝') console.log('\n 打开 demo/index.html 或 npm run demo 启动测试页面\n') })