Connect: Spring AI¶
Recipe · through our MCP server · their docs checked 2026-10-30
Spring AI's MCP client Boot starter connects at start-up and turns each of our tools into a
ToolCallback. Your ChatClient then calls them like local tools.
Our side¶
def public_app(tools: Registry, host: str, agent_token: str, reviewer_token: str) -> Any:
"""REST at /, MCP at /mcp, one bearer check for both: for a host on the internet."""
from mcp.server.transport_security import TransportSecuritySettings
from agent_adapter.server import create_app
tokens = {
agent_token: Principal("hosted-agent"), # the token the remote framework sends
reviewer_token: Principal("reviewer@example.com", scopes=frozenset({APPROVER_SCOPE})),
}
security = TransportSecuritySettings(allowed_hosts=[host], allowed_origins=[f"https://{host}"])
return create_app(
tools,
authenticate=bearer_auth(tokens),
host="0.0.0.0", # a public bind: the public-host checks apply
allowed_hosts=[host], # REST: any other Host header gets 421
mcp_options={"transport_security": security}, # MCP: the same rule
)
Their side¶
Dependency: org.springframework.ai:spring-ai-starter-mcp-client (or
spring-ai-starter-mcp-client-webflux).
application.yml:
spring:
ai:
mcp:
client:
type: SYNC # all clients sync or all async
request-timeout: 30s # default 20s
streamable-http:
connections:
shop-tools:
url: https://tools.example.com # base URL
endpoint: /mcp # default /mcp
The bearer token, on every request:
@Bean
McpSyncHttpClientRequestCustomizer agentAdapterToken(@Value("${aa.agent-token}") String token) {
return (builder, method, endpoint, body, context) ->
builder.header("Authorization", "Bearer " + token);
}
@Bean
ChatClient chatClient(ChatClient.Builder builder, SyncMcpToolCallbackProvider mcpTools) {
return builder.defaultTools(mcpTools).build(); // MCP tools are not added automatically
}
| Key | Meaning |
|---|---|
streamable-http.connections.<name>.url |
our server's base URL |
…connections.<name>.endpoint |
the path of our endpoint (/mcp) |
request-timeout |
per request (default 20s) |
type |
SYNC or ASYNC for every client |
McpSyncHttpClientRequestCustomizer |
edits each outgoing request: adds the header |
McpToolFilter bean |
one bean; return false to drop a tool |
Checklist¶
| Check | Why |
|---|---|
| the auto-configured client is one session for the whole app | every user of the app shares the token's principal and its runs; for per-user principals build clients yourself |
drop confirm tools with an McpToolFilter, or serve hosted |
Spring AI does not send our resume keys back |
| the token comes from config or a vault, not source | ${aa.agent-token} |
| everything on the public-host checklist |
Sources¶
- MCP client Boot starter: https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html
- MCP security (request customizers): https://docs.spring.io/spring-ai/reference/api/mcp/mcp-security.html
- Tool calling (
defaultToolswith MCP): https://docs.spring.io/spring-ai/reference/api/tools.html - MCP Java SDK 2.0 migration (
httpRequestCustomizer): https://github.com/modelcontextprotocol/java-sdk/blob/main/MIGRATION-2.0.md