Let's get started with a Microservice Architecture with Spring Cloud:
Introduction to the Model Context Protocol (MCP) Java SDK
Last updated: March 18, 2026
1. Overview
With recent developments around AI, more and more tools and systems are integrating with AI models. However, this poses a challenge, as each implementation defines its own standards for incorporating external tools, resources, and systems with the AI models.
The Model Context Protocol (MCP) is an open-source standard that defines the integration of AI applications, LLM models, image generators, etc., with tools, data sources, and other resources. This enables AI applications to access data, use tools, and execute workflows as defined in the external systems.
The Java SDK provides developers with a set of libraries supporting multiple protocols and mechanisms to connect with AI applications.
In this tutorial, we’ll explore the SDK and perform a simple test using MCP.
2. Architecture
The key components of MCP architecture include the following:
- MCP Host, which manages multiple MCP Clients
- MCP Client that receives context for the MCP Server for the MCP host to use
- MCP Server, which provides context to MCP Clients
MCP defines communication through two conceptual layers. The Data Layer, which defines the protocol for client-server communication and life-cycle management, and the Transport Layer, which defines the communication channels and mechanisms used by the client and server.
The Java SDK for MCP maps these concepts into the following layers:
- Client/Server Layer, which implements and manages the client/server operations using McpClient/McpServer
- Session Layer manages communication patterns and state via McpSession
- Transport Layer handles message serialization and deserialization with McpTransport
The clients invoke one or more tools exposed by an MCP server, and the communication occurs via the transport layer.
Primitives are fundamental building blocks of MCP. They define the type of contextual information and range of actions that can be performed. Both the server and the client offer some primitives.
Server primitives include tools, resources, and prompts. Tools are executable functions that AI applications can use to perform actions, such as querying a database, file operations, etc. Resources are data sources that provide contextual information to clients, like database schemas, file contents, etc. Prompts are reusable templates that help with interactions with the language models.
The Client primitives, on the other hand, allow McpServer authors to build richer interactions and include sampling, elicitation, and logging. Sampling allows servers to request language model completions from clients when they don’t want to include the model SDK on the server itself. Elicitation gives servers a way to request additional information from the user or confirm any action. Logging allows servers to send log messages to clients for debugging and monitoring
3. Setup
To use the SDK, we’ll use the mcp dependency:
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.15.0</version>
</dependency>
3.1. Defining an MCP Tool
We’ll define a simple MCP tool that will print the received prompt via the LoggingTool class, which returns a 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();
});
}
}
Initially, we’ve defined the input schema, which creates a clear contract for user input. Next, this input schema is used to instantiate a Tool, which extracts the prompt argument and finally returns a TextContent result including the extracted prompt.
4. MCP Client and Server Setup
We’ll need an MCP server to expose our custom tools, as well as one or more MCP clients, capable of connecting to a server and invoking a tool.
4.1. MCP Server Implementation
The McpServer has certain capabilities that tell the clients which categories of protocol operations-such as logging, prompt completion, and resources, are available. Additionally, tools allow the clients to call the actionable functions that the server exposes.
Let’s begin by defining an implementation of the 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();
}
}
We’ve defined a synchronous McpServer which communicates via standard input/output streams using JSON message format. Next, the server capabilities are defined to include tools and logging (via SLF4J), and finally, we include our custom logPromptTool.
4.3. MCP Client Implementation
Next, we’ll define a simple McpClient that connects to a server:
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();
}
}
We’ve used the sample server provided by MCP, defined in the ServerParameters. In addition, the communication is via standard input/output streams using JSON message format for our synchronous McpClient.
5. Test
We’ve all the components needed to test some of the MCP interactions and concepts.
5.1. Testing the MCP Tool and Client Implementations
Let’s begin by testing the LoggingTool and verifying the output for the same:
@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());
}
In this test, we’re creating a CallToolRequest with a prompt, which is then passed to the SyncToolSpecification of the LoggingTool.
Next, we’ll test the McpClient by connecting to the sample server exposed by MCP:
@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());
}
The MCP sample server exposes a tool called “echo”, which returns the input prompt, similar to what we created with the LoggingTool.
5.2. Testing the Local Server
Finally, let’s test the local server we’ve written. We’ll need to define a separate McpClient with different server params pointing to the local 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();
}
}
We’ll run the client and check the logs to verify that it is connected to our local server defined in the jar file:
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
As we can see here, firstly, the McpServer is started, then the client initializes and tries to connect with the server, which is successful.
Next, the client requests a list of tools exposed by the server, and finally, upon the client’s invocation of the logPrompt tool, we see the response from the server.
6. Conclusion
In this tutorial, we’ve explored the architecture of MCP and Java SDK. The main components are the McpServer, which exposes various functionalities that the McpClient can use from within the McpHost, while the transport channels and their details are handled by the McpTransport.
Next, we explored primitives and the different types available on the server and the client.
Finally, we implemented one of the tools and verified the McpClient connection and invocation with both the MCP sample server and our local McpServer.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















