PHPackages                             wizcoders/mcp - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. wizcoders/mcp

ActiveLibrary

wizcoders/mcp
=============

Model Context Protocol server for Laravel — expose your application to MCP clients as RBAC-aware tools. Runs on PHP 7.3 / Laravel 8 and up.

v1.0.0(today)00MITPHPPHP ^7.3|^8.0

Since Aug 24Pushed todayCompare

[ Source](https://github.com/wizcoders/mcp)[ Packagist](https://packagist.org/packages/wizcoders/mcp)[ RSS](/packages/wizcoders-mcp/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (4)Versions (2)Used By (0)

wizcoders/mcp
=============

[](#wizcodersmcp)

A Model Context Protocol server for Laravel. Expose your application to MCP clients — Claude Code, Claude Desktop, or anything else that speaks MCP — as a set of tools that respect your existing permission model.

Runs on **PHP 7.3 / Laravel 8** and on **PHP 8.x / Laravel 9–12** from the same source.

Why not an existing MCP SDK
---------------------------

[](#why-not-an-existing-mcp-sdk)

`logiscape/mcp-sdk-php` and `php-mcp/server` both require PHP ≥ 8.1. MCP is JSON-RPC 2.0 over a byte stream, so the protocol layer here is a few hundred lines with no runtime dependency beyond `illuminate/*` and `psr/log` — which is cheaper than blocking on a platform upgrade.

Compatibility
-------------

[](#compatibility)

The source deliberately avoids everything added after PHP 7.3, so one codebase serves every supported version: no arrow functions, typed properties, `??=`, `match`, enums, constructor promotion, named arguments, or union types. Public interfaces declare no parameter or return types, so adding them later would be the breaking change — not the absence.

`composer.json` accepts `illuminate/* ^8|^9|^10|^11|^12`, and Composer picks the set your PHP version allows.

Install
-------

[](#install)

```
composer require wizcoders/mcp
php artisan vendor:publish --tag=mcp-config
```

The provider is auto-discovered. Nothing is registered on the HTTP path, so a web request pays nothing for having this installed.

Configure
---------

[](#configure)

```
// config/mcp.php
'server_name' => 'my-app',
'authorizer'  => \App\Mcp\MyAuthorizer::class,
'tools'       => [
    \Wizcoders\Mcp\Tools\WhoAmITool::class,
    \App\Mcp\Tools\SalesSummaryTool::class,
],
```

`mergeConfigFrom` is a shallow merge, so `tools` **replaces** the package default rather than adding to it — re-list `WhoAmITool` unless you mean to drop it.

Packages can also contribute tools at runtime, without touching app config:

```
$this->app->make(\Wizcoders\Mcp\ToolRegistry::class)->register(new MyTool);
```

Authorization
-------------

[](#authorization)

Tools declare a permission string; an `Authorizer` decides what it means.

AuthorizerBehaviour`NullAuthorizer` (default)Denies everything gated — fails closed`GateAuthorizer`Resolves through Laravel's `Gate`; works with policies and spatie/laravel-permissionyour ownImplement `Wizcoders\Mcp\Contracts\Authorizer`The default denies rather than allows on purpose: an app that installs this and forgets to configure an authorizer gets a server exposing only ungated tools, not one that quietly hands an assistant the keys.

The check runs before a tool is **listed**, not just before it runs — a user without the permission never learns the tool exists, and `tools/call` returns the same "unknown tool" message for forbidden and nonexistent alike so the set cannot be enumerated by probing.

Bundled tools
-------------

[](#bundled-tools)

ToolPurpose`whoami`Acting user, app-supplied context, and which tools they can see`db_schema`Table list, or one table's columns / indexes / foreign keys`app_packages`Composer packages, flagging local path packages and their PSR-4 namespaces`app_routes`Routes by URI, name or controller, with middlewareThe last three are **project-insight** tools: they let an assistant learn your schema and layout before writing a query or hunting for a controller, instead of guessing column names. They return structure only — never row data.

`db_schema` works across three Laravel generations: native `Schema::getTables()`on Laravel 11+, doctrine/dbal on 8–10, and a name-only fallback otherwise. It also registers Doctrine type mappings for `enum`, `set`, `year`, `bit` and the spatial types, because DBAL throws `Unknown database type enum requested`rather than degrading — one ENUM column would otherwise make a whole table un-introspectable.

They are gated by `mcp.introspection_permission`, which defaults to null (ungated). That is reasonable for a **local stdio server**, where the client already had to be able to launch a PHP process and therefore already has database and filesystem access. **Set a permission before serving over HTTP**, where that assumption no longer holds — or drop the tools from `mcp.tools`.

Writing a tool
--------------

[](#writing-a-tool)

```
class SalesSummaryTool implements \Wizcoders\Mcp\Contracts\Tool
{
    public function name() { return 'sales_summary'; }

    public function description()
    {
        return 'Total sales for a date range, broken down by status. Use this for '
             . '"what did we sell last month" or any revenue figure over a period.';
    }

    public function inputSchema()
    {
        return [
            'type'       => 'object',
            'properties' => [
                'from' => ['type' => 'string', 'format' => 'date', 'description' => 'Inclusive start.'],
            ],
            'required'             => ['from'],
            'additionalProperties' => false,
        ];
    }

    public function permission() { return 'report_dailysales_view'; }

    public function handle(array $arguments) { /* ... */ }
}
```

Tools are resolved through the container, so constructor dependencies are injected.

Three things matter more than they look:

- **`description()` is the prompt.** It decides whether the model reaches for the tool at all, and whether it reaches for the right one. Say *when* to call it, not only what it does.
- **Return aggregates, not rows.** Whatever a tool returns is billed as input tokens on the model's next turn. A tool that returns six numbers beats one that returns the thousand invoices behind them.
- **Throw `ToolException` for anything the model can fix** (unknown record, bad date range). Those come back as a tool result with `isError: true`, which the model reads and retries around. Anything else becomes a generic error and is logged server-side — stack traces and SQL must never reach the client.

For an empty argument list use `new \stdClass()`, not `[]`: an empty PHP array encodes to `[]` and clients reject that as an invalid JSON Schema property bag.

Running
-------

[](#running)

```
php artisan mcp:serve --user=1
```

It speaks newline-delimited JSON-RPC on stdin/stdout, so it is only useful by hand for a smoke test:

```
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"1"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  | php artisan mcp:serve --user=1
```

**Nothing may write to stdout** — that channel belongs to the protocol. The transport redirects PHP's own diagnostics to stderr for this reason; a stray `dd()` or `echo` in a tool will still corrupt the stream, and the client will report only "server disconnected".

### Claude Code

[](#claude-code)

```
claude mcp add my-app --scope project -- /usr/bin/php /path/to/artisan mcp:serve --user=1
```

`--scope project` writes `.mcp.json` at the repo root so the team shares it; `--scope local` (the default) keeps it to your machine. Verify with `/mcp`inside Claude Code.

### Claude Desktop

[](#claude-desktop)

```
{
  "mcpServers": {
    "my-app": {
      "command": "/usr/bin/php",
      "args": ["/path/to/artisan", "mcp:serve", "--user=1"]
    }
  }
}
```

Use an absolute path to the PHP binary in both cases — the client does not inherit your shell's `PATH`.

Architecture
------------

[](#architecture)

ClassRole`Protocol\JsonRpc`JSON-RPC 2.0 envelopes`Server``initialize`, `ping`, `tools/list`, `tools/call``ToolRegistry`Holds tools, applies the authorizer`Transport\Transport`Interface — moves messages`Transport\StdioTransport`Newline-delimited JSON on stdin/stdout`Support\UserResolver`Finds the acting user via the guard's provider`Server` never touches a stream and `UserResolver` never names a user model, so adding a Streamable HTTP transport is one new class and no changes to any tool.

Not implemented
---------------

[](#not-implemented)

- **Streamable HTTP transport and OAuth.** Required for remote or multi-user access, and required before the Claude API's MCP connector can reach this server at all — that connector takes a URL, so stdio is not an option there.
- **Resources and prompts.** Only the `tools` capability is advertised.
- **Progress notifications and cancellation.**

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3297384?v=4)[faisalkaleem](/maintainers/faisalkaleem)[@faisalkaleem](https://github.com/faisalkaleem)

---

Top Contributors

[![faisalkaleem](https://avatars.githubusercontent.com/u/3297384?v=4)](https://github.com/faisalkaleem "faisalkaleem (2 commits)")

---

Tags

laravelmcpaijson-rpcModel Context Protocol

### Embed Badge

![Health badge](/badges/wizcoders-mcp/health.svg)

```
[![Health](https://phpackages.com/badges/wizcoders-mcp/health.svg)](https://phpackages.com/packages/wizcoders-mcp)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M337](/packages/laravel-ai)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80427.1M248](/packages/laravel-mcp)[laravel/sail

Docker files for running a basic Laravel application.

1.9k212.4M1.5k](/packages/laravel-sail)[laravel/boost

Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.

3.6k26.0M836](/packages/laravel-boost)[propaganistas/laravel-disposable-email

Disposable email validator

6023.2M7](/packages/propaganistas-laravel-disposable-email)[tallstackui/tallstackui

TallStackUI is a powerful suite of Blade components that elevate your workflow of Livewire applications.

731189.9k16](/packages/tallstackui-tallstackui)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
