php-mcp/laravel
Laravel integration for the Model Context Protocol (MCP). Build MCP servers in your Laravel app, register tools, resources, and prompts, and connect AI clients with familiar Artisan commands, routing, configuration, and authorization.
php-mcp/laravel wires the php-mcp/server core into Laravel so you can expose tools, resources, and prompts to MCP clients (Claude Desktop, Cursor, Zed, custom agents) over stdio or HTTP without hand-rolling JSON-RPC.
composer require php-mcp/laravel, then publish the config/routes the service provider offers (check php artisan list mcp for the package's install/inspect commands). The published config/mcp.php is the source of truth for transport, discovery, and session settings; read it before anything else.php and laravel/framework constraints in composer.json before upgrading; the release adds Laravel 13 support and, as with any 3.x → 4.x jump, expect minimum-version bumps and possible config/signature changes. Run composer update php-mcp/laravel -W and diff the published config against the new stub.app/Mcp or a routes/mcp.php-style file) and attach tools:
Mcp::server('app')
->withTools([LookupOrder::class, SearchDocs::class])
->withResources([...])
->withPrompts([...]);
Tool class, point Claude Desktop or Cursor at your endpoint (or your php artisan mcp:serve-style stdio command), and confirm the tool shows up in the client's tool list. That proves discovery, schema generation, and transport in one shot.Tools as thin adapters over existing application code. Don't put business logic in tool classes. Inject your services/actions and let the tool do argument → DTO → call → response mapping. This keeps tools testable and lets the LLM-facing schema evolve independently of domain code.
Validate and describe explicitly. LLM clients pick tools from names + descriptions + JSON schemas. Name tools as verbs (search_orders, refund_payment), write descriptions for a reader with no context, and use the package's schema/attribute support to mark required fields and enums rather than relying on inference from PHP types alone. Return structured results (arrays/DTOs the package serialises) with a clear error/success shape instead of throwing raw exceptions — the model needs actionable text, not a stack trace.
Split read from write, and gate writes. Read-only tools ship first. For mutating tools, combine three things: an authorisation check inside the tool (the MCP request is still a request — run it through your policies/Gates using the authenticated user, not a service account), idempotency where the operation allows it, and a confirmation-style parameter or two-step tool for destructive actions. Never assume the client will ask the user first.
Transport selection matrix.
Stateless mode in practice. Keep request handling self-contained: no in-memory state between calls, no reliance on progress notifications or resource-subscription updates, and re-resolve the authenticated user from the request each time. Tools should be idempotent or explicitly non-idempotent, and any long-running work should be queued with the tool returning a job/handle the client can poll via a second tool. The stateless flag lives in the transport configuration/registration call — the exact key is in the published config after upgrade.
Testing and CI. Write feature tests that invoke each tool class directly with a constructed request object and assert on the returned payload — fast, no transport needed. Add one integration test per configured transport that boots the server and lists tools, so registration/discovery breakage is caught. Assert on the tool schema too: a renamed parameter is a breaking change for every prompt that used it.
Observability. Log every tool invocation with tool name, argument keys (not raw sensitive values), duration, and outcome, using a dedicated channel. This is your only window into what an agent is doing, and it's what you'll replay when a model starts calling the wrong tool.
^4.0 deliberately, run your test suite, and re-publish/diff config/mcp.php — a changed default (transport, discovery, middleware group) can silently alter behaviour.file or array cache driver will look fine locally and fail under load. Stateless mode sidesteps this entirely — often the single best reason to upgrade.dump(), dd(), a deprecation notice, a package that echos) corrupts the JSON-RPC stream and produces baffling client errors. Route logs to stderr/files and never leave debugging output in tool code.$this->authorize(...) or a policy check inside every mutating tool — a permissive endpoint exposed to an agent will eventually be asked to do something creative.search_docs_v2) or keep backwards-compatible aliases during transitions.How can I help you explore Laravel packages today?