
# AI应用开发:LangGraph+MCP
# 1. 前言
AI应用层出不穷,很多同学仅仅会用已有的平台 + prompt 调用大模型;本文将从2个部分介绍一下如何使用 LangGraph + MCP 实现一个自己的AI 应用。读完本文你将学会
- 如何实现一个Agent
- 如何实现一个简易的coze workflow
- 什么是MCP
- 如何自定义MCP 服务 + Client
- MCP + AI 应用如何结合
# 2. 名词介绍
名词 | 介绍 |
---|---|
Agent | 在 LLM 语境下,Agent 是某种能自主理解意图、规划决策、执行复杂任务的智能体 |
workflow | 工作流,指一系列自动化或半自动化的步骤,用于完成特定任务(如数据处理、模型处理等) |
MCP | Model Context Protocol(模型上下文协议),规范了应用程序如何为 LLMs 提供上下文,类似AI应用的USB-C端口 |
Langchain | 开源AI框架,用于构建基于大语言模型(LLM)的应用程序,支持模块化链式调用、记忆管理等 |
LangGraph | LangChain的扩展库,专注于通过图结构(如循环、分支)构建复杂的多步骤AI工作流 |
# 3. LangGraph
# 3.1 什么是LangGraph
LangChain的扩展库,专注于通过图结构(如循环、分支)构建复杂的多步骤AI工作流。 什么是LangChain,LangChain 是一个AI 应用开发框架,简而言之 LangGraph 是在LangChain 的基础上提供了图的能力让大家能够快速的对AI 进行开发,简化了部分对langchain的理解。其中的关系如下。
# 3.1.1 LangChain具备6大能力
PS: langchain的开发 待后续分享
【Model IO】
- 作用:
- Model I/O 是 LangChain 与底层语言模型(如 GPT、Claude 等)交互的接口。
- 负责处理输入数据的预处理(如格式化、编码)和输出数据的后处理(如解析、提取)。
- 它是其他组件与语言模型之间的桥梁。
- 关系:
- Chains 和 Agent 通过 Model I/O 调用语言模型。
- Retrieval 的结果可以作为输入传递给 Model I/O,以增强模型的上下文理解。 【Memory】
- 作用:
- Memory 用于存储和管理历史信息,使系统能够记住上下文或用户偏好。
- 它可以是短期记忆(如对话历史)或长期记忆(如用户配置文件)。
- Memory 使得 Chains 和 Agent 能够在多轮交互中保持一致性。
- 关系:
- Memory 被 Chains 和 Agent 使用,以访问历史信息。
- Memory 的内容可以作为输入传递给 Model I/O,增强模型的理解能力。
【Retreival】 它结合了检索和生成的能力,为我们的文本序列生成任务引入外部知识。RAG 可以将传统的语言模型与外部知识库相结合,使模型在生成响应或文本时能够动态地从这些知识库中检索相关信息。这种结合方法的目的是增强模型的生成能力,使其能够产生更丰富、准确、有根据的内容。特别是在需要具体细节或外部事实支持的场合,可以简单理解为给大语言模型外挂了一个知识库。
【Chains】
- 作用:
- Chains 是 LangChain 中的逻辑流程单元,用于定义一系列的操作步骤。
- 它可以将多个操作(如调用模型、处理数据、调用工具等)串联起来,形成一个顺序执行的流程。
- 常见的 Chain 类型包括 LLMChain(语言模型链)、SequentialChain(顺序链)等。
- 关系:
- Chains 依赖于 Model I/O 来与语言模型交互。
- Chains 可以与 Retrieval 结合,从外部数据源获取信息。
- Chains 可以通过 Memory 访问历史信息,实现上下文感知。
- Agent 可以调用 Chains 来完成特定任务。
【Agent】
- 作用:
- Agent 是一个决策组件,负责根据当前状态和环境动态决定下一步的操作。
- 它可以根据任务需求调用不同的工具、Chains 或 Model I/O。
- Agent 通常用于需要复杂决策的场景,如对话系统、任务自动化等。
- 关系:
- Agent 依赖于 Model I/O 与语言模型交互。
- Agent 可以调用 Chains 来执行特定任务。
- Agent 可以通过 Memory 访问历史信息,做出更智能的决策。
- Agent 可以与 Retrieval 结合,从外部数据源获取信息。
【Callbacks】
- 作用:
- Callback 是 LangChain 中的事件处理机制,用于在特定事件发生时执行自定义逻辑。
- 例如,可以在模型调用开始、结束或出错时触发回调函数,用于日志记录、监控或调试。
- 关系:
- Callback 可以监控 Model I/O、Chains、Agent 等组件的行为。
- 它为开发者提供了对系统运行过程的细粒度控制。
# 3.1.2 LangGraph节点及边
import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
export async function main() {
// 第一步:定义 节点及边 要存储的状态数据
const TestStateAnnotation = Annotation.Root({
list: Annotation<string[]>({
default: () => [],
reducer: (current, updated) => {
return Array.from(new Set(current.concat(updated)));
}
})
});
const graphBuilder = new StateGraph(TestStateAnnotation);
// 第二步:定义每个节点要做什么事情
const node1 = (_state: typeof TestStateAnnotation.State) => {
return {
list: ["a", "b", "cc"]
};
};
const node2 = (_state: typeof TestStateAnnotation.State) => {
return {
list: ["cc", "ddd"]
};
};
// 添加三个节点
const node3Action = (_state: typeof TestStateAnnotation.State) => {
return {
list: ["a", "ee"],
};
};
// 第三步:注册节点 + 边的关联关系
graphBuilder.addNode("node1", node1);
graphBuilder.addNode("node2", node2);
graphBuilder.addNode("node3", node3Action);
graphBuilder.addEdge(START, "node1");
graphBuilder.addEdge(START, "node2");
graphBuilder.addEdge(START, "node3");
graphBuilder.addEdge("node1", END);
graphBuilder.addEdge("node2", END);
graphBuilder.addEdge("node3", END);
// 第四步:编译执行
const graph = await graphBuilder.compile();
// 执行,并且传入初始的 state 值,['1']
const res = await graph.invoke({ list: ["1"] });
// 获取 graph
const graphStructure = await graph.getGraphAsync();
// 绘制 mermaid 图
const mermaid = graphStructure.drawMermaid();
// 返回结果
return res;
}
# 3.2 如何实现一个Agent
在前面的内容讲到Agent是具备 自主规+决策的 智能体。要具有智能的能力 所以至少需要有大模型。 Langgraph 给大家提供了一个快捷方式 createReactAgent 来实现一个Agent。
- 什么是reAct?
ReAct 的灵感来自于 “行为” 和 “推理” 之间的协同作用,正是这种协同作用使得人类能够学习新任务并做出决策或推理。生成推理轨迹使模型能够诱导、跟踪和更新操作计划,甚至处理异常情况。操作步骤允许与外部源(如知识库或环境)进行交互并且收集信息。
ReAct 框架允许 LLMs 与外部工具交互来获取额外信息,从而给出更可靠和实际的回应。
- createReactAgent 的运行流程是咋样的?
export async function main() {
// 手动定义一个tool
const getWeather = tool(
input => {
if (["sf", "san francisco"].includes(input.location.toLowerCase())) {
return "It's 60 degrees and foggy.";
} else {
return "It's 90 degrees and sunny.";
}
},
{ // 告诉大模型 这个tools 是干嘛的,需要那些参数
name: "get_weather",
description: "Call to get the current weather.",
schema: z.object({
location: z.string().describe("Location to get the weather for.")
})
}
);
const tools = [getWeather];
// 使用createReactAgent 实现一个agent,并外挂一些tools 来实现自主规划调用的能力
const agent = createReactAgent({
llm: new ChatOpenAI({
temperature: 0,
model: "gpt-3.5-turbo",
maxTokens: 600
}),
tools: tools
});
const inputs = {
messages: [{ role: "user", content: "what is the weather in SF?" }]
};
const graph = await agent.getGraphAsync();
const graphImg = graph?.drawMermaid();
console.log(graphImg);
const stream = await agent.stream(inputs, {
streamMode: "values"
});
for await (const { messages } of stream) {
console.log(messages);
}
// Returns the messages in the state at each step of execution
}
createReactAgent 实现原理?
有兴趣的同学可以看看:
https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph/src/prebuilt/react_agent_executor.ts#L378
简而言之:createReactAgent 内置了 ToolNode、StateGraph(Node/Edge/Annotation)、compile 能力,方便大家快速使用
# 3.3 如何实现一个简易的 coze workflow
首先我们俩认识一下 什么是workflow,以coze 为例 workflow 具备 半自动 + 全自动执行的能力。
那么如何使用langgraph 实现 具备 IF 判断 + 大模型+ 人工聚合等能力于一身的 workflow呢?此时需要使用到 langgrah 的高级功能。addConditionEdges 及 自定义 State。 先看看生成的效果图吧。 目标:分别获取xx 地方的天气 + 距离当前位置的距离,最后统一输出 流程:
- 开始节点后 同时执行 天气的agent + 距离的agent; 每个agent 调用自己的 tools 等,在内部进行决策。
- 最后通过 一个 message_wrapper 组合所有的数据进行返回
简化的代码如下:完整代码请见: https://github.com/MrGaoGang/langgraph-mcp-example/blob/main/src/langgraph/workflow-single-use-muti-agent/index.ts
// 定义一个 距离获取的 agent
function getDistanceFromChina(location: string) {
// 距离的数据定义
const DistanceStateAnnotation = Annotation.Root({
distanceMessages: Annotation<BaseMessage[]>({
default: () => [
new HumanMessage({
content: `what is the Distance from ${location} to Chengdu, China`
})
],
reducer: messagesStateReducer
})
});
// 距离的tools 定义
const getDistance = tool(
async (input, config) => {
// ......
},
{
name: "get_distance",
description: "Call to get the distance from the location to Chengdu China.",
schema: z.object({
location: z.string().describe("Current Location to get the distance for.")
})
}
);
const tools = [getDistance];
// 距离的大模型
const modelWithTools = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0,
maxTokens: 600
}).bindTools(tools);
// 工具的集合
const toolNodeForGraph = new ToolNode(tools);
const shouldContinue = (state: typeof DistanceStateAnnotation.State) => {
const { distanceMessages = [] } = state;
const messages = distanceMessages;
const lastMessage = messages[messages.length - 1];
if (isAIMessage(lastMessage) && (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0)) {
return END;
} else {
return "continue";
}
};
const callModel = async (state: typeof DistanceStateAnnotation.State) => {
const { distanceMessages } = state;
const response = await modelWithTools.invoke(distanceMessages);
return { distanceMessages: response };
};
return {
callModel,
toolNodeForGraph,
shouldContinue,
DistanceStateAnnotation
};
}
// // 定义一个 天气获取的 agent
function getWether(location: string) {
// .. 基本和 距离 agent 一致
}
export async function main() {
const location = "SF";
const weather = getWether(location);
const distance = getDistanceFromChina(location);
const StateAnnotation = Annotation.Root({
location: Annotation<string>(),
...weather.WeatherStateAnnotation.spec,
...distance.DistanceStateAnnotation.spec,
result: Annotation<string>()
});
// 创建工作流
const workflow = new StateGraph(StateAnnotation);
workflow.addNode("weather_agent", weather.callModel);
/**
* 注意事项!!!
* 如果N个ToolNode并行调用,则不能直接使用 callModel 自动调用 tools 的形式
* workflow.addNode("weather_tools", weather.toolNodeForGraph);
* 原因是 ToolNode 源码默认消费的是 state 中的 messages 字段,而实际上是没有这个字段的(因为不同的agent使用不同的message字段存储)
* 所以需要读取上下文数据后 自定义调用 invoke 方法,并解析出内部的数据
*
*/
workflow.addNode("weather_tools", async state => {
// .....
return {
weatherMessages: weatherMessages
};
});
workflow.addNode("distance_agent", distance.callModel);
workflow.addNode("distance_tools", async state => {
// .....
return {
distanceMessages: messages
};
});
// 聚合数据
workflow.addNode("messgae_wrapper", state => {
return {
result:
state.weatherMessages[state.weatherMessages.length - 1].content +
"\n" +
state.distanceMessages[state.distanceMessages.length - 1].content
};
});
workflow.addEdge(START, "weather_agent");
workflow.addEdge(START, "distance_agent");
workflow.addEdge("weather_tools", "weather_agent");
workflow.addConditionalEdges("weather_agent", weather.shouldContinue, {
continue: "weather_tools",
[END]: "messgae_wrapper"
});
workflow.addEdge("distance_tools", "distance_agent");
workflow.addConditionalEdges("distance_agent", distance.shouldContinue, {
continue: "distance_tools",
[END]: "messgae_wrapper"
});
workflow.addEdge("messgae_wrapper", END);
const graph = await workflow.compile();
const graphStructure = await graph.getGraphAsync();
const graphImg = graphStructure?.drawMermaid();
console.log("=======merchantid=====start==");
console.log(graphImg);
console.log("=======merchantid=====end==");
//......
}
疑问?为啥不使用 creaeReactAgent 解释:使用creaeReactAgent 是可以的,但是creaeReactAgent 可以理解成一个独立的workflow,其内部的数据完全独立,若外层需要获取数据只能使用 messages 这个内置的字段取获取。 对应的代码: https://github.com/MrGaoGang/langgraph-mcp-example/blob/main/src/langgraph/workflow-muti/index.ts
# 4. LangGraph如何与MCP结合?
# 4.1 什么是MCP
从function call 到 mcp的转变
Model Context Protocol(模型上下文协议)是 Anthropic 在推出的用于 LLM 应用和外部数据源(Resources)或工具(Tools)通信的标准协议,遵循 JSON-RPC 2.0 的基础消息格式。
- MCP Client:通过 MCP 协议与 Servers 通信,并保持 1:1 连接
- MCP Servers:上下文提供方,暴露外部数据源(Resources)、工具(Tools)、提示词(Prompts)等由 Client 进行调用。
- 语言支持层面:TypeScript 和 Python、Java、Kotlin、C#
# 4.2 如何 使用MCP
想要使用MCP 则需要 1个mcp client + 多个 MCP server
- MCP Client : Trae, Cursor ,Cline ......(也可以自定义)
- MCP Server:
- 外网 的 MCP 仓库 https://mcp.so/ 使用方式:
在 MCP Client 处注册 MCP server (以trae 为例) 本地的 我使用 tsx 执行 自定义 mcp server
在 Agent 中调用
为什么是「晴朗+多云」,因为我自己的MCP server 是这样返回的
4.3 自定义天气获取 MCP Server
定义MCP Server 其实比较简单。
- 引用@modelcontextprotocol/sdk
- 声明你的MCP Server 需要提供的 tools
- tool的名称
- tool描述
- tool的入参
- tool需要执行的动作
import { z } from "zod";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// 1. initialize `MCPServer`
const server = new McpServer({
name: "weathe_mcp_server",
version: "1.0.0",
});
// // 2.2 resources
// server.resource("filename", "mcp://resource/filename", (uri) => ({
// contents: [{ uri: uri.href, text: "content of filename" }],
// }));
// 2.2 prompts
// server.prompt("split-message", { message: z.string() }, ({ message }) => ({
// messages: [
// {
// role: "user",
// content: {
// type: "text",
// text: `解析${message},得到数字a和b,返回a+b的结果。`,
// },
// },
// ],
// }));
// 2.3 tools
server.tool(
"get_weather",
"获取所给地址的天气信息",
{
location: z.string().optional().describe("location to get weather for"),
},
async ({ location }) => {
console.log("step5: 执行mcp server tool add 方法");
return {
// 你可以选择调用API 去获取天气信息
content: [{ type: "text", text: `${location} 的天气是「晴朗+多云」` }],
};
}
);
// 3. run MCP Server
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
// console.info("Demo MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});
如果你需要debug mcp server 可以在运行文件前加上
npx -y @modelcontextprotocol/inspector tsx ./xxxx你的文件路径
这时候将会 启动一个页面方便你debug
# 4.4 如何自定义MCP Client
一般的情况(如果你使用现有的AI 应用)我们并不需要自定义MCP Client,但如果你自定义的AI 应用需要具备 MCP Client 的能力,可以使用现有的 实现 https://modelcontextprotocol.io/quickstart/client#node 大致流程为:
- 定义Client
- 连接MCP Server
- 获取到有的 tools 并转换成 大模型能识别的 tools
- 大模型使用 转换后的tools 进行调用
export class MCPClient {
private mcp: Client;
private transport: StdioClientTransport | null = null;
private tools: any[] = [];
constructor() {
// 第一步:定义client
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
}
async connectToServer(serverScriptPath: string) {
try {
const isJs =
serverScriptPath.endsWith(".js") || serverScriptPath.endsWith(".ts");
if (!isJs) {
throw new Error("Server script must be a .js or .py or .tsfile");
}
// 第二步:连接MCP Server
this.transport = new StdioClientTransport({
command: "tsx",
args: [serverScriptPath],
});
this.mcp.connect(this.transport);
// 第三步:获取到有的 tools 并转换成 大模型能识别的 tools
const toolsResult = await this.mcp.listTools();
this.tools = await Promise.all(
toolsResult.tools.map((item) => {
return new DynamicStructuredTool({
name: item.name,
description: item.description || "",
schema: item.inputSchema,
responseFormat: "content_and_artifact",
func: async (args: any) => {
const result = await this.mcp.callTool({
name: item.name,
arguments: args,
});
// 此处还有问题 需要转换成 langchain 的格式
return transformToLangGraphTools(result);
},
});
})
);
console.log(
"Connected to server with tools:",
this.tools.map(({ name }) => name)
);
} catch (e) {
console.log("Failed to connect to MCP server: ", e);
throw e;
}
}
getTools() {
return this.tools;
}
getMCP() {
return this.mcp;
}
// methods will go here
}
# 4.5 自定义MCP Server &LangGraph实现一个天气查询功能
前面讲到了LangGraph可以实现Agent的能力,而 MCP Server可以提供一个额外的数据,那么如何将二者结合起来呢?
注意
由于MCP Server 提供的 tools 和 langgrah/langchain 中的 tools 是有区别的,所以实际应用场景时,需要将MCP Server的 tools 转换成 langgrah/langchain 中的 tools 才可以使用。
我们可以使用开源的 langchainjs-mcp-adapters 完成: https://github.com/langchain-ai/langchainjs-mcp-adapters/blob/main/src/tools.ts
核心原理是将mcp 的 tools 转换成langchain 中的 DynamicStructuredTool
实现 完整代码请见:https://github.com/MrGaoGang/langgraph-mcp-example/blob/main/src/langgrah_mcp/index.ts
import * as dotenv from "dotenv";
import Koa from "koa";
import Router from "@koa/router";
import { runAgent } from "./agent";
import { MCPClient } from "custom-mcp-client";
import path from "path";
import { GraphMcpClient } from "./graph-mcp-client";
dotenv.config();
const app = new Koa();
const router = new Router();
app.use(router.routes());
router.get("/", async (ctx) => {
const message = "获取成都的天气,并计算1+1等于几";
console.log("step1: 初始化mcpClient");
const mcpClient = new GraphMcpClient();
console.log("step2: 连接mcpServer");
await mcpClient.connectToServer(path.join(__dirname, "custom-mcp-server.ts"));
console.log("step3: 获取mcpClient的tools,agent执行调用");
const agent = await runAgent(mcpClient.getTools(), message);
console.log("step6: 返回结果");
ctx.body = agent;
});
app.listen(3000);
MCP Client | MCP Server | Agent |
---|---|---|
![]() | ![]() | ![]() |
效果如下:
分析一下调用链路:
[{ // 第一步:大模型获取到输入内容
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "HumanMessage"],
"kwargs": {
"content": "获取成都的天气,并计算1+1等于几",
"additional_kwargs": {},
"response_metadata": {},
"id": "13808fbf-cdcb-48fd-8b45-acc82c1e19b4"
}
},
{// 第二步:大模型知道有那些工具,构造对应的入参,并执行对应的工具
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "AIMessage"],
"kwargs": {
"content": "",
"additional_kwargs": {
"tool_calls": [{
"id": "call_wstyvMhqsELPKpdRHDY8hDWO",
"type": "function",
"function": {
"name": "mcp__weather__get_weather",
"arguments": "{\"location\": \"Chengdu\"}"
}
},
{
"id": "call_BadFrTfyqgH7CLORKWG3GhS6",
"type": "function",
"function": {
"name": "baseTool",
"arguments": "{\"message\": \"1+1\"}"
}
}]
},
"response_metadata": {
"tokenUsage": {
"promptTokens": 115,
"completionTokens": 52,
"totalTokens": 167
},
"finish_reason": "tool_calls",
"model_name": "gpt-3.5-turbo-0125"
},
"id": "chatcmpl-BXOAakVBA8aATPifPsaESwyZcBKg3",
"tool_calls": [{
"name": "mcp__weather__get_weather",
"args": {
"location": "Chengdu"
},
"type": "tool_call",
"id": "call_wstyvMhqsELPKpdRHDY8hDWO"
},
{
"name": "baseTool",
"args": {
"message": "1+1"
},
"type": "tool_call",
"id": "call_BadFrTfyqgH7CLORKWG3GhS6"
}],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 52,
"input_tokens": 115,
"total_tokens": 167,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
},
{ // 第三步:调用mcp server tool获取天气
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "ToolMessage"],
"kwargs": {
"content": "Chengdu 的天气是「晴朗+多云」",
"artifact": [],
"tool_call_id": "call_wstyvMhqsELPKpdRHDY8hDWO",
"name": "mcp__weather__get_weather",
"additional_kwargs": {},
"response_metadata": {},
"id": "284a3a34-bdf9-4715-b62c-e79c13339e21"
}
},
{ // 第四步:调用兜底 tool 计算 1+1 等于几
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "ToolMessage"],
"kwargs": {
"content": "2",
"tool_call_id": "call_BadFrTfyqgH7CLORKWG3GhS6",
"name": "baseTool",
"additional_kwargs": {},
"response_metadata": {},
"id": "b1027cec-06b3-4f40-b37b-ffc30d575275"
}
},
{ // 第四步:聚合返回最后的内容
"lc": 1,
"type": "constructor",
"id": ["langchain_core", "messages", "AIMessage"],
"kwargs": {
"content": "成都的天气是「晴朗+多云」,1+1等于2。",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 219,
"completionTokens": 26,
"totalTokens": 245
},
"finish_reason": "stop",
"model_name": "gpt-3.5-turbo-0125"
},
"id": "chatcmpl-BXOAcUaK8fV4jqLBrFpYwBedDUWff",
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 26,
"input_tokens": 219,
"total_tokens": 245,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
}]
最后的最后,如果觉得分享对你有用,何不点点👍🏻
- 本文链接: https://mrgaogang.github.io/ai/langgraph/LangGraph%E5%92%8CMCP%E5%BC%80%E5%8F%91.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!