🔍 什么是 Model Context Protocol (MCP)?
Model Context Protocol(MCP) 是一种用于 大语言模型(LLM)上下文管理与交互标准化 的协议。它旨在规范模型与外部世界之间的信息传递方式,使得模型能够更稳定、高效、可控地执行复杂任务。
🧠 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:定义每个接口的ID、URL、请求方式、参数结构(JSON)、描述文档等。data_interface_oauth:存储每个 AppID 对应授权的接口 ID 列表,便于在中间件中实现过滤。
2. FastMCP 中间件实现权限控制
中间件通过拦截 on_list_tools,从请求头读取 appid 和 appsecret,查询授权信息,仅返回该用户有权限访问的接口工具。
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")
四、如何使用
- 启动 MCP 服务:
python main.py
- 注册新接口(写入数据库)
插入新的接口配置到 data_interface 表。
- 调用热更新:
curl http://localhost:8000/reload
- 使用带授权头访问:
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")