For the complete documentation index, see llms.txt. This page is also available as Markdown.

9.3 MCP 服务端开发

本节详细讲解如何从零开始构建 MCP Server,包括基本开发步骤、完整的 Python 实现示例、关键概念说明以及错误处理方案。通过这些实现细节,你将掌握如何定义工具(Tool)、资源(Resource)和提示词(Prompt),选择合适的传输方式,并确保 Server 的稳定性。

示例的协议版本:本节代码按当前修订版 2026-07-28 的无状态模型编写——没有 initialize 握手,协议版本与客户端能力随每个请求的 params._meta 传入,服务端实现 server/discover 供客户端一次性发现,每个 result 带 resultType,列表与读取类结果带 ttlMs/cacheScope。若要对接仍讲 2025-11-25 的旧客户端,见 9.1 协议设计 的向后兼容小节与本节末尾的旧流量处理建议。

9.3.1 开发 MCP Server 的基本步骤

创建一个完整的 MCP Server 需要:

  1. 定义 Tools(可调用的函数)

  2. 定义 Resources(可访问的数据)

  3. 定义 Prompts(提示词模板)

  4. 实现处理器

  5. 选择传输方式并启动 Server

9.3.2 完整的 Python MCP Server 实现

MCP Server 的完整 Python 实现包括四个主要部分:工具定义、资源处理、不同传输方式的实现,以及主函数。下面是教学用的最小协议骨架,用来解释 JSON-RPC 消息结构;生产项目应优先使用官方或社区维护的 MCP SDK。

第一部分:数据模型与工具定义

首先需要定义数据模型来表示工具、资源和提示词:

# mcp_server.py
# 一个完整的MCP Server示例,提供文件系统工具和资源访问

import json
import os
import subprocess
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, asdict
from enum import Enum
import asyncio
import sys
from pathlib import Path

@dataclass
class MCPTool:
    """MCP工具定义"""
    name: str
    description: str
    inputSchema: Dict[str, Any]

@dataclass
class MCPResource:
    """MCP资源定义"""
    uri: str
    name: str
    description: str
    mimeType: str  # MIME 类型

@dataclass
class MCPPrompt:
    """MCP提示词定义"""
    name: str
    description: str
    arguments: List[Dict[str, Any]]

class MCPServerBase:
    """MCP Server基类"""

    def __init__(self):
        self.tools: Dict[str, MCPTool] = {}
        self.resources: Dict[str, MCPResource] = {}
        self.prompts: Dict[str, MCPPrompt] = {}
        self.tool_handlers: Dict[str, callable] = {}
        self.resource_readers: Dict[str, callable] = {}
        self.prompt_generators: Dict[str, callable] = {}

    def register_tool(
        self, name: str, description: str,
        input_schema: Dict[str, Any], handler: callable
    ) -> None:
        """注册工具"""
        self.tools[name] = MCPTool(name, description, input_schema)
        self.tool_handlers[name] = handler

    def register_resource(
        self, uri: str, name: str, description: str,
        mime_type: str, reader: callable
    ) -> None:
        """注册资源"""
        self.resources[uri] = MCPResource(uri, name, description, mime_type)
        self.resource_readers[uri] = reader

    def register_prompt(
        self, name: str, description: str,
        arguments: List[Dict[str, Any]], generator: callable
    ) -> None:
        """注册提示词"""
        self.prompts[name] = MCPPrompt(name, description, arguments)
        self.prompt_generators[name] = generator

设计说明:基类采用了“注册模式”(Registration Pattern),允许在运行时动态注册工具、资源和提示词。这提供了灵活性,使不同的 Server 实例可以有不同的功能集合。

第二部分:请求处理与 JSON-RPC 协议

MCP 使用 JSON-RPC 2.0 协议进行通信。以下是请求处理的核心逻辑:

设计说明:请求处理是 MCP 通信的“中枢”。每个请求必须返回合法的 JSON-RPC 2.0 响应,包括 id、result 或 error。这确保了客户端可以准确关联请求和响应,即使在异步场景中也不会混淆。

第三部分:资源与提示词处理

现在实现具体的处理方法,展示工具、资源和提示词的导出:

设计说明:这些处理方法遵循了一致的模式:列表方法返回所有已注册的对象、调用方法执行对应的处理器。注意 _handle_prompts_get 支持参数,使提示词能够根据不同的上下文动态生成。上面的返回结构沿用 2025-11-25 修订版;若要适配 2026-07-28 修订版,有两个细节容易被忽略:其一,每个 result 都必须携带 resultType,取值是 "complete""input_required";客户端遇到旧服务端返回的、没有 resultType 的结果时按 "complete" 处理。其二,tools/listprompts/listresources/listresources/readresources/templates/list 的结果还必须带 ttlMs(新鲜度提示,毫秒)和 cacheScope"public""private"),客户端据此缓存——把用户私有的文件内容标成 "public" 会让它被跨用户复用。另外,只有 prompts/getresources/readtools/call 可以返回 resultType"input_required" 的结果,用来向客户端索取输入(如获取根目录列表、请求用户确认或请求模型补全),客户端补齐输入后用新的 JSON-RPC id 重试同一请求。

第四部分:文件系统实现与传输

最后,我们实现一个具体的文件系统 Server 和两种传输方式。以下展示文件系统工具的实现:

设计说明:文件系统 Server 展示了三个关键的安全做法:(1) 限制可访问的根路径;(2) 验证路径不会逃出根目录;(3) 在执行操作前解析并验证所有路径。这是处理用户输入的敏感操作(如文件访问)的标准防御方式。上面代码使用 resolved.relative_to(self.root_path.resolve()) 进行边界检查是最佳实践——相比字符串前缀比较(resolved.startswith()),它能正确处理 Windows 路径、符号链接和相对路径。完整的五层递进式路径校验实践(长度检查、URL 编码双解码、Unicode 规范化、平台特定规范化、符号链接解析与边界检查)详见第 12.4 节。

第五部分:传输层实现

MCP 支持多种传输方式。以下展示 stdio 和 HTTP 两种传输的简化实现:

stdio 传输的优势:(1) 无需网络配置,完全通过管道通信;(2) 天然支持进程隔离;(3) 无 HTTP 分帧与连接管理开销,只需逐行 JSON 处理。这使 stdio 成为本地工具集成的理想选择。这里要提醒一个常见误解:进程一直开着并不等于“会话一直在”。上面的示例遵循 2025-11-25 修订版,仍带有 initialize 握手;而在 2026-07-28 修订版中,握手与协议级会话已被移除,服务端不得把前一条请求留下的信息当作后一条请求的上下文,协议版本与客户端能力改为随每个请求放在 params._meta 中传递。迁移时,凡是原本在握手期建立、后续请求默认可见的状态,都要改成由客户端每次显式传入的标识。

HTTP 传输的优势:(1) 支持远程访问,跨机器通信;(2) GET 流连接用于 Server 推送事件;(3) 标准的 HTTP 协议,易于在网络中部署和监控。

第六部分:启动函数

最后,主函数根据命令行参数选择传输方式:

Server 开发的关键概念

1. 工具定义的 JSON Schema

工具的inputSchema应该是完整的 JSON Schema,包含:

  • type: 必须是“object”

  • properties: 参数定义

  • required: 必需参数列表

2. 资源 URI 规范

Resources 应该有结构化的 URI(统一资源标识符):

3. 提示词参数化

Prompts 可以接受参数,在生成消息时使用这些参数:

4. 面向旧版客户端的兼容

真实环境里仍有大量按 2025-11-25 修订版实现的客户端和服务端,兼容问题绕不开。如果你的服务端只实现 2026-07-28,就要让旧流量快速失败,而不是让它挂在半路:

  • MCP 端点上的 GET 与 DELETE 一律返回 405 Method Not Allowed

  • 收到 Mcp-Session-Id 请求头直接忽略,不要据此建立或恢复会话;

  • 收到 Last-Event-ID 请求头直接忽略,新版不再支持 SSE 断点续传。

如果服务端要同时服务两代客户端,就得在同一端点上保留旧的 initialize/notifications/initialized 握手分支和旧的结果格式,按请求实际使用的修订版分派。这类兼容代码应当有明确的下线时间,而不是长期并存。反过来,客户端探测服务端时的做法是:先按新版发请求,如果收到 HTTP 400 就检查响应体——能解析出新版定义的 JSON-RPC 错误,说明对方是新版服务端,改正请求重试即可;响应体为空或无法识别,才回退到旧的 initialize 握手。stdio 场景下没有 HTTP 状态码可用,server/discover 就是那次探测请求:旧服务端不认识它,会按未知方法返回 -32601

注意区分“移除”和“弃用”:Roots、Sampling、Logging 属于后者,它们至少还有 12 个月的弃用期,现有服务端继续可用,但新写的服务端不应再依赖——需要目录或文件范围就用工具参数、资源 URI 传入,需要模型补全就直接调用模型提供方的 API,需要日志就写 stderr 或接入 OpenTelemetry。

错误处理

MCP Server 应该正确处理和报告错误。除 JSON-RPC 2.0 标准错误码外,2026-07-28 修订版还增加了几个 MCP 专有错误码:-32020(请求头中的协议版本与 _meta 中的不一致)、-32021(客户端未声明服务端所需的能力)、-32022(不支持的协议版本);资源不存在的错误码也从 -32002 改成了 -32602,但客户端仍应接受旧服务端返回的 -32002

本小节小结

开发 MCP Server 的核心是:

  1. 定义 Tool、Resource 和 Prompt 的 Schema

  2. 实现对应的处理器

  3. 选择合适的传输方式

  4. 正确处理错误和异常

关键要点:

  • JSON Schema 应该准确且完整

  • 资源 URI 应该有明确的结构

  • 提示词应该支持参数化

  • 错误响应应该遵循 JSON-RPC 2.0 规范

下一节将讨论 Harness 如何在系统级别集成多个 MCP Server。

最后更新于