@langchain/mcp-adapters
Version:
LangChain.js adapters for Model Context Protocol (MCP)
169 lines (168 loc) • 6.49 kB
JavaScript
;
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;