skip to content

How do you consume an external MCP server's tools inside a Spring AI ChatClient, i.e. as ToolCallbacks?

level: seniorimportance: should knowfreq 35%

answer

  1. client starter + stdio(command/args) or sse(url)
  2. SyncMcpToolCallbackProvider adapts remote tools
  3. remote tool -> ToolCallback -> tools/call
  4. builder.defaultToolCallbacks(provider)
  5. network hop: timeouts, collisions, trust

basics

~20 s

Add spring-ai-starter-mcp-client and configure the server connection (stdio command or SSE url). The auto-config exposes a ToolCallbackProvider that adapts each remote tool into a Spring AI ToolCallback. Inject it and pass it to ChatClient via defaultToolCallbacks — the model can then call remote tools like local ones.

solid answer

~40 s

The client starter auto-configures the MCP client(s) from spring.ai.mcp.client.* (a stdio connection with command/args, or an SSE connection with a url) and registers a SyncMcpToolCallbackProvider (or async variant). That provider performs tools/list against each connected server and wraps every remote tool as a Spring AI ToolCallback whose invocation issues a tools/call over MCP. You inject the ToolCallbackProvider and register it on the ChatClient — builder.defaultToolCallbacks(toolProvider) — or pass it per-request via .toolCallbacks(...). From the model's perspective the remote tools are ordinary tools: the ChatClient advertises them in the chat request, and when the model asks to call one, Spring AI dispatches the call across MCP and feeds the result back. Multiple servers can be aggregated. Because a network/process hop and remote failures are now in the loop, add timeouts and error handling.

go deeper

for a junior

Know that the client starter turns a remote server's tools into tools your ChatClient can use.

for a middle

Configure a stdio/SSE connection and pass the ToolCallbackProvider to ChatClient.defaultToolCallbacks.

for a senior

Explain the SyncMcpToolCallbackProvider adapter, the tools/list + tools/call flow, and multi-server aggregation with collision risk.

for a principal

Treat remote tools as an untrusted, failure-prone boundary: timeouts, retries/circuit-breaking, namespacing, and governance over which external servers are allowed.

## The goal You have an LLM flow built on Spring AI's `ChatClient`, and you want the model to be able to call tools that live on a *separate* MCP server (a third-party or another team's process) — without writing a custom client per tool. ## Step 1 — client starter + connection config Add `spring-ai-starter-mcp-client`. Configure one or more connections under `spring.ai.mcp.client.*`. Two transport shapes: ```properties # stdio: Spring spawns the server as a child process spring.ai.mcp.client.stdio.connections.filesystem.command=npx spring.ai.mcp.client.stdio.connections.filesystem.args=-y,@modelcontextprotocol/server-filesystem,/data # or SSE/HTTP: connect to a running server spring.ai.mcp.client.sse.connections.weather.url=http://localhost:8080 ``` You also choose `spring.ai.mcp.client.type=SYNC` or `ASYNC`. ## Step 2 — the adapter provider The auto-config creates the MCP client(s) and a `ToolCallbackProvider` implementation — `SyncMcpToolCallbackProvider` (or `AsyncMcpToolCallbackProvider`). On startup/first use it calls `tools/list` on each connected server and produces one Spring AI **`ToolCallback`** per remote tool. Each `ToolCallback` carries the remote tool's name/description/schema and, when invoked, performs a `tools/call` over the MCP transport, returning the result. This is the key abstraction: **a remote MCP tool becomes an ordinary Spring AI tool.** ## Step 3 — wire into ChatClient ```java @Bean ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider mcpTools) { return builder .defaultToolCallbacks(mcpTools) // remote tools available on every call .build(); } ``` Or add them per request: `chatClient.prompt().toolCallbacks(mcpTools).user(...).call()`. When you send a prompt, the ChatClient lists these tools to the model; if the model emits a tool call, Spring AI's tool-execution loop resolves the matching `ToolCallback`, dispatches it across MCP, and returns the tool result to the model for the next step — the standard Spring AI function-calling loop, just with MCP as the execution channel. ## Multiple servers & naming You can connect several servers; all their tools are aggregated into the provider. Watch for **name collisions** across servers — two servers exposing a tool of the same name is a real hazard. ## Sync vs async `SYNC` uses blocking `McpSyncClient`; `ASYNC` uses reactive `McpAsyncClient` (Reactor) and pairs with WebFlux — match this to your app's stack and to whether your ChatClient flow is imperative or reactive. ## Gotchas - **Startup coupling:** a stdio server is launched as a subprocess; a bad command or a down SSE endpoint can fail discovery. Handle connection errors and set timeouts. - **Latency & failure:** every tool call is now a remote call — network errors, slow responses, and partial availability must be handled; don't assume tools are infallible local methods. - **Trust boundary:** you are executing tools you may not control. Validate/limit what the model can do and treat results as untrusted input. - **Discovery timing:** tool lists are typically fetched at connect; a server that adds tools later may need a reconnect/refresh depending on configuration.

  • What actually happens when the model decides to call a remote MCP tool?
    Spring AI's tool-execution loop resolves the ToolCallback for that tool name, which issues an MCP tools/call over the configured transport to the remote server, receives the result, and feeds it back into the conversation for the model's next turn.
  • You connect two MCP servers and both expose a tool named 'search'. What's the risk?
    A name collision — the model/ChatClient may bind to the wrong implementation. You need distinct names (or prefixing/namespacing) to disambiguate before exposing both.

saying these in an interview costs you the question

  • Treating remote MCP tool calls as infallible local method calls (no timeouts/error handling)
  • Ignoring tool-name collisions when aggregating multiple servers
  • Confusing MethodToolCallbackProvider (server/publish) with the client-side McpToolCallbackProvider (consume)

context