MCP服务器开发实战:使用mcp-custom-dev快速构建AI工具集成
1. 项目概述:一个为MCP生态量身定制的开发工具
最近在折腾AI Agent开发,特别是围绕OpenAI的Model Context Protocol(MCP)做点东西,发现了一个挺有意思的项目:
CCCpan/mcp-custom-dev
。这名字乍一看有点抽象,但如果你也在这个领域摸索过,就会明白它直指一个核心痛点——如何高效、灵活地创建和管理自定义的MCP服务器。
MCP,简单来说,是让大型语言模型(比如ChatGPT)能够安全、标准化地调用外部工具和数据的一套协议。你可以把它想象成给AI模型装上了一套标准化的“插件接口”。而
mcp-custom-dev
这个项目,就是一套专门用来快速搭建这类“插件”(即MCP服务器)的开发工具包。它不是某个具体的应用,而是一个脚手架、一个工具箱,目的是把开发者从繁琐的协议实现、项目初始化、调试配置中解放出来,让你能更专注于业务逻辑本身。
我最初接触它,是因为想给团队内部的一些数据源和工具做个AI接口。手动去实现MCP规范里的各种细节,比如资源(Resources)的定义、工具(Tools)的注册、上下文管理,不仅耗时,还容易出错。
mcp-custom-dev
的出现,相当于提供了一个“最佳实践”的起点,内置了类型安全、热重载、标准化日志这些现代开发流程中必不可少的东西。对于任何想进入MCP生态,或者希望将自己的服务快速AI化的开发者来说,这无疑是一个能显著提升效率的利器。
2. 核心设计思路与架构拆解
2.1 为什么需要这样一个开发工具?
在深入代码之前,我们得先理解MCP服务器开发的典型挑战。MCP协议本身定义了一套清晰的规范,但实现起来,开发者需要处理不少“脏活累活”:
-
协议样板代码
:每个MCP服务器都需要实现标准化的Stdio通信、处理初始化(
initialize)、列出资源/工具(list_)、处理调用请求等。这些代码结构固定,但手动编写枯燥且易错。 - 开发体验割裂 :传统的开发流程可能是:写代码 -> 编译/打包 -> 启动服务器 -> 在另一个终端连接测试 -> 发现问题 -> 修改代码 -> 重启... 这个循环非常低效。
- 类型安全缺失 :MCP请求和响应的数据结构复杂。如果没有良好的类型提示,在Python里很容易因为字段名拼写错误或类型不匹配导致运行时错误,调试起来很头疼。
- 部署与配置 :如何将开发好的服务器方便地集成到AI客户端(如Claude Desktop)中?如何管理环境变量和配置?
CCCpan/mcp-custom-dev
正是瞄准了这些问题。它的设计思路非常明确:
提供一套开箱即用、体验流畅的MCP服务器开发框架
。它不是一个运行时,而是一个高度集成的开发环境(Dev Environment)。
2.2 项目架构与核心模块
虽然项目本身可能还在迭代,但根据其命名和常见模式,我们可以推断其架构通常包含以下几个核心部分:
-
脚手架生成器(Scaffolding Generator) :这是项目的入口点。通过一条命令(比如
mcp-dev init my-server),就能生成一个完整的、结构清晰的MCP服务器项目目录。这个目录里通常会预置:-
pyproject.toml或requirements.txt:管理项目依赖,核心依赖必然是官方mcpSDK。 -
主服务器文件(如
server.py):包含一个继承了基础MCP服务器类的骨架代码,其中已经定义好了标准生命周期方法的占位符。 -
资源(Resource)和工具(Tool)的模块化目录:鼓励你将不同的功能模块化存放,例如
resources/目录下放数据源定义,tools/目录下放可执行操作。 -
配置文件(如
.env.example,config.yaml):用于管理服务器行为和环境变量。 -
开发工具配置:如
Dockerfile用于容器化,docker-compose.yml用于本地编排,以及用于代码质量检查的pre-commit配置。
-
-
开发服务器与热重载(Dev Server with Hot Reload) :这是提升体验的关键。项目会内置一个开发服务器命令(如
mcp-dev run)。这个命令会启动你的MCP服务器,并监听文件变化。当你修改了资源定义、工具逻辑或者任何业务代码后,开发服务器会自动重启,而无需你手动中断再启动。这极大地缩短了开发反馈循环。 -
类型存根与代码提示(Type Stubs & IntelliSense) :优秀的框架会充分利用Python的类型提示。
mcp-custom-dev应该提供了完善的类型定义,使得你在IDE(如VSCode, PyCharm)中编写工具函数时,能获得参数类型、返回值类型的自动补全和错误检查,将很多bug消灭在编码阶段。 -
集成测试工具(Integrated Testing Utilities) :它可能会提供一些辅助函数或夹具(Fixtures),让你能够方便地模拟MCP客户端的请求,对你自己编写的工具和资源处理器进行单元测试或集成测试,确保逻辑正确。
-
客户端连接调试工具(Client Debugging Helper) :为了方便调试,它可能包含一个简单的、本地的MCP客户端模拟器,或者提供便捷的脚本,帮助你将开发中的服务器快速连接到真实的AI客户端(如通过SSE或Stdio)进行端到端测试。
注意 :以上是基于“自定义MCP开发工具”这一目标的合理推断。实际项目的功能可能有所不同,但核心价值—— 提升开发效率与体验 ——是不变的。使用这类工具,你的关注点可以从“如何实现协议”转移到“我要提供什么数据和能力”上。
3. 从零开始:使用 mcp-custom-dev 创建你的第一个MCP服务器
理论说了不少,现在我们动手实操。假设我们已经将
cccpan/mcp-custom-dev
安装或克隆到了本地(具体安装方式可能为
pip install mcp-custom-dev
或通过
uv
/
poetry
添加依赖)。下面,我将模拟一个完整的开发流程。
3.1 项目初始化与结构解析
首先,我们创建一个名为
my-weather-agent
的MCP服务器,它提供一个查询天气的工具。
# 假设工具提供的命令是 mcp-dev
mcp-dev init my-weather-agent
cd my-weather-agent
执行后,我们会看到一个类似如下的目录结构:
my-weather-agent/
├── pyproject.toml # 项目依赖和配置
├── .env.example # 环境变量示例
├── .gitignore
├── src/
│ └── my_weather_agent/
│ ├── __init__.py
│ ├── server.py # 主服务器入口
│ ├── resources/ # 资源定义模块
│ │ ├── __init__.py
│ │ └── weather_data.py
│ └── tools/ # 工具定义模块
│ ├── __init__.py
│ └── query_weather.py
├── tests/ # 测试目录
│ └── test_tools.py
└── README.md
让我们快速浏览关键文件:
-
pyproject.toml: 这里定义了项目依赖。核心是mcp库,可能还有pydantic(用于数据验证)、httpx(用于HTTP请求)以及开发依赖如pytest,black,isort。 -
src/my_weather_agent/server.py: 这是心脏。内容骨架可能如下:
import logging
from mcp.server import Server
from mcp.server.models import InitializationOptions
# 导入我们即将编写的模块
from .tools import query_weather
from .resources import weather_data
# 初始化MCP服务器实例
app = Server("my-weather-agent")
# 注册工具(Tool)
# 这里调用了从 tools.query_weather 模块导入的 `query_weather_tool` 函数
# 该函数返回一个符合MCP规范的Tool对象
app.list_tools()(query_weather.tool)
# 注册资源(Resource)
# 同样,从 resources.weather_data 模块导入资源列表
app.list_resources()(weather_data.resources)
# 可选的:添加自定义的初始化逻辑
@app.initialize()
async def handle_initialize(params: InitializationOptions) -> None:
"""服务器初始化处理,可以在这里进行一些预备工作"""
logging.info(f"Client initialized with options: {params}")
# 例如:验证API密钥、建立数据库连接池等
pass
if __name__ == "__main__":
# 使用开发服务器运行,支持热重载
# 这是 mcp-custom-dev 带来的便利
app.run(development_mode=True)
这个骨架已经处理了协议通信的基础,我们只需要填充
tools/
和
resources/
下的具体内容。
3.2 定义你的第一个工具(Tool)
工具是MCP的核心,代表AI可以调用的一个具体操作。我们打开
src/my_weather_agent/tools/query_weather.py
。
一个完整的工具定义需要包含:工具名称、描述、输入参数模式(JSON Schema)和执行函数。
import httpx
import logging
from typing import Any
from pydantic import BaseModel, Field
from mcp.server.models import Tool
# 1. 定义输入参数的模型(使用Pydantic)
class QueryWeatherInput(BaseModel):
"""查询天气的输入参数"""
city: str = Field(description="城市名称,例如:北京、Shanghai")
# 可以添加更多参数,如 country_code, units (metric/imperial)
# country_code: str = Field("CN", description="国家代码,默认CN")
# 2. 实现工具的执行逻辑
async def query_weather_execute(arguments: QueryWeatherInput) -> str:
"""
根据城市名称查询天气信息。
这里我们模拟调用一个虚拟的天气API。
在实际项目中,你会替换为真实的API调用,如OpenWeatherMap。
"""
city = arguments.city
logging.info(f"正在查询城市 {city} 的天气...")
# 模拟API调用和响应处理
# 真实情况示例(需要安装httpx和配置API_KEY):
# async with httpx.AsyncClient() as client:
# api_key = os.getenv("WEATHER_API_KEY")
# url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
# resp = await client.get(url)
# data = resp.json()
# temp = data['main']['temp']
# desc = data['weather'][0]['description']
# return f"{city}的天气:{desc},温度 {temp}°C。"
# 模拟返回
# 在实际开发中,你应该返回结构化的文本信息,方便AI理解。
return f"{city}的模拟天气信息:晴,温度 22°C,湿度 65%,东南风2级。"
# 3. 创建并导出MCP Tool对象
# 这是最关键的一步,将上述两部分组装成MCP协议能识别的Tool对象。
query_weather_tool = Tool(
name="query_weather", # 工具的唯一标识名
description="根据指定的城市名称查询当前天气情况。",
inputSchema=QueryWeatherInput.model_json_schema(), # 将Pydantic模型转为JSON Schema
callback=query_weather_execute, # 绑定执行函数
)
关键点解析 :
-
Pydantic模型
:用于定义和验证输入参数。
Field的description非常重要,AI客户端(如Claude)会读取这个描述来理解如何调用工具。 -
异步函数
:工具执行函数通常是
async的,因为可能涉及网络I/O(调用外部API)。这符合现代Python异步生态。 -
Tool对象
:这是与MCP SDK对接的桥梁。
inputSchema必须是一个合法的JSON Schema字典,model_json_schema()方法能完美生成。 -
错误处理
:在实际代码中,务必在
execute函数内部用try...except包裹核心逻辑,并抛出清晰的错误信息,方便AI和开发者调试。
3.3 定义资源(Resource)
资源代表AI可以读取的静态或动态数据。例如,我们可以提供一个“天气报告”资源。打开
src/my_weather_agent/resources/weather_data.py
。
from mcp.server.models import Resource
from datetime import datetime
# 定义一个资源列表
resources = []
# 示例1:一个静态的、说明性的资源
static_resource = Resource(
uri="weather://guide/introduction", # 资源URI,可以自定义协议(如weather://)
name="天气查询服务指南",
description="本服务提供的天气查询工具使用说明。",
# `contents` 可以是一个返回文本/列表的函数,也可以是直接的值
# 这里我们用一个简单的函数返回文本
contents=lambda: [{
"type": "text",
"text": """
欢迎使用天气查询MCP服务。
可用工具:
1. query_weather: 输入城市名,获取当前天气概况。
未来计划:
- 增加天气预报(未来3天)
- 增加空气质量指数查询
"""
}]
)
resources.append(static_resource)
# 示例2:一个动态资源,内容随时间或查询变化
def get_daily_weather_report():
"""生成当天的模拟天气简报"""
today = datetime.now().strftime("%Y-%m-%d")
report_text = f"""# {today} 全国主要城市天气简报(模拟数据)
* **北京**: 晴转多云,15-25°C。
* **上海**: 小雨,18-22°C。
* **广州**: 雷阵雨,24-30°C。
* **成都**: 阴,17-20°C。
(注:此为模拟数据,仅供演示。)
"""
return [{"type": "text", "text": report_text}]
dynamic_resource = Resource(
uri="weather://report/daily",
name="每日天气简报",
description="每天自动生成的全国主要城市天气简报。",
contents=get_daily_weather_report # 注意这里传递的是函数引用,不是调用结果
)
resources.append(dynamic_resource)
资源的关键在于
uri
和
contents
。AI客户端可以通过
read
请求来获取
contents
返回的内容。动态资源通过函数实现,使得每次读取都能获得最新信息。
4. 开发、调试与集成实战
4.1 启动开发服务器与热重载
项目初始化并编写了基础工具和资源后,我们就可以启动开发服务器了。在项目根目录下运行:
# 方式一:使用工具提供的命令(如果封装了)
mcp-dev run
# 方式二:直接运行主文件(如果server.py已配置好)
python -m src.my_weather_agent.server
如果
mcp-custom-dev
设计得当,你会看到类似下面的输出:
INFO: Started server process [12345]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: MCP server running in development mode with hot reload.
INFO: Watching for file changes in /path/to/my-weather-agent...
INFO: Server initialized. Ready to accept connections via stdio.
看到“hot reload”和“Watching for file changes”就对了。现在,尝试修改
query_weather.py
文件中的返回字符串,保存。观察终端,你应该能看到服务器自动重启的日志,新代码立即生效。
4.2 使用MCP客户端进行测试
服务器跑起来了,我们怎么测试它是否工作?你需要一个MCP客户端。有几个选择:
- 官方MCP Inspector :这是一个图形化调试工具,可以连接到本地服务器,手动调用工具、读取资源,非常适合开发阶段。你需要单独安装它。
- Claude Desktop :这是最终集成的目标之一。你可以在Claude Desktop的配置中添加你的本地服务器(通过SSE或stdio),然后在聊天窗口中直接测试。
-
编写简单的测试脚本
:对于自动化测试,可以基于
mcpSDK的客户端库写一个Python脚本。
这里以使用一个假设的、简单的命令行测试客户端为例(概念演示):
# test_client.py (一个非常简化的概念示例)
import asyncio
import json
import sys
# 这里需要你根据实际情况导入或实现一个简单的MCP客户端
# 假设我们有一个 MinimalMCPClient 类
async def test_tool():
# 1. 通过stdio连接到我们本地运行的服务器
# 实际中,这需要启动一个子进程并管理其stdin/stdout
proc = await asyncio.create_subprocess_exec(
sys.executable, '-m', 'src.my_weather_agent.server',
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
# 2. 发送初始化请求 (简化版,实际协议更复杂)
init_request = json.dumps({"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {...}})
proc.stdin.write(init_request.encode() + b'\n')
await proc.stdin.drain()
# ... 读取响应,处理协议 ...
# 3. 发送调用工具的请求
tool_request = json.dumps({
"jsonrpc": "2.0",
"method": "tools/call",
"id": 2,
"params": {
"name": "query_weather",
"arguments": {"city": "杭州"}
}
})
proc.stdin.write(tool_request.encode() + b'\n')
await proc.stdin.drain()
# 4. 读取工具调用结果
line = await proc.stdout.readline()
response = json.loads(line.decode())
print("工具调用结果:", response.get('result', {}).get('content'))
# 5. 清理
proc.terminate()
if __name__ == "__main__":
asyncio.run(test_tool())
实操心得 :在开发初期, 强烈推荐使用 MCP Inspector 这类可视化工具。它能直观地展示服务器提供的所有工具和资源,并允许你填充参数进行点击测试,能快速验证协议通信和业务逻辑是否正确,远比手动写测试客户端高效。将调试时间投入到Inspector中,是事半功倍的做法。
4.3 集成到 Claude Desktop
当你的服务器在本地测试通过后,下一步就是让它能被Claude Desktop调用。这需要在Claude Desktop的配置文件中添加你的服务器。
-
找到配置文件 :Claude Desktop的配置文件通常位于:
-
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json -
Windows:
%APPDATA%\Claude\claude_desktop_config.json -
Linux:
~/.config/Claude/claude_desktop_config.json
-
macOS:
-
添加服务器配置 :在配置文件的
mcpServers对象中添加一个新条目。假设我们的服务器通过stdio运行。
{
"mcpServers": {
"my-weather-agent": {
"command": "/path/to/your/python",
"args": [
"-m",
"src.my_weather_agent.server"
],
"env": {
"WEATHER_API_KEY": "your_real_api_key_here" // 通过环境变量传递密钥
},
"cwd": "/path/to/your/my-weather-agent" // 工作目录很重要
}
}
}
- 重启Claude Desktop :保存配置文件并完全重启Claude Desktop。
- 验证 :在Claude聊天窗口,你应该能看到新工具可用。尝试输入“请用query_weather工具查询一下北京的天气”。Claude会识别到可用的工具并调用它。
关键细节 :
-
cwd(工作目录)必须设置为你的项目根目录,这样Python才能正确找到你的模块(src.my_weather_agent)。 - 所有依赖必须在该Python环境下可用。建议使用虚拟环境(venv, conda)并确保Claude Desktop启动时能访问到该环境下的Python解释器。
-
环境变量(
env)是传递配置(如API密钥、数据库连接串)的安全方式,避免硬编码在代码中。
5. 进阶配置、优化与问题排查
5.1 管理配置与密钥
任何实用的MCP服务器都需要配置,比如API端点、认证密钥。硬编码是绝对要避免的。
mcp-custom-dev
生成的项目通常鼓励使用环境变量或配置文件。
-
环境变量
:如上文Claude配置所示,通过
env字段传入。在代码中使用os.getenv("WEATHER_API_KEY")读取。项目根目录的.env.example文件就是用来列出所有必需的环境变量,你应该复制它为.env并在本地填写(确保.env在.gitignore中)。 -
配置文件
:对于更复杂的配置(如多个数据源、开关选项),可以使用
config.yaml或config.toml,并用pydantic-settings这样的库进行加载和验证。这能让配置结构化、有类型提示。
5.2 性能优化与最佳实践
-
异步与并发
:MCP服务器本质是IO密集型的(处理请求、调用外部API)。确保你的工具执行函数是
async的,并在内部使用异步HTTP客户端(如httpx.AsyncClient)、异步数据库驱动等。合理使用asyncio.gather来并发执行多个独立的外部调用。 -
错误处理与重试
:在工具函数中,对外部服务调用添加重试逻辑(使用
tenacity库)和详细的错误日志。返回给AI的错误信息应友好且包含可操作建议(例如“天气服务暂时不可用,请稍后再试”而非堆栈跟踪)。 -
资源管理
:对于需要昂贵连接(如数据库连接池、大型模型)的资源,应在服务器初始化(
initialize)时创建,并在一个生命周期内复用。避免在每个工具调用中创建和销毁。 - 工具设计的原子性 :一个工具最好只做一件事。不要设计一个“查询天气并发送邮件”的工具。将其拆分为“查询天气”和“发送邮件”两个工具,由AI来组合调用。这更符合MCP的哲学,也更具灵活性。
-
详细的描述
:工具和资源的
name、description以及参数的description要尽可能清晰、具体。这是AI理解如何调用你的服务的唯一依据。好的描述能极大提升工具被正确使用的概率。
5.3 常见问题与排查技巧
在开发过程中,你肯定会遇到各种问题。下面是一个快速排查指南:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 服务器启动失败,提示导入错误(ModuleNotFoundError) |
1. Python路径问题。
2. 依赖未安装。 |
1. 确认在项目根目录下运行,且
PYTHONPATH
设置正确。使用
python -m
方式运行。
2. 运行
pip install -e .
或
poetry install
安装所有依赖。
|
| Claude Desktop 无法发现/连接服务器 |
1. 配置文件路径或格式错误。
2. 命令或工作目录 (
cwd
) 错误。
3. 服务器进程启动失败。 |
1. 检查配置文件路径和JSON语法。
2. 确认
command
的Python路径和
args
正确,
cwd
指向项目根目录。
3. 查看Claude Desktop的日志(通常可在其设置中找到或通过命令行启动查看)。先手动在终端运行服务器命令,确保它能独立启动。 |
| 工具调用失败,返回“Tool not found” |
1. 工具未正确注册。
2. 工具名称不匹配。 |
1. 检查
server.py
中
app.list_tools()()
调用是否包含了你的工具。
2. 确保在客户端调用的工具名称与代码中
Tool(name="...")
定义的名字完全一致(大小写敏感)。
|
| 工具调用时参数验证失败 |
1. 客户端发送的参数格式不符合JSON Schema。
2. Pydantic模型验证失败。 |
1. 使用MCP Inspector测试,查看它生成的请求参数。
2. 在工具函数开头添加日志,打印接收到的
arguments
,检查其类型和值。确保你的Pydantic模型定义正确。
|
| 热重载不工作 |
1. 开发服务器未以热重载模式启动。
2. 文件监视路径不对。 |
1. 确认启动命令包含了开发模式标志(如
development_mode=True
)。
2. 检查项目结构,确保主文件在正确的包内,文件改动能被监听到。 |
| 异步函数内出现“事件循环”错误 | 在错误的上下文中调用了异步函数。 |
确保工具执行函数是
async def
,并且在异步上下文中被调用。不要在同步函数中直接
await
。MCP SDK会处理好事件循环。
|
一个关键的调试技巧
:在
server.py
或工具函数中增加详细的日志。配置Python的
logging
模块,将级别设为
DEBUG
,可以让你看到MCP协议层详细的请求和响应,对于定位通信问题非常有帮助。
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
最后,别忘了为你编写的工具和资源编写单元测试。
mcp-custom-dev
生成的项目通常包含了
tests/
目录和
pytest
配置。测试可以模拟MCP请求,验证你的业务逻辑在各种输入下的行为,这是保证代码质量、避免回归的最有效手段。
更多推荐



所有评论(0)