import { env } from "../../../../env.js";
import runDbClient_default from "../../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../../logger.js";
import { setSpanWithError as setSpanWithError$1, tracer } from "../../utils/tracer.js";
import { agentSessionManager } from "../../session/AgentSession.js";
import { mergeHeadersWithoutOverrides } from "../../utils/merge-headers.js";
import { z } from "@hono/zod-openapi";
import { JsonTransformer, MCPServerType, MCPTransportType, McpClient, TRUSTED_WORK_APP_MCP_PATHS, configureComposioMCPServer, isGithubWorkAppTool, isSlackWorkAppTool, isTrustedWorkAppMcpUrl, resolveSlackUserContext } from "@inkeep/agents-core";
import { jsonSchema, tool } from "ai";

//#region src/domains/run/agents/services/AgentMcpManager.ts
const logger = getLogger$1("AgentMcpManager");
var AgentMcpManager = class AgentMcpManager {
	mcpClientCache = /* @__PURE__ */ new Map();
	mcpConnectionLocks = /* @__PURE__ */ new Map();
	constructor(config, executionContext, credentialStuffer, getConversationId$1, getStreamRequestId, getRelationshipIdForTool) {
		this.config = config;
		this.executionContext = executionContext;
		this.credentialStuffer = credentialStuffer;
		this.getConversationId = getConversationId$1;
		this.getStreamRequestId = getStreamRequestId;
		this.getRelationshipIdForTool = getRelationshipIdForTool;
	}
	async getToolSet(tool$1) {
		const forwardedHeadersHash = this.config.forwardedHeaders ? Object.keys(this.config.forwardedHeaders).sort().join(",") : "no-fwd";
		const cacheKey = `${this.config.tenantId}-${this.config.projectId}-${tool$1.id}-${tool$1.credentialReferenceId || "no-cred"}-${forwardedHeadersHash}`;
		const project = this.executionContext.project;
		const credentialReferenceId = tool$1.credentialReferenceId;
		const toolRelation = (project.agents[this.config.agentId]?.subAgents?.[this.config.id])?.canUse?.find((t) => t.toolId === tool$1.id);
		const agentToolRelationHeaders = toolRelation?.headers || void 0;
		const selectedTools = toolRelation?.toolSelection || void 0;
		const toolPolicies = toolRelation?.toolPolicies || {};
		const isUserScoped = tool$1.credentialScope === "user";
		const userId = this.config.userId;
		const conversationId = this.getConversationId();
		let serverConfig;
		let storeReference;
		if (this.credentialStuffer) {
			if (isUserScoped && userId) {
				const userCredRef = project.credentialReferences ? Object.values(project.credentialReferences).find((c) => c.toolId === tool$1.id && c.userId === userId) : void 0;
				if (userCredRef) storeReference = {
					credentialStoreId: userCredRef.credentialStoreId,
					retrievalParams: userCredRef.retrievalParams || {}
				};
				else logger.warn({
					toolId: tool$1.id,
					userId
				}, "User-scoped tool has no credential connected for this user");
			} else if (credentialReferenceId) {
				const credRef = project.credentialReferences?.[credentialReferenceId];
				if (!credRef) throw new Error(`Credential reference not found: ${credentialReferenceId}`);
				storeReference = {
					credentialStoreId: credRef.credentialStoreId,
					retrievalParams: credRef.retrievalParams || {}
				};
			}
			serverConfig = await this.credentialStuffer.buildMcpServerConfig({
				tenantId: this.config.tenantId,
				projectId: this.config.projectId,
				contextConfigId: this.config.contextConfigId || void 0,
				conversationId: conversationId || void 0
			}, this.convertToMCPToolConfig(tool$1, agentToolRelationHeaders), storeReference, selectedTools);
		} else {
			if (tool$1.config.type !== "mcp") throw new Error(`Cannot build server config for non-MCP tool: ${tool$1.id}`);
			serverConfig = {
				type: tool$1.config.mcp.transport?.type || MCPTransportType.streamableHttp,
				url: tool$1.config.mcp.server.url,
				activeTools: tool$1.config.mcp.activeTools,
				selectedTools,
				headers: agentToolRelationHeaders
			};
		}
		const urlString = typeof serverConfig.url === "string" ? serverConfig.url : serverConfig.url.toString();
		if (isGithubWorkAppTool(tool$1) && isTrustedWorkAppMcpUrl(urlString, TRUSTED_WORK_APP_MCP_PATHS.github, env.INKEEP_AGENTS_API_URL)) serverConfig.headers = {
			...serverConfig.headers,
			"x-inkeep-tool-id": tool$1.id,
			"x-inkeep-tenant-id": this.config.tenantId,
			"x-inkeep-project-id": this.config.projectId,
			Authorization: `Bearer ${env.GITHUB_MCP_API_KEY}`
		};
		if (isSlackWorkAppTool(tool$1) && isTrustedWorkAppMcpUrl(urlString, TRUSTED_WORK_APP_MCP_PATHS.slack, env.INKEEP_AGENTS_API_URL)) serverConfig.headers = {
			...serverConfig.headers,
			"x-inkeep-tool-id": tool$1.id,
			"x-inkeep-tenant-id": this.config.tenantId,
			"x-inkeep-project-id": this.config.projectId,
			Authorization: `Bearer ${env.SLACK_MCP_API_KEY}`
		};
		const composioConnectedAccountId = storeReference?.retrievalParams?.connectedAccountId;
		if (composioConnectedAccountId) configureComposioMCPServer(serverConfig, this.config.tenantId, this.config.projectId, isUserScoped ? "user" : "project", userId, composioConnectedAccountId);
		else if (serverConfig.url?.toString().includes("composio.dev")) logger.warn({
			toolName: tool$1.name,
			toolId: tool$1.id
		}, "Composio tool missing connectedAccountId — skipping auth injection to prevent credential leakage");
		if (this.config.forwardedHeaders && Object.keys(this.config.forwardedHeaders).length > 0) serverConfig.headers = mergeHeadersWithoutOverrides(serverConfig.headers, this.config.forwardedHeaders);
		logger.info({
			toolName: tool$1.name,
			credentialReferenceId,
			transportType: serverConfig.type,
			headers: tool$1.headers,
			hasForwardedHeaders: !!this.config.forwardedHeaders
		}, "Built MCP server config with credentials");
		let client = this.mcpClientCache.get(cacheKey);
		if (client && !client.isConnected()) {
			this.mcpClientCache.delete(cacheKey);
			client = void 0;
		}
		if (!client) {
			let connectionPromise = this.mcpConnectionLocks.get(cacheKey);
			if (!connectionPromise) {
				connectionPromise = this.createMcpConnection(tool$1, serverConfig);
				this.mcpConnectionLocks.set(cacheKey, connectionPromise);
			}
			try {
				client = await connectionPromise;
				this.mcpClientCache.set(cacheKey, client);
			} catch (error) {
				this.mcpConnectionLocks.delete(cacheKey);
				logger.error({
					toolName: tool$1.name,
					subAgentId: this.config.id,
					cacheKey,
					error: AgentMcpManager.errMsg(error)
				}, "MCP connection failed");
				throw error;
			}
		}
		const originalTools = await client.tools();
		const tools = await this.applyToolOverrides(originalTools, tool$1);
		if (!tools || Object.keys(tools).length === 0) this.reportEmptyToolSet(tool$1, conversationId);
		let serverInstructions = tool$1.config.mcp.prompt ?? client.getInstructions();
		if (isSlackWorkAppTool(tool$1) && this.config.userId) try {
			const slackUserContext = await resolveSlackUserContext(runDbClient_default)(this.config.userId);
			if (slackUserContext) serverInstructions = (serverInstructions ? `${serverInstructions}\n` : "") + slackUserContext;
		} catch (error) {
			logger.warn({
				error,
				userId: this.config.userId
			}, "Failed to resolve Slack user context");
		}
		return {
			tools,
			toolPolicies,
			mcpServerId: tool$1.id,
			mcpServerName: tool$1.name,
			serverInstructions
		};
	}
	reportEmptyToolSet(mcpTool, conversationId) {
		const streamRequestId = this.getStreamRequestId();
		if (!streamRequestId) return;
		const serverUrl = mcpTool.config.type === "mcp" ? mcpTool.config.mcp.server.url : "unknown";
		tracer.startActiveSpan("ai.toolCall", { attributes: {
			"ai.toolCall.name": mcpTool.name,
			"ai.toolCall.args": JSON.stringify({ operation: "mcp_tool_discovery" }),
			"ai.toolCall.result": JSON.stringify({
				status: "no_tools_available",
				message: `MCP server has 0 effective tools. Double check the selected tools in your agent and the active tools in the MCP server configuration.`,
				serverUrl,
				originalToolName: mcpTool.name
			}),
			"ai.toolType": "mcp",
			"subAgent.name": this.config.name || "unknown",
			"subAgent.id": this.config.id || "unknown",
			"conversation.id": conversationId || "unknown",
			"agent.id": this.config.agentId || "unknown",
			"tenant.id": this.config.tenantId || "unknown",
			"project.id": this.config.projectId || "unknown"
		} }, (span) => {
			setSpanWithError$1(span, /* @__PURE__ */ new Error(`0 effective tools available for ${mcpTool.name}`));
			const relationshipId = this.getRelationshipIdForTool(mcpTool.name, "mcp");
			agentSessionManager.recordEvent(streamRequestId, "error", this.config.id, {
				message: `MCP server has 0 effective tools. Double check the selected tools in your graph and the active tools in the MCP server configuration.`,
				code: "no_tools_available",
				severity: "error",
				context: {
					toolName: mcpTool.name,
					serverUrl,
					operation: "mcp_tool_discovery"
				},
				relationshipId
			});
			span.end();
		});
	}
	convertToMCPToolConfig(tool$1, agentToolRelationHeaders) {
		if (tool$1.config.type !== "mcp") throw new Error(`Cannot convert non-MCP tool to MCP config: ${tool$1.id}`);
		return {
			id: tool$1.id,
			name: tool$1.name,
			description: tool$1.name,
			serverUrl: tool$1.config.mcp.server.url,
			activeTools: tool$1.config.mcp.activeTools,
			mcpType: tool$1.config.mcp.server.url.includes("api.nango.dev") ? MCPServerType.nango : MCPServerType.generic,
			transport: tool$1.config.mcp.transport,
			headers: {
				...tool$1.headers,
				...agentToolRelationHeaders
			},
			toolOverrides: tool$1.config.mcp.toolOverrides
		};
	}
	async createMcpConnection(tool$1, serverConfig) {
		const client = new McpClient({
			name: tool$1.name,
			server: serverConfig
		});
		try {
			await client.connect();
			return client;
		} catch (error) {
			if (error instanceof Error) {
				if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) throw new Error("Connection refused. Please check if the MCP server is running.");
				if (error.message.includes("404")) throw new Error("Error accessing endpoint (HTTP 404)");
				throw new Error(`MCP server connection failed: ${error.message}`);
			}
			throw error;
		}
	}
	static errMsg(error) {
		return error instanceof Error ? error.message : String(error);
	}
	buildOverriddenTool(toolName, toolDef, override, mcpToolName) {
		const rawSchema = toolDef.inputSchema;
		let inputSchema;
		try {
			inputSchema = override.schema ? z.fromJSONSchema(override.schema) : jsonSchema(rawSchema);
		} catch (schemaError) {
			logger.error({
				mcpToolName,
				toolName,
				schemaError: AgentMcpManager.errMsg(schemaError),
				overrideSchema: override.schema
			}, "Failed to convert override schema, using original");
			inputSchema = jsonSchema(rawSchema);
		}
		const finalName = override.displayName || toolName;
		return {
			finalName,
			definition: tool({
				description: override.description || toolDef.description || `Tool ${finalName}`,
				inputSchema,
				execute: async (simpleArgs) => {
					let complexArgs = simpleArgs;
					if (override.transformation) try {
						if (typeof override.transformation === "string") complexArgs = await JsonTransformer.transform(simpleArgs, override.transformation, { timeout: 1e4 });
						else if (typeof override.transformation === "object" && override.transformation !== null) complexArgs = await JsonTransformer.transformWithConfig(simpleArgs, { objectTransformation: override.transformation }, { timeout: 1e4 });
						else logger.warn({
							mcpToolName,
							toolName,
							transformationType: typeof override.transformation
						}, "Invalid transformation type, skipping transformation");
					} catch (transformError) {
						logger.error({
							mcpToolName,
							toolName,
							transformError: AgentMcpManager.errMsg(transformError),
							transformation: override.transformation
						}, "Failed to transform tool arguments, using original arguments");
						complexArgs = simpleArgs;
					}
					if (typeof toolDef.execute !== "function") throw new Error(`Original tool ${toolName} does not have a valid execute function`);
					try {
						return await toolDef.execute(complexArgs);
					} catch (executeError) {
						const msg = AgentMcpManager.errMsg(executeError);
						logger.error({
							mcpToolName,
							toolName,
							executeError: msg,
							complexArgs
						}, "Failed to execute original tool");
						throw new Error(`Tool execution failed for ${toolName}: ${msg}`);
					}
				}
			})
		};
	}
	async applyToolOverrides(originalTools, mcpTool) {
		const toolOverrides = mcpTool.config.mcp?.toolOverrides;
		if (!toolOverrides) return originalTools;
		if (!originalTools || typeof originalTools !== "object") {
			logger.warn({
				mcpToolName: mcpTool.name,
				originalToolsType: typeof originalTools
			}, "Invalid original tools structure, skipping overrides");
			return originalTools || {};
		}
		const availableToolNames = Object.keys(originalTools);
		const invalidOverrides = Object.keys(toolOverrides).filter((name) => !availableToolNames.includes(name));
		if (invalidOverrides.length > 0) logger.warn({
			mcpToolName: mcpTool.name,
			invalidOverrides,
			availableTools: availableToolNames
		}, "Tool override configured for non-existent tools");
		const processedTools = {};
		for (const [toolName, toolDef] of Object.entries(originalTools)) {
			if (!toolDef || typeof toolDef !== "object") {
				logger.warn({
					mcpToolName: mcpTool.name,
					toolName,
					toolDefType: typeof toolDef
				}, "Invalid tool definition structure, skipping tool");
				continue;
			}
			const override = toolOverrides[toolName];
			if (override && (override.schema || override.description || override.displayName)) try {
				const { finalName, definition } = this.buildOverriddenTool(toolName, toolDef, override, mcpTool.name);
				processedTools[finalName] = definition;
			} catch (error) {
				logger.error({
					mcpToolName: mcpTool.name,
					toolName,
					error: AgentMcpManager.errMsg(error),
					override
				}, "Failed to apply tool overrides, using original tool");
				processedTools[toolName] = toolDef;
			}
			else processedTools[toolName] = toolDef;
		}
		const processedToolNames = Object.keys(processedTools);
		logger.info({
			mcpToolName: mcpTool.name,
			originalToolCount: availableToolNames.length,
			processedToolCount: processedToolNames.length,
			processedTools: processedToolNames
		}, "Completed tool override application");
		return processedTools;
	}
	async cleanup() {
		const entries = Array.from(this.mcpClientCache.entries());
		if (entries.length > 0) {
			const results = await Promise.allSettled(entries.map(([, client]) => client.disconnect()));
			for (let i = 0; i < results.length; i++) {
				const result = results[i];
				if (result.status === "rejected") logger.warn({
					error: result.reason,
					clientKey: entries[i][0]
				}, "Failed to disconnect MCP client during cleanup");
			}
		}
		this.mcpClientCache.clear();
		this.mcpConnectionLocks.clear();
	}
};

//#endregion
export { AgentMcpManager };