PHPackages                             shaxzodbek-uzb/laravel-model-mcp - 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. [Database &amp; ORM](/categories/database)
4. /
5. shaxzodbek-uzb/laravel-model-mcp

ActiveLibrary[Database &amp; ORM](/categories/database)

shaxzodbek-uzb/laravel-model-mcp
================================

Auto-expose Eloquent models as policy-enforced MCP tools. Safe-by-default CRUD over the Model Context Protocol, gated by your Laravel Policies, scoped to your tenant, and audited.

v0.1.0(today)00MITPHPPHP ^8.2CI passing

Since Jun 20Pushed todayCompare

[ Source](https://github.com/shaxzodbek-uzb/laravel-model-mcp)[ Packagist](https://packagist.org/packages/shaxzodbek-uzb/laravel-model-mcp)[ Docs](https://github.com/shaxzodbek-uzb/laravel-model-mcp)[ RSS](/packages/shaxzodbek-uzb-laravel-model-mcp/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (12)Versions (2)Used By (0)

Laravel Model MCP
=================

[](#laravel-model-mcp)

 [![Latest Version on Packagist](https://camo.githubusercontent.com/e29a53fbf8d8002d2f502004f540adb4bdb58b84e6dce3b12a0e203c5f5741fd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736861787a6f6462656b2d757a622f6c61726176656c2d6d6f64656c2d6d63702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/shaxzodbek-uzb/laravel-model-mcp) [![Tests](https://camo.githubusercontent.com/ee26763957a74df3dda0472cb1cb476cbb5c499b21ae555e08e8880323cb3f6b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736861787a6f6462656b2d757a622f6c61726176656c2d6d6f64656c2d6d63702f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/shaxzodbek-uzb/laravel-model-mcp/actions/workflows/tests.yml) [![Quality](https://camo.githubusercontent.com/289f7fae94ee670657719930e54c57882c8113e8d67651e7312949277a08dbc3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736861787a6f6462656b2d757a622f6c61726176656c2d6d6f64656c2d6d63702f7175616c6974792e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307175616c697479267374796c653d666c61742d737175617265)](https://github.com/shaxzodbek-uzb/laravel-model-mcp/actions/workflows/quality.yml) [![PHP Version](https://camo.githubusercontent.com/5c9028d8f28f141a748a3f6f7c75c73a9c1057364352951f598bbe212eb406e0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f736861787a6f6462656b2d757a622f6c61726176656c2d6d6f64656c2d6d63703f7374796c653d666c61742d737175617265)](https://packagist.org/packages/shaxzodbek-uzb/laravel-model-mcp) [![License](https://camo.githubusercontent.com/70957fce9a974f63ca71c46c62db5b7a31c49d8c94408962fc61370f0cd9c357/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f736861787a6f6462656b2d757a622f6c61726176656c2d6d6f64656c2d6d63702e7376673f7374796c653d666c61742d737175617265)](LICENSE)

**Expose your Eloquent models to AI agents as MCP tools — without handing them the keys to your database.**

`laravel-model-mcp` turns any Eloquent model into a full set of [Model Context Protocol](https://modelcontextprotocol.io) tools (`list`, `view`, `create`, `update`, `delete`, `search`) on top of the official [`laravel/mcp`](https://github.com/laravel/mcp) package — and every single call is checked against your **Laravel Policies**, scoped to the **current tenant**, and **audited**. Safe by default, no boilerplate.

---

The problem
-----------

[](#the-problem)

The official `laravel/mcp` package is excellent, but it makes you hand-write one tool class per operation, and authorization is a manual `if` inside each handler:

```
class UpdatePostTool extends Tool
{
    public function handle(Request $request): Response
    {
        $post = Post::findOrFail($request->get('id'));

        // You have to remember this, on every tool, for every model.
        if (! $request->user()->can('update', $post)) {
            return Response::error('Forbidden.');
        }

        $post->update($request->validate([...]));

        return Response::json($post);
    }

    public function schema(JsonSchema $schema): array
    {
        // ...and hand-maintain a schema that mirrors your migration.
    }
}
```

Multiply that by 6 operations × every model you want to expose. Forget the `can()` check on one of them, and an agent can now edit anything.

The solution
------------

[](#the-solution)

List the models. Register one server. Done — policy-enforced CRUD for all of them:

```
// config/model-mcp.php
'models' => [
    App\Models\Post::class,
    App\Models\Comment::class,
],
```

```
// routes/ai.php
use Blaze\ModelMcp\Server\ModelMcpServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/models', ModelMcpServer::class)->middleware(['auth:sanctum']);
```

That's it. You now have `list_posts`, `get_post`, `create_post`, `update_post`, `delete_post`, `search_posts` (and the same for `comment`) — each one running `PostPolicy@viewAny`, `@view`, `@create`, `@update`, `@delete` for the authenticated user before it touches a row.

Why this package
----------------

[](#why-this-package)

`laravel/mcp` alone`laravel-model-mcp`Eloquent → MCP CRUD toolshand-written**auto-generated**Laravel Policy enforced per callmanual `can()` in each handler**built in, fail-closed**Multi-tenant row scopingDIY**built in**Audit log of every tool callDIY**built in**Token-safe pagination &amp; field limitsDIY**built in**JSON Schema from casts/columnshand-written**generated**This package **builds on** `laravel/mcp` — it does not replace it. Transport, OAuth, and the protocol stay with the official package; this layer adds the opinionated, safe-by-default model exposure on top.

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

[](#installation)

```
composer require shaxzodbek-uzb/laravel-model-mcp
```

Requires PHP 8.2+ and Laravel 12.41+ / 13.x (it relies on `laravel/mcp`'s JSON Schema builder). If you haven't set up `laravel/mcp` yet:

```
php artisan vendor:publish --tag=ai-routes   # creates routes/ai.php
```

Optionally publish the config:

```
php artisan vendor:publish --tag=model-mcp-config
```

Quickstart
----------

[](#quickstart)

**1. Add a policy** for the model you want to expose (standard Laravel — nothing special):

```
class PostPolicy
{
    public function viewAny(User $user): bool { return true; }
    public function view(User $user, Post $post): bool { return true; }
    public function create(User $user): bool { return $user->can_write; }
    public function update(User $user, Post $post): bool { return $user->id === $post->user_id; }
    public function delete(User $user, Post $post): bool { return $user->id === $post->user_id; }
}
```

**2. Expose the model:**

```
// config/model-mcp.php
'models' => [
    App\Models\Post::class,
],
```

**3. Register the server** in `routes/ai.php` behind your auth middleware:

```
Mcp::web('/mcp/models', \Blaze\ModelMcp\Server\ModelMcpServer::class)
    ->middleware(['auth:sanctum']);
```

**4. See exactly what you exposed:**

```
php artisan model-mcp:list

#  Tool          Model               Operation
#  list_posts    App\Models\Post     list
#  get_post      App\Models\Post     view
#  create_post   App\Models\Post     create
#  update_post   App\Models\Post     update
#  delete_post   App\Models\Post     delete
#  search_posts  App\Models\Post     search
```

Point any MCP client (Claude, your agent, `php artisan mcp:inspector`) at the server and the tools are live — each one acting as the authenticated user.

The security model
------------------

[](#the-security-model)

This is the whole point, so it's worth being explicit. By default:

1. **Nothing is exposed implicitly.** Only models in `model-mcp.models` (or tagged with `#[McpModel]`) become tools.
2. **Every operation enforces the matching policy ability** for the authenticated MCP user, *before* any read or write:

    OperationPolicy ability`list`, `search``viewAny``view``view``create``create``update``update``delete``delete`
3. **Fail-closed.** No policy for the model → every operation is denied. No authenticated user → denied. (Both configurable, both default to safe.)
4. **Tenant scope is applied to the query *before* the policy runs**, so even a missing or over-permissive policy can't leak another tenant's rows. If tenancy is enabled and no tenant resolves, the request fails closed.
5. **Writes are limited to `$fillable`**; reads honor `$hidden`; and `fields.always_hidden` is a hard block on top (e.g. `password`).

Denials and errors come back as MCP `isError` results with safe messages — the agent can recover, and your internals never leak. Every call (allowed, denied, errored) is recorded by the [audit log](#audit-log).

> See [SECURITY.md](SECURITY.md) for the full model and how to report issues.

Configuration
-------------

[](#configuration)

The published `config/model-mcp.php` is fully documented. The essentials:

```
return [
    // Explicit allow-list. Bare class, or Class => [overrides].
    'models' => [
        App\Models\Post::class,
        App\Models\Invoice::class => [
            'operations'    => ['list', 'view'],          // read-only
            'tenant_column' => 'team_id',
            'policy'        => App\Policies\InvoicePolicy::class,
            'name'          => 'invoice',                 // tool name stem
        ],
    ],

    // Operations exposed by default for an opted-in model.
    'operations' => ['list', 'view', 'create', 'update', 'delete', 'search'],

    'authorization' => [
        'enabled'                => true,
        'deny_without_policy'    => true,   // fail closed
        'require_authentication' => true,
    ],

    'tenancy' => [
        'enabled'  => false,                // rely on your global scopes by default
        'column'   => 'tenant_id',
        'resolver' => Blaze\ModelMcp\Tenancy\AuthUserTenantResolver::class,
        'fail_closed' => true,
    ],

    'pagination' => ['default_per_page' => 25, 'max_per_page' => 100],

    'audit' => ['enabled' => true, 'logger' => Blaze\ModelMcp\Audit\LogAuditor::class],
];
```

### Read-only or partial exposure

[](#read-only-or-partial-exposure)

Expose only the operations you want, per model:

```
'models' => [
    App\Models\AuditEntry::class => ['operations' => ['list', 'view', 'search']],
],
```

Or flip a single global kill-switch so **no** model can ever be mutated — only `list` / `view` / `search` tools are generated, regardless of per-model settings:

```
'read_only' => true,
```

Multi-tenancy
-------------

[](#multi-tenancy)

If your app already scopes models with **global scopes** (a `BelongsToTenant`trait, `#[ScopedBy]`, `stancl/tenancy`, `spatie/laravel-multitenancy`), you need to do **nothing** — every query runs through `Model::query()`, so your scopes apply transparently and are never stripped.

Turn on the package's own explicit scoping only when a global scope alone won't filter (e.g. you want a hard `where(tenant_column, id)` regardless):

```
'tenancy' => [
    'enabled' => true,
    'column'  => 'tenant_id',   // default; override per model via 'tenant_column'
],
```

The default `AuthUserTenantResolver` reads the column off the authenticated user (`$user->tenant_id`). Provide your own by binding `Blaze\ModelMcp\Contracts\TenantResolver`.

Audit log
---------

[](#audit-log)

Every tool call is recorded with the acting user, the model, the operation, and the outcome (`allowed` / `denied` / `error`). The default `LogAuditor` writes to your log channel; swap in your own to persist to a table:

```
use Blaze\ModelMcp\Contracts\ToolAuditor;
use Blaze\ModelMcp\Audit\ToolCallEvent;

class DatabaseAuditor implements ToolAuditor
{
    public function record(ToolCallEvent $event): void
    {
        McpAuditLog::create($event->toArray());
    }
}
```

```
// config/model-mcp.php
'audit' => ['enabled' => true, 'logger' => App\Mcp\DatabaseAuditor::class],
```

Attribute discovery (optional)
------------------------------

[](#attribute-discovery-optional)

Prefer to opt in from the model itself? Enable discovery and tag your models — it's off by default so nothing is ever exposed by accident:

```
use Blaze\ModelMcp\Attributes\McpModel;

#[McpModel(operations: ['list', 'view'], tenantColumn: 'team_id')]
class Post extends Model { /* ... */ }
```

```
'discovery' => ['enabled' => true, 'paths' => [app_path('Models')]],
```

Extending
---------

[](#extending)

- **Custom tools alongside generated ones** — subclass `ModelMcpServer` and add your hand-written tools to the `$tools` array; the generated ones are merged in.
- **Custom policies** — point a model's `policy` option at any class, or rely on Laravel's normal policy resolution.
- **Custom auditor / tenant resolver** — implement the contract and bind it.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 12.41+ or 13.x
- [`laravel/mcp`](https://github.com/laravel/mcp) ^0.8

Testing
-------

[](#testing)

```
composer test       # Pest
composer analyse    # PHPStan / Larastan (level 6)
composer lint       # Pint
```

Credits
-------

[](#credits)

Built by [Blaze](https://blaze.uz). Stands on the shoulders of the [`laravel/mcp`](https://github.com/laravel/mcp) team.

License
-------

[](#license)

The MIT License (MIT). See [LICENSE](LICENSE).

###  Health Score

36

—

LowBetter than 80% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 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

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/52601832881370c15b8c88de19f249fa511d46114d02d0a9d1fe2e51e2cb2f16?d=identicon)[Shaxzodbek](/maintainers/Shaxzodbek)

---

Top Contributors

[![shaxzodbek-uzb](https://avatars.githubusercontent.com/u/47610909?v=4)](https://github.com/shaxzodbek-uzb "shaxzodbek-uzb (3 commits)")

---

Tags

ai-agentsauthorizationeloquentlaravellaravel-packagemcpmodel-context-protocolmulti-tenantlaravelmcpaieloquentauthorizationmulti-tenantagentsModel Context ProtocolPolicy

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/shaxzodbek-uzb-laravel-model-mcp/health.svg)

```
[![Health](https://phpackages.com/badges/shaxzodbek-uzb-laravel-model-mcp/health.svg)](https://phpackages.com/packages/shaxzodbek-uzb-laravel-model-mcp)
```

###  Alternatives

[spatie/laravel-health

Monitor the health of a Laravel application

87411.3M152](/packages/spatie-laravel-health)[laravel/ai

The official AI SDK for Laravel.

9782.1M160](/packages/laravel-ai)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.5k](/packages/larastan-larastan)[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

436834.4k1](/packages/clickbar-laravel-magellan)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

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