Filament MCP

The admin panel your team can talk to. Easily implement a MCP server for your Filament panel based on your existing Resources.

Latest version
v1.0.0
Downloads
12k+
Rating
4.9/5

$56 /lifetime

Purchase package
Filament MCP preview

MCP for Filament

The admin panel your team can talk to. Easily implement a MCP server for your Filament panel based on your existing Resources.

Every Filament resource already describes your application in detail: its fields, its relations, its validation, who is allowed to touch what. This package hands that description to Claude, Cursor or any other MCP client, so an agent can work in your panel the way a colleague would — with the same permissions, and the same audit trail.

McpPlugin::make()

That is the whole integration. What follows is quieter than a demo suggests, and more useful:

  • Support checks an order and its refund status mid-conversation, without opening a tab.
  • Finance gets churn by plan for last quarter while asking for it, rather than waiting on a report.
  • The chore that was forty clicks — every unpaid invoice past sixty days moved to collections — becomes a sentence, previewed before it runs.
  • And there is nothing new to maintain: no REST layer, no tool definitions quietly drifting away from the forms they were copied from.
You      Which customers churned last month, and what did it cost us in MRR?

Claude   → list_records      {"resource": "customer",
                              "filters": {"cancelled_at": {"between": ["2026-07-01", "2026-07-31"]}}}
         → aggregate_records {"resource": "subscription", "metric": "sum", "column": "amount",
                              "filters": {"status": "cancelled"}}

         14 customers, €4,210 MRR. Two were on the enterprise plan —
         both cancelled within a week of the price change.

The agent only ever sees what that user would see in the panel — same policies, same tenant scopes, same validation.

Why not just…

Instead you could What it costs you
Hand-write MCP tools with laravel/mcp A week per handful of resources, and a second description of your app to keep in step with the first.
Point the agent at the database No policies, no tenancy, no validation — and no record of what happened, or on whose behalf.
Build a REST API and wrap it in tools Two layers to secure and version, for data your panel already exposes correctly.
Wait until it feels safe Reasonable. So start with readOnly() — one method, no write tool is even advertised.

This package takes the fourth line seriously: your existing resources, unchanged, with every gate closed until you open it.

Install

Requires PHP 8.3+, Laravel 12 or 13, and Filament v5. Backstage itself is not required. Distributed through the Backstage Composer repository, so add that once:

composer config repositories.backstage composer https://satis.backstagephp.com

Then:

composer require backstage/mcp
php artisan backstage:mcp:install
php artisan migrate

The install command wires up Laravel Passport for you: signing keys, the api guard driver, the user model traits.

Register the plugin:

use Backstage\Mcp\McpPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->id('admin')
        ->plugin(McpPlugin::make()->readOnly());
}

Your server is live at /backstage/mcp/admin. Drop readOnly() when you want writes. Rebuild the cache after changing resources with php artisan backstage:mcp:prepare.

Connect an agent

Paste the panel URL into Claude, Cursor, VS Code or any MCP client. There is nothing to copy afterwards: OAuth 2.1 with dynamic client registration means the client registers itself and runs an authorization-code + PKCE flow through a consent screen served inside your own panel, with your branding and your login.

Each panel advertises only its own scopes, so a token for admin can never reach app:

Panel Scopes
admin mcp:admin, mcp:admin:read
app mcp:app, mcp:app:read

The MCP setup page in the user menu shows the endpoint, the scopes and every authorized client, with one click to revoke.

What the agent gets

A fixed set of about twenty tools, parameterised by a resource key — not one tool per resource per operation. Fifty resources stay twenty tools instead of becoming 250, so the model's tool list stays readable and its choices stay accurate. Prefer the per-resource shape? ->consolidatedTools(false) gives you list_posts, create_post and friends.

Records. list_records, get_record, create_record, update_record, opt-in delete_record. Listing supports search, sorting, pagination and rich filters: a scalar for equality, a list for IN, or an operator map like {"gte": 100} (eq, ne, gt, gte, lt, lte, like, between, in, null, not_null).

Reporting. aggregate_records for counts and sum/avg/min/max with grouping, export_records for CSV or JSON, panel-wide global_search, and widget_stats / widget_charts / widget_tables to read your dashboard as data. All tenant-scoped.

Relations. list_related, get_related, create_related, update_related, delete_related across every relationship.

Actions. Every custom resource action you register is callable through run_action, including its modal fields.

Files. request_upload mints a single-use token; bytes go over HTTP, never through the conversation as base64. Magic bytes are verified against the field's own accepted types, and the stored filename is generated, never client-supplied.

Soft deletes. Models using SoftDeletes gain a trashed filter, plus restore and force flags on delete_record — dedicated restore_* and force_delete_* tools in the per-resource shape.

Dynamic fields. With backstage/fields, agents inspect and build per-record field structures through dedicated tools.

Orientation. list_tools gives a typed overview of everything available; describe_resources maps fields, types, enums and relations; built-in prompts (explore_data, build_report) teach an assistant the order to use them in.

Dry runs. Every write accepts preview: true to validate and return the intended change, diff included, without touching the database.

Stay in control

Everything is fluent on the plugin, and everything dangerous is off until you say otherwise:

McpPlugin::make()
    ->only([PostResource::class])
    ->except([SecretResource::class])
    ->readOnly()
    ->allowDelete([PostResource::class])
    ->bulk()
    ->requireApproval()
    ->export(false)
    ->globalSearch(false)
    ->widgets(false)
    ->actions([PostResource::class => ['publish' => PublishPost::class]])
    ->prepare([PostResource::class => PreparePostData::class])
    ->prompts([BuildWeeklyReport::class])
    ->reports([SalesByRegionReport::class]);
  • readOnly() makes the panel advertise and accept only its read scope. Every write tool refuses, server-side — not by omission from a list the model could guess around.
  • requireApproval() parks writes as pending changes. A human approves or rejects on the MCP pending changes page.
  • bulk() and allowDelete() are off until turned on, and take an allowlist.
  • Saved reports let you ship the trusted query yourself; the agent only fills declared, validated parameters.

See exactly what an agent will see, before connecting one:

php artisan backstage:mcp:inspect --panel=admin

Why you can trust it

  • Authorization is Filament's, not ours. Policies, tenant scopes, action ->authorize() and form validation rules are evaluated per call, per record.
  • Every tool call is logged, with configurable retention.
  • Every field is accounted for. Each Filament component is classified as supported or not, and the suite fails when that changes — so a Filament upgrade cannot quietly turn a field an agent writes to into one it drops.
  • The SQL tool is defended in depth (see below) and disabled automatically on panels with tenancy.

And what it deliberately does not do: ViewField, LivewireField and ViewEntry mount arbitrary Blade or Livewire, so they have no attribute semantics to read or write and are dropped; actions whose ->action() is a string Livewire method name are skipped rather than run against unfilled state; an allowlisted SVG upload is stored unsanitized and can carry active content.

Read-only SQL reporting

For joins and cross-resource analysis the per-resource tools cannot express, opt in per panel:

McpPlugin::make()->allowQuery(connection: 'reporting');

A single SELECT or WITH statement only, tables limited to exposed models, bindings instead of concatenation, a rolled-back read-only transaction, row caps, statement timeouts, and redaction of hidden and sensitive columns.

Redaction works on result-column names and SET TRANSACTION READ ONLY is a no-op on MySQL, so for a hard guarantee point connection: at a database user holding only SELECT grants. The application guard is then a second net rather than the only one.

Testing

use Backstage\Mcp\Testing\InteractsWithMcp;

uses(InteractsWithMcp::class);

$this->actingOnMcpPanel('admin', $user);
$result = $this->callMcpTool('admin', 'aggregate_records', [
    'resource' => 'order',
    'metric' => 'count',
]);

Configuration

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

Lands at config/backstage/mcp.php, read as config('backstage.mcp.*'). Row caps, query limits, OAuth client constraints and audit retention live there.

Good to know

  • OAuth endpoints live outside the backstage/mcp/* prefix. If you run a bot blocker, domain allowlist or firewall, exempt .well-known/oauth-* and oauth/* as well. MCP clients run discovery from their own backends.
  • Do not call Mcp::oauthRoutes() from routes/ai.php. This package publishes its own discovery documents; Laravel MCP's helper advertises one application-wide scope and would erase the per-panel boundary.
  • Passport's consent screen runs on the default auth guard. If a panel uses its own guard, users must also be signed in on the default guard to approve a client.

FAQ

Can an agent wreck my data?

Only what you allowed. Deletes and bulk writes are opt-in per resource, writes can be parked for human approval, and every write accepts preview: true first. Start readOnly().

Does it bypass my policies?

No. Reads and writes run through the same policies, tenant scopes and validation rules as the panel. A user who cannot see a record through Filament cannot see it through MCP.

Multi-tenant?

Every tool is tenant-scoped, tokens are per-panel, and the raw SQL tool disables itself on tenant panels rather than risking a scope-free query.

Does introspection slow down requests?

No. It runs once at build time and is cached; a request only reads arrays. Re-run backstage:mcp:prepare after changing resources.

Will fifty resources drown the model in tools?

No — the default toolset is fixed at roughly twenty regardless of resource count.

Do I need Backstage to use this?

No. Any Filament v5 panel, in any Laravel 12 or 13 app. Backstage only distributes the package.

Can I expose one panel and not

egister the plugin only where you want it. Scopes are per panel, so a token for one can never reach the other.

Get this package

What's included:

  • Complete source code
  • Detailed documentation
  • Installation & support
  • Future updates included

$56 /lifetime

We're here to help!

Have questions about this package? Our team is ready to assist you.