88 lines
3.5 KiB
JavaScript
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
|