Skip to content

[Feature]: Keep STDIO Processes Alive #24

@MaestroError

Description

@MaestroError

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() and clear()
  • 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:

  1. Every request spawns a new process
  2. Process startup overhead occurs every time
  3. Initialization handshakes are repeated unnecessarily
  4. 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 again

Each 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions