LangChain上手 MCP:从用别人工具到自己写工具

LangChain上手 MCP:从用别人工具到自己写工具

读完这篇文章,你将能:用别人的 MCP Server (比如百度地图)、自己写一个 MCP Server (比如计算器)、用 Agent 自动调度多个工具


先看效果

用别人的:百度地图 MCP

十几行代码,让 AI 变成地图助手:

python 复制代码
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from llm import llm

async def main():
    client = MultiServerMCPClient({
        "baidu_map": {
            "transport": "streamable_http",
            "url": "https://mcp.map.baidu.com/mcp?ak=你的AK",
        }
    })
    tools = await client.get_tools()
    print("✅ 工具列表:", [t.name for t in tools])

    agent = create_agent(
        llm, tools,
        system_prompt="你是地图助手,必须使用工具查询。",
    )
    result = await agent.ainvoke({
        "messages": [("user", "北京今天天气怎么样?")]
    })
    print(result["messages"][-1].content)

asyncio.run(main())

输出:

css 复制代码
✅ 工具列表:['map_geocode', 'map_reverse_geocode', 'map_weather', ...]
北京今天晴朗,温度23°C,微风...

自己写的:计算器 MCP Server

python 复制代码
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("计算器")

@mcp.tool()
def add(a: float, b: float) -> float:
    return a + b

mcp.run()  # 就这样,一个 MCP Server 写好了

MCP 是什么

一句话:MCP 是工具和大模型之间的"USB 接口"。

以前你写 @tool def cal(...),这个工具只能给 LangChain 用。写成 MCP Server 后,LangChain、Claude Desktop、ChatGPT、Go 写的 Agent------谁都能调

arduino 复制代码
┌──────────┐                  ┌──────────┐
│  MCP     │                  │  MCP     │
│  Server  │ ←── 标准协议 ──→  │  Client  │
│ (工具端)  │                  │ (模型端)  │
└──────────┘                  └──────────┘
  • MCP Server:暴露工具(数据库、文件、API、你的计算器)
  • MCP Client:连到大模型,负责调用 Server 提供的工具
  • 协议:JSON-RPC,就是 JSON 格式的"调哪个函数、传什么参数、返回什么结果"

📖 官方文档:LangChain MCP | MCP 协议 | FastMCP


3 分钟上手:用别人的 MCP Server

安装

bash 复制代码
pip install langchain-mcp-adapters

百度地图 MCP 实战

  1. lbsyun.baidu.com 申请 AK(应用类别选"服务端",勾选 MCP 服务,要实名认证)
  2. 设置环境变量 export BAIDU_MAP_AK="你的AK"
  3. 跑代码:
python 复制代码
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from llm import llm
import os

async def main():
    client = MultiServerMCPClient({
        "baidu_map": {
            "transport": "streamable_http",
            "url": f"https://mcp.map.baidu.com/mcp?ak={os.getenv('BAIDU_MAP_AK')}",
        }
    })
    tools = await client.get_tools()
    print("✅ 工具列表:", [t.name for t in tools])

    # 先直接调用,不用 Agent ------ 看看工具能返回什么
    tool = {t.name: t for t in tools}
    r = await tool["map_geocode"].ainvoke({"address": "北京市朝阳区国贸中心"})
    print(f"📍 经纬度:{r}")

    # 再用 Agent ------ 让大模型自动判断调哪个工具
    agent = create_agent(
        llm, tools,
        system_prompt="你是地图助手,当用户询问天气、地址、路线、地点等信息时,必须使用工具查询。",
    )
    result = await agent.ainvoke({
        "messages": [("user", "北京今天天气怎么样?")]
    })
    print(f"\n🌤 {result['messages'][-1].content}")

asyncio.run(main())

关键点: MCP 和 Agent 是解耦的。client.get_tools() 拿到工具后,可以直接调,也可以交给 Agent 自动调。


两种传输方式(必知)

MCP 支持两种通信方式,你都要知道:

1. stdio --- 本地开发首选

Client 自动拉起 Server 作为子进程,不需要启动 HTTP 服务:

python 复制代码
client = MultiServerMCPClient({
    "math": {
        "transport": "stdio",
        "command": "python",
        "args": ["demo/mcp-server-stdio.py"],
    }
})
tools = await client.get_tools()

这段配置翻译成人话就是一句命令行:python demo/mcp-server-stdio.py

Client 自动帮你执行这个命令,然后通过 stdin/stdout 和它对话。你不需要手动开终端去启动 Server。

2. streamable_http --- 远程生产环境

Server 作为 HTTP 服务运行,可以跨机器调用:

python 复制代码
# 服务端
mcp = FastMCP("服务", host="0.0.0.0", port=8080)
mcp.run(transport="streamable-http")

# 客户端
client = MultiServerMCPClient({
    "service": {
        "transport": "streamable_http",
        "url": "http://localhost:8080/mcp",
    }
})

这段配置翻译成人话:Client 向 http://localhost:8080/mcp 发 HTTP 请求,Server 在那等着响应。

和 stdio 的区别是:Server 是一个独立的 HTTP 服务,你需要先手动启动它,可以部署在任何机器上。

对比

stdio streamable_http
需要先启动 Server ❌ 自动拉起 ✅ 手动启动
跨机器
鉴权 不需要 支持 headers
场景 本地开发 生产环境

SSE 呢?

老版本 MCP 用 SSE(Server-Sent Events)做远程通信,但连接单向、实现复杂。2025 年 3 月官方推出 streamable_http 替代 SSE,新项目直接用 streamable_http,不用管 SSE。


自己写一个 MCP Server

最小版本 --- stdio 模式

服务端:

python 复制代码
# demo/mcp-server-stdio.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("计算器服务")

@mcp.tool()
def add(a: float, b: float) -> float:
    return a + b

@mcp.tool()
def multiply(a: float, b: float) -> float:
    return a * b

if __name__ == "__main__":
    mcp.run()  # 默认 stdio

客户端:

python 复制代码
# demo/mcp-client-stdio.py
import asyncio
from pathlib import Path
from langchain_mcp_adapters.client import MultiServerMCPClient

async def main():
    client = MultiServerMCPClient({
        "math": {
            "transport": "stdio",
            "command": "python",
            "args": [str(Path(__file__).parent / "mcp-server-stdio.py")],
        }
    })
    tools = await client.get_tools()
    tool = {t.name: t for t in tools}
    r = await tool["add"].ainvoke({"a": 100, "b": 50})
    print(f"100 + 50 = {r}")

asyncio.run(main())

多工具版 --- streamable_http 模式

服务端:

python 复制代码
# demo/mcp-server-calculator.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("计算器服务", host="0.0.0.0", port=8080)

@mcp.tool()
def add(a: float, b: float) -> float:
    return a + b

@mcp.tool()
def multiply(a: float, b: float) -> float:
    return a * b

@mcp.tool()
def divide(a: float, b: float) -> float:
    if b == 0:
        raise ValueError("除数不能为0")
    return a / b

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

启动:

bash 复制代码
python3 demo/mcp-server-calculator.py
# → 监听 http://localhost:8080/mcp

客户端:

python 复制代码
# demo/mcp-client-calculator.py
client = MultiServerMCPClient({
    "my_calculator": {
        "transport": "streamable_http",
        "url": "http://localhost:8080/mcp",
    }
})
tools = await client.get_tools()

# 直接调
tool = {t.name: t for t in tools}
r = await tool["add"].ainvoke({"a": 100, "b": 50})

# Agent 调
agent = create_agent(llm, tools, system_prompt="你是数学助手,计算必须用工具。")
result = await agent.ainvoke({
    "messages": [("user", "100乘以50再加20等于多少?")]
})

完整流程

sql 复制代码
┌────────────────────┐         ┌────────────────────┐
│ mcp-server.py      │         │ mcp-client.py      │
│                    │  HTTP   │                    │
│ @mcp.tool() add    │◄───────►│ MultiServerMCPClient│
│ @mcp.tool() multi  │  JSON   │ → get_tools()      │
│ :8080/mcp          │  -RPC   │ → tool.ainvoke()   │
│                    │         │ → create_agent()   │
└────────────────────┘         └────────────────────┘

对比之前在 LangChain 里 @tool def cal(...):MCP 版多了一步 mcp.run(),但换来的是任何语言、任何框架都能调。


多 Server 同时接入 + 鉴权

一个 Agent 可以同时连多个 MCP Server:

python 复制代码
client = MultiServerMCPClient({
    "math": {
        "transport": "stdio",
        "command": "python",
        "args": ["/path/to/math_server.py"],
    },
    "baidu_map": {
        "transport": "streamable_http",
        "url": "https://mcp.map.baidu.com/mcp?ak=xxx",
    }
})
tools = await client.get_tools()
# tools = math 的 add/multiply + baidu_map 的 13 个地图工具
agent = create_agent(llm, tools)

带 Token 鉴权:

企业场景下,Server 通常需要带 Token 鉴权。在写服务端的时候增加一个鉴权中间件,Client 调用时在 headers 里带上 Token 就好:

python 复制代码
client = MultiServerMCPClient({
    "service": {
        "transport": "streamable_http",
        "url": "http://localhost:8000/mcp",
        "headers": {"Authorization": "Bearer YOUR_TOKEN"},
    }
})

总结

  1. MCP = 工具和模型的 USB 接口,谁的 Server 都能给谁的 Client 用
  2. 会用别人的:MultiServerMCPClient({...})get_tools() → 直接调或放 Agent
  3. 会写自己的:FastMCP + @mcp.tool() + mcp.run()
  4. 本地开发用 stdio,生产用 streamable_http
  5. 多个 Server 自由组合,Agent 自动判断调哪个

附录 A:MCP 是什么协议?JSON-RPC 详解

MCP 底层是 JSON-RPC(JSON Remote Procedure Call)。没有 HTTP Headers、没有 REST 语义------就是用 JSON 格式请求远程服务器执行一个函数,然后返回结果

请求:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": { "name": "add", "arguments": { "a": 100, "b": 50 } },
  "id": 1
}

响应:

json 复制代码
{
  "jsonrpc": "2.0",
  "result": { "content": [{ "type": "text", "text": "150" }] },
  "id": 1
}

为什么不是 REST?

REST JSON-RPC
通信方式 URL 路径 + HTTP 动词 单一端点,方法名在 body
工具调用 POST /api/tools/cal {"method": "tools/call", ...}
批量调用 多次请求 一次请求数组
实时推送 需要 WebSocket 同一连接支持 notification

工具调用天然是"调用函数",和 JSON-RPC 的语义完全匹配。

三种消息类型

类型 说明 有 id?
Request Client → Server,请求执行
Response Server → Client,返回结果
Notification 单向通知,无需回复

一句话:请求-响应是对话,通知是广播。


附录 B:MCP Server 功能分类

按"工具是干嘛的"分,常见 9 大类:

类别 场景 例子
文件系统 读写本地文件 FileSystem MCP
数据库 执行 SQL PostgreSQL MCP
搜索网页 抓取、搜索 Brave Search MCP
云存储 管理云端文件 S3 MCP
版本控制 Git 操作 GitHub MCP
开发工具 终端、Lint 命令行 MCP
通信平台 发消息 Slack MCP
监控 查日志、指标 可观测性 MCP
其他 地图、支付、笔记 百度地图 MCP

任何能被 AI 调用的能力,都能做成一个 MCP Server。


附录 C:常见踩坑

Q1:TypeError: FastMCP.run() got an unexpected keyword argument 'host'

host/port 写在构造函数里,不是 run() 里:

python 复制代码
# ✅ 正确
mcp = FastMCP("服务", host="0.0.0.0", port=8080)
mcp.run(transport="streamable-http")

# ❌ 错误
mcp.run(transport="streamable-http", host="0.0.0.0", port=8080)

Q2:NotImplementedError: MultiServerMCPClient cannot be used as a context manager

0.1.0 版本去掉了 async with,直接创建就行:

python 复制代码
# ✅ 新版
client = MultiServerMCPClient({...})
tools = await client.get_tools()

# ❌ 旧版
async with MultiServerMCPClient({...}) as client:
    ...

Q3:MCP 工具 Agent 调用报错

MCP 工具只支持异步。用 agent.ainvoke() 而不是 agent.invoke()

Q4:stdio 模式 Server 找不到

用绝对路径,或者在客户端用 Path(__file__).parent 拼接。


相关推荐
颜酱14 小时前
LangChain使用RAG 入门:让大模型读懂你的私有文档
python·langchain
质造者17 小时前
LangChain + Ollama + Tavily 实现旅游问答系统
linux·人工智能·python·langchain·rag
Solis程序员17 小时前
LangChain从入门到精通(1)
langchain
leeyi18 小时前
Workflow 编排:字段映射、数据流分离
langchain·workflow·graphql
倾颜18 小时前
从手写 Runner 到 LangGraph:受控 Agent 接入 LangGraph
前端·后端·langchain
wuhen_n18 小时前
从零到一!前端搭建本地轻量化 RAG 问答系统
前端·langchain·ai编程
Solis程序员1 天前
LangChain从入门到精通(2)
langchain
kishu_iOS&AI1 天前
LLM —— LangChain
人工智能·langchain
老梁agent1 天前
Agent 返回 JSON 而不是闲聊:LangChain4j 结构化输出实战
物联网·langchain