yoosuf/laravel-mcp-server
A Laravel package for building Model Context Protocol servers, letting you expose tools, resources, and prompts to AI clients like Claude through a clean, framework-native API. Includes simple tool registration, configurable transport, and authentication.
This is the first stable release, so treat it as a greenfield adoption rather than an upgrade. Start by reading the package's MCP concept docs before touching code — the tool/resource/discovery split is the mental model everything else hangs off, and it does not map 1:1 onto Laravel conventions you already know.
Minimal path to a working server:
composer require the package into an existing Laravel app (it is Laravel-native, so it expects the framework's container, config, and auth — do not try this in a bare PHP project).Your first use case should be something read-only and boring: a tool that answers a question from your own data (e.g. "look up an order by number and return status"). Resist starting with a write/mutating tool; you want to learn the framework's serialization and error surface on something that cannot corrupt state.
Confirm the exact Artisan command names and the serve/transport commands against the v1.0.0 docs — the release notes describe capability, not invocation syntax, and guessing command names is the fastest way to conclude a working package is broken.
One tool per class, thin handler, fat service. The tool class should own the name, description, and input schema, then delegate to an existing action/service/query object. This keeps your MCP surface testable independently of the transport and stops business logic from accreting inside the tool layer, which is where it will otherwise end up within a month.
Resources for reads, tools for effects. Resources are the natural fit for "expose this model/report to the client" — cheap, cacheable, no side effects. Reserve tools for anything that mutates or computes. Mixing the two makes both the audit log and the security policy incoherent.
Reuse the Eloquent integration, but scope it. The Eloquent integration is the highest-leverage feature and the highest-risk one. Prefer explicitly scoped queries and dedicated API resources / toArray shapes over handing raw models to the client: you want a deliberate allow-list of attributes, not whatever $hidden happens to protect today. Pair every model-backed tool with the app's existing policies or a gate check — the package gives you security hooks, but the authorization semantics are yours to define.
Queue the slow things. Async queue support exists specifically so a tool that takes 30 seconds does not hold a transport connection open past the client's patience. Anything touching external APIs, large exports, or multi-step writes should dispatch a job and return a handle/polling contract. Keep fast lookups synchronous; a queue round-trip for a 40ms query is pure latency.
Layer the transports deliberately. STDIO for local dev and desktop-client use, HTTP for anything shared or multi-tenant, CLI for cron/ops/scripting. Same tool classes behind all three — if a tool only works on one transport, that is a design smell worth fixing early.
Treat audit logging as a schema decision. Decide up front what a log entry contains (tool name, caller identity, redacted argument summary, outcome, duration) and how long it is retained. Retrofitting redaction after you have logged six weeks of raw arguments containing customer records is unpleasant.
Version your tool schemas. Clients cache discovery output. When you rename or reshape a tool's inputs, add rather than mutate, and keep the old shape alive for a deprecation window.
STDOUT is the STDIO wire protocol. On the STDIO transport, a stray dump(), var_dump, echo, or a chatty logger writing to stdout will corrupt the message framing and produce baffling client-side parse errors. Route logs to stderr or a file for STDIO runs, and grep your codebase for debug output before you file a bug against the package. This is the single most common first-day failure.
HTTP transport needs auth you actually configured. A locally-trusting default is fine for 127.0.0.1 and catastrophic on a public host. Verify the middleware stack on the MCP routes explicitly, and confirm rate limiting is on — an exposed tool endpoint is an unbounded compute surface with your database behind it.
Validate inputs on the server, not just in the schema. The declared JSON schema is a description for the client, not enforcement. Assume malformed or hostile arguments arrive anyway and validate in the handler with Laravel's validator.
Watch for N+1 inside resources. Resource endpoints invite "return the collection," and a client walking a list will happily trigger hundreds of queries. Eager-load aggressively and cap result sizes with a pagination contract from day one.
Client timeouts beat your queue timeouts. If the client gives up at 60s and your job timeout is 300s, you have built a tool that always appears to fail. Give long-running tools an immediate acknowledgement-and-handle response pattern instead of making the caller wait.
Confirm state assumptions. MCP calls are effectively stateless from the client's perspective; do not rely on in-memory properties persisting across invocations, particularly under queue workers or Octane-style long-lived processes.
Extension points to look for early: the middleware/pipeline around tool execution (ideal place for tenant scoping and rate limiting), the discovery/registration mechanism (so you can conditionally hide tools per environment or per authenticated user), and the audit log driver (so you can ship to your existing logging stack rather than a second place to look).
Debugging workflow that works: reproduce over STDIO with a minimal client, log the raw request and response payloads at debug level, then move to HTTP. Trying to debug auth, transport, and tool logic simultaneously is how afternoons disappear.
Pin the version. It is 1.0.0 of a young package with a broad surface (three transports, queues, Eloquent, generators). Lock the constraint and read the changelog before bumping; expect the 1.x line to move.
How can I help you explore Laravel packages today?