The official Laravel companion package for the Cipi server control panel.
Webhook deploys, health monitoring, an MCP server for AI assistants, cipi.yml tooling and a database anonymizer — all in one package.
Cipi Agent runs inside your Laravel application on a server managed by Cipi. Cipi manages the infrastructure (LEMP stack, SSL, PHP versions, domains, workers, backups); this package is the part that lives in the app and bridges the two:
- Webhook-triggered deployments from GitHub, GitLab, Bitbucket, Gitea and Azure DevOps, recorded in Cipi's deploy audit ledger
- Health monitoring of app, database, cache, queue (pending and failed jobs), storage and deploy state
- An MCP server that lets AI assistants (Cursor, VS Code, Claude Code, Claude Desktop, any MCP client) inspect and operate the production app
cipi.ymltooling — validate, inspect and generate the declarative server configuration with the same rules ascipi yml validateon the server- A database anonymizer for MySQL/MariaDB and PostgreSQL that produces safe, privacy-compliant dumps for local development
- Optional panel integration through
cipi/sdk: rollback, deploy audit ledger, database backups and job status from the same MCP server
Everything is configured through environment variables. When you create an application through Cipi, the core variables are injected automatically.
Note: some features (health check, MCP,
cipi.ymlvalidation, anonymizer) work on any host. Automated deployments, infrastructure logs and release information require a Cipi-managed environment.
- Requirements
- Installation
- Configuration
- Webhook Deploy
- Health Check
- MCP Server
- cipi.yml Tooling
- Database Anonymizer
- Panel Integration (cipi/sdk)
- Artisan Commands
- Security
- Upgrading from 1.x
- Development
- License
| Requirement | Version |
|---|---|
| PHP | 8.3 or higher |
| Laravel | 12 or 13 |
| Database | MySQL / MariaDB or PostgreSQL (for the anonymizer) |
| CLI tools | mysqldump (or mariadb-dump) / pg_dump on the server |
| Optional | cipi/sdk ^1.0 for the panel tools, ext-zlib for gzip |
composer require cipi/agentThe service provider is auto-discovered. On a Cipi-managed server the required environment variables are already in place. To customize defaults, publish the configuration:
php artisan vendor:publish --tag=cipi-agent-configCheck that everything is wired up:
php artisan cipi:status| Variable | Default | Description |
|---|---|---|
CIPI_WEBHOOK_TOKEN |
"" |
Secret for webhook authentication (set by Cipi) |
CIPI_APP_USER |
"" |
Linux user of the app (set by Cipi) — locates /home/<user> |
CIPI_HOME |
/home/<app_user> |
Override the app home directory |
CIPI_PHP_VERSION |
system PHP | PHP version assigned by Cipi (set by Cipi) |
CIPI_DEPLOY_BRANCH |
null |
Only deploy pushes to this branch (null = any) |
CIPI_ROUTE_PREFIX |
cipi |
URL prefix for all agent routes |
CIPI_LOG_CHANNEL |
null |
Log channel for deploy events |
CIPI_HEALTH_CHECK |
true |
Enable the health endpoint |
CIPI_HEALTH_TOKEN |
"" |
Bearer token for the health endpoint (falls back to CIPI_WEBHOOK_TOKEN) |
CIPI_MCP |
false |
Enable the MCP server |
CIPI_MCP_TOKEN |
"" |
Bearer token for MCP |
CIPI_MCP_ALLOWED_ORIGINS |
app host | Browser origins allowed on the MCP endpoint (* = any) |
CIPI_MCP_DB_WRITE |
true |
Allow INSERT/UPDATE/DELETE through the db_query tool |
CIPI_MCP_MAX_ROWS |
100 |
Default row cap for db_query (max 500) |
CIPI_MCP_REDACT |
true |
Redact common secrets in logs and command output sent over MCP |
CIPI_ANONYMIZER |
false |
Enable the anonymizer endpoints |
CIPI_ANONYMIZER_TOKEN |
"" |
Bearer token for the anonymizer API |
CIPI_ANONYMIZER_CONFIG |
~/.db/anonymization.json |
Explicit path of the anonymization config |
CIPI_ANONYMIZER_TTL |
15 |
Minutes a download link stays valid |
CIPI_MYSQLDUMP / CIPI_PG_DUMP |
from PATH |
Dump binaries, when not on PATH |
CIPI_PANEL |
true |
Expose the panel tools when cipi/sdk is installed and configured |
CIPI_PANEL_APP |
CIPI_APP_USER |
App name on the panel |
CIPI_BASE_URL / CIPI_TOKEN |
— | Panel API URL and Sanctum token (read by cipi/sdk) |
Toggle features and generate tokens from the CLI; the configuration cache is rebuilt for you when one exists:
php artisan cipi:service mcp --enable
php artisan cipi:generate-token mcp
php artisan cipi:service anonymize --enable
php artisan cipi:generate-token anonymizeEndpoint: POST /cipi/webhook
The endpoint verifies the push, then drops ~/.deploy-trigger. The app's crontab (installed by Cipi) picks it up within a minute and runs cipi-app-deploy, which wraps Deployer: clone, composer install, migrations, symlink swap, worker restart, cipi.yml reconciliation, post-deploy steps and the post-deploy healthcheck.
| Provider | Verification | Events |
|---|---|---|
| GitHub | X-Hub-Signature-256 (HMAC-SHA256) |
push; ping answers pong |
| GitLab | X-Gitlab-Token |
Push Hook |
| Bitbucket | X-Hub-Signature (sha256=) |
repo:push |
| Gitea | X-Gitea-Signature |
push |
| Azure DevOps | X-Gitlab-Token (as registered by Cipi) |
git.push |
Other events are acknowledged with 202 ignored. With CIPI_DEPLOY_BRANCH set, pushes to other branches get 200 skipped.
Audit ledger. The trigger file carries what the payload said about the push — provider, actor, commit, delivery id and the client IP. Cipi records these under claimed in the root-owned, hash-chained deploy audit ledger (cipi deploy <app> --audit), next to the facts root establishes itself. The same format is used by the MCP deploy tool (source: mcp), so every deploy says who asked for it.
On Cipi-managed servers the webhook is configured automatically when you connect the repository through the panel. Use
CIPI_WEBHOOK_TOKENas the webhook secret elsewhere.
Endpoint: GET /cipi/health — Bearer token (CIPI_HEALTH_TOKEN, then CIPI_WEBHOOK_TOKEN).
php artisan cipi:generate-token health
curl -H "Authorization: Bearer $TOKEN" https://yourdomain.com/cipi/health{
"status": "healthy",
"app_user": "myapp",
"php": "8.4",
"laravel": "13.35.0",
"environment": "production",
"checks": {
"app": { "ok": true, "version": "2.1.0", "debug": false, "maintenance": false },
"database": { "ok": true, "driver": "mysql", "database": "myapp" },
"cache": { "ok": true, "driver": "database" },
"queue": { "ok": true, "connection": "database", "pending_jobs": 0, "failed_jobs": 0 },
"storage": { "ok": true, "disk_free_gb": 31.2, "disk_used_percent": 41 },
"deploy": {
"ok": true,
"release": "20261009103000",
"commit": "0123456789abcdef0123456789abcdef01234567",
"short_commit": "0123456",
"commit_source": "REVISION",
"in_progress": false,
"pending": false,
"last_deploy": { "status": "ok", "trigger": "webhook", "branch": "main", "duration_seconds": 42 }
}
},
"timestamp": "2026-10-09T10:00:00+00:00"
}status is degraded (HTTP 503) when the database, cache, queue or storage check fails. The deployed commit is read from Deployer's REVISION file of the live release, with fallbacks for apps not deployed by Cipi. A failed last deploy is reported under deploy.attention without degrading the status: the previous release is still serving.
Endpoint: POST /cipi/mcp — Streamable HTTP, JSON-RPC 2.0, Bearer token (CIPI_MCP_TOKEN).
The server speaks every current revision of the Model Context Protocol: the handshake-based revisions 2024-11-05 → 2025-11-25 (initialize, used by today's Cursor, VS Code, Claude Desktop…) and the stateless revision 2026-07-28 (server/discover, per-request _meta, resultType, Mcp-Method/Mcp-Name header validation). Tools carry titles, behaviour annotations (readOnlyHint, destructiveHint, …) and return both text and structuredContent; tool failures use isError. GET/DELETE answer 405 (no SSE stream, no sessions), the Origin header is validated against the app host, and notifications get 202.
php artisan cipi:service mcp --enable
php artisan cipi:generate-token mcp
php artisan cipi:mcp # endpoint, tools and ready-to-paste client configurationcipi:mcp prints snippets for Cursor (~/.cursor/mcp.json), VS Code (.vscode/mcp.json, token prompted once), Claude Code (claude mcp add --transport http …) and Claude Desktop (via the mcp-remote bridge).
| Tool | Mode | What it does |
|---|---|---|
health |
read-only | Same report as /cipi/health |
app_info |
read-only | Versions, drivers, Cipi endpoints, enabled features, live release and commit (no secrets) |
deploy |
action | Queue a zero-downtime deploy; records actor, ref, request_id as audit claims; refuses while a deploy is running |
deploy_status |
read-only | Live release and commit, releases kept for rollback, queued/running state, last deploy outcome, deploy log tail |
logs |
read-only | laravel, nginx, php, worker, horizon, reverb, deploy or all; severity and keyword filters; redacted |
artisan |
can change data | Run an Artisan command; interactive, long-running and data-destroying commands are blocked |
db_query |
configurable | SELECT/WITH/SHOW/DESCRIBE/EXPLAIN as table or JSON (auto-capped); INSERT/UPDATE/DELETE unless CIPI_MCP_DB_WRITE=false |
db_schema |
read-only | Tables with row counts, or one table's columns, indexes, foreign keys and the columns that look like personal data |
env_check |
read-only | Which .env keys are set or empty, vs .env.example and cipi.yml env.required — names only, never values |
cipi_yml |
read-only | validate, show, generate or example for cipi.yml; validates drafts passed as content |
anonymizer |
action | audit, config, scan, transformations, or start an anonymized dump |
panel_* |
see below | Rollback, deploy audit ledger, DB backup, job status, app/server status through the panel API |
Resources: cipi://cipi.yml, cipi://cipi.yml/schema, cipi://cipi.yml/example, cipi://anonymization.json, cipi://anonymization.json/schema.
- "Is production healthy, and what commit is live?"
- "Show the last 100 error entries and tell me which job is failing."
- "Which columns of the users table look like personal data?"
- "Validate this cipi.yml draft before I commit it."
- "Queue a deploy and record that Jane asked for it."
- Blocked Artisan commands:
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— extend the list withcipi.mcp_blocked_artisan - Blocked SQL: DDL (
DROP,ALTER,TRUNCATE,CREATE …),GRANT/REVOKE, file I/O, administrative statements, multi-statement queries; reads withoutLIMITare capped - Redaction: tokens, passwords, DSNs,
APP_KEY, provider keys are masked in logs and command output; a production-content warning is prepended (CIPI_MCP_REDACT) - Origin validation against the app host (
CIPI_MCP_ALLOWED_ORIGINSto extend)
cipi.yml is the declarative file committed with the app that drives its server configuration: PHP version, aliases, php.ini overrides, limits, basic auth, extra databases, workers (queues, Horizon, Reverb), scheduler, healthcheck, deploy recipe and post-deploy steps, redirects, proxies, SSL, required .env names, crons and backup profiles. On the server cipi yml validate|plan|apply reconcile it.
This package ships the same parser and validator (a line-for-line port of the server's), so you get the server's verdict — and the server's exact error messages — locally and in CI:
php artisan cipi:yml validate # ./cipi.yml, then the server locations; exit 1 on errors
php artisan cipi:yml validate --json # machine-readable, for CI
php artisan cipi:yml validate --strict # warnings (e.g. env.required missing in .env) also fail
php artisan cipi:yml show # the normalized document with the server defaults applied
php artisan cipi:yml generate --write # a cipi.yml inferred from this Laravel app
php artisan cipi:yml example --write # the commented template, in the app's namespace
php artisan cipi:yml schema --write # JSON Schema for editor autocompletiongenerate looks at the application itself: Horizon or plain queue workers (QUEUE_CONNECTION), Reverb, Scout with Meilisearch, scheduled tasks, the /up health route, CIPI_PHP_VERSION, extra databases in the app namespace and the credential-looking keys of .env.example for env.required. The output is run through the validator before it is returned. cipi yml generate <app> on the server remains the source of truth for what is configured today.
For editor validation add this as the first line of cipi.yml after cipi:yml schema --write:
# yaml-language-server: $schema=./cipi.schema.jsonNamespace rules need the app name: CIPI_APP_USER, or --app=<name> outside the server.
Creates an anonymized dump — MySQL/MariaDB (mysqldump, extended inserts) and PostgreSQL (pg_dump, COPY blocks and INSERT statements) — by streaming the dump and replacing sensitive columns with realistic fake values from Faker. The real data never lingers: the original dump is deleted as soon as it has been processed, output files are 0600, and database passwords travel in the environment, never on the command line.
php artisan cipi:service anonymize --enable
php artisan cipi:generate-token anonymize
php artisan cipi:init-anonymize --scan # propose a config from the live schema
php artisan cipi:anonymize-audit # check it against the database--scan reads the schema and proposes a transformation for every column that looks like personal data (e-mails, names, phones, addresses, IPs, tokens, passwords, tax ids, card data, free-text notes…) and empties the usual operational tables (sessions, jobs, failed_jobs, password_reset_tokens, personal_access_tokens, Telescope, Pulse…). Review the file before the first dump. The config lives at /home/<app>/.db/anonymization.json, outside the repository, with permissions 0640.
cipi:anonymize-audit (also cipi:anonymize --check and the MCP anonymizer tool) reports tables and columns that no longer exist, unknown transformations, password columns not using password, and the personal-data columns left uncovered, with a suggestion for each.
{
"transformations": {
"users": {
"name": "fakeName",
"email": { "type": "fakeEmail", "unique": true, "consistent": true },
"password": "password",
"remember_token": "null",
"phone": "mask:3,2"
},
"orders": {
"customer_email": { "type": "fakeEmail", "consistent": true },
"card_last_four": "numerify:####",
"internal_notes": "fakeParagraph"
}
},
"truncate": ["sessions", "jobs", "failed_jobs", "password_reset_tokens", "personal_access_tokens"],
"options": {
"hash_algorithm": "auto",
"password": "password",
"faker_locale": "en_US",
"consistent": true,
"seed": null,
"gzip": false
}
}A transformation is a name ("fakeEmail"), a name with an argument ("fixed:ACME", "numerify:###", "mask:1,2", "hash:32"), or an object with type plus unique / consistent flags. NULL values stay NULL (except with fixed). A JSON Schema is published with vendor:publish --tag=cipi-schemas.
consistent— the same original value becomes the same fake value in every table and every run with the sameseed, so denormalized e-mails and names still join.unique— no two rows of a column receive the same value; on by default forfakeEmail,fakeSafeEmail,fakeUserName,fakeUuid,fakeToken,fakeIban(columns that usually carry a unique index).password— every row gets the hash ofoptions.password(defaultpassword), computed once, so developers can log in to the anonymized copy.hash_algorithmautouses the app's Hash driver.truncate— structure is kept, rows are dropped from the dump.
| Transformation | Output | Transformation | Output |
|---|---|---|---|
fakeName / fakeFirstName / fakeLastName |
Person names | fakeIpv4 / fakeIpv6 / fakeMacAddress |
Network identifiers |
fakeUserName |
User name (unique) | fakeUserAgent |
Browser user agent |
fakeEmail / fakeSafeEmail |
E-mail (unique) | fakeUuid |
UUID v4 (unique) |
fakeCompany / fakeJobTitle |
Company, job title | fakeLatitude / fakeLongitude |
Coordinates |
fakeAddress / fakeStreetAddress |
Postal / street address | fakeNumber[:digits] |
Random integer |
fakeCity / fakeState / fakePostcode |
City, region, postcode | fakeBoolean |
Random true/false |
fakeCountry / fakeCountryCode |
Country name / ISO code | fakeToken[:length] |
Random hex token |
fakePhoneNumber |
Phone number | fakeIdentifier |
Alphanumeric id (tax id, passport…) |
fakeDate / fakeDateTime |
Y-m-d / Y-m-d H:i:s |
numerify:### / lexify:??? / bothify:?# |
Patterns |
fakeUrl / fakeDomain |
URL, domain | password |
Hash of options.password |
fakeParagraph / fakeSentence / fakeText[:len] / fakeWord |
Text | null / empty / fixed:value / redact |
Constants |
fakeIban[:CC] / fakeCreditCardNumber |
Banking (test ranges) | hash[:len] / mask:first,last / keep |
Derived from the original |
php artisan cipi:anonymize --transformations prints the list.
# queue a dump — the signed download link is e-mailed (a queue worker and MAIL_* are required)
curl -X POST https://yourdomain.com/cipi/db \
-H "Authorization: Bearer $CIPI_ANONYMIZER_TOKEN" -H "Content-Type: application/json" \
-d '{"email": "developer@example.com", "gzip": true}'
# find the original user id for an e-mail (debugging an anonymized dataset)
curl -X POST https://yourdomain.com/cipi/db/user \
-H "Authorization: Bearer $CIPI_ANONYMIZER_TOKEN" -H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'The e-mail contains a signed link (GET /cipi/db/{token}, valid CIPI_ANONYMIZER_TTL minutes) and a summary: size, rows transformed per table, tables emptied, warnings.
php artisan cipi:anonymize # server config → storage/cipi/anonymized_<timestamp>.sql
php artisan cipi:anonymize --gzip # .sql.gz
php artisan cipi:anonymize /path/config.json /path/out.sql # explicit paths (1.x signature)
php artisan cipi:anonymize --check # validate + audit, no dumpThe agent only knows the app it runs in. With the PHP SDK installed and a Sanctum token created on the server with the abilities you want to allow, the MCP server gains tools that no in-app process could offer:
composer require cipi/sdk
# on the server: cipi api token create → abilities: apps-view, deploy-manage, dbs-manage, status-viewCIPI_BASE_URL=https://api.myserver.com
CIPI_TOKEN=...| Tool | Panel operation | Ability |
|---|---|---|
panel_status |
App as the panel sees it + server snapshot | apps-view, status-view |
panel_deploy_audit |
Hash-chained deploy audit ledger (--audit) |
deploy-manage |
panel_rollback |
cipi deploy --rollback (code only, migrations stay) |
deploy-manage |
panel_db_backup |
cipi db backup server-side snapshot |
dbs-manage |
panel_job |
Status, result and output of an asynchronous panel job | any |
The tools appear automatically when the SDK is installed and CIPI_BASE_URL/CIPI_TOKEN are set (CIPI_PANEL=false hides them). Both packages read config/cipi.php; publish the agent's copy with --tag=cipi-agent-config.
| Command | Description |
|---|---|
cipi:status |
Agent configuration, release and commit, feature state, cipi.yml validity, database connectivity |
cipi:deploy-key |
Print the SSH deploy key of the app |
cipi:mcp [--json] |
MCP endpoint, tools, resources and client configuration snippets |
cipi:generate-token {mcp|health|anonymize} |
Generate a token and write it to .env |
cipi:service {mcp|health|anonymize} --enable|--disable |
Toggle a feature in .env |
cipi:yml validate [--file=] [--app=] [--json] [--strict] |
Validate cipi.yml with the server's rules |
cipi:yml show [--json] |
Normalized document with defaults applied |
cipi:yml generate [--write] [--force] |
Infer a cipi.yml from the application |
cipi:yml example [--write] |
Commented template in the app's namespace |
cipi:yml schema [--write] |
JSON Schema for editors |
cipi:init-anonymize [--scan] [--path=] [--stdout] [--force] |
Create anonymization.json from the example or from the live schema |
cipi:anonymize-audit [config] [--json] |
Check the config against the database |
cipi:anonymize [config] [output] [--gzip] [--check] [--transformations] |
Create an anonymized dump |
- Token isolation — every feature has its own Bearer token (
CIPI_WEBHOOK_TOKEN,CIPI_HEALTH_TOKEN,CIPI_MCP_TOKEN,CIPI_ANONYMIZER_TOKEN); compromising one grants nothing else - Feature gating — disabled features return 404
- Webhook verification — provider HMAC signatures or secret headers, compared in constant time
- Deploy claims — what the agent writes about a deploy is recorded by Cipi as a claim, never as a fact
- MCP — blocked commands and statements, redaction, Origin validation, row caps,
isErrorfor failures, read-only database mode available - Anonymizer — config outside the repository, passwords via environment, original dump deleted right after processing,
0600outputs, signed time-limited download links, downloads confined tostorage/cipi
2.0 is backward compatible on the wire — existing MCP clients, webhooks, health monitors and the anonymizer API keep working. Changes worth knowing:
passwordtransformation now sets every password to the hash ofoptions.password(defaultpassword) instead of re-hashing the stored hash. The old behaviour produced unusable passwords; setoptions.passwordif you relied on a specific value.- Health no longer reports
degradedwhen deploy metadata is unavailable or the last deploy failed; the information is inchecks.deploy. - Deploy trigger file gained
source,actor,ip,ref,request_id(read by Cipi's audit ledger); the 1.x keys are still written. db_queryadditionally blocksALTER,RENAME,CREATE …, administrative statements and multi-statement queries;CIPI_MCP_DB_WRITE=falsemakes it read-only.artisantool additionally blocksdb:wipe,migrate:fresh,migrate:reset,pail,horizon:*.- Publishing —
vendor:publish --tag=cipi-agent-configpublishes only this package's config (thecipi-configtag is shared withcipi/sdkand still works). - Anonymizer config —
truncateand the object form of transformations are new; existing files keep working. The PostgreSQL dump format changed fromcustomtoplainso that it can actually be transformed.
composer install
composer testThe suite runs on Orchestra Testbench with an in-memory SQLite database and a fake app home; the cipi.yml validator is additionally checked against the server's Python validator on a corpus of valid and invalid files.
MIT — see LICENSE.
Built with care by Andrea Pollastri for the Cipi community.