import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import cors from 'cors';
import express from 'express';
import { parseToolConfigFromArgs, filterTools } from './config/toolConfig.js';
import { getAllTools } from './tools/toolRegistry.js';
import { patchGlobalFetch } from './utils/requestUtils.js';
import { getVersionInfo } from './utils/versionUtils.js';
let serverVersionInfo = getVersionInfo();
patchGlobalFetch(serverVersionInfo);
// Parse configuration from command-line arguments
const config = parseToolConfigFromArgs();
// Get and filter tools based on configuration
const allTools = getAllTools();
const enabledTools = filterTools(allTools, config);
// Create an MCP server
const server = new McpServer(
{
name: serverVersionInfo.name,
version: serverVersionInfo.version
},
{
capabilities: {
logging: {}
}
}
);
// Register enabled tools to the server
enabledTools.forEach((tool) => {
tool.installTo(server);
});
// Check if PORT environment variable is set for HTTP transport
const PORT = process.env.PORT;
if (PORT) {
// HTTP transport mode
const app = express();
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
// Add CORS middleware for browser clients
app.use(
cors({
origin: '*', // Configure appropriately for production
exposedHeaders: ['Mcp-Session-Id'],
allowedHeaders: ['Content-Type', 'mcp-session-id']
})
);
app.use(express.json());
// Handle POST requests for client-to-server communication
app.post('/mcp', async (req, res) => {
// Check for existing session ID
const sessionId = req.headers['mcp-session-id'] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
// Store the transport by session ID
transports[sessionId] = transport;
}
// DNS rebinding protection is disabled by default for backwards compatibility
// enableDnsRebindingProtection: true,
// allowedHosts: ['127.0.0.1'],
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
// Connect to the MCP server
await server.connect(transport);
} else {
// Invalid request
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
});
return;
}
// Handle the request
await transport.handleRequest(req, res, req.body);
});
// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (
req: express.Request,
res: express.Response
) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);
// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);
// Start the HTTP server
app.listen(PORT, () => {
console.log(`Mapbox MCP Server running in HTTP mode on port ${PORT}`);
});
} else {
// STDIO transport mode (default)
const transport = new StdioServerTransport();
await server.connect(transport);
console.log('Mapbox MCP Server running in STDIO mode');
}