/**
 * Simple MCP client for Pi Agent version.
 * Supports stdio and HTTP transports.
 */
import { spawn } from 'node:child_process';
export class McpStdioClient {
    name;
    config;
    child = null;
    nextId = 1;
    pending = new Map();
    buffer = Buffer.alloc(0);
    connected = false;
    constructor(name, config) {
        this.name = name;
        this.config = config;
    }
    /**
     * Connect to the MCP server
     */
    async connect() {
        if (this.config.disabled) {
            throw new Error(`MCP server '${this.name}' is disabled`);
        }
        if (this.child) {
            await this.disconnect();
        }
        this.child = spawn(this.config.command, this.config.args || [], {
            cwd: this.config.cwd,
            env: { ...process.env, ...(this.config.env || {}) },
            stdio: ['pipe', 'pipe', 'pipe'],
        });
        this.buffer = Buffer.alloc(0);
        this.pending.clear();
        // Handle stdout (MCP protocol messages)
        this.child.stdout.on('data', (chunk) => {
            this.buffer = Buffer.concat([this.buffer, chunk]);
            this.processBuffer();
        });
        // Handle stderr (log for debugging)
        this.child.stderr.on('data', (data) => {
            console.debug(`[MCP ${this.name} stderr] ${data.toString().trim()}`);
        });
        // Handle process exit
        this.child.on('exit', (code) => {
            console.debug(`[MCP ${this.name}] Process exited with code ${code}`);
            this.connected = false;
            this.rejectAllPending(new Error(`MCP server '${this.name}' exited with code ${code}`));
        });
        // Handle process error
        this.child.on('error', (error) => {
            console.error(`[MCP ${this.name}] Process error:`, error);
            this.connected = false;
            this.rejectAllPending(error);
        });
        // Initialize connection
        try {
            await this.request('initialize', {
                protocolVersion: '2024-11-05',
                capabilities: {},
                clientInfo: {
                    name: 'clawx-pi-agent',
                    version: '0.9.20',
                },
            });
            this.connected = true;
            console.log(`[MCP ${this.name}] Connected successfully`);
        }
        catch (error) {
            this.child.kill();
            this.child = null;
            throw error;
        }
    }
    /**
     * Disconnect from the MCP server
     */
    async disconnect() {
        if (!this.child)
            return;
        try {
            await this.request('notifications/initialized', {});
        }
        catch (error) {
            // Ignore errors during shutdown
        }
        this.child.kill();
        this.child = null;
        this.connected = false;
        this.rejectAllPending(new Error(`MCP server '${this.name}' disconnected`));
    }
    /**
     * List tools from the MCP server
     */
    async listTools() {
        const result = await this.request('tools/list', {});
        return result.tools || [];
    }
    /**
     * Call a tool on the MCP server
     */
    async callTool(toolName, params) {
        return await this.request('tools/call', {
            name: toolName,
            arguments: params,
        });
    }
    /**
     * Check if client is connected
     */
    isConnected() {
        return this.connected && this.child !== null;
    }
    /**
     * Send a JSON-RPC request and wait for response
     */
    async request(method, params) {
        if (!this.child) {
            throw new Error(`MCP server '${this.name}' not connected`);
        }
        const id = this.nextId++;
        const request = {
            jsonrpc: '2.0',
            id,
            method,
            params,
        };
        return new Promise((resolve, reject) => {
            this.pending.set(id, { resolve, reject });
            // Send the request
            const message = JSON.stringify(request);
            const data = `Content-Length: ${Buffer.byteLength(message, 'utf8')}\r\n\r\n${message}`;
            this.child.stdin.write(data, 'utf8', (error) => {
                if (error) {
                    this.pending.delete(id);
                    reject(error);
                }
            });
            // Timeout after 30 seconds
            setTimeout(() => {
                if (this.pending.has(id)) {
                    this.pending.delete(id);
                    reject(new Error(`MCP request timeout for method '${method}'`));
                }
            }, 30000);
        });
    }
    /**
     * Process incoming data buffer
     */
    processBuffer() {
        while (true) {
            // Find header end
            const headerEnd = this.buffer.indexOf('\r\n\r\n');
            if (headerEnd === -1)
                break;
            // Parse content length
            const headerText = this.buffer.slice(0, headerEnd).toString('utf8');
            const lengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
            if (!lengthMatch) {
                console.error(`[MCP ${this.name}] Invalid header: ${headerText}`);
                this.buffer = Buffer.alloc(0);
                break;
            }
            const contentLength = Number(lengthMatch[1]);
            const totalLength = headerEnd + 4 + contentLength;
            // Wait for complete message
            if (this.buffer.length < totalLength)
                break;
            // Parse JSON-RPC message
            const body = this.buffer.slice(headerEnd + 4, totalLength).toString('utf8');
            this.buffer = this.buffer.slice(totalLength);
            let message;
            try {
                message = JSON.parse(body);
            }
            catch (error) {
                console.error(`[MCP ${this.name}] Invalid JSON:`, error);
                continue;
            }
            // Handle response
            if (typeof message.id === 'number' && this.pending.has(message.id)) {
                const waiter = this.pending.get(message.id);
                this.pending.delete(message.id);
                if (message.error) {
                    waiter.reject(new Error(`MCP error: ${message.error.message}`));
                }
                else {
                    waiter.resolve(message.result);
                }
            }
        }
    }
    /**
     * Reject all pending requests
     */
    rejectAllPending(error) {
        for (const [id, waiter] of this.pending.entries()) {
            waiter.reject(error);
            this.pending.delete(id);
        }
    }
}
//# sourceMappingURL=client.js.map