import { spawn } from "child_process";
import { createInterface } from "readline";
export class MCPClient {
    process = null;
    requestId = 0;
    pending = new Map();
    buffer = "";
    _tools = null;
    config;
    _connected = false;
    constructor(config) {
        this.config = config;
    }
    get connected() {
        return this._connected;
    }
    send(msg) {
        if (!this.process?.stdin?.writable)
            return;
        const body = JSON.stringify(msg);
        const header = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n`;
        this.process.stdin.write(header + body);
    }
    request(method, params, timeoutMs = 15_000) {
        return new Promise((resolve, reject) => {
            const id = ++this.requestId;
            const timer = setTimeout(() => {
                this.pending.delete(id);
                reject(new Error(`MCP "${method}" timed out after ${timeoutMs}ms`));
            }, timeoutMs);
            this.pending.set(id, {
                resolve: (v) => {
                    clearTimeout(timer);
                    resolve(v);
                },
                reject: (e) => {
                    clearTimeout(timer);
                    reject(e);
                },
            });
            this.send({ jsonrpc: "2.0", id, method, params: params ?? {} });
        });
    }
    async connect() {
        if (this.process)
            return;
        this.process = spawn(this.config.command, this.config.args ?? [], {
            env: { ...process.env, ...this.config.env },
            stdio: ["pipe", "pipe", "pipe"],
        });
        const rl = createInterface({ input: this.process.stdout });
        rl.on("line", (line) => {
            this.buffer += `${line}\n`;
            this.processBuffer();
        });
        this.process.on("exit", () => {
            this._connected = false;
            this.process = null;
            for (const [, p] of this.pending)
                p.reject(new Error("MCP process exited"));
            this.pending.clear();
        });
        this.process.stderr?.on("data", () => { });
        // Initialize
        await this.request("initialize", {
            protocolVersion: "2025-11-25",
            capabilities: {},
            clientInfo: { name: "voxcode", version: "1.0.0" },
        });
        this._connected = true;
    }
    processBuffer() {
        while (true) {
            const m = this.buffer.match(/^Content-Length: (\d+)\r\n\r\n/);
            if (!m)
                break;
            const len = parseInt(m[1], 10);
            const headerEnd = m[0].length;
            if (this.buffer.length < headerEnd + len)
                break;
            const raw = this.buffer.slice(headerEnd, headerEnd + len);
            this.buffer = this.buffer.slice(headerEnd + len);
            try {
                const msg = JSON.parse(raw);
                const p = this.pending.get(msg.id);
                if (p) {
                    this.pending.delete(msg.id);
                    if (msg.error)
                        p.reject(new Error(msg.error.message));
                    else
                        p.resolve(msg.result ?? {});
                }
            }
            catch {
                // Ignore parse errors
            }
        }
    }
    async listTools() {
        if (this._tools)
            return this._tools;
        const result = await this.request("tools/list");
        const tools = result.tools ?? [];
        this._tools = tools.map((t) => ({
            name: t.name,
            description: t.description,
            inputSchema: t.inputSchema ?? {},
        }));
        return this._tools;
    }
    async callTool(name, args) {
        const result = await this.request("tools/call", { name, arguments: args });
        const content = result.content ?? [];
        return { content, isError: result.isError };
    }
    async shutdown() {
        if (!this.process) {
            this._connected = false;
            return;
        }
        try {
            await this.request("shutdown");
        }
        catch {
            // Ignore
        }
        this.process.kill();
        this.process = null;
        this._connected = false;
    }
}
export class MCPServerManager {
    clients = new Map();
    addServer(name, config) {
        const client = new MCPClient(config);
        this.clients.set(name, client);
        return client;
    }
    getServer(name) {
        return this.clients.get(name);
    }
    async connectAll() {
        const results = await Promise.allSettled([...this.clients.entries()].map(async ([_name, client]) => {
            await client.connect();
        }));
        for (let i = 0; i < results.length; i++) {
            const r = results[i];
            if (r.status === "rejected") {
                const name = [...this.clients.keys()][i];
                console.error(`Failed to connect MCP server "${name}": ${r.reason}`);
            }
        }
    }
    async shutdownAll() {
        for (const [, client] of this.clients) {
            await client.shutdown().catch(() => { });
        }
        this.clients.clear();
    }
    getAllClients() {
        return this.clients;
    }
}
//# sourceMappingURL=client.js.map