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协议本身定义了一套清晰的规范,但实现起来,开发者需要处理不少“脏活累活”:

  1. 协议样板代码 :每个MCP服务器都需要实现标准化的Stdio通信、处理初始化( initialize )、列出资源/工具( list_ )、处理调用请求等。这些代码结构固定,但手动编写枯燥且易错。
  2. 开发体验割裂 :传统的开发流程可能是:写代码 -> 编译/打包 -> 启动服务器 -> 在另一个终端连接测试 -> 发现问题 -> 修改代码 -> 重启... 这个循环非常低效。
  3. 类型安全缺失 :MCP请求和响应的数据结构复杂。如果没有良好的类型提示,在Python里很容易因为字段名拼写错误或类型不匹配导致运行时错误,调试起来很头疼。
  4. 部署与配置 :如何将开发好的服务器方便地集成到AI客户端(如Claude Desktop)中?如何管理环境变量和配置?

CCCpan/mcp-custom-dev 正是瞄准了这些问题。它的设计思路非常明确: 提供一套开箱即用、体验流畅的MCP服务器开发框架 。它不是一个运行时,而是一个高度集成的开发环境(Dev Environment)。

2.2 项目架构与核心模块

虽然项目本身可能还在迭代,但根据其命名和常见模式,我们可以推断其架构通常包含以下几个核心部分:

  1. 脚手架生成器(Scaffolding Generator) :这是项目的入口点。通过一条命令(比如 mcp-dev init my-server ),就能生成一个完整的、结构清晰的MCP服务器项目目录。这个目录里通常会预置:

    • pyproject.toml 或 requirements.txt :管理项目依赖,核心依赖必然是官方 mcp SDK。
    • 主服务器文件(如 server.py ):包含一个继承了基础MCP服务器类的骨架代码,其中已经定义好了标准生命周期方法的占位符。
    • 资源(Resource)和工具(Tool)的模块化目录:鼓励你将不同的功能模块化存放,例如 resources/ 目录下放数据源定义, tools/ 目录下放可执行操作。
    • 配置文件(如 .env.example , config.yaml ):用于管理服务器行为和环境变量。
    • 开发工具配置:如 Dockerfile 用于容器化, docker-compose.yml 用于本地编排,以及用于代码质量检查的 pre-commit 配置。
  2. 开发服务器与热重载(Dev Server with Hot Reload) :这是提升体验的关键。项目会内置一个开发服务器命令(如 mcp-dev run )。这个命令会启动你的MCP服务器,并监听文件变化。当你修改了资源定义、工具逻辑或者任何业务代码后,开发服务器会自动重启,而无需你手动中断再启动。这极大地缩短了开发反馈循环。

  3. 类型存根与代码提示(Type Stubs & IntelliSense) :优秀的框架会充分利用Python的类型提示。 mcp-custom-dev 应该提供了完善的类型定义,使得你在IDE(如VSCode, PyCharm)中编写工具函数时,能获得参数类型、返回值类型的自动补全和错误检查,将很多bug消灭在编码阶段。

  4. 集成测试工具(Integrated Testing Utilities) :它可能会提供一些辅助函数或夹具(Fixtures),让你能够方便地模拟MCP客户端的请求,对你自己编写的工具和资源处理器进行单元测试或集成测试,确保逻辑正确。

  5. 客户端连接调试工具(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客户端。有几个选择:

  1. 官方MCP Inspector :这是一个图形化调试工具,可以连接到本地服务器,手动调用工具、读取资源,非常适合开发阶段。你需要单独安装它。
  2. Claude Desktop :这是最终集成的目标之一。你可以在Claude Desktop的配置中添加你的本地服务器(通过SSE或stdio),然后在聊天窗口中直接测试。
  3. 编写简单的测试脚本 :对于自动化测试,可以基于 mcp SDK的客户端库写一个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的配置文件中添加你的服务器。

  1. 找到配置文件 :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
  2. 添加服务器配置 :在配置文件的 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" // 工作目录很重要
    }
  }
}
  1. 重启Claude Desktop :保存配置文件并完全重启Claude Desktop。
  2. 验证 :在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 性能优化与最佳实践

  1. 异步与并发 :MCP服务器本质是IO密集型的(处理请求、调用外部API)。确保你的工具执行函数是 async 的,并在内部使用异步HTTP客户端(如 httpx.AsyncClient )、异步数据库驱动等。合理使用 asyncio.gather 来并发执行多个独立的外部调用。
  2. 错误处理与重试 :在工具函数中,对外部服务调用添加重试逻辑(使用 tenacity 库)和详细的错误日志。返回给AI的错误信息应友好且包含可操作建议(例如“天气服务暂时不可用,请稍后再试”而非堆栈跟踪)。
  3. 资源管理 :对于需要昂贵连接(如数据库连接池、大型模型)的资源,应在服务器初始化( initialize )时创建,并在一个生命周期内复用。避免在每个工具调用中创建和销毁。
  4. 工具设计的原子性 :一个工具最好只做一件事。不要设计一个“查询天气并发送邮件”的工具。将其拆分为“查询天气”和“发送邮件”两个工具,由AI来组合调用。这更符合MCP的哲学,也更具灵活性。
  5. 详细的描述 :工具和资源的 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请求,验证你的业务逻辑在各种输入下的行为,这是保证代码质量、避免回归的最有效手段。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐