Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Server Laravel Package

php-mcp/server

PHP implementation of the Model Context Protocol server, letting Laravel and PHP apps expose tools, resources, and prompts to MCP clients like Claude and other AI assistants through a clean, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

php-mcp/server implements the server side of the Model Context Protocol — the JSON-RPC contract that lets an LLM client (Claude Desktop, Cursor, a custom agent) discover and call your tools, read your resources, and render your prompts. For a Laravel developer the mental model is: you are exposing an API, but the consumer is a model, not a browser.

Minimal path:

  1. composer require php-mcp/server. If you're on Laravel 10/11, also install the framework bridge (php-mcp/laravel) — it wires the server into the container and gives you an artisan entrypoint, so you don't hand-roll a bootstrap script.
  2. Read the README's examples/ directory first. The MCP spec surface is small: tools (callable functions), resources (readable URIs), prompts (parameterised templates). Every example maps to one of those three.
  3. Pick your transport immediately, because it dictates how you run and debug: stdio for a local process spawned by the client (Claude Desktop command + args config), or HTTP/SSE for a long-lived remote endpoint your team shares.
  4. Register one read-only tool and prove the loop end-to-end before writing anything else.
// app/Mcp/Tools/OrderLookup.php — illustrative shape
#[McpTool(name: 'orders.lookup', description: 'Find a single order by its public reference.')]
class OrderLookup
{
    public function __invoke(string $reference): string
    {
        $order = Order::where('public_ref', $reference)->firstOrFail();

        return "Order {$order->public_ref}: {$order->status}, "
             . $order->items()->count() . " items, total {$order->total}";
    }
}

First use case worth shipping: a read-only lookup tool over a domain you already understand (orders, tickets, invoices) plus a resource that lists recent records. That combination is the "hello world" of MCP and immediately demonstrates value in a chat client. Resist the urge to start with write tools — those need guardrails you haven't designed yet.

Implementation Patterns

Thin tool, fat action. A tool handler should be a 5-line adapter. Put the logic in an Action/Service class that your HTTP controllers also call. This keeps the MCP surface testable with plain unit tests and prevents business rules from forking between your API and your model interface.

Register by attribute discovery, not a giant array. Point the server at app/Mcp/ and let it scan for #[McpTool]/#[McpResource]/#[McpPrompt]. Auto-discovery beats a manual registry because adding a tool becomes a one-file commit and the tool list stays in sync with the codebase. Boot the registry in a McpServiceProvider.

Input schemas are your contract. Derive the JSON Schema from typed method parameters where the package supports it, and annotate descriptions per-parameter. The model reads those descriptions to decide when to call you — treat them as prompt engineering, not documentation. Vague descriptions are the #1 cause of "the model picked the wrong tool."

Resources for Eloquent, tools for actions. A resource URI like laravel://orders/{id} maps cleanly onto route-model binding: parse the URI, resolve the model, serialise a compact JSON representation. Use resources for anything the client should read and cite; use tools for anything with side effects or arguments that need validation. Listing resources should be paginated and stable — clients cache the list.

Prompts as versioned Blade templates. #[McpPrompt] handlers should render a Blade view with validated arguments rather than concatenating strings. You get escaping, @include composition, and diffable changes when you tune prompt wording.

Transport is a deployment decision.

  • stdio — best for a per-developer or per-workstation tool. The client spawns php artisan mcp:serve. Zero auth, zero infrastructure, zero shared state.
  • HTTP/SSE — best for a shared team endpoint. Put it behind your normal auth middleware, issue scoped tokens, and run it on a long-lived runtime (Octane, RoadRunner, Swoole). This is the pattern that actually scales to an org.

Auth and tenancy belong in middleware. If the transport exposes a PSR-15-style middleware pipeline, resolve the API token there, bind the tenant into the container, and let every tool assume tenant() is set. Do not pass a tenant ID as a tool parameter — the model will happily hallucinate one.

Log every invocation with its request ID. A tool call is a production action taken by a non-deterministic actor. Capture: tool name, arguments, duration, success/failure, tenant, and the MCP request ID so you can correlate with client-side transcripts. This log is your only forensic trail when someone asks "why did the assistant refund that order?"

Test the handler, not the protocol. Instantiate the tool class directly in Pest/PHPUnit, pass a payload array, assert on the returned string or structured content. Add one integration test per transport that does a real initialize → tools/list → tools/call round trip so you catch schema regressions.

Gotchas and Tips

Never write to stdout on stdio transport. This is the single most common failure. Any echo, var_dump, stray dd(), or Laravel deprecation notice printed to stdout corrupts the JSON-RPC frame and the client shows a cryptic parse error. Configure a stderr log channel for the MCP process and audit every dump() in the call path. If a tool "silently does nothing," suspect stray output before you suspect the handler.

HTTP transport does not fit PHP-FPM. SSE requires a process that stays open. On FPM you'll hit max_execution_time and buffering issues that look like client timeouts. Use Octane/RoadRunner/Swoole, or terminate SSE at a proxy. Budget for this early — it's an architecture change, not a config tweak.

Stale state in long-running workers. Octane-style runtimes keep the container alive across requests. Cached Eloquent models, Auth::user(), and resolved tenant bindings all leak between calls. Reset request-scoped state in the middleware or in an Octane RequestReceived listener, and be explicit about which singletons are safe.

Return strings sized for a context window. A resource that dumps 5,000 rows will blow the model's context and produce useless answers. Truncate, paginate, and summarise server-side. A good rule: if the serialised output exceeds a few KB, you're returning too much — give the model a handle and let it fetch details in a second call.

Validate inputs as hostile. The model will send null where you declared string, send a string where you declared integer, and invent enum values. Run inputs through Laravel's Validator and return a structured error the model can read and recover from — a raw exception becomes a dead turn.

Separate destructive tools into their own namespace and require explicit intent. orders.refund should never be one fuzzy inference away from orders.lookup. Name it verbosely, describe the consequences in the schema description, and consider a two-step confirm flow. Also make writes idempotent where possible, because clients retry.

Never leak internals in errors. Stack traces and SQL fragments returned as tool output are both a security issue and a prompt-injection vector. Catch at the tool boundary, log the exception, return a sanitised message.

Tool names and schemas are a public API. Renaming a tool or removing a parameter breaks every client config silently. Version by adding new tools rather than mutating old ones, and keep a changelog.

Discovery should be cached but invalidatable. Scanning attributes on every tools/list is wasteful in production. Cache the manifest keyed by a build hash, and clear it in your deploy pipeline — not on every request.

Extension points worth knowing: custom transports (if you need WebSocket or a queue-backed async call model), the middleware pipeline (auth, tenancy, rate limiting, audit logging), and the attribute readers (if you want a custom #[TenantScoped] marker that filters tool visibility per tier). These three cover most of what a Laravel-shaped integration needs.

Debugging tip: when a client connects but lists nothing, it's almost always discovery scope — the scanner is pointed at the wrong namespace, or the attribute class import is wrong and PHP silently ignores it. Dump the resolved manifest in a test before you debug the transport.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager