当我们把各种内部系统、数据源、工具接入大语言模型时,往往会遇到一个尴尬的问题:每个团队、每套系统都有自己的一套“接入规范”。有的用 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 依赖:- <dependency>
- <groupId>io.modelcontextprotocol.sdk</groupId>
- mcp</artifactId>
- <version>0.15.0</version>
- </dependency>
复制代码 3.1 定义一个 MCP 工具
我们先通过 LoggingTool 这个类,定义一个非常简单的 MCP 工具,用来打印收到的提示词(prompt),该方法返回一个 SyncToolSpecification:- public class LoggingTool {
- public static McpServerFeatures.SyncToolSpecification logPromptTool() {
- McpSchema.JsonSchema inputSchema = new McpSchema.JsonSchema("object",
- Map.of("prompt", String.class), List.of("prompt"), false, null, null);
- return new McpServerFeatures.SyncToolSpecification(
- new McpSchema.Tool(
- "logPrompt", "Log Prompt","Logs a provided prompt", inputSchema, null, null, null),
- (exchange, args) -> {
- String prompt = (String) args.get("prompt");
- return McpSchema.CallToolResult.builder()
- .content(List.of(new McpSchema.TextContent("Input Prompt: " + prompt)))
- .isError(false)
- .build();
- });
- }
- }
复制代码 这里我们首先定义了输入的 JSON Schema,用来为用户输入建立一个清晰的契约。接着,使用该输入 Schema 来实例化一个 Tool,在处理逻辑中提取出 prompt 参数,并最终返回包含该 prompt 的 TextContent 结果。
4. MCP 客户端与服务端搭建
接下来,我们需要一个 MCP 服务端来暴露自定义工具,以及一个或多个 MCP 客户端,用于连接该服务端并调用其中的工具。
4.1 MCP 服务端实现
McpServer 具有一组能力(capabilities),用来告知客户端当前服务器支持哪些类别的协议操作,例如日志记录、提示词补全、资源访问等。此外,工具(tools)则提供给客户端可调用的具体函数。
先来看一个 McpServer 的实现:- public class McpServerApp {
- public static McpSyncServer createServer() {
- JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
- StdioServerTransportProvider transportProvider = new StdioServerTransportProvider(
- jsonMapper);
- return McpServer.sync(transportProvider)
- .serverInfo("baeldung-demo-server", "0.0.1")
- .capabilities(McpSchema.ServerCapabilities.builder()
- .tools(true)
- .logging()
- .build())
- .tools(LoggingTool.logPromptTool())
- .build();
- }
- public static void main(String[] args) {
- createServer();
- }
- }
复制代码 上面代码定义了一个同步的 McpServer,通过标准输入/输出流并使用 JSON 消息格式进行通信。随后我们声明了服务器的能力:开启工具以及日志功能(基于 SLF4J 日志框架),最后把自定义的 logPromptTool 注册到服务器上。
4.3 MCP 客户端实现
下面再定义一个简单的 McpClient,用于连接到服务端:- public class McpClientApp {
- public static McpSyncClient getClient() {
- ServerParameters params = ServerParameters
- .builder("npx")
- .args("-y", "@modelcontextprotocol/server-everything")
- .build();
- JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
- McpClientTransport transport = new StdioClientTransport(params, jsonMapper);
- return io.modelcontextprotocol.client.McpClient.sync(transport)
- .build();
- }
- public static void main(String[] args) {
- McpSyncClient client = getClient();
- client.initialize();
- }
- }
复制代码 这里我们使用 MCP 提供的示例服务端,并通过 ServerParameters 进行配置。客户端同样通过标准输入/输出流以及 JSON 消息格式与服务端进行同步通信。
5. 测试
到目前为止,我们已经具备了测试 MCP 交互和核心概念所需的全部组件。
5.1 测试 MCP 工具与客户端实现
我们先从测试 LoggingTool 开始,验证其输出是否正确:- @Test
- void whenLogPromptToolCalled_thenReturnsResult() {
- McpSchema.CallToolRequest request = new McpSchema.CallToolRequest("",
- Map.of("prompt", "Unit test message"));
- McpServerFeatures.SyncToolSpecification toolSpec = LoggingTool.logPromptTool();
- McpSchema.CallToolResult result
- = toolSpec.callHandler().apply(null, request);
- assertNotNull(result);
- assertFalse(result.isError());
- assertEquals(
- "Input Prompt: Unit test message",((McpSchema.TextContent) (result.content()
- .getFirst()))
- .text());
- }
复制代码 在这个测试中,我们创建了一个带有 prompt 的 CallToolRequest,并将其传递给 LoggingTool 的 SyncToolSpecification。随后,我们断言返回结果非空、无错误,并且返回的文本内容与预期相同。
接下来,我们再通过 MCP 提供的示例服务端来测试 McpClient:- @Test
- void whenCalledViaClient_thenReturnsLoggedResult() {
- McpSchema.CallToolRequest request = new McpSchema.CallToolRequest(
- "echo", Map.of("message", "Client-server test message"));
- McpSchema.CallToolResult result = client.callTool(request);
- assertNotNull(result);
- assertNull(result.isError());
- assertEquals("Echo: Client-server test message",
- ((McpSchema.TextContent) (result.content()
- .getFirst())).text());
- }
复制代码 MCP 示例服务端暴露了一个名为 echo 的工具,它会把输入消息原样返回,这与我们之前实现的 LoggingTool 的行为类似。
5.2 测试本地服务端
最后,我们来测试自己编写的本地服务端。为此需要定义一个单独的 McpClient,并使用指向本地 JAR 的不同服务端参数:- public class McpClientApp2 {
- private static final Logger log = LoggerFactory.getLogger(McpClientApp2.class);
- public static void main(String[] args) {
- String jarPath = new java.io.File("java-mcp/target/java-mcp-1.0.0-SNAPSHOT.jar")
- .getAbsolutePath();
- ServerParameters params = ServerParameters.builder("java")
- .args("-jar", jarPath)
- .build();
- JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper());
- McpClientTransport transport = new StdioClientTransport(params, jsonMapper);
- McpSyncClient client = McpClient.sync(transport)
- .build();
- client.initialize();
- ListToolsResult tools = client.listTools();
- McpClientApp2.log.info("Tools exposed by the server:");
- tools
- .tools()
- .forEach(tool -> System.out.println(" - " + tool.name()));
- McpClientApp2.log.info("\nCalling 'logPrompt' tool...");
- CallToolResult result = client.callTool(
- new CallToolRequest("logPrompt", Map.of("prompt", "Hello from MCP client!")));
- McpClientApp2.log.info("Result: " + result.content());
- client.closeGracefully();
- }
- }
复制代码 运行该客户端后,我们可以通过日志来验证它是否成功连接到了本地 JAR 中定义的服务端:- 14:04:27.879 [boundedElastic-1] INFO i.m.c.transport.StdioClientTransport - MCP server starting.
- 14:04:27.920 [boundedElastic-1] INFO i.m.c.transport.StdioClientTransport - MCP server started
- 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]
- 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
- 14:04:28.626 [main] INFO mcp.McpClientApp2 - Tools exposed by the server:
- 14:04:28.626 [main] INFO mcp.McpClientApp2 -
- Calling 'logPrompt' tool...
- - logPrompt
- 14:04:28.671 [main] INFO mcp.McpClientApp2 - Result: [TextContent[annotations=null, text=Input Prompt: Hello from MCP client!, meta=null]]
- 14:04:28.784 [ForkJoinPool.commonPool-worker-1] WARN i.m.c.transport.StdioClientTransport - Process terminated with code 143
- 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 应用,也能在不同项目之间复用这套集成模式。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |