{"version":3,"file":"mcp-handler.cjs","names":["createJsonRpcDispatcher"],"sources":["../src/mcp-handler.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport type {\n  MCPToolDefinition,\n  MCPResourceDefinition,\n  MCPResourceContent,\n  MCPPromptDefinition,\n  MCPPromptResult,\n  MCPContent,\n  MCPSession,\n} from \"./mcp-types.js\";\n\nexport interface MCPState {\n  serverInfo: { name: string; version: string };\n  tools: Map<string, { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }>;\n  resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }>;\n  prompts: Map<\n    string,\n    {\n      def: MCPPromptDefinition;\n      handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n    }\n  >;\n  sessions: Map<string, MCPSession>;\n}\n\nfunction jsonRpcResult(id: string | number, result: unknown) {\n  return { jsonrpc: \"2.0\" as const, id, result };\n}\n\nfunction jsonRpcError(id: string | number | null, code: number, message: string) {\n  return { jsonrpc: \"2.0\" as const, id, error: { code, message } };\n}\n\nexport function createMCPRequestHandler(state: MCPState) {\n  const dispatcher = createJsonRpcDispatcher({\n    methods: {\n      // initialize is handled directly in the outer function — this entry is\n      // only here so the dispatcher doesn't return \"Method not found\" if the\n      // request somehow reaches it.\n      initialize: async (_params, id) => {\n        return jsonRpcResult(id, {\n          protocolVersion: \"2025-03-26\",\n          capabilities: { tools: {}, resources: {}, prompts: {} },\n          serverInfo: state.serverInfo,\n        });\n      },\n\n      \"notifications/initialized\": async (_params, _id, req) => {\n        const sessionId = req.headers[\"mcp-session-id\"] as string;\n        const session = state.sessions.get(sessionId);\n        if (session) {\n          session.initialized = true;\n        }\n        return null;\n      },\n\n      ping: async (_params, id) => {\n        return jsonRpcResult(id, {});\n      },\n\n      \"tools/list\": async (_params, id) => {\n        const tools: MCPToolDefinition[] = [];\n        for (const { def } of state.tools.values()) {\n          tools.push(def);\n        }\n        return jsonRpcResult(id, { tools });\n      },\n\n      \"tools/call\": async (params, id) => {\n        const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n        if (!name) {\n          return jsonRpcError(id, -32602, \"Missing tool name\");\n        }\n        const entry = state.tools.get(name);\n        if (!entry) {\n          return jsonRpcError(id, -32602, `Unknown tool: ${name}`);\n        }\n        if (entry.handler) {\n          try {\n            const result = await entry.handler(args);\n            const content: MCPContent[] = Array.isArray(result)\n              ? result\n              : [{ type: \"text\", text: String(result) }];\n            return jsonRpcResult(id, { content, isError: false });\n          } catch (err: unknown) {\n            const message = err instanceof Error ? err.message : String(err);\n            return jsonRpcResult(id, {\n              content: [{ type: \"text\", text: message }],\n              isError: true,\n            });\n          }\n        }\n        // No handler — return empty content\n        return jsonRpcResult(id, { content: [], isError: false });\n      },\n\n      \"resources/list\": async (_params, id) => {\n        const resources: MCPResourceDefinition[] = [];\n        for (const { def } of state.resources.values()) {\n          resources.push(def);\n        }\n        return jsonRpcResult(id, { resources });\n      },\n\n      \"resources/read\": async (params, id) => {\n        const { uri } = (params ?? {}) as { uri?: string };\n        if (!uri) {\n          return jsonRpcError(id, -32602, \"Missing resource URI\");\n        }\n        const entry = state.resources.get(uri);\n        if (!entry) {\n          return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);\n        }\n        return jsonRpcResult(id, {\n          contents: [\n            {\n              uri,\n              ...(entry.content?.text !== undefined && { text: entry.content.text }),\n              ...(entry.content?.blob !== undefined && { blob: entry.content.blob }),\n              ...(entry.content?.mimeType !== undefined && { mimeType: entry.content.mimeType }),\n            },\n          ],\n        });\n      },\n\n      \"prompts/list\": async (_params, id) => {\n        const prompts: MCPPromptDefinition[] = [];\n        for (const { def } of state.prompts.values()) {\n          prompts.push(def);\n        }\n        return jsonRpcResult(id, { prompts });\n      },\n\n      \"prompts/get\": async (params, id) => {\n        const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n        if (!name) {\n          return jsonRpcError(id, -32602, \"Missing prompt name\");\n        }\n        const entry = state.prompts.get(name);\n        if (!entry) {\n          return jsonRpcError(id, -32602, `Unknown prompt: ${name}`);\n        }\n        if (entry.handler) {\n          try {\n            const result = await entry.handler(args);\n            return jsonRpcResult(id, result);\n          } catch (err: unknown) {\n            const message = err instanceof Error ? err.message : String(err);\n            return jsonRpcError(id, -32603, `Prompt handler error: ${message}`);\n          }\n        }\n        // No handler — return empty messages\n        return jsonRpcResult(id, { messages: [] });\n      },\n    },\n  });\n\n  return async (\n    req: http.IncomingMessage,\n    res: http.ServerResponse,\n    body: string,\n  ): Promise<void> => {\n    // DELETE handler: session teardown\n    if (req.method === \"DELETE\") {\n      const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n      if (!sessionId) {\n        res.writeHead(400, { \"Content-Type\": \"application/json\" });\n        res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n        return;\n      }\n      if (!state.sessions.has(sessionId)) {\n        res.writeHead(404, { \"Content-Type\": \"application/json\" });\n        res.end(JSON.stringify({ error: \"Session not found\" }));\n        return;\n      }\n      state.sessions.delete(sessionId);\n      res.writeHead(200, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ ok: true }));\n      return;\n    }\n\n    // Parse the body to determine method for session validation\n    let parsed: unknown;\n    try {\n      parsed = JSON.parse(body);\n    } catch {\n      // Let the dispatcher handle parse errors\n      await dispatcher(req, res, body);\n      return;\n    }\n\n    const method =\n      typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n        ? (parsed as { method: unknown }).method\n        : undefined;\n\n    // Handle initialize directly to control response headers\n    if (method === \"initialize\") {\n      const id =\n        typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n          ? (parsed as { id: unknown }).id\n          : null;\n\n      const sessionId = randomUUID();\n      state.sessions.set(sessionId, {\n        id: sessionId,\n        initialized: false,\n        createdAt: Date.now(),\n      });\n\n      const response = {\n        jsonrpc: \"2.0\",\n        id,\n        result: {\n          protocolVersion: \"2025-03-26\",\n          capabilities: { tools: {}, resources: {}, prompts: {} },\n          serverInfo: state.serverInfo,\n        },\n      };\n\n      res.writeHead(200, {\n        \"Content-Type\": \"application/json\",\n        \"Mcp-Session-Id\": sessionId,\n      });\n      res.end(JSON.stringify(response));\n      return;\n    }\n\n    // Session validation for all other methods\n    const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n    if (!sessionId) {\n      res.writeHead(400, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n      return;\n    }\n    if (!state.sessions.has(sessionId)) {\n      res.writeHead(404, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ error: \"Session not found\" }));\n      return;\n    }\n\n    // Enforce initialization: only allow notifications/initialized through\n    // before the session is fully initialized\n    const session = state.sessions.get(sessionId)!;\n    if (!session.initialized && method !== \"notifications/initialized\") {\n      res.writeHead(400, { \"Content-Type\": \"application/json\" });\n      res.end(\n        JSON.stringify(\n          jsonRpcError(\n            typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n              ? ((parsed as { id: unknown }).id as string | number)\n              : null,\n            -32002,\n            \"Session not initialized\",\n          ),\n        ),\n      );\n      return;\n    }\n\n    // Delegate to the JSON-RPC dispatcher for all other methods\n    await dispatcher(req, res, body);\n  };\n}\n"],"mappings":"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"}