feat: basics #1

Merged
albnnc merged 13 commits from specific-primate into main 2026-07-09 06:42:45 +00:00
6 changed files with 1193 additions and 74 deletions
Showing only changes of commit 4d621e0a54 - Show all commits
+78 -45
View File
@@ -1,6 +1,6 @@
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { Command } from "commander";
import http from "node:http";
import crypto from "node:crypto";
@@ -32,59 +32,92 @@ function buildServer(): McpServer {
return server;
}
async function main() {
const transportType: string = options.transport;
async function runStdio() {
const server = buildServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
if (transportType === "stdio") {
serveStdio(buildServer);
return;
}
async function runSSE() {
const transports = new Map<string, SSEServerTransport>();
if (transportType === "sse") {
const server = buildServer();
const mcpTransport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
await server.connect(mcpTransport);
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;
}
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");
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();
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 sessionId = crypto.randomUUID();
const transport = new SSEServerTransport("/message", res);
transports.set(sessionId, transport);
res.on("close", () => {
transports.delete(sessionId);
});
const server = buildServer();
await server.connect(transport);
return;
}
if (req.method === "POST" && url.pathname === "/message") {
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 mcpTransport.handleRequest(req, res);
});
await transport.handlePostMessage(req, res);
return;
}
const port = Number(options.port);
httpServer.listen(port, options.host);
res.writeHead(404);
res.end("Not found");
});
console.error(
`Exchange MCP Server running via SSE on http://${options.host}:${port}`,
);
return;
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);
}
console.error(`Unsupported transport: ${transportType}. Use "stdio" or "sse".`);
process.exit(1);
}
main().catch((error) => {
+1111 -24
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -7,8 +7,7 @@
"format": "dprint fmt"
},
"dependencies": {
"@modelcontextprotocol/node": "^2.0.0-beta.2",
"@modelcontextprotocol/server": "^2.0.0-beta.2",
"@modelcontextprotocol/sdk": "^1.29.0",
"commander": "^15.0.0",
"fast-xml-parser": "^5.2.0",
"httpntlm": "^1.8.13",
+1 -1
View File
@@ -1,6 +1,6 @@
import { z } from "zod/v4";
import { EwsClient, loadConfig, saveConfig, clearConfig } from "../ews_client.ts";
import { type McpServer } from "@modelcontextprotocol/server";
import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export function registerAuthTools(server: McpServer): void {
server.registerTool(
+1 -1
View File
@@ -1,6 +1,6 @@
import { z } from "zod/v4";
import { EwsClient, loadConfig } from "../ews_client.ts";
import { type McpServer } from "@modelcontextprotocol/server";
import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function getClient(): EwsClient {
return new EwsClient(loadConfig());
+1 -1
View File
@@ -1,6 +1,6 @@
import { z } from "zod/v4";
import { EwsClient, loadConfig } from "../ews_client.ts";
import { type McpServer } from "@modelcontextprotocol/server";
import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export function registerFolderTools(server: McpServer): void {
server.registerTool(