模型上下文協定 (MCP) Java SDK 簡介
1. 概述
隨著人工智慧技術的最新發展,越來越多的工具和系統開始與人工智慧模型整合。然而,這帶來了一個挑戰,因為每種實作方式都定義了自身將外部工具、資源和系統與人工智慧模型整合的標準。
模型上下文協定 (MCP) 是一項開源標準,它定義了人工智慧應用、LLM 模型、圖像生成器等與工具、資料來源和其他資源的整合。這使得人工智慧應用能夠存取資料、使用工具並執行外部系統中定義的工作流程。
Java SDK 為開發人員提供了一組程式庫,支援多種協定和機制,以便與 AI 應用程式連接。
在本教程中,我們將探索 SDK 並使用 MCP 執行一個簡單的測試。
2. 建築
MCP架構的關鍵組成部分包括以下幾類:
- MCP主機,用於管理多個MCP用戶端
- MCP 用戶端接收 MCP 伺服器的上下文,供 MCP 主機使用。
- MCP 伺服器為 MCP 用戶端提供上下文資訊
MCP 透過兩個概念層定義通訊。資料層定義了客戶端-伺服器通訊和生命週期管理的協議,傳輸層定義了客戶端和伺服器使用的通訊通道和機制。
MCP 的 Java SDK 將這些概念映射到以下幾層:
- 客戶端/伺服器層,它使用
McpClient/McpServer實作和管理客戶端/伺服器操作。 - 會話層透過
McpSession管理通訊模式和狀態。 - 傳輸層使用
McpTransport處理訊息的序列化和反序列化。
客戶端呼叫 MCP 伺服器公開的一個或多個工具,通訊透過傳輸層進行。
原語是 MCP 的基本建構模組。它們定義了上下文資訊的類型以及可以執行的操作範圍。伺服器和客戶端都提供了一些原語。
伺服器原語包括工具、資源和提示。工具是**可執行函數,人工智慧應用程式可以使用這些函數來執行諸如查詢資料庫、檔案操作等操作。資源是**為客戶端提供上下文資訊的資料來源,例如資料庫模式、文件內容等。提示是可重複使用的**模板,用於幫助與語言模型進行互動**。
另一方面,客戶端原語允許McpServer開發者建立更豐富的交互,並包含採樣、請求獲取和日誌記錄功能。取樣允許伺服器在不想在伺服器端本身整合模型 SDK 的情況下,向客戶端請求語言模型補全。請求獲取為伺服器提供了一種向使用者請求**額外資訊或確認操作的方法。日誌記錄允許伺服器向客戶端發送**日誌訊息,以便進行偵錯和監控。
3. 設定
要使用 SDK,我們將使用mcp依賴項:
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.15.0</version>
</dependency>
3.1. 定義 MCP 工具
我們將定義一個簡單的 MCP 工具,它將透過LoggingTool類別列印接收到的提示符,該類別傳回一個 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();
});
}
}
首先,我們定義了輸入模式,它為使用者輸入創建了一個清晰的規格。接下來,我們使用該輸入模式實例化一個Tool ,該工具提取提示參數,並最終傳回一個包含提取提示TextContent結果。
4. MCP客戶端和伺服器設置
我們需要一個 MCP 伺服器來公開我們的自訂工具,以及一個或多個 MCP 用戶端,這些客戶端能夠連接到伺服器並呼叫工具。
4.1. MCP 伺服器實現
McpServer具備某些功能,可以告知客戶端哪些協定操作類別(例如日誌記錄、提示完成和資源存取)可用。此外,客戶端還可以透過工具呼叫伺服器公開的可執行函數。
我們先來定義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中定義。此外,通訊是透過標準輸入/輸出流進行的,同步McpClient使用 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());
}
在這個測試中,我們建立了一個帶有提示的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 imctransport.StdioClientTransport - MCP server starting.
14:04:27.920 [boundedElastic-1] INFO imctransport.StdioClientTransport - MCP server started
14:04:28.517 [pool-4-thread-1] INFO imctransport.StdioClientTransport - STDERR Message received: 14:04:28.504 [pool-1-thread-1] INFO imserver.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 imclient.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 imctransport.StdioClientTransport - Process terminated with code 143
Process finished with exit code 0
從這裡我們可以看到,首先McpServer啟動,然後客戶端初始化並嘗試與伺服器連接,連接成功。
接下來,客戶端請求伺服器公開的工具列表,最後,當客戶端呼叫 logPrompt 工具時,我們就可以看到伺服器的回應。
6. 結論
在本教程中,我們探討了 MCP 和 Java SDK 的架構。其主要組件包括McpServer和 McpTransport。 McpServer 公開了McpClient可以從McpHost,而傳輸通道及其詳細資訊則由McpTransport.
接下來,我們探索了基本類型以及伺服器和客戶端上可用的不同類型。
最後,我們實作了其中一個工具,並驗證了 McpClient 與 MCP 範例伺服器和我們本地McpServer連接和呼叫。
與往常一樣,本教程的完整程式碼可在 GitHub 上找到。