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);
});