Govern Spring AI's MCP tool calls with DVARA
The Spring AI MCP client connects your application straight to MCP servers, and the model decides which tools to call. Connect it to DVARA instead. Your application sees the same tools, and DVARA applies your workspace's policy to every call, redacts personal data in results and records each one.
What you need
- A DVARA MCP Gateway with at least one MCP server behind it. The
MCP quickstart gives you one in
about five minutes. It runs at
http://localhost:8080/mcp, and its API key isDEMO_API_KEYin the quickstart's.env. - A DVARA workspace API key.
- Spring AI 2.0 and Spring Boot 4.
Add the MCP client
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
Point a Streamable HTTP connection at DVARA:
spring:
ai:
mcp:
client:
streamable-http:
connections:
dvara:
url: http://localhost:8080
endpoint: /mcp
Send your API key
DVARA needs your key on every request. Add it with a request customizer bean:
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
@Bean
McpSyncHttpClientRequestCustomizer dvaraApiKey(@Value("${dvara.api-key}") String key) {
return (builder, method, endpoint, body, context) ->
builder.header("Authorization", "Bearer " + key);
}
Set dvara.api-key, for example with the DVARA_API_KEY environment variable.
Give the tools to your model
Spring AI turns every tool DVARA lists into a tool callback. Pass them to a
request with tools(...):
@Bean
CommandLineRunner ask(ChatClient.Builder chat, SyncMcpToolCallbackProvider tools) {
return args -> System.out.println(
chat.build()
.prompt("Look up customer C-42, then delete them.")
.tools(tools)
.call()
.content());
}
Spring AI changes the names a little: DVARA lists the quickstart's tools as
demo-tools__lookup_customer, and Spring AI calls them
demo_tools__lookup_customer. Policies in DVARA use the tool's own name,
lookup_customer.
What happens to each call
You can see this without a model by calling the tools yourself:
@Bean
CommandLineRunner check(SyncMcpToolCallbackProvider tools) {
return args -> {
for (var tool : tools.getToolCallbacks()) {
String name = tool.getToolDefinition().name();
String input = name.endsWith("get_order_status")
? "{\"orderId\":\"ORD-1001\"}"
: "{\"customerId\":\"C-42\"}";
try {
System.out.println(name + " -> " + tool.call(input));
} catch (Exception e) {
System.out.println(name + " -> refused: " + e.getMessage());
}
}
};
}
Against the quickstart:
demo_tools__delete_customer -> refused: delete_customer is irreversible and not allowed in this workspace
demo_tools__lookup_customer -> [{"text":"\"Customer C-42: Jane Doe, [REDACTED_EMAIL], [REDACTED_PHONE_NUMBER],\\ncard on file [REDACTED_CREDIT_CARD], plan Enterprise.\""}]
demo_tools__get_order_status -> [{"text":"\"Order ORD-1001: shipped, arriving Thursday.\""}]
- The delete never reaches the MCP server. The quickstart's policy denies it, and Spring AI raises the refusal as an exception with the policy's message.
- The customer record comes back redacted. The email, phone number and card number are replaced before your application or the model sees them.
- Every call is recorded. Find them in Flightdeck under MCP → Tool Calls.
Govern the model calls too
The steps above govern your agent's tool calls. Its model calls are a separate stream: point Spring AI's model at DVARA's LLM Gateway as well, and the same workspace governs both.
spring:
ai:
openai:
base-url: http://localhost:8080 # DVARA, without /v1
api-key: ${DVARA_API_KEY}
The Gateway needs a model provider for this. The MCP quickstart doesn't set one up. See Connect Spring AI for the full setup, and Agent sessions to tie an agent's model and tool calls into one session in the audit trail.
Where to go next
- Policy as Code to write your own rules.
- MCP Gateway for approvals, loop detection and the audit trail.