Download the PHP package decocode/laravel-mcp without Composer

On this page you can find all versions of the php package decocode/laravel-mcp. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-mcp

decocode/laravel-mcp

Read-only MCP server for Laravel applications — safe production data diagnostics for Claude (claude.ai + Claude Code). Built on the official laravel/mcp.

Requirements

Infrastructure — three MySQL accounts (per project)

Read-only is enforced at the database level, not just in code.

Account Grants Scope When
mcp_ctl SELECT, INSERT, UPDATE, DELETE only mcp_*, oauth_*, mcp_audit_log now
mcp_ro SELECT business tables minus blocklist now
mcp_rw SELECT, INSERT, UPDATE (no DELETE/DDL) business tables in scope when write is enabled

Generate the exact grant statements with php artisan mcp:grants:print (hand them to a DBA — the command only prints, never executes). Two read-grant modes:

Per-table means new tables aren't readable until granted. After adding a business table, run php artisan mcp:grants:diff — it reads mcp_ro's current grants and prints only the missing GRANT lines (one or two), so you don't re-run the whole script. To move from schema to per-table: REVOKE SELECT ON db.* FROM mcp_ro first, then apply mcp:grants:print (apply in one session — mcp_ro loses read in between).

.env

Install

mcp:install publishes config + migrations, runs the migrations, and prints the manual steps (the mcp auth guard for config/auth.php and the routes/ai.php entries) — these are printed rather than auto-applied, because those files differ per project and version.

Channel A (Claude Code, Bearer) — the primary path

Beyond composer require + mcp:install, six steps get channel A live. mcp:install prints the first two verbatim:

  1. config/auth.php — add the mcp guard (Passport driver) + mcp_service provider (McpServiceAccount). Existing guards stay untouched.
  2. routes/ai.phpMcp::local('diagnostics', DiagnosticsServer::class).
  3. Three MySQL accounts + grantsphp artisan mcp:grants:print emits the exact GRANT statements; a DBA runs them (the package never provisions DB users). This is where read-only is actually enforced (mcp_ro is SELECT-only at the DB level).
  4. php artisan passport:install — keys + personal access client (required for Bearer tokens).
  5. Account → grant → token: mcp:account:create <name>mcp:account:grant <name> readmcp:token:issue <name> (the token is shown once).
  6. Review masking against the project schema — patterns match by column name and are best-effort: a bare name/city or an unlabelled person column (applicant) is not auto-masked. Extend masking.patterns / masking.allowlist per database before exposing data. Re-do this for every new project — a different schema means different PII.
    • Run php artisan mcp:masking:audit — a full-schema scan that lists PII-suspect columns that are not masked, table by table. Reviewing masking against a diff or a hand-built list misses gaps a full scan catches (legacy/foreign column names, a bare ip, old/new audit values); make this part of the deployment's Definition of Done. Add --strict to fail CI on any gap or any table it could not scan. The heuristic is deliberately broad — expect false positives to dismiss, and treat an empty result as "no suspect column slipped the current config", not proof of no PII. It fails (never green) on zero readable tables or an un-introspectable table.
    • For a column that is PII in one table but a harmless label in another (customers.name vs tracks.name), use masking.table_patterns (mask table.column) instead of a global pattern; masking.table_allowlist un-masks a column in one table only. Keep table_allowlist keys specific — a glob key (or a matching view name) lifts the mask across every match.

Channel B (claude.ai, OAuth 2.1 + PKCE)

PREREQUISITE — Passport must be dedicated to MCP in this app. Channel B configures Passport globally, so enabling it in an app that also uses Passport for its own OAuth will disrupt that OAuth. Concretely, with http.enabled + manage_passport (default on) the package sets: (1) the consent view — bound as a callback so only MCP connectors (redirect on the allowlist) get the MCP screen and other clients keep the default passport::authorize; (2) global token lifetimes (1d / 30d / 90d) for all Passport tokens; and you must set (3) passport.guard = mcp_web globally, which changes the OAuth resource owner for the whole app. If Passport is shared, either separate the concerns first or set mcp.oauth.manage_passport=false and wire Passport yourself.

php artisan mcp:install --with-oauth scaffolds channel B: it publishes the consent view and prints the wiring. The package ships the whole operator-authorization layer — an operator middleware, the consent screen, the redirect allowlist and the Passport bootstrap — so you only wire it up and fill one project-specific hook.

  1. config/auth.php — add a mcp_web session guard (over mcp_service) so the minted token's sub is the service account, not the human operator.
  2. config/passport.php'guard' => 'mcp_web' only if Passport is used solely for MCP in this app (it's a global setting; an app using Passport for other things must keep them separate).
  3. bootstrap/app.php — append the operator gate to the web group: $middleware->web(append: [\Decocode\LaravelMcp\Http\Middleware\EnsureMcpOperator::class]).
  4. Answer "who may authorize a connector" — the one project-specific edit:
    • Gate ability (simple): Gate::define('mcp-operator', fn ($u) => $u->hasRole('Super Admin')), then mcp.oauth.operator_gate = 'mcp-operator' (+ operator_guard, operator_login_route).
    • McpOperatorCheck class (advanced): set mcp.oauth.operator_check to a class implementing authorize(Request): bool + serviceAccountId(): ?int — wins over the gate; use it for logic a Gate can't express or a dynamically chosen service account.
  5. MCP_HTTP_ENABLED=true + a public HTTPS endpoint (a dedicated domain or a tunnel), and point mcp.oauth.account at the provisioned service account.

The consent screen is always shown — never auto-approved. Client registration is public + dynamic, so a silent approve would let one phished operator click authorize an attacker's client; the redirect allowlist pins the delivery channel (claude.ai), the consent click pins the recipient. The package applies this Passport bootstrap automatically when channel B is on (opt out with mcp.oauth.manage_passport=false). Make your own RODO/GDPR decisions on what to mask.

Channel A is repeatable and mostly config. Channel B adds the wiring above — five steps, one of them the operator hook.

Security model (summary)

Tools (read-only)

Each tool self-filters by the caller's capabilities — a read-only identity only ever sees the read tools, and execution is re-checked server-side. Every call is recorded to a fail-closed audit trail (if the call cannot be logged, no data is returned).

Tool Capability What it does
read_query read Ad-hoc SELECT against mcp_ro. SELECT-only, single statement, forced LIMIT, blocked tables refused, sensitive columns + nested JSON masked. To keep masking from being evaded, the projection allows only * / t.* / bare columns / numeric literals — function calls, expressions, aliasing, UNION/INTERSECT/EXCEPT, CTEs, FROM-subqueries, JOINs/comma-joins and JSON extraction are rejected (each can surface a column past name-based or table-qualified masking). A masked column may appear in WHERE only if the project marks it masking.filterable, and never in ORDER BY / GROUP BY / HAVING. EXPLAIN <SELECT> is held to the same rules as the SELECT it wraps; after an EXPLAIN/DESCRIBE only a SELECT or a plain DESCRIBE <table> is accepted (ANALYZE, FORMAT=JSON/TREE, TABLE … and FOR CONNECTION are refused). A double quote outside a string literal is refused too — its meaning depends on sql_mode, so use ' for values and back-ticks for identifiers — as are control bytes (C0 except tab/newline/CR, plus DEL), which can steer how MySQL parses a comment, a backslash before a quote inside a literal (write '' instead of \'), and a back-quoted identifier containing anything outside [A-Za-z0-9_$]. Single-table only — aggregates live in count_rows.
count_rows read Row count for a table, optional WHERE. The projection is fixed to COUNT(*) (never a column value), the table must be a plain non-blocked identifier, and the assembled query is guarded. Masked columns may not be referenced — except those the project marks masking.filterable, which accept an equality test against a value the caller already holds (never a range, LIKE or function). Fills the aggregation gap read_query leaves.
schema_describe read Lists readable tables, or a table's columns. Blocked tables are hidden; each column notes whether its values are masked and whether it is filterable — freely for an un-masked column, equality-only, and only in a WHERE, for a masked one. Returns no row data.
order_inspect read Example domain tool — fetches one record + related rows by id. Config-driven; only registers once mcp.tools.order_inspect is set, so the package ships no project-specific schema.

The server exposing these tools is registered in the app (not auto-edited by mcp:install). Add to routes/ai.php:

Reading the audit trail

Every call lands in mcp_audit_log on the control-plane connection — the ones that ran, the ones the database rejected, and (since 0.3.6) the ones a guard refused before they reached the database. mcp:audit:report is how you read it. This is not a convenience: the trail is the compensating control the masking.filterable relaxation is argued on. The cap on how many values one call may test is defended by "a sweep would show up as a burst in the audit" — which is only true if somebody looks, and only useful if the refused attempts are in there too. They are the ones that matter most: where a column is not marked filterable, every probe against it is refused, so before 0.3.6 the most suspicious traffic was the only traffic that left no trace.

The report ends with per-column counts for masked-column predicates, split by account and by outcome — executed (the project marked the column masking.filterable, so the call was answered) versus refused (it did not, and a guard stopped the call). The shape to look for is one column, one account, many calls in a short window: that is a value sweep, not a diagnosis. Refusals in that shape mean someone kept hitting a wall; executions in that shape mean they were being answered. --outcome takes ok, rejected (a guard refused the call), denied (the identity lacks the capability), invalid (the arguments never got as far as a guard), failed (the database refused) or unknown (the row cannot be read as expected — never hidden by a filter, whatever you pass). denied is the one to reach for after a grant is revoked: it is how an identity sweeping tools it may not use looks.

Counts cover the whole window, not the printed page: --limit caps the rows shown, never what was counted, so a sweep that falls off page one still appears in the per-column totals.

It reads only, and it is an operator-side command: the artisan-over-MCP channel is not implemented, so no command can be invoked over MCP at all. (config('mcp.commands.denylist') names mcp:* for the day it is — that entry is a stated intention, not yet an enforced control.) Its output carries production data: a statement that ran is stored with the literals compared against masked columns cut out, and everything else is verbatim SQL; one a guard refused is stored as a skeleton (see below). The table argument is an exception on both paths — it is recorded close to what the caller wrote (bounded, control bytes removed, runs of three or more digits blanked, so orders_2024 reads as orders_#), because a trail that cannot say which table was asked about answers nothing.

Straight SQL, if you would rather not go through artisan:

Two things to know when reading rows by hand:

The trail is append-only and the package never prunes it. Retention is the deployment's call (it holds production SQL); created_at is indexed, so a date-bounded DELETE is cheap.

Authorization & exposure

Tools resolve the calling identity through a dedicated mcp auth guard (Passport driver over McpServiceAccount) — the application's own guards (api, sanctum, …) are untouched. Capability gating denies by default, so an identity only sees and runs the tools its grants allow.

End-to-end OAuth against a live claude.ai connector and Claude Code is verified against a running application with Passport installed, so it is out of scope for this package's own test suite.


All versions of laravel-mcp with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
laravel/mcp Version ^0.8
laravel/passport Version ^12.0|^13.0
illuminate/console Version ^11.45|^12.0|^13.0
illuminate/contracts Version ^11.45|^12.0|^13.0
illuminate/database Version ^11.45|^12.0|^13.0
illuminate/http Version ^11.45|^12.0|^13.0
illuminate/support Version ^11.45|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package decocode/laravel-mcp contains the following files

Loading the files please wait ...