我用 Python 30 分钟搭了个 MCP 网关,把四个 AI 工具统一成了一套协议

2026-05-03 22:39 我用 Python 30 分钟搭了个 MCP 网关,把四个 AI 工具统一成了一套协议已关闭评论

我用 Python 30 分钟搭了个 MCP 网关,把四个 AI 工具统一成了一套协议

手上同时维护着 Slack bot、微信机器人和内部 CLI 工具,每个都对接了不同的 AI API,接口风格五花八门,维护成本快赶上了业务代码本身。

问题是什么

三个月前我接手了一个内部工具平台,三个 AI 功能模块——代码审查、文档问答、日志分析——分别对接了 OpenAI、Claude 和本地跑的 LLaMA 模型。每个模块的调用方式五花八门:OpenAI 用 messages 数组,Claude 用 prompt 字符串,LLaMA 又是另一种 JSON 格式。

更头疼的是,每次新增一个工具(比如搜索、计算器、查数据库),就得在每个模块里单独实现一遍。那会儿我就琢磨,得有一个统一的工具调用网关。正好 MCP(Model Context Protocol)在这时候冒了出来。

解决思路

MCP 的核心说白了就一句话:用标准化的方式,让 AI 模型发现和调用外部工具。它定义了三种基本操作:

操作 含义
tools/list 返回可用工具清单
tools/call 调用指定工具并返回结果
tools/notify 工具状态变化时主动通知

我的方案很简单:用 Python 写一个轻量 MCP 网关,后端工具以插件方式注册,前端对接任何支持 MCP 的 AI 客户端。不折腾重量级框架,只依赖标准库 + FastMCP。

操作步骤

步骤1:安装依赖,搭好项目骨架

mkdir mcp-gateway && cd mcp-gateway
python -m venv venv && source venv/bin/activate
pip install "mcp[cli]>=1.0.0"
pip install httpx  # 调用外部 API 用

MCP 官方提供了 Python SDK,mcp 命令行工具可以直接拿来调试和测试。

项目结构:

mcp-gateway/
├── server.py          # MCP 网关主入口
├── tools/             # 工具插件目录
│   ├── __init__.py
│   ├── weather.py     # 天气查询工具
│   └── calculator.py  # 计算器工具
└── config.py          # 配置文件

步骤2:用 FastMCP 起一个网关服务

MCP Python SDK 提供了 FastMCP——一个类似 FastAPI 风格的高层封装,十来行就能跑起来。

# server.py
from mcp.server.fastmcp import FastMCP

# 创建 MCP 服务,名称会出现在客户端的能力列表里
mcp = FastMCP("my-tool-gateway")

# 注册一个简单的计算器工具
@mcp.tool()
def calculate(expression: str) -> str:
    """执行数学运算,传入表达式如 '1 + 2 * 3'"""
    try:
        # 安全起见用 ast 而不是 eval
        import ast
        import operator as op
        allowed_ops = {
            ast.Add: op.add, ast.Sub: op.sub,
            ast.Mult: op.mul, ast.Div: op.truediv,
            ast.Pow: op.pow, ast.USub: op.neg,
        }
        def eval_expr(node):
            if isinstance(node, ast.Constant):
                return node.value
            elif isinstance(node, ast.BinOp):
                return allowed_ops[type(node.op)](eval_expr(node.left), eval_expr(node.right))
            elif isinstance(node, ast.UnaryOp):
                return allowed_ops[type(node.op)](eval_expr(node.operand))
            else:
                raise ValueError("不支持的表达式")
        result = eval_expr(ast.parse(expression.strip(), mode='eval').body)
        return str(result)
    except Exception as e:
        return f"计算错误: {e}"

if __name__ == "__main__":
    # stdio 传输模式,适合跟 AI 客户端配合
    mcp.run(transport="stdio")

这段代码一跑起来,MCP 客户端就能通过 tools/list 拿到 calculate 工具的元信息——名称、描述、参数结构(从类型注解自动推断)。

注意: transport="stdio" 是最简单的对接方式,适合嵌入到 AI 助手的子进程里。如果要用 HTTP 接口,换成 transport="sse" 就行。

步骤3:搞个插件化注册机制

硬编码工具不是长久之计——我想要的是写一个工具就自动注册一个。下面是我搞的插件加载器:

# tools/__init__.py
import os
import importlib
import inspect
from typing import List
from mcp.server.fastmcp import FastMCP

def discover_and_register_tools(mcp: FastMCP, tools_dir: str = "tools"):
    """自动扫描 tools 目录,注册所有 tool 函数"""
    tools_path = os.path.join(os.path.dirname(__file__), "..", tools_dir)
    for f in os.listdir(tools_path):
        if f.startswith("_") or not f.endswith(".py"):
            continue
        module_name = f"{tools_dir}.{f[:-3]}"
        try:
            module = importlib.import_module(module_name)
            for name, obj in inspect.getmembers(module):
                if getattr(obj, "_is_mcp_tool", False):
                    mcp.add_tool(obj)
                    print(f"  ✓ 注册工具: {name}")
        except Exception as e:
            print(f"  ✗ 加载 {module_name} 失败: {e}")

然后给每个工具函数加个装饰器标记:

# tools/calculator.py
from functools import wraps

def tool():
    """标记函数为 MCP 工具"""
    def decorator(func):
        func._is_mcp_tool = True
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

@tool()
def add(a: float, b: float) -> float:
    """两数相加"""
    return a + b

@tool()
def multiply(a: float, b: float) -> float:
    """两数相乘"""
    return a * b
# tools/weather.py
import httpx
from tools.calculator import tool

@tool()
def get_weather(city: str) -> str:
    """查询指定城市的当前天气"""
    # 用 wttr.in 的免费 API 做演示
    try:
        resp = httpx.get(f"https://wttr.in/{city}?format=%C+%t", timeout=10)
        return f"{city}: {resp.text.strip()}"
    except Exception as e:
        return f"查询天气失败: {e}"

插件化之后,加一个新工具只需要在 tools/ 下新建一个 .py 文件,写一个带 @tool() 的函数就行了。

步骤4:对接 AI 客户端——拿 Claude Code 举例

MCP 最实在的地方在于:主流 AI 工具已经开始原生支持它。Claude Code 就是其中之一。

在项目根目录创建 MCP 配置文件:

{
  "mcpServers": {
    "my-tool-gateway": {
      "command": "python",
      "args": ["server.py"],
      "env": {},
      "disabled": false,
      "autoApprove": []
    }
  }
}

桌面端 Claude App 的话,配置文件路径是 ~/claude_desktop_config.json。Claude Code CLI 则放在项目目录的 .claude/settings.json 里。

配置好后,Claude 会自动拉起 server.py 子进程,通过 stdio 通信。对话中需要计算或查天气时,Claude 会通过 MCP 协议调用注册的工具。

踩坑记录: 我第一次测试时工具没被识别,排查了半天发现是 mcp 版本问题。0.x1.x 的 API 完全不兼容,FastMCP 是 1.0 才引入的。一定要 pip install "mcp>=1.0.0"

结果与总结

整个网关从零到跑通花了大概 30 分钟,核心代码不到 80 行。最终效果:

  • **新增一个工具只需 5 行代码**:写一个函数加 `@tool()` 装饰器,扔到 `tools/` 目录,自动注册
  • **传输层解耦**:网关对客户端暴露的是标准 MCP 协议,底层用 stdio 还是 SSE,客户端不需要关心
  • **类型标注即文档**:函数的参数类型和 docstring 自动映射成工具的 JSON Schema
  • 几个坑,记住了能省不少时间:

  • **MCP SDK 版本分裂严重** — 0.x 和 1.x 的 API 几乎重写了一遍,网上的教程大多基于 0.x,看文档一定认准版本号
  • **stdio 模式下调试很蛋疼** — 不能直接 `print()`,因为 stdout 被协议占了。用 `logging` 输出到 stderr 或文件
  • **工具调用记得加超时** — 默认实现没有超时,一个 HTTP 请求卡死,整个 AI 响应也跟着卡住。在工具函数里自己加 `timeout`
  • 延伸思考

    这个 MVP 还有很多可以折腾的空间:

  • **动态工具注入**: 目前是启动时加载所有工具。更实用的做法是根据用户当前上下文动态决定暴露哪些——比如用户在写代码就暴露代码相关工具,问天气就暴露天气工具
  • **调用链路追踪**: 工具数量超过 10 个后,"AI 为什么调了这个工具"就成了新问题。可以在网关层加一个调用日志中间件
  • **多后端路由**: 当前只对接了 Claude。实际上 MCP 是协议层的抽象,完全可以在网关背后挂多个 AI 模型,按工具类型做路由——计算类走本地小模型,推理类走云端大模型
  • **流式工具调用**: MCP 1.0 对工具调用的流式返回支持还在早期,后续如果支持了,可以实现"边算边返",对用户体验提升很大
  • 如果你也在折腾多个 AI 工具对接,MCP 值得一试。协议本身不复杂,用起来顺不顺手全看 SDK 的成熟度——Python 的 FastMCP 已经是"开箱即用"的水平了。

    你可能感兴趣的文章

    来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
    转载请注明出处: https://teachcourse.cn/4067.html ,谢谢支持!

    资源分享

    分类:Android 标签:
    Android学习笔记二:JVM内存模型 Android学习笔记二:JVM内存
    Python框架Flask开发用户登录、注册、校验功能,存储到MySQL数据库 Python框架Flask开发用户登录、
    100个python小工具001:文件重命名 100个python小工具001:文件重命
    012-conda如何创建python虚拟环境 012-conda如何创建python虚拟环境

    评论已关闭!