Repository navigation
Releases: catidegla/laravel-agent-kit
Release list
v0.5.0
Laravel 13
Tested against Laravel 12 and 13, across PHP 8.2, 8.3 and 8.4, on every commit.
Laravel 13 requires PHP 8.3, so the 8.2 rows stay on 12 and the rest run both. The constraint widens rather than moves: nothing about Laravel 12 support changes.
58 tests. They assert the security properties directly rather than describing them: that internal_notes is absent from a payload, that Bob's ticket is not in Alice's list, that a model without a policy throws, that a denied get is indistinguishable from a missing one, that no field value ever reaches the audit record, that a relation cannot reach a model nobody exposed, and that a generated tool class loads and serialises through laravel/mcp itself rather than merely looking right.
v0.4.0
Adds the tool generator, which was the last thing the README listed as missing.
php artisan agent-kit:mcpOne laravel/mcp tool class per declared ability, written to app/Mcp/Tools,
and the command prints the block to paste into your server.
The generated bodies delegate to the resource and do nothing else, which
the generated file states at the top. The field allowlist, the per record
policy check, the result ceiling, the relation rules and the audit trail all
live in the resource, so anything added to a tool would run outside every one
of them. Change the attribute and run the command again rather than editing the
class.
The input schema is generated from the same attribute, so an agent sees which
fields are filterable, which relations it may expand, and the ceiling it will
be held to whatever it asks for.
Nothing is generated from an exposure that does not hold together. The
command runs verify() first and writes no files if it reports anything,
because a tool built on a resource with no policy fails the first time an agent
calls it, which is the wrong place to find out.
An existing file is left alone unless you pass --force. Somebody will
have edited a generated class, and losing that quietly is worse than making
them ask. --path and --namespace are there for applications that do not use
the defaults.
laravel/mcp is a development dependency of this package, not a requirement.
It is needed by the code the generator writes, not by the package itself.
The tests write a generated class to disk, load it, and run it through
laravel/mcp's own serialiser, so the output is checked against the real API
rather than against a reading of the documentation.
58 tests across PHP 8.2, 8.3 and 8.4, up from 49.
The "not built yet" list is now empty.
v0.3.0
Adds relation traversal, leaving the laravel/mcp generator as the only thing
the README still lists as missing.
An agent may ask for declared relations to be expanded:
$resource->get($user, $id, include: ['comments']);The target's own rules apply, not the parent's. Expanding comments
returns exactly what Comment declares in its own attribute, checked against
Comment's own policy, for the same signed in user. A relation composes two
exposures that already existed. It never creates a third.
A relation cannot reach a model you never exposed. If the target is not a
registered resource the call is refused, because there would be no field list
and no policy to apply. Registering a model with abilities: [] makes it
reachable through relations without publishing a tool that can enumerate it.
One level, never two. An expanded record does not itself expand. The next
hop is another tool call, separately authorized and separately recorded.
Walking the object graph inside a single call is how one question becomes a
full export.
A to-many relation is capped by the target's own ceiling and reports truncation
rather than trimming quietly. A to-one the viewer may not see comes back as
null. Relations are eager loaded, so expanding across a page costs one extra
query rather than one per row, and a test asserts it.
verify() now catches a relation with no method behind it and one pointing at
an unexposed model, at deploy time rather than when an agent asks. The audit
trail records the ids reached through each relation, because "what did the
agent read" is wrong in exactly the interesting case if it stops at the parent.
49 tests across PHP 8.2, 8.3 and 8.4, up from 34.
v0.2.0
Adds the audit trail, the one thing the README listed as missing.
Every list, get and search is recorded before its result is returned.
Identifiers and field names, never field values. A trail that stored the
rows would become a second copy of every record an agent ever read, in a table
nobody wrote a policy for, and the log would be a softer target than the data
it was meant to protect. To see values, read those ids from the source, where
the policy still applies.
A denied read and a missing one are recorded apart, while staying identical
to the agent. get still returns null for both so ids cannot be enumerated;
the operator gets the distinction they need, in a record the agent never sees.
Refused calls are recorded too. One filter on an unexposed field is a
mistake. A run of them is probing.
Strict by default. A sink that refuses takes the read down with it, because
a trail with silent gaps cannot answer the question it exists for, and the
moment a gap opens is exactly when an attacker would choose to be reading.
Set agent-kit.audit.strict to false if availability matters more to you.
The default sink writes to a log channel, so it works on a fresh install with
no migration. Bind Audit\AuditSink for anything else. ArraySink ships so
your own tests can assert that a tool call produced the record you expected.
34 tests across PHP 8.2, 8.3 and 8.4, including that no field value ever
reaches the audit record and that the log line format does not drift.
Still not built, and still not pretended otherwise: relation traversal, and a
generator that emits laravel/mcp tool classes from a resource.
v0.1.0
First release.
Expose Eloquent models to an AI agent without handing it your database.
laravel/mcp has 34 million installs and gives you the primitive: define a tool, an agent can call it. What it does not give you is any reason to believe the tool is safe to expose. The usual first version returns every column, has no idea who is asking, and does nothing to stop an agent iterating ids until it has the table.
This makes those three the default rather than the thing you remember to add.
What it guarantees
- Undeclared columns never leave. Projection works from the declared field list, never from the model, so a migration adding a column cannot widen exposure.
- Every record is authorized individually, as the signed in user rather than as the application. Denied rows are counted and reported, not silently dropped.
- It fails closed everywhere. A model annotated without a policy is refused rather than published. A field you can search but cannot read is a configuration error, because it leaks one query at a time.
- Ids cannot be enumerated. A denied record and a missing one answer identically.
- An agent cannot page out your table. maxResults is a ceiling, not a default, and truncation is reported honestly.
- verify() reports every configuration problem in one pass, so a misconfigured model fails your build instead of surfacing in production.
Scope. This is the authorization and exposure layer, deliberately not an MCP server, because laravel/mcp already is one and is good at it.
Not built yet and not pretended otherwise: an audit trail of tool calls, relation traversal, and a generator emitting laravel/mcp tool classes.
20 tests on PHP 8.2, 8.3 and 8.4. They assert the properties directly: that a hidden column is absent from a payload, that one user's record is not in another's list, that a model without a policy throws.