How to Build an MCP Client Using the Java SDK
The Model Context Protocol (MCP) allows language models to interface with tools and external systems in a structured, programmable way. If you’re building intelligent apps or agents that need dynamic tool invocation, MCP gives you a solid foundation.
In this post, we’ll walk through how to build a robust MCP Client using the Java MCP SDK, with practical code examples taken from a real-world integration.
🚀 Prerequisites
- Java 17+
- Spring Boot (optional for HTTP transport)
- Maven or Gradle project
- MCP-compatible server or tool (can be local, HTTP, or cloud-based)
🧱 Step 1: Choose the Right Transport
The MCP client supports different transports:
🔌 A. HTTP/SSE Transport
javaCopyEditString url = "https://mcp.deepwiki.com/";
McpClientTransport transport = new HttpClientSseClientTransport(url);
Or use a Spring WebFlux-compatible SSE transport:
javaCopyEditWebClient.Builder builder = WebClient.builder().baseUrl(url);
McpClientTransport transport = new WebFluxSseClientTransport(builder);
🖥️ B. STDIO Transport (for CLI/Node.js tools)
javaCopyEditServerParameters params = ServerParameters.builder("/usr/local/bin/node")
.args("/path/to/your/server.js")
.build();
McpClientTransport transport = new StdioClientTransport(params);
⚙️ Step 2: Create and Configure the MCP Client
Using the selected transport, build the MCP client with optional hooks like sampling, elicitation, or logging.
javaCopyEditMcpSyncClient client = McpClient.sync(transport)
.clientInfo(new Implementation("Sample client", "1.0.0"))
.sampling(request -> {
// Forward to a model or apply business rules
return new CreateMessageResult(Role.ASSISTANT, new TextContent("response"), "model", StopReason.END_TURN);
})
.elicitation(request -> {
// Example dummy handler
return new ElicitResult(ElicitResult.Action.COMPLETE, Map.of("key", "value"));
})
.loggingConsumer(msg -> System.out.println("Log: " + msg.data()))
.build();
🧪 Step 3: Initialize and Discover Tools
javaCopyEditInitializeResult init = client.initialize();
System.out.println("Server initialized: " + init);
ListToolsResult tools = client.listTools();
for (Tool tool : tools.tools()) {
System.out.println("Tool: " + tool.name() + " - " + tool.description());
}
🛠️ Step 4: Wrap Tools as Executables
You can dynamically turn tools into LangChain4j-compatible ToolSpecifications and executors:
javaCopyEditToolSpecification spec = ToolSpecification.builder()
.name(tool.name())
.description(tool.description())
.parameters(JsonObjectSchema.builder()
.addStringProperty("input") // Adapt to actual schema
.required("input")
.build())
.build();
ToolExecutor executor = (request, context) -> {
CallToolResult result = client.callTool(new CallToolRequest(tool.name(), request.arguments()));
return ((TextContent) result.content().get(0)).text();
};
🧠 Optional: Integrate with LLM Agents
Using LangChain4j, you can plug this into a ChatModel-based agent:
javaCopyEditChatModel model = OpenAiChatModel.builder()
.apiKey("your-key")
.modelName("gpt-4o-mini")
.build();
Bot bot = AiServices.builder(Bot.class)
.chatModel(model)
.tools(Map.of(spec, executor))
.build();
String reply = bot.chat("Check weather in Atlanta");
System.out.println(reply);
🪄 Bonus: Set Logging Level via Tool
javaCopyEditclient.callTool(new CallToolRequest("logging.set-level", Map.of(
"logger", "root",
"level", "DEBUG"
)));