基于MCP协议构建AI助手扩展工具:从原理到实践
在AI助手与外部工具集成领域,API协议是实现能力扩展的核心技术基础。Model Context Protocol(MCP)作为AI工具生态的标准化通信协议,定义了客户端与服务器之间的交互规范,类似于计算机领域的USB协议,为不同AI助手调用外部工具提供了统一接口。其技术价值在于实现了工具调用的安全性和一致性,使开发者能够为Cursor、Claude等AI助手构建专属功能模块。在实际应用场景中,开
1. 项目概述:为你的AI助手打造专属工具箱
如果你和我一样,每天都在和Cursor、Claude这类AI编程助手打交道,那你肯定遇到过这样的场景:想让它帮你查一下某个开源库的最新版本,或者快速抓取一个网页的标题摘要,却发现它只能干瞪眼,告诉你“我无法访问实时网络”。这种时候,一个能扩展AI助手能力的“工具箱”就显得至关重要。今天要聊的,就是构建这类工具箱的“脚手架”——一个基于Model Context Protocol(MCP)的服务器模板。
MCP,你可以把它理解成AI世界的“USB协议”。它定义了一套标准,让外部的工具和服务(比如一个能联网查询的服务器)能够安全、规范地被AI助手(如Cursor)调用。这个由Kirill Markin维护的 example-mcp-server 项目,就是一个绝佳的起点。它不是一个功能庞杂的成品,而是一个结构清晰、五脏俱全的模板。你拿到手后,可以像搭积木一样,替换掉里面那个简单的“查询服务器心情”的示例工具,快速集成你自己的业务逻辑,比如调用内部API、查询数据库,或者就像我们最需要的——进行安全的网络请求。
对于开发者,尤其是希望将AI深度融入工作流的全栈或后端工程师来说,掌握MCP服务器的构建意味着你能为团队定制专属的AI能力。想象一下,让AI助手直接查询项目部署状态、提交内部工单,或者分析特定的业务数据,这能极大提升开发与协作效率。接下来,我会带你从零开始,彻底拆解这个模板,不仅告诉你每一步怎么做,更会分享我在适配和部署过程中踩过的坑和总结的技巧,让你能快速打造出属于自己的、稳定可靠的AI扩展工具。
2. 核心架构与MCP协议深度解析
在动手敲代码之前,我们必须先搞清楚手里的“图纸”和“施工标准”。这个模板的核心价值在于它完整实现了一个符合MCP协议的服务器骨架。理解MCP,是后续一切自定义开发的基础。
2.1 MCP协议:AI工具生态的“通用插座”
MCP不是一个具体的工具,而是一套通信协议。它的核心目标是解决一个关键问题:如何让不同的AI助手(客户端)安全、一致地调用五花八门的外部工具(服务器)。你可以把它类比成电脑的USB接口协议。无论你插的是U盘、键盘还是手机,只要设备遵循USB协议,电脑就能识别并使用它。MCP协议就是AI世界的“USB协议”,它规定了工具(服务器)需要以什么样的格式“自我介绍”(公布工具列表),以及客户端(如Cursor)应该如何“按下按钮”(调用工具)并“读取屏幕”(获取结果)。
这套协议主要基于JSON-RPC 2.0,通信方式支持两种主流“方言”: stdio(标准输入输出) 和 SSE(服务器发送事件) 。 stdio 模式就像在本地终端里运行一个命令行程序,AI助手直接通过进程的标准输入输出流与你的服务器对话,简单直接,延迟极低,非常适合本地开发调试。而 SSE 模式则像开启了一个Web服务,AI助手通过HTTP长连接来监听服务器推送的消息,这使得你的工具服务器可以部署到远程(比如Heroku、Railway),被任何地方的Cursor实例调用,实现了能力的云端化与共享。
这个模板的巧妙之处在于,它同时支持了这两种传输方式,并且通过清晰的代码结构将协议通信的底层细节封装了起来。作为开发者,你几乎不需要关心JSON-RPC消息该如何序列化或反序列化,只需要聚焦在最核心的两件事上: 定义你的工具 和 实现工具的逻辑 。
2.2 项目结构解剖:从模板到成品的演化路径
让我们打开这个模板的目录,看看它为我们准备了什么:
example-mcp-server/
├── src/
│ └── mcp_server_template/
│ ├── __init__.py
│ ├── __main__.py # 程序入口,处理命令行参数和服务器启动
│ ├── server.py # **核心文件**,MCP服务器主类,工具定义和逻辑都在这里
│ └── tools/ # 工具模块目录(可按功能分拆多个文件)
│ └── mood.py # 示例工具:查询服务器心情
├── cursor-run-mcp-server.sh # 专为Cursor stdio模式准备的启动脚本
├── docker-compose.yml # Docker一站式部署配置
├── pyproject.toml # 项目依赖和元数据配置(使用uv管理)
├── .env.example # 环境变量示例文件
└── README.md # 项目说明
这个结构非常经典且易于扩展。 server.py 是心脏,它继承自 mcp.Server ,并在初始化时注册工具。目前它只注册了一个示例工具 get_server_mood 。你的主要工作就是在这里“动手术”:移除这个示例,换上你自己的工具。 tools/ 目录的设计鼓励你将不同功能的工具实现分门别类,保持代码的整洁。例如,你可以创建 tools/web_fetcher.py 来处理网页抓取,创建 tools/internal_api.py 来调用公司内部接口。
实操心得:从“玩具”到“工具”的第一步 很多人在拿到模板后,会急于直接修改
mood.py里的逻辑。我的建议是: 不要修改,而是替换 。先完整运行一遍原始示例,确保整个MCP链路在你的环境下是通的。然后,在tools/目录下新建一个文件,比如fetch_web.py,从头实现你的第一个工具。这样做的好处是,你始终保留着一个可以工作的“基准版本”,当你的新工具出问题时,可以快速回退对比,排除是工具逻辑问题还是MCP通信的基础设施问题。
3. 从零开始:环境配置与首次运行
理论说得再多,不如跑起来看看。我们选择最直观的 传统Python设置 方式开始,这能让你最清晰地看到整个流程的细节。
3.1 依赖管理与uv工具链
项目推荐使用 uv 作为Python包管理器和运行器,它比传统的 pip+venv 组合更快、更现代。如果你的系统没有安装,可以通过以下命令安装:
# 在macOS上使用Homebrew安装
brew install uv
# 或者在任意系统上通过pip安装(uv也可以管理自己)
pip install uv
安装完成后,进入项目目录,安装开发依赖。这里 -e “.[dev]” 中的点 . 代表当前目录, [dev] 表示安装 pyproject.toml 中 [project.optional-dependencies] 下的 dev 组依赖(通常包含测试框架pytest等)。
# 克隆模板仓库(建议先Fork到自己的账号下)
git clone https://github.com/your-username/example-mcp-server.git
cd example-mcp-server
# 使用uv安装项目及其开发依赖
uv pip install -e ".[dev]"
这个命令会创建一个虚拟环境(uv会自动管理),并将当前项目以“可编辑”模式安装进去。这意味着你后续在 src/ 目录下的代码修改会立即生效,无需重新安装。
3.2 双模式运行与Cursor连接实战
模板支持两种运行模式,对应MCP的两种传输方式。
模式一:stdio传输(本地直接调用) 这是调试时最常用的模式。服务器作为一个独立的进程运行,通过标准输入输出与调用者通信。
# 在项目根目录下执行
uv run mcp-simple-tool
执行后,程序会挂起,等待输入。这时它已经是一个完整的MCP服务器了,只不过我们在手动测试时看不到而已。为了连接Cursor,我们需要用到项目提供的 cursor-run-mcp-server.sh 脚本。这个脚本的核心就是上面这行命令,但它被包装成了Cursor能直接通过stdio调用的形式。
在Cursor IDE中连接的详细步骤:
- 在Cursor的文件树中找到
cursor-run-mcp-server.sh,右键点击它。 - 选择“Copy Path”或类似选项,获取它的 绝对路径 。例如:
/Users/yourname/projects/example-mcp-server/cursor-run-mcp-server.sh。 - 打开Cursor设置(左下角齿轮图标或Cmd/Ctrl + ,)。
- 导航到“Features”标签页。
- 向下滚动找到“MCP Servers”部分。
- 点击“Add new MCP server”。
- 在弹出的表单中填写:
- Name: 任意名称,如
my-local-tool-server。 - Type: 务必选择
stdio。因为我们的服务器是本地进程,不是HTTP服务。 - Command: 粘贴你刚才复制的脚本绝对路径。
- Name: 任意名称,如
保存后,Cursor会在后台启动这个脚本进程,并与之建立stdio连接。你可以尝试在Chat界面问它:“请询问服务器的心情并告诉我。”如果配置正确,你应该会收到一个包含“ cheerful”和爱心表情的回复。
模式二:SSE传输(网络服务模式) 这种模式将服务器启动为一个HTTP服务,允许通过网络访问。
# 指定使用SSE传输,并在8000端口启动
uv run mcp-simple-tool --transport sse --port 8000
运行后,服务器将在 http://localhost:8000 启动。MCP的SSE端点固定为 /sse ,所以完整的连接地址是 http://localhost:8000/sse 。你可以用curl快速测试:
curl -i http://localhost:8000/sse
如果看到返回的HTTP头中包含 Content-Type: text/event-stream ,说明SSE流已成功开启。
在Cursor中连接SSE服务器: 步骤与添加stdio服务器类似,关键区别在于:
- Type: 选择
sse。 - URL: 填写
http://localhost:8000/sse(或你的远程部署地址)。
避坑指南:端口冲突与防火墙
- 端口占用 :如果启动SSE服务器时提示端口8000被占用,可以通过
--port参数指定其他端口,如--port 8080。同时,记得更新Cursor中配置的URL。- 本地连接SSE :在本地开发时,使用
http://localhost:8000/sse连接通常没问题。但如果你在Windows的WSL2中运行服务器,在Windows的Cursor客户端中连接localhost可能会失败。这时需要将WSL2的IP地址(可通过hostname -I在WSL中查看)作为主机名,如http://172.xx.xx.xx:8000/sse,并确保WSL的防火墙允许该端口入站。- stdio脚本权限 :在Linux/macOS系统,首次运行
.sh脚本前,可能需要赋予执行权限:chmod +x cursor-run-mcp-server.sh。
4. 打造你的第一个自定义工具:网页摘要抓取器
现在,我们已经让模板成功跑起来了。接下来就是最激动人心的部分——把它从“示例玩具”改造成“生产工具”。我们将实现一个实用的工具: fetch_web_summary ,它接受一个URL,返回该网页的标题和一段简洁的文本摘要。
4.1 定义工具:与AI助手的“契约”
首先,在 src/mcp_server_template/tools/ 目录下创建一个新文件 web_fetcher.py 。工具的定义遵循MCP的规范,主要包含名称、描述、参数模式(schema)和具体的执行函数。
import httpx
from mcp.types import Tool, TextContent
from pydantic import BaseModel, HttpUrl
import logging
logger = logging.getLogger(__name__)
# 1. 定义输入参数模型
class FetchWebSummaryInput(BaseModel):
"""输入参数:需要抓取的网页URL"""
url: HttpUrl # 使用Pydantic的HttpUrl类型,会自动验证URL格式
# 2. 实现工具的核心逻辑函数
async def fetch_web_summary(url: HttpUrl) -> str:
"""
抓取给定URL的网页,并提取标题和首段作为摘要。
Args:
url: 有效的HTTP或HTTPS URL。
Returns:
格式化的字符串,包含网页标题和摘要文本。
Raises:
httpx.HTTPError: 当网络请求失败时。
ValueError: 当无法从HTML中解析出所需内容时。
"""
# 设置一个合理的请求超时和User-Agent,避免被某些网站屏蔽
headers = {
'User-Agent': 'MCP-Web-Fetcher/1.0 (+https://github.com/your-username/your-mcp-server)'
}
timeout = httpx.Timeout(10.0, connect=5.0)
async with httpx.AsyncClient(headers=headers, timeout=timeout, follow_redirects=True) as client:
logger.info(f"Fetching URL: {url}")
try:
response = await client.get(str(url))
response.raise_for_status() # 如果HTTP状态码不是2xx,抛出异常
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error for {url}: {e.response.status_code}")
return f"抓取网页失败,HTTP状态码: {e.response.status_code}"
except httpx.RequestError as e:
logger.error(f"Request failed for {url}: {e}")
return f"请求失败,可能网络不通或URL无法访问: {str(e)}"
html_content = response.text
# 简单的HTML解析(示例使用字符串查找,生产环境建议用BeautifulSoup)
# 提取<title>标签内容
title_start = html_content.find('<title>')
title_end = html_content.find('</title>', title_start)
title = html_content[title_start+7:title_end] if title_start != -1 and title_end != -1 else "未找到标题"
# 尝试提取第一个<p>标签的内容作为摘要
# 先清理一些换行和多余空格,便于查找
clean_content = ' '.join(html_content.replace('\n', ' ').split())
p_start = clean_content.find('<p>')
p_end = clean_content.find('</p>', p_start)
summary = clean_content[p_start+3:p_end] if p_start != -1 and p_end != -1 else "无法提取正文摘要"
# 限制摘要长度
if len(summary) > 300:
summary = summary[:297] + "..."
result = f"**网页标题**: {title}\n\n**内容摘要**: {summary}"
logger.info(f"Successfully fetched summary for {url}, title: {title[:50]}...")
return result
# 3. 创建MCP Tool对象
# 这是暴露给AI助手看的“工具说明书”
fetch_web_summary_tool = Tool(
name="fetch_web_summary", # 工具的唯一标识名
description="抓取指定URL的网页,并返回其标题和主要内容的文本摘要。适用于快速了解网页内容。",
inputSchema={
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "需要抓取摘要的完整网页URL,必须以http://或https://开头。"
}
},
"required": ["url"] # 指定url为必填参数
}
)
关键点解析:
- 输入验证 :使用Pydantic的
HttpUrl类型,能自动校验传入的字符串是否为合法URL,这是防止错误调用的第一道防线。 - 健壮的网络请求 :
- 超时设置 :务必设置连接和读取超时,防止因目标网站响应慢而导致你的MCP服务器线程被无限挂起。
- User-Agent :设置一个友好的User-Agent是网络爬虫的基本礼仪,能减少被屏蔽的概率。
- 错误处理 :使用
httpx的异常捕获,将网络错误转化为用户友好的错误信息返回,而不是让服务器崩溃。
- 工具描述 :
description和参数描述要尽可能清晰。AI助手(如Cursor)会读取这些描述来决定在什么场景下调用这个工具。好的描述能大幅提升工具调用的准确率。
4.2 集成工具到主服务器
工具实现好了,接下来需要把它“安装”到服务器上。打开核心的 src/mcp_server_template/server.py 文件。
import logging
from typing import Any
import mcp.server as mcp
from mcp.server.models import InitializationOptions
import mcp.server.stdio
# 导入我们新写的工具函数和定义
from .tools.mood import get_server_mood, get_server_mood_tool # 原始的示例工具,可以保留或删除
from .tools.web_fetcher import fetch_web_summary, fetch_web_summary_tool # 新增导入
logger = logging.getLogger(__name__)
class McpServer(mcp.Server):
def __init__(self) -> None:
super().__init__("example-mcp-server")
# 注册工具:将工具“说明书”和对应的处理函数绑定
self.tool(
name="get_server_mood", # 工具名,需与Tool对象中的name一致
description=get_server_mood_tool.description, # 直接使用导入的描述
callback=get_server_mood, # 指定当该工具被调用时,执行哪个函数
input_schema=get_server_mood_tool.inputSchema,
)
# 注册我们新的网页抓取工具
self.tool(
name="fetch_web_summary",
description=fetch_web_summary_tool.description,
callback=fetch_web_summary, # 注意:这里直接传入异步函数fetch_web_summary
input_schema=fetch_web_summary_tool.inputSchema,
)
async def initialize(self, options: InitializationOptions) -> None:
"""服务器初始化时调用,可以在这里进行资源加载等操作"""
logger.info("MCP Server initializing...")
# 例如:初始化数据库连接池、加载配置文件等
# async with httpx.AsyncClient() as client:
# self.client = client
pass
# ... 文件后续的启动代码保持不变
修改说明:
- 导入 :在文件顶部,从新创建的
web_fetcher模块导入我们需要的工具函数和定义。 - 注册 :在
__init__方法中,仿照示例工具的格式,使用self.tool()方法注册fetch_web_summary。关键点在于callback参数直接指向了我们实现的异步函数fetch_web_summary。MCP SDK会自动处理参数的传递和结果的封装。
4.3 测试与验证
修改完成后,无需重启安装的包(因为我们用了 -e 可编辑安装),直接重启服务器即可。
- 重启SSE服务器 (如果之前运行着,先按Ctrl+C停止):
uv run mcp-simple-tool --transport sse --port 8000 - 在Cursor中测试 :
- 确保Cursor已连接到你的SSE服务器(
http://localhost:8000/sse)。 - 在Chat中输入:“请使用 fetch_web_summary 工具,帮我抓取一下 GitHub 首页的摘要,URL是 https://github.com”。
- 观察Cursor的回复。它应该会识别出这个工具,并尝试调用。如果一切顺利,你将看到返回的标题和摘要。
- 确保Cursor已连接到你的SSE服务器(
高级技巧:提升网页抓取质量 上面的示例使用了简单的字符串查找来解析HTML,这在面对复杂或不符合规范的网页时很容易失败。 对于生产环境,强烈建议使用专业的HTML解析库 ,如
beautifulsoup4或lxml。
- 首先,在
pyproject.toml的dependencies部分添加:beautifulsoup4 = "^4.12.0"- 然后使用uv安装:
uv add beautifulsoup4- 重写
fetch_web_summary函数中的解析部分:from bs4 import BeautifulSoup # ... 在获取到html_content之后 ... soup = BeautifulSoup(html_content, 'html.parser') title = soup.title.string if soup.title else "未找到标题" # 寻找第一个有实质内容的<p>标签 first_paragraph = soup.find('p') summary = first_paragraph.get_text(strip=True) if first_paragraph else "无法提取正文摘要"这样能极大提高抓取的鲁棒性和准确性。
5. 生产级部署与运维指南
本地开发测试通过后,你可能希望将工具服务器部署到云端,供团队其他成员或在不同设备上使用。模板提供了Docker和云平台部署的完美支持。
5.1 使用Docker Compose进行容器化部署
Docker能确保运行环境的一致性,是部署的首选。项目自带的 docker-compose.yml 已经配置好了所需的一切。
# docker-compose.yml 内容概览
version: '3.8'
services:
mcp-server:
build: .
ports:
- "${MCP_SERVER_PORT:-8000}:8000" # 将容器内端口映射到主机,使用环境变量或默认8000
environment:
- MCP_SERVER_PORT=8000
- MCP_SERVER_HOST=0.0.0.0
- DEBUG=${DEBUG:-false}
# volumes:
# - ./cache:/app/cache # 如果需要持久化数据,可以挂载卷
部署步骤:
- 复制环境变量文件 :
cp .env.example .env。你可以编辑.env文件来覆盖默认配置,例如设置DEBUG=true以便查看更详细的日志。 - 构建并启动 :在项目根目录运行
docker compose up --build -d。--build会重新构建镜像,-d表示在后台运行。 - 查看日志 :使用
docker compose logs -f可以实时查看服务器日志,这对于调试至关重要。 - 测试连接 :服务器启动后,你可以通过主机的
http://localhost:8000/sse(如果映射端口是8000)来访问。同样使用curl -i http://localhost:8000/sse进行测试。 - 停止服务 :运行
docker compose down。
连接远程Docker服务到Cursor :如果你的Docker运行在另一台服务器(如云主机)上,你需要将Cursor中的MCP服务器URL改为 http://<你的服务器公网IP>:8000/sse 。 务必确保云主机的安全组或防火墙规则允许该端口的入站流量。
5.2 部署到Heroku(或其他PaaS)
模板README中提到了“Deploy to Heroku”按钮,这是一种极简的部署方式。但理解其背后的原理更有助于你部署到其他平台如Railway、Fly.io或Render。
Heroku部署的本质是,它识别项目根目录的 Procfile (如果没有,Heroku会尝试猜测启动命令)。为了适配Heroku,我们可能需要做一点调整:
-
创建
Procfile(如果不存在):web: uv run mcp-simple-tool --transport sse --port $PORTHeroku会动态分配一个
$PORT环境变量给应用,我们必须使用这个变量。 -
修改服务器代码以兼容动态端口 :当前的
server.py可能通过命令行参数--port读取端口。我们需要确保它也能读取环境变量PORT(Heroku的标准)或我们自定义的MCP_SERVER_PORT。通常mcp.server.stdio.run_server或类似的启动函数会处理这些。 -
部署 :
- 将你的代码推送到GitHub仓库。
- 在Heroku创建新应用,并连接到该仓库。
- Heroku会自动检测为Python项目,安装依赖(根据
requirements.txt或pyproject.toml),然后按照Procfile启动命令运行。
-
获取连接地址 :部署成功后,在Heroku应用的设置(Settings)页找到“Domains”信息,你会得到一个类似
https://your-app-name.herokuapp.com的地址。你的MCP SSE端点就是https://your-app-name.herokuapp.com/sse。将这个地址填入Cursor的MCP服务器配置中。
重要安全警告:网络请求代理问题 如果你的MCP服务器工具需要访问外部网络(如我们的网页抓取工具),在部署到云平台时可能会遇到问题。 许多云平台(包括Heroku的免费套餐)默认不允许对外发起HTTP请求,或者需要配置特殊的网络出口 。如果你的工具调用失败并出现网络超时错误,你需要:
- 查看云平台的文档,确认是否需要为你的应用启用“出站网络”(Outbound Networking)或类似功能。
- 绝对禁止 :在任何情况下,都 不能 、也 不应该 在代码中配置或提及任何形式的代理、VPN或非法的网络穿透工具来绕过此限制。这不仅违反云平台条款,更涉及严重的安全与合规风险。
- 正确的做法是:使用云平台提供的官方网络解决方案,或考虑将需要访问外部API的工具部署在允许出站访问的环境中。对于内部工具,应尽量调用同样部署在云平台内部或可信网络内的服务。
5.3 通过Smithery一键安装(针对Claude Desktop)
项目README中提到了Smithery。这是一个专门为Claude Desktop管理MCP服务器的平台。如果你开发了一个对团队有用的通用工具(比如一个查询公司内部员工目录的工具),你可以将它发布到Smithery。团队成员只需要运行一行命令,就能将你的服务器配置添加到他们的Claude Desktop中,无需手动填写URL或命令。
npx -y @smithery/cli install @your-username/your-mcp-server --client claude
这行命令会引导用户完成安装。作为开发者,你需要按照Smithery的规范来准备你的项目(主要是正确的 package.json 或 smithery.yaml 配置),这通常是项目成熟后考虑的方向。
6. 故障排除与性能优化实战
即使按照步骤操作,也难免会遇到问题。这里汇总了一些常见问题的排查思路和优化建议。
6.1 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| Cursor中提示“无法连接到MCP服务器”或连接超时。 | 1. 服务器进程未启动。 2. 传输类型(stdio/SSE)选错。 3. 端口被占用或防火墙阻止。 4. SSE服务器地址错误。 |
1. 检查终端,确认服务器进程正在运行且无报错。 2. 确认Cursor中配置的Type :本地脚本用 stdio ,网络地址用 sse 。 3. 对于SSE,用浏览器或curl访问 http://localhost:端口/sse ,看是否返回event-stream。 4. 检查URL是否正确,特别是 /sse 后缀。 |
| 工具出现在Cursor中,但调用时无反应或报错。 | 1. 工具函数存在语法或运行时错误。 2. 输入参数格式不符合schema。 3. 异步函数未正确 await 或出现未处理异常。 |
1. 查看服务器日志 ,这是最重要的信息源。运行服务器时确保无 DEBUG=false ,或直接看终端输出。 2. 检查工具函数的输入参数,是否与定义的 inputSchema 完全匹配(参数名、类型)。 3. 在工具函数内部添加更详细的 logger.info 打印关键步骤和中间值。 |
| Docker容器启动后立即退出。 | 1. Dockerfile中启动命令错误。 2. 应用启动时遇到致命错误(如依赖缺失)。 3. 端口绑定冲突。 |
1. 运行 docker compose logs mcp-server 查看退出前的日志。 2. 检查 Dockerfile 中 CMD 或 ENTRYPOINT 指令是否正确。 3. 尝试在 docker-compose.yml 中移除 -d 参数,在前台运行以查看实时输出: docker compose up 。 |
| 网页抓取工具返回超时或403错误。 | 1. 目标网站屏蔽了你的请求(User-Agent或频率)。 2. 云平台网络出口限制。 3. 工具函数中未设置超时。 |
1. 优化请求头,使用更常见的User-Agent字符串。 2. 在云平台配置中,确认允许出站流量 。 3. 在代码中为 httpx.AsyncClient 设置明确的超时参数(如 timeout=10.0 )。 4. 考虑添加简单的重试逻辑(使用 tenacity 库)。 |
| 修改代码后,Cursor调用的工具逻辑未更新。 | 1. 服务器未重启。 2. Python缓存未更新(.pyc文件)。 3. Cursor客户端缓存了旧的工具列表。 |
1. 停止并重启你的MCP服务器进程。 2. 删除 __pycache__ 目录: find . -name "__pycache__" -type d -exec rm -rf {} + 。 3. 在Cursor中尝试禁用再重新启用该MCP服务器,或重启Cursor客户端。 |
6.2 性能与稳定性优化建议
当你的工具服务器开始承载真实使用时,以下几点优化能显著提升体验:
-
连接池与客户端复用 :如果你的工具需要频繁进行HTTP请求(如调用多个外部API),不要在每次工具调用时都创建新的
httpx.AsyncClient。应该在服务器初始化时(initialize方法)创建一个全局的、带连接池的客户端,并在整个服务器生命周期内复用。这能大幅减少TCP连接建立的开销。# 在server.py的McpServer类中 def __init__(self): super().__init__("example-mcp-server") self.client = None # 初始化占位 # ... 注册工具 async def initialize(self, options: InitializationOptions): # 创建全局HTTP客户端 self.client = httpx.AsyncClient(timeout=10.0, limits=httpx.Limits(max_connections=100)) logger.info("Global HTTP client initialized.") async def on_exit(self): # 服务器退出时,优雅地关闭客户端 if self.client: await self.client.aclose() logger.info("Global HTTP client closed.")然后在工具函数中,通过
self.client来使用这个共享客户端。 -
实施速率限制 :如果你的工具可能被高频调用(例如一个查询数据库的工具),为了避免拖垮后端服务或触发风控,应该在工具逻辑中加入简单的速率限制。可以使用
asyncio.Semaphore或第三方库如slowapi。import asyncio class McpServer(mcp.Server): def __init__(self): super().__init__("example-mcp-server") self.semaphore = asyncio.Semaphore(5) # 同时最多5个请求 # ... async def fetch_web_summary(self, url: HttpUrl): async with self.semaphore: # 控制并发 # ... 原有的抓取逻辑 await asyncio.sleep(0.1) # 可添加轻微延迟 -
完善的日志与监控 :在生产环境,将日志输出到文件或日志收集系统(如ELK、Loki)是必须的。使用Python的
logging模块配置不同级别的日志(INFO, ERROR),并在关键步骤(如工具调用开始/结束、网络请求)记录信息。这能让你在出现问题时快速定位。 -
输入验证与清理 :永远不要信任来自客户端的输入。除了Pydantic的模型验证,对于像URL这样的输入,还应检查其协议(是否只允许https?)、域名(是否在白名单内?)以防止SSRF(服务器端请求伪造)攻击。对于从网页抓取的HTML内容,在展示前要进行清理,避免XSS攻击。
经过以上步骤,你已经从一个MCP模板的消费者,变成了一个能够定制、部署和运维自己AI工具服务器的创造者。这个过程的本质,是为AI助手打开了通往外部世界的一扇安全、可控的门。你可以继续扩展这个服务器,加入更多工具:一个调用GitHub API查询仓库信息的工具、一个查询天气的工具、一个将Markdown转换为PPT的工具……可能性只受限于你的想象力。
更多推荐

所有评论(0)