当我们把各种内部系统、数据源、工具接入大语言模型时,往往会遇到一个尴尬的问题:每个团队、每套系统都有自己的一套“接入规范”。有的用 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 依赖:
/* by 01022.hk - online tools website : 01022.hk/zh/escape.html */
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.15.0</version>
</dependency>
3.1 定义一个 MCP 工具
我们先通过 LoggingTool 这个类,定义一个非常简单的 MCP 工具,用来打印收到的提示词(prompt),该方法返回一个 SyncToolSpecification:
/* by 01022.hk - online tools website : 01022.hk/zh/escape.html */
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 应用,也能在不同项目之间复用这套集成模式。
2255

被折叠的 条评论
为什么被折叠?



