Version v1.6.5 Older versions: docs on GitHub
AI assistants: skills and the MCP server
On this page
- What ships
- Installing with Laravel Boost
- Installing without Boost: larsadmin:ai
- Other MCP clients
- The MCP server
- When the server runs
- Tools
- search-docs
- read-docs
- application-info
- list-entities and describe-entity
- list-dashboards and list-extensions
- generate
- Security model
- Keeping the skills up to date
- Troubleshooting
Since 1.6 the package ships material for AI assistants that write your application's code (Claude Code,
Cursor, VS Code and others): a guideline with the package's rules, 15 skills with recipes for each
area, and a local MCP server larsadmin through which the assistant searches this guide, sees the
entities registered in the application and generates classes with the same generators as
php artisan larsadmin:*.
All of it is optional. The panel has no new required dependencies and works exactly as in 1.5 without the AI tooling. The AI gets no access to record data: the server returns only code metadata and guide text.
What ships
| What | Where in the package | What for |
|---|---|---|
| Guideline | resources/boost/guidelines/core.md |
in short: what Larsadmin is, the mandatory rules, a "task → skill" table, how to use MCP. It is loaded into every session, so it is short (at most 80 lines) |
| Skills | resources/boost/skills/larsadmin-*/SKILL.md (+ references/*.md) |
task instructions: when to use, recipes, an API cheat sheet, security rules, pitfalls, links to the full guide. The assistant loads a skill only when it takes on the matching task |
| MCP server | php artisan larsadmin:mcp |
8 tools: searching and reading the guide, application introspection, class generation (Tools) |
The larsadmin:ai command |
installs the skills, the MCP server entry and the guideline without Laravel Boost (Without Boost) |
The skills and the guideline are in English. They link to the English guides of the installed package
version (vendor/maxcxam/larsadmin/docs/guide/en/NN-name.md), so the assistant reads the documentation of
exactly your version.
| Skill | Area | Guide |
|---|---|---|
larsadmin-setup |
installation, configuration, upgrades, AI tooling | 01, 20 |
larsadmin-entities |
entities, queries, the generator | 02 |
larsadmin-fields-layout |
fields, validation, columns, panels and tabs | 03, 12 |
larsadmin-relations |
BelongsTo, HasMany, BelongsToMany |
04 |
larsadmin-authorization-security |
gate, policies, scoping, the security checklist | 05 |
larsadmin-filters |
built-in and custom filters | 06 |
larsadmin-actions |
actions, action fields, background actions | 07, 18 |
larsadmin-metrics-dashboards |
metrics, cards, dashboards | 13, 14 |
larsadmin-files |
File, Image, disks, FileSafety |
15 |
larsadmin-global-search |
global search, Scout | 16 |
larsadmin-audit-log |
the audit log | 17 |
larsadmin-notifications-realtime |
the bell, notifications, Reverb/Pusher | 18 |
larsadmin-ui-i18n-theming |
languages, translations, themes, the list UI | 08, 09, 11 |
larsadmin-extending |
extensions, custom fields, cards, tools, the JS SDK | 10, 19 |
larsadmin-testing |
tests of the panel and of extensions | 21 |
Installing with Laravel Boost
Laravel Boost finds the guidelines and skills of the packages your application depends on directly:
composer require laravel/boost --dev
php artisan boost:install
In boost:install, select maxcxam/larsadmin among the third-party packages. The guideline goes into your
assistant's guidelines file (CLAUDE.md, AGENTS.md and so on), the skills are copied into the assistant's
skills directory (for Claude Code, .claude/skills/larsadmin-*).
- Skills need Boost 2.x (tested with 2.4 and 2.10). Boost 1.x installs the guideline only.
- Boost sees direct dependencies only:
maxcxam/larsadminmust be in therequiresection of yourcomposer.json, not only a dependency of another package (an add-on, say). - After
composer update, refresh the files withphp artisan boost:update. - Boost already puts the guideline into
CLAUDE.md: do not add the import line (larsadmin:ai --claude-md).
Without interactive questions (--no-interaction, in a script or CI, say) boost:install takes the agents
and packages from boost.json in the application root:
{"agents":["claude_code"],"packages":["maxcxam/larsadmin"]}
php artisan boost:install --no-interaction
Exclude the inertia-laravel/core guideline. Inertia comes into the application as a dependency of the
panel, so Boost adds its Inertia guideline as well. It can push the assistant to write Inertia pages in your
application, although the panel's pages already ship in the package. config/boost.php does not exist until
you publish it:
php artisan vendor:publish --tag=boost-config
// config/boost.php
'guidelines' => [
'exclude' => ['inertia-laravel/core'],
],
Then run php artisan boost:update. skills.exclude turns off single skills the same way. The
boost:install summary shows the guideline names to use in exclude.
Boost adds only its own server to .mcp.json. Add the larsadmin server separately:
composer require laravel/mcp --dev
php artisan larsadmin:ai --mcp
Installing without Boost: larsadmin:ai
Pick one way to install and update the skills: Boost (boost:install, boost:update) or
larsadmin:ai --skills. With Boost you need larsadmin:ai only for --mcp.
php artisan larsadmin:ai [--skills] [--mcp] [--claude-md] [--path=.claude/skills] [--force]
Without options the command asks what to install (skills and MCP are preselected). With --no-interaction
and no options it runs --skills --mcp. The command needs neither Boost nor laravel/mcp.
| Option | What it does |
|---|---|
--skills |
copies the package's larsadmin-* skills to {path}/larsadmin-* (.claude/skills by default). Each directory gets a .larsadmin-skill marker (the package version and a hash of the contents) |
--mcp |
adds the larsadmin server to .mcp.json in the application root; other servers and keys of the file are kept. Prints the same snippet for other clients. Without laravel/mcp it warns that the server will not start |
--claude-md |
appends the guideline import line to the end of CLAUDE.md, only when it is not there yet; your text is not changed. Without the option the command only prints a tip with that line |
--path= |
the skills directory, relative to the application root |
--force |
replaces skill directories changed by hand or not created by this command, and an existing larsadmin entry in .mcp.json |
The .mcp.json entry (Claude Code format):
{
"mcpServers": {
"larsadmin": {
"command": "php",
"args": ["artisan", "larsadmin:mcp"]
}
}
}
The line --claude-md adds to CLAUDE.md:
@vendor/maxcxam/larsadmin/resources/boost/guidelines/core.md
The command writes only inside the application root: a --path that leads outside it or through a symlink
(or a junction on Windows) is refused before any file is written. When .mcp.json is not a JSON object, has a
duplicate key or a number PHP cannot keep exactly (an integer beyond the native range, an over-long decimal),
the file is left unchanged: the command fails and prints the entry to add by hand.
Other MCP clients
The server speaks stdio, so any MCP client (Cursor, VS Code, Windsurf and others) starts it with the same
command: php artisan larsadmin:mcp in the application root. Copy the snippet larsadmin:ai --mcp prints
into your client's configuration. The skills use the Agent Skills format (SKILL.md) and can go to your
client's skills directory through --path.
The MCP server
The larsadmin server is built on the laravel/mcp package. The panel does
not depend on it; install it as a dev dependency:
composer require laravel/mcp --dev
laravel/mcp ^0.7.1 … ^1.0 is supported. It needs Laravel 12.41.1+ or 13.
The transport is stdio only: the client starts php artisan larsadmin:mcp itself as a child process.
There is no HTTP endpoint, so the server cannot be reached over the network. The command writes errors to
stderr, because stdout is the MCP channel.
When the server runs
APP_ENV |
LARSADMIN_MCP not set (null) |
LARSADMIN_MCP=true |
LARSADMIN_MCP=false |
|---|---|---|---|
local |
runs | runs | off |
anything but production (staging, testing, a dev container) |
off | runs | off |
production, prod, live (any case) |
never | never | never |
Production is detected both by app()->isProduction() and by the environment names production, prod and
live, case-insensitively.
The config key is larsadmin.mcp.enabled (env('LARSADMIN_MCP')). The strings true/false/1/0/on/off
are recognised; an unrecognised value counts as off.
The check runs in three places: when the server is registered, in the larsadmin:mcp command and at the
start of every tool. So starting the server around the command (php artisan mcp:start larsadmin) or a
config cached in another environment does not open it in production. When the check fails,
larsadmin:mcp exits with code 1 and prints the reason, for example:
The larsadmin MCP server runs only when APP_ENV=local; set LARSADMIN_MCP=true to run it in the [staging] environment (never in production).
Without laravel/mcp the command prints The larsadmin MCP server needs the laravel/mcp package: composer require laravel/mcp --dev.
Tools
The server has 8 tools. Seven only read (readOnly, idempotent). generate is marked destructive (with
force: true it overwrites files) and writes files only when asked to explicitly. Each returns structured JSON of at most 50,000 characters; longer lists are cut with
"truncated": true. Errors are returned as text, without a stack trace and without values.
| Tool | Input | Returns |
|---|---|---|
search-docs |
query (2–200 characters), limit (1–10, 5), page (optional) |
the best matching guide sections with an excerpt |
read-docs |
page, section (optional) |
the page's table of contents and a section's Markdown |
application-info |
versions, panel settings (allow-listed), features, registration counts | |
list-entities |
the registered entities in short | |
describe-entity |
entity (uriKey or class) |
the structure of one entity |
list-dashboards |
the dashboards and their cards | |
list-extensions |
extensions and tools | |
generate |
kind, name, options, write, force |
the plan of files; with write: true writes them |
search-docs
Full-text search (BM25) over the English guides of the installed package copy (docs/guide/en), by ## and
### sections. Identifiers are found both whole (larsadmin:entity, LARSADMIN_PATH, relatableQuery) and in
parts. page limits the search to one page (03-fields); an unknown page gives an error with the list.
{
"version": "1.6.0",
"results": [
{ "page": "04-relations", "title": "Relations", "section": "Which records can be selected",
"anchor": "which-records-can-be-selected", "score": 12.4, "excerpt": "…" }
]
}
read-docs
A guide page by its NN-name (only from the list of existing pages; no path from the client is opened).
Without section it returns the table of contents and the start of the page; with section (an anchor or a
heading) the section with its subsections: { page, title, sections: [{ title, anchor, level }], content, truncated }. The content is cut at 40,000 characters.
application-info
Versions (larsadmin, apiVersion, laravel, php), panel settings (name, path, domainSet, guard,
locale, locales, timezone, the files disk name, the actions queue name), enabled features (audit,
notifications, realtime, scout, globalSearch), the number of entities, dashboards, tools and
extensions, mcp.writesAllowed, and where the provider and the app/Larsadmin directory are.
The configuration is read through an explicit list of keys. Never returned: the domain (only
domainSet: true|false), realtime hosts, ports and keys, connections, any keys, passwords and tokens.
list-entities and describe-entity
list-entities returns, for each entity, key, class, file, model, label, singularLabel, group,
inNavigation, globallySearchable, auditable and the number of fields, actions, filters and cards.
describe-entity (input entity: the uriKey or class of a registered entity) returns the static
properties, whether indexQuery/detailQuery/relatableQuery are overridden, the policy class and file,
the fields, layout blocks, actions (with their fields), filters, cards and warnings: no policy,
$globallySearchable without $search, filters without an overridden indexQuery.
Introspection sees what is visible without a user. Prototypes of fields, actions, filters and cards are built with a synthetic request without authentication, and the server never:
- runs closures (
canSee,canRun, visibility,readonly): it reports that a closure is there instead of a result. In a field'scontextsandreadonlythat is the string"closure", while a field'scanSeeis a flag:truemeans the field has acanSeeclosure (it was not run, so visibility for a given user is unknown),falsemeans there is none. In actions and filters,canSee/canRunare"closure","overridden"(the method is overridden in the class) or"default"; - calls
resolve(),calculate()/data()of cards,Tool::props(),options()of filters or any model query; - returns values,
default,placeholder,help,withMeta()orconfirmText; - returns
Selectoptions (only{ source, count }) or rule parameters that may be data. Numeric parameters are shown only for size and bound rules (min,max,size,between,digits,digits_between,decimal,gt,ltand so on), formats in full (date_format,mimes,mimetypes,extensions);in,not_in,exists,unique,regex,required_ifand the others are given by name only, even with numeric parameters (they may be record IDs).
Three keys describe a field's validation rules:
| Key | What it holds |
|---|---|
declaredRules |
{ create, update }: the rules from rules() together with creationRules() / updateRules() |
rules |
{ create, update }: the complete rules the panel validates with (the field type's implicit rules first, then the declared ones), or null |
rulesComputed |
true when rules could be computed without host code |
rulesComputed: false and rules: null occur for extension fields, Select (it builds in from its
options), File/Image and fields with an overridden rulesFor(): the implicit rules of such fields are not
run. Read the field's documentation then. Relation panels (HasMany, BelongsToMany) are not validated: all
three keys are null / false there. A leading implicit nullable in rules only means the field accepts
empty input.
When fields(), actions(), filters() or cards() of your entity throws without a user (it calls
$request->user()->…, say), the response holds
"unavailable": [{ "unavailable": "fields", "reason": "<exception class> without an authenticated user" }],
without the exception's message. The rest of the description is returned as usual.
list-dashboards and list-extensions
list-dashboards: key, class, file, name, inNavigation, isHome (the first registered dashboard),
canSee and the cards (uriKey, class, type, width, …). Metric data is not calculated.
list-extensions: extensions (name, path, version, script, style, translations, package) and
tools (uriKey, class, file, name, component, icon). Tool::props() is not called.
package tells where to find the extension's PHP classes: { name, namespaces, source }, where namespaces
are the package's PSR-4 namespaces and source is "installed.json" (from vendor/composer/installed.json)
or "composer.json" (from the composer.json in the extension directory); null when no package is found.
The component keys an extension registers in JavaScript are not listed.
generate
Generates classes with the same code as the artisan commands (the same stubs, the same name validation, byte-for-byte the same output).
| Field | Value |
|---|---|
kind |
entity, action, filter, dashboard, value, trend, partition, progress, field, card, data-card (= larsadmin:card --data), tool, extension |
name |
a StudlyCase class name, with / for sub-namespaces (Blog/Post); for extension the Composer package name (acme/larsadmin-rating) |
options |
only the options of the matching command: entity { model }; action { destructive }; field, card, data-card, tool { extension } (the extension package directory); extension { path, field, card }. An unknown key gives an error |
write |
false (default): only the plan; true: write the files |
force |
overwrite existing files; only together with write: true |
Names that are PHP reserved words (Class, List, Match and so on) are refused, as in the artisan commands.
The response:
{
"kind": "action",
"written": false,
"files": [
{ "path": "app/Larsadmin/Actions/MarkLive.php", "status": "create", "contents": "<?php …" }
],
"nextSteps": ["Return it from an entity's actions(Request $request): new \\App\\Larsadmin\\Actions\\MarkLive"]
}
status: create (no such file), exists (the file exists and stays unchanged without force),
overwrite (the file existed and was overwritten with force: true). contents is returned in the plan
only (write: false).
The usual flow: the assistant calls generate without write, shows you the plan, and only after you agree
calls it again with write: true. The writing rules:
- all or nothing: when any file already exists and
forceis not set, nothing is written, andnextStepsexplains how to overwrite. Every path of the plan is checked before the first file is written; - application classes are written only to
app/Larsadmin/**(the Vue components of application fields, cards and tools toresources/larsadmin/js), an extension package only to thepathdirectory (packagesby default), which must be belowpackages/orextensions/of the application root. The same goes for theextensionoption of fields, cards and tools; - never into
vendor,node_modules,storage,bootstrap,publicor directories starting with a dot; path segments of dots only or ending in a dot are refused; - a path through a symlink (a dangling one included) or outside the application root is refused;
- never in production;
- no
composer,npmor any other code execution: only files from stubs.nextStepssays what to run next; - every write is logged at
infolevel with the messagelarsadmin.mcp.generateand the file paths.
The assistant changes existing code (adding a field to an entity and so on) with its own means: generate
only creates new files.
Security model
- The server runs locally only (stdio) and never in production (When the server runs).
- No tool reads database records or runs host closures, SQL, artisan or arbitrary code.
- Responses are built only from explicit lists of allowed keys; configuration secrets and exception messages never get into them.
- Only
generatewrites files, only withwrite: trueand only inside the application. - The skills and the guideline are plain text files: they give the assistant no rights it did not have.
Introspection shows the assistant the structure of the admin panel, the policies and the validation rules.
Do not turn the server on (LARSADMIN_MCP=true) in shared environments where other people can reach the
terminal.
Keeping the skills up to date
The skills have the same version as the package. After composer update maxcxam/larsadmin, refresh them:
- with Boost:
php artisan boost:update; - without Boost:
php artisan larsadmin:ai --skills.
larsadmin:ai replaces only directories with a .larsadmin-skill marker whose contents did not change since
they were installed. A directory you edited or created yourself is skipped with a warning
(it was changed by hand); --force replaces it as well. To keep your own changes, put them in a separate
skill with a different name.
The guideline imported through CLAUDE.md (without Boost only) is read straight from vendor/ and is updated
with the package. With Boost, boost:update refreshes the guideline.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
The client says the larsadmin server failed to start or was not found |
run php artisan larsadmin:mcp by hand: the command prints the reason to stderr. Most often APP_ENV is not local (set LARSADMIN_MCP=true) or laravel/mcp is missing |
needs the laravel/mcp package |
composer require laravel/mcp --dev |
composer require laravel/mcp does not install on Laravel 12 |
Laravel 12.41.1+ is needed: composer update laravel/framework |
LARSADMIN_MCP was changed but nothing happened |
the configuration is cached: php artisan config:clear |
never runs in production |
by design; work with a local copy of the application |
boost:install does not offer maxcxam/larsadmin or installs no skills |
the package is not in the application's require, or Boost 1.x (skills need 2.x) |
larsadmin:ai prints Skipped … changed by hand |
the skill directory was edited by hand: --force replaces it |
larsadmin:ai prints Skipped … it was installed by Laravel Boost |
Boost installed the skills: update them with php artisan boost:update, or pass --force to manage them with larsadmin:ai from now on |
larsadmin:ai prints Skipped … not created by larsadmin:ai |
the directory was not created by this command (by hand or another tool): --force replaces it |
| The assistant writes Inertia pages or Vue components in the application for ordinary panel tasks | Boost added the inertia-laravel/core guideline: exclude it in config/boost.php (with Boost) |
Skipped .mcp.json: the "larsadmin" server is already configured |
the entry exists; --force overwrites only that entry |
The file [.mcp.json] has the duplicate key … or … has the number …, which cannot be kept exactly |
rewriting the file would lose data; add the printed larsadmin entry by hand |
generate: must be below packages/ or extensions/ |
MCP creates extensions only in packages/ or extensions/; for another directory use php artisan larsadmin:extension --path=… |
describe-entity returns unavailable |
the entity's fields()/actions()/filters()/cards() fail without a user; make the code cope with $request->user() === null, or ignore it: the rest of the description is there |
generate returns written: false with already exist |
the files exist; call it with write: true and force: true if you really want to overwrite them |
would be generated outside app/Larsadmin |
name starts with the root namespace (App/Models/…); give the name relative to app/Larsadmin (Post, Blog/Post) |
Next: Testing.