mnapoli/backoffice-mcp
Laravel package that exposes your backoffice as an MCP (Model Context Protocol) server, letting AI assistants securely query and manage admin resources through a standardized interface. It bridges existing CRUD and dashboard actions with LLM-powered tools without custom API code.
Update the package (composer update) and check the server definition you already have — the $description property is still the first thing to fill in.
Look first at four places, in this order:
$description lives. This is the string handed to the agent before the generic instructions, so it is your highest-leverage piece of context.First use case: pick your busiest domain server (billing, orders, tenancy) and write a short description that names the domain, the vocabulary, and the invariants the code doesn't make obvious. Something like:
protected ?string $description = <<<'TXT'
Billing domain. Invoices are immutable once `status` is `paid`; corrections
are new invoices with `parent_id` set. `amount_cents` is integer cents in
the tenant's currency, never floats. All timestamps are UTC; `paid_at` is
null for anything not settled. `status` is a string enum, not an int.
TXT;
That single change typically removes the most common class of wrong answers: the agent inferring semantics from column names.
Treat $description as an onboarding note, not documentation. The generic instructions already cover "go read the code." Your description should carry what code reading cannot reveal: business meaning, historical baggage, unit conventions, enum semantics, soft-delete expectations, which tables are legacy/deprecated, which columns are denormalized caches, and which connection is a read replica.
Ordering matters. Because the description precedes the generic instructions, it sets the frame the agent reads everything else through. Use it to establish domain vocabulary ("in this app, a 'customer' is an organisation, not a user") so subsequent tool results are interpreted correctly. Keep procedural instructions out of it — those belong to the generic layer.
One server per bounded context. In multi-server setups, give each a description scoped to its own domain rather than repeating an app-wide preamble. App-wide conventions belong in the published instructions stub, which you should publish and keep in version control if the package allows it.
Layer your descriptions. Roughly:
$description → domain framing and invariants;Avoid three copies of the same sentence.
Dynamic descriptions where the environment differs. If a description is resolved from config or a method rather than a literal, use it for environment-specific caveats (read-only replica, tenant isolation, seeded demo data). Keep closures out of config/*.php — they break config:cache.
Register OAuth clients out of band, not in a loop. With the 0.3.1 throttle, anything that calls discovery or registration repeatedly will get a 429: test suites that register a client per test, seeders that re-register on every migrate:fresh, local scripts that re-run php artisan serve workflows. Register once (a seeder guarded by firstOrCreate on the client id, or a committed fixture) and have tests reuse that client. If you genuinely need to exercise the registration flow, isolate it in one test and reset the rate limiter in setUp.
Cache the discovery document. Discovery is throttled alongside registration, so a client that refetches metadata before every call burns the same 30-per-minute budget. Fetch it once at boot and reuse it.
Review description changes like migrations. They change agent behaviour. A diff to $description deserves the same scrutiny as a diff to a query scope.
App\Models\Invoice for the full cast list").php artisan config:clear / view:clear in local development — otherwise you will edit the file and see no change.TrustProxies configured for your infrastructure, every request looks like it came from the proxy's IP, so the limit is global. Check app/Http/Middleware/TrustProxies.php before you tune anything.Retry-After header before debugging credentials or DCR metadata.$description. If it never looked at the code, your description is probably too prescriptive and is suppressing exploration.How can I help you explore Laravel packages today?