How to use cipi/agent in Laravel — project MCP for debug and seeders
By Andrea Pollastri · Last updated: · free to read, no paywall
SSHing into production for a migrate:status, a failed-job peek or a staging seeder is slow, unshareable and useless to an AI assistant. The official Laravel package cipi/agent puts webhooks, health checks and — most useful — a project MCP inside the app, so Cursor can read logs, run Artisan and query the database over HTTPS.
Why an agent inside the app
Cipi already owns the server: Nginx, PHP, databases, SSL, zero-downtime deploys. That is infrastructure. A Laravel app still has its own world — Eloquent models, seeders, Horizon queues, daily log rotation, the current migration set. An AI that only sees the repo guesses. An AI that can call tools on the running app stops guessing.
That is what the project MCP is for. It lives at POST /cipi/mcp on the app domain, talks every current MCP revision (2024-11-05 through 2026-07-28) over HTTPS, and exposes eleven tools scoped to that application — plus five panel tools when cipi/sdk is configured. No root SSH. No shared deploy key in the IDE. The assistant asks for health, tails laravel.log, runs db:seed --class=RoleSeeder on staging, and checks the row count with db_query.
Health check and MCP work on any Laravel 12+ host. Webhook deploys and the full log set (nginx, php, worker, horizon, reverb, deploy) expect a Cipi-managed environment. The reference lives in the Cipi Agent docs.
Install cipi/agent
Requirements: PHP 8.3+ and Laravel 12 or 13. The service provider auto-discovers — leave config/app.php alone.
$ composer require cipi/agent
$ php artisan cipi:status # config + live DB connectivityOn a Cipi VPS, cipi app create already injects CIPI_APP_USER, CIPI_WEBHOOK_TOKEN and the deploy paths. You only enable the optional services you want. Off Cipi, publish the config if you need different defaults:
$ php artisan vendor:publish --tag=cipi-agent-configCommit and push. The next deploy picks the package up. Nothing else to wire on the panel side.
What the package actually does
| Feature | Endpoint | When you use it |
|---|---|---|
| Webhook deploy | POST /cipi/webhook | GitHub / GitLab / Bitbucket / Gitea / Azure push writes .deploy-trigger (with audit-ledger claims); Deployer runs as the app user within a minute |
| Health check | GET /cipi/health | UptimeRobot / Grafana: app, database, cache, queue backlog and failed jobs, storage, live release and commit |
| Project MCP | POST /cipi/mcp | Cursor / VS Code / Claude: health, deploy status, logs, SQL and schema, Artisan, env check, cipi.yml, anonymizer, deploy — this guide |
| cipi.yml tooling | php artisan cipi:yml | Validate, inspect or generate the cipi.yml with the server's own rules, locally and in CI (2.0) |
| DB anonymizer | POST /cipi/db | GDPR-safe MySQL/PostgreSQL dumps for local/QA, consistent Faker transforms, signed 15-minute download — from a laptop: cipi-cli db anonymize |
Each feature has its own Bearer token and can be switched off independently. A disabled endpoint returns 404 — it does not exist, as far as a scanner is concerned.
Turn on the project MCP
The MCP server is off by default. Enable it, mint a dedicated token, then print the client snippets:
$ php artisan cipi:service mcp --enable
$ php artisan cipi:generate-token mcp
$ php artisan cipi:mcpcipi:mcp prints the tools, the resources and ready-to-paste snippets for Cursor (native HTTP), VS Code / Copilot (token prompted once), Claude Code (claude mcp add) and Claude Desktop (via mcp-remote). The token lands in .env as CIPI_MCP_TOKEN. Do not reuse the webhook secret.
Connect Cursor, VS Code or Claude
For Cursor, put this in ~/.cursor/mcp.json (or Settings → MCP). Replace the domain and the token from your .env:
{
"mcpServers": {
"cipi-myapp": {
"type": "http",
"url": "https://yourdomain.com/cipi/mcp",
"headers": {
"Authorization": "Bearer YOUR_CIPI_MCP_TOKEN"
}
}
}
}VS Code 1.102+ uses the same HTTP transport in .vscode/mcp.json under a servers key. Claude Desktop needs the mcp-remote stdio bridge — php artisan cipi:mcp prints that block too.
Name the server after the app user (cipi-myapp, cipi-staging). One Laravel app, one MCP. If you run staging and production, register two servers and say which one you mean.
Debug without SSH
This is the part that changes the daily rhythm. You stay in the IDE. The assistant talks to the live app.
health — is it even up?
Same payload as GET /cipi/health: Laravel version, APP_DEBUG, maintenance mode, database name, cache, queue driver, pending and failed jobs, storage, live release and commit. deploy_status adds the releases kept for rollback, whether a deploy is queued or running, and the last automatic deploy's outcome. Ask: “Is production healthy? Any queue backlog?” If checks.app.debug is true on a public host, you just found a problem without opening .env.
logs — last errors, not the whole file
The logs tool reads the last N lines (default 50, max 500) and keeps stack traces intact. Filters:
type—laravel,nginx,php,worker,horizon,reverb,deploy,alllevel—error,warning, … (Laravel only)search— case-insensitive keyword, e.g.PaymentFailedor a job class
Daily rotation (laravel-YYYY-MM-DD.log) is detected automatically, and since 2.0 common secrets (tokens, passwords, DSNs, APP_KEY) are redacted before the text reaches the model. A useful first prompt after a 500: “Show the last 100 Laravel errors, then the matching nginx lines.”
db_query — look, don’t wreck
Read: SELECT, WITH, SHOW, DESCRIBE, EXPLAIN. Write: INSERT, UPDATE, DELETE — or nothing, with CIPI_MCP_DB_WRITE=false. Blocked: DDL (DROP, ALTER, TRUNCATE, CREATE), GRANT, REVOKE, file I/O, multi-statement input. Results come back as a table or JSON, capped at 100 rows (reads without LIMIT are capped automatically) — enough to confirm a seeder, not enough to dump the users table. db_schema shows tables, row counts, columns and which columns look like personal data before you write the query.
# after a RoleSeeder on staging
SELECT id, name, created_at FROM roles ORDER BY id;
# did today’s signups land?
SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE;Seeders, migrate and cache via artisan
The artisan tool is the reason this package earns a slot in a Laravel repo. It runs any Artisan command except the long-running / interactive ones (serve, tinker, pail, queue:work, queue:listen, schedule:work, horizon, horizon:work, octane:start, reverb:start) and, since 2.0, the data-destroying ones (db:wipe, migrate:fresh, migrate:reset). Everything else — including seeders — is fair game.
Prompts that actually save a hop to SSH:
- “Run
migrate:statuson staging.” - “Seed roles only:
db:seed --class=RoleSeeder.” - “Load demo orders with
DemoDataSeeder, thenSELECT COUNT(*) FROM orders.” - “
queue:failed— any jobs stuck since the last deploy?” - “
cache:clearandoptimize:clearafter the config change.”
Do not point this at production and say “run the DatabaseSeeder”. migrate:fresh is blocked since 2.0, but db:seed is not — the package will execute it. Use named seeders, on staging, and confirm with db_query. Production data is not a playground just because the transport is MCP instead of SSH.
A safe staging pattern: one seeder class per fixture, idempotent where you can, called by name. The assistant runs the class, reads the table, and only then do you promote the same seeder through CI. That is the opposite of “dump production and pray”.
Pair it with the anonymizer when local needs volume without PII: anonymize a production-shaped dump (cipi-cli db anonymize myapp --email you@example.com from your terminal), load it locally, keep MCP seeders for the small reference tables (roles, plans, feature flags) that change every sprint.
A daily loop you can copy
You: Staging healthy? Any pending jobs?
Agent: health → healthy, queue 0, debug false, commit a1b2c3d
You: Last Laravel errors, search "InvoiceJob"
Agent: logs type=laravel level=error search=InvoiceJob
→ 1 error, missing column invoices.paid_at
You: migrate:status. Is 2026_08_22_add_paid_at pending?
Agent: artisan migrate:status → yes, pending
You: After I deploy, seed InvoiceStatusSeeder only. Record that I asked.
Agent: deploy actor=you@example.com → queued (audit ledger: claimed.actor)
artisan db:seed --class=InvoiceStatusSeeder
db_query SELECT id, name FROM invoice_statuses
→ 4 rowsNo SSH session. No copy-paste from storage/logs. The same conversation works for a teammate who has the MCP token and should never have root on the box — which is most of the team.
Project MCP vs panel API MCP
Cipi ships two MCP servers. Mixing them up is the usual first mistake.
Project MCP (cipi/agent) | Panel API MCP (cipi/api) | |
|---|---|---|
| Where | POST /cipi/mcp on the app domain | POST /mcp on the API vhost |
| Scope | One Laravel app: its DB, logs, Artisan, deploy flag | The whole server: apps, SSL, databases, PHP, workers |
| Tools | 11 — health, app_info, deploy, deploy_status, logs, artisan, db_query, db_schema, env_check, cipi_yml, anonymizer (+5 panel_* via cipi/sdk) | 68 — create apps, issue certs, edit .env, run as app user |
| Use it for | Debug, seeders, migrate:status, queue peek, cipi.yml drafts, anonymizer audits | Provision, SSL, list every database, server cockpit |
Keep both in Cursor if you like. Ask the project MCP about this app; ask the panel API MCP to create the next staging clone. The spec-driven AI guide covers the wider loop — this page is the in-app half.
Tokens, blocks and least privilege
- Dedicated token.
CIPI_MCP_TOKENis not the webhook secret and not the health token. If it leaks,php artisan cipi:generate-token mcpand restart. Anyone with the token can run Artisan and write SQL (within the 100-row cap) — setCIPI_MCP_DB_WRITE=falsefor a read-only server. Logs and command output are redacted before they leave the server. - Off means 404.
php artisan cipi:service mcp --disableorCIPI_MCP=false. Prefer this on production until you actually need the IDE connected. - Blocked Artisan:
serve,tinker,queue:work,queue:listen,schedule:work,horizon,octane:start,reverb:start. - Blocked SQL:
DROP,TRUNCATE,GRANT,REVOKE, file I/O. - HTTPS only, no SSH. Fine for a developer who should deploy and inspect without
sudo. Still a privileged channel — treat the token like a production password.
Install the package, then the server
cipi/agent is the Laravel companion. Cipi is the free, open-source deploy CLI that injects the env and runs Deployer when the webhook or the MCP deploy tool fires.
Frequently asked questions
Do I need SSH to use the project MCP?
No. Once the package is deployed and CIPI_MCP is on, the IDE talks to https://yourdomain.com/cipi/mcp with the Bearer token. That is the point for teammates who should never log in as root.
What is the difference between the agent MCP and the Cipi API MCP?
The agent MCP is inside one Laravel app (eleven tools: health, deploy status, logs, SQL and schema, Artisan, env check, cipi.yml, anonymizer, deploy — plus panel tools via cipi/sdk). The panel API MCP manages the whole VPS — create apps, SSL, databases, PHP. Use the agent for debug and seeders; use the API to provision.
Can I run db:seed on production through MCP?
db:seed is not blocked, so yes. migrate:fresh, migrate:reset and db:wipe are blocked since cipi/agent 2.0. Practically, only run a named, reviewed seeder, prefer staging, then confirm with db_query.
Which Artisan commands are blocked?
serve, tinker, pail, queue:work, queue:listen, schedule:work, horizon, horizon:work, horizon:supervisor, octane:start, reverb:start, db, db:wipe, migrate:fresh, migrate:reset. Long-running, interactive and data-destroying processes do not belong on an HTTP tool call; extend the list with cipi.mcp_blocked_artisan.
Does cipi/agent work without a Cipi server?
Health check, MCP, cipi:yml validate and the anonymizer yes, on any Laravel 12 or 13 host. Webhook-triggered Deployer deploys, release information and the extra log types expect the Cipi layout under /home/<app>/.
How do I rotate a leaked MCP token?
php artisan cipi:generate-token mcp (it rebuilds the configuration cache), reload Octane or the workers if you run them, update ~/.cursor/mcp.json. The old token dies with the .env rewrite.