當我們把各種內部系統、數據源、工具接入大語言模型時,往往會遇到一個尷尬的問題:每個團隊、每套系統都有自己的一套“接入規範”。有的用 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>
<artifactId>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 應用,也能在不同項目之間複用這套集成模式。