摘要
本文将详细介绍如何使用Python构建一个基于LangGraph的MCP Chat Agent,该系统能够集成Coze插件,并支持图像生成功能。我们将探讨其架构设计、核心组件以及实际应用场景。
1. 引言
随着大语言模型(LLM)技术的发展,构建智能对话系统已成为热门话题。本文介绍了一个基于LangGraph的MCP(Model Context Protocol)Chat Agent,它集成了Coze插件系统,能够处理文本对话并支持图像生成与保存功能。
2. 技术栈与依赖
本项目主要使用以下技术:
- Python 3.x
- LangChain生态系统(langchain-ollama, langchain-openai)
- LangGraph用于构建有状态的图结构
- MultiServerMCPClient用于连接MCP服务
- Requests库用于HTTP请求处理

3.Linux / mac 配置
bash
export COZE_API_KEY=xxxxxxx
4.Windows 配置
在 Windows 下用下面任意一种方式即可(按你使用的终端选)👇
方法一:PowerShell(最推荐)
powershell
$env:COZE_API_KEY="xxxxxxx"
只对当前 PowerShell 窗口 生效
关闭窗口后失效
验证:
powershell
echo $env:COZE_API_KEY
方法二:CMD(传统命令行)
cmd
set COZE_API_KEY=xxxxxxx
验证:
cmd
echo %COZE_API_KEY%
⚠️ 同样只在当前窗口有效
安装所需依赖:
bash
pip install langchain-ollama langchain-openai langgraph requests langchain-mcp-adapters
5. 代码解析
5.1 项目结构与配置
python
#!/usr/bin/env python3
"""
最终稳定版 MCP Chat Agent
- 自动注册 MCP 工具
- 支持 Ollama / OpenAI
- 支持保存 MCP 生成的图片
"""
import asyncio
import os
import re
import requests
from pathlib import Path
from typing import Annotated, TypedDict
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages
# ================== 配置 ==================
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_API_BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1"
COZE_API_KEY = os.getenv("COZE_API_KEY")
在这部分代码中,我们导入了必要的库并定义了API密钥等配置项。值得注意的是,代码使用了阿里云的DashScope兼容接口。
5.2 状态管理
python
# ================== LangGraph State ==================
class State(TypedDict):
messages: Annotated[list, add_messages]
这里定义了LangGraph的状态结构,其中messages字段用于存储对话历史,并使用add_messages函数来处理消息列表的合并。
5.3 MCPAgent类详解
5.3.1 类初始化
python
# ================== Agent ==================
class MCPAgent:
def __init__(self, backend: str = "ollama"):
print("🚀 初始化 MCPAgent", flush=True)
self.backend = backend
self.llm = None
self.tools = None
self.graph = None
self.history = []
self.last_image_url: str | None = None
self.stream_buffer: str = "" # ✅ 必须初始化
在初始化方法中,我们设置了代理的基本属性,包括后端类型、语言模型、工具列表、图结构等。特别地,stream_buffer被初始化为处理流式输出中的图像URL。
5.3.2 图像保存功能
python
# -------- 保存图片(修复点 1)--------
def save_image(self, url: str, filename: str | None = None):
Path("images").mkdir(exist_ok=True)
if not filename:
filename = url.rstrip("/").split("/")[-1]
if not filename.endswith(".png"):
filename += ".png"
path = Path("images") / filename
resp = requests.get(url, timeout=30)
resp.raise_for_status()
path.write_bytes(resp.content)
return str(path)
这个方法实现了从URL下载图像并保存到本地的功能。它会自动创建images目录,并根据URL生成合适的文件名。
5.3.3 异步初始化
python
# -------- async 初始化 --------
async def async_init(self):
if self.backend == "ollama":
self.llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)
print("🔹 使用 Ollama", flush=True)
else:
self.llm = ChatOpenAI(
model="qwen-vl-max-2025-08-13",
temperature=0.7,
api_key=OPENAI_API_KEY,
base_url=OPENAI_API_BASE,
)
print("🔹 使用 OpenAI API", flush=True)
if not COZE_API_KEY:
raise RuntimeError("❌ 未设置 COZE_API_KEY")
print("🔄 连接 Coze MCP", flush=True)
client = MultiServerMCPClient(
{
"coze_plugin_tuxiangshengcheng_seedream4": {
"transport": "http",
"url": "https://mcp.coze.cn/v1/plugins/7548380026094370867",
"headers": {
"Authorization": f"Bearer {COZE_API_KEY}"
}
}
}
)
self.tools = await client.get_tools()
if not self.tools:
raise RuntimeError("❌ MCP 工具为空")
print(f"✅ 已注册 {len(self.tools)} 个 MCP 工具", flush=True)
self.llm = self.llm.bind_tools(self.tools)
self.graph = self._build_graph()
异步初始化方法根据指定的后端类型(Ollama或OpenAI)配置LLM,然后连接到Coze MCP服务并注册工具。该方法确保了系统在启动时正确配置所有必需的组件。
5.3.4 图结构构建
python
# -------- Graph --------
def _build_graph(self):
g = StateGraph(State)
g.add_node("agent", self._agent)
g.add_node("tools", ToolNode(self.tools))
g.add_edge(START, "agent")
g.add_conditional_edges("agent", tools_condition)
g.add_edge("tools", "agent")
return g.compile(checkpointer=MemorySaver())
这里使用LangGraph构建了一个有状态的图结构,包含两个节点:agent(负责生成回复)和tools(负责执行工具)。条件边允许根据是否需要调用工具来决定下一步的操作。
5.3.5 对话主循环
python
# -------- 主循环 --------
async def run(self):
print("\n🤖 MCP Chat Agent 已启动\n", flush=True)
while True:
user = input("你: ").strip()
if user in ("exit", "quit"):
break
if user in ("保存图片", "我要保存图片", "save image"):
if not self.last_image_url:
print("❌ 还没有可保存的图片", flush=True)
else:
path = self.save_image(self.last_image_url)
print(f"💾 图片已保存到:{path}", flush=True)
continue
self.history.append(("user", user))
async for event in self.graph.astream_events(
{"messages": self.history},
config={"configurable": {"thread_id": "default"}},
version="v2",
):
if event["event"] == "on_chat_model_stream":
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)
self.stream_buffer += chunk.content
matches = re.findall(
r"!\[\]\((https://s\.coze\.cn/[^)]+)\)",
self.stream_buffer,
)
if matches:
self.last_image_url = matches[-1]
elif event["event"] == "on_tool_start":
print(f"\n🛠️ 调用工具:{event['name']}", flush=True)
elif event["event"] == "on_tool_end":
print("\n✅ 工具执行完成\n", flush=True)
self.stream_buffer = ""
主循环处理用户输入,检测特定指令(如保存图片),并将用户消息传递给图结构处理。它还监听事件流以捕获工具调用和模型输出,特别是识别图像URL并保存到last_image_url变量中。
5.4 程序入口
python
# ================== 入口 ==================
async def main():
choice = input("选择模型(1=Ollama,2=OpenAI):").strip()
backend = "ollama" if choice != "2" else "openai"
agent = MCPAgent(backend)
await agent.async_init()
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
程序入口允许用户选择后端模型,然后初始化并运行代理。
6. 功能特性
6.1 多后端支持
系统支持Ollama和OpenAI两种后端,用户可以根据自己的需求和资源选择合适的模型。
6.2 MCP工具集成
通过MultiServerMCPClient,系统能够自动注册并使用Coze插件提供的工具。
6.3 图像生成与保存
系统可以检测并保存由Coze插件生成的图像,提供了完整的图像处理流程。
6.4 流式输出处理
采用流式输出处理,能够实时显示模型回复,并从中提取图像URL。
5. 使用方法
-
设置环境变量:
bashexport COZE_API_KEY=your_coze_api_key export OPENAI_API_KEY=your_openai_api_key # 如果使用OpenAI后端 -
完整代码
python
#!/usr/bin/env python3
"""
最终稳定版 MCP Chat Agent
- 自动注册 MCP 工具
- 支持 Ollama / OpenAI
- 支持保存 MCP 生成的图片
"""
import asyncio
import os
import re
import requests
from pathlib import Path
from typing import Annotated, TypedDict
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages
# ================== 配置 ==================
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_API_BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1"
COZE_API_KEY = os.getenv("COZE_API_KEY")
# ================== LangGraph State ==================
class State(TypedDict):
messages: Annotated[list, add_messages]
# ================== Agent ==================
class MCPAgent:
def __init__(self, backend: str = "ollama"):
print("🚀 初始化 MCPAgent", flush=True)
self.backend = backend
self.llm = None
self.tools = None
self.graph = None
self.history = []
self.last_image_url: str | None = None
self.stream_buffer: str = "" # ✅ 必须初始化
# -------- 保存图片(修复点 1)--------
def save_image(self, url: str, filename: str | None = None):
Path("images").mkdir(exist_ok=True)
if not filename:
filename = url.rstrip("/").split("/")[-1]
if not filename.endswith(".png"):
filename += ".png"
path = Path("images") / filename
resp = requests.get(url, timeout=30)
resp.raise_for_status()
path.write_bytes(resp.content)
return str(path)
# -------- async 初始化 --------
async def async_init(self):
if self.backend == "ollama":
self.llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)
print("🔹 使用 Ollama", flush=True)
else:
self.llm = ChatOpenAI(
model="qwen-vl-max-2025-08-13",
temperature=0.7,
api_key=OPENAI_API_KEY,
base_url=OPENAI_API_BASE,
)
print("🔹 使用 OpenAI API", flush=True)
if not COZE_API_KEY:
raise RuntimeError("❌ 未设置 COZE_API_KEY")
print("🔄 连接 Coze MCP", flush=True)
client = MultiServerMCPClient(
{
"coze_plugin_tuxiangshengcheng_seedream4": {
"transport": "http",
"url": "https://mcp.coze.cn/v1/plugins/7548380026094370867",
"headers": {
"Authorization": f"Bearer {COZE_API_KEY}"
}
}
}
)
self.tools = await client.get_tools()
if not self.tools:
raise RuntimeError("❌ MCP 工具为空")
print(f"✅ 已注册 {len(self.tools)} 个 MCP 工具", flush=True)
self.llm = self.llm.bind_tools(self.tools)
self.graph = self._build_graph()
# -------- Agent Node --------
def _agent(self, state: State):
return {"messages": [self.llm.invoke(state["messages"])]}
# -------- Graph --------
def _build_graph(self):
g = StateGraph(State)
g.add_node("agent", self._agent)
g.add_node("tools", ToolNode(self.tools))
g.add_edge(START, "agent")
g.add_conditional_edges("agent", tools_condition)
g.add_edge("tools", "agent")
return g.compile(checkpointer=MemorySaver())
# -------- 主循环 --------
async def run(self):
print("\n🤖 MCP Chat Agent 已启动\n", flush=True)
while True:
user = input("你: ").strip()
if user in ("exit", "quit"):
break
if user in ("保存图片", "我要保存图片", "save image"):
if not self.last_image_url:
print("❌ 还没有可保存的图片", flush=True)
else:
path = self.save_image(self.last_image_url)
print(f"💾 图片已保存到:{path}", flush=True)
continue
self.history.append(("user", user))
async for event in self.graph.astream_events(
{"messages": self.history},
config={"configurable": {"thread_id": "default"}},
version="v2",
):
if event["event"] == "on_chat_model_stream":
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)
self.stream_buffer += chunk.content
matches = re.findall(
r"!\[\]\((https://s\.coze\.cn/[^)]+)\)",
self.stream_buffer,
)
if matches:
self.last_image_url = matches[-1]
elif event["event"] == "on_tool_start":
print(f"\n🛠️ 调用工具:{event['name']}", flush=True)
elif event["event"] == "on_tool_end":
print("\n✅ 工具执行完成\n", flush=True)
self.stream_buffer = ""
# ================== 入口 ==================
async def main():
choice = input("选择模型(1=Ollama,2=OpenAI):").strip()
backend = "ollama" if choice != "2" else "openai"
agent = MCPAgent(backend)
await agent.async_init()
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
- 运行程序:
bash
python 1.py

-
选择模型后端,开始对话
-
在对话过程中,如果生成了图像,可以输入"保存图片"来保存最新生成的图像
6. 总结
本文介绍了基于LangGraph的MCP Chat Agent的设计与实现。该系统具有良好的架构设计,支持多种后端模型,集成了Coze插件系统,并具备图像生成与保存功能。通过使用LangGraph,我们能够构建一个有状态的、可扩展的对话系统,为后续的功能扩展奠定了基础。
这种架构为构建复杂的AI应用提供了很好的参考,特别是在需要多步骤推理和工具调用的场景下。未来可以进一步扩展更多类型的MCP工具,或增加更丰富的对话管理功能。