Skip to content

[Feature]: Connection/Transporter Pooling and Reuse #22

@MaestroError

Description

@MaestroError

Summary

Priority: 🔴 HIGH
Impact: 50-80% reduction in latency for subsequent requests
Difficulty: Medium

Testing Checklist

  • TransporterPool correctly stores and retrieves transporters
  • Same transporter instance is returned for same server name
  • forget() method removes specific transporter
  • clear() method removes all transporters
  • MCPClient uses TransporterPool instead of creating new instances
  • All existing MCPClient tests still pass
  • Multiple connects to same server reuse transporter

Expected Outcome

After implementing this:

  • First connect() call: Creates new transporter (same as before)
  • Subsequent connect() calls to same server: Reuses existing transporter (MUCH faster)
  • Users can force reconnection with reconnect() method if needed

Problem

Currently, every time MCPClient::connect() is called, a new transporter instance is created. This means:

  1. For HTTP transporters:

    • A new Guzzle HTTP client is instantiated
    • A new session initialization handshake occurs (extra network round-trip)
    • Memory overhead from multiple client instances
  2. For STDIO transporters:

    • A new process is spawned every time
    • Process startup overhead (even with reduced delays)
    • Initialization handshakes are repeated unnecessarily

Example of Current Inefficiency

// User code that makes multiple requests
$client = app(MCPClient::class);

// First request - creates new transporter, initializes session
$client->connect('github')->tools();

// Second request - creates ANOTHER new transporter, initializes ANOTHER session
$client->connect('github')->resources();

// Third request - yet ANOTHER new transporter and session
$client->connect('github')->callTool('search', ['query' => 'test']);

Each of these creates a brand new transporter instance, even though they're connecting to the same server!

Proposed Solution

Create a TransporterPool class that maintains a singleton registry of active transporters and reuses them.

Implementation Steps

Step 1: Create TransporterPool Class

Create file: src/Core/TransporterPool.php

Step 2: Update MCPClient to Use TransporterPool

Modify src/MCPClient.php:

Step 3: Update Service Provider

Modify src/MCPClientServiceProvider.php:

public function packageBooted(): void
{
    // Register TransporterPool as singleton
    $this->app->singleton(TransporterPool::class, function ($app) {
        return new TransporterPool();
    });

    $this->app->bind(MCPClient::class, function ($app) {
        $servers = $app['config']->get('mcp-client.servers', []);

        return new MCPClient($servers, $app->make(TransporterPool::class));
    });
}

Step 4: Create Tests

Create file: tests/Core/TransporterPoolTest.php

  • creates transporter on first call
  • returns same transporter on subsequent calls
  • forget removes transporter from pool
  • clear removes all transporters
  • getActiveServers returns list of server names

Step 5: Update Existing Tests

Update tests/MCPClient/MCPClientTest.php to use TransporterPool instead of TransporterFactory:

Replace all instances of:

$mockFactory = Mockery::mock(TransporterFactory::class);

With:

$mockPool = Mockery::mock(TransporterPool::class);

And update the expectations from shouldReceive('make') to shouldReceive('get').

Alternatives Considered

No response

Package Version

1

PHP Version

8.4

Laravel Version

12

Notes

No response

Metadata

Metadata

Assignees

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