"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var McpService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const core_entities_1 = require("../core-entities");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
let McpService = McpService_1 = class McpService {
    constructor(mcpConfigRepository, mcpToolRepository) {
        this.mcpConfigRepository = mcpConfigRepository;
        this.mcpToolRepository = mcpToolRepository;
        this.logger = new common_1.Logger(McpService_1.name);
        this.activeClients = new Map();
    }
    async onModuleInit() {
        this.logger.log('Initializing MCP connections...');
        const configs = await this.mcpConfigRepository.find({
            where: { is_active: true },
        });
        if (configs.length === 0) {
            this.logger.warn('No active MCP configurations found. MCP tools will be unavailable.');
            return;
        }
        for (const config of configs) {
            await this.connectToServer(config);
        }
    }
    async onModuleDestroy() {
        this.logger.log('Closing all active MCP connections...');
        await Promise.all(Array.from(this.activeClients.keys()).map((serverName) => this.disconnectFromServer(serverName)));
    }
    async connectToServer(config) {
        const { server_name, command, args, env } = config;
        if (this.activeClients.has(server_name)) {
            this.logger.log(`Already connected to ${server_name}. Skipping.`);
            return;
        }
        this.logger.log(`Attempting to connect to MCP server: ${server_name}`);
        try {
            const transport = new stdio_js_1.StdioClientTransport({ command, args, env });
            const client = new index_js_1.Client({
                name: `repoburg-backend-${server_name}`,
                version: '1.0.0',
            });
            client.connect(transport);
            const toolsResult = await client.listTools();
            if (!toolsResult || !toolsResult.tools) {
                throw new Error('Invalid or empty tool list received from server.');
            }
            this.logger.log(`Connected to ${server_name}. Discovered ${toolsResult.tools.length} tools.`);
            this.activeClients.set(server_name, client);
            await this.syncToolsToDb(server_name, toolsResult.tools);
        }
        catch (e) {
            this.logger.error(`Failed to connect to MCP server "${server_name}": ${e.message}`);
        }
    }
    async disconnectFromServer(serverName) {
        const client = this.activeClients.get(serverName);
        if (client) {
            try {
                await client.close();
                this.activeClients.delete(serverName);
                this.logger.log(`Disconnected from ${serverName}`);
            }
            catch (e) {
                this.logger.error(`Error disconnecting from ${serverName}: ${e.message}`);
            }
        }
    }
    async syncToolsToDb(serverName, tools) {
        this.logger.log(`Syncing tools for server: ${serverName}`);
        const validTools = tools.filter((tool) => !!tool.name);
        const existingTools = await this.mcpToolRepository.find({
            where: { server_name: serverName },
        });
        const existingToolsMap = new Map(existingTools.map((t) => [t.tool_name, t]));
        const toolsToSave = [];
        for (const sdkTool of validTools) {
            const existingTool = existingToolsMap.get(sdkTool.name);
            if (existingTool) {
                existingTool.description = sdkTool.description || '';
                existingTool.input_schema = sdkTool.inputSchema || {};
                toolsToSave.push(existingTool);
            }
            else {
                toolsToSave.push(this.mcpToolRepository.create({
                    server_name: serverName,
                    tool_name: sdkTool.name,
                    description: sdkTool.description || '',
                    input_schema: sdkTool.inputSchema || {},
                }));
            }
        }
        await this.mcpToolRepository.save(toolsToSave);
        const newToolNames = validTools.map((t) => t.name);
        const toolsToDelete = existingTools.filter((t) => !newToolNames.includes(t.tool_name));
        if (toolsToDelete.length > 0) {
            await this.mcpToolRepository.remove(toolsToDelete);
        }
        this.logger.log(`Successfully synced tools for ${serverName}. Saved: ${toolsToSave.length}, Deleted: ${toolsToDelete.length}`);
    }
    async createMcpConfig(configJson) {
        let parsedConfig;
        try {
            parsedConfig = JSON.parse(configJson);
        }
        catch (e) {
            throw new common_1.BadRequestException('Invalid JSON format.');
        }
        const serverName = Object.keys(parsedConfig)[0];
        if (!serverName) {
            throw new common_1.BadRequestException('JSON must have a root key as the server name.');
        }
        const configData = parsedConfig[serverName];
        if (!configData || !configData.command || !configData.args) {
            throw new common_1.BadRequestException('Configuration must include "command" and "args" properties.');
        }
        const existingConfig = await this.mcpConfigRepository.findOneBy({
            server_name: serverName,
        });
        if (existingConfig) {
            throw new common_1.BadRequestException(`A configuration for "${serverName}" already exists.`);
        }
        const newConfig = this.mcpConfigRepository.create({
            server_name: serverName,
            command: configData.command,
            args: configData.args,
            env: configData.env || {},
            is_active: true,
        });
        const savedConfig = await this.mcpConfigRepository.save(newConfig);
        await this.connectToServer(savedConfig);
        return savedConfig;
    }
    async updateMcpConfigStatus(serverName, isActive) {
        const config = await this.mcpConfigRepository.findOneBy({
            server_name: serverName,
        });
        if (!config) {
            throw new common_1.NotFoundException(`MCP configuration for "${serverName}" not found.`);
        }
        config.is_active = isActive;
        await this.mcpConfigRepository.save(config);
        if (isActive) {
            await this.connectToServer(config);
        }
        else {
            await this.disconnectFromServer(serverName);
        }
        return config;
    }
    async deleteMcpConfig(serverName) {
        const config = await this.mcpConfigRepository.findOneBy({
            server_name: serverName,
        });
        if (!config) {
            throw new common_1.NotFoundException(`MCP configuration for "${serverName}" not found.`);
        }
        await this.disconnectFromServer(serverName);
        await this.mcpToolRepository.delete({ server_name: serverName });
        await this.mcpConfigRepository.remove(config);
    }
    async batchUpdateToolStatus(toolIds, isActive) {
        if (toolIds.length === 0) {
            return { affected: 0 };
        }
        const result = await this.mcpToolRepository.update({ id: (0, typeorm_2.In)(toolIds) }, { is_active: isActive });
        return { affected: result.affected || 0 };
    }
    async getMcpServers() {
        const configs = await this.mcpConfigRepository.find({
            order: { server_name: 'ASC' },
        });
        const tools = await this.mcpToolRepository.find({
            order: { tool_name: 'ASC' },
        });
        const toolsByServer = new Map();
        for (const tool of tools) {
            if (!toolsByServer.has(tool.server_name)) {
                toolsByServer.set(tool.server_name, []);
            }
            toolsByServer.get(tool.server_name).push({
                id: tool.id,
                tool_name: tool.tool_name,
                description: tool.description,
                input_schema: tool.input_schema,
                is_active: tool.is_active,
            });
        }
        return configs.map((config) => ({
            server_name: config.server_name,
            description: `Tools available from the ${config.server_name} server.`,
            is_active: config.is_active,
            tools: toolsByServer.get(config.server_name) || [],
        }));
    }
    async executeMcpTool(serverName, toolName, args) {
        this.logger.log(`Executing MCP tool: ${serverName}.${toolName} with args: ${JSON.stringify(args)}`);
        const client = this.activeClients.get(serverName);
        if (!client) {
            const errorMsg = `No active MCP connection found for server: ${serverName}`;
            this.logger.error(errorMsg);
            return { stdout: '', stderr: errorMsg };
        }
        try {
            const result = await client.callTool({ name: toolName, arguments: args });
            const stdout = JSON.stringify(result, null, 2);
            return { stdout, stderr: '' };
        }
        catch (e) {
            this.logger.error(`Error calling tool ${toolName} on ${serverName}: ${e.message}`);
            return { stdout: '', stderr: e.message };
        }
    }
    async getMcpToolsDefinition() {
        const servers = await this.getMcpServers();
        const activeServers = servers.filter((s) => s.is_active);
        if (activeServers.length === 0) {
            return 'No active MCP servers are currently connected.';
        }
        let definition = '';
        for (const server of activeServers) {
            const activeTools = server.tools.filter((t) => t.is_active);
            if (activeTools.length === 0)
                continue;
            definition += `### Server: ${server.server_name}\n`;
            definition += `Description: ${server.description}\n`;
            definition += 'Tools:\n';
            for (const tool of activeTools) {
                definition += `- **${tool.tool_name}**\n`;
                definition += `  - Description: ${tool.description}\n`;
                definition += `  - Input Schema: \`${JSON.stringify(tool.input_schema)}\`\n`;
            }
            definition += '\n';
        }
        return definition;
    }
    async getActiveMcpToolNames() {
        const activeConfigs = await this.mcpConfigRepository.find({
            where: { is_active: true },
        });
        if (activeConfigs.length === 0)
            return [];
        const activeTools = await this.mcpToolRepository.find({
            where: { is_active: true },
        });
        if (activeTools.length === 0)
            return [];
        const activeServerNames = new Set(activeConfigs.map((c) => c.server_name));
        return activeTools
            .filter((t) => activeServerNames.has(t.server_name))
            .map((t) => `${t.server_name}__${t.tool_name}`);
    }
    async getActiveMcpToolDefinitions() {
        const activeConfigs = await this.mcpConfigRepository.find({
            where: { is_active: true },
        });
        if (activeConfigs.length === 0)
            return [];
        const activeTools = await this.mcpToolRepository.find({
            where: { is_active: true },
        });
        if (activeTools.length === 0)
            return [];
        const activeServerNames = new Set(activeConfigs.map((c) => c.server_name));
        const definitions = [];
        for (const tool of activeTools) {
            if (!activeServerNames.has(tool.server_name))
                continue;
            let parameters = tool.input_schema;
            if (!parameters || typeof parameters !== 'object') {
                parameters = { type: 'object', properties: {} };
            }
            if (!parameters.type) {
                parameters = { type: 'object', ...parameters };
            }
            definitions.push({
                type: 'function',
                function: {
                    name: `${tool.server_name}__${tool.tool_name}`,
                    description: tool.description ||
                        `MCP tool: ${tool.server_name}.${tool.tool_name}`,
                    parameters,
                },
            });
        }
        this.logger.log(`Generated ${definitions.length} MCP tool definitions from ${activeServerNames.size} active servers.`);
        return definitions;
    }
    parseEnabledMcpTools(value) {
        if (!value) {
            return null;
        }
        if (value === 'all') {
            return 'all';
        }
        try {
            let parsed = JSON.parse(value);
            while (typeof parsed === 'string') {
                try {
                    parsed = JSON.parse(parsed);
                }
                catch {
                    return null;
                }
            }
            if (Array.isArray(parsed)) {
                return parsed;
            }
            return null;
        }
        catch (e) {
            return null;
        }
    }
    serializeEnabledMcpTools(value) {
        if (value === null || value === undefined) {
            return null;
        }
        if (value === 'all') {
            return 'all';
        }
        if (typeof value === 'string') {
            try {
                let parsed = JSON.parse(value);
                while (typeof parsed === 'string') {
                    try {
                        parsed = JSON.parse(parsed);
                    }
                    catch {
                        break;
                    }
                }
                if (Array.isArray(parsed)) {
                    return JSON.stringify(parsed);
                }
                if (parsed === 'all') {
                    return 'all';
                }
            }
            catch {
            }
        }
        return JSON.stringify(value);
    }
    resolveMcpToolNames(entries, allActiveMcpToolNames) {
        const result = [];
        for (const entry of entries) {
            for (const toolName of allActiveMcpToolNames) {
                if (toolName === entry || toolName.startsWith(entry + '__')) {
                    if (!result.includes(toolName)) {
                        result.push(toolName);
                    }
                }
            }
        }
        return result;
    }
};
exports.McpService = McpService;
exports.McpService = McpService = McpService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(core_entities_1.McpConfig)),
    __param(1, (0, typeorm_1.InjectRepository)(core_entities_1.McpTool)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository])
], McpService);
//# sourceMappingURL=mcp.service.js.map