In part five I wrote tools with @Tool and the model called them. Those tools work with one application: yours, your client, your code.
Now scale it. Five AI applications in your company, each needing GitHub, Jira, Postgres and your internal API. That’s twenty integrations — written separately, maintained separately, broken separately.
The industry has solved this problem several times already. Device drivers. JDBC. USB. The answer was always the same: stop writing N times M integrations and define a protocol.
What MCP actually is
The Model Context Protocol is that protocol. Write a server once for GitHub, and any MCP-speaking client can use it. Write a client once, and it can use any MCP server. Twenty integrations becomes nine — and more importantly, the GitHub server is written once, by whoever knows GitHub best.
Three roles, worth getting straight now:
- Host — the AI application. Your Spring Boot app, or Claude Desktop, or an IDE.
- Client — lives inside the host, one per server, and manages that connection.
- Server — exposes capabilities. Might be local, might be remote, might be written by someone else in a language you don’t use.
One host, many clients, many servers. Your application might talk to a GitHub server, a Postgres server and one you wrote, simultaneously.
Underneath it’s JSON-RPC 2.0. Requests, responses, notifications. The protocol is deliberately boring, which is what protocols should be.
The two-thirds everyone skips
Servers expose three kinds of thing, and most coverage stops after the first:
- Tools — actions the model can invoke. Like part five, but over a protocol.
- Resources — data the client can read. Files, records, documents.
- Prompts — reusable prompt templates the server offers.
The distinction is about who decides. The model decides to call a tool. Your application decides to read a resource. The user decides to invoke a prompt.
That’s a real design tool, not trivia. If your application needs a document’s contents, exposing it as a tool means hoping the model chooses to fetch it. Exposing it as a resource means your code reads it deterministically and puts it in the prompt. When you need something to happen, don’t leave it to the model.
Consuming a server someone else wrote
Add the client starter:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
Configure a connection:
spring:
ai:
mcp:
client:
stdio:
connections:
filesystem:
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "/tmp/mcp-demo"
Wire the discovered tools into a ChatClient:
@Bean
ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpTools) {
return builder.defaultTools(mcpTools).build();
}
ToolCallbackProvider is the bridge. At startup Spring AI connected to the server, asked what it offers, and adapted those tools into the same mechanism from part five. The model neither knows nor cares that they arrived over a protocol from a Node process — they’re just tools.
Ask “what files are in the demo directory?” and it lists them. Your Java application just used a TypeScript MCP server, and you wrote configuration rather than code. That’s the entire pitch, demonstrated in about fifteen lines.
Transports
Three options, and the choice is mostly about where the server runs.
STDIO — the client launches the server as a child process and they talk over standard input and output. Simplest, no network, no auth to configure. Right for local tools and desktop integrations.
Streamable HTTP — the server is a web service somewhere. Right for anything shared between applications or teams, and the one you’ll deploy.
SSE — the older HTTP transport, still widely seen. Recognize it; prefer streamable HTTP for new work.
Debug with the Inspector
npx @modelcontextprotocol/inspector
This is Postman for MCP. You can see exactly what a server advertises, call a tool with hand-written arguments, and read the raw response.
It answers the single most common question in this space. When a tool isn’t being called, there are exactly two possible causes: the server isn’t exposing it correctly, or the model isn’t choosing it. The Inspector tells you which. If the tool works there, your server is fine and your problem is the description — a prompt problem, not a protocol problem.
Building your own server
Same annotation, different consumer:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
spring:
main:
web-application-type: none
banner-mode: off
ai:
mcp:
server:
name: order-service
version: 1.0.0
stdio: true
Those settings are all about one thing: keeping stdout clean.
Anything written to stdout that isn’t a protocol message breaks the connection. The Spring banner will corrupt the stream. So will a stray
System.out.println. The symptom is a client that connects and then fails to parse, with no useful error.
Send logging to a file in logback.xml, not the console. When an MCP server mysteriously won’t talk, this is the first thing to check.
The tools themselves are unchanged from part five:
@Service
public class OrderMcpTools {
@Tool(description = "Look up the current status of an order by its id")
public OrderStatus getOrderStatus(long orderId) {
return repository.findStatus(orderId);
}
}
That is genuinely the same annotation. The identical method can serve your own ChatClient and be exposed over MCP — one implementation, two consumers.
To deploy it as a service rather than a subprocess, switch starters and drop the STDIO config:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
Now it’s an ordinary Spring Boot web application, deployed the way you deploy everything else.
Fewer tools beats more
Connect three MCP servers and you might have sixty tools. This degrades quality in two ways, and both surprise people.
It costs tokens. Every tool definition — name, description, parameter schema — is in every request. Sixty tools is a substantial fixed tax on every single call.
It degrades model decisions. A model choosing between sixty similarly-described tools chooses worse than one choosing between six. Two servers offering search and query that do nearly the same thing is a recipe for the wrong one being picked.
So filter. Expose the subset relevant to what the application is doing, not everything the connected servers happen to offer. This is the MCP-scale version of the registration-scope point from part five, and at this scale it stops being an optimization and becomes a correctness issue.
Securing a server
An MCP server exposes capabilities to a model, and models are steered by text that may come from users or documents. Everything from part five applies, plus some:
- Authenticate the client. A remote MCP server is an API. It needs the same auth as any other service you expose — and the identity it authenticates is the calling application, not the end user.
- Carry the end user’s identity separately, server-side. Same rule as
ToolContext: authorization must never be a parameter the model fills in. - Scope tools narrowly. A server with
readOrderandcancelOrdershould not hand both to a client that only needs to read. - Log every invocation with arguments and resolved identity. When something unexpected happens, that log is your reconstruction.
- Version deliberately. Clients discover tools at startup. Renaming a tool or changing a parameter breaks every consumer silently, because nothing fails at compile time. Tool signatures are public API.
Two capabilities worth knowing about
Sampling lets a server ask the client to run a model call on its behalf. The server gets model access without holding an API key or picking a provider — the host stays in control of cost and model choice. Powerful, and worth guarding: a server that can trigger model calls in your application can spend your budget.
Elicitation lets a tool pause and ask the user something before acting. That’s the protocol-level version of an approval gate, and it’s the right shape for anything irreversible. It also foreshadows part eight, where human-in-the-loop becomes a design pattern rather than a feature.
Conclusion
MCP is worth your attention for an unglamorous reason: it makes an integration outlive the application it was written for. The @Tool method you wrote in part five can serve your ChatClient today and any MCP client tomorrow, with a starter and some YAML.
Three practical things. Keep stdout clean or nothing works. Filter aggressively, because sixty tools is worse than six. And remember that tools are only a third of the protocol — resources and prompts exist precisely for the cases where you don’t want the model deciding.
Next: the security part. Prompt injection, poisoned documents, and what all of this costs when it’s running at volume.
More info: docs.spring.io/spring-ai/reference/api/mcp