wazobia/nexus-mcp-laravel

Laravel HMAC middleware and MCP server helpers for the Nexus MCP ecosystem

Maintainers

Package info

github.com/wazobiatech/nexus-mcp-laravel

pkg:composer/wazobia/nexus-mcp-laravel

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-06-11 17:32 UTC

This package is not auto-updated.

Last update: 2026-06-12 15:38:45 UTC


README

Laravel HMAC middleware and MCP server helpers for the Nexus MCP ecosystem.

The PHP/Laravel equivalent of the TypeScript (@wazobiatech/nexus-mcp), Python (wazobiatech-nexus-mcp), and Go (github.com/wazobiatech/nexus-mcp-go) SDKs. All four SDKs produce byte-identical HMAC-SHA256 signatures, verified by the shared contract test vectors.

Requirements

  • PHP ^8.1
  • Laravel ^10.0 or ^11.0
  • Guzzle ^7.0

Installation

composer require wazobia/nexus-mcp-laravel

The McpServiceProvider is auto-discovered — no manual registration needed.

Quick Start

Create a route file routes/mcp.php and register it in your RouteServiceProvider (or bootstrap/app.php in Laravel 11):

use Wazobia\NexusMcp\Manifest;
use Wazobia\NexusMcp\ManifestContext;
use Wazobia\NexusMcp\McpRouter;
use Wazobia\NexusMcp\McpToolDefinition;

$manifest = new Manifest(
    namespace: 'my-service',
    description: 'My service MCP manifest',
    version: '1.0.0',
    context: new ManifestContext(
        boundedContext: 'my-service',
        description: 'Handles ...',
        capabilities: ['...'],
        knownGaps: [],
    ),
    tools: [],
);

$tools = [
    new McpToolDefinition(
        name: 'my_tool',
        description: 'Does something useful',
        inputSchema: [
            'type' => 'object',
            'properties' => [
                'message' => ['type' => 'string', 'description' => 'Input message'],
            ],
            'required' => ['message'],
        ],
        handler: function (array $args): array {
            return ['result' => 'Hello, ' . $args['message']];
        },
    ),
];

McpRouter::register(
    manifest: $manifest,
    tools:    $tools,
    secret:   env('MCP_HMAC_SECRET'),
    healthExtra: [
        'server'    => 'my-service-mcp-server',
        'version'   => '1.0.0',
        'timestamp' => now()->toISOString(),
    ],
);

This registers three routes:

Method Path Auth Description
GET /health None K8s liveness probe
GET /mcp/manifest HMAC Returns service manifest JSON
POST /mcp/call HMAC Invokes a named tool

Environment Variables

Variable Required Description
MCP_HMAC_SECRET Shared HMAC-SHA256 secret (min 16 chars)

Classes

Hmac

Core signing utilities.

use Wazobia\NexusMcp\Hmac;

// Compute a signature
$sig = Hmac::computeSignature('POST', '/mcp/call', '1718000000', $secret);

// Sign a request — returns [signature, timestamp]
[$sig, $ts] = Hmac::signRequest('GET', '/mcp/manifest', $secret);

// Check if a timestamp is stale (> 300s from now)
$stale = Hmac::isStale(1718000000);

HmacMiddleware

Laravel HTTP middleware that validates incoming HMAC signatures on MCP routes. Automatically exempts /health, /health/live, and /health/ready for K8s probes.

// Recommended — via McpRouter::register() (secret bound through container)
McpRouter::register($manifest, $tools, env('MCP_HMAC_SECRET'));

// Direct usage
app()->singleton('nexus-mcp.hmac_secret', fn () => env('MCP_HMAC_SECRET'));
Route::middleware(\Wazobia\NexusMcp\HmacMiddleware::class)->group(function () {
    // your HMAC-protected routes
});

Note: Do not pass the secret as a middleware string parameter (HmacMiddleware::class . ':' . $secret). Laravel's param parser splits on commas, which would silently truncate any secret containing one. The container binding avoids this entirely.

HmacClient

Guzzle-based HTTP client that automatically signs every outbound request.

use Wazobia\NexusMcp\HmacClient;

$client = new HmacClient('http://mercury:4001', env('MERCURY_HMAC_SECRET'));

// GET /mcp/manifest
$response = $client->get('/mcp/manifest');
$manifest = json_decode($response->getBody(), true);

// POST /mcp/call
$response = $client->post('/mcp/call', [
    'json' => ['tool' => 'login', 'arguments' => ['email' => 'a@b.com']],
]);

// Query strings are signed correctly (ksorted, RFC3986-encoded)
$response = $client->get('/mcp/manifest', ['query' => ['version' => '1', 'format' => 'full']]);

McpRouter

Registers the three standard MCP endpoints on the Laravel router.

McpRouter::register(
    manifest:    $manifest,       // Manifest DTO
    tools:       $tools,          // McpToolDefinition[]
    secret:      $secret,         // HMAC secret
    prefix:      'api',           // Optional route prefix (default: empty)
    healthExtra: [                // Optional extra fields in /health response
        'server'    => 'my-service',
        'version'   => '1.0.0',
        'timestamp' => now()->toISOString(),
        'endpoints' => ['POST /mcp/call' => 'Tool invocation'],
    ],
);

McpToolDefinition

DTO for a tool definition. The handler is excluded from manifest JSON serialisation.

use Wazobia\NexusMcp\McpToolDefinition;

$tool = new McpToolDefinition(
    name:        'create_post',
    description: 'Create a new blog post',
    inputSchema: [
        'type'       => 'object',
        'properties' => [
            'title'   => ['type' => 'string'],
            'content' => ['type' => 'string'],
        ],
        'required' => ['title', 'content'],
    ],
    handler: function (array $args, array $context): array {
        // $args    — tool arguments from the MCP call
        // $context — ['headers' => [...], 'method' => 'POST', 'path' => '/mcp/call']
        return ['id' => '123', 'title' => $args['title']];
    },
);

Manifest / ManifestContext

DTOs that serialise to the Nexus MCP manifest schema.

use Wazobia\NexusMcp\Manifest;
use Wazobia\NexusMcp\ManifestContext;

$manifest = new Manifest(
    namespace:   'my-service',
    description: 'My service tools',
    version:     '1.0.0',
    context: new ManifestContext(
        boundedContext: 'my-service',
        description:    'Handles blog management',
        capabilities:   ['create posts', 'manage tags'],
        knownGaps:      ['no draft support yet'],
    ),
    tools: $tools, // McpToolDefinition[] — handlers stripped automatically
);

HMAC Contract

All Nexus MCP SDKs use the same signing spec:

payload = METHOD.upper() + path + timestamp
          where path includes query string, no fragment, no host
          timestamp = Unix epoch, whole seconds, decimal string
digest  = HMAC-SHA256(secret_utf8, payload_utf8), lowercase hex
headers = x-signature: {digest}
          x-timestamp: {timestamp}
reject if |now - timestamp| > 300

Cross-language correctness is verified by 16 contract test vectors shared across all four SDKs.

Testing

composer install
./vendor/bin/phpunit
  • tests/Unit/HmacTest.php — 8 unit tests (signature format, method normalisation, staleness window)
  • tests/Contract/VectorTest.php — 16 cross-language contract vectors (vendored, hermetic — no network required)

License

MIT