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

77 阅读7分钟

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

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


先看效果

用别人的:百度地图 MCP

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

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())

输出:

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

自己写的:计算器 MCP Server

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——谁都能调

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

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


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

安装

pip install langchain-mcp-adapters

百度地图 MCP 实战

  1. lbsyun.baidu.com 申请 AK(应用类别选"服务端",勾选 MCP 服务,要实名认证)
  2. 设置环境变量 export BAIDU_MAP_AK="你的AK"
  3. 跑代码:
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 服务:

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 服务运行,可以跨机器调用:

# 服务端
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 服务,你需要先手动启动它,可以部署在任何机器上。

对比

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

SSE 呢?

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


自己写一个 MCP Server

最小版本 — stdio 模式

服务端:

# 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

客户端:

# 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 模式

服务端:

# 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")

启动:

python3 demo/mcp-server-calculator.py
# → 监听 http://localhost:8080/mcp

客户端:

# 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等于多少?")]
})

完整流程

┌────────────────────┐         ┌────────────────────┐
│ 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:

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 就好:

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 格式请求远程服务器执行一个函数,然后返回结果

请求:

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

响应:

{
  "jsonrpc": "2.0",
  "result": { "content": [{ "type": "text", "text": "150" }] },
  "id": 1
}

为什么不是 REST?

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

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

三种消息类型

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

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


附录 B:MCP Server 功能分类

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

类别场景例子
文件系统读写本地文件FileSystem MCP
数据库执行 SQLPostgreSQL 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() 里:

# ✅ 正确
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,直接创建就行:

# ✅ 新版
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 拼接。