前言

大家好呀~ 我是菲菲,一名刚入门大模型开发的学妹!在跟着教程探索 Agent 与工具协作的过程中,“MCP 协议” 这个词频繁出现 —— 官网白皮书看着清晰易懂,可真要动手实操时,却总陷入 “一看就会、一写就懵” 的困境。后来发现,不只是我,很多初入行的小伙伴都对这个 “连接 Agent 与工具的桥梁” 感到困惑。
其实在 AI 时代,通过 MCP 协议让 Agent 获取业务上下文早已成为行业标配,“会调接口” 也成了 Agent 开发者的必备技能。为了帮自己和更多像我一样的初学者彻底吃透 MCP,我决定从 0 到 1 动手实现一个 MCP Server。这篇文章里,我会把自己的学习心得和实操过程毫无保留地分享出来:不仅会用通俗的语言拆解 MCP 协议的核心原理,还会手把手带大家完成代码实现,让每一位和我一样的学妹、学弟都能告别 “只听说过 MCP” 的尴尬,真正理解它的底层逻辑~ 跟着步骤一步步来,相信你也能轻松入门 MCP 开发!在 AI 时代,“会调接口” 已成为 Agent 开发者的必备技能。通过 MCP 协议让 Agent 获取业务上下文,早已成为行业标配,但官网白皮书往往 “一看就懂,一写就懵”。本文将手把手带您从 0 到 1 实现 MCP Server,全量剖析 MCP 协议及底层技术原理,让您彻底告别 “只听说过 MCP” 的尴尬。

一、MCP 协议核心概念解析

  1. 什么是 MCP?
    MCP(Model Context Protocol,模型上下文协议)是规范应用程序向大语言模型提供上下文的开放协议。其核心是实现 MCP Client 与 MCP Server 之间的标准化通信,让 Agent 开发与工具开发彻底解耦 —— 就像 Type-C 接口统一充电标准一样,MCP 统一了 Agent 调用工具的方式。
    简单来说,当 AI 客户端(MCP Host)需要获取上下文时,会通过集成的 MCP Client 向 MCP Server 发送请求,由 Server 返回模型所需的上下文数据。若 AI 客户端未集成 MCP Client,该协议则与 AI 无任何关联。
  2. 三大核心组件协作机制
    MCP 协议的正常运行依赖三个核心组件的协同工作:
    MCP Client:负责发送请求与接收响应,通常集成在 MCP Host 中,无需单独部署
    MCP Server:核心处理单元,负责接收请求、处理逻辑并返回上下文数据
    MCP Host:协议执行者,完整流程为:接收用户问题→选择工具→构建参数→通过 Client 调用 Server→解析结果→继续对话
    集成了 MCP Client 的智能体执行平台(如 IDEA LAB)或模型厂商的 Agent/AI 客户端(如 Cherry Studio),均可作为 MCP Host。以阿里云百练平台为例,MCP Host 即平台上的智能体应用,支持低代码、高代码等多种开发模式,可通过简单配置实现 MCP 服务调用。
  3. 与 Type-C、统一 function call 的关联
    若将 Agent 比作手机,工具接口比作充电线,那么 MCP Server 就相当于 Type-C 接口 —— 当所有 Agent/AI 客户端都集成 MCP Client,并用 MCP 协议调用工具时,就实现了 function call 的统一。这种标准化带来的价值,正如 Type-C 接口统一充电标准一样,打破了不同平台间的兼容性壁垒。
  4. 核心解决的两大问题
    简化模型调用流程:统一协议后,IDEA LAB、通义千问等平台的 AI 客户端内置 MCP Client,开发者只需将接口封装为 MCP Server 即可接入,选工具、拼参数、调接口、解析结果等流程由平台自动完成
    实现开发解耦:工具按 MCP Server 标准发布后,所有支持 MCP 协议的平台均可直接调用,无需关心 “选 - 拼 - 调 - 解” 全流程,实现工具开发与 Agent 开发的分离

二、MCP 技术原理深度拆解

MCP 协议的底层技术支撑主要包括 SSE(数据传输方式)、JSON-RPC 2.0(数据格式规范)和 MCP 自身的通信流程定义,三者共同构成了完整的技术体系。

  1. SSE:实时数据传输的核心载体
    SSE(Server-Sent Events)是基于 HTTP 协议的 Web 技术,专门用于服务器向客户端实时推送数据。其核心原理是通过建立持久 HTTP 连接,将 “请求 - 响应” 模式伪装成 “长时间下载”,让数据源源不断流向客户端。
    关键特性与规范
    基于 HTTP/1.1,复用现有基础设施,浏览器原生支持
    响应格式固定:Content-Type 为 text/event-stream,需设置 Cache-Control: no-cache 和 Connection: keep-alive
    消息体包含四个固定字段:
    data:实际负载数据
    id:事件序号,用于断线续传
    event:事件类型,前端通过 addEventListener 监听
    retry:断线重连间隔(毫秒)
    实现方式灵活:Spring 框架可通过同步阻塞的 SseEmitter 或响应式的 Flux实现
    两种技术栈实现对比
技术栈 模型 API对象 适用场景 备注
Spring MVC+Servlet 同步阻塞 SseEmitter 连接数可控、开发简单 Tomcat 线程数≈连接数
Spring WebFlux + Reactor 异步非阻塞 Flux 超高并发、低延迟 Netty 事件循环,少量线程支撑海量连接
  1. JSON-RPC 2.0:标准化消息格式
    JSON-RPC 2.0 是无状态、轻量级的远程过程调用协议,定义了 MCP Client 与 MCP Server 之间的通信格式,确保两端能零差异解析数据。
    核心消息结构(四固定字段)
字段 要求 类型 作用
jsonrpc 必选 固定值 “2.0” 协议版本号
method 必选 string 远端要执行的函数名
jsonrpc 可选 object/array 入参数据
jsonrpc 可选 string/number/null 请求匹配符,无 id 视为通知(不期待回复)
jsonrpc 二选一 - 成功返回 result,失败返回 error

完整范式示例

  • 请求示例:
{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "sampling": {},
      "roots": { "listChanged": true }
    },
    "clientInfo": { "name": "mcp-inspector", "version": "0.9.0" }
  },
  "id": 0
}
  • 成功响应示例:
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "capabilities": { "tools": { "listChanged": true } },
    "serverInfo": { "name": "SpringBoot MCP Server", "version": "1.0.0" },
    "protocolVersion": "2024-11-05"
  }
}
  1. MCP 协议:全生命周期通信规范
    MCP 协议在 SSE+JSON-RPC 2.0 基础上,定义了 “两通道、四步骤” 的全生命周期通信流程,明确了 “怎么连、怎么握手、怎么调工具、怎么结束” 的标准流程。
    通信两通道(缺一不可)
通信 作用 通道类型 方向
SSE Stream (GET /sse) 服务器→客户端推送事件 Server-Sent Events 单向
HTTP POST Endpoint 客户端→服务器发起调用 HTTP 双向请求 - 响应

通信四步骤
连:客户端发送 GET /sse 请求,建立 SSE 长连接,用于接收服务器推送
取:服务器返回 event: endpoint 消息,提供客户端后续通信的 POST 端点 URI
握:客户端向 POST 端点发送两包 JSON-RPC:
initialize 请求:协商协议版本和能力
notifications/initialized 通知:确认握手完成
用:正常会话阶段,支持 tools/list(获取工具列表)、tools/call(调用具体工具),服务器通过 SSE 流推送状态更新
断:任一端关闭 SSE 连接,会话结束

三、从零实现 MCP Server(实战教程)

任何语言 / 框架实现 MCP Server,只需满足三个条件:开启 SSE 端点、开启 POST 端点、实现 “四步骤” 所需接口。以下将用 Spring Boot+WebFlux+Jackson 技术栈,带您实现一个完整的 MCP Server。

  1. 项目准备
    技术栈与依赖
    核心框架:Spring Boot + WebFlux
    序列化工具:Jackson
    测试工具:MCP Inspector、内置 HTML 测试页面
    项目结构

springboot-mcp-server/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── HelloWorldApplication.java
│ │ │ ├── config/ // HTTP配置
│ │ │ │ └── McpConfig.java
│ │ │ └── mcp/
│ │ │ ├── protocol/ // JSON-RPC 2.0请求响应模型
│ │ │ │ ├── McpRequest.java
│ │ │ │ └── McpResponse.java
│ │ │ ├── transport/ // 传输端点(SSE+POST)
│ │ │ │ └── SseTransport.java
│ │ │ ├── server/ // MCP协议处理逻辑
│ │ │ │ └── McpServerHandler.java
│ │ │ └── tools/ // 工具注册与调用
│ │ │ └── McpToolRegistry.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── static/
│ │ └── mcp-test.html // 内置测试页面
│ └── test/
│ └── java/com/example/HelloWorldApplicationTests.java
└── README.md

  1. 核心组件实现
    (1)SSE 端点与 POST 端点构建(SseTransport.java)
    首先实现 MCP 协议要求的两通道,确保客户端能建立连接并发送请求。
    SSE 端点(GET /sse):
/**
 * 标准MCP SSE端点 - 客户端连接此端点接收服务器推送消息
 */
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> sseEndpoint(@RequestParam(required = false) String clientId,
                                                ServerHttpRequest request) {
    // 安全检查:验证Origin头防止DNS重绑定攻击
    String origin = request.getHeaders().getFirst("Origin");
    if (origin != null && !isValidOrigin(origin)) {
        System.err.println("Invalid origin rejected: " + origin);
        return Flux.error(new SecurityException("Invalid origin"));
    }

    String sessionId = clientId != null ? clientId : "client-" + System.currentTimeMillis();
    // 为每个客户端创建独立消息流
    Sinks.Many<ServerSentEvent<String>> sink = Sinks.many().multicast().onBackpressureBuffer();
    clientSinks.put(sessionId, sink);

    System.out.println("MCP SSE client connected: " + sessionId + " from origin: " + origin);

    // 30秒心跳流,维持连接
    Flux<ServerSentEvent<String>> heartbeat = Flux.interval(Duration.ofSeconds(30))
            .map(tick -> ServerSentEvent.<String>builder()
                    .event("ping")
                    .data("{\"type\":\"ping\"}")
                    .build());

    // 合并消息流与心跳流,发送endpoint事件(MCP协议要求)
    return Flux.merge(sink.asFlux(), heartbeat)
            .doOnSubscribe(subscription -> {
                try {
                    String endpointUri = getBaseUrl(request) + "/message/" + sessionId;
                    // 发送endpoint事件,提供POST通信地址
                    ServerSentEvent<String> endpointEvent = ServerSentEvent.<String>builder()
                            .event("endpoint")
                            .data(endpointUri)
                            .build();
                    sink.tryEmitNext(endpointEvent);
                    System.out.println("Sent endpoint event to client: " + sessionId + " with URI: " + endpointUri);
                } catch (Exception e) {
                    System.err.println("Error sending endpoint event: " + e.getMessage());
                }
            })
            .doOnCancel(() -> {
                System.out.println("MCP SSE client disconnected: " + sessionId);
                clientSinks.remove(sessionId);
                sink.tryEmitComplete();
            })
            .doOnError(error -> {
                System.err.println("MCP SSE error for client " + sessionId + ": " + error.getMessage());
                clientSinks.remove(sessionId);
            })
            .onErrorResume(error -> {
                System.err.println("SSE stream error, attempting to recover: " + error.getMessage());
                return Flux.empty();
            });
}

POST 端点(POST /message/{sessionId}):

/**
 * 处理客户端MCP请求 - 客户端通过POST发送JSON-RPC消息
 */
@PostMapping(value = "/message/{sessionId}", consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<Void> handleSessionMessage(@PathVariable String sessionId,
                                       @RequestBody String messageJson,
                                       ServerHttpRequest request) {
    return handleMessageInternal(sessionId, messageJson, request);
}

private Mono<Void> handleMessageInternal(String sessionId, String messageJson, ServerHttpRequest request) {
    return Mono.fromRunnable(() -> {
        try {
            // 安全校验与消息解析
            String origin = request.getHeaders().getFirst("Origin");
            if (origin != null && !isValidOrigin(origin)) {
                System.err.println("Invalid origin rejected for message: " + origin);
                return;
            }

            McpRequest mcpRequest = objectMapper.readValue(messageJson, McpRequest.class);
            System.out.println("Received MCP request: " + mcpRequest.getMethod() +
                    " (id: " + mcpRequest.getId() + ") from session: " + sessionId);

            // 处理请求并通过SSE返回响应
            McpResponse response = serverHandler.handleRequest(mcpRequest);
            if (response != null) {
                sendMessageToClient(sessionId, response);
            }
        } catch (Exception e) {
            System.err.println("Error processing MCP message: " + e.getMessage());
            e.printStackTrace();
            // 发送错误响应
            try {
                McpRequest mcpReq = objectMapper.readValue(messageJson, McpRequest.class);
                McpResponse errorResponse = new McpResponse();
                errorResponse.setId(mcpReq.getId());
                errorResponse.setError(new McpResponse.McpError(-32603, "Internal error: " + e.getMessage()));
                sendMessageToClient(sessionId, errorResponse);
            } catch (Exception ex) {
                System.err.println("Error sending error response: " + ex.getMessage());
            }
        }
    });
}

/**
 * 向指定客户端发送MCP响应消息
 */
public void sendMessageToClient(String sessionId, Object message) {
    Sinks.Many<ServerSentEvent<String>> sink = clientSinks.get(sessionId);
    if (sink != null) {
        try {
            String jsonData = objectMapper.writeValueAsString(message);
            ServerSentEvent<String> event = ServerSentEvent.<String>builder()
                    .event("message")
                    .data(jsonData)
                    .build();
            sink.tryEmitNext(event);
            System.out.println("Sent MCP message to client: " + sessionId);
        } catch (Exception e) {
            System.err.println("Error sending message to client " + sessionId + ": " + e.getMessage());
        }
    } else {
        System.err.println("No active connection found for session: " + sessionId);
    }
}

(2)协议核心逻辑处理(McpServerHandler.java)
实现 MCP 通信 “四步骤” 中的握手与工具调用逻辑,处理 initialize、tools/list、tools/call 等关键请求。

public McpResponse handleRequest(McpRequest request) {
    String method = request.getMethod();
    String id = request.getId();
    Map<String, Object> params = request.getParams();

    try {
        switch (method) {
            // 处理初始化请求:协商协议版本与能力
            case "initialize":
                return handleInitialize(id, params);
            
            // 处理工具列表请求:返回可用工具
            case "tools/list":
                return handleListTools(id);
            
            // 处理工具调用请求:执行具体工具逻辑
            case "tools/call":
                return handleCallTool(id, params);
            
            // 客户端初始化完成通知:无需返回响应
            case "notifications/initialized":
                this.initialized = true;
                System.out.println("Client initialization completed");
                return null;
            
            // 未知方法处理
            default:
                McpResponse errorResponse = new McpResponse();
                errorResponse.setId(id);
                errorResponse.setError(new McpResponse.McpError(-32601, "Method not found: " + method));
                return errorResponse;
        }
    } catch (Exception e) {
        System.err.println("Error handling request " + method + ": " + e.getMessage());
        McpResponse errorResponse = new McpResponse();
        errorResponse.setId(id);
        errorResponse.setError(new McpResponse.McpError(-32603, "Internal error: " + e.getMessage()));
        return errorResponse;
    }
}
  1. 测试验证
    方式一:使用 MCP Inspector 测试
    启动 Spring Boot 应用,默认端口 8080
    打开 MCP Inspector v0.9.0
    选择 “SSE Transport”,输入 URL:http://localhost:8080/sse
    点击 “Connect”,按顺序执行:
    Initialize(初始化握手)
    List Tools(获取工具列表)
    Call Tools(调用具体工具)
    方式二:使用内置测试页面
    访问:http://localhost:8080/mcp-test.html
    点击 “Connect to MCP Server” 建立连接
    依次执行:Initialize → List Tools → Call Hello World → Get Time → Echo Message,验证全流程
  2. 实现效果
    通过上述代码,我们完成的 MCP Server 具备以下能力:
    支持 SSE 长连接建立与心跳维持
    按 MCP 协议要求发送 endpoint 事件
    处理 initialize 握手流程,协商协议能力
    支持工具列表查询与具体工具调用
    异常处理与错误响应反馈

四、总结

MCP 协议通过 SSE(传输通道)+ JSON-RPC 2.0(数据格式)+ 标准化流程(通信规范)的组合,成功解决了 Agent 与工具之间的协作难题。通过亲手实现 MCP Server,我们不仅掌握了 “两通道、四步骤” 的核心流程,更理解了其 “解耦开发、统一标准” 的设计理念。
在大模型应用开发日益普及的今天,MCP 作为连接 Agent 与工具的 “桥梁”,正在重塑行业开发模式。掌握 MCP 技术原理,将帮助开发者在 Agent 开发领域构建更灵活、更具兼容性的应用系统,为业务创新提供强大支撑。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐