feat: basics
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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 { registerEmailTools } from "./tools/email.ts";
|
||||
import { registerFolderTools } from "./tools/folders.ts";
|
||||
|
||||
const program = new Command()
|
||||
.name("exchange-mcp")
|
||||
.description("Exchange MCP Server — EWS integration via MCP")
|
||||
.option("--transport <type>", "Transport: stdio or sse", "stdio")
|
||||
.option(
|
||||
"--token-hash <hash>",
|
||||
"SHA-256 hash of bearer token for HTTP transport auth",
|
||||
)
|
||||
.option("--host <host>", "HTTP host", "127.0.0.1")
|
||||
.option("--port <port>", "HTTP port", "8000");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
function buildServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
name: "exchange-mcp",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
registerAuthTools(server);
|
||||
registerEmailTools(server);
|
||||
registerFolderTools(server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function runStdio() {
|
||||
const server = buildServer();
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
async function runSSE() {
|
||||
const transports = new Map<string, SSEServerTransport>();
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user