The Model Context Protocol (MCP) is an open standard that lets an AI application call tools and read data from external systems through one common protocol, instead of a custom integration for every system. An MCP server wraps a system, such as a database, a ticket tracker or our own Java service, and any MCP-capable AI application can use it, from a coding assistant in the IDE to our own Spring AI app.
Java developers meet MCP from two sides. We write MCP servers to expose our company’s data and actions to AI tools, and we write MCP clients when our own application needs tools that someone else already published as an MCP server.
A tiny MCP server built with the official MCP Java SDK fits in a few lines. It exposes one tool, check_availability, which tells an AI application how many copies of a book a library has.
McpSyncServer server = McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))
.serverInfo("library-server", "1.0.0")
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
.tools(checkAvailability) // the tool definition plus its Java handler, shown in section 6.1
.build();
The server knows nothing about any AI model. It only describes its tools and runs them when a client asks. In the rest of the article, we cover the MCP architecture, the three things a server can offer, the JSON-RPC messages on the wire, a complete Java server and client, and the security rules that matter before we connect an MCP server to anything important.
1. Why MCP Exists
Before MCP, every AI application had its own plugin format. A team that wanted its order system available in three AI tools wrote three integrations, and each tool vendor repeated the work for every popular system. MCP replaces that with one protocol, so one server works with every client that speaks MCP.
The MCP documentation compares the protocol to a USB-C port for AI applications. Clients that support MCP include coding assistants, chat apps and IDEs such as VS Code and Cursor, and frameworks such as Spring AI and LangChain4j.
MCP does not replace tool calling; it standardizes where the tools come from. The model still decides to call a tool, as explained in LLM terms for Java developers, but the tool definitions and their execution live in an MCP server instead of in our application code.
2. MCP Architecture with Host, Client and Server
MCP has three roles. The host is the AI application the user works with, the client is a connector inside the host, and the server is the program that offers tools and data. A host creates one client for each server it connects to, and each client keeps its own connection.

The protocol itself has two layers. The data layer defines the messages, which are JSON-RPC 2.0 requests, responses and notifications. The transport layer defines how those messages travel, and MCP has two standard transports. Older servers still use a third one, which is deprecated.
| Transport | How it works | Typical use |
|---|---|---|
| stdio | The client starts the server as a child process and exchanges one JSON message per line over standard input and output | Local servers, such as a tool that reads files on the developer’s machine |
| Streamable HTTP | The client sends JSON-RPC messages with HTTP POST, and the server can answer with a stream of events | Remote servers shared by many users, such as a company-wide orders server |
| HTTP with SSE (old) | The original HTTP transport with a separate Server-Sent Events channel | Deprecated since the 2025-03-26 spec; use Streamable HTTP for new servers |
3. Tools, Resources and Prompts
An MCP server can offer three kinds of things, called server primitives. They differ in who decides to use them, which matters for both design and security.
| Primitive | What it is | Who decides to use it | Library example |
|---|---|---|---|
| Tools | Functions the AI application can call, with a name, a description and a JSON Schema for the arguments | The model, and the user can approve or deny each call | check_availability(title), reserve_book(title, memberId) |
| Resources | Read-only data identified by a URI, such as a file or a database record | The application or the user | library://catalog/clean-code |
| Prompts | Reusable prompt templates with arguments | The user, for example from a slash command | summarize_reviews(title) |
Most servers start with tools only, because tools cover both reading and changing data. The client side has a smaller list of features, and in the 2025-11-25 spec the main one is elicitation, which lets a server ask the user for missing input during a call.
4. What MCP Messages Look Like
Every MCP exchange is plain JSON-RPC, so we can watch it without any client. In the next example, we start the library server from section 6 with java -cp and type four messages into its standard input, one per line. The server answers on standard output.
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"terminal","version":"1.0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"check_availability","arguments":{"title":"Clean Code"}}}
The server sends one response for each request with an id. The notification has no id, so it gets no response.
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"logging":{},"tools":{"listChanged":true}},"serverInfo":{"name":"library-server","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"check_availability","description":"Returns how many copies of a book the library has on the shelf","inputSchema":{"required":["title"],"type":"object","properties":{"title":{"description":"Book title","type":"string"}}}}]}}
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"2 copies of 'clean code' available"}],"isError":false}}
In four lines we can see the whole 2025-11-25 lifecycle. The client and server agree on a protocol version and their capabilities in initialize, and the client confirms with notifications/initialized. After that, tools/list returns the tool definitions that the host passes to the model, and tools/call runs the tool when the model asks for it.
5. MCP Spec Versions and the Java SDK
MCP versions are dates. The spec has had five versions, 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and the current 2026-07-28, and the client and server agree on one version when they connect.
The 2026-07-28 version makes the protocol stateless. It removes the initialize handshake and sessions, sends the protocol version and client capabilities with every request instead, adds a server/discover method, and marks sampling, roots and logging as deprecated. The spec changelog lists every change.
The MCP Java SDK 2.0.1 supports protocol versions up to 2025-11-25, which is why our server answers with that version. Java clients and servers built with SDK 2.0.1 use 2025-11-25 with each other, so the examples work as shown. To connect to a host or server that accepts only 2026-07-28, we need an SDK release that supports the new version.
6. Building an MCP Server and Client in Java
The following example is a library server and a client that starts it, written with the MCP Java SDK 2.0.1 on Java 25. The SDK needs Java 17 or newer, and the complete project, including the Spring AI version from section 7, is in the mcp-explained folder on GitHub.
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>2.0.1</version>
</dependency>
6.1. The Server
A tool has two parts. The definition is what the model sees, and the handler is the Java code that runs when the model calls the tool. The input schema is a JSON Schema written as a Map, and the handler returns a CallToolResult, with isError(true) when the call fails in a way the model should know about.
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
import java.util.Map;
public class LibraryServer {
public static void main(String[] args) {
Map<String, Integer> copies = Map.of("clean code", 2, "effective java", 0, "refactoring", 1);
// 1. The tool's input: one required string argument named "title"
Map<String, Object> inputSchema = Map.of(
"type", "object",
"properties", Map.of("title", Map.of("type", "string", "description", "Book title")),
"required", List.of("title"));
// 2. The tool definition plus the Java code that runs when a client calls it
McpServerFeatures.SyncToolSpecification checkAvailability =
McpServerFeatures.SyncToolSpecification.builder()
.tool(McpSchema.Tool.builder("check_availability", inputSchema)
.description("Returns how many copies of a book the library has on the shelf")
.build())
.callHandler((exchange, request) -> {
String title = String.valueOf(request.arguments().get("title")).toLowerCase();
Integer count = copies.get(title);
if (count == null) {
return McpSchema.CallToolResult.builder()
.addTextContent("No book with the title '" + title + "' in the catalog")
.isError(true)
.build();
}
return McpSchema.CallToolResult.builder()
.addTextContent(count + " copies of '" + title + "' available")
.build();
})
.build();
// 3. A server that talks JSON-RPC over stdin and stdout
McpSyncServer server = McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))
.serverInfo("library-server", "1.0.0")
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
.tools(checkAvailability)
.build();
}
}
The stdio transport reads standard input on its own thread, so the JVM keeps running after main() returns, until the client closes the stream. A stdio server must never print anything else to standard output, because every line there is read as a JSON-RPC message, so logging goes to standard error or a file.
6.2. The Client
The client starts the server as a child process, runs the handshake and calls the tool. In a real host, the tool list goes to the model, and the model decides when to call the tool, but here we call it ourselves to see the result.
ServerParameters params = ServerParameters.builder(Path.of(System.getProperty("java.home"), "bin", "java").toString())
.args("-cp", System.getProperty("java.class.path"), "com.howtodoinjava.mcp.LibraryServer")
.build();
try (McpSyncClient client = McpClient.sync(new StdioClientTransport(params, McpJsonDefaults.getMapper()))
.requestTimeout(Duration.ofSeconds(10))
.build()) {
McpSchema.InitializeResult init = client.initialize(); // protocol 2025-11-25
McpSchema.ListToolsResult tools = client.listTools(); // [check_availability]
McpSchema.CallToolResult found = client.callTool(
McpSchema.CallToolRequest.builder("check_availability").arguments(Map.of("title", "Clean Code")).build());
// isError=false, "2 copies of 'clean code' available"
McpSchema.CallToolResult missing = client.callTool(
McpSchema.CallToolRequest.builder("check_availability").arguments(Map.of("title", "Dune")).build());
// isError=true, "No book with the title 'dune' in the catalog"
}
The client uses the same Java executable as the current process to start the server, so the child process runs on the same Java version. The try-with-resources block closes the client, which also stops the server process.
7. MCP in Spring AI and LangChain4j
Most Java applications use MCP through a framework instead of calling the SDK classes. Both main frameworks build on the same protocol, so a server written with one works with clients written with the other.
| Need | Spring AI 2.0 | LangChain4j |
|---|---|---|
| MCP server over stdio | spring-ai-starter-mcp-server with spring.ai.mcp.server.stdio=true | Use the MCP Java SDK |
| MCP server over HTTP | spring-ai-starter-mcp-server-webmvc or -webflux | Use the MCP Java SDK |
| Define tools | @McpTool and @McpToolParam on Spring bean methods | Tools come from the server |
| MCP client | spring-ai-starter-mcp-client, tools passed to ChatClient | langchain4j-mcp module, McpToolProvider passed to AiServices |
8. MCP Security Rules
An MCP server gives an AI application real access to real systems, so a bad server or a careless tool can leak data or change things nobody approved. The MCP security best practices list the main rules.
- The spec asks hosts to get the user’s explicit consent before calling a tool or sharing user data with a server.
- Tool descriptions from an untrusted server are untrusted input, because a description can contain instructions that try to steer the model (often called tool poisoning).
- Servers must validate every tool argument, check access rights and limit the call rate, the same as for any public API.
- An MCP server must not accept access tokens that were issued for a different service, and it must not pass the client’s token through to other APIs.
For our own servers, the practical rule is to give each tool the smallest permission it needs. A read-only tool such as check_availability carries little risk, whereas a tool such as reserve_book needs an authenticated user and should not be reachable from a server that anyone can install.
9. MCP FAQs
Most questions about MCP come from comparing it with features Java developers already use, such as tool calling and REST APIs.
9.1. What Is the Difference Between MCP and Tool Calling?
Tool calling is the model feature that returns a tool name and arguments instead of text. MCP is the protocol that delivers tool definitions to the application and runs the tools in a separate server, so the same tools work in many applications.
9.2. Is MCP Only for Anthropic Models?
No. MCP is an open standard, and the host can use any model that supports tool calling. The server has no direct access to the model. In the 2025-11-25 spec, a server can ask the host for a completion through sampling, and the host decides whether to run it.
9.3. Is MCP Different From a REST API?
Yes, but an MCP server often calls a REST API inside its tools. MCP adds what an AI application needs on top, such as tool descriptions and schemas that the model reads, discovery with tools/list, and one format for results and errors.
10. Conclusion
MCP gives AI applications one way to reach tools and data. A host creates a client for each server it uses, and every request and result travels as JSON-RPC over stdio or Streamable HTTP.
For Java developers, the MCP Java SDK and the Spring AI starters make a server a few dozen lines of code. The design work is in the tools themselves. They need clear descriptions for the model and strict argument checks, and each server should get the smallest permissions that still do the job.
11. References
- What Is the Model Context Protocol
- MCP Architecture Overview
- MCP Specification 2026-07-28 Changelog
- MCP Specification 2025-11-25, Tools
- MCP Java SDK Documentation
- Spring AI MCP Overview
- LangChain4j MCP Tutorial
Happy Learning !!