wazobia / nexus-mcp-laravel
Laravel HMAC middleware and MCP server helpers for the Nexus MCP ecosystem
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/http: ^10.0|^11.0
- illuminate/routing: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
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.0or^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