I’m integrating a Spring Boot application with the Spring AI MCP Server (spring-ai-starter-mcp-server-webmvc). I’m testing it using another Spring Boot application as the client, leveraging the spring-ai-starter-mcp-client dependency. The client is configured to send requests with a bearer token, which works fine for listing tools:
java
McpSyncClient client = McpClientFactory.getInstance();
client.listTools();
However, when invoking a tool with:
java
client.callTool(new McpSchema.CallToolRequest(toolName, params));
I encounter the error: “Security Context is invalid”. The issue seems to be that the MCP server delegates tool execution to a boundedElastic thread, which does not propagate the original request’s security context. The security context is required at the tool level to determine parameters like organizationId.
What I’ve Tried:
- DelegatingSecurityContextAsyncTaskExecutor (didn’t propagate)
- DelegatingSecurityContextTaskExecutor (didn’t propagate)
- SecurityContextHolder.MODE_INHERITABLETHREADLOCAL (propagates, but a mess for thread pool)
- Swapping sdk’s WebMvcSseServerTransportProvider with a custom provider: so I can manually set the context in the new thread, but just copying the sdk’s class still changed the behavior and failed to even get the toolList, let alone modifying it.
Below is basically how I’d enforce use of my custom provider
@Bean
@Primary
public McpServerTransportProvider customWebMvcSseServerTransportProvider() {
return new CustomWebMvcSseServerTransportProvider(objectMapper, "/mcp/message");
}
sdk’s provider method that leads to the boundedElastic thread:
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
try {
String jsonText = WebMvcSseServerTransportProvider.this.objectMapper.writeValueAsString(message);
this.sseBuilder.id(this.sessionId).event("message").data(jsonText);
WebMvcSseServerTransportProvider.logger.debug("Message sent to session {}", this.sessionId);
} catch (Exception var3) {
Exception e = var3;
WebMvcSseServerTransportProvider.logger.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage());
this.sseBuilder.error(e);
}
});
}
Question:
How can I ensure the security context is properly propagated to the boundedElastic thread when calling tools in the Spring AI MCP Server? Is there a recommended configuration or workaround for this issue?
Dependencies I am using:
Server:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
Client:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>