Skip to content

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