Looking to hire Laravel developers? Try LaraJobs

laravel-mcp-guard maintained by stvnrlnd

Description
A governance layer for Laravel MCP servers: scoped permissions, rate limiting, audit logging, and human approval for mutating tool calls.
Author
Last update
2026/07/28 05:03 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

Laravel MCP Guard

A governance layer for Laravel MCP servers.

Laravel MCP lets you expose application capabilities to AI agents as tools. What it does not give you is a way to say which agent may call which tool, how often, or what happened afterwards. Its authorization story is HTTP middleware on the server route — all-or-nothing for the whole server — plus whatever Gate checks you remember to write inside each tool.

MCP Guard adds the missing layer:

  • Scoped permissions — per-agent tool allowlists, wildcards, and deny lists.
  • Rate limiting — per caller, per tool, on Laravel's own rate limiter.
  • Audit logging — every call, allowed or denied, with arguments and timing.
  • Approval workflow — hold mutating tool calls until a human approves them.

It integrates by substituting Laravel MCP's tools/call handler in the container. Your servers, tools, and routes stay exactly as they are.

Status: Phase 1 (scaffold). The interception point is built and tested; the policy layers land in subsequent phases.

Requirements

  • PHP 8.2+
  • Laravel 12 or 13
  • laravel/mcp ^0.9.1

Installation

composer require stvnrlnd/laravel-mcp-guard

The service provider is auto-discovered. Publish the config if you want to edit it:

php artisan vendor:publish --tag=mcp-guard-config

How interception works

Laravel\Mcp\Server resolves its JSON-RPC method handlers out of the container by class name. MCP Guard binds Laravel\Mcp\Server\Methods\CallTool to its own subclass, so every tools/call request passes through the guard before the tool's handle() method is reached — with no changes to user code.

Denials are returned as MCP tool errors (a successful JSON-RPC result carrying isError: true), not as transport-level errors, so the calling agent actually receives and can reason about the reason it was blocked.

use McpGuard\Decision;
use McpGuard\Facades\McpGuard;

McpGuard::before(fn ($call, $tool) => $call->tool === 'delete_customer'
    ? Decision::deny('This agent may not delete customers.')
    : null);

Set MCP_GUARD_ENABLED=false to take the guard out of the path entirely.

Testing

composer test

License

MIT. See LICENSE.