拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 LangGraph + MCP 打造自己的 Claude Code:极简 AI 编码助手配置实战

用 LangGraph + MCP 打造自己的 Claude Code:极简 AI 编码助手配置实战 1. 从零复刻一个 CLI 编码助手到底难在哪很多人第一次听到「自己做一个 Claude Code」会觉得门槛很高其实拆开看一个极简 AI 编码助手要解决的核心问题只有三个模型怎么在循环里持续调用工具、工具从哪来、状态怎么在轮次之间保持。LangGraph 负责第一个MCP模型上下文协议负责第二个SQLite 检查点负责第三个。把这三块拼起来你就能得到一个能在终端里读文件、跑测试、搜网页的编码代理。这篇面向的是想在本地跑通极简 AI 编码助手的开发者不追求商业代理那些花哨的上下文压缩和提示工程只验证一件事用最朴素的「用户输入 → 模型响应 → 工具调用 → 回到用户」循环配上 MCP 工具链能不能真的干活。答案是能而且代码量比你想象的小。下面我会给出 LangGraph 的图结构骨架、MCP server 的接入配置、CLI 入口代码以及一次端到端验证动作确认工具调用和代码生成链路是通的。在动手之前先把模型接入这一层准备好。我这边用的是 TaoToken 的 API 来跑 Claude 系列模型它兼容 Anthropic 的接口格式LangChain 的 ChatAnthropic 直接改 base_url 就能接上省去自己搭转发层的麻烦。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 后面配置里会用到。2. TaoToken 前置拿到 Key 并确认模型可用2.1 注册与创建 API Key先去控制台创建一个 API Key。打开 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 登录后在 API Keys 页面点新建复制出来的字符串形如sk-xxxxxxxx。这个 Key 只显示一次建议立刻存到本地环境变量里别写死在代码里。export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api2.2 确认模型名与接口格式TaoToken 走的是 Anthropic 兼容协议LangChain 侧用langchain-anthropic的ChatAnthropic即可把base_url指向https://taotoken.net/apiapi_key读环境变量。模型名按控制台里列出的填比如claude-sonnet-4-5这类。如果你不确定当前有哪些模型可用可以直接在模型对话页试一下 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 发一条消息确认返回正常再回到代码里配置。注意base_url 末尾不要多加/v1Anthropic 兼容层已经处理了路径多写反而会 404。3. 可复制配置LangGraph 图结构 MCP 接入 CLI 入口3.1 项目结构与依赖先建目录依赖用 uv 管理用 pip 也行命令等价mkdir mini-code-agent cd mini-code-agent uv init uv add langgraph langchain-anthropic langchain-mcp-adapters langgraph-checkpoint-sqlite目录大致长这样mini-code-agent/ ├── main.py ├── agent.py ├── mcps/ │ └── deno/ │ └── Dockerfile └── checkpoints.db # 运行后自动生成3.2 AgentState 与三个核心节点LangGraph 的 StateGraph 本质是一个带持久化状态的有向图。状态用 Pydantic 模型定义消息历史用add_messages归约器自动追加# agent.py import os from typing import Annotated, Sequence from pydantic import BaseModel from langchain_core.messages import BaseMessage, SystemMessage from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langchain_anthropic import ChatAnthropic from langchain_mcp_adapters.client import MultiServerMCPClient class AgentState(BaseModel): messages: Annotated[Sequence[BaseMessage], add_messages]模型初始化指向 TaoTokendef build_model(): return ChatAnthropic( modelclaude-sonnet-4-5, api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], temperature0, )3.3 MCP server 接入配置MCP 工具通过MultiServerMCPClient加载每个 server 用命令启动。文件系统操作用 Desktop Commander沙箱 Python 用 Deno 容器搜索用 DuckDuckGoMCP_SERVERS { desktop-commander: { command: npx, args: [-y, wonderwhy-er/desktop-commander], transport: stdio, }, deno-python: { command: docker, args: [run, -i, --rm, deno-docker:latest], transport: stdio, }, duckduckgo: { command: npx, args: [-y, duckduckgo-mcp-server], transport: stdio, }, } async def load_tools(): client MultiServerMCPClient(MCP_SERVERS) return await client.get_tools()Deno 沙箱镜像的 Dockerfile 放在mcps/deno/Dockerfile构建命令docker build -t deno-docker:latest -f ./mcps/deno/Dockerfile .3.4 图结构与条件路由三个节点user_input收集输入model_response调模型tool_use执行工具。条件边根据模型是否返回 tool_calls 决定走向class Agent: def __init__(self, tools): self.model build_model().bind_tools(tools) self.tools_by_name {t.name: t for t in tools} self.workflow StateGraph(AgentState) self.workflow.add_node(user_input, self.user_input) self.workflow.add_node(model_response, self.model_response) self.workflow.add_node(tool_use, self.tool_use) self.workflow.set_entry_point(user_input) self.workflow.add_edge(user_input, model_response) self.workflow.add_edge(tool_use, model_response) self.workflow.add_conditional_edges( model_response, self.check_tool_use, {tool_use: tool_use, user_input: user_input}, ) def check_tool_use(self, state: AgentState): last state.messages[-1] return tool_use if getattr(last, tool_calls, None) else user_inputuser_input节点在 CLI 场景下其实是从队列里取一条消息tool_use节点遍历tool_calls逐个执行并把结果包成ToolMessage塞回状态。这样图就能在「模型 → 工具 → 模型」之间循环直到模型不再请求工具。3.5 SQLite 检查点与 CLI 入口持久化用AsyncSqliteSaver对话历史跨会话保留async def build_agent(): tools await load_tools() agent Agent(tools) db_path os.path.join(os.getcwd(), checkpoints.db) ctx AsyncSqliteSaver.from_conn_string(db_path) checkpointer await ctx.__aenter__() graph agent.workflow.compile(checkpointercheckpointer) return graph, checkpointerCLI 入口就是一个 while 循环读一行输入丢进图里跑打印最后一条 AI 消息# main.py import asyncio from langchain_core.messages import HumanMessage from agent import build_agent async def main(): graph, _ await build_agent() config {configurable: {thread_id: cli-session-1}} print(Mini Code Agent 已启动输入 exit 退出。) while True: user input(\n ).strip() if user in (exit, quit): break async for event in graph.astream( {messages: [HumanMessage(contentuser)]}, configconfig, ): for node, payload in event.items(): if node model_response: msg payload[messages][-1] if msg.content: print(msg.content) if __name__ __main__: asyncio.run(main())启动命令uv run main.py4. 验证请求一次端到端工具调用启动后先问一句「What tools do you have?」模型会列出当前挂载的 MCP 工具名。接着做一次真实的文件读取验证 Read requirements.txt and tell me whats inside预期行为是模型返回一个read_file的 tool_calltool_use节点通过 Desktop Commander 执行把文件内容作为ToolMessage回灌模型再基于内容生成自然语言回答。终端里你会先看到工具调用日志然后看到文件内容的总结。再验证沙箱执行 Run this Python in the sandbox: print(sum(range(10)))这条会走 Deno 容器的 run-python 工具返回45。如果两条都通了说明 LangGraph 的循环、MCP 的工具桥接、TaoToken 的模型调用三层链路都是活的。想确认对话状态有没有落盘直接查 SQLitesqlite3 checkpoints.db SELECT * from checkpoints LIMIT 2 sqlite3 checkpoints.db SELECT * from writes LIMIT 2能看到 thread_id 和消息快照就说明检查点机制生效了。5. 本篇常见错排查报错一401 Unauthorized或invalid api key。九成是环境变量没导出或者 Key 复制时带了空格。先在终端echo $TAOTOKEN_API_KEY确认非空再检查base_url是不是写成了https://taotoken.net/api/v1多写/v1会 404。报错二MCP 工具加载为空模型说「我没有工具」。通常是npx或docker命令不在 PATH 里或者 Deno 镜像没构建。先手动跑一遍npx -y wonderwhy-er/desktop-commander看能不能启动再确认docker images | grep deno-docker有镜像。工具名冲突也会导致部分工具被覆盖加载后打印一下[t.name for t in tools]排查。报错三图跑完不回到 user_input直接结束。检查check_tool_use的返回值必须严格是tool_use或user_input和add_conditional_edges的映射键一致。大小写或拼写不一致会导致路由失败。报错四AsyncSqliteSaver报事件循环相关错误。这个 checkpointer 必须在 async 上下文里__aenter__别在同步函数里初始化。另外thread_id每次换新会话要改否则历史会串。报错五工具调用后模型重复请求同一个工具。多半是ToolMessage的tool_call_id没对上模型的tool_calls里的 id。执行工具时务必把原始 call 的 id 透传进ToolMessage(tool_call_id...)。如果排查到一半卡住可以直接去接入文档对照接口细节 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 或者到 API Keys 页面重新生成一个 Key 排除凭证问题 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。6. 继续往下走把循环跑顺之后这套骨架跑通之后你会发现真正影响体验的不是图结构而是工具的选择和上下文的管理。工具挂太多每次请求都要把几十个工具描述塞进上下文token 消耗很快上去模型选错工具的概率也变高。我的做法是先把文件读写和沙箱执行这两个高频工具跑稳搜索类工具按需临时挂载。另一个值得加的是工具调用前的人工确认。现在模型说读文件就直接读了在可信目录里没问题但一旦把工作目录指到真实项目最好在tool_use节点里加一个input(确认执行? y/n)的拦截代价是打断自动化流程收益是安全。这个开关做成配置项可信操作时关掉就行。如果你打算长期跑编码任务比如让代理连续修多个文件的 bug单次 API 调用按量计费会不太划算可以看看 Coding Plan 这类包月方案 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 适合把代理挂在后台持续干活的场景。至于 Claude Code 本身的接入方式官方文档里有更细的说明 https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以和你自己搭的这套对比着看哪些是必需的哪些只是锦上添花。
返回列表