Closed
Description
I built an MCP Server using spring-ai-mcp-server-webmvc-spring-boot-starter:1.0.0-SNAPSHOT
. It can connect normally through a custom MCP Client or MCP Inspector, get tools, and call tool logic by integrating with ChatGPT (spring-ai-openai-spring-boot-starter:1.0.0-M5
). However, after calling a tool, an error occurs(sometimes it succeeds, sometimes it fails, but it fails most of the time.):
Failed to send message to session 012dcdf9-1654-4d08-873c-9d20ac112fbf: Cannot invoke "org.apache.catalina.connector.OutputBuffer.isBlocking()" because "this.ob" is null.
But when I implement the MCP Server using spring-boot-starter-webflux
, this issue does not occur.
Server core
@Configuration
public class McpWebServerConfig {
private static final Logger logger = LoggerFactory.getLogger(McpWebServerConfig.class);
private final ApiClient apiClient;
private final S2SAccessTokenGenerator tokenGenerator;
public McpWebServerConfig(ApiClient apiClient, S2SAccessTokenGenerator tokenGenerator) {
this.apiClient = apiClient;
this.tokenGenerator = tokenGenerator;
}
@Bean
public List<McpServerFeatures.SyncToolRegistration> settingsToolRegistrations() {
var userSettings = new McpServerFeatures.SyncToolRegistration(new McpSchema.Tool("userSettings",
"Retrieve a user's settings. For user-level apps, pass the me value instead of the userId parameter.",
"{\"type\":\"object\",\"properties\":{\"userId\":{\"type\":\"string\",\"description\":\"The user ID or email address of the user. For user-level apps, pass the `me` value.\"}},\"required\":[\"userId\"]}"),
(args) -> {
logger.info("Get user settings request: {}\"", args);
Object result = apiClient.get()
.uri("/users/{userId}/settings", args.get("userId"))
.header("Authorization", "Bearer " + tokenGenerator.generateAccessToken())
.retrieve()
.body(Object.class);
try {
logger.info("Get user settings success: {}\"", new ObjectMapper().writeValueAsString(result));
return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(
"Get user settings successfully, " + new ObjectMapper().writeValueAsString(result))),
false);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
});
return List.of(userSettings);
}
}
Maven dependences
Client core
@Configuration
@EnableConfigurationProperties(McpServerConfig.class)
public class McpClientConfig {
private static final Logger logger = LoggerFactory.getLogger(McpClientConfig.class);
private final McpServerConfig mcpServerConfig;
public McpClientConfig(McpServerConfig mcpServerConfig) {
this.mcpServerConfig = mcpServerConfig;
}
@Bean
public List<McpFunctionCallback> functionCallbacks(McpSyncClient mcpClient) {
var callbacks = mcpClient.listTools(null)
.tools()
.stream()
.map(tool -> new McpFunctionCallback(mcpClient, tool))
.toList();
return callbacks;
}
@Bean(destroyMethod = "close")
public McpSyncClient mcpClient() {
var mcpClient = McpClient
.sync(new HttpClientSseClientTransport(mcpServerConfig.getUrl()))
.requestTimeout(Duration.ofSeconds(60))
.build();
var init = mcpClient.initialize();
logger.info("MCP Initialized: {}", init);
return mcpClient;
}
}
**ChatContoller**
private final ChatClient.Builder chatClientBuilder;
private final List<McpFunctionCallback> functionCallbacks;
public ChatController(ChatClient.Builder chatClientBuilder,
List<McpFunctionCallback> functionCallbacks) {
this.chatClientBuilder = chatClientBuilder;
this.functionCallbacks = functionCallbacks;
}
@PostMapping("/chat")
public Object question(@RequestBody ChatMessage chatMessage) {
var chatClient = chatClientBuilder.defaultFunctions(functionCallbacks.toArray(new McpFunctionCallback[0]))
.build();
logger.info("Communicate with AI question: {}", chatMessage.getMessage());
ChatClient.CallResponseSpec response = chatClient.prompt(chatMessage.getMessage()).call();
String responseContent = response.content();
logger.info("Get AI response: {}", responseContent);
return responseContent;
}
Client dependences
Metadata
Metadata
Assignees
Labels
No labels