Laravel Mcp Invoicing Playground
БесплатноНе проверенA production-shaped Laravel 13 invoicing app whose entire feature surface (auth, tenancy, invoice lifecycle, PDFs) is also reachable over the Model Context Prot
Описание
A production-shaped Laravel 13 invoicing app whose entire feature surface (auth, tenancy, invoice lifecycle, PDFs) is also reachable over the Model Context Protocol, with an automated parity test proving every UI capability has an equally-policed MCP tool.
README
A Laravel 13 invoicing app where the browser and an AI client are two equal front doors onto the same domain layer - every screen a human can use is also an MCP tool an AI client can call, with the same policies, validation, and audit trail either way. An automated parity test proves it: if the HTTP surface and the MCP surface ever drift apart, CI fails.
what this demonstrates
- A real invoicing app, not a demo shell - login, multi-tenant organisations, roles, customer/invoice CRUD, a PDF pipeline, a dashboard. Every one of these is also reachable over MCP.
- The MCP server reuses the app, not a parallel copy -
app/Mcp/Servers/InvoicingServer.phpexposes tools, resources and prompts that call the same policies and Form Request validation as the controllers. - A parity guarantee, enforced in CI -
CapabilityMapdeclares every HTTP route's MCP equivalent (or a reasoned exemption), andMcpParityTestfails the build if a route and its tool ever go out of sync. - MCP hardening most demos skip - per-tool authorisation, prompt-injection-resistant tool output, confirmation gates on destructive actions, idempotency keys, a full audit log, and a kill switch that removes every write tool from the catalogue at once.
- phoenix1331/laravel-auth-audit extended to MCP - one command reports authorisation coverage across both the HTTP and MCP surfaces, not just routes.
tech stack
- Laravel 13 (PHP 8.3 / 8.4)
- MySQL
- FrankenPHP via Docker Compose
laravel/mcp- the MCP server itselflaravel/passport- OAuth 2.1 for MCP clientslaravel/dusk- browser end-to-end testsvimeo/psalm, Larastan, OSV-Scanner - static analysis and dependency scanning
getting started
Prerequisites: Docker and Docker Compose. No local PHP install needed - everything runs in the container.
git clone https://github.com/phoenix1331/laravel-mcp-invoicing-playground.git
cd laravel-mcp-invoicing-playground
cp .env.example .env
make build
make up
make composer cmd="install"
make npm cmd="install"
make artisan cmd="key:generate"
make fresh
make storage-link
make npm cmd="run build"
Open http://localhost:8000/login and sign in with one of the accounts below.
If docker compose ps doesn't show app and mysql as healthy, check make logs - the usual cause is another process already using port 8000 or 3307.
demo accounts
All seeded users share the password password.
| Organisation | Role | |
|---|---|---|
[email protected] |
Acme Ltd | Owner |
[email protected] |
Acme Ltd | Member |
[email protected] |
Acme Ltd | Viewer |
[email protected] |
Globex Inc | Owner |
[email protected] sits in a separate organisation - handy for checking cross-tenant denial by hand.
everyday commands
| Command | What it does |
|---|---|
make up / make down |
start / stop the containers |
make fresh |
migrate:fresh --seed |
make test |
run the Pest suite |
make dusk-setup then make dusk |
seed the Dusk database, then run the browser suite |
make pint / make stan / make psalm |
linting, Larastan, Psalm taint analysis |
make shell |
open a shell in the app container |
make artisan cmd="..." / make composer cmd="..." / make npm cmd="..." |
run any Artisan/Composer/npm command |
make dev |
run the dev server, queue listener and Vite together - see note below |
make help |
list every target |
Note on make dev: Vite's HMR port isn't published to the host, so leaving make dev running in the background silently breaks CSS/JS reloading. Use it for a one-off foreground session, otherwise just run make npm cmd="run build" after editing Blade/CSS/JS.
connecting an AI client
Every connection method below is also generated live, with your own token pre-filled, at /settings/mcp once you're logged in.
Claude Code (local, stdio) - no tunnel, no token, runs inside the container as you:
{
"mcpServers": {
"invoicing": {
"command": "docker",
"args": ["compose", "exec", "-T", "app", "php", "artisan", "mcp:start", "invoicing"]
}
}
}
Claude Code / Cursor (remote) - reads url/headers directly from the client's MCP config. Needs a tunnel (below) plus a token from /settings/tokens:
{
"mcpServers": {
"invoicing": {
"url": "https://xxxx.trycloudflare.com/mcp/invoicing",
"headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" }
}
}
}
Claude Desktop (remote) - added via Settings → Connectors → Add custom connector, not the config file, since Desktop only starts local servers from claude_desktop_config.json. Unlike the two clients above, there's no bearer token to copy in manually - Desktop authenticates via the full OAuth 2.1 flow instead:
- Server URL:
https://xxxx.trycloudflare.com/mcp/invoicing
That's the only field needed. On Add, Desktop discovers the OAuth endpoints from /.well-known/oauth-authorization-server, registers itself as a client via /oauth/register (dynamic client registration, no manual client ID/secret), then opens the authorization screen below for you to log in and approve access:
Claude Desktop connects from Anthropic's cloud, not your machine, so localhost is never reachable for it - a tunnel is required, not optional.
Once connected, Claude can call any tool in the catalogue directly from the conversation - for example, asking it to fetch your latest invoices:
Known issue - "Ask every time" tool permissions: if Claude Desktop's connector permission is set to ask before every call rather than always allow, the approval dialog can time out waiting for a click and the call comes back reported as denied instead of timed out. The server never receives the request in this case - confirmed via /audit/mcp, which shows nothing logged for the failed attempt - so this is a Claude Desktop client-side issue, not something wrong with the tunnel or the app. Every layer on the server side (Caddy, Laravel, the OAuth token check) responds in ~150-200ms regardless of how long the connection has been idle, both warm and cold. Workaround: set the connector's tool permissions to always allow for read tools.
exposing your local server with a tunnel
This repo uses cloudflared rather than ngrok - ngrok's free tier serves an interstitial "you are about to visit..." warning page to real browser navigation (not just API calls), which silently breaks the OAuth login redirect Claude Desktop's connector wizard depends on. cloudflared's quick tunnels don't have this problem and need no account.
- Install
cloudflared. cloudflared tunnel --url http://localhost:8000- prints ahttps://xxxx.trycloudflare.comforwarding URL after a few seconds.- Set
TUNNEL_HOSTNAME=http://xxxx.trycloudflare.com:8000in.env-http://, nothttps://, even though the forwarding URL itself is HTTPS. This only tells Caddy inside the container which Host header to match; usinghttps://here makes Caddy try to fetch its own Let's Encrypt certificate for a hostname it can't complete an ACME challenge for, which hangs silently in the background. The:8000is required too - a bare hostname defaults to Caddy's standard port 80, which nothing listens on, so Caddy silently routes it to an unreachable listener instead of alongsidelocalhost:8000and every request comes back an empty 200. make upto pick up the change - no committed file needs editing,docker-compose.ymlreadsTUNNEL_HOSTNAMEfrom the environment.- Use the
https://forwarding URL from step 2 in whichever client config above. - When you're done, remove
TUNNEL_HOSTNAMEfrom.envand re-runmake up.
The tunnel hostname changes every restart on a quick tunnel, so this is a repeatable manual step, not something to commit.
the MCP server
app/Mcp/Servers/InvoicingServer.php registers everything below. The live, always-current catalogue with input/output schemas is at /settings/mcp, and in docs/data/capability-map.json (regenerated by php artisan docs:capability-map).
- Read tools:
invoices.list,invoices.get,customers.list,customers.get,organisation.get,team.list,reports.summary,reports.aging - Write tools:
invoices.create,invoices.update,invoices.add_line,invoices.remove_line,invoices.send,invoices.mark_paid,invoices.void,invoices.delete,invoices.download_pdf,customers.create,customers.update,customers.delete,organisation.update,team.invite,team.set_role - Resources:
invoicing://guidelines,invoicing://schema,invoice://{invoiceId},invoice://{invoiceId}/pdf,customer://{customerId} - Prompts:
draft-invoice,chase-overdue,month-end-review
Set MCP_WRITES_ENABLED=false in .env to pull every write tool from the catalogue at once - not just reject calls to it, remove it from discovery entirely.
the parity guarantee
app/Mcp/Support/CapabilityMap.php maps every named HTTP route to its MCP tool, or a reasoned exemption (health checks, OAuth well-known endpoints, and similar non-domain routes). tests/Mcp/McpParityTest.php checks three things on every run: every non-exempt route has a registered tool, every mapped tool actually exists, and no mapping points at something that's been removed.
The same data renders as a live parity matrix on /settings/mcp - add a route without updating CapabilityMap and that page goes red, and CI fails.
security model
- Tenancy - every model is scoped by a global scope and the policy checks the organisation ID again independently, so a missing scope on a new query can't silently leak across tenants.
- Roles - Owner, Member, Viewer, enforced identically on both surfaces via the same policies.
- Per-tool authorisation - the
AuthorizesToolAccesstrait gives every tool a consistent check before it reads or writes anything. - Prompt injection resistance - user content returned to the model (names, notes) is wrapped in explicit delimiters via
App\Mcp\Support\UntrustedText, so an injected instruction arrives clearly labelled as data.tests/Mcp/Security/PromptInjectionTest.phpseeds real payloads and checks nothing privileged is reachable. - Confirmation gates -
invoices.deleteandinvoices.voidneed an explicitconfirm: true, or they return a structured "are you sure" instead of acting. - Idempotency - write tools accept an
idempotency_key; a repeat within 24 hours replays the original result instead of repeating the mutation. - Audit log - every MCP call is recorded (who, what, arguments, outcome, duration) and visible at
/audit/mcp. - Kill switch -
MCP_WRITES_ENABLED=falseserver-wide. - Rate limiting -
throttle:mcpon every call, tighter on writes than reads.
auth coverage
phoenix1331/laravel-auth-audit scans routes for authorisation signals and reports what percentage are provably guarded. This repo extends the scan to app/Mcp/Tools, so one command covers both surfaces:
make artisan cmd="auth-audit:run --min=90"
Current result: 100% coverage, 0 unauthorised routes. auth-audit-baseline.json is committed and deliberately empty - nothing to grandfather. CI runs this on every push and uploads the HTML report as a build artifact.
testing
| Suite | Command | Covers |
|---|---|---|
| Feature + Unit (Pest) | make test |
controllers, MCP tools, policies, prompt injection resistance, cross-tenant denial for every tool |
| Browser (Dusk) | make dusk-setup && make dusk |
the full invoice lifecycle through the real UI, role-appropriate visibility of actions |
441 Pest tests currently pass. Dusk runs against a real running server, so it needs its own seeded database first.
CI/CD
.github/workflows/ci.yml - PHP 8.3/8.4 matrix, concurrency group so a new push cancels a stale run.
| Job | Contents |
|---|---|
lint |
Pint, Prettier, composer validate --strict |
static-analysis |
Larastan level 8 |
tests |
Pest, both PHP versions |
security-scan |
composer audit, npm audit, Psalm taint analysis |
osv-scan |
OSV-Scanner across both lockfiles |
auth-audit |
coverage check, HTML report artifact |
docs |
capability map, GitHub Pages deploy |
e2e (Dusk) is currently disabled in CI - it passes reliably locally (make dusk), but hit an intermittent CI-runner-only failure not worth blocking every push on. Run it yourself with make dusk-setup && make dusk.
If you fork this, set up branch protection on main requiring all of the above to pass before merge.
licence
MIT - see LICENSE.
Portfolio/reference project, not an actively maintained package - PRs welcome but not expected.
from github.com/phoenix1331/laravel-mcp-invoicing-playground
Установка Laravel Mcp Invoicing Playground
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/phoenix1331/laravel-mcp-invoicing-playgroundFAQ
Laravel Mcp Invoicing Playground MCP бесплатный?
Да, Laravel Mcp Invoicing Playground MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Laravel Mcp Invoicing Playground?
Нет, Laravel Mcp Invoicing Playground работает без API-ключей и переменных окружения.
Laravel Mcp Invoicing Playground — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Laravel Mcp Invoicing Playground в Claude Desktop, Claude Code или Cursor?
Открой Laravel Mcp Invoicing Playground на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiSupabase
Database, auth and storage
автор: SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Laravel Mcp Invoicing Playground with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
