动态生成FastMCP服务,支持自动更新服务

466 阅读7分钟

🔍 什么是 Model Context Protocol (MCP)?

Model Context Protocol(MCP) 是一种用于 大语言模型(LLM)上下文管理与交互标准化 的协议。它旨在规范模型与外部世界之间的信息传递方式,使得模型能够更稳定、高效、可控地执行复杂任务。

68747470733a2f2f66696c65732e6d646e6963652e636f6d2f757365722f34333433392f65343364383565332d353363342d343430642d616438382d6264353231383032386232302e706e67.webp

🧠 MCP 的设计理念

随着大模型在多任务、多轮对话、多工具调用等场景中的应用越来越广,传统的单轮 Prompt 交互方式显得不够灵活。MCP 提出的核心思想是:

将模型的输入与上下文结构化、组件化,并通过标准协议进行交互和协作。


🔧 MCP 的核心要素

一个标准的 MCP 流程通常包含以下几个部分:

组件功能说明
Context(上下文)提供模型运行所需的背景信息,包括用户输入、历史记录、外部数据等
Prompt Component(提示组件)将复杂任务拆解成多个可管理的提示片段,比如指令、目标、输出格式等
Tool / Function Calling支持模型调用外部工具(如搜索、数据库、计算器等)来增强推理能力
Memory(记忆模块)管理长短期记忆,实现多轮对话和长期用户偏好保持
Response Schema(响应结构)定义模型输出的格式,比如 JSON、Markdown、SQL、API 参数等

✅ MCP 的优势

  • 可扩展:支持多模块、插件化的任务构建
  • 更强鲁棒性:减少 prompt 崩溃或“幻觉”现象
  • 便于调试与观察:每一部分都有结构可分析
  • 利于多模型协作:标准化接口更容易实现模型之间的协同

🧩 适用场景

  • 智能客服 / 多轮对话系统
  • 自动化 Agent 系统(如自动写代码、问答、搜索)
  • 与外部工具协同的大模型应用(如智能体调用数据库、API)
  • 多模态信息整合(文本 + 图像 + 表格)

接下来将使用 FastMCP 如何快速动态生成我们自己的 MCP 服务

在构建现代化、低耦合、高扩展性的微服务架构中,我们常常需要一个统一的服务聚合平台,能够根据数据库动态生成 API 服务,并对外提供标准接口能力。本文将分享如何基于 FastMCP 实现一个“动态注册 API 接口工具”的服务平台,并支持通过数据库控制服务的增删改,实现服务的“热更新”

一、核心能力

本文构建的系统具备以下几个核心特性:

  • 💡 自动从数据库加载服务接口定义
  • 🔐 基于 AppID 和 AppSecret 实现接口授权控制
  • 🔁 提供热更新能力,通过 HTTP 接口实时刷新服务列表
  • 🛠 支持接口参数结构动态生成(Pydantic)
  • 🌐 集成 FastMCP 统一服务注册机制

二、服务架构总览

系统整体架构如下:

+------------------------+
|    MySQL 数据库        |
|  - 接口定义表          |
|  - 授权信息表          |
+------------------------+
           |
           v
+------------------------+
|    FastMCP 服务        |
|                        |
|  [中间件] 权限控制      |
|  [工具注册] 动态注册    |
|  [更新接口] 热更新      |
+------------------------+
           |
           v
      对外提供工具服务

三、核心模块详解

1. 数据库接口定义

数据库中维护了两张核心表:

  • data_interface:定义每个接口的 IDURL、请求方式、参数结构(JSON)、描述文档等。
  • data_interface_oauth:存储每个 AppID 对应授权的接口 ID 列表,便于在中间件中实现过滤。

2. FastMCP 中间件实现权限控制

中间件通过拦截 on_list_tools,从请求头读取 appidappsecret,查询授权信息,仅返回该用户有权限访问的接口工具。

class ListingFilterMiddleware(Middleware):
    async def on_list_tools(self, context: MiddlewareContext, call_next):
        ...
        appid = context.fastmcp_context.request_context.request.headers.get("appid", "")
        appsecret = context.fastmcp_context.request_context.request.headers.get("appsecret", "")
        ...
        # 查询并过滤工具
        if row:
            id_list = row["data_interface_ids"].split(',')
            ...
            for tool in result:
                if tool.name in ids:
                    filtered_tools.append(tool)
        return filtered_tools

3. 动态加载工具定义

系统启动时调用 load_tools_from_interface() 函数,根据数据库配置自动生成 FastMCP 工具接口。

动态注册为工具

每个接口通过 FunctionTool 创建MCP工具,通过mcp.add_tool注册为 MCP 工具:

tool = FunctionTool(
                fn=make_tool_fn(api_def),
                name=name,
                description=f"{desc}\n\n{returns}",
                parameters=input_schema
            )
mcp.add_tool(tool)

4. 热更新服务接口

提供 /reload 接口,可以实时清除旧的注册工具,重新加载数据库内容,实现服务的热更新:

@mcp.custom_route("/reload", methods=["GET"])
async def reload(r: Request) -> PlainTextResponse:
    for name in registered_tools_id.copy():
        registered_tools_id.remove(name)
        mcp.remove_tool(name)
    load_tools_from_interface()
    return PlainTextResponse("ok")

四、如何使用

  1. 启动 MCP 服务:
python main.py
  1. 注册新接口(写入数据库)

插入新的接口配置到 data_interface 表。

  1. 调用热更新:
curl http://localhost:8000/reload
  1. 使用带授权头访问:
import asyncio

from fastmcp import Client

config = {
    "mcpServers": {
        "weather": {
            "url": "http://127.0.0.1:8000/sse",
            "headers": {
                "Content-Type": "application/json",
                "appid": "你的ID",
                "appsecret": "你的秘钥"
            },
        },

    }
}

client = Client(config)

async def main():
    async with client:
        for tool in await client.list_tools():
            print(tool)
            print("\n")
if __name__ == '__main__':
    asyncio.run(main())


五、总结与扩展

通过本文的实践,你可以轻松实现一个 “动态生成 + 授权控制 + 实时更新” 的 API 工具平台。基于 FastMCP 的框架优势,我们可以继续扩展:

  • ✅ 接入 Swagger 自动生成参数结构
  • ✅ 支持接口签名验证 / IP 白名单
  • ✅ 多租户支持
  • ✅ 接口调用频率控制(Rate Limit)

如果你正在构建企业级 API 管理平台、数据接口统一网关或开发助手平台,这种模式将大大提升你的开发效率和系统灵活性。

完整demo

from fastmcp import FastMCP, Context
from fastmcp.server.dependencies import get_context
from fastmcp.tools import Tool, FunctionTool
import requests
import json
import pymysql
from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.tools.tool_transform import ArgTransform
from starlette.requests import Request
from starlette.responses import PlainTextResponse

registered_tools_id = [] #已注册服务

# MySQL数据库配置
db_config = {
    'host': '127.0.0.1',
    'port': 3306,
    'user': 'root',
    'password': '123456',
    'database': 'fastmcp',
    'charset': 'utf8mb4',
    'cursorclass': pymysql.cursors.DictCursor
}

# FastMCP中间件
class ListingFilterMiddleware(Middleware):
    async def on_list_tools(self, context: MiddlewareContext, call_next):
        result = await call_next(context) # 已注册的工具
        filtered_tools = [] # 存储过滤后的当前用户工具列表
        # 拿到 header 中的 appid,appsecret
        appid = context.fastmcp_context.request_context.request.headers.get("appid", "")
        appsecret = context.fastmcp_context.request_context.request.headers.get("appsecret", "")

        try:
            connection = pymysql.connect(**db_config)
            with connection.cursor() as cursor:
                # 查当前用户授权的接口列表
                sql = "select app_id,app_secret,data_interface_ids from data_interface_oauth where app_id = %s and app_secret = %s"
                # 执行查询
                cursor.execute(sql, (appid,appsecret))
                # 获取单条记录
                row = cursor.fetchone()
                if row:
                    print("找到记录:", row)
                    id_list = row["data_interface_ids"].split(',')  # 分割字符串

                    # 使用参数化查询
                    id_in = ','.join(['%s'] * len(id_list))
                    # 使用用户授权的接口ID,查询所有接口列表,确保用户授权的接口都是启用的
                    sql2 = f"SELECT GROUP_CONCAT(id) AS combined_ids FROM data_interface WHERE id IN ({id_in}) and status = 1 "
                    # 执行查询
                    cursor.execute(sql2, (id_list))
                    row2 = cursor.fetchone()
                    data_interface_ids = row2["combined_ids"]
                    ids = data_interface_ids.split(",")
                    print(result)
                    # 获取注册的工具
                    for tool in result:
                        if tool.name in ids:
                            filtered_tools.append(tool)
                else:
                    print("没有找到匹配的记录")
        finally:
            connection.close()
        print(filtered_tools)
        return filtered_tools

# 创建 MCP 服务器实例
mcp = FastMCP("MyFastMCPServer", host="0.0.0.0", port=8000)

# 注册中间件
mcp.add_middleware(ListingFilterMiddleware())

# 查询数据动态注册接口为MCP工具
def load_tools_from_interface():
    try:
        connection = pymysql.connect(**db_config)
        with connection.cursor() as cursor:
            # 查询接口列表,注册MCP工具,这里也可以查询swagger文档接口
            sql = "select id,full_name,api_url,api_method,api_parameters,api_doc from data_interface where status = 1"

            # 执行查询
            cursor.execute(sql)
            # 获取记录
            rows = cursor.fetchall()
            for api_def in rows:
                name = api_def.get("id")
                api_url = api_def.get("api_url")
                method = api_url.get("api_method", "GET")
                desc = api_def.get("full_name", "")
                api_parameters = api_def.get("api_parameters", "")
                # 转成json
                parameter_json = json.loads(api_parameters)

                # 参数
                parameters = {}

                for p in parameter_json:
                    required = False
                    if p.get("required", 0) == 1:
                        required = True
                    type = p.get("type", "str")
                    defaultValue = p.get("default", "")
                    if defaultValue == "":
                        defaultValue = None
                    parameters[p["field"]] = {
                        "type": type,
                        "description": p.get("description", ""),
                        "required": required,
                        "default": defaultValue,
                    }

                # 转成 MCP inputSchema
                input_schema = {
                    "type": "object",
                    "properties": {},
                    "required": []
                }
                for param_name, param_info in parameters.items():
                    input_schema["properties"][param_name] = {
                        "type": param_info["type"],
                        "description": param_info["description"],
                        "default": param_info["default"]
                    }
                    if param_info["required"]:
                        input_schema["required"].append(param_name)


                # 返回值 schema 提取
                markdown = api_def.get("api_doc", "")
                returns = f"\n{markdown}"

                print(desc + ":开始注册")
                registered_tools_id.append(name)

                def make_tool_fn(tool_def):
                    async def tool_fn(**kwargs):
                        ctx = get_context()
                        args = dict(kwargs)
                        print("调用参数:")
                        print(args)
                        name = tool_def.get("id")
                        print("调用参数:")
                        print(name)
                        api_url = tool_def.get("api_url")
                        method = tool_def.get("api_method", "GET")

                        print("调用接口:" + api_url)
                        # ctx.get_http_request() # 可获取请求用户信息 https://gofastmcp.com/patterns/http-requests#important-notes
                        resp = requests.request(method, api_url, params=args)  # 可自行根据接口传参数, 也可以调用一些函数处理一些特定的业务
                        return resp.json()

                    return tool_fn

            # FunctionTool 封装
            tool = FunctionTool(
                fn=make_tool_fn(api_def),
                name=name,
                description=f"{desc}\n\n{returns}",
                parameters=input_schema
            )
            mcp.add_tool(tool)
    finally:
        connection.close()

# 提供一个接口更新MCP工具列
@mcp.custom_route("/reload", methods=["GET"])
async def reload(r: Request) -> PlainTextResponse:

    # 删除已注册的工具
    for name in registered_tools_id.copy():
        registered_tools_id.remove(name)
        mcp.remove_tool(name)

    load_tools_from_interface()

    return PlainTextResponse("ok")

if __name__ == '__main__':
    load_tools_from_interface()
    print("成功注册服务\n")
    print(registered_tools_id)
    mcp.run(transport="sse")

qrcode_for_gh_63be2075d819_258.jpg