PHPackages                             code-wheel/mcp-tool-gateway - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. code-wheel/mcp-tool-gateway

ActiveLibrary

code-wheel/mcp-tool-gateway
===========================

MCP Tool Gateway - Dynamic tool discovery, middleware pipeline, and caching for MCP servers

v1.1.2(2mo ago)1504↓68%MITPHPPHP &gt;=8.1CI passing

Since Jan 9Pushed 2mo agoCompare

[ Source](https://github.com/code-wheel/mcp-tool-gateway-php)[ Packagist](https://packagist.org/packages/code-wheel/mcp-tool-gateway)[ RSS](/packages/code-wheel-mcp-tool-gateway/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (6)Versions (5)Used By (0)

MCP Tool Gateway
================

[](#mcp-tool-gateway)

[![CI](https://github.com/code-wheel/mcp-tool-gateway/actions/workflows/ci.yml/badge.svg)](https://github.com/code-wheel/mcp-tool-gateway/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/93cbfa71a842ec1277de515b2f1fd4b3ea3eaceceba165cc8d618eb9e9207297/68747470733a2f2f636f6465636f762e696f2f67682f636f64652d776865656c2f6d63702d746f6f6c2d676174657761792f67726170682f62616467652e737667)](https://codecov.io/gh/code-wheel/mcp-tool-gateway)[![Latest Stable Version](https://camo.githubusercontent.com/b939f613001192c2dccd5eb1f46020ca6e6082c3280c98cb760048e861b0f383/68747470733a2f2f706f7365722e707567782e6f72672f636f64652d776865656c2f6d63702d746f6f6c2d676174657761792f76)](https://packagist.org/packages/code-wheel/mcp-tool-gateway)[![License](https://camo.githubusercontent.com/fd5404a2a54a423c2609f5e3777160fd31d091c9da99f2baa2dafc4600d225f7/68747470733a2f2f706f7365722e707567782e6f72672f636f64652d776865656c2f6d63702d746f6f6c2d676174657761792f6c6963656e7365)](https://packagist.org/packages/code-wheel/mcp-tool-gateway)

A production-ready framework for PHP MCP (Model Context Protocol) servers. Features middleware pipeline, input validation, tool composition, caching, and event dispatching.

Installation
------------

[](#installation)

```
composer require code-wheel/mcp-tool-gateway
```

Features
--------

[](#features)

- **Middleware Pipeline** - Chain validation, auth, logging, and custom middleware
- **Input Validation** - Reject malformed LLM inputs before execution
- **Tool Composition** - Combine multiple tool providers with namespacing
- **Caching** - PSR-16 cache support for discovery and read-only results
- **Events** - PSR-14 event dispatching for tool lifecycle
- **Framework Agnostic** - Works with Drupal, Laravel, Symfony, or vanilla PHP

Quick Start
-----------

[](#quick-start)

### Basic Usage

[](#basic-usage)

```
use CodeWheel\McpToolGateway\ArrayToolProvider;
use CodeWheel\McpToolGateway\ToolInfo;
use CodeWheel\McpToolGateway\ToolResult;

// Create a tool provider
$provider = new ArrayToolProvider([
    'greet' => new ToolInfo(
        name: 'greet',
        label: 'Greet User',
        description: 'Says hello to a user',
        inputSchema: [
            'type' => 'object',
            'properties' => [
                'name' => ['type' => 'string'],
            ],
            'required' => ['name'],
        ],
    ),
]);

// Register handler
$provider->setHandler('greet', function (array $args): ToolResult {
    return ToolResult::success("Hello, {$args['name']}!");
});

// Execute
$result = $provider->execute('greet', ['name' => 'World']);
echo $result->message; // "Hello, World!"
```

### Middleware Pipeline

[](#middleware-pipeline)

```
use CodeWheel\McpToolGateway\Middleware\MiddlewarePipeline;
use CodeWheel\McpToolGateway\Middleware\ValidatingMiddleware;
use CodeWheel\McpToolGateway\Middleware\LoggingMiddleware;
use CodeWheel\McpSchemaBuilder\SchemaValidator;

$pipeline = new MiddlewarePipeline($provider);

// Add validation (rejects malformed inputs)
$validator = new SchemaValidator();
$pipeline->add(new ValidatingMiddleware($provider, $validator));

// Add logging (PSR-3)
$pipeline->add(new LoggingMiddleware($logger));

// Execute through pipeline
$result = $pipeline->execute('create_user', [
    'email' => 'invalid',  // Will be rejected by validation
    'name' => 'John',
]);
```

### Composing Multiple Providers

[](#composing-multiple-providers)

```
use CodeWheel\McpToolGateway\CompositeToolProvider;

// Combine providers with prefixes
$composite = new CompositeToolProvider([
    'drupal' => $drupalProvider,   // drupal/get_users, drupal/create_node
    'custom' => $customProvider,    // custom/my_tool
    'api' => $externalProvider,     // api/fetch_data
]);

// Or without prefixes (tools must have unique names)
$composite = new CompositeToolProvider([
    $provider1,
    $provider2,
], prefixed: false);

$allTools = $composite->getTools();
$result = $composite->execute('drupal/get_users', ['limit' => 10]);
```

### Caching

[](#caching)

```
use CodeWheel\McpToolGateway\CachingToolProvider;

$cached = new CachingToolProvider(
    $provider,
    $cache,              // PSR-16 CacheInterface
    discoveryTtl: 3600,  // Cache tool list for 1 hour
    resultTtl: 300,      // Cache read-only tool results for 5 minutes
    cacheableTools: ['get_config', 'list_users'],  // Tools to cache results
);

// First call hits provider, subsequent calls use cache
$tools = $cached->getTools();
```

### Events

[](#events)

```
use CodeWheel\McpToolGateway\Middleware\EventMiddleware;
use CodeWheel\McpToolGateway\Event\ToolExecutionStarted;
use CodeWheel\McpToolGateway\Event\ToolExecutionSucceeded;
use CodeWheel\McpToolGateway\Event\ToolExecutionFailed;

// Add event dispatching (PSR-14)
$pipeline->add(new EventMiddleware($eventDispatcher));

// Listen for events
$dispatcher->addListener(ToolExecutionStarted::class, function ($event) {
    $this->metrics->increment("tool.{$event->toolName}.started");
});

$dispatcher->addListener(ToolExecutionSucceeded::class, function ($event) {
    $this->metrics->timing("tool.{$event->toolName}.duration", $event->duration);
});

$dispatcher->addListener(ToolExecutionFailed::class, function ($event) {
    $this->alerting->notify("Tool {$event->toolName} failed: {$event->exception->getMessage()}");
});
```

Middleware
----------

[](#middleware)

### Built-in Middleware

[](#built-in-middleware)

MiddlewarePurposeRequires`ValidatingMiddleware`Validates inputs against JSON Schema`mcp-schema-builder``LoggingMiddleware`Logs execution with timingPSR-3 Logger`EventMiddleware`Dispatches lifecycle eventsPSR-14 EventDispatcher### Custom Middleware

[](#custom-middleware)

```
use CodeWheel\McpToolGateway\Middleware\MiddlewareInterface;
use CodeWheel\McpToolGateway\ExecutionContext;
use CodeWheel\McpToolGateway\ToolResult;

class AuthorizationMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly AccessChecker $access,
    ) {}

    public function process(
        string $toolName,
        array $arguments,
        ExecutionContext $context,
        callable $next,
    ): ToolResult {
        // Check access before execution
        if (!$this->access->canExecute($context->userId, $toolName)) {
            return ToolResult::error("Access denied to tool: {$toolName}");
        }

        return $next($toolName, $arguments, $context);
    }
}

class RateLimitMiddleware implements MiddlewareInterface
{
    public function process(
        string $toolName,
        array $arguments,
        ExecutionContext $context,
        callable $next,
    ): ToolResult {
        $key = "{$context->userId}:{$toolName}";

        if ($this->limiter->isLimited($key)) {
            return ToolResult::error("Rate limit exceeded for {$toolName}");
        }

        $this->limiter->hit($key);
        return $next($toolName, $arguments, $context);
    }
}

class AuditMiddleware implements MiddlewareInterface
{
    public function process(
        string $toolName,
        array $arguments,
        ExecutionContext $context,
        callable $next,
    ): ToolResult {
        $start = microtime(true);

        try {
            $result = $next($toolName, $arguments, $context);

            $this->audit->log([
                'tool' => $toolName,
                'user' => $context->userId,
                'success' => $result->success,
                'duration' => microtime(true) - $start,
            ]);

            return $result;
        } catch (\Throwable $e) {
            $this->audit->logError($toolName, $e);
            throw $e;
        }
    }
}
```

### Production Pipeline Example

[](#production-pipeline-example)

```
$pipeline = new MiddlewarePipeline($provider);

// Order matters: outer middleware wraps inner
$pipeline->add(new AuditMiddleware($auditor));              // 1. Audit everything
$pipeline->add(new RateLimitMiddleware($limiter));          // 2. Rate limiting
$pipeline->add(new AuthorizationMiddleware($access));       // 3. Access control
$pipeline->add(new ValidatingMiddleware($provider, $validator)); // 4. Input validation
$pipeline->add(new LoggingMiddleware($logger));             // 5. Execution logging
$pipeline->add(new EventMiddleware($dispatcher));           // 6. Lifecycle events

$result = $pipeline->execute('delete_user', ['user_id' => 123], $context);
```

Execution Context
-----------------

[](#execution-context)

```
use CodeWheel\McpToolGateway\ExecutionContext;

$context = ExecutionContext::create(
    userId: 'user-123',
    requestId: 'req-abc',
    scopes: ['read', 'write', 'admin'],
    metadata: ['client' => 'claude-desktop'],
);

// Check scopes
$context->hasScope('write');           // true
$context->hasAnyScope(['admin', 'superuser']); // true

// Use in middleware
if (!$context->hasScope('write')) {
    return ToolResult::error('Write scope required');
}
```

Custom Tool Provider
--------------------

[](#custom-tool-provider)

```
use CodeWheel\McpToolGateway\ToolProviderInterface;
use CodeWheel\McpToolGateway\ToolInfo;
use CodeWheel\McpToolGateway\ToolResult;
use CodeWheel\McpToolGateway\ExecutionContext;

class DrupalToolProvider implements ToolProviderInterface
{
    public function __construct(
        private readonly ToolPluginManager $pluginManager,
    ) {}

    public function getTools(): array
    {
        $tools = [];
        foreach ($this->pluginManager->getDefinitions() as $id => $def) {
            $tools[$id] = new ToolInfo(
                name: $id,
                label: $def['label'],
                description: $def['description'],
                inputSchema: $def['input_schema'],
                annotations: $def['annotations'] ?? [],
                provider: $def['provider'],
            );
        }
        return $tools;
    }

    public function getTool(string $toolName): ?ToolInfo
    {
        $tools = $this->getTools();
        return $tools[$toolName] ?? null;
    }

    public function execute(
        string $toolName,
        array $arguments,
        ?ExecutionContext $context = null,
    ): ToolResult {
        $plugin = $this->pluginManager->createInstance($toolName);
        $result = $plugin->execute($arguments);

        return new ToolResult(
            success: $result['success'],
            message: $result['message'],
            data: $result['data'] ?? [],
        );
    }
}
```

Gateway Pattern
---------------

[](#gateway-pattern)

For MCP servers with many tools, use the gateway pattern to reduce context window usage:

```
use CodeWheel\McpToolGateway\ToolGateway;

$gateway = new ToolGateway($provider);

// Register just 3 gateway tools instead of 100+ individual tools
foreach ($gateway->getGatewayTools() as $tool) {
    $mcpServer->registerTool($tool['name'], $tool['handler'], $tool['inputSchema']);
}

// LLM uses:
// - gateway/discover-tools { "query": "user" }
// - gateway/get-tool-info { "tool_name": "create-user" }
// - gateway/execute-tool { "tool_name": "create-user", "arguments": {...} }
```

Integration with mcp-schema-builder
-----------------------------------

[](#integration-with-mcp-schema-builder)

```
use CodeWheel\McpSchemaBuilder\SchemaBuilder;
use CodeWheel\McpSchemaBuilder\McpSchema;
use CodeWheel\McpSchemaBuilder\SchemaValidator;

// Build schema with presets
$schema = SchemaBuilder::object()
    ->property('user_id', McpSchema::entityId('user')->required())
    ->property('status', SchemaBuilder::string()->enum(['active', 'blocked']))
    ->merge(McpSchema::pagination())
    ->build();

// Validate in middleware
$validator = new SchemaValidator();
$pipeline->add(new ValidatingMiddleware($provider, $validator));
```

Integration with mcp-error-codes
--------------------------------

[](#integration-with-mcp-error-codes)

```
use CodeWheel\McpErrorCodes\McpError;

// In your tool handler
public function execute(array $args): ToolResult
{
    $user = $this->users->find($args['user_id']);

    if (!$user) {
        return McpError::notFound('user', $args['user_id'])
            ->withSuggestion('Check if the user ID is correct')
            ->toToolResult();
    }

    // ...
}
```

License
-------

[](#license)

MIT License - see [LICENSE](LICENSE) file.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

74d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4cb6eebdce9234e8ad086e2786111257d1f959a3f7323c9fdae46c600082f646?d=identicon)[mowens3](/maintainers/mowens3)

---

Top Contributors

[![mowens3](https://avatars.githubusercontent.com/u/1635473?v=4)](https://github.com/mowens3 "mowens3 (18 commits)")

---

Tags

middlewaremcpdiscoverygatewaytoolsModel Context Protocol

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/code-wheel-mcp-tool-gateway/health.svg)

```
[![Health](https://phpackages.com/badges/code-wheel-mcp-tool-gateway/health.svg)](https://phpackages.com/packages/code-wheel-mcp-tool-gateway)
```

###  Alternatives

[php-mcp/laravel

Laravel SDK for building Model Context Protocol (MCP) servers - Seamlessly integrate MCP tools, resources, and prompts into Laravel applications

47269.4k1](/packages/php-mcp-laravel)[php-mcp/server

PHP SDK for building Model Context Protocol (MCP) servers - Create MCP tools, resources, and prompts

822230.3k21](/packages/php-mcp-server)[wordpress/mcp-adapter

Adapter for Abilities API, letting WordPress abilities to be used as MCP tools, resources or prompts

72644.2k1](/packages/wordpress-mcp-adapter)[vizra/vizra-adk

Vizra Agent Development Kit - A comprehensive Laravel package for building intelligent AI agents.

28621.7k](/packages/vizra-vizra-adk)[klapaudius/symfony-mcp-server

Build your own LLM tools inside your symfony project by adding to it a Model Context Protocol Server

2614.3k](/packages/klapaudius-symfony-mcp-server)[symfony/ai-mate

AI development assistant MCP server for Symfony projects

147.4k7](/packages/symfony-ai-mate)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
