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
JavaScript
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);