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 { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
import { jsonSchemaToZod } from "./schema-utils.js";
const DEFAULT_PORT = 3001;
const log = (message, ...args) => {
  console.error(`[MCP Dev] ${message}`, ...args);
};
async function getApiKeyFromMcpJson() {
  const { readFile, access } = await import("node:fs/promises");
  const { resolve, dirname } = await import("node:path");
  const findMcpJson = async (startDir) => {
    let dir = startDir;
    const root = resolve("/");
    while (dir !== root) {
      const candidate = resolve(dir, ".mcp.json");
      try {
        await access(candidate);
        return candidate;
      } catch {
        dir = dirname(dir);
      }
    }
    return null;
  };
  try {
    const mcpJsonPath = await findMcpJson(process.cwd());
    if (!mcpJsonPath) {
      return void 0;
    }
    const content = await readFile(mcpJsonPath, "utf-8");
    const config = JSON.parse(content);
    const serverConfig = config?.mcpServers?.["open-mercato"];
    return serverConfig?.headers?.["x-api-key"];
  } catch {
    return void 0;
  }
}
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);
  });
}
function createDevMcpServer(toolContext, authFeatures, isSuperAdmin, debug) {
  const server = new McpServer(
    { name: "open-mercato-mcp-dev", version: "0.1.0" },
    { capabilities: { tools: {} } }
  );
  const registry = getToolRegistry();
  const tools = Array.from(registry.getTools().values());
  const rbacService = toolContext.container.resolve("rbacService");
  const accessibleTools = tools.filter(
    (tool) => hasRequiredFeatures(tool.requiredFeatures, authFeatures, isSuperAdmin, rbacService)
  );
  if (debug) {
    log(`Registering ${accessibleTools.length}/${tools.length} tools (filtered by API key permissions)`);
  }
  for (const tool of accessibleTools) {
    if (debug) {
      log(`Registering tool: ${tool.name}`);
    }
    let safeSchema;
    if (tool.inputSchema) {
      try {
        const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: "any" });
        const converted = jsonSchemaToZod(jsonSchema);
        safeSchema = converted.passthrough();
      } catch (error) {
        if (debug) {
          log(`Skipping tool ${tool.name} - schema conversion failed:`, error instanceof Error ? error.message : error);
        }
        continue;
      }
    } else {
      safeSchema = z.object({}).passthrough();
    }
    try {
      server.registerTool(
        tool.name,
        {
          description: tool.description,
          inputSchema: safeSchema
        },
        async (args) => {
          const toolArgs = args ?? {};
          if (debug) {
            log(`Calling tool: ${tool.name}`, JSON.stringify(toolArgs));
          }
          const result = await executeTool(tool.name, toolArgs, toolContext);
          if (!result.success) {
            log(`Tool error: ${result.error}`);
            return {
              content: [
                {
                  type: "text",
                  text: JSON.stringify({ error: result.error, code: result.errorCode })
                }
              ],
              isError: true
            };
          }
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(result.result, null, 2)
              }
            ]
          };
        }
      );
    } catch (error) {
      if (debug) {
        log(`Skipping tool ${tool.name} - registration failed:`, error instanceof Error ? error.message : error);
      }
      continue;
    }
  }
  return server;
}
async function runMcpDevServer() {
  const apiKey = await getApiKeyFromMcpJson();
  const port = parseInt(process.env.MCP_DEV_PORT ?? "", 10) || DEFAULT_PORT;
  const debug = process.env.MCP_DEBUG === "true";
  if (!apiKey) {
    log("Error: API key not found in .mcp.json");
    log("");
    log("To get an API key:");
    log("  1. Log into Open Mercato as an admin");
    log("  2. Go to Settings > API Keys");
    log("  3. Create a new key with the required permissions");
    log("");
    log("Then configure in .mcp.json:");
    log("  {");
    log('    "mcpServers": {');
    log('      "open-mercato": {');
    log('        "type": "http",');
    log('        "url": "http://localhost:3001/mcp",');
    log('        "headers": {');
    log('          "x-api-key": "omk_your_api_key_here"');
    log("        }");
    log("      }");
    log("    }");
    log("  }");
    process.exit(1);
  }
  log("Starting development MCP HTTP server...");
  const { createRequestContainer } = await import("@open-mercato/shared/lib/di/container");
  const container = await createRequestContainer();
  log("Authenticating API key...");
  const authResult = await authenticateMcpRequest(apiKey, container);
  if (!authResult.success) {
    log(`Authentication failed: ${authResult.error}`);
    process.exit(1);
  }
  log(`Authenticated as: ${authResult.keyName}`);
  log(`Tenant: ${authResult.tenantId ?? "(global)"}`);
  log(`Organization: ${authResult.organizationId ?? "(none)"}`);
  log(`Super admin: ${authResult.isSuperAdmin}`);
  log(`Features: ${authResult.features.length > 0 ? authResult.features.join(", ") : "(none)"}`);
  log("Loading tools...");
  await loadAllModuleTools();
  try {
    const { extractEntityGraph, cacheEntityGraph } = await import("./entity-graph.js");
    const { getOrm } = await import("@open-mercato/shared/lib/db/mikro");
    log("Generating entity relationship graph...");
    const orm = await getOrm();
    const graph = await extractEntityGraph(orm);
    cacheEntityGraph(graph);
    log(`Entity graph: ${graph.nodes.length} entities, ${graph.edges.length} relationships`);
  } catch (error) {
    log("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) {
      log("Rich OpenAPI spec cached for Code Mode (with requestBody schemas)");
    } else {
      log("OpenAPI spec not available");
    }
  } catch (error) {
    log("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) {
          log(`Indexed ${count} entity schemas for discovery`);
        }
      }
    } catch (entityError) {
      log("Entity schema indexing skipped:", entityError instanceof Error ? entityError.message : entityError);
    }
  } catch {
    log("Search indexing skipped (search service not available)");
  }
  const { randomBytes } = await import("node:crypto");
  const devSessionId = "dev_" + randomBytes(8).toString("hex");
  log(`Session ID: ${devSessionId} (stable for this server instance)`);
  const toolContext = {
    tenantId: authResult.tenantId,
    organizationId: authResult.organizationId,
    userId: authResult.userId,
    container,
    userFeatures: authResult.features,
    isSuperAdmin: authResult.isSuperAdmin,
    apiKeySecret: apiKey,
    sessionId: devSessionId
  };
  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",
        mode: "development",
        tools: getToolRegistry().listToolNames().length,
        tenant: authResult.tenantId,
        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;
    }
    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;
    }
    if (providedApiKey !== apiKey) {
      res.writeHead(401, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "Invalid API key" }));
      return;
    }
    if (debug) {
      log(`Authenticated request (${req.method})`);
    }
    try {
      const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: void 0,
        enableJsonResponse: req.method === "POST"
      });
      const mcpServer = createDevMcpServer(toolContext, authResult.features, authResult.isSuperAdmin, debug);
      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 (debug) {
          log(`Request completed, cleaned up`);
        }
      });
    } catch (error) {
      log("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;
  log(`Tools registered: ${toolCount}`);
  log(`Endpoint: http://localhost:${port}/mcp`);
  log(`Health: http://localhost:${port}/health`);
  log(`Mode: Development (API key auth, no session tokens)`);
  return new Promise((resolve) => {
    httpServer.listen(port, () => {
      log(`Server listening on port ${port}`);
      log("Ready for Claude Code connections");
    });
    const shutdown = async () => {
      log("Shutting down...");
      httpServer.close(() => {
        log("Server closed");
        resolve();
      });
    };
    process.on("SIGINT", shutdown);
    process.on("SIGTERM", shutdown);
  });
}
export {
  runMcpDevServer
};
//# sourceMappingURL=mcp-dev-server.js.map
