Skip to content

MCP tools

AF4J does not implement MCP. It governs the tools that Spring AI's MCP client already exposes: hand them to an ExecutorAgent and they go through the same ToolPolicy, tool-call audit and lifecycle hooks as any other tool.

Wiring

With spring-ai-starter-mcp-client on the classpath, Spring Boot creates a ToolCallbackProvider bean for the configured MCP servers. Pass it to the executor with toolProviders(...):

@Bean
ExecutorAgent support(ChatClient.Builder chat, ToolCallbackProvider mcpTools) {
    return ExecutorAgent.builder()
            .name("support")
            .chatClient(chat.build())
            .toolProviders(mcpTools)
            .toolPolicy(ToolPolicy.denyList("orders_refund_order"))
            .build();
}

The provider is queried on every run, so tools an MCP server adds or removes while the application is running are picked up without a restart.

Tool names

Spring AI prefixes each MCP tool with the client name: tool refund_order served through the client orders becomes orders_refund_order. ToolPolicy rules, ToolCallRecord.name() and onToolCall all use the prefixed name. McpToolUtils.prefixedToolName(client, tool) computes it.

Do not register MCP tools on the ChatClient

Warning

Tools registered on the ChatClient itself, for example with ChatClient.builder(model).defaultToolCallbacks(mcpTools), are invisible to AF4J: they bypass the ToolPolicy, are not recorded as ToolCallRecords and do not reach onToolCall. Register them on the ExecutorAgent with toolProviders(...) instead.

What you get

Concern Behaviour for MCP tools
ToolPolicy Checked before the call reaches the MCP server; a denied call never leaves the process and the model receives the denial reason as the tool result
Audit Every call, allowed or denied, is a ToolCallRecord on the node's AgentResult
Listeners AgentListener.onToolCall fires once per call
Streaming AgentEvent.ToolCallStart / ToolCallEnd are emitted from invokeStream