"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpService = exports.knowhowConfig = exports.knowhowMcpClient = void 0;
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const fs_1 = __importDefault(require("fs"));
const config_1 = require("../config");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
const McpWebsocketTransport_1 = require("./McpWebsocketTransport");
exports.knowhowMcpClient = {
    name: "knowhow-mcp-client",
    version: "1.0.0",
};
exports.knowhowConfig = {
    capabilities: {},
};
__exportStar(require("./McpServer"), exports);
__exportStar(require("./McpWebsocketTransport"), exports);
class McpService {
    connected = [];
    transports = [];
    clients = [];
    config = [];
    tools = [];
    mcpPrefix = "mcp";
    toolAliases = {};
    async createStdioClients(mcpServers = []) {
        if (this.clients.length) {
            return this.clients;
        }
        this.config = mcpServers;
        this.transports = mcpServers.map((mcp) => {
            const commandString = mcp.command
                ? `${mcp.command} ${mcp.args?.join(" ")}`
                : "";
            const logFormat = `${mcp.name}: Command: ${commandString}, URL: ${mcp.url}`;
            console.log("Creating transport for", logFormat);
            if (mcp.command) {
                const stdioParams = {
                    command: mcp.command,
                    args: mcp.args,
                    env: mcp.env
                        ? {
                            ...process.env,
                            ...mcp.env,
                        }
                        : undefined,
                };
                return new stdio_js_1.StdioClientTransport(stdioParams);
            }
            if (mcp?.params?.socket) {
                return new McpWebsocketTransport_1.MCPWebSocketTransport(mcp.params.socket);
            }
            if (mcp.url) {
                if (mcp.authorization_token_file) {
                    const token = fs_1.default.readFileSync(mcp.authorization_token_file, "utf-8");
                    mcp.authorization_token = token.trim();
                }
                return new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(mcp.url), {
                    requestInit: {
                        headers: {
                            "User-Agent": exports.knowhowMcpClient.name,
                            ...(mcp.authorization_token && {
                                Authorization: `Bearer ${mcp.authorization_token}`,
                            }),
                        },
                    },
                });
            }
        });
        this.clients = this.transports.map((transport) => {
            return new index_js_1.Client(exports.knowhowMcpClient, exports.knowhowConfig);
        });
        return this.clients;
    }
    setMcpPrefix(prefix) {
        this.mcpPrefix = prefix;
    }
    createClient(mcp, transport) {
        this.config.push(mcp);
        this.clients.push(new index_js_1.Client(exports.knowhowMcpClient, exports.knowhowConfig));
        this.transports.push(transport);
    }
    async connectToConfigured(tools) {
        const config = await (0, config_1.getConfig)();
        return this.connectTo(config.mcps, tools);
    }
    async connectTo(mcpServers = [], tools) {
        const clients = await this.createStdioClients(mcpServers);
        await this.connectAutoServers();
        if (tools) {
            await this.addTools(tools);
        }
    }
    async connectAutoServers() {
        const results = await Promise.allSettled(this.clients.map(async (client, index) => {
            const config = this.config[index];
            const shouldAutoConnect = config.autoConnect !== false;
            if (shouldAutoConnect && !this.connected[index]) {
                console.log(`Connecting to MCP server: ${config.name}`);
                try {
                    await new Promise(async (resolve, reject) => {
                        try {
                            await client.connect(this.transports[index]);
                            resolve();
                        }
                        catch (err) {
                            reject(err);
                        }
                    });
                }
                catch (error) {
                    console.error(`Failed to connect to MCP server '${config.name}':`, error.message || error);
                    return;
                }
                this.connected[index] = true;
            }
            else if (!shouldAutoConnect) {
                console.log(`Skipping auto-connect for MCP server: ${config.name} (autoConnect: false)`);
            }
        }));
        const successful = results.filter((r) => r.status === "fulfilled").length;
        const failed = this.clients.filter((_, i) => this.config[i]?.autoConnect !== false && !this.connected[i]).length;
        if (failed > 0) {
            console.warn(`Auto-connected ${successful}/${this.clients.length} MCP servers (${failed} failed)`);
        }
    }
    async connectSingle(serverName, timeout = 30000) {
        const index = this.getClientIndex(serverName);
        if (index < 0) {
            return {
                success: false,
                toolsAdded: [],
                error: `MCP server '${serverName}' not found in configuration`,
            };
        }
        if (this.connected[index]) {
            return {
                success: true,
                toolsAdded: [],
                error: `MCP server '${serverName}' already connected`,
            };
        }
        try {
            const client = this.clients[index];
            const transport = this.transports[index];
            await Promise.race([
                client.connect(transport),
                new Promise((_, reject) => setTimeout(() => reject(new Error("Connection timeout")), timeout)),
            ]);
            this.connected[index] = true;
            const clientTools = await client.listTools();
            const toolNames = [];
            for (const tool of clientTools.tools) {
                const transformed = this.toOpenAiTool(index, tool);
                if (transformed.function.name !== tool.name) {
                    this.toolAliases[transformed.function.name] = tool.name;
                }
                toolNames.push(transformed.function.name);
                if (!this.tools.find((t) => t.function.name === transformed.function.name)) {
                    this.tools.push(transformed);
                }
            }
            return {
                success: true,
                toolsAdded: toolNames,
            };
        }
        catch (error) {
            return {
                success: false,
                toolsAdded: [],
                error: error.message,
            };
        }
    }
    async disconnectSingle(serverName) {
        const index = this.getClientIndex(serverName);
        if (index < 0) {
            return {
                success: false,
                toolsRemoved: [],
                error: `MCP server '${serverName}' not found`,
            };
        }
        if (!this.connected[index]) {
            return {
                success: true,
                toolsRemoved: [],
                error: `MCP server '${serverName}' not connected`,
            };
        }
        try {
            const toolsToRemove = this.tools
                .filter((t) => this.getToolClientIndex(t.function.name) === index)
                .map((t) => t.function.name);
            await this.transports[index]?.close();
            this.connected[index] = false;
            this.tools = this.tools.filter((t) => this.getToolClientIndex(t.function.name) !== index);
            return {
                success: true,
                toolsRemoved: toolsToRemove,
            };
        }
        catch (error) {
            return {
                success: false,
                toolsRemoved: [],
                error: error.message,
            };
        }
    }
    getAvailableServers() {
        return this.config.map((config, index) => {
            const toolCount = this.connected[index]
                ? this.tools.filter((t) => this.getToolClientIndex(t.function.name) === index).length
                : 0;
            return {
                name: config.name,
                connected: this.connected[index] || false,
                autoConnect: config.autoConnect !== false,
                toolCount,
            };
        });
    }
    async addTools(tools) {
        tools.addTools(await this.getTools());
        tools.addFunctions(await this.getToolMap());
    }
    async copyFrom(mcp) {
        this.clients.push(...mcp.clients);
        this.transports.push(...mcp.transports);
        this.config.push(...mcp.config);
        this.connected.push(...mcp.connected);
    }
    async closeTransports() {
        await Promise.all(this.transports.map(async (transport, index) => {
            this.connected[index] = false;
            return transport && transport.close();
        }));
        this.connected = [];
    }
    async closeClients() {
        await Promise.all(this.clients.map((client) => {
            return client.close();
        }));
        this.clients = [];
        this.connected = [];
    }
    async closeAll() {
        await this.closeTransports();
        await this.closeClients();
    }
    getClientIndex(clientName) {
        const index = this.config.findIndex((mcp) => mcp.name === clientName);
        return index;
    }
    parseToolName(wrappedName) {
        return this.toolAliases[wrappedName] || wrappedName;
    }
    getToolClientIndex(toolName) {
        if (this.clients.length <= 1) {
            return 0;
        }
        const split = toolName.split("_");
        if (split.length < 2) {
            return -1;
        }
        const index = Number(split[1]);
        return index;
    }
    getToolClient(toolName) {
        const index = this.getToolClientIndex(toolName);
        if (index < 0) {
            throw new Error(`Invalid tool name ${toolName}`);
        }
        return this.clients[index];
    }
    getFunction(toolName, timeout) {
        const client = this.getToolClient(toolName);
        if (!this.toolAliases[toolName] &&
            !toolName.startsWith(this.mcpPrefix) &&
            this.clients.length === 1) {
            const wrappedName = this.getWrappedFunctionName(toolName, 0);
            toolName = this.toolAliases[wrappedName] ? wrappedName : toolName;
        }
        const realName = this.parseToolName(toolName);
        return async (args) => {
            console.log("Calling tool via mcp client", realName, "with args", args);
            const tool = await client.callTool({
                name: realName,
                arguments: args,
            }, types_js_1.CallToolResultSchema, {
                timeout: timeout || 10 * 60 * 1000,
                maxTotalTimeout: timeout || 10 * 60 * 1000,
            });
            return tool;
        };
    }
    async callFunction(toolName, args = {}) {
        try {
            const fn = this.getFunction(toolName);
            const result = await fn(args);
            if (result.content && Array.isArray(result.content)) {
                const textContent = result.content.find((c) => c.type === "text");
                if (textContent && textContent.text) {
                    const parsedResult = JSON.parse(textContent.text);
                    return parsedResult;
                }
            }
            throw new Error(`Invalid response format from MCP service for tool ${toolName}`);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to call MCP function ${toolName}: ${errorMessage}`);
        }
    }
    async getFunctions() {
        const tools = await this.getTools();
        return tools.map((tool) => {
            return this.getFunction(tool.function.name);
        });
    }
    async getToolMap() {
        const tools = await this.getTools();
        return tools.reduce((acc, tool) => {
            acc[tool.function.name] = this.getFunction(tool.function.name);
            return acc;
        }, {});
    }
    async connectAll() {
        const results = await Promise.allSettled(this.clients.map(async (client, index) => {
            const config = this.config[index];
            if (this.connected[index]) {
                return;
            }
            try {
                await client.connect(this.transports[index]);
            }
            catch (error) {
                console.error(`Failed to connect to MCP server '${config?.name || `index ${index}`}':`, error.message || error);
                throw error;
            }
            this.connected[index] = true;
        }));
        const successful = results.filter((r) => r.status === "fulfilled").length;
        const failed = results.filter((r) => r.status === "rejected").length;
        if (failed > 0) {
            console.warn(`Connected ${successful}/${this.clients.length} MCP servers (${failed} failed)`);
        }
    }
    async getClient() {
        if (this.clients.length) {
            return this.clients;
        }
        this.clients = await this.createStdioClients(this.config);
        return this.clients;
    }
    async getTools() {
        if (this.tools.length) {
            return this.tools;
        }
        const tools = [];
        for (let i = 0; i < this.config.length; i++) {
            const config = this.config[i];
            const client = this.clients[i];
            if (!this.connected[i]) {
                continue;
            }
            const clientTools = await client.listTools();
            for (const tool of clientTools.tools) {
                const transformed = this.toOpenAiTool(i, tool);
                if (transformed.function.name !== tool.name) {
                    this.toolAliases[transformed.function.name] = tool.name;
                }
                tools.push(transformed);
            }
        }
        this.tools = tools;
        return tools;
    }
    getToolPrefix(index = 0) {
        const mcpName = this.config[index]?.name
            ?.toLowerCase()
            ?.replaceAll(" ", "_");
        const prefix = mcpName
            ? `${this.mcpPrefix}_${index}_${mcpName}`
            : `${this.mcpPrefix}_${index}`;
        return prefix;
    }
    getWrappedFunctionName(toolName, index = 0) {
        const prefix = this.getToolPrefix(index);
        return `${prefix}_${toolName}`;
    }
    toOpenAiTool(index, tool) {
        const name = this.getWrappedFunctionName(tool.name, index);
        const transformed = {
            type: "function",
            function: {
                name,
                description: tool.description,
                parameters: {
                    type: "object",
                    positional: Boolean(tool.inputSchema.positional),
                    properties: tool.inputSchema.properties,
                    required: tool.inputSchema.required,
                },
            },
        };
        return transformed;
    }
}
exports.McpService = McpService;
//# sourceMappingURL=Mcp.js.map