spring-projects / spring-projects/spring-ai
StdIO transport doesn't detect MCP server process exit and conceals exit code
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 6
Description
Bug description
If MCP server process exits - this is not detected by an StdioClientTransport that launches said process, or any other code. The initialization of MCP server just times out waiting for stdio response from a process that's no longer running
Environment
Spring AI 2.0.0-M5, mcp-core-2.0.0-M2.jar, macOS Tahoe 26.3.1
Steps to reproduce
Configured chrome-devtools MCP server
application.yaml:
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
mcp-servers.json:
{
"mcpServers": {
"chrome-devtools": {
"command": "/opt/homebrew/bin/npx",
"args": [
"chrome-devtools-mcp@latest",
"--autoConnect"
],
"env":{
"PATH": "/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin"
}
}
}
}
Without the PATH variable explicitly listing paths, the PATH provided by Spring AI is not sufficient for Node/NPX to launch, the command exits with exit code 127 - which is not detected by Spring AI at all, instead initialize command is sent and response is awaited for default 20 seconds
Expected behavior
In case process exits, there should be no wait for response from it, instead exception should be thrown logging exit code of the process
Minimal Complete Reproducible example
Use above files with this main method in @SpringBootApplication
public static void main(String[] args) {
List<McpSyncClient> clients = SpringApplication.run(AiagorcApplication.class, args)
.getBean("mcpSyncClients", List.class);
System.out.println("Clients: " + clients.get(0).listTools());
}
Note: if process exits immediately or quickly, supposedly this can be detected right away in the StdioClientTransport:
ProcessBuilder processBuilder = this.getProcessBuilder();
processBuilder.command(fullCommand);
processBuilder.environment().putAll(params.getEnv());
// Start the process
try {
this.process = processBuilder.start();
}
catch (IOException e) {
throw new RuntimeException("Failed to start process with command: " + fullCommand, e);
}
// Validate process streams
if (this.process.getInputStream() == null || process.getOutputStream() == null) {
this.process.destroy();
throw new RuntimeException("Process input or output stream is null");
}
Debug has shown here the this.process has PID and exit code, but no IOException is thrown - because process ran, it just exited. Alternatively, the input/output streams of exited process are probably closed - and this can be detected in Lambdas created in startInboundProcessing/startOutboundProcessing methods of StdioClientTransport
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with StdioClientTransport, focusing on its startInboundProcessing and startOutboundProcessing methods and the process started by getProcessBuilder(). Reproduce the failing configuration with the documented PATH and observe the process exit. Done means an early exception reports the process exit code instead of waiting for the initialization timeout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100