/**
* Mock HTTP Server for Debug Tools
*
* Provides browser bridge endpoints that simulate the real askme-server behavior
* for UI development without requiring an MCP client.
*/
import { createServer, IncomingMessage, ServerResponse } from 'http';
import { EventEmitter } from 'events';
export interface MockRequest {
id: string;
type: string;
data: any;
timestamp: string;
}
export interface MockResponse {
requestId: string;
sessionId: string;
response: any;
}
export class MockServer extends EventEmitter {
private server: any;
private port: number;
private requests: Map<string, MockRequest> = new Map();
private sseConnections: Set<ServerResponse> = new Set();
constructor(port: number = 3000) {
super();
this.port = port;
}
/**
* Start the mock server with browser bridge endpoints
*/
async start(): Promise<void> {
return new Promise((resolve, reject) => {
this.server = createServer((req, res) => {
this.handleRequest(req, res);
});
this.server.listen(this.port, () => {
console.error(`[Debug Server] Mock server started on port ${this.port}`);
resolve();
});
this.server.on('error', (error: any) => {
if (error.code === 'EADDRINUSE') {
reject(new Error(`Port ${this.port} is already in use`));
} else {
reject(error);
}
});
});
}
/**
* Stop the mock server
*/
async stop(): Promise<void> {
return new Promise((resolve) => {
if (this.server) {
// Close all SSE connections
this.sseConnections.forEach(res => {
res.end();
});
this.sseConnections.clear();
this.server.close(() => {
console.error('[Debug Server] Mock server stopped');
resolve();
});
} else {
resolve();
}
});
}
/**
* Add a new request that will be sent via SSE
*/
addRequest(request: MockRequest): void {
this.requests.set(request.id, request);
// Send to all connected SSE clients
const sseData = JSON.stringify({
type: 'new_request',
data: request
});
this.sseConnections.forEach(res => {
res.write(`data: ${sseData}\n\n`);
});
console.error(`[Debug Server] Added request: ${request.type} (${request.id})`);
}
/**
* Handle incoming HTTP requests
*/
private handleRequest(req: IncomingMessage, res: ServerResponse): void {
const url = req.url || '';
const method = req.method || 'GET';
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
if (url === '/mcp/browser-events' && method === 'GET') {
this.handleSSE(req, res);
} else if (url === '/mcp/response' && method === 'POST') {
this.handleResponse(req, res);
} else if (url === '/mcp/health' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', connections: this.sseConnections.size }));
} else {
res.writeHead(404);
res.end('Not Found');
}
}
/**
* Handle Server-Sent Events connection
*/
private handleSSE(req: IncomingMessage, res: ServerResponse): void {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
// Send keepalive immediately
res.write('data: {"type":"keepalive"}\n\n');
// Add to connections
this.sseConnections.add(res);
console.error(`[Debug Server] SSE client connected (${this.sseConnections.size} total)`);
// Send existing requests
this.requests.forEach(request => {
const sseData = JSON.stringify({
type: 'new_request',
data: request
});
res.write(`data: ${sseData}\n\n`);
});
// Handle client disconnect
req.on('close', () => {
this.sseConnections.delete(res);
console.error(`[Debug Server] SSE client disconnected (${this.sseConnections.size} remaining)`);
});
req.on('error', () => {
this.sseConnections.delete(res);
});
}
/**
* Handle response submission from UI
*/
private handleResponse(req: IncomingMessage, res: ServerResponse): void {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const responseData: MockResponse = JSON.parse(body);
console.error(`[Debug Server] Received response for request: ${responseData.requestId}`);
// Find the request
const request = this.requests.get(responseData.requestId);
if (request) {
// Emit response event
this.emit('response', {
request,
response: responseData.response
});
// Remove the request
this.requests.delete(responseData.requestId);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Request not found' }));
}
} catch (error) {
console.error('[Debug Server] Error parsing response:', error);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
}
/**
* Get the number of connected SSE clients
*/
getConnectionCount(): number {
return this.sseConnections.size;
}
/**
* Check if there are any pending requests
*/
hasPendingRequests(): boolean {
return this.requests.size > 0;
}
}