Files
2026-07-31 13:12:54 -04:00

88 lines
3.5 KiB
JavaScript

import { randomUUID } from 'node:crypto';
import express from 'express';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createServer } from '../server.js';
import { processManager } from '../lib/processManager.js';
import { browserManager } from '../tools/browser/index.js';
import { dockerManager } from '../tools/docker/index.js';
import { logger } from '../lib/logger.js';
import { config } from '../config.js';
const app = express();
app.use(express.json());
if (config.httpToken) {
app.use((req, res, next) => {
const auth = req.header('authorization');
if (auth === `Bearer ${config.httpToken}`) {
next();
return;
}
res.status(401).json({ error: 'Unauthorized' });
});
}
else if (config.httpHost !== '127.0.0.1' && config.httpHost !== 'localhost') {
logger.error(`Refusing to bind to non-localhost host "${config.httpHost}" without MCP_HTTP_TOKEN set. ` +
'Set MCP_HTTP_TOKEN, or bind to 127.0.0.1/localhost for unauthenticated local use.');
process.exit(1);
}
const transports = new Map();
app.post('/mcp', async (req, res) => {
try {
const sessionId = req.header('mcp-session-id');
let transport = sessionId ? transports.get(sessionId) : undefined;
if (!transport) {
const server = createServer();
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => {
transports.set(id, transport);
logger.info(`http session initialized: ${id}`);
},
onsessionclosed: (id) => {
transports.delete(id);
logger.info(`http session closed: ${id}`);
},
});
await server.connect(transport);
}
await transport.handleRequest(req, res, req.body);
}
catch (error) {
logger.error('error handling POST /mcp', { error: error instanceof Error ? error.message : error });
if (!res.headersSent)
res.status(500).json({ error: 'Internal server error' });
}
});
app.get('/mcp', async (req, res) => {
const sessionId = req.header('mcp-session-id');
const transport = sessionId ? transports.get(sessionId) : undefined;
if (!transport) {
res.status(400).send('Invalid or missing mcp-session-id');
return;
}
await transport.handleRequest(req, res);
});
app.delete('/mcp', async (req, res) => {
const sessionId = req.header('mcp-session-id');
const transport = sessionId ? transports.get(sessionId) : undefined;
if (!transport) {
res.status(400).send('Invalid or missing mcp-session-id');
return;
}
await transport.handleRequest(req, res);
});
app.get('/healthz', (_req, res) => {
res.json({ status: 'ok', workspaceRoot: config.workspaceRoot });
});
const httpServer = app.listen(config.httpPort, config.httpHost, () => {
logger.info(`web-dev-mcp HTTP server listening on http://${config.httpHost}:${config.httpPort}/mcp`);
});
async function shutdown(signal) {
logger.info(`received ${signal}, shutting down`);
processManager.stopAll();
await browserManager.close();
await dockerManager.cleanupAll();
httpServer.close(() => process.exit(0));
}
process.on('SIGINT', () => void shutdown('SIGINT'));
process.on('SIGTERM', () => void shutdown('SIGTERM'));
//# sourceMappingURL=http.js.map