UNPKG

webrtc-mcp-chat

Version:

A remote WebRTC chat server with secure temporary rooms and MCP support for background agents

837 lines (744 loc) • 23.1 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import fetch from 'node-fetch'; // Remote chat server configuration const CHAT_SERVER_URL = process.env.CHAT_SERVER_URL || process.env.REMOTE_CHAT_URL || 'http://localhost:3000'; // Helper function to create fetch headers with ngrok support function createHeaders(additionalHeaders = {}) { const headers = { 'Content-Type': 'application/json', ...additionalHeaders }; // Add ngrok header if URL contains ngrok if (CHAT_SERVER_URL.includes('ngrok') || CHAT_SERVER_URL.includes('ngrok-free.app')) { headers['ngrok-skip-browser-warning'] = 'true'; } return headers; } class RemoteChatMCPServer { constructor() { this.server = new Server( { name: "webrtc-chat-mcp", version: "2.0.2", }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } ); this.setupToolHandlers(); this.setupResourceHandlers(); this.setupPromptHandlers(); } setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_temp_chat", description: "Create a secure temporary chat room that expires after a specified time", inputSchema: { type: "object", properties: { expiresInMinutes: { type: "number", description: "Minutes until the room expires (default: 60, max: 1440)", default: 60 }, createdBy: { type: "string", description: "Identifier for who created the room" } }, required: [] } }, { name: "join_temp_chat", description: "Join a temporary secure chat room using room ID and token", inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The temporary room ID (starts with 'tmp_')" }, roomToken: { type: "string", description: "The secure room token" }, username: { type: "string", description: "Your username for the chat" }, message: { type: "string", description: "Optional message to send when joining" } }, required: ["roomId", "roomToken", "username"] } }, { name: "send_temp_message", description: "Send a message to a temporary secure chat room", inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The temporary room ID" }, roomToken: { type: "string", description: "The secure room token" }, username: { type: "string", description: "Your username" }, message: { type: "string", description: "The message to send" } }, required: ["roomId", "roomToken", "username", "message"] } }, { name: "get_temp_room_info", description: "Get information about a temporary chat room", inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The temporary room ID" }, roomToken: { type: "string", description: "The secure room token" } }, required: ["roomId", "roomToken"] } }, { name: "join_public_chat", description: "Join a public chat room (no token required)", inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The public room ID" }, username: { type: "string", description: "Your username for the chat" }, message: { type: "string", description: "Optional message to send when joining" } }, required: ["roomId", "username"] } }, { name: "send_public_message", description: "Send a message to a public chat room", inputSchema: { type: "object", properties: { roomId: { type: "string", description: "The public room ID" }, username: { type: "string", description: "Your username" }, message: { type: "string", description: "The message to send" } }, required: ["roomId", "username", "message"] } }, { name: "check_server_status", description: "Check the status of the remote chat server", inputSchema: { type: "object", properties: {}, required: [] } } ] }; }); // Handle tool execution this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "create_temp_chat": return await this.createTempChat(args); case "join_temp_chat": return await this.joinTempChat(args); case "send_temp_message": return await this.sendTempMessage(args); case "get_temp_room_info": return await this.getTempRoomInfo(args); case "join_public_chat": return await this.joinPublicChat(args); case "send_public_message": return await this.sendPublicMessage(args); case "check_server_status": return await this.checkServerStatus(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error: ${error.message}` } ], isError: true }; } }); } setupResourceHandlers() { // List available resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "chat://server-info", name: "Chat Server Information", description: "Information about the remote chat server", mimeType: "application/json" }, { uri: "chat://temp-rooms", name: "Temporary Rooms Guide", description: "Guide for using secure temporary chat rooms", mimeType: "text/plain" }, { uri: "chat://deployment", name: "Deployment Guide", description: "Guide for deploying to remote servers", mimeType: "text/markdown" } ] }; }); // Handle resource reading this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case "chat://server-info": try { const serverInfo = await this.getServerInfo(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(serverInfo, null, 2) } ] }; } catch (error) { return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify({ error: "Unable to connect to chat server", serverUrl: CHAT_SERVER_URL, message: error.message }, null, 2) } ] }; } case "chat://temp-rooms": return { contents: [ { uri, mimeType: "text/plain", text: `Secure Temporary Chat Rooms Guide OVERVIEW: Temporary chat rooms provide secure, time-limited communication channels that automatically expire. Perfect for background agents and remote operations. FEATURES: - Cryptographically secure room IDs and tokens - Automatic expiration (1 hour to 24 hours) - No configuration required - Remote server compatible - Token-based access control WORKFLOW: 1. create_temp_chat - Creates a new secure room 2. Share room ID and token with participants 3. join_temp_chat - Join using credentials 4. send_temp_message - Send secure messages 5. Room automatically expires and cleans up SECURITY: - 256-bit room tokens (cryptographically secure) - 128-bit room IDs - Server-side validation - Automatic cleanup of expired rooms - No persistent storage required EXAMPLE USAGE: 1. Agent A: create_temp_chat(expiresInMinutes=120, createdBy="agent-1") 2. Agent A: Share roomId and roomToken with Agent B 3. Agent B: join_temp_chat(roomId="tmp_...", roomToken="...", username="agent-2") 4. Both agents: send_temp_message() to communicate 5. Room expires after 2 hours automatically REMOTE DEPLOYMENT: - Works with any remote server - No local configuration needed - Environment variable: CHAT_SERVER_URL or REMOTE_CHAT_URL - Health checking included ` } ] }; case "chat://deployment": return { contents: [ { uri, mimeType: "text/markdown", text: `# Remote Deployment Guide ## Quick Deploy Options ### Railway \`\`\`bash # Install Railway CLI npm install -g @railway/cli # Login and deploy railway login railway init railway up \`\`\` ### Vercel \`\`\`bash # Install Vercel CLI npm install -g vercel # Deploy vercel --prod \`\`\` ### Render 1. Connect GitHub repository 2. Set build command: \`npm install\` 3. Set start command: \`npm start\` 4. Deploy ### Heroku \`\`\`bash # Install Heroku CLI heroku create your-chat-app git push heroku main \`\`\` ## Environment Variables Set these on your hosting platform: - \`NODE_ENV=production\` - \`SERVER_URL=https://your-domain.com\` - \`PORT=3000\` (or platform default) ## MCP Configuration No local server needed! Just set: \`\`\`bash export CHAT_SERVER_URL=https://your-deployed-app.com # or export REMOTE_CHAT_URL=https://your-deployed-app.com \`\`\` ## Health Checking Your deployed app includes: - \`GET /health\` - Server status - \`GET /\` - Web interface - \`POST /api/create-temp-room\` - Create secure rooms ## Background Agents Perfect for: - Remote CI/CD notifications - Inter-service communication - Temporary coordination channels - Secure agent-to-agent messaging ` } ] }; default: throw new Error(`Unknown resource: ${uri}`); } }); } setupPromptHandlers() { // List available prompts this.server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "temp_room_invite", description: "Generate an invitation message for a temporary chat room", arguments: [ { name: "roomId", description: "The temporary room ID", required: true }, { name: "roomToken", description: "The secure room token", required: true }, { name: "expiresInMinutes", description: "Minutes until expiration", required: true }, { name: "purpose", description: "Purpose of the chat room", required: false } ] }, { name: "agent_coordination", description: "Generate a coordination message for background agents", arguments: [ { name: "agentId", description: "The agent identifier", required: true }, { name: "task", description: "The task being coordinated", required: true }, { name: "roomInfo", description: "Room connection information", required: false } ] } ] }; }); // Handle prompt execution this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "temp_room_invite": const purpose = args?.purpose ? ` for ${args.purpose}` : ''; return { description: `Invitation for temporary room ${args.roomId}`, messages: [ { role: "user", content: { type: "text", text: `šŸ” Secure Temporary Chat Room Invitation Room ID: ${args.roomId} Token: ${args.roomToken} Expires: ${args.expiresInMinutes} minutes${purpose} To join: 1. Use: join_temp_chat(roomId="${args.roomId}", roomToken="${args.roomToken}", username="your-name") 2. Or visit: ${CHAT_SERVER_URL}?room=${args.roomId}&token=${args.roomToken} This room will automatically expire and be cleaned up after ${args.expiresInMinutes} minutes.` } } ] }; case "agent_coordination": const roomInfo = args?.roomInfo ? `\n\nRoom Info:\n${args.roomInfo}` : ''; return { description: `Coordination message for agent ${args.agentId}`, messages: [ { role: "user", content: { type: "text", text: `šŸ¤– Agent Coordination - ${args.task} Agent: ${args.agentId} Task: ${args.task} Status: Ready for coordination Use create_temp_chat() to establish a secure communication channel for this task.${roomInfo}` } } ] }; default: throw new Error(`Unknown prompt: ${name}`); } }); } async createTempChat(args) { const { expiresInMinutes = 60, createdBy = 'mcp-agent' } = args; const maxMinutes = 1440; // 24 hours max const actualMinutes = Math.min(expiresInMinutes, maxMinutes); try { const response = await fetch(`${CHAT_SERVER_URL}/api/create-temp-room`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ expiresInMinutes: actualMinutes, createdBy }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to create temporary room'); } return { content: [ { type: "text", text: `āœ… Created secure temporary chat room! šŸ” Room Details: • Room ID: ${result.roomId} • Token: ${result.roomToken} • Expires: ${actualMinutes} minutes (${new Date(result.expiresAt).toLocaleString()}) • Join URL: ${result.joinUrl} šŸš€ To join this room: join_temp_chat(roomId="${result.roomId}", roomToken="${result.roomToken}", username="your-name") āš ļø Keep the token secure! Anyone with the room ID and token can join.` } ] }; } catch (error) { throw new Error(`Failed to create temporary room: ${error.message}`); } } async joinTempChat(args) { const { roomId, roomToken, username, message } = args; try { const response = await fetch(`${CHAT_SERVER_URL}/mcp/join`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, roomToken, username, message: message || `${username} joined the secure chat` }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to join room'); } const expiryInfo = result.expiresAt ? `\nā° Room expires: ${new Date(result.expiresAt).toLocaleString()}` : ''; return { content: [ { type: "text", text: `āœ… Successfully joined secure room "${roomId}" as "${username}" ${result.message} šŸ‘„ Active users: ${result.activeUsers}${expiryInfo} Use send_temp_message() to send messages to this room.` } ] }; } catch (error) { throw new Error(`Failed to join temporary room: ${error.message}`); } } async sendTempMessage(args) { const { roomId, roomToken, username, message } = args; try { const response = await fetch(`${CHAT_SERVER_URL}/mcp/message`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, roomToken, username, message }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to send message'); } return { content: [ { type: "text", text: `šŸ’¬ Message sent to secure room "${roomId}" šŸ‘„ Active users: ${result.activeUsers} šŸ“ Message: "${message}"` } ] }; } catch (error) { throw new Error(`Failed to send message: ${error.message}`); } } async getTempRoomInfo(args) { const { roomId, roomToken } = args; try { const response = await fetch(`${CHAT_SERVER_URL}/api/temp-room/${roomId}?token=${roomToken}`, { headers: createHeaders() }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to get room info'); } const userList = result.users.map(user => `• ${user.username} (${user.isMCP ? 'MCP' : 'Web'})` ).join('\n'); const expiryInfo = result.expiresAt ? `ā° Expires: ${new Date(result.expiresAt).toLocaleString()}` : 'ā° Permanent room'; return { content: [ { type: "text", text: `šŸ” Secure Room "${roomId}" Info: šŸ‘„ Active users (${result.userCount}): ${userList || '• No users currently active'} ${expiryInfo} šŸ—ļø Created by: ${result.createdBy || 'Unknown'} Note: Web users have video/audio capabilities, MCP users participate via text chat.` } ] }; } catch (error) { throw new Error(`Failed to get room info: ${error.message}`); } } async joinPublicChat(args) { const { roomId, username, message } = args; try { const response = await fetch(`${CHAT_SERVER_URL}/mcp/join`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, username, message: message || `${username} joined via MCP` }) }); const result = await response.json(); return { content: [ { type: "text", text: `āœ… Joined public room "${roomId}" as "${username}" ${result.message} šŸ‘„ Active users: ${result.activeUsers}` } ] }; } catch (error) { throw new Error(`Failed to join public room: ${error.message}`); } } async sendPublicMessage(args) { const { roomId, username, message } = args; try { const response = await fetch(`${CHAT_SERVER_URL}/mcp/message`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, username, message }) }); const result = await response.json(); return { content: [ { type: "text", text: `šŸ’¬ Message sent to public room "${roomId}" šŸ‘„ Active users: ${result.activeUsers}` } ] }; } catch (error) { throw new Error(`Failed to send message: ${error.message}`); } } async checkServerStatus(args) { try { const response = await fetch(`${CHAT_SERVER_URL}/health`, { headers: createHeaders() }); const result = await response.json(); return { content: [ { type: "text", text: `🟢 Chat server is ${result.status} 🌐 Server URL: ${result.serverUrl} šŸ”§ Remote mode: ${result.remoteMode} šŸ  Active rooms: ${result.activeRooms} ā° Temporary rooms: ${result.temporaryRooms} šŸ‘„ Connected users: ${result.connectedUsers} āœ… Server is ready for chat operations!` } ] }; } catch (error) { return { content: [ { type: "text", text: `šŸ”“ Unable to connect to chat server āŒ Server URL: ${CHAT_SERVER_URL} šŸ’„ Error: ${error.message} Please check: 1. Server is running and accessible 2. CHAT_SERVER_URL environment variable is correct 3. Network connectivity to the server` } ] }; } } async getServerInfo() { const response = await fetch(`${CHAT_SERVER_URL}/health`, { headers: createHeaders() }); if (!response.ok) { throw new Error(`Server responded with status ${response.status}`); } return await response.json(); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error(`WebRTC Chat MCP Server v2.0.2 running on stdio`); console.error(`Connected to: ${CHAT_SERVER_URL}`); } } // Start the server const server = new RemoteChatMCPServer(); server.run().catch(console.error);