UNPKG

@langchain/mcp-adapters

Version:
169 lines (168 loc) 6.49 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadMcpTools = exports.ToolException = void 0; const tools_1 = require("@langchain/core/tools"); const debug_1 = __importDefault(require("debug")); // Replace direct initialization with lazy initialization let debugLog; function getDebugLog() { if (!debugLog) { debugLog = (0, debug_1.default)("@langchain/mcp-adapters:tools"); } return debugLog; } async function _embeddedResourceToArtifact(resource, client) { if (!resource.blob && !resource.text && resource.uri) { const response = await client.readResource({ uri: resource.resource.uri, }); return response.contents.map((content) => ({ type: "resource", resource: { ...content, }, })); } return [resource]; } /** * Custom error class for tool exceptions */ class ToolException extends Error { constructor(message) { super(message); this.name = "ToolException"; } } exports.ToolException = ToolException; /** * Process the result from calling an MCP tool. * Extracts text content and non-text content for better agent compatibility. * * @param result - The result from the MCP tool call * @returns A tuple of [textContent, nonTextContent] */ async function _convertCallToolResult(serverName, toolName, result, client) { if (!result) { throw new ToolException(`MCP tool '${toolName}' on server '${serverName}' returned an invalid result - tool call response was undefined`); } if (!Array.isArray(result.content)) { throw new ToolException(`MCP tool '${toolName}' on server '${serverName}' returned an invalid result - expected an array of content, but was ${typeof result.content}`); } if (result.isError) { throw new ToolException(`MCP tool '${toolName}' on server '${serverName}' returned an error: ${result.content .map((content) => content.text) .join("\n")}`); } const mcpTextAndImageContent = result.content.filter((content) => content.type === "text" || content.type === "image").map((content) => { switch (content.type) { case "text": return { type: "text", text: content.text, }; case "image": return { type: "image_url", image_url: { url: `data:${content.mimeType};base64,${content.data}`, }, }; default: throw new ToolException(`MCP tool '${toolName}' on server '${serverName}' returned an invalid result - expected a text or image content, but was ${ // eslint-disable-next-line @typescript-eslint/no-explicit-any content.type}`); } }); // Create the text content output const artifacts = (await Promise.all(result.content.filter((content) => content.type === "resource").map((content) => _embeddedResourceToArtifact(content, client)))).flat(); if (mcpTextAndImageContent.length === 1 && mcpTextAndImageContent[0].type === "text") { return [mcpTextAndImageContent[0].text, artifacts]; } return [mcpTextAndImageContent, artifacts]; } /** * Call an MCP tool. * * Use this with `.bind` to capture the fist three arguments, then pass to the constructor of DynamicStructuredTool. * * @internal * * @param client - The MCP client * @param toolName - The name of the tool (forwarded to the client) * @param args - The arguments to pass to the tool * @returns A tuple of [textContent, nonTextContent] */ async function _callTool(serverName, toolName, client, args) { let result; try { getDebugLog()(`INFO: Calling tool ${toolName}(${JSON.stringify(args)})`); result = (await client.callTool({ name: toolName, arguments: args, })); } catch (error) { getDebugLog()(`Error calling tool ${toolName}: ${String(error)}`); // eslint-disable-next-line no-instanceof/no-instanceof if (error instanceof ToolException) { throw error; } throw new ToolException(`Error calling tool ${toolName}: ${String(error)}`); } return _convertCallToolResult(serverName, toolName, result, client); } const defaultLoadMcpToolsOptions = { throwOnLoadError: true, prefixToolNameWithServerName: false, additionalToolNamePrefix: "", }; /** * Load all tools from an MCP client. * * @param serverName - The name of the server to load tools from * @param client - The MCP client * @returns A list of LangChain tools */ async function loadMcpTools(serverName, client, options) { const { throwOnLoadError, prefixToolNameWithServerName, additionalToolNamePrefix, } = { ...defaultLoadMcpToolsOptions, ...(options ?? {}), }; // Get tools in a single operation const toolsResponse = await client.listTools(); getDebugLog()(`INFO: Found ${toolsResponse.tools?.length || 0} MCP tools`); const initialPrefix = additionalToolNamePrefix ? `${additionalToolNamePrefix}__` : ""; const serverPrefix = prefixToolNameWithServerName ? `${serverName}__` : ""; const toolNamePrefix = `${initialPrefix}${serverPrefix}`; // Filter out tools without names and convert in a single map operation return (await Promise.all((toolsResponse.tools || []) .filter((tool) => !!tool.name) .map(async (tool) => { try { const dst = new tools_1.DynamicStructuredTool({ name: `${toolNamePrefix}${tool.name}`, description: tool.description || "", schema: tool.inputSchema, responseFormat: "content_and_artifact", func: _callTool.bind(null, serverName, tool.name, client), }); getDebugLog()(`INFO: Successfully loaded tool: ${dst.name}`); return dst; } catch (error) { getDebugLog()(`ERROR: Failed to load tool "${tool.name}":`, error); if (throwOnLoadError) { throw error; } return null; } }))).filter(Boolean); } exports.loadMcpTools = loadMcpTools;