#!/usr/bin/env node
import {
  ToolRegistry
} from "./chunk-4JWMT7XQ.js";
import "./chunk-HDSKW7Q3.js";
import "./chunk-ZWVIDFGY.js";
import "./chunk-NVCB6SFZ.js";
import {
  runTool
} from "./chunk-PS6S4IIW.js";
import {
  getDangerLevel,
  schemaToJsonSchema
} from "./chunk-HIU2SH4V.js";
import "./chunk-TZQHYZKT.js";
import {
  VERSION
} from "./chunk-2DVGTYZG.js";
import "./chunk-4BKXL7SM.js";
import "./chunk-MM3F43H6.js";
import "./chunk-KHYD3WXE.js";
import "./chunk-SLSWPBK3.js";
import "./chunk-VNNYHW6N.js";
import "./chunk-OVWE4E46.js";
import "./chunk-PDX44BCA.js";

// src/mcp/server.ts
import { createInterface } from "readline";
import { resolve } from "path";
import { realpathSync } from "fs";
var STDIN_TOOLS = /* @__PURE__ */ new Set(["ask_user", "spawn_agent"]);
function looksLikePath(s) {
  if (s.startsWith("/")) return true;
  if (s.startsWith("\\\\")) return true;
  if (/^[a-zA-Z]:[\\/]/.test(s)) return true;
  if (s.startsWith("../") || s.startsWith("..\\")) return true;
  if (s === "..") return true;
  return false;
}
var MCP_ALWAYS_DESTRUCTIVE = /* @__PURE__ */ new Set([
  "bash",
  "run_interactive",
  "task_create"
]);
var TOOL_PATH_ARGS = {
  read_file: ["path"],
  write_file: ["path"],
  edit_file: ["path"],
  list_dir: ["path"],
  grep_files: ["path"],
  glob_files: ["path"],
  notebook_edit: ["path"],
  save_last_response: ["path"],
  git_status: ["path"],
  git_diff: ["path", "file"],
  git_log: ["path", "file"],
  git_commit: ["path", "files"],
  find_symbol: ["path"],
  get_outline: ["file", "path"],
  find_references: ["path"],
  search_code: ["path"],
  bash: ["cwd"],
  run_interactive: ["cwd"]
};
var McpServer = class {
  registry;
  opts;
  initialized = false;
  sandboxRoot;
  eligibleNames;
  constructor(registry, opts = {}) {
    this.registry = registry;
    this.opts = opts;
    this.sandboxRoot = this.resolveSandboxRoot(opts.cwd);
    this.eligibleNames = this.computeEligibleNames();
  }
  resolveSandboxRoot(cwd) {
    const base = cwd ? resolve(cwd) : process.cwd();
    try {
      return realpathSync(base);
    } catch {
      return base;
    }
  }
  computeEligibleNames() {
    const names = /* @__PURE__ */ new Set();
    const whitelist = this.opts.tools && this.opts.tools.length > 0 ? new Set(this.opts.tools) : null;
    for (const tool of this.registry.listAll()) {
      const name = tool.definition.name;
      if (STDIN_TOOLS.has(name)) continue;
      if (whitelist && !whitelist.has(name)) continue;
      names.add(name);
    }
    return names;
  }
  async start() {
    if (this.opts.cwd) {
      try {
        process.chdir(this.opts.cwd);
      } catch {
      }
    }
    const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
    rl.on("line", async (line) => {
      const trimmed = line.trim();
      if (!trimmed) return;
      let msg;
      try {
        msg = JSON.parse(trimmed);
      } catch {
        this.sendError(null, -32700, "Parse error");
        return;
      }
      await this.dispatch(msg);
    });
    rl.on("close", () => process.exit(0));
    process.stdin.resume();
    this.log(`aicli MCP server v${VERSION} started. sandboxRoot=${this.sandboxRoot}`);
    if (this.opts.allowDestructive) this.log("WARNING: --allow-destructive enabled");
    if (this.opts.allowOutsideCwd) this.log("WARNING: --allow-outside-cwd enabled");
  }
  // ── dispatcher ─────────────────────────────────────────────────────────────
  async dispatch(msg) {
    const id = "id" in msg ? msg["id"] : void 0;
    const method = msg["method"];
    const params = msg["params"] ?? {};
    if (id === void 0) {
      if (method === "notifications/initialized") this.initialized = true;
      return;
    }
    if (!method) {
      this.sendError(id, -32600, "Invalid request: missing method");
      return;
    }
    try {
      switch (method) {
        case "initialize":
          this.handleInitialize(id, params);
          break;
        case "tools/list":
          if (!this.initialized) {
            this.sendError(id, -32002, "Server not initialized");
            break;
          }
          this.handleToolsList(id);
          break;
        case "tools/call":
          if (!this.initialized) {
            this.sendError(id, -32002, "Server not initialized");
            break;
          }
          await this.handleToolCall(id, params);
          break;
        case "ping":
          this.sendResult(id, {});
          break;
        default:
          this.sendError(id, -32601, `Method not found: ${method}`);
      }
    } catch (err) {
      const emsg = err instanceof Error ? err.message : String(err);
      this.sendError(id, -32603, `Internal error: ${emsg}`);
    }
  }
  // ── handlers ───────────────────────────────────────────────────────────────
  handleInitialize(id, _params) {
    this.initialized = true;
    this.sendResult(id, {
      protocolVersion: "2024-11-05",
      capabilities: { tools: {} },
      serverInfo: { name: "aicli", version: VERSION }
    });
  }
  handleToolsList(id) {
    const tools = [...this.eligibleNames].map((n) => this.registry.get(n)).filter((t) => !!t);
    const list = tools.map((tool) => {
      const params = tool.definition.parameters;
      const properties = {};
      const required = [];
      for (const [key, schema] of Object.entries(params)) {
        properties[key] = schemaToJsonSchema(schema);
        if (schema.required) required.push(key);
      }
      return {
        name: tool.definition.name,
        description: tool.definition.description,
        inputSchema: {
          type: "object",
          properties,
          ...required.length > 0 ? { required } : {}
        }
      };
    });
    this.sendResult(id, { tools: list });
  }
  async handleToolCall(id, params) {
    const name = params["name"];
    const args = params["arguments"] ?? {};
    if (!name) {
      this.sendError(id, -32602, "Missing required param: name");
      return;
    }
    if (!this.eligibleNames.has(name)) {
      this.sendError(id, -32602, `Tool not exposed: ${name}`);
      return;
    }
    const tool = this.registry.get(name);
    if (!tool) {
      this.sendError(id, -32602, `Unknown tool: ${name}`);
      return;
    }
    const forcedDestructive = MCP_ALWAYS_DESTRUCTIVE.has(name);
    const level = forcedDestructive ? "destructive" : getDangerLevel(name, args);
    if (level === "destructive" && !this.opts.allowDestructive) {
      this.sendResult(id, {
        content: [{ type: "text", text: `Tool '${name}' requires --allow-destructive in MCP mode.` }],
        isError: true
      });
      return;
    }
    const pathErr = this.validatePathArgs(name, args);
    if (pathErr) {
      this.sendResult(id, {
        content: [{ type: "text", text: pathErr }],
        isError: true
      });
      return;
    }
    try {
      const output = await runTool(tool, args, name);
      this.sendResult(id, {
        content: [{ type: "text", text: output }]
      });
    } catch (err) {
      const text = err instanceof Error ? err.message : String(err);
      this.sendResult(id, {
        content: [{ type: "text", text }],
        isError: true
      });
    }
  }
  /**
   * Validate that every known path-bearing argument of `toolName` resolves
   * inside the sandbox root. Returns an error message on violation, else undef.
   *
   * We do NOT realpath() per-call: write_file targets may not exist yet, and
   * realpath on every call is a syscall per arg. The root itself is realpath'd
   * once at construction, which stops the "sandbox root is a symlink pointing
   * elsewhere" class of attack. Symlinks inside the tree are allowed.
   */
  validatePathArgs(toolName, args) {
    if (this.opts.allowOutsideCwd) return void 0;
    const keys = TOOL_PATH_ARGS[toolName];
    if (keys) {
      for (const key of keys) {
        const value = args[key];
        if (value === void 0 || value === null) continue;
        const list = Array.isArray(value) ? value : [value];
        for (const entry of list) {
          if (typeof entry !== "string" || entry.length === 0) continue;
          const abs = resolve(this.sandboxRoot, entry);
          if (!this.isInsideSandbox(abs)) {
            return `Path '${entry}' escapes sandbox root '${this.sandboxRoot}'. Pass --allow-outside-cwd to permit.`;
          }
        }
      }
    }
    const knownKeys = new Set(keys ?? []);
    for (const [k, v] of Object.entries(args)) {
      if (knownKeys.has(k)) continue;
      if (typeof v !== "string" || v.length === 0) continue;
      if (!looksLikePath(v)) continue;
      const abs = resolve(this.sandboxRoot, v);
      if (!this.isInsideSandbox(abs)) {
        return `Path-like argument '${k}=${v}' escapes sandbox root '${this.sandboxRoot}'. Pass --allow-outside-cwd to permit.`;
      }
    }
    return void 0;
  }
  isInsideSandbox(abs) {
    const norm = process.platform === "win32" ? abs.toLowerCase() : abs;
    const root = process.platform === "win32" ? this.sandboxRoot.toLowerCase() : this.sandboxRoot;
    if (norm === root) return true;
    const sep = process.platform === "win32" ? "\\" : "/";
    return norm.startsWith(root + sep);
  }
  // ── transport ──────────────────────────────────────────────────────────────
  sendResult(id, result) {
    this.write({ jsonrpc: "2.0", id, result });
  }
  sendError(id, code, message) {
    this.write({ jsonrpc: "2.0", id, error: { code, message } });
  }
  write(obj) {
    process.stdout.write(JSON.stringify(obj) + "\n");
  }
  /** MCP servers must use stderr for diagnostics so stdout stays JSON-RPC clean. */
  log(msg) {
    process.stderr.write(`[aicli-mcp] ${msg}
`);
  }
};
async function startMcpServer(opts) {
  const registry = new ToolRegistry();
  const server = new McpServer(registry, opts);
  await server.start();
}
export {
  McpServer,
  startMcpServer
};
