import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { getToolRegistry } from "./tool-registry.js";
import { executeTool } from "./tool-executor.js";
import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
import { extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
import { jsonSchemaToZod } from "./schema-utils.js";
import { findApiKeyBySecret, findSessionApiKeyWithSecret } from "@open-mercato/core/modules/api_keys/services/apiKeyService";
async function resolveSessionContext(sessionToken, baseContext, debug) {
  try {
    const em = baseContext.container.resolve("em");
    const rbacService = baseContext.container.resolve("rbacService");
    const sessionResult = await findSessionApiKeyWithSecret(em, sessionToken);
    if (!sessionResult) {
      if (debug) {
        console.error(`[MCP HTTP] Session token not found, expired, or secret unavailable: ${sessionToken}`);
      }
      return null;
    }
    const { key: sessionKey, secret: sessionSecret } = sessionResult;
    const userId = sessionKey.sessionUserId || sessionKey.createdBy;
    if (!userId) {
      if (debug) {
        console.error(`[MCP HTTP] Session key has no associated user`);
      }
      return null;
    }
    const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {
      tenantId: sessionKey.tenantId ?? null,
      organizationId: sessionKey.organizationId ?? null
    });
    if (debug) {
      console.error(`[MCP HTTP] Session context resolved for user ${userId}:`, {
        tenantId: sessionKey.tenantId,
        organizationId: sessionKey.organizationId,
        features: acl.features.length,
        isSuperAdmin: acl.isSuperAdmin,
        hasSessionSecret: !!sessionSecret
      });
    }
    return {
      tenantId: sessionKey.tenantId ?? null,
      organizationId: sessionKey.organizationId ?? null,
      userId,
      container: baseContext.container,
      userFeatures: acl.features,
      isSuperAdmin: acl.isSuperAdmin,
      // Use the decrypted session secret for API calls (not the MCP server key)
      apiKeySecret: sessionSecret
    };
  } catch (error) {
    if (debug) {
      console.error(`[MCP HTTP] Error resolving session context:`, error);
    }
    return null;
  }
}
async function resolveApiKeyContext(apiKeyRecord, baseContext, debug) {
  try {
    const rbacService = baseContext.container.resolve("rbacService");
    const userId = apiKeyRecord.sessionUserId ?? apiKeyRecord.createdBy;
    if (!userId) {
      if (debug) {
        console.error(`[MCP HTTP] API key has no associated user`);
      }
      return null;
    }
    const acl = await rbacService.loadAcl(`api_key:${apiKeyRecord.id}`, {
      tenantId: apiKeyRecord.tenantId ?? null,
      organizationId: apiKeyRecord.organizationId ?? null
    });
    if (debug) {
      console.error(`[MCP HTTP] API key context resolved for user ${userId}:`, {
        tenantId: apiKeyRecord.tenantId,
        organizationId: apiKeyRecord.organizationId,
        features: acl.features.length,
        isSuperAdmin: acl.isSuperAdmin
      });
    }
    return {
      tenantId: apiKeyRecord.tenantId ?? null,
      organizationId: apiKeyRecord.organizationId ?? null,
      userId,
      container: baseContext.container,
      userFeatures: acl.features,
      isSuperAdmin: acl.isSuperAdmin,
      apiKeySecret: baseContext.apiKeySecret
    };
  } catch (error) {
    if (debug) {
      console.error(`[MCP HTTP] Error resolving API key context:`, error);
    }
    return null;
  }
}
function createMcpServerForRequest(config, toolContext, apiKeyRecord) {
  const server = new McpServer(
    { name: config.name, version: config.version },
    { capabilities: { tools: {} } }
  );
  const registry = getToolRegistry();
  const tools = Array.from(registry.getTools().values());
  if (config.debug) {
    console.error(`[MCP HTTP] Registering ${tools.length} tools (ACL checked per-call via session token)`);
  }
  for (const tool of tools) {
    if (config.debug) {
      console.error(`[MCP HTTP] Registering tool: ${tool.name}`);
    }
    let safeSchema;
    if (tool.inputSchema) {
      try {
        const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: "any" });
        const properties = jsonSchema.properties ?? {};
        properties._sessionToken = {
          type: "string",
          description: "Session authorization token. If omitted, the server API key roles are used instead."
        };
        jsonSchema.properties = properties;
        const converted = jsonSchemaToZod(jsonSchema);
        safeSchema = converted.passthrough();
      } catch (error) {
        if (config.debug) {
          console.error(
            `[MCP HTTP] Skipping tool ${tool.name} - schema conversion failed:`,
            error instanceof Error ? error.message : error
          );
        }
        continue;
      }
    } else {
      safeSchema = z.object({
        _sessionToken: z.string().optional().describe("Session authorization token (REQUIRED for all tool calls)")
      });
    }
    try {
      server.registerTool(
        tool.name,
        {
          description: tool.description,
          inputSchema: safeSchema
        },
        async (args) => {
          const toolArgs = args ?? {};
          const sessionToken = toolArgs._sessionToken;
          delete toolArgs._sessionToken;
          console.error(`[MCP HTTP] \u25B6 Tool call: ${tool.name}`, {
            hasSessionToken: !!sessionToken,
            args: JSON.stringify(toolArgs).slice(0, 200)
          });
          let effectiveContext = toolContext;
          if (sessionToken) {
            const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug);
            if (sessionContext) {
              effectiveContext = { ...sessionContext, sessionId: sessionToken };
            } else {
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify({
                      error: "Your chat session has expired. Please close and reopen the chat window to continue.",
                      code: "SESSION_EXPIRED"
                    })
                  }
                ],
                isError: true
              };
            }
          } else {
            const apiKeyContext = await resolveApiKeyContext(apiKeyRecord, toolContext, config.debug);
            if (apiKeyContext) {
              effectiveContext = apiKeyContext;
            } else if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify({
                      error: "Authentication failed: provide a session token (_sessionToken) or a valid API key with assigned roles",
                      code: "UNAUTHORIZED"
                    })
                  }
                ],
                isError: true
              };
            }
            if (!effectiveContext.sessionId && effectiveContext.apiKeySecret) {
              effectiveContext = {
                ...effectiveContext,
                sessionId: "apikey_" + effectiveContext.apiKeySecret.slice(0, 16)
              };
            }
          }
          if (tool.requiredFeatures?.length) {
            const rbacService = effectiveContext.container.resolve("rbacService");
            const hasAccess = hasRequiredFeatures(
              tool.requiredFeatures,
              effectiveContext.userFeatures,
              effectiveContext.isSuperAdmin,
              rbacService
            );
            if (!hasAccess) {
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify({
                      error: `Insufficient permissions for tool "${tool.name}". Required: ${tool.requiredFeatures.join(", ")}`,
                      code: "UNAUTHORIZED"
                    })
                  }
                ],
                isError: true
              };
            }
          }
          try {
            const result = await executeTool(tool.name, toolArgs, effectiveContext);
            if (!result.success) {
              console.error(`[MCP HTTP] \u2717 Tool error: ${tool.name}`, { error: result.error, code: result.errorCode });
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify({ error: result.error, code: result.errorCode })
                  }
                ],
                isError: true
              };
            }
            console.error(`[MCP HTTP] \u2713 Tool success: ${tool.name}`, {
              resultPreview: JSON.stringify(result.result).slice(0, 200)
            });
            return {
              content: [
                {
                  type: "text",
                  text: JSON.stringify(result.result, null, 2)
                }
              ]
            };
          } catch (err) {
            console.error(`[MCP HTTP] \u2717 Tool exception: ${tool.name}`, err);
            return {
              content: [
                {
                  type: "text",
                  text: JSON.stringify({ error: err instanceof Error ? err.message : "Unknown error", code: "EXCEPTION" })
                }
              ],
              isError: true
            };
          }
        }
      );
    } catch (error) {
      if (config.debug) {
        console.error(
          `[MCP HTTP] Skipping tool ${tool.name} - registration failed:`,
          error instanceof Error ? error.message : error
        );
      }
      continue;
    }
  }
  return server;
}
const MAX_BODY_SIZE = 1 * 1024 * 1024;
async function parseJsonBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    let totalSize = 0;
    req.on("data", (chunk) => {
      totalSize += chunk.length;
      if (totalSize > MAX_BODY_SIZE) {
        req.destroy();
        reject(new Error("Request payload too large"));
        return;
      }
      chunks.push(chunk);
    });
    req.on("end", () => {
      try {
        const body = Buffer.concat(chunks).toString("utf-8");
        resolve(body ? JSON.parse(body) : void 0);
      } catch (error) {
        reject(error);
      }
    });
    req.on("error", reject);
  });
}
async function runMcpHttpServer(options) {
  const { config, container, port } = options;
  await loadAllModuleTools();
  try {
    const { extractEntityGraph, cacheEntityGraph } = await import("./entity-graph.js");
    const { getOrm } = await import("@open-mercato/shared/lib/db/mikro");
    const orm = await getOrm();
    const graph = await extractEntityGraph(orm);
    cacheEntityGraph(graph);
    console.error(`[MCP HTTP] Entity graph: ${graph.nodes.length} entities, ${graph.edges.length} relationships`);
  } catch (error) {
    console.error("[MCP HTTP] Entity graph generation skipped:", error instanceof Error ? error.message : error);
  }
  try {
    const { loadRichOpenApiSpec } = await import("./api-endpoint-index.js");
    const spec = await loadRichOpenApiSpec();
    if (spec) {
      console.error("[MCP HTTP] Rich OpenAPI spec cached for Code Mode (with requestBody schemas)");
    } else {
      console.error("[MCP HTTP] OpenAPI spec not available");
    }
  } catch (error) {
    console.error("[MCP HTTP] OpenAPI spec caching skipped:", error instanceof Error ? error.message : error);
  }
  try {
    const searchService = container.resolve("searchService");
    await indexToolsForSearch(searchService);
    try {
      const { getCachedEntityGraph } = await import("./entity-graph.js");
      const { indexEntitiesForSearch } = await import("./entity-index.js");
      const graph = getCachedEntityGraph();
      if (graph) {
        const { count } = await indexEntitiesForSearch(searchService, graph);
        if (count > 0) {
          console.error(`[MCP HTTP] Indexed ${count} entity schemas for hybrid search`);
        }
      }
    } catch (entityError) {
      console.error("[MCP HTTP] Entity schema indexing skipped:", entityError instanceof Error ? entityError.message : entityError);
    }
  } catch (error) {
    console.error("[MCP HTTP] Search indexing skipped (search service not available):", error);
  }
  const httpServer = createServer(async (req, res) => {
    const url = new URL(req.url || "/", `http://localhost:${port}`);
    if (url.pathname === "/health") {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        status: "ok",
        tools: getToolRegistry().listToolNames().length,
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      }));
      return;
    }
    if (url.pathname !== "/mcp") {
      res.writeHead(404, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "Not found" }));
      return;
    }
    console.error(`[MCP HTTP] \u2190 Request: ${req.method} ${url.pathname}`);
    const headers = {};
    for (const [key, value] of Object.entries(req.headers)) {
      headers[key] = Array.isArray(value) ? value[0] : value;
    }
    const providedApiKey = extractApiKeyFromHeaders(headers);
    if (!providedApiKey) {
      res.writeHead(401, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "API key required (x-api-key header)" }));
      return;
    }
    const em = container.resolve("em");
    const apiKeyRecord = await findApiKeyBySecret(em, providedApiKey);
    if (!apiKeyRecord) {
      res.writeHead(401, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "Invalid or expired API key" }));
      return;
    }
    if (config.debug) {
      console.error(`[MCP HTTP] Server-level auth passed (${req.method}) - API key: ${apiKeyRecord.keyPrefix}...`);
    }
    const toolContext = {
      tenantId: apiKeyRecord.tenantId ?? null,
      organizationId: apiKeyRecord.organizationId ?? null,
      userId: apiKeyRecord.createdBy ?? null,
      container,
      userFeatures: [],
      isSuperAdmin: false,
      apiKeySecret: providedApiKey
    };
    try {
      const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: void 0,
        enableJsonResponse: req.method === "POST"
      });
      const mcpServer = createMcpServerForRequest(config, toolContext, apiKeyRecord);
      if (config.debug) {
        const registeredTools = mcpServer._registeredTools || {};
        console.error(`[MCP HTTP] Registered tools in McpServer:`, Object.keys(registeredTools));
        console.error(`[MCP HTTP] Tool handlers initialized:`, mcpServer._toolHandlersInitialized);
      }
      await mcpServer.connect(transport);
      if (req.method === "POST") {
        const body = await parseJsonBody(req);
        await transport.handleRequest(req, res, body);
      } else {
        await transport.handleRequest(req, res);
      }
      res.on("finish", () => {
        transport.close();
        mcpServer.close();
        if (config.debug) {
          console.error(`[MCP HTTP] Request completed, cleaned up`);
        }
      });
    } catch (error) {
      console.error("[MCP HTTP] Error handling request:", error);
      if (!res.headersSent) {
        if (error instanceof Error && error.message === "Request payload too large") {
          res.writeHead(413, { "Content-Type": "application/json" });
          res.end(JSON.stringify({ error: "Request payload too large (max 1MB)" }));
          return;
        }
        res.writeHead(500, { "Content-Type": "application/json" });
        res.end(
          JSON.stringify({
            jsonrpc: "2.0",
            error: {
              code: -32603,
              message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`
            },
            id: null
          })
        );
      }
    }
  });
  const toolCount = getToolRegistry().listToolNames().length;
  console.error(`[MCP HTTP] Starting ${config.name} v${config.version}`);
  console.error(`[MCP HTTP] Endpoint: http://localhost:${port}/mcp`);
  console.error(`[MCP HTTP] Health: http://localhost:${port}/health`);
  console.error(`[MCP HTTP] Tools registered: ${toolCount}`);
  console.error(`[MCP HTTP] Mode: Stateless (new server per request)`);
  console.error(`[MCP HTTP] Server Auth: API key validated against database (x-api-key header)`);
  console.error(`[MCP HTTP] User Auth: Session token (_sessionToken) preferred, falls back to API key roles`);
  return new Promise((resolve) => {
    httpServer.listen(port, () => {
      console.error(`[MCP HTTP] Server listening on port ${port}`);
    });
    const shutdown = async () => {
      console.error("[MCP HTTP] Shutting down...");
      httpServer.close(() => {
        console.error("[MCP HTTP] Server closed");
        resolve();
      });
    };
    process.on("SIGINT", shutdown);
    process.on("SIGTERM", shutdown);
  });
}
export {
  runMcpHttpServer
};
//# sourceMappingURL=http-server.js.map
