@cgaspard/webappmcp
Version:
WebApp MCP - Model Context Protocol integration for web applications with server-side debugging tools
315 lines • 12.7 kB
JavaScript
;
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