import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Command } from "commander"; import crypto from "node:crypto"; import http from "node:http"; import { registerAuthTools } from "./tools/auth.ts"; import { registerAvailabilityTools } from "./tools/availability.ts"; import { registerCalendarTools } from "./tools/calendar.ts"; import { registerEmailTools } from "./tools/email.ts"; import { registerFolderTools } from "./tools/folders.ts"; import { registerPeopleTools } from "./tools/people.ts"; import { initDataDir } from "./utils/data.ts"; const program = new Command() .name("exchange-mcp") .description("Exchange MCP Server — EWS integration via MCP") .option("--transport ", "Transport: stdio or sse", "stdio") .option( "--token-hash ", "SHA-256 hash of bearer token for HTTP transport auth", ) .option("--host ", "HTTP host", "127.0.0.1") .option("--port ", "HTTP port", "8000") .option("--data-dir ", "Data directory for config.json", "./data"); program.parse(process.argv); const options = program.opts(); initDataDir(options.dataDir); function buildServer(): McpServer { const server = new McpServer({ name: "exchange-mcp", version: "1.0.0", }); registerAuthTools(server); registerEmailTools(server); registerFolderTools(server); registerCalendarTools(server); registerAvailabilityTools(server); registerPeopleTools(server); return server; } async function runStdio() { const server = buildServer(); const transport = new StdioServerTransport(); await server.connect(transport); } async function runSSE() { const transports = new Map(); const httpServer = http.createServer(async (req, res) => { if (options.tokenHash) { const auth = req.headers.authorization || ""; const token = auth.startsWith("Bearer ") ? auth.slice(7) : ""; const hash = crypto.createHash("sha256").update(token).digest("hex"); if (hash !== options.tokenHash) { res.writeHead(401, { "Content-Type": "text/plain" }); res.end("Unauthorized"); return; } } res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization", ); if (req.method === "OPTIONS") { res.writeHead(200); res.end(); return; } const url = new URL(req.url || "/", `http://${req.headers.host}`); if (req.method === "GET") { const transport = new SSEServerTransport("/sse", res); const server = buildServer(); await server.connect(transport); transports.set(transport.sessionId, transport); res.on("close", () => { transports.delete(transport.sessionId); }); return; } if (req.method === "POST" && url.pathname === "/sse") { const sessionId = url.searchParams.get("sessionId") || ""; const transport = transports.get(sessionId); if (!transport) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Session not found"); return; } await transport.handlePostMessage(req, res); return; } res.writeHead(404); res.end("Not found"); }); const port = Number(options.port); httpServer.listen(port, options.host); console.error( `Exchange MCP Server running via SSE on http://${options.host}:${port}`, ); } async function main() { const transportType: string = options.transport; switch (transportType) { case "stdio": await runStdio(); break; case "sse": await runSSE(); break; default: console.error( `Unsupported transport: "${transportType}". Use "stdio" or "sse".`, ); process.exit(1); } } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });