找回密码
 立即注册
首页 业界区 业界 上下文协议(MCP)Java SDK 指南

上下文协议(MCP)Java SDK 指南

豌笆 4 小时前
当我们把各种内部系统、数据源、工具接入大语言模型时,往往会遇到一个尴尬的问题:每个团队、每套系统都有自己的一套“接入规范”。有的用 HTTP API,有的用消息队列,有的直接连数据库,最后一圈串下来,既难以统一治理,又很难在不同应用之间复用。这时,你可能会问:有没有一种通用的协议,既能让 AI 模型方便地调用外部工具、访问数据,又能让后端服务方用标准方式暴露能力?
Model Context Protocol(MCP)就是为此而生的标准之一,而本文要介绍的 Java SDK,则为 Java 开发者提供了一条直接接入 MCP 生态的通路。通过它,你可以用统一的模型,在 Java 应用里暴露工具、资源、提示模版,也可以轻松作为客户端去调用这些能力。本文将从整体架构讲起,一步步带你用一个可运行的示例,搭建起自己的 MCP 服务端与客户端。
1. 概览

随着近年来 AI 的快速发展,越来越多的工具和系统开始与 AI 模型集成。但随之而来的一个挑战是:每种集成都可能采用完全不同的标准和方式,将外部工具、资源和系统接入到 AI 模型中。
Model Context Protocol(MCP)是一个开源标准,它定义了 AI 应用(如大语言模型、图像生成模型等)与工具、数据源以及其他资源之间的集成方式。借助 MCP,AI 应用可以按外部系统约定的方式访问数据、调用工具并执行工作流。
MCP 的 Java SDK 为开发者提供了一组库,支持多种协议和通信机制,用于把 Java 应用与 AI 应用连接起来。
在本教程中,我们将一起了解这个 SDK,并通过一个简单示例来体验 MCP 的使用方式。
2. 架构

MCP 架构的核心组件主要包括:

  • MCP Host:负责管理多个 MCP Client
  • MCP Client:从 MCP Server 获取上下文,供 MCP Host 使用
  • MCP Server:向 MCP Client 提供上下文信息和可调用能力
MCP 将通信划分为两个概念层次:数据层(Data Layer),用于定义客户端与服务端的通信协议和生命周期管理;以及 传输层(Transport Layer),用于定义客户端和服务端之间的具体传输通道和机制
Java 版的 MCP SDK 将这些概念映射为如下几个层次:

  • Client/Server 层:通过 McpClient / McpServer 实现并管理客户端/服务端的具体操作
  • Session 层:通过 McpSession 管理通信模式和会话状态
  • Transport 层:通过 McpTransport 处理消息的序列化与反序列化
客户端会调用 MCP 服务端暴露的一到多个工具(tool),而底层的通信则由传输层负责。
在 MCP 中,Primitive(原语) 是最基础的构建单元,用来定义可用的上下文信息类型以及可执行的操作范围。服务端和客户端都提供了一些原语。
服务端侧的原语包括工具(tools)、资源(resources)和提示模版(prompts)。工具是 AI 应用可以调用的可执行函数,例如查询数据库、文件操作等。资源是提供给客户端的上下文数据源,例如数据库结构、文件内容等。提示模版是可复用的模版,用于与语言模型进行交互
客户端侧的原语则帮助 McpServer 的实现者构建更丰富的交互能力,包括采样(sampling)、信息补充(elicitation)和日志(logging)。采样允许服务端在不集成模型 SDK 的情况下,向客户端请求语言模型补全结果。 信息补充让服务端能够向用户请求额外信息或确认操作。 日志则允许服务端向客户端发送日志消息,用于调试和监控。
3. 环境准备

要使用 MCP Java SDK,我们需要在项目中加入 mcp 依赖:
  1. <dependency>
  2.     <groupId>io.modelcontextprotocol.sdk</groupId>
  3.     mcp</artifactId>
  4.     <version>0.15.0</version>
  5. </dependency>
复制代码
3.1 定义一个 MCP 工具

我们先通过 LoggingTool 这个类,定义一个非常简单的 MCP 工具,用来打印收到的提示词(prompt),该方法返回一个 SyncToolSpecification:
  1. public class LoggingTool {
  2.     public static McpServerFeatures.SyncToolSpecification logPromptTool() {
  3.         McpSchema.JsonSchema inputSchema = new McpSchema.JsonSchema("object",
  4.             Map.of("prompt", String.class), List.of("prompt"), false, null, null);
  5.         return new McpServerFeatures.SyncToolSpecification(
  6.             new McpSchema.Tool(
  7.               "logPrompt", "Log Prompt","Logs a provided prompt", inputSchema, null, null, null),
  8.                 (exchange, args) -> {
  9.                     String prompt = (String) args.get("prompt");
  10.                     return McpSchema.CallToolResult.builder()
  11.                       .content(List.of(new McpSchema.TextContent("Input Prompt: " + prompt)))
  12.                       .isError(false)
  13.                       .build();
  14.                 });
  15.     }
  16. }
复制代码
这里我们首先定义了输入的 JSON Schema,用来为用户输入建立一个清晰的契约。接着,使用该输入 Schema 来实例化一个 Tool,在处理逻辑中提取出 prompt 参数,并最终返回包含该 prompt 的 TextContent 结果。
4. MCP 客户端与服务端搭建

接下来,我们需要一个 MCP 服务端来暴露自定义工具,以及一个或多个 MCP 客户端,用于连接该服务端并调用其中的工具。
4.1 MCP 服务端实现

McpServer 具有一组能力(capabilities),用来告知客户端当前服务器支持哪些类别的协议操作,例如日志记录、提示词补全、资源访问等。此外,工具(tools)则提供给客户端可调用的具体函数。
先来看一个 McpServer 的实现:
  1. public class McpServerApp {
  2.     public static McpSyncServer createServer() {
  3.         JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
  4.         StdioServerTransportProvider transportProvider = new StdioServerTransportProvider(
  5.             jsonMapper);
  6.         return McpServer.sync(transportProvider)
  7.           .serverInfo("baeldung-demo-server", "0.0.1")
  8.           .capabilities(McpSchema.ServerCapabilities.builder()
  9.             .tools(true)
  10.             .logging()
  11.             .build())
  12.           .tools(LoggingTool.logPromptTool())
  13.           .build();
  14.     }
  15.     public static void main(String[] args) {
  16.         createServer();
  17.     }
  18. }
复制代码
上面代码定义了一个同步的 McpServer,通过标准输入/输出流并使用 JSON 消息格式进行通信。随后我们声明了服务器的能力:开启工具以及日志功能(基于 SLF4J 日志框架),最后把自定义的 logPromptTool 注册到服务器上。
4.3 MCP 客户端实现

下面再定义一个简单的 McpClient,用于连接到服务端:
  1. public class McpClientApp {
  2.     public static McpSyncClient getClient() {
  3.         ServerParameters params = ServerParameters
  4.           .builder("npx")
  5.           .args("-y", "@modelcontextprotocol/server-everything")
  6.           .build();
  7.         JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
  8.         McpClientTransport transport = new StdioClientTransport(params, jsonMapper);
  9.         return io.modelcontextprotocol.client.McpClient.sync(transport)
  10.          .build();
  11.     }
  12.     public static void main(String[] args) {
  13.         McpSyncClient client = getClient();
  14.         client.initialize();
  15.     }
  16. }
复制代码
这里我们使用 MCP 提供的示例服务端,并通过 ServerParameters 进行配置。客户端同样通过标准输入/输出流以及 JSON 消息格式与服务端进行同步通信。
5. 测试

到目前为止,我们已经具备了测试 MCP 交互和核心概念所需的全部组件。
5.1 测试 MCP 工具与客户端实现

我们先从测试 LoggingTool 开始,验证其输出是否正确:
  1. @Test
  2. void whenLogPromptToolCalled_thenReturnsResult() {
  3.     McpSchema.CallToolRequest request = new McpSchema.CallToolRequest("",
  4.         Map.of("prompt", "Unit test message"));
  5.     McpServerFeatures.SyncToolSpecification toolSpec = LoggingTool.logPromptTool();
  6.     McpSchema.CallToolResult result
  7.         = toolSpec.callHandler().apply(null, request);
  8.     assertNotNull(result);
  9.     assertFalse(result.isError());
  10.     assertEquals(
  11.         "Input Prompt: Unit test message",((McpSchema.TextContent) (result.content()
  12.         .getFirst()))
  13.         .text());
  14. }
复制代码
在这个测试中,我们创建了一个带有 prompt 的 CallToolRequest,并将其传递给 LoggingTool 的 SyncToolSpecification。随后,我们断言返回结果非空、无错误,并且返回的文本内容与预期相同。
接下来,我们再通过 MCP 提供的示例服务端来测试 McpClient:
  1. @Test
  2. void whenCalledViaClient_thenReturnsLoggedResult() {
  3.     McpSchema.CallToolRequest request = new McpSchema.CallToolRequest(
  4.         "echo", Map.of("message", "Client-server test message"));
  5.     McpSchema.CallToolResult result = client.callTool(request);
  6.     assertNotNull(result);
  7.     assertNull(result.isError());
  8.     assertEquals("Echo: Client-server test message",
  9.         ((McpSchema.TextContent) (result.content()
  10.         .getFirst())).text());
  11. }
复制代码
MCP 示例服务端暴露了一个名为 echo 的工具,它会把输入消息原样返回,这与我们之前实现的 LoggingTool 的行为类似。
5.2 测试本地服务端

最后,我们来测试自己编写的本地服务端。为此需要定义一个单独的 McpClient,并使用指向本地 JAR 的不同服务端参数:
  1. public class McpClientApp2 {
  2.     private static final Logger log = LoggerFactory.getLogger(McpClientApp2.class);
  3.     public static void main(String[] args) {
  4.         String jarPath = new java.io.File("java-mcp/target/java-mcp-1.0.0-SNAPSHOT.jar")
  5.                              .getAbsolutePath();
  6.         ServerParameters params = ServerParameters.builder("java")
  7.             .args("-jar", jarPath)
  8.             .build();
  9.         JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
  10.         McpClientTransport transport = new StdioClientTransport(params, jsonMapper);
  11.         McpSyncClient client = McpClient.sync(transport)
  12.             .build();
  13.         client.initialize();
  14.         ListToolsResult tools = client.listTools();
  15.         McpClientApp2.log.info("Tools exposed by the server:");
  16.         tools
  17.           .tools()
  18.           .forEach(tool -> System.out.println(" - " + tool.name()));
  19.         McpClientApp2.log.info("\nCalling 'logPrompt' tool...");
  20.         CallToolResult result = client.callTool(
  21.           new CallToolRequest("logPrompt", Map.of("prompt", "Hello from MCP client!")));
  22.         McpClientApp2.log.info("Result: " + result.content());
  23.         client.closeGracefully();
  24.     }
  25. }
复制代码
运行该客户端后,我们可以通过日志来验证它是否成功连接到了本地 JAR 中定义的服务端:
  1. 14:04:27.879 [boundedElastic-1] INFO  i.m.c.transport.StdioClientTransport - MCP server starting.
  2. 14:04:27.920 [boundedElastic-1] INFO  i.m.c.transport.StdioClientTransport - MCP server started
  3. 14:04:28.517 [pool-4-thread-1] INFO  i.m.c.transport.StdioClientTransport - STDERR Message received: 14:04:28.504 [pool-1-thread-1] INFO  i.m.server.McpAsyncServer - Client initialize request - Protocol: 2024-11-05, Capabilities: ClientCapabilities[experimental=null, roots=null, sampling=null, elicitation=null], Info: Implementation[name=Java SDK MCP Client, title=null, version=0.15.0]
  4. 14:04:28.575 [pool-1-thread-1] INFO  i.m.client.LifecycleInitializer - Server response with Protocol: 2024-11-05, Capabilities: ServerCapabilities[completions=null, experimental=null, logging=LoggingCapabilities[], prompts=null, resources=null, tools=ToolCapabilities[listChanged=true]], Info: Implementation[name=baeldung-demo-server, title=null, version=0.0.1] and Instructions null
  5. 14:04:28.626 [main] INFO  mcp.McpClientApp2 - Tools exposed by the server:
  6. 14:04:28.626 [main] INFO  mcp.McpClientApp2 -
  7. Calling 'logPrompt' tool...
  8. - logPrompt
  9. 14:04:28.671 [main] INFO  mcp.McpClientApp2 - Result: [TextContent[annotations=null, text=Input Prompt: Hello from MCP client!, meta=null]]
  10. 14:04:28.784 [ForkJoinPool.commonPool-worker-1] WARN  i.m.c.transport.StdioClientTransport - Process terminated with code 143
  11. Process finished with exit code 0
复制代码
从日志可以看到,首先 McpServer 启动成功,随后客户端完成初始化并与服务端建立连接。接着客户端请求列出服务端暴露的工具,最后在调用 logPrompt 工具之后,我们也看到了来自服务端的返回结果。
6. 总结

在本文中,我们首先回顾了 MCP 及其 Java SDK 的整体架构,重点介绍了 McpServer、McpClient 和 McpHost 之间的关系,以及由 McpTransport 负责的传输层细节。
接着,我们了解了 MCP 中的各种原语(primitives),以及在服务端和客户端侧分别可用的类型和能力。
最后,我们实现了一个简单的 MCP 工具,并通过 MCP 示例服务端和本地自建服务端,验证了 McpClient 的连接与调用流程。借助 MCP 标准和 Java SDK,你可以更方便地把现有系统能力以统一的方式暴露给 AI 应用,也能在不同项目之间复用这套集成模式。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册