"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMcpServer = createMcpServer;
exports.startHttpServer = startHttpServer;
exports.startMultiProjectHttpServer = startMultiProjectHttpServer;
const path_1 = __importDefault(require("path"));
const http_1 = __importDefault(require("http"));
const crypto_1 = require("crypto");
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const embedder_1 = require("../lib/embedder");
const logger_1 = require("../lib/logger");
const index_1 = require("../api/rest/index");
const websocket_1 = require("../api/rest/websocket");
const access_1 = require("../lib/access");
const defaults_1 = require("../lib/defaults");
const multi_config_1 = require("../lib/multi-config");
const docs_1 = require("../graphs/docs");
const code_1 = require("../graphs/code");
const knowledge_1 = require("../graphs/knowledge");
const file_index_1 = require("../graphs/file-index");
const task_1 = require("../graphs/task");
const skill_1 = require("../graphs/skill");
const manager_types_1 = require("../graphs/manager-types");
const listTopics = __importStar(require("../api/tools/docs/list-topics"));
const getToc = __importStar(require("../api/tools/docs/get-toc"));
const search = __importStar(require("../api/tools/docs/search"));
const getNode = __importStar(require("../api/tools/docs/get-node"));
const searchDocFiles = __importStar(require("../api/tools/docs/search-files"));
const findExamples = __importStar(require("../api/tools/docs/find-examples"));
const searchSnippets = __importStar(require("../api/tools/docs/search-snippets"));
const listSnippets = __importStar(require("../api/tools/docs/list-snippets"));
const explainSymbol = __importStar(require("../api/tools/docs/explain-symbol"));
const crossReferences = __importStar(require("../api/tools/docs/cross-references"));
const listFiles = __importStar(require("../api/tools/code/list-files"));
const getFileSymbols = __importStar(require("../api/tools/code/get-file-symbols"));
const searchCode = __importStar(require("../api/tools/code/search-code"));
const getSymbol = __importStar(require("../api/tools/code/get-symbol"));
const searchCodeFiles = __importStar(require("../api/tools/code/search-files"));
const createNote = __importStar(require("../api/tools/knowledge/create-note"));
const updateNote = __importStar(require("../api/tools/knowledge/update-note"));
const deleteNote = __importStar(require("../api/tools/knowledge/delete-note"));
const getNote = __importStar(require("../api/tools/knowledge/get-note"));
const listNotes = __importStar(require("../api/tools/knowledge/list-notes"));
const searchNotes = __importStar(require("../api/tools/knowledge/search-notes"));
const createRelation = __importStar(require("../api/tools/knowledge/create-relation"));
const deleteRelation = __importStar(require("../api/tools/knowledge/delete-relation"));
const listRelations = __importStar(require("../api/tools/knowledge/list-relations"));
const findLinkedNotes = __importStar(require("../api/tools/knowledge/find-linked-notes"));
const addNoteAttachment = __importStar(require("../api/tools/knowledge/add-attachment"));
const removeNoteAttachment = __importStar(require("../api/tools/knowledge/remove-attachment"));
const listAllFiles = __importStar(require("../api/tools/file-index/list-all-files"));
const searchAllFiles = __importStar(require("../api/tools/file-index/search-all-files"));
const getFileInfo = __importStar(require("../api/tools/file-index/get-file-info"));
const createTask = __importStar(require("../api/tools/tasks/create-task"));
const updateTask = __importStar(require("../api/tools/tasks/update-task"));
const deleteTask = __importStar(require("../api/tools/tasks/delete-task"));
const getTask = __importStar(require("../api/tools/tasks/get-task"));
const listTasksTool = __importStar(require("../api/tools/tasks/list-tasks"));
const searchTasksTool = __importStar(require("../api/tools/tasks/search-tasks"));
const moveTask = __importStar(require("../api/tools/tasks/move-task"));
const reorderTaskTool = __importStar(require("../api/tools/tasks/reorder-task"));
const linkTask = __importStar(require("../api/tools/tasks/link-task"));
const createTaskLink = __importStar(require("../api/tools/tasks/create-task-link"));
const deleteTaskLink = __importStar(require("../api/tools/tasks/delete-task-link"));
const findLinkedTasks = __importStar(require("../api/tools/tasks/find-linked-tasks"));
const bulkMove = __importStar(require("../api/tools/tasks/bulk-move"));
const bulkPriority = __importStar(require("../api/tools/tasks/bulk-priority"));
const bulkDelete = __importStar(require("../api/tools/tasks/bulk-delete"));
const addTaskAttachment = __importStar(require("../api/tools/tasks/add-attachment"));
const createEpicTool = __importStar(require("../api/tools/epics/create-epic"));
const updateEpicTool = __importStar(require("../api/tools/epics/update-epic"));
const deleteEpicTool = __importStar(require("../api/tools/epics/delete-epic"));
const getEpicTool = __importStar(require("../api/tools/epics/get-epic"));
const listEpicsTool = __importStar(require("../api/tools/epics/list-epics"));
const searchEpicsTool = __importStar(require("../api/tools/epics/search-epics"));
const linkEpicTaskTool = __importStar(require("../api/tools/epics/link-task"));
const unlinkEpicTaskTool = __importStar(require("../api/tools/epics/unlink-task"));
const removeTaskAttachment = __importStar(require("../api/tools/tasks/remove-attachment"));
const createSkillTool = __importStar(require("../api/tools/skills/create-skill"));
const updateSkillTool = __importStar(require("../api/tools/skills/update-skill"));
const deleteSkillTool = __importStar(require("../api/tools/skills/delete-skill"));
const getSkillTool = __importStar(require("../api/tools/skills/get-skill"));
const listSkillsTool = __importStar(require("../api/tools/skills/list-skills"));
const searchSkillsTool = __importStar(require("../api/tools/skills/search-skills"));
const linkSkill = __importStar(require("../api/tools/skills/link-skill"));
const createSkillLink = __importStar(require("../api/tools/skills/create-skill-link"));
const deleteSkillLink = __importStar(require("../api/tools/skills/delete-skill-link"));
const findLinkedSkills = __importStar(require("../api/tools/skills/find-linked-skills"));
const addSkillAttachment = __importStar(require("../api/tools/skills/add-attachment"));
const removeSkillAttachment = __importStar(require("../api/tools/skills/remove-attachment"));
const recallSkills = __importStar(require("../api/tools/skills/recall-skills"));
const bumpSkillUsage = __importStar(require("../api/tools/skills/bump-usage"));
const getContext = __importStar(require("../api/tools/context/get-context"));
/**
 * Create an McpServer proxy that wraps registerTool handlers in a PromiseQueue.
 * Used for mutation tools to prevent concurrent graph modifications.
 */
function createMutationServer(server, queue, getSessionId) {
    const proxy = Object.create(server);
    const origRegister = server.registerTool.bind(server);
    const toolLog = (0, logger_1.createLogger)('mcp-tool');
    proxy.registerTool = function (name, config, handler) {
        if (typeof handler === 'function') {
            const wrapped = (...handlerArgs) => queue.enqueue(async () => {
                const sid = getSessionId?.() ?? '?';
                toolLog.debug({ sessionId: sid, tool: name, args: handlerArgs[0] }, 'Tool call');
                const start = Date.now();
                try {
                    const result = await handler(...handlerArgs);
                    toolLog.debug({ sessionId: sid, tool: name, durationMs: Date.now() - start }, 'Tool result');
                    return result;
                }
                catch (err) {
                    toolLog.debug({ sessionId: sid, tool: name, durationMs: Date.now() - start, err }, 'Tool error');
                    throw err;
                }
            });
            return origRegister(name, config, wrapped);
        }
        return origRegister(name, config, handler);
    };
    return proxy;
}
function buildInstructions(ctx) {
    const lines = [];
    if (ctx.workspaceId) {
        lines.push(`Connected to project "${ctx.projectId}" in workspace "${ctx.workspaceId}".`);
        if (ctx.workspaceProjects?.length) {
            lines.push(`Workspace projects: ${ctx.workspaceProjects.join(', ')}.`);
        }
        lines.push('Knowledge, tasks, and skills are shared across all workspace projects.');
        lines.push('Docs, code, and files are specific to the current project.');
        lines.push('Use projectId parameter in cross-graph links to reference nodes from other projects.');
    }
    else {
        lines.push(`Connected to project "${ctx.projectId}".`);
    }
    lines.push('Use get_context tool for structured project/workspace information.');
    return lines.join(' ');
}
/**
 * Creates the McpServer with all tools wired to the given graphs.
 * Pass docGraph to enable the 9 doc tools (5 base + 4 code-block) + 1 cross_references (needs codeGraph too);
 * pass codeGraph to enable the 5 code tools;
 * pass fileIndexGraph to enable the 3 file index tools.
 * cross_references requires both docGraph and codeGraph.
 * Knowledge tools (12) are always registered.
 * Task tools (13) + Epic tools (8) are always registered when taskGraph is provided.
 * @param embedFn  Single EmbedFn (all graphs share it) or per-graph EmbedFnMap.
 *                 Tests typically pass a single function; CLI passes a map for per-graph models.
 * @param mutationQueue  Optional PromiseQueue to serialize mutation tool handlers.
 */
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext, readonlyGraphs, userAccess, getSessionId) {
    // Backward-compat: single EmbedFn → use for both document and query
    const defaultPair = { document: (q) => (0, embedder_1.embed)(q, ''), query: (q) => (0, embedder_1.embed)(q, '') };
    const fns = typeof embedFn === 'function'
        ? {
            docs: { document: embedFn, query: embedFn },
            code: { document: embedFn, query: embedFn },
            knowledge: { document: embedFn, query: embedFn },
            tasks: { document: embedFn, query: embedFn },
            files: { document: embedFn, query: embedFn },
            skills: { document: embedFn, query: embedFn },
        }
        : {
            docs: embedFn?.docs ?? defaultPair,
            code: embedFn?.code ?? defaultPair,
            knowledge: embedFn?.knowledge ?? defaultPair,
            tasks: embedFn?.tasks ?? defaultPair,
            files: embedFn?.files ?? defaultPair,
            skills: embedFn?.skills ?? defaultPair,
        };
    // Build instructions for MCP clients (workspace/project context)
    const instructions = sessionContext ? buildInstructions(sessionContext) : undefined;
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const pkgVersion = require(path_1.default.resolve(__dirname, '../../package.json')).version;
    const server = new mcp_js_1.McpServer({ name: 'graphmemory', version: pkgVersion }, instructions ? { instructions } : undefined);
    // Mutation tools are registered through mutServer to serialize concurrent writes
    const mutServer = mutationQueue ? createMutationServer(server, mutationQueue, getSessionId) : server;
    // Check if mutation tools should be registered for a graph:
    // - graph must not be readonly (global setting — tools hidden for all)
    // - user must have write access (per-user — tools hidden for this user)
    // - if no userAccess map, all mutations are allowed (no auth configured)
    const canMutate = (graphName) => {
        if (readonlyGraphs?.has(graphName))
            return false;
        if (userAccess) {
            const level = userAccess.get(graphName);
            if (level && !(0, access_1.canWrite)(level))
                return false;
        }
        return true;
    };
    // Check if a graph's tools should be registered at all (deny = no tools)
    const canAccess = (graphName) => {
        if (!userAccess)
            return true;
        const level = userAccess.get(graphName);
        if (level && !(0, access_1.canRead)(level))
            return false;
        return true;
    };
    // Context tool (always registered)
    getContext.register(server, sessionContext);
    const ext = { docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, skillGraph };
    // Docs tools (only when docGraph is provided and user has access)
    if (docGraph && canAccess('docs')) {
        const docMgr = new docs_1.DocGraphManager(docGraph, fns.docs, ext);
        listTopics.register(server, docMgr);
        getToc.register(server, docMgr);
        search.register(server, docMgr);
        getNode.register(server, docMgr);
        searchDocFiles.register(server, docMgr);
        findExamples.register(server, docMgr);
        searchSnippets.register(server, docMgr);
        listSnippets.register(server, docMgr);
        explainSymbol.register(server, docMgr);
        // Cross-graph tools (require both docGraph and codeGraph)
        if (codeGraph && canAccess('code')) {
            const codeMgrForCross = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
            crossReferences.register(server, docMgr, codeMgrForCross);
        }
    }
    // Code tools (only when codeGraph is provided and user has access)
    if (codeGraph && canAccess('code')) {
        const codeMgr = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
        listFiles.register(server, codeMgr);
        getFileSymbols.register(server, codeMgr);
        searchCode.register(server, codeMgr);
        getSymbol.register(server, codeMgr);
        searchCodeFiles.register(server, codeMgr);
    }
    // File index tools (when fileIndexGraph is provided and user has access)
    if (fileIndexGraph && canAccess('files')) {
        const fileIndexMgr = new file_index_1.FileIndexGraphManager(fileIndexGraph, fns.files, ext);
        listAllFiles.register(server, fileIndexMgr);
        searchAllFiles.register(server, fileIndexMgr);
        getFileInfo.register(server, fileIndexMgr);
    }
    // Knowledge tools — read tools gated by canAccess, mutation tools gated by canMutate
    if (knowledgeGraph && canAccess('knowledge')) {
        const ctx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
        const knowledgeMgr = new knowledge_1.KnowledgeGraphManager(knowledgeGraph, fns.knowledge, ctx, {
            docGraph, codeGraph, fileIndexGraph, taskGraph, skillGraph,
        });
        getNote.register(server, knowledgeMgr);
        listNotes.register(server, knowledgeMgr);
        searchNotes.register(server, knowledgeMgr);
        listRelations.register(server, knowledgeMgr);
        findLinkedNotes.register(server, knowledgeMgr);
        if (canMutate('knowledge')) {
            createNote.register(mutServer, knowledgeMgr);
            updateNote.register(mutServer, knowledgeMgr);
            deleteNote.register(mutServer, knowledgeMgr);
            createRelation.register(mutServer, knowledgeMgr);
            deleteRelation.register(mutServer, knowledgeMgr);
            addNoteAttachment.register(mutServer, knowledgeMgr);
            removeNoteAttachment.register(mutServer, knowledgeMgr);
        }
    }
    // Task tools — read tools gated by canAccess, mutation tools gated by canMutate
    if (taskGraph && canAccess('tasks')) {
        const taskCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
        const taskMgr = new task_1.TaskGraphManager(taskGraph, fns.tasks, taskCtx, {
            docGraph, codeGraph, knowledgeGraph, fileIndexGraph, skillGraph,
        });
        getTask.register(server, taskMgr);
        listTasksTool.register(server, taskMgr);
        searchTasksTool.register(server, taskMgr);
        findLinkedTasks.register(server, taskMgr);
        if (canMutate('tasks')) {
            createTask.register(mutServer, taskMgr);
            updateTask.register(mutServer, taskMgr);
            deleteTask.register(mutServer, taskMgr);
            moveTask.register(mutServer, taskMgr);
            reorderTaskTool.register(mutServer, taskMgr);
            linkTask.register(mutServer, taskMgr);
            createTaskLink.register(mutServer, taskMgr);
            deleteTaskLink.register(mutServer, taskMgr);
            bulkMove.register(mutServer, taskMgr);
            bulkPriority.register(mutServer, taskMgr);
            bulkDelete.register(mutServer, taskMgr);
            addTaskAttachment.register(mutServer, taskMgr);
            removeTaskAttachment.register(mutServer, taskMgr);
        }
        // Epic tools (same graph, same access)
        getEpicTool.register(server, taskMgr);
        listEpicsTool.register(server, taskMgr);
        searchEpicsTool.register(server, taskMgr);
        if (canMutate('tasks')) {
            createEpicTool.register(mutServer, taskMgr);
            updateEpicTool.register(mutServer, taskMgr);
            deleteEpicTool.register(mutServer, taskMgr);
            linkEpicTaskTool.register(mutServer, taskMgr);
            unlinkEpicTaskTool.register(mutServer, taskMgr);
        }
    }
    // Skill tools — read tools gated by canAccess, mutation tools gated by canMutate
    if (skillGraph && canAccess('skills')) {
        const skillCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
        const skillMgr = new skill_1.SkillGraphManager(skillGraph, fns.skills, skillCtx, {
            docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph,
        });
        getSkillTool.register(server, skillMgr);
        listSkillsTool.register(server, skillMgr);
        searchSkillsTool.register(server, skillMgr);
        findLinkedSkills.register(server, skillMgr);
        recallSkills.register(server, skillMgr);
        if (canMutate('skills')) {
            createSkillTool.register(mutServer, skillMgr);
            updateSkillTool.register(mutServer, skillMgr);
            deleteSkillTool.register(mutServer, skillMgr);
            linkSkill.register(mutServer, skillMgr);
            createSkillLink.register(mutServer, skillMgr);
            deleteSkillLink.register(mutServer, skillMgr);
            addSkillAttachment.register(mutServer, skillMgr);
            removeSkillAttachment.register(mutServer, skillMgr);
            bumpSkillUsage.register(mutServer, skillMgr);
        }
    }
    return server;
}
// ---------------------------------------------------------------------------
// HTTP transport (Streamable HTTP)
// ---------------------------------------------------------------------------
async function collectBody(req) {
    const chunks = [];
    let size = 0;
    for await (const chunk of req) {
        size += chunk.length;
        if (size > defaults_1.MAX_BODY_SIZE)
            throw new Error('Request body too large');
        chunks.push(chunk);
    }
    return JSON.parse(Buffer.concat(chunks).toString());
}
async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, projectDir, skillGraph, sessionContext, readonlyGraphs) {
    const sessions = new Map();
    // Sweep stale sessions every 60s
    const sweepInterval = setInterval(() => {
        const now = Date.now();
        for (const [sid, s] of sessions) {
            if (now - s.lastActivity > sessionTimeoutMs) {
                s.server.close().catch(() => { });
                sessions.delete(sid);
            }
        }
    }, defaults_1.SESSION_SWEEP_INTERVAL_MS);
    sweepInterval.unref();
    const httpServer = http_1.default.createServer(async (req, res) => {
        try {
            if (req.url !== '/mcp') {
                res.writeHead(404).end();
                return;
            }
            const sessionId = req.headers['mcp-session-id'];
            // Existing session — route to its transport
            if (sessionId && sessions.has(sessionId)) {
                const session = sessions.get(sessionId);
                session.lastActivity = Date.now();
                const body = req.method === 'POST' ? await collectBody(req) : undefined;
                await session.transport.handleRequest(req, res, body);
                return;
            }
            // Stale/unknown session ID — return 404 per MCP spec so client can re-initialize
            if (sessionId) {
                res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32001, message: 'Session not found' }, id: null }));
                return;
            }
            // New session — only POST (initialize) can create one
            if (req.method !== 'POST') {
                res.writeHead(400).end('No session');
                return;
            }
            const body = await collectBody(req);
            const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
                sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
                onsessioninitialized: (sid) => {
                    sessions.set(sid, { server: mcpServer, transport, lastActivity: Date.now() });
                    (0, logger_1.createLogger)('http').debug({ sessionId: sid }, 'Session started');
                },
            });
            transport.onclose = () => {
                const sid = transport.sessionId;
                if (sid)
                    sessions.delete(sid);
            };
            const mcpServer = createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, undefined, projectDir, skillGraph, sessionContext, readonlyGraphs, undefined, () => transport.sessionId);
            await mcpServer.connect(transport);
            await transport.handleRequest(req, res, body);
        }
        catch (err) {
            if (!res.headersSent)
                res.writeHead(400).end(String(err));
        }
    });
    return new Promise((resolve) => {
        httpServer.listen(port, host, () => {
            (0, logger_1.createLogger)('server').info({ host, port }, 'MCP HTTP server listening');
            resolve(httpServer);
        });
    });
}
async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, projectManager, restOptions) {
    const sessions = new Map();
    // Sweep stale sessions every 60s
    const sweepInterval = setInterval(() => {
        const now = Date.now();
        for (const [sid, s] of sessions) {
            if (now - s.lastActivity > sessionTimeoutMs) {
                s.server.close().catch(() => { });
                sessions.delete(sid);
            }
        }
    }, defaults_1.SESSION_SWEEP_INTERVAL_MS);
    sweepInterval.unref();
    // Express app handles /api/* routes
    const restApp = (0, index_1.createRestApp)(projectManager, restOptions);
    const httpServer = http_1.default.createServer(async (req, res) => {
        try {
            // Route /api/* to Express
            if (req.url?.startsWith('/api/')) {
                restApp(req, res);
                return;
            }
            // Route: /mcp/{workspaceId}/{projectId} or /mcp/{projectId}
            const wsMatch = req.url?.match(/^\/mcp\/([^/?]+)\/([^/?]+)/);
            const projMatch = !wsMatch ? req.url?.match(/^\/mcp\/([^/?]+)/) : null;
            if (!wsMatch && !projMatch) {
                // Everything else (UI static files, SPA fallback) goes through Express
                restApp(req, res);
                return;
            }
            // Resolve project and optional workspace from URL
            let projectId;
            let workspaceId;
            if (wsMatch) {
                const maybeWs = decodeURIComponent(wsMatch[1]);
                const maybeProjId = decodeURIComponent(wsMatch[2]);
                const ws = projectManager.getWorkspace(maybeWs);
                if (ws) {
                    // Valid workspace route
                    if (!ws.config.projects.includes(maybeProjId)) {
                        res.writeHead(404).end(JSON.stringify({ error: `Project "${maybeProjId}" is not part of workspace "${maybeWs}"` }));
                        return;
                    }
                    workspaceId = maybeWs;
                    projectId = maybeProjId;
                }
                else {
                    // Not a workspace — treat first segment as projectId (fallback)
                    projectId = maybeWs;
                }
            }
            else {
                projectId = decodeURIComponent(projMatch[1]);
            }
            const sessionId = req.headers['mcp-session-id'];
            // Existing session — route to its transport
            if (sessionId && sessions.has(sessionId)) {
                const session = sessions.get(sessionId);
                if (session.projectId !== projectId) {
                    res.writeHead(400).end('Session belongs to a different project');
                    return;
                }
                session.lastActivity = Date.now();
                const body = req.method === 'POST' ? await collectBody(req) : undefined;
                await session.transport.handleRequest(req, res, body);
                return;
            }
            // Stale/unknown session ID — return 404 per MCP spec so client can re-initialize
            if (sessionId) {
                res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32001, message: 'Session not found' }, id: null }));
                return;
            }
            // New session — only POST (initialize) can create one
            if (req.method !== 'POST') {
                res.writeHead(400).end('No session');
                return;
            }
            // Auth: if users configured, require valid Bearer token (OAuth JWT or API key).
            // Done before project lookup to avoid revealing which projects exist to unauthenticated callers.
            const users = restOptions?.users ?? {};
            const hasUsers = Object.keys(users).length > 0;
            let userId;
            if (hasUsers) {
                const auth = req.headers.authorization;
                if (!auth?.startsWith('Bearer ') || auth.length <= 7) {
                    res.writeHead(401, { 'WWW-Authenticate': 'Bearer', 'content-type': 'application/json' }).end(JSON.stringify({ error: 'Authentication required' }));
                    return;
                }
                const jwtSecret = restOptions?.serverConfig?.jwtSecret;
                const result = (0, access_1.resolveUserFromBearer)(auth.slice(7), users, jwtSecret);
                if (!result) {
                    res.writeHead(401, { 'WWW-Authenticate': 'Bearer', 'content-type': 'application/json' }).end(JSON.stringify({ error: 'Invalid credentials' }));
                    return;
                }
                userId = result.userId;
            }
            // Validate project exists
            const project = projectManager.getProject(projectId);
            if (!project) {
                res.writeHead(404).end(JSON.stringify({ error: `Project "${projectId}" not found` }));
                return;
            }
            // Build session context (auto-detect workspace if not in URL)
            const ws = workspaceId
                ? projectManager.getWorkspace(workspaceId)
                : projectManager.getProjectWorkspace(projectId);
            const sessionCtx = {
                projectId,
                projectDescription: project.config.description,
                workspaceId: ws?.id,
                workspaceProjects: ws?.config.projects,
                userId,
            };
            // Build readonly set from config + workspace overrides
            const mcpReadonlyGraphs = new Set();
            for (const gn of multi_config_1.GRAPH_NAMES) {
                if (project.config.graphConfigs[gn].readonly)
                    mcpReadonlyGraphs.add(gn);
            }
            if (project.workspaceId) {
                const wsInst = projectManager.getWorkspace(project.workspaceId);
                if (wsInst) {
                    for (const gn of ['knowledge', 'tasks', 'skills']) {
                        if (wsInst.config.graphConfigs[gn].readonly)
                            mcpReadonlyGraphs.add(gn);
                    }
                }
            }
            // Build per-user access map
            let mcpUserAccess;
            if (userId && restOptions?.serverConfig) {
                mcpUserAccess = new Map();
                for (const gn of multi_config_1.GRAPH_NAMES) {
                    const level = (0, access_1.resolveAccess)(userId, gn, project.config, restOptions.serverConfig, ws?.config);
                    mcpUserAccess.set(gn, level);
                }
            }
            const body = await collectBody(req);
            const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
                sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
                onsessioninitialized: (sid) => {
                    sessions.set(sid, { projectId, workspaceId: ws?.id, userId, server: mcpServer, transport, lastActivity: Date.now() });
                    (0, logger_1.createLogger)('http').debug({ sessionId: sid, projectId, workspaceId: ws?.id, userId }, 'Session started');
                },
            });
            transport.onclose = () => {
                const sid = transport.sessionId;
                if (sid)
                    sessions.delete(sid);
            };
            const mcpServer = createMcpServer(project.docGraph, project.codeGraph, project.knowledgeGraph, project.fileIndexGraph, project.taskGraph, project.embedFns, project.mutationQueue, project.config.projectDir, project.skillGraph, sessionCtx, mcpReadonlyGraphs, mcpUserAccess, () => transport.sessionId);
            await mcpServer.connect(transport);
            await transport.handleRequest(req, res, body);
        }
        catch (err) {
            if (!res.headersSent)
                res.writeHead(400).end(String(err));
        }
    });
    // Attach WebSocket server for real-time events
    (0, websocket_1.attachWebSocket)(httpServer, projectManager, {
        jwtSecret: restOptions?.serverConfig?.jwtSecret,
        users: restOptions?.users,
        serverConfig: restOptions?.serverConfig,
    });
    return new Promise((resolve) => {
        httpServer.listen(port, host, () => {
            const base = `http://${host}:${port}`;
            const projects = projectManager.listProjects();
            const workspaces = projectManager.listWorkspaces();
            const lines = [
                '',
                '  ╔══════════════════════════════════════════════╗',
                '  ║         Graph Memory Server Ready            ║',
                '  ╚══════════════════════════════════════════════╝',
                '',
                `  UI        ${base}/ui/`,
                `  REST API  ${base}/api/`,
                `  WebSocket ws://${host}:${port}/api/ws`,
                '',
                '  MCP endpoints:',
            ];
            for (const id of projects) {
                const ws = projectManager.getProjectWorkspace(id);
                const wsLabel = ws ? ` (${ws.id})` : '';
                lines.push(`    ${id}${wsLabel}  ${base}/mcp/${id}`);
            }
            if (projects.length === 0) {
                lines.push('    (no projects configured)');
            }
            if (workspaces.length > 0) {
                lines.push('');
                lines.push(`  Workspaces: ${workspaces.join(', ')}`);
            }
            lines.push('');
            (0, logger_1.createLogger)('server').info(lines.join('\n'));
            resolve(httpServer);
        });
    });
}
