MCP Support
springdoc-openapi provides integration with the Model Context Protocol (MCP), bridging your REST API with AI Agents using Spring AI.
This integration automatically turns your documented @RestController endpoints into AI Tools, making them discoverable and callable by MCP-enabled clients such as Claude Desktop, Cursor, and other AI agents.
Modules
| Module | Description |
|---|---|
|
Core engine: properties, tool callbacks, schema converter, customizers, annotations, MCP dashboard controller, and frontend assets. |
|
WebMvc integration: static resource configurer, servlet API version strategy, |
|
WebFlux integration: reactive resource configurer, reactive API version strategy, |
Getting Started
Add the dependency matching your web stack:
For WebMvc (Spring MVC / Servlet):
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-mcp</artifactId>
<version>3.1.0</version>
</dependency>
For WebFlux (Reactive):
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-mcp</artifactId>
<version>3.1.0</version>
</dependency>
Enable the integration in your application.properties:
springdoc.ai.mcp.enabled=true
That’s it. Your @RestController endpoints are now available as MCP tools. AI agents connecting to your MCP server will automatically discover them.
When MCP is enabled, springdoc.pre-loading-enabled is automatically forced to true by an environment post-processor, ensuring the OpenAPI specification is available at startup for tool registration.
|
How It Works
-
At startup, springdoc scans your
@RestControllerendpoints and builds the OpenAPI specification (pre-loading is forced automatically). -
Each OpenAPI operation is converted into a
ToolCallbackwith:-
A name derived from the
operationId(converted tosnake_casefor better LLM tokenization, e.g.,getUserByIdbecomesget_user_by_id). -
A description from
summary/description, enriched with return type information (e.g., "Returns User (id, name, email)"). -
A JSON Schema input schema from parameters and request body.
-
A group from the first
@Tagannotation on the controller.
-
-
The customizer chain runs on each tool definition, applying
@McpToolDescriptionannotations first, then any user-registeredMcpToolCustomizerbeans. -
AI agents connect to the MCP server, discover the available tools, and can invoke your API endpoints directly.
-
When an MCP server (
McpSyncServer) is present, tools are registered dynamically after all singletons are instantiated via a deferred registration mechanism to avoid startup deadlocks.
MCP Guardrails
Guardrails protect your API from unintended mutations triggered by AI agents. Two complementary safety features are built in:
Safe vs. Mutating Classification
Every MCP tool is automatically classified based on its HTTP method:
| Classification | HTTP Methods |
|---|---|
Safe (read-only) |
|
Mutating |
|
Individual endpoints can override this classification by calling context.setSafeEndpoint(Boolean) inside a McpToolCustomizer bean, which takes precedence over the global safe-methods list.
Human-in-the-Loop (HITL)
When require-approval-for-mutating-tools is true (the default), calling a mutating tool through the MCP protocol does not execute the underlying HTTP request. Instead, the agent receives a structured JSON response:
{
"requires_human_approval": true,
"tool_name": "create_book",
"http_method": "POST",
"path": "/books",
"arguments": { "title": "...", "author": "..." },
"message": "This mutating operation (POST /books) requires human approval before execution."
}
The AI agent is expected to relay this to the user, who can then confirm or reject the action.
To allow AI agents to call mutating tools freely (e.g., in a trusted internal environment):
springdoc.ai.mcp.guardrails.require-approval-for-mutating-tools=false
Extension Points
McpToolCustomizer
Register a McpToolCustomizer bean to modify the tool name, description, or input schema for any operation. Return null to exclude a tool entirely. Multiple customizers are applied in order.
@Bean
McpToolCustomizer rewriteDescriptions() {
return (context, path, method, operation) -> {
// Rename a tool
if ("listUsers".equals(context.getName())) {
context.setName("fetchAllUsers");
}
// Rewrite a description
if ("getUserById".equals(context.getName())) {
context.setDescription("Look up a single user by their unique ID");
}
// Exclude a tool by returning null
if (path.startsWith("/internal")) {
return null;
}
return context;
};
}
The McpToolDefinitionContext passed to each customizer exposes:
| Getter / Setter | Description |
|---|---|
|
The tool name (defaults to the |
|
The tool description shown to AI agents. |
|
The JSON Schema string for tool input. |
|
Set to |
|
Override the safe/mutating classification for this endpoint. |
Excluding endpoints via context.setExclude(true):
@Bean
McpToolCustomizer excludeDeleteAndInternal() {
return (context, path, method, operation) -> {
if (method == PathItem.HttpMethod.DELETE || path.startsWith("/internal")) {
context.setExclude(true);
}
return context;
};
}
Per-endpoint safety override via context.setSafeEndpoint(Boolean):
@Bean
McpToolCustomizer overrideSafety() {
return (context, path, method, operation) -> {
// Treat POST /reports/generate as safe (read-only despite POST method)
if (method == PathItem.HttpMethod.POST && "/reports/generate".equals(path)) {
context.setSafeEndpoint(true);
}
// Treat GET /cache/invalidate as mutating despite GET method
else if (method == PathItem.HttpMethod.GET && "/cache/invalidate".equals(path)) {
context.setSafeEndpoint(false);
}
return context;
};
}
@McpToolDescription Annotation
Place @McpToolDescription on a controller method to provide an AI-optimized description that overrides the OpenAPI summary/description in the MCP tool definition. Optionally override the tool name.
@GetMapping("/orders/{id}")
@Operation(summary = "Get an order by ID", operationId = "getOrderById")
@McpToolDescription(value = "Look up a single order by its unique identifier",
name = "findOrder")
public Order getOrderById(@PathVariable String id) { ... }
| Attribute | Required | Description |
|---|---|---|
|
yes |
AI-optimized description for the tool. |
|
no |
Tool name override. When empty, the |
The built-in McpToolDescriptionCustomizer reads these annotations and runs at Ordered.HIGHEST_PRECEDENCE, so user-registered McpToolCustomizer beans can further modify the annotation-provided values.
Tool Grouping
Tools in the MCP dashboard are automatically organized into groups:
-
OpenAPI-generated tools are grouped by their first
@Tagannotation. For example, a controller annotated with@Tag(name = "Users")will have all its operations grouped under "Users". -
Native MCP tools (from
@McpTool/@Toolannotated methods) are grouped under"mcp-tools". -
Tools without a tag appear under "Other" in the dashboard.
@RestController
@Tag(name = "Users")
public class UserController {
@GetMapping("/users")
@Operation(operationId = "listUsers")
public List<User> listUsers() { ... } // group: "Users"
@GetMapping("/users/{id}")
@Operation(operationId = "getUserById")
public User getUserById(@PathVariable String id) { ... } // group: "Users"
}
@RestController
@Tag(name = "Products")
public class ProductController {
@GetMapping("/products")
@Operation(operationId = "listProducts")
public List<Product> listProducts() { ... } // group: "Products"
}
MCP Dashboard
The built-in MCP Developer Dashboard is a web UI served at the configured dashboard-path (default: /mcp-ui). Enable it with:
springdoc.ai.mcp.dashboard-enabled=true
It provides:
-
Tool Discovery: Lists all registered MCP tools grouped by their OpenAPI tags, with names, descriptions, HTTP methods, paths, and input schemas.
-
Tool Execution: Execute any tool directly from the browser with JSON input, view response body, status code, and execution duration.
-
Guardrail Indicators: Mutating tools that require human approval are marked with an amber warning badge in the tool list. Executing a blocked tool shows an
Approval Requiredresponse. -
Header Forwarding: Authentication headers (e.g.,
Authorization, API keys) are automatically forwarded from the dashboard to the underlying API calls. -
Security Settings: Configure authentication credentials (Bearer tokens, Basic auth, API keys) in the dashboard UI.
-
Multi-Source Support: Discovers tools from both
ToolCallbackProviderbeans (OpenAPI-backed tools) andMcpSyncServertool specifications (@McpToolannotated methods). -
cURL Export: Copy tool invocations as cURL commands for use outside the dashboard.
-
Latency Attribution: The audit tab displays a stacked latency bar per tool call with a tooltip showing the full breakdown.
-
Payload Size & Token Impact: Trace viewer tabs show byte sizes and estimated token counts for MCP arguments, request bodies, and response bodies. Responses larger than 2 KB trigger an orange warning suggesting lightweight DTOs to reduce context window usage.
Schema Conversion
The OpenApiSchemaConverter translates OpenAPI operations into JSON Schema suitable for AI tool input. It handles:
-
Parameters: Path, query, and header parameters with types, descriptions, and required markers.
-
Undeclared Path Variables: Detects
{var}in path templates not declared as parameters and adds them as required string properties. -
Request Body: Mapped as a
bodyproperty; supports$refresolution and multiple media types (prefersapplication/json). -
Composed Schemas:
allOf(merged),anyOf,oneOfcomposition keywords. -
Nested Objects: Recursive property processing with circular reference detection.
-
Validation Constraints:
pattern,minimum/maximum,exclusiveMinimum/exclusiveMaximum,minLength/maxLength,minItems/maxItems,multipleOf,enum,default. -
Response Descriptions: Appends a human-readable return type description (e.g., "Returns an array of User (id, name, email)") to the tool description.