MCP with Spring AI: Connect AI to Tools the Standard Way (Spring AI 2.0.0)

July 11, 2026 · Spring AI for Enterprise Java (part 8)

▶ Watch on YouTube & subscribe to The Stack Underflow

Your team shipped an @Tool method last sprint — one annotation, and the model could call your Java code. It worked. Then someone on the platform team asked to reuse that same tool from Claude Desktop. Then a teammate wanted to call it from an agent built on a completely different stack. Each time, the answer was the same: write another bespoke integration, wire it by hand, ship it, maintain it forever.

That doesn’t scale past two consumers, let alone an enterprise with a dozen AI clients and a dozen internal tool servers. The tool-calling pattern from Episode 7 is correct — it’s just app-local. Nothing about @Tool says how a different application should discover it, call it, or trust it.

That’s the gap the Model Context Protocol (MCP) closes. It isn’t a new AI capability sitting on top of tool calling — it’s a standard port for the handshake you already learned: discover tools, call one, get a structured result, the same way every time, for any client and any server.

The one-sentence version: MCP doesn’t replace @Tool — it standardizes how any client discovers and calls it, so your Spring Boot app can be a tool consumer (MCP client) and a tool provider (MCP server) without writing custom wiring for every new pair.

The problem: bespoke tool wiring doesn’t scale

In Episode 7 you registered @Tool methods and handed them to a single ChatClient. That ChatClient instance is the only thing that knows those tools exist. If Claude Desktop, a teammate’s LangChain agent, or your own second microservice wants to call the same method, none of them can — they’d need their own custom integration against your app’s internals.

MCP fixes the n × m integration problem by standardizing the boundary instead of the capability. Any MCP client can talk to any MCP server, because both sides speak the same protocol for tool discovery and invocation. Your @Tool method doesn’t change. What changes is that it becomes reachable through a standard port instead of a private door.

Concretely, your Spring Boot app can sit on either side of that port — sometimes both:

        MCP CLIENT ROLE                    MCP SERVER ROLE
  (you call OUT to a server        (you expose YOUR @Tool methods
   you don't own)                   to a client you don't control)

┌─────────────────┐  STDIO/SSE/   ┌──────────────────┐
│  Context7 MCP    │◄─────────────┤  Your Spring Boot │
│  server           │  Streamable  │  App               │
└─────────────────┘   HTTP        └──────────────────┘

                                    STDIO/SSE/Streamable HTTP

                                   ┌──────────────────┐
                                   │  Claude Desktop /  │
                                   │  another client     │
                                   └──────────────────┘

Both roles need Java 21 and Spring Boot 4.0, the platform floor for Spring AI 2.0.0 — check that before you add either starter.

MCP client: pointing your app at an external tool server

To consume tools you don’t own, add the client starter:

implementation("org.springframework.ai:spring-ai-starter-mcp-client")

Point it at an external server. Here’s Context7, a documentation-lookup MCP server, launched locally over STDIO:

spring.ai.mcp.client.stdio.connections.context7.command=npx
spring.ai.mcp.client.stdio.connections.context7.args=-y,@upstash/context7-mcp@latest

Spring AI’s auto-configuration (McpClientAutoConfiguration) reads that config and hands you a ToolCallbackProvider bean. Pull the callbacks out and hand them to your ChatClient:

@Bean
ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpTools) {
    return builder
        .defaultTools(mcpTools)
        .build();
}

The model can now call Context7’s tools exactly like it called your own @Tool method in Episode 7 — same request/response shape, different origin.

Accuracy callout — an API renamed since 1.0.3. If you learned this pattern from an older course or blog post, you may have seen defaultToolCallbacks(...). That method is deprecated in Spring AI 2.0.0:

// 1.0.3 (deprecated in 2.0.0) — do not use
chatClientBuilder.defaultToolCallbacks(mcpTools);

// 2.0.0 (current) — use this
chatClientBuilder.defaultTools(mcpTools);

ChatClient.Builder.defaultTools(Object...) is the 2.0.0 replacement. It’s more flexible than the old method too — it accepts a ToolCallbackProvider, plain ToolCallback instances, arrays or collections of either, or @Tool-annotated POJOs directly, all through the same entry point.

MCP server: turning your own @Tool methods into a server

Now flip the direction. Say you have this service from Episode 7, unchanged:

@Service
public class CalculatorService {

    @Tool(description = "Add two numbers")
    public double add(double a, double b) {
        return a + b;
    }

    @Tool(description = "Multiply two numbers")
    public double multiply(double a, double b) {
        return a * b;
    }

    @Tool(description = "Square root of a number")
    public double sqrt(double value) {
        return Math.sqrt(value);
    }
}

Add a server starter — you don’t need to register these methods anywhere new:

// STDIO-only (e.g. Claude Desktop as a local subprocess)
implementation("org.springframework.ai:spring-ai-starter-mcp-server")

// Network-reachable, servlet stack
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc")

// Network-reachable, reactive stack
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webflux")

Then pick a transport with McpServerProperties:

# Local mode — Claude Desktop launches your app as a subprocess
spring.ai.mcp.server.stdio=true

# Network mode — pick a protocol
spring.ai.mcp.server.protocol=STREAMABLE

spring.ai.mcp.server.protocol accepts SSE, STREAMABLE, or STATELESS. Auto-configuration finds every @Tool method on your Spring-managed beans and exposes it — zero extra registration code. Any MCP client, including Claude Desktop, can now connect, discover add/multiply/sqrt, and call them.

Accuracy callout — server transport config changed shape too. An older course-era pattern configured HTTP mode with a standalone spring.ai.mcp.server.sse-message-endpoint property. In 2.0.0, that’s generalized into the single .protocol enum shown above. The spring.ai.mcp.server.stdio boolean for local mode is unchanged.

Transports compared: STDIO, SSE, Streamable HTTP

TransportWhere it runsTypical use2.0.0 status
STDIOLocal subprocess, stdin/stdoutClaude Desktop, CLI-launched servers (like Context7)Unchanged
SSENetwork, Server-Sent EventsLong-lived network connectionsUnchanged, still supported
Streamable HTTPNetwork, MCP-spec-alignedThe newer network optionNew in 2.0.0

On the client side, each transport has its own property namespace:

spring.ai.mcp.client.stdio.connections.<name>.command=...
spring.ai.mcp.client.stdio.connections.<name>.args=...

spring.ai.mcp.client.sse.connections.<name>.url=...

spring.ai.mcp.client.streamable-http.connections.<name>.url=...

Pick STDIO when the server is a local process you or the client spawns. Pick SSE or Streamable HTTP when the server is a separately deployed network service — Streamable HTTP is the newer, spec-aligned option, so prefer it for anything new unless you have a reason to stay on SSE.

Enterprise checklist: MCP servers are supply-chain dependencies

Standardization cuts both ways. The same protocol that lets Claude Desktop call your CalculatorService also lets your app run a command written by someone else — that npx -y @upstash/context7-mcp@latest line from earlier spawns a process on your machine, and @latest means you don’t even know which version you’re running today versus tomorrow.

Treat every external MCP server exactly like any third-party dependency you’d bring into a build:

  • Pin the version. Replace floating tags like @latest with an exact, reviewed version — e.g. @upstash/context7-mcp@1.2.3 (illustrative; use the real pinned version for whatever server you connect to).
  • Review the command. STDIO connections execute an arbitrary command with arbitrary args. Read it before you deploy it, the same way you’d read a Dockerfile from an unfamiliar base image.
  • Sandbox the subprocess. Run STDIO servers in a locked-down runtime with the least privilege they need — no wider filesystem or network access than the tool actually requires.
  • Audit network servers the same way. SSE and Streamable HTTP connections are still code you didn’t write, answering your app’s calls. Apply the same vendor-review bar you’d apply to any external API integration.

MCP being a standard makes this discipline possible — a uniform boundary is auditable in a way that ad-hoc integrations never were. It doesn’t make the discipline optional.

How to apply this right now

  1. Confirm your platform floor. Spring AI 2.0.0 needs Java 21 to build and Spring Boot 4.0 — check both before adding either MCP starter.
  2. Add spring-ai-starter-mcp-client if your app needs to call an external tool server; configure the connection under spring.ai.mcp.client.<transport>.connections.<name>.*.
  3. Wire the ToolCallbackProvider bean into ChatClient.Builder.defaultTools(...) — not the deprecated defaultToolCallbacks(...).
  4. Add an -mcp-server starter (plain, -webmvc, or -webflux) if you want to expose your own @Tool methods to external clients; no new registration code needed.
  5. Choose the transport deliberately: STDIO for a local subprocess relationship, SSE or Streamable HTTP (preferred, 2.0.0) for a network service.
  6. Before connecting to any external MCP server, pin its version, read the launch command, and sandbox it — put this in your team’s dependency-review checklist, not just in your head.
  7. Test both directions independently. Verify your client can discover and call the external server’s tools; separately verify an MCP client (Claude Desktop is the easiest smoke test) can discover and call your own server’s tools.

Common misconceptions

“MCP is a new AI capability, on top of what @Tool already does.” It isn’t. MCP standardizes the discovery and invocation handshake around tool calling — the same @Tool method from Episode 7 is what actually runs. MCP is the plug, not the appliance.

defaultToolCallbacks(...) still works, I’ll migrate later.” It’s deprecated in Spring AI 2.0.0 in favor of ChatClient.Builder.defaultTools(Object...). The replacement isn’t just a rename — it also accepts plain ToolCallbacks and @Tool-annotated POJOs directly, not just a ToolCallbackProvider.

“STDIO is the only transport that matters.” It’s the right choice for local subprocesses like Claude Desktop, but SSE and the 2.0.0-new Streamable HTTP exist specifically for network-deployed servers — pick based on where the server actually runs, not by default.

“Configuring an MCP connection is just config, not a security decision.” An MCP server, especially over STDIO, is a command your app executes. An unpinned, unreviewed connection is an unpinned, unreviewed dependency with process-execution rights — govern it accordingly.

Frequently asked questions

What’s the actual difference between the @Tool pattern from Episode 7 and MCP? @Tool is the annotation that makes a Java method callable by a model — that doesn’t change. MCP is the protocol layer that lets a client outside your app discover and call that same @Tool method without custom integration code. Episode 7 made your method callable; this episode makes it discoverable and callable by anything that speaks MCP.

Do I need both spring-ai-starter-mcp-client and an -mcp-server starter? Only if your app plays both roles. Many apps are client-only (calling external tool servers like Context7) or server-only (exposing their own tools to Claude Desktop or other clients). Add both starters only when your app genuinely needs to consume external tools and expose its own.

Which transport should I use for Claude Desktop versus a network service? Claude Desktop launches MCP servers as local subprocesses, so use STDIO (spring.ai.mcp.server.stdio=true on the server side). For a server reachable over the network by remote clients, use spring.ai.mcp.server.protocol=STREAMABLE (or SSE/STATELESS) instead.

Is spring.ai.mcp.server.sse-message-endpoint still valid in 2.0.0? No — that standalone property was the 1.0.3-era way to configure HTTP mode. In 2.0.0 it’s generalized into spring.ai.mcp.server.protocol, which accepts SSE, STREAMABLE, or STATELESS. If you’re upgrading old config, replace the endpoint property with the protocol enum.

What Java and Spring Boot version does Spring AI 2.0.0’s MCP support require? Java 21 to build and Spring Boot 4.0 — that’s the platform floor for Spring AI 2.0.0 generally, not just MCP. Confirm your build toolchain before adding either starter.

Can one Spring Boot app be an MCP client and an MCP server at the same time? Yes — that’s the hub-and-spoke shape this episode covers. Nothing about the client starter and the server starter conflicts; add both, configure each independently, and your app calls out through one spoke while exposing its own @Tool methods through the other.

Where this fits in the series

This is Episode 8 of Spring AI for Enterprise Java. The previous episode covered the @Tool pattern this episode builds on — read that first if MCP’s “same handshake, standard port” framing doesn’t click yet. The next episode covers observability: once tools are calling in and out over a standard protocol, you need to see every one of those calls, not just trust they happened.

Runnable client and server modules for this episode live in the 08-mcp-integration/ path of the spring-ai-enterprise-examples repo.

Spring AI moves fast — this series is pinned to 2.0.0; when the API shifts, update notes land at thestackunderflow.com/spring-ai. If this saved you a debugging session, the video walkthrough for this episode is coming to @TheStackUnderflow — subscribe so it lands in your feed.

Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.

Subscribe on YouTube →