Spring AI Recipe: Controlling MCP Tool Visibility
A well-stocked toolbox is useful. That is, until you have to dig through dozens of tools to find the one you need. The same is true for MCP servers, where exposing fewer tools can often make an AI application faster, cheaper, and more focused.
After a while, most people accumulate a collection of tools for projects around the house. Various screwdrivers, hammers, hex wrenches, and pliers all end up in a toolbox that’s ready for the next project.
But you don’t need every tool for every project. If you’re hanging a picture, you probably only need a hammer and a few nails. Wouldn’t it be nice if, when you opened your toolbox, it contained only the tools relevant to the task at hand? It would save you from digging through tools you’ll never use.
MCP servers are a bit like that toolbox. An MCP server may expose several tools to a client, but not every client needs access to every tool. Moreover, every tool definition — including its description and parameters — must be sent to the LLM as part of the prompt. The more tools you expose, the more tokens you consume.
Fortunately, Spring AI offers a way to filter out tools that your client doesn’t need. In this recipe, you’ll learn how to use McpToolFilter to keep tool availability focused on the task at hand while reducing unnecessary token usage.
To get started, we’ll need an MCP server with several tools to choose from. Begin with the MCP server from the Streamable HTTP MCP Server recipe and add a few more tools alongside the existing weather tool.
NOTE: This recipe has been updated since it’s original publication to reflect changes in Spring AI 2.0.0.
Adding Tools to the MCP Server
In addition to current weather conditions, our MCP server could also provide air quality and allergen information. To represent that data, let’s add a few domain types:
// Allergy conditions
public record AllergyConditions(
AllergyLevel overall,
AllergyLevel treePollen,
AllergyLevel grassPollen,
AllergyLevel weedPollen,
AllergyLevel mold) {}
// Allergy level enum
public enum AllergyLevel {
VERY_HIGH("Very High"),
HIGH("High"),
MODERATE("Moderate"),
LOW("Low"),
NONE("None");
private final String label;
AllergyLevel(String label) {
this.label = label;
}
@JsonValue
public String getLabel() {
return label;
}
}
// Air quality
public record AirQuality(
int aqi,
AirQualityCategory category,
String primaryPollutant) {}
// Air quality category enum
public enum AirQualityCategory {
GOOD("Good"),
MODERATE("Moderate"),
UNHEALTHY_FOR_SENSITIVE_GROUPS("Unhealthy for Sensitive Groups"),
UNHEALTHY("Unhealthy"),
VERY_UNHEALTHY("Very Unhealthy"),
HAZARDOUS("Hazardous");
private final String label;
AirQualityCategory(String label) {
this.label = label;
}
@JsonValue
public String getLabel() {
return label;
}
}Now let’s add a few more MCP tools to the existing WeatherTools class:
@Service
public class WeatherTools {
@McpTool(name = "get-weather-for-zipcode",
description = "Get weather conditions for a given zipcode")
public Weather getWeatherForZipCode(
@McpToolParam(description = "The zipcode to get weather for") String zipcode) {
return new Weather(zipcode, "Raining cats and dogs", "78F");
}
@McpTool(name = "get-air-quality-for-zipcode",
description = "Get AQI for a given zipcode")
public AirQuality getAirQualityForZipCode(
@McpToolParam(description = "The zipcode to get AQI for") String zipcode) {
return new AirQuality(87, AirQualityCategory.MODERATE, "PM2.5");
}
@McpTool(name = "get-allergens-for-zipcode",
description = "Get current allergy conditions for a given zipcode")
public AllergyConditions getAllAllergensForZipCode(
@McpToolParam(description = "The zipcode to get AQI for") String zipcode) {
return new AllergyConditions(
AllergyLevel.MODERATE,
AllergyLevel.MODERATE,
AllergyLevel.MODERATE,
AllergyLevel.NONE,
AllergyLevel.LOW);
}
}As with the existing weather tool, these new tools return hardcoded values to keep the example simple. In a real-world implementation, these methods would likely call an external API to retrieve current conditions.
With multiple tools available, we can now move to the client side and decide which of those tools should actually be exposed to the LLM.
Filtering Tools on the Client
Use the code from the MCP Client recipe as a starting point. Without making any changes, you should be able to run the server, then run the client, and ask questions about weather, allergens, and air quality. For example: “My eyes are burning. What allergens are high in 88252?
Now let’s add a filter that prevents the allergen tool from being used. To do that, create an implementation of McpToolFilter and register it as a Spring bean:
@Component
public class ToolFilter implements McpToolFilter {
private static final Logger logger =
LoggerFactory.getLogger(ToolFilter.class);
@Override
public boolean test(McpConnectionInfo mcpConnectionInfo,
McpSchema.Tool tool) {
if (tool.name().contains("allergen")) {
logger.info("Filtering out tool: {}", tool.name());
return false;
}
logger.info("Allowing tool: {}", tool.name());
return true;
}
}The @Component annotation ensures that Spring's component scanning discovers the filter and registers it in the application context.
The filtering logic happens in the test() method. Spring AI invokes this method once for every tool exposed by the MCP server.
- Returning
truemakes the tool available to the client. - Returning
falseremoves the tool from consideration. - Returning
truefor every tool exposes them all. - Returning
falsefor every tool exposes none of them.
In this example, any tool whose name contains "allergen" is filtered out. As a result, the get-allergens-for-zipcode tool is hidden from the LLM, while the weather and air quality tools remain available.
If you run the client and ask about allergens, the model may still provide general information about allergies from its training data, but it won’t be able to retrieve zipcode-specific allergen levels because the MCP tool required to obtain that information is no longer available.
Why Filter Tools?
Reducing token usage is one benefit of tool filtering, but it isn’t the only one. Another common reason is controlling access to capabilities. Consider an MCP server that integrates with an issue-tracking system. It might expose tools for both reading and updating issues. If you want your client to operate in a read-only mode, you could filter out the update tools while leaving the query tools available.
Similarly, you might expose different subsets of tools depending on:
- The user’s role or permissions
- The environment (development vs. production)
- The task being performed
- The specific MCP server connection
Tool filtering gives you fine-grained control over which capabilities are available to the LLM, helping reduce token costs, improve focus, and prevent accidental use of tools that shouldn’t be invoked.
By carefully selecting which tools are exposed, you can ensure that your MCP-enabled applications provide exactly the capabilities they need, and nothing more.
Get the code for this recipe and other Spring AI recipes at https://github.com/habuma/spring-ai-recipes.
If you’re exploring Spring AI, I’ve written a book (Spring AI in Action) that covers the core foundations — and I’ll continue sharing newer patterns like this here as the ecosystem evolves. You can find my book on the publisher’s website, at Amazon, or anywhere you buy technical books.