laravel/mcp server of your application with no code: you install the package and set your project ID.
Requirements
- PHP 8.3, 8.4 or 8.5.
laravel/mcp1.x, with Laravel 12.41.1 or later, or Laravel 13. On every pull request and every night, CI tests AgentCat with the newest patch of eachlaravel/mcpminor release from 1.0 up.- ext-curl, to send the events.
Install
agentcat:install asks for your project ID, or takes it as an argument (php artisan agentcat:install proj_…):
- It checks the ID:
proj_followed by 27 letters or digits. - It writes
AGENTCAT_PROJECT_IDinto.env. When the application has no.envfile, it creates one with that key only, and says so. - It adds an empty
AGENTCAT_PROJECT_ID=to.env.example, when the application has one. - With
--config, it also publishesconfig/agentcat.php. - It sends nothing over the network.
AGENTCAT_PROJECT_ID in .env yourself. Without a project ID, no event goes to AgentCat; your exporters still get them.
What AgentCat tracks
- Every
laravel/mcpserver that the container resolves, also whenlaravel/mcpbuilds the server for each HTTP request. The server of Laravel Boost is excluded by default. - Every tool call. AgentCat adds a
session_idand acontextparameter to the input schema of each tool (andagent_idwithenable_agent_tracking), and removes them from the arguments before your tool runs. It adds theget_more_toolstool. - After the response. AgentCat runs your hooks and sends the events in Laravel’s
terminating()callbacks, after the client has its answer.
$_SERVER, so Octane workers record them too.
Configuration reference
To change the defaults, publish the configuration file:config/agentcat.php:
The file is safe with
php artisan config:cache: the keys that hold environment variables read them through env(), and the service provider hands the configured values to AgentCat when the application boots.
A value of the wrong type, or a word that is not in the list, logs one warning line and AgentCat uses the default. When enabled holds a word that is not in the list, the AGENTCAT_ENABLED variable of the process decides, and without one, the default. An unknown key logs WARN: Unknown AgentCat option "agentcat.<key>" ignored.
exclude and projects
exclude lists the server classes that AgentCat never tracks. A class also excludes its subclasses. An excluded server touches neither the AgentCat log nor diagnostics.
null, or noexcludekey, is the default list: Laravel Boost’s server.[]excludes nothing, Laravel Boost’s server included.- A blank string names no server: AgentCat logs one warning and uses the default list.
projects sends the events of some servers to another project. The first class that the server is an instance of wins. A null project (env() of an unset variable) means project_id.
The log file
AgentCat writes its own lines tostorage/logs/agentcat.log, through a Laravel single channel that it builds. They stay out of your application’s log and its error trackers: laravel/mcp builds a server for each HTTP request, so the setup lines repeat on every request. To route them into one of your channels, set log_channel, for example to stack.
Hooks are invokable classes
A hook inconfig/agentcat.php is the class name of an invokable class. A closure cannot go in this file, because php artisan config:cache cannot store it. AgentCat resolves the class from the container when the hook runs (under Octane, from the container of the request), and calls it with the same argument as the PHP hook: an AgentCat\ToolCall for identify, resolve_session_id, event_tags and event_properties, a string for redact_sensitive_information, and an AgentCat\Event for redact_event.
Exporters
Inconfig/agentcat.php, an exporter is an array with a type and the fields of its integration page, with the TypeScript field names:
Octane and other long-running workers
Under Laravel Octane (Swoole, RoadRunner or FrankenPHP), a worker serves many requests. After each request, AgentCat waits at most 1 second interminating() for its delivery, so the worker is soon free for the next request.
- The wait shows in request durations. The worker’s time for a request that sends events can be up to 1 second longer, in Octane’s numbers and in your monitoring. The client has its answer before the wait.
- An idle worker holds its last request’s events. Events that are still waiting after the 1 second go out at the next request of the worker, or when the worker stops.
- When a worker stops, AgentCat delivers what is left for at most 5 seconds, at Octane’s
WorkerStoppingevent. Measured on Swoole 6.2.3: when the whole server stops, Swoole did not cut that delivery. A worker that Swoole restarts (for example after Octane’s--max-requests) is stopped by force about twice itsmax_wait_timeserver setting after it is told to stop: about 6 seconds with the default of 3. So amax_wait_timebelow 3 seconds can cut the delivery of a restarting worker short, and with the default, the otherWorkerStoppinglisteners leave AgentCat its 5 seconds only when they take less than about 1 second.
terminating(): it only starts the delivery there, and finishes it at the end of the script, after the response. See PHP runtimes.
AgentCat binds one method object per tracked server in the container, under a key with the server’s object ID. In a worker, the binding of a server that is gone stays until PHP reuses its object ID or the container ends.
Your own method handlers
Atools/list, tools/call or initialize class that your server declares in its $methods property is wrapped: AgentCat delegates to it and records one event per call. For your own tools/call class, the event of a failing tool carries the error of the answer, not the original exception.
A method handler that you set in the server’s boot() method replaces AgentCat’s for that server: laravel/mcp calls boot() after AgentCat has tracked the server. AgentCat then records no event for that method, and logs nothing:
$methods property instead.
ToolSearch catalogs
A server that exposes its tools through alaravel/mcp ToolSearch catalog has the two tools search_tools and execute_tools. AgentCat injects its parameters into them like into any other tool. One execute_tools call is one event: AgentCat does not record the tool calls that run inside it one by one.
Streamed answers
A tool whosehandle() is a generator answers with a stream. Its notifications pass unchanged, and AgentCat makes the event from the final result, so the event’s duration includes the whole stream. A stream that the client abandons publishes no event.
Client name and version
laravel/mcp 1.x has no MCP sessions. AgentCat takes the client’s name and version from the _meta of the call, else from the initialize request of an older client to the same server instance. A stdio server keeps that for the whole process. Over HTTP it is lost, because each request builds a new server. The events have no transport session ID and no auth object: the Authorization header is in the request headers.
Responses on Apache mod_php, php-cgi and php -S
laravel/mcp sends its JSON answers without a Content-Length header. Where PHP sends a response only when the script ends, the bridge gives the plain JSON answers of a tracked server their Content-Length, so the client has the whole answer before AgentCat delivers. PHP runtimes has the rules and the runtimes where it does that.
Testing
AgentCat is off while your tests run (PHPUnit, Pest, orAPP_ENV=testing): your servers answer as without AgentCat, and laravel/mcp’s test helpers see their own answers. To assert on the events of a test, call AgentCat::fake() before the test builds a server, and restore the real client in tearDown():
mcp/sdk page says what each assertion of the fake counts. Three things to know in a Laravel suite:
- Two settings outlive the test that made them. The fake stays the shared client of the PHP process until
AgentCat::setClient(null). Theagentcat.enabledvalue that the service provider reads when the application boots stays in effect until the next application boots. - With the fake, AgentCat changes the answers as in production. A call without a
session_idgets the issued session ID as the first text block ofcontent. A tool with an output schema gets anmcp_sessionentry in itsstructuredContent. An assertion on the exactstructuredContent(for exampleassertStructuredContent()) then sees that entry. - A bad value in
config/agentcat.phplogs one line per test application, also without the fake, because each test boots a new application. The lines go tostorage/logs/agentcat.log.
Laravel Boost guidelines
The package ships guidelines for AI coding agents inresources/boost/guidelines/core.blade.php. When you run php artisan boost:install, Laravel Boost adds them to your agent’s guidelines, so your coding agent knows how AgentCat is set up: the install command, the configuration keys, the hooks as invokable classes, and AgentCat::fake() in tests.