工具 (Tools)
将 MCP 工具加载到 LangChain 智能体中,控制其执行并处理服务器结果与请求。
MCPAdapter 搭建了 MCP 服务器与 LangChain 智能体之间的桥梁:它能够发现服务器所宣告的工具,并将它们适配为标准的 LangChain 工具。你可以像使用任何其他 LangChain 工具一样,将 listTools() 返回的工具传递给 createAgent。
本页面介绍了该桥接特有的内容:识别 MCP 工具、控制其执行、处理其输出,以及在调用期间服务器需要输入时进行响应。有关可运行的工具发现与智能体示例,请参阅 MCP 快速入门。
在智能体中使用 MCP 工具
使用 MCPAdapter.listTools 发现服务器的目录,然后将返回的工具提供给 createAgent。从智能体的角度来看,它们的行为与 LangChain 工具无异:模型选择工具,LangChain 调用该工具,生成的 ToolMessage 返回给模型。
import { MCPAdapter } from "@langchain/mcp-adapters";
import { createAgent } from "langchain";
async function main(server: string) {
const adapter = new MCPAdapter({
servers: { weather: { url: server } },
});
try {
// 发现服务器的工具,然后像使用任何其他 LangChain 工具一样将它们交给智能体。
// 在智能体调用其工具期间,请保持适配器处于开启状态。
const tools = await adapter.listTools();
const agent = createAgent({ model: "claude-sonnet-5", tools });
await agent.invoke({
messages: [{ role: "user", content: "What is the forecast for Oslo?" }],
});
} finally {
await adapter.close();
}
}打开此示例的公开 LangSmith 运行记录。
在智能体可以调用其工具期间保持适配器处于开启状态,包括在恢复被中断的运行(run)时。在智能体使用完毕后,调用 await adapter.close()。
有关定义、绑定和使用 LangChain 工具的通用指南,请参阅工具。有关多个 MCP 服务器及其带命名空间的工具目录,请参阅连接。
处理工具输出
MCP 工具结果会转换为 ToolMessage 对象,其中包含模型可读取的内容、用于应用程序数据的工件(artifact),以及指示成功或失败的状态。
多模态内容
适配器将模型可见的 MCP 内容转换为 LangChain 内容块。例如,返回带有屏幕截图的 MCP 图像块的工具,会以 image 块的形式到达模型。
import { MCPAdapter } from "@langchain/mcp-adapters";
import { createAgent } from "langchain";
async function accessMultimodalToolContent(serverUrl: string) {
const adapter = new MCPAdapter({
servers: { browser: { url: serverUrl } },
});
try {
const tools = await adapter.listTools();
const agent = createAgent({ model: "claude-sonnet-5", tools });
const result = await agent.invoke({
messages: [
{ role: "user", content: "Take a screenshot of the current page" },
],
});
// MCP 结果以 LangChain 内容块的形式到达。图像内容
// 会与 `text` 一起转换为标准化的 `image` 块。
for (const message of result.messages) {
if (message.type === "tool") {
for (const block of message.contentBlocks) {
if (block.type === "text") {
console.log(`Text: ${block.text}`);
} else if (block.type === "image") {
console.log(`Image MIME type: ${block.mimeType}`);
console.log(`Image data: ${String(block.data).slice(0, 50)}...`);
}
}
}
}
} finally {
await adapter.close();
}
}打开此示例的公开 LangSmith 运行记录。
结构化内容
当工具返回结构化内容时,适配器会将其作为工件(artifact)附加到 ToolMessage 上,而不是折叠到模型可见的文本中。运行智能体,然后从结果中的 ToolMessage 实例中读取 artifact:
import { MCPAdapter } from "@langchain/mcp-adapters";
import { createAgent, ToolMessage } from "langchain";
async function readStructuredContent(serverUrl: string) {
const adapter = new MCPAdapter({
servers: { crm: { url: serverUrl } },
});
try {
const tools = await adapter.listTools();
const agent = createAgent({ model: "claude-sonnet-5", tools });
const result = await agent.invoke({
messages: [{ role: "user", content: "Look up user 42." }],
});
// 适配器将 MCP 结果条目存储在 `artifact` 数组中。
// 找到 `mcp_structured_content` 条目,然后读取其 `data` 字段。
for (const message of result.messages) {
if (ToolMessage.isInstance(message) && Array.isArray(message.artifact)) {
const structured = message.artifact.find(
(entry) => entry.type === "mcp_structured_content",
);
if (structured) {
console.log(`Structured content: ${JSON.stringify(structured.data)}`);
}
}
}
} finally {
await adapter.close();
}
}打开此示例的公开 LangSmith 运行记录。
artifact 是一个 MCP 结果条目数组。结构化内容出现在 type: "mcp_structured_content" 的条目中,其 data 字段保存工具结果的 structuredContent。
错误处理
当服务器在智能体工具调用期间返回 isError: true 时,适配器会返回一个带有 status: "error" 和服务器自身消息的 ToolMessage。模型可以读取该错误并重新尝试。
适配器会抛出传输故障。当工具在 createAgent 内部运行时,智能体的默认错误处理也会将这些故障转换为错误工具消息。仅凭消息的 status 无法区分导致错误的原因:
import { MCPAdapter } from "@langchain/mcp-adapters";
import { createAgent, ToolMessage } from "langchain";
async function divideByZero(serverUrl: string) {
const adapter = new MCPAdapter({
servers: { calculator: { url: serverUrl } },
});
try {
const tools = await adapter.listTools();
const agent = createAgent({ model: "claude-sonnet-5", tools });
const result = await agent.invoke({
messages: [{ role: "user", content: "What is 10 divided by 0?" }],
});
// 服务器错误(isError: true)会作为失败的 ToolMessage 传递给模型,
// 因此智能体可以读取服务器自身的消息并进行恢复。默认情况下,
// createAgent 也会将传输失败转换为错误工具消息。
for (const message of result.messages) {
if (ToolMessage.isInstance(message) && message.status === "error") {
console.log(`Tool error: ${message.text}`);
}
}
} finally {
await adapter.close();
}
}打开此示例的公开 LangSmith 运行记录。
如果你直接使用普通参数调用适配后的工具,返回 isError: true 的服务器结果会抛出 ToolException,并在 error.result 中包含 MCP 结果。
工具元数据
适配器将 MCP 工具注解存储在 tool.metadata.annotations 中:
tool.metadata;
// {
// annotations: {
// destructiveHint: true,
// readOnlyHint: false,
// },
// }annotations 保存了服务器的 MCP 工具注解,例如 readOnlyHint 和 destructiveHint。服务器可能会省略它们,因此该字段可能为 undefined。
防御性地读取可选元数据,以便缺失字段时返回默认值而不是失败:
本示例以及人在回路示例从 @modelcontextprotocol/client 导入类型;请将其添加为直接依赖项。
import type { DynamicStructuredTool } from "@langchain/core/tools";
import type { ToolAnnotations } from "@modelcontextprotocol/client";
/** 从适配器的工具元数据中读取 MCP destructive 提示。 */
function isDestructive(tool: DynamicStructuredTool): boolean {
// 使用可选链和默认值,以便缺少任何字段的工具返回 false 而不是抛出异常。
const annotations = tool.metadata?.annotations as ToolAnnotations | undefined;
return annotations?.destructiveHint ?? false;
}人在回路 (Human-in-the-loop)
通过读取注解,你可以根据服务器对工具的声明来对工具进行门控,而不是硬编码工具名称。MCP 注解对工具进行分类,而 LangChain 的人在回路中间件负责执行审批策略。
在工具发现期间从元数据中读取一次 destructive 提示。然后为人在回路配置提供一个 when 谓词函数,该函数接收每个待处理的工具调用并返回该调用是否需要审批:
import type { DynamicStructuredTool } from "@langchain/core/tools";
import { MemorySaver } from "@langchain/langgraph";
import { MCPAdapter } from "@langchain/mcp-adapters";
import type { ToolAnnotations } from "@modelcontextprotocol/client";
import {
createAgent,
humanInTheLoopMiddleware,
type InterruptOnConfig,
type WhenPredicate,
} from "langchain";
function isDestructive(tool: DynamicStructuredTool): boolean {
const annotations = tool.metadata?.annotations as ToolAnnotations | undefined;
return annotations?.destructiveHint ?? false;
}
async function gateDestructiveTools(serverUrl: string) {
const adapter = new MCPAdapter({
servers: { crm: { url: serverUrl } },
});
const tools = await adapter.listTools();
// 读取一次提示,然后将相同的谓词应用于每次工具调用。
const destructive = new Set(
tools.filter(isDestructive).map((tool) => tool.name),
);
const needsApproval: WhenPredicate = (request) =>
destructive.has(request.toolCall.name);
const gate: InterruptOnConfig = {
allowedDecisions: ["approve", "reject"],
when: needsApproval,
};
const interruptOn = Object.fromEntries(
tools.map((tool) => [tool.name, gate]),
);
const agent = createAgent({
model: "claude-sonnet-5",
tools,
middleware: [humanInTheLoopMiddleware({ interruptOn })],
checkpointer: new MemorySaver(),
});
return { agent, adapter };
}WARNING
interruptOn 的键必须是适配器的工具名称,其中包含服务器前缀(crm_delete_file)。未带前缀的键永远无法匹配,因此工具将在未经批准的情况下运行。
当智能体调用受谓词门控的工具时,运行会暂停。批准该调用以运行它,或拒绝它以跳过该工具并通知模型:
import { Command } from "@langchain/langgraph";
// 批准待处理的破坏性调用并恢复执行。
const resumed = await agent.invoke(
new Command({ resume: { decisions: [{ type: "approve" }] } }),
config
);该谓词还可以检查调用的参数。这样可以在输入安全时让工具自由运行,仅在存在风险时暂停,例如针对受保护路径的 delete_file 调用。
通过 request.toolCall.args 访问这些参数。
将元数据分类与参数检查相结合,仅在工具的类型和输入确实需要审批时才对其进行门控。有关完整的审批工作流程,请参阅人在回路。
工具执行期间的服务器请求
大多数工具在执行完成时不会在调用中途向客户端请求任何内容。当服务器需要输入时,MCPAdapter 会将启发式输入 (elicitation) 作为 LangGraph 的 interrupt 呈现出来。
启发式输入 (Elicitation)
启发式输入 (Elicitation) 允许 MCP 服务器在工具调用期间请求输入。适配器使用 LangGraph 中断暂停运行。你的应用程序向用户展示该请求,并使用用户的回答恢复运行。
为智能体附加一个检查点保存器 (checkpointer),并在调用和恢复它时使用相同的 thread_id。
本示例假定预订工具请求一个日期并暂停一次。请将示例日期替换为用户的回答。
中断驱动的启发式输入需要现代 MCP 服务器并默认启用。在服务器上设置 elicitation: false 可以选择退出。对于旧版服务器,请配置带有 mode: "legacy" 的 onElicitation 处理程序。如果没有检查点保存器,请求输入的工具将失败并返回错误的 ToolMessage,而不是暂停。
import { Command, MemorySaver } from "@langchain/langgraph";
import {
MCPAdapter,
createMCPElicitationResume,
type MCPElicitationInterrupt,
} from "@langchain/mcp-adapters";
import { createAgent } from "langchain";
async function bookWithElicitation(serverUrl: string) {
// 当服务器在调用过程中需要输入时,适配器会将问题作为 LangGraph 中断暴露出来。
const adapter = new MCPAdapter({
servers: { booking: { url: serverUrl } },
});
try {
const tools = await adapter.listTools();
// 检查点保存器会保存被中断的运行,以便其可以恢复。
const agent = createAgent({
model: "claude-sonnet-5",
tools,
checkpointer: new MemorySaver(),
});
const config = { configurable: { thread_id: "booking-1" } };
const paused = await agent.invoke(
{ messages: [{ role: "user", content: "Book a table for 4." }] },
config,
);
const [pending] = paused.__interrupt__!;
const question = pending.value as MCPElicitationInterrupt;
const [key] = Object.keys(question.requests);
// 答复以服务器自身的请求键(key)为索引。
// 使用 `decline` 或 `cancel` 可以拒绝该请求。
const answer = {
action: "accept" as const,
content: { date: "2026-09-14" },
};
// 将答复定向到请求它的中断。
const resume = createMCPElicitationResume(pending, { [key]: answer });
return await agent.invoke(new Command({ resume }), config);
} finally {
await adapter.close();
}
}打开此示例的公开 LangSmith 运行记录。
createMCPElicitationResume 将答复定向到请求它的中断。响应使用服务器的请求键(request key)。请始终使用 createMCPElicitationResume 构建每个恢复值,而不是使用纯粹的 { responses } 对象,因为后者并未指明它响应的是哪个中断。
每个中断的 value 都是一个 MCPElicitationInterrupt:type: "mcp_elicitation"、server、tool(服务器自身的工具名称,不带前缀)、arguments(生效的工具参数)以及 requests。当同一个智能体也可以引发人在回路中断时,请检查 type。
当多个调用同时暂停时,请答复每个中断。将 createMCPElicitationResume 返回的对象合并为一个 Command({ resume }),例如 Object.assign({}, ...paused.__interrupt__!.map((q) => createMCPElicitationResume(q, answers))),或者一直恢复直到 __interrupt__ 为空。仅答复第一个中断的恢复操作会让其他中断保持暂停状态。
NOTE
恢复执行会从头重新运行该工具。服务器请求输入之前所执行的任何工作都可能会重复进行。请确保这些工作能够安全重复执行,且不会产生重复的副作用。
每个答复使用以下操作之一:
accept:提供符合请求模式(schema)的表单content,或者在没有content的情况下确认 URL 交互已完成。decline:拒绝提供所请求的信息。cancel:表明用户取消了该交互。
适配器将答复转发给服务器,由服务器决定工具调用如何结束。
只有启发式输入以此方式进行响应。适配器在每次调用时仅声明启发式输入功能,因此需要采样 (sampling) 或根目录 (roots) 的工具将失败并抛出 ToolException,createAgent 会将其作为错误的 ToolMessage 返回给模型。请参阅采样与根目录。
NOTE
中断驱动的启发式输入用于响应以 InputRequiredResult 形式返回其请求的服务器。如果服务器仅通过旧版握手会话推送启发式输入,则无法通过这种方式进行响应。
