UNPKG

@cgaspard/webappmcp

Version:

WebApp MCP - Model Context Protocol integration for web applications with server-side debugging tools

315 lines 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MCPSocketServer = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const ws_1 = require("ws"); const index_js_2 = require("./tools/index.js"); const net = __importStar(require("net")); const fs = __importStar(require("fs")); const readline = __importStar(require("readline")); class MCPSocketServer { constructor(config) { this.socketServer = null; this.ws = null; this.isConnected = false; this.socketPath = config.socketPath; this.wsUrl = config.wsUrl; this.authToken = config.authToken; this.getClients = config.getClients; this.server = new index_js_1.Server({ name: 'webapp-mcp-socket', version: '0.1.0', }, { capabilities: { tools: {}, }, }); this.setupHandlers(); } setupHandlers() { this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { console.log(`[MCP Socket] ListTools request received`); const tools = (0, index_js_2.registerTools)(); console.log(`[MCP Socket] Returning ${tools.length} tools`); return { tools: tools, }; }); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { console.log(`[MCP Socket] Tool call: ${request.params.name}`); const { name, arguments: args } = request.params; // Special handling for webapp_list_clients if (name === 'webapp_list_clients') { const clients = this.getClients ? this.getClients() : []; return { content: [ { type: 'text', text: JSON.stringify(clients, null, 2), }, ], }; } if (!this.isConnected) { throw new Error('Not connected to web application'); } try { const result = await this.executeToolOnClient(name, args); return result; } catch (error) { return { content: [ { type: 'text', text: `Error executing tool: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } }); } async connectWebSocket() { return new Promise((resolve, reject) => { const wsUrl = this.authToken ? `${this.wsUrl}?token=${this.authToken}` : this.wsUrl; this.ws = new ws_1.WebSocket(wsUrl); this.ws.on('open', () => { console.log('[MCP Socket] Connected to WebSocket'); this.isConnected = true; this.ws.send(JSON.stringify({ type: 'init', url: 'mcp-socket-server', })); resolve(); }); this.ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'connected') { console.log(`[MCP Socket] Registered with clientId: ${message.clientId}`); } }); this.ws.on('close', () => { console.log('[MCP Socket] Disconnected from WebSocket'); this.isConnected = false; }); this.ws.on('error', (error) => { console.error('[MCP Socket] WebSocket error:', error); reject(error); }); }); } async handleToolCall(params) { const { name, arguments: args } = params; // Special handling for webapp_list_clients if (name === 'webapp_list_clients') { const clients = this.getClients ? this.getClients() : []; return { content: [ { type: 'text', text: JSON.stringify(clients, null, 2), }, ], }; } if (!this.isConnected) { throw new Error('Not connected to web application'); } return await this.executeToolOnClient(name, args); } async executeToolOnClient(toolName, args) { return new Promise((resolve, reject) => { const requestId = Math.random().toString(36).substring(7); const timeout = setTimeout(() => { reject(new Error('Tool execution timeout')); }, 30000); const messageHandler = (data) => { const message = JSON.parse(data.toString()); if (message.requestId === requestId) { clearTimeout(timeout); this.ws.off('message', messageHandler); if (message.error) { reject(new Error(message.error)); } else { resolve({ content: [ { type: 'text', text: JSON.stringify(message.result, null, 2), }, ], }); } } }; this.ws.on('message', messageHandler); this.ws.send(JSON.stringify({ type: 'execute_tool', requestId, tool: toolName, args, })); }); } async start() { try { // Connect to WebSocket first await this.connectWebSocket(); // Remove existing socket if it exists if (fs.existsSync(this.socketPath)) { fs.unlinkSync(this.socketPath); } // Create Unix socket server this.socketServer = net.createServer((socket) => { console.log(`[MCP Socket] Client connected to ${this.socketPath}`); let buffer = ''; const rl = readline.createInterface({ input: socket, output: socket, terminal: false }); rl.on('line', async (line) => { try { const message = JSON.parse(line); console.log(`[MCP Socket] Received: ${message.method}`); // Handle different message types let response; if (message.method === 'initialize') { response = { jsonrpc: '2.0', id: message.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'webapp-mcp-socket', version: '0.1.0' } } }; } else if (message.method === 'tools/list') { const tools = (0, index_js_2.registerTools)(); response = { jsonrpc: '2.0', id: message.id, result: { tools } }; } else if (message.method === 'tools/call') { try { const result = await this.handleToolCall(message.params); response = { jsonrpc: '2.0', id: message.id, result }; } catch (error) { response = { jsonrpc: '2.0', id: message.id, error: { code: -32603, message: error instanceof Error ? error.message : 'Internal error' } }; } } else { response = { jsonrpc: '2.0', id: message.id, error: { code: -32601, message: 'Method not found' } }; } socket.write(JSON.stringify(response) + '\n'); } catch (error) { console.error('[MCP Socket] Error processing request:', error); const errorResponse = { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }; socket.write(JSON.stringify(errorResponse) + '\n'); } }); socket.on('end', () => { console.log('[MCP Socket] Client disconnected'); rl.close(); }); socket.on('error', (err) => { console.error('[MCP Socket] Socket error:', err); rl.close(); }); }); this.socketServer.listen(this.socketPath, () => { console.log(`[MCP Socket] Unix socket server listening on ${this.socketPath}`); console.log(`[MCP Socket] Configure Claude CLI with:`); console.log(`[MCP Socket] claude mcp add webapp-socket "socat - UNIX-CONNECT:${this.socketPath}"`); }); // Cleanup on exit process.on('SIGINT', this.cleanup.bind(this)); process.on('SIGTERM', this.cleanup.bind(this)); } catch (error) { console.error('[MCP Socket] Failed to start:', error); throw error; } } cleanup() { console.log('[MCP Socket] Shutting down...'); if (this.socketServer) { this.socketServer.close(); } if (fs.existsSync(this.socketPath)) { fs.unlinkSync(this.socketPath); } if (this.ws) { this.ws.close(); } } } exports.MCPSocketServer = MCPSocketServer; //# sourceMappingURL=mcp-socket-server.js.map