mcp/index.jsjavascript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mcpRouter = void 0;
const express_1 = require("express");
const bearerAuth_js_1 = require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const winston_1 = __importDefault(require("../plugins/winston.js"));
const auth_1 = require("./auth");
const discovery_1 = require("./discovery");
const tools_1 = require("./tools");
const verifier = new auth_1.JwtTokenVerifier();
const router = (0, express_1.Router)();
exports.mcpRouter = router;
/**
* JSON-RPC methods that an agent may call before authenticating.
*
* The capability handshake (`initialize`, `tools/list`, `ping`) is left public
* so scanners and agents can discover the server's WebMCP capabilities and
* enumerate tools without a bearer token. Everything else — notably
* `tools/call` and all `resources/*` and `prompts/*` reads — stays behind
* `requireBearerAuth`, which answers with a 401 and the RFC 9728
* `WWW-Authenticate` pointer to the protected-resource metadata.
*/
const PUBLIC_MCP_METHODS = new Set([
"initialize",
"notifications/initialized",
"tools/list",
"ping",
]);
const bearerAuth = (0, bearerAuth_js_1.requireBearerAuth)({
verifier,
resourceMetadataUrl: discovery_1.RESOURCE_METADATA_URL,
});
/**
* Enforce bearer auth only for methods not in {@link PUBLIC_MCP_METHODS}.
*
* GET (stream resume) and DELETE (session teardown) always require a prior
* authenticated session, so they stay gated. For POST we inspect the JSON-RPC
* method and skip auth for the public capability handshake.
*/
const conditionalBearerAuth = (req, res, next) => {
if (req.method === "POST") {
const method = req.body?.method;
if (typeof method === "string" && PUBLIC_MCP_METHODS.has(method)) {
next();
return;
}
}
bearerAuth(req, res, next);
};
router.use(conditionalBearerAuth);
const transports = {};
router.post("/", async (req, res) => {
winston_1.default.debug(`[MCP] POST received — content-type: ${req.headers["content-type"]}, session-id: ${req.headers["mcp-session-id"] ?? "(none)"}, body: ${JSON.stringify(req.body)}`);
const sessionId = req.headers["mcp-session-id"];
const existingTransport = sessionId ? transports[sessionId] : undefined;
const method = req.body?.method ?? "(unknown)";
if (existingTransport) {
winston_1.default.debug(`[MCP] ${method} — existing session ${existingTransport.sessionId}`);
await existingTransport.handleRequest(req, res, req.body);
return;
}
winston_1.default.debug(`[MCP] ${method} — new session`);
const mcpServer = (0, tools_1.createMcpServer)();
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (sessionId) => {
winston_1.default.debug(`[MCP] session initialized: ${sessionId}`);
transports[sessionId] = transport;
},
onsessionclosed: (sessionId) => {
winston_1.default.debug(`[MCP] session closed: ${sessionId}`);
delete transports[sessionId];
},
});
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
await mcpServer.connect(transport);
await transport.handleRequest(req, res, req.body);
});
router.get("/", async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
const transport = sessionId ? transports[sessionId] : undefined;
if (!transport) {
res.status(404).json({ error: "Session not found" });
return;
}
await transport.handleRequest(req, res);
});
router.delete("/", async (req, res) => {
winston_1.default.debug(`[MCP] DELETE received — content-type: ${req.headers["content-type"]}, session-id: ${req.headers["mcp-session-id"] ?? "(none)"}, body: ${JSON.stringify(req.body)}`);
const sessionId = req.headers["mcp-session-id"];
const transport = sessionId ? transports[sessionId] : undefined;
if (!transport) {
res.status(404).json({ error: "Session not found" });
return;
}
await transport.handleRequest(req, res);
});