/**
 * MCP Tool Adapter.
 *
 * Bridges MCP servers to the Tool Registry, handling:
 * - Connection lifecycle management
 * - Tool discovery and registration
 * - Schema conversion (JSON Schema to Zod)
 * - Tool execution delegation
 */
import { jsonSchemaToZod, createPermissiveSchema, } from "./json-schema-to-zod.js";
/**
 * Default tool name transformer.
 *
 * Creates prefixed names like "mcp_playwright_navigate"
 *
 * @param serverName - The MCP server name
 * @param toolName - The original tool name from the MCP server
 * @returns The prefixed tool name
 */
export const defaultToolNameTransform = (serverName, toolName) => `mcp_${serverName}_${toolName}`;
/**
 * Adapter that bridges MCP servers to the Tool Registry.
 *
 * Handles the complete lifecycle of MCP tool integration:
 * 1. Server connection management
 * 2. Tool discovery from MCP server
 * 3. Schema conversion (JSON Schema -> Zod)
 * 4. Tool registration with registry
 * 5. Execution delegation to MCP server
 * 6. Cleanup on disconnect
 *
 * @example
 * const adapter = new McpToolAdapter(registry, logger);
 *
 * // Register an embedded server
 * const client = new MCPClient(serverUrl, logger);
 * await client.connect();
 *
 * await adapter.addServer({
 *   name: "playwright",
 *   url: serverUrl,
 *   category: ToolCategory.GENERAL,
 *   defaultPermission: PermissionLevel.PROMPT
 * }, client);
 *
 * // Tools are now available in registry as "mcp:playwright:*"
 *
 * // Later, clean up
 * await adapter.removeServer("playwright");
 */
export class McpToolAdapter {
    registry;
    logger;
    servers = new Map();
    constructor(registry, logger) {
        this.registry = registry;
        this.logger = logger;
    }
    /**
     * Adds an MCP server and registers its tools.
     *
     * @param config - Server configuration
     * @param client - MCP client instance (should already be connected or will be connected)
     * @returns Names of registered tools
     * @throws Error if server name already registered
     */
    async addServer(config, client) {
        // Validate server name uniqueness
        if (this.servers.has(config.name)) {
            throw new Error(`MCP server "${config.name}" is already registered`);
        }
        // Initialize entry with connecting state
        const entry = {
            config,
            client,
            registeredTools: [],
            state: { status: "connecting" },
        };
        this.servers.set(config.name, entry);
        try {
            // Connect if not already connected
            if (!client.isConnected()) {
                await client.connect();
            }
            // Discover tools
            const toolDefinitions = client.getToolDefinitions();
            this.logger.info(`Discovered ${toolDefinitions.length} tools from MCP server "${config.name}"`);
            // Register each tool
            const registeredToolNames = [];
            for (const toolDef of toolDefinitions) {
                const tool = this.createTool(config, client, toolDef);
                try {
                    this.registry.registerTool(tool);
                    registeredToolNames.push(tool.name);
                    this.logger.debug(`Registered MCP tool: ${tool.name}`);
                }
                catch (error) {
                    const errorMsg = error instanceof Error ? error.message : String(error);
                    this.logger.warn(`Failed to register MCP tool "${tool.name}": ${errorMsg}`);
                }
            }
            // Update entry state
            entry.registeredTools = registeredToolNames;
            entry.state = {
                status: "connected",
                toolCount: registeredToolNames.length,
            };
            this.logger.info(`Successfully registered ${registeredToolNames.length} tools from MCP server "${config.name}"`);
            return registeredToolNames;
        }
        catch (error) {
            // Update state to error
            entry.state = {
                status: "error",
                error: error instanceof Error ? error : new Error(String(error)),
            };
            this.logger.error(`Failed to add MCP server "${config.name}"`, {
                error: {
                    kind: "McpServerError",
                    message: error instanceof Error ? error.message : String(error),
                    stack: error instanceof Error ? error.stack : undefined,
                },
            });
            throw error;
        }
    }
    /**
     * Removes an MCP server and unregisters its tools.
     *
     * Note: The ToolRegistry doesn't currently support unregistering tools,
     * so this method disconnects the client and clears the tracking state.
     * Tools will fail on execute after removal.
     *
     * @param serverName - Name of server to remove
     */
    async removeServer(serverName) {
        const entry = this.servers.get(serverName);
        if (!entry) {
            this.logger.warn(`MCP server "${serverName}" not found for removal`);
            return;
        }
        try {
            // Disconnect the client
            if (entry.client.isConnected()) {
                await entry.client.disconnect();
            }
        }
        catch (error) {
            const errorMsg = error instanceof Error ? error.message : String(error);
            this.logger.warn(`Error disconnecting MCP server "${serverName}": ${errorMsg}`);
        }
        // Note: We can't unregister tools from the registry currently.
        // The tools will remain registered but will fail when executed
        // since the client is disconnected.
        this.servers.delete(serverName);
        this.logger.info(`Removed MCP server "${serverName}" (${entry.registeredTools.length} tools)`);
    }
    /**
     * Gets the current state of a server.
     *
     * @param serverName - Server name
     * @returns Server state or undefined if not found
     */
    getServerState(serverName) {
        return this.servers.get(serverName)?.state;
    }
    /**
     * Gets all registered server names.
     *
     * @returns Array of server names
     */
    getServerNames() {
        return Array.from(this.servers.keys());
    }
    /**
     * Gets tool names registered for a server.
     *
     * @param serverName - Server name
     * @returns Array of tool names or empty array if server not found
     */
    getServerTools(serverName) {
        return this.servers.get(serverName)?.registeredTools ?? [];
    }
    /**
     * Checks if a server is registered.
     *
     * @param serverName - Server name
     * @returns True if server is registered
     */
    hasServer(serverName) {
        return this.servers.has(serverName);
    }
    /**
     * Removes all servers and cleans up.
     */
    async dispose() {
        const serverNames = this.getServerNames();
        for (const name of serverNames) {
            await this.removeServer(name);
        }
        this.logger.info("Disposed all MCP servers");
    }
    /**
     * Creates a Tool instance for an MCP tool.
     *
     * @param config - Server configuration
     * @param client - MCP client for execution
     * @param toolDef - Tool definition from MCP server
     * @returns Tool instance ready for registration
     */
    createTool(config, client, toolDef) {
        const toolName = (config.toolNameTransform ?? defaultToolNameTransform)(config.name, toolDef.name);
        // Get schema: override > converted > permissive fallback
        let inputSchema;
        const schemaOverride = config.schemaOverrides?.[toolDef.name];
        if (schemaOverride) {
            inputSchema = schemaOverride;
            this.logger.debug(`Using schema override for ${toolName}`);
        }
        else if (config.convertSchemas !== false && toolDef.inputSchema) {
            try {
                inputSchema = jsonSchemaToZod(toolDef.inputSchema);
                this.logger.debug(`Converted JSON Schema for ${toolName}`);
            }
            catch (error) {
                const errorMsg = error instanceof Error ? error.message : String(error);
                this.logger.warn(`Failed to convert schema for ${toolName}, using permissive fallback: ${errorMsg}`);
                inputSchema = createPermissiveSchema(toolDef.description);
            }
        }
        else {
            inputSchema = createPermissiveSchema(toolDef.description);
        }
        // Store original tool name for MCP call
        const originalToolName = toolDef.name;
        return {
            name: toolName,
            description: toolDef.description ?? toolDef.name,
            category: config.category,
            defaultPermission: config.defaultPermission,
            inputSchema,
            readOnly: true,
            skipOutputLimit: config.skipOutputLimit?.(originalToolName),
            toModelOutput: config.toModelOutput?.(originalToolName),
            execute: async (input) => {
                // Check if client is still connected
                if (!client.isConnected()) {
                    throw new Error(`MCP server "${config.name}" is not connected. ` +
                        `The server may have been removed or disconnected.`);
                }
                // Delegate execution to MCP server using original tool name
                return client.callTool(originalToolName, input);
            },
            getSummary: (input) => {
                // Generate a summary based on the MCP tool name and input
                return this.generateMcpToolSummary(originalToolName, input);
            },
        };
    }
    /**
     * Generates a human-readable summary for MCP tool execution.
     *
     * @param toolName - Original MCP tool name
     * @param input - Input parameters for the tool
     * @returns Separate summaries for input and output
     */
    generateMcpToolSummary(toolName, input) {
        const inputObj = input;
        // Browser/Playwright specific tool summaries
        switch (toolName) {
            case "browser_navigate":
                return {
                    input: `url: ${inputObj.url ?? "page"}`,
                    output: "Navigated",
                };
            case "browser_click":
                return {
                    input: `element: ${inputObj.element ?? inputObj.ref ?? "element"}`,
                    output: "Clicked",
                };
            case "browser_type":
                return {
                    input: `element: ${inputObj.element ?? inputObj.ref ?? "element"}`,
                    output: "Text entered",
                };
            case "browser_snapshot":
                return { output: "Page state captured" };
            case "browser_take_screenshot":
                return { output: "Image captured" };
            case "browser_wait_for":
                if (inputObj.text)
                    return {
                        input: `text: "${inputObj.text}"`,
                        output: "Text appeared",
                    };
                if (inputObj.textGone)
                    return {
                        input: `textGone: "${inputObj.textGone}"`,
                        output: "Text disappeared",
                    };
                if (inputObj.time)
                    return {
                        input: `time: ${inputObj.time}s`,
                        output: "Wait completed",
                    };
                return { output: "Waiting completed" };
            case "browser_hover":
                return {
                    input: `element: ${inputObj.element ?? inputObj.ref ?? "element"}`,
                    output: "Hovered",
                };
            case "browser_select_option":
                return {
                    input: `element: ${inputObj.element ?? inputObj.ref ?? "element"}`,
                    output: "Option selected",
                };
            case "browser_drag":
                return {
                    input: `from: ${inputObj.startElement ?? "element"}, to: ${inputObj.endElement ?? "target"}`,
                    output: "Dragged",
                };
            case "browser_evaluate":
                return { output: "Code executed" };
            case "browser_fill_form": {
                const fieldCount = Array.isArray(inputObj.fields)
                    ? inputObj.fields.length
                    : 0;
                return {
                    input: `${fieldCount} field${fieldCount === 1 ? "" : "s"}`,
                    output: "Form filled",
                };
            }
            case "browser_press_key":
                return {
                    input: `key: ${inputObj.key ?? "key"}`,
                    output: "Key pressed",
                };
            case "browser_tabs":
                return {
                    input: `action: ${inputObj.action ?? "list"}`,
                    output: "Tab action completed",
                };
            case "browser_navigate_back":
                return { output: "Went back" };
            case "browser_resize":
                return {
                    input: `${inputObj.width}x${inputObj.height}`,
                    output: "Resized",
                };
            case "browser_handle_dialog":
                return {
                    input: `accept: ${inputObj.accept}`,
                    output: inputObj.accept ? "Accepted" : "Dismissed",
                };
            case "browser_console_messages":
                return { output: "Messages retrieved" };
            case "browser_network_requests":
                return { output: "Requests retrieved" };
            default:
                // Generic fallback for unknown MCP tools
                return { output: "Executed" };
        }
    }
}
//# sourceMappingURL=mcp-tool-adapter.js.map