MCP Java SDK 2.2.0-M2 把 modern 服务端(协议修订 2026-07-28)里的工具级 requiredCapabilities 落到可下载的里程碑:客户端在 _meta 里没声明对应能力时,服务端在 handler 之前直接返回 JSON-RPC 错误码 -32021(Missing required client capability),本地对照调用里 confirm_handler_ran=false。同版本还带了 McpTransportContext.putAll:后写入的 map 键覆盖用户 extractor 的同名键。本地用 Spring Boot 4.0.5 + io.modelcontextprotocol.sdk:mcp:2.2.0-M2 起了 Servlet modern 服务并用 JDK HttpClient 打 tools/call;代码已编译验证,未实际调用外部模型 API。

Git 注解标签 v2.2.0-M2 的 tagger 时间是 2026-11-03 01:26:39(Asia/Shanghai);仓库里目前没有同名的 GitHub Release 页面,以标签与 Maven Central 为准。Central 上 mcp-2.2.0-M2.pom 的 Last-Modified 是 2026-11-03 01:41:22(Asia/Shanghai)。CHANGELOG「Unreleased (2.2.0)」写明:modern 包预览无状态服务端、支持 required client capabilities,以及 McpTransportContext.putAll(Map)。相关 PR:#1283 Enforce required client capabilities(合并 2026-10-27 19:30:55 Asia/Shanghai)、#1282 find 返回 handler(同日)、#1288 putAll(2026-10-31 23:12:36 Asia/Shanghai)、#1286 SEP-2243 Mcp-Param(2026-11-02 16:45:04 Asia/Shanghai)。官方文档 server-modern.md 同步在该标签上。

MCP Java SDK 2.1.0 工具过滤那篇讲的是经典协议线上的 addToolFilter;本文只核 modern 线上的能力门禁与 putAll 优先级,不复述 2.1 会话清扫。Spring AI 侧 #7284 已于 2026-11-03 05:23:26(Asia/Shanghai)合入 main,为 2026-07-28 modern 协议加服务端支持,但本稿截稿时仍走未发布快照线,下面动手用的是裸 MCP SDK + spring-boot-starter-web。

工具怎么声明 requiredCapabilities

McpSyncTool.of(tool, ClientCapabilities, handler) 的三参数重载会把能力要求绑在工具上。缺能力时 RequiredCapabilities 拦截,handler 不会执行。本例给 confirm_delete 要求 elicitation.form;无 ping 不要求能力。

McpSyncTool confirmDelete = McpSyncTool.of(
    Tool.builder("confirm_delete", Map.of(
            "type", "object",
            "properties", Map.of("id", Map.of("type", "string")),
            "required", List.of("id")))
        .description("needs elicitation form")
        .build(),
    ClientCapabilities.builder()
        .elicitation(new ClientCapabilities.Elicitation(
            new ClientCapabilities.Elicitation.Form(), null))
        .build(),
    (ctx, request) -> {
        CONFIRM_HANDLER_RAN.set(true);
        String id = String.valueOf(request.arguments().get("id"));
        return McpSyncResponse.result(InputRequiredResult.builder()
            .elicit("confirm", ElicitFormRequest.builder(
                "Delete task " + id + "?",
                Map.of(
                    "type", "object",
                    "properties", Map.of("ok", Map.of("type", "boolean")),
                    "required", List.of("ok")))
                .build())
            .requestState(id)
            .build());
    });

客户端能力写在每次请求 params._meta 的键 io.modelcontextprotocol/clientCapabilities;协议版本键是 io.modelcontextprotocol/protocolVersion,值 2026-07-28。Servlet 传输还要求 HTTP 头 Mcp-Method、MCP-Protocol-Version,以及 tools/call 时的 Mcp-Name。

本地验证

环境:JDK 21.0.12.1、Maven 3.9.9、Spring Boot 4.0.5、mcp 2.2.0-M2。进程监听 18089,注册 HttpServletMcpTransport 到 /mcp,ApplicationRunner 用 JDK HttpClient 连本机。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.0.5</version>
</parent>

<dependency>
  <groupId>io.modelcontextprotocol.sdk</groupId>
  <artifactId>mcp</artifactId>
  <version>2.2.0-M2</version>
</dependency>
mvn -q -DskipTests package
mvn -q spring-boot:run

启动后的关键输出如下(原文摘录):

=== putAll precedence (in-process) ===
user.role=guest merged.role=transport-admin merged.tenant=acme merged.region=cn
=== server/discover ===
HTTP 200
{
  "jsonrpc" : "2.0",
  "id" : 1,
  "result" : {
    "supportedVersions" : [ "2026-07-28" ],
    "capabilities" : {
      "tools" : {
        "listChanged" : false
      }
    },
    ...
  }
}
=== tools/call ping (no required caps) ===
HTTP 200
{
  "result" : {
    "content" : [ { "type" : "text", "text" : "pong:guest" } ],
    "resultType" : "complete"
  }
}
=== tools/call confirm_delete WITHOUT elicitation ===
HTTP 400
{
  "error" : {
    "code" : -32021,
    "message" : "Missing required client capability",
    "data" : {
      "requiredCapabilities" : {
        "elicitation" : { "form" : { } }
      }
    }
  }
}
confirm_handler_ran=false
=== tools/call confirm_delete WITH elicitation.form ===
HTTP 200
{
  "result" : {
    "inputRequests" : {
      "confirm" : {
        "method" : "elicitation/create",
        "params" : {
          "message" : "Delete task t-42?",
          "mode" : "form"
        }
      }
    },
    "resultType" : "input_required"
  }
}
confirm_handler_ran=true

-32021 拦在 handler 前

同一进程里:空 clientCapabilities 调 confirm_delete 得到 HTTP 400、error.code=-32021,且 confirm_handler_ran=false;补上 "elicitation":{"form":{}} 后 handler 才跑,返回 resultType=input_required 的 elicitation 表单。不要求能力的 ping 在空能力声明下仍返回 pong:guest(角色来自 X-Role → transport context)。

{
  "params": {
    "name": "confirm_delete",
    "arguments": { "id": "t-42" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "elicitation": { "form": {} }
      }
    }
  }
}

putAll:后写入的键覆盖

in-process 断言:用户 context 是 role=guest, tenant=acme;putAll({role=transport-admin, region=cn}) 之后 merged.role=transport-admin,保留 tenant=acme,并带上 region=cn。CHANGELOG 写明后写入条目优先,便于传输层在用户 extractor 之上叠自己的键。

McpTransportContext user =
    McpTransportContext.create(Map.of("role", "guest", "tenant", "acme"));
McpTransportContext merged =
    user.putAll(Map.of("role", "transport-admin", "region", "cn"));
// user.role=guest merged.role=transport-admin
// merged.tenant=acme merged.region=cn

以下是对官方说明的归纳,与里程碑原文区分开:写需要用户确认或 sampling 的工具时,应在服务端用 McpSyncTool.of(..., requiredCapabilities, handler) 声明门槛,让不支持该能力的客户端在进业务前就被拒;传输层若要覆盖 extractor 里的同名键,用 putAll,后写覆盖先写。M2 仍是里程碑,API 在 3.0 前可能微调;Spring AI 对 modern 的 starter 封装需等其发布线,当前可先用 SDK 直接挂 Servlet。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。