-
-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Summary
Priority: 🟡 MEDIUM-HIGH
Impact: 200-300ms saved per request
Difficulty: Medium
Testing Checklist
- Process is created on first request
- Same process is reused for subsequent requests
- Process is not re-initialized on reuse
-
disconnect()method properly closes process -
isHealthy()correctly identifies healthy processes - TransporterPool closes STDIO processes on
forget()andclear() - Process is restarted if it dies unexpectedly
- No memory leaks from keeping processes alive
Expected Outcome
After implementing this:
- First request: Process starts (startup delay incurred)
- Subsequent requests: Process is reused (NO startup delay!)
- Savings: 200-300ms per request after the first one
Problem
Currently, StdioTransporter destroys the process after each use via the __destruct() method. This means:
- Every request spawns a new process
- Process startup overhead occurs every time
- Initialization handshakes are repeated unnecessarily
- Resources are wasted stopping and starting the same process
Example of Current Behavior
// First request
$result1 = $client->connect('npx_server')->callTool('tool1', []);
// Process started, initialized, used, then DESTROYED
// Second request
$result2 = $client->callTool('tool2', []);
// New process started, initialized again, used, then DESTROYED againEach request pays the full process startup cost!
Proposed Solution
Keep STDIO processes alive for reuse across multiple requests. Only close them when explicitly needed or when the application terminates.
Implementation Steps
Step 1: Remove Automatic Cleanup from __destruct
In src/Core/Transporters/StdioTransporter.php:
Current code:
public function __destruct()
{
$this->close();
}New code:
public function __destruct()
{
// Don't automatically close - let TransporterPool manage lifecycle
// Only close if process has encountered errors
if (isset($this->process) && !$this->process->isRunning()) {
$this->cleanup();
}
}Step 2: Add Process Health Check
Add a method to check if the process is still healthy:
/**
* Check if the process is running and healthy.
*
* @return bool
*/
public function isHealthy(): bool
{
return isset($this->process)
&& $this->process->isRunning()
&& !$this->hasErrors();
}
/**
* Check if the process has encountered errors.
*
* @return bool
*/
private function hasErrors(): bool
{
if (!isset($this->process)) {
return false;
}
$errorOutput = $this->process->getErrorOutput();
// Check for common error patterns
// Adjust these patterns based on your MCP servers
$errorPatterns = [
'/error/i',
'/exception/i',
'/fatal/i',
'/crash/i',
];
foreach ($errorPatterns as $pattern) {
if (preg_match($pattern, $errorOutput)) {
return true;
}
}
return false;
}Step 3: Update start() to Reuse Existing Process
Step 4: Update sendInitializeRequests to Track Initialization
If it's not already there, Add a flag to prevent re-initialization
Update cleanup() to Reset Initialization Flag
Step 6: Add Explicit Close Method for Users
Add a public method to explicitly close the process if needed:
/**
* Explicitly close the STDIO process.
* Use this when you're done with the transporter and want to free resources.
*
* @return void
*/
public function disconnect(): void
{
$this->close();
}Step 7: Update TransporterPool to Cleanup on Clear
In src/Core/TransporterPool.php
Step 8: Add Configuration Option
Update config/mcp-client.php to add keep-alive option:
'npx_mcp_server' => [
'type' => \Redberry\MCPClient\Enums\Transporters::STDIO,
// ...
'keep_alive' => true, // Keep process alive for reuse (recommended)
],Default true (even when not provided via config)
When false, disable keep alive feature and use the current approach
Step 9: Add Tests
Add to tests/Transporter/StdioTransporterTest.php:
process is reused across multiple requests
isHealthy returns true for running process
disconnect closes the process
Alternatives Considered
No response
Package Version
1
PHP Version
8.4
Laravel Version
12
Notes
No response