@alex-engvall/laravel-debug-mcp
v0.1.0
Published
Interactive installer and production-safe MCP server for debugging Laravel apps via a hardened SSH diagnostic runner.
Readme
laravel-debug-mcp
An installable CLI + production-safe MCP server that lets Codex CLI run common Laravel diagnostics through a hardened SSH remote runner without granting interactive shell access.
Architecture
- Codex CLI ⇄ local MCP server over stdio
- local MCP server ⇄ SSH ⇄
/usr/local/bin/laravel-diag laravel-diagexecutes a strict allowlist of diagnostics and returns JSON- SSH access can be restricted with an
authorized_keysforced command
Quick start
The recommended setup path is the interactive installer:
npx @alex-engvall/laravel-debug-mcp initThe wizard asks for the profile name, server host, SSH setup user, Laravel app path, diagnostic user, SSH key choice, Codex configuration preference, and mutation policy. It then:
- creates or reuses a dedicated local Ed25519 key;
- connects to the server over SSH;
- creates the diagnostic user when needed;
- installs
/usr/local/bin/laravel-diag; - writes
/etc/laravel-diag.envasroot:<diag-user>with mode0640; - installs the public key in
~<diag-user>/.ssh/authorized_keyswith a forced command; - saves a local profile under
~/.config/laravel-debug-mcp/profiles/; - runs
codex mcp addwhen Codex CLI is available; - runs
doctorsmoke checks.
You can also install globally:
npm install -g @alex-engvall/laravel-debug-mcp
laravel-debug-mcp initNon-interactive / CI setup
Use --config --yes for repeatable setup from CI/CD or a checked-in non-secret config file:
laravel-debug-mcp init --config ./laravel-debug-mcp.prod.json --yesExample config:
{
"profile": "easytoday-prod",
"host": "app.easytoday.se",
"port": 22,
"setupUser": "root",
"diagUser": "codexdiag",
"appDir": "/home/easytoday/domains/app.easytoday.se/app",
"healthUrl": "http://127.0.0.1/up",
"enableMutations": false,
"codex": {
"configure": true,
"serverName": "laravelProdEasyToday"
}
}Keep SSH private keys in your CI secret store. Do not commit secrets to this repository.
CLI commands
laravel-debug-mcp init [--profile <name>] [--config <path>] [--yes] [--dry-run]
laravel-debug-mcp doctor --profile <name>
laravel-debug-mcp rotate-key --profile <name>rotate-key generates a new per-profile key, installs it with the same forced-command bootstrap, updates the local profile and Codex MCP entry, runs doctor, and removes the previous public key from authorized_keys after verification.
Doctor checks
After installation, run:
laravel-debug-mcp doctor --profile easytoday-proddoctor validates local prerequisites and performs remote smoke checks:
- Node.js version;
- SSH binary availability;
- private key existence and permissions;
- remote
sys.infoaction; - Laravel
healthaction; artisan.versionaction.
Local profile format
Profiles are written to:
~/.config/laravel-debug-mcp/profiles/<profile>.jsonA profile contains the host, port, diagnostic user, key path, remote command, mutation policy, output caps, timeout, and Codex server name. The MCP server still reads runtime configuration through LARAVEL_PROD_* environment variables, which makes Codex and CI integration straightforward.
Manual remote install
The full installer is preferred, but the legacy helper remains available:
sudo DIAG_USER=codexdiag scripts/remote/install-remote.shThen edit /etc/laravel-diag.env and configure SSH authorized keys manually. The full init flow does this automatically.
MCP server environment variables
When running the MCP server directly, provide at least:
LARAVEL_PROD_HOST=prod.example.com
LARAVEL_PROD_USER=codexdiag
LARAVEL_PROD_SSH_KEY=/home/alex/.ssh/laravel-debug-mcp/prod_ed25519Optional variables:
LARAVEL_PROD_SSH_PORT=22
LARAVEL_PROD_REMOTE_COMMAND=/usr/local/bin/laravel-diag
LARAVEL_PROD_TOOL_TIMEOUT_SEC=45
LARAVEL_PROD_MAX_OUTPUT_CHARS=200000
LARAVEL_PROD_ENABLE_MUTATIONS=0Available diagnostics
App / Laravel
healthartisan_versionartisan_aboutartisan_migrate_statusartisan_schedule_listartisan_queue_failedartisan_horizon_statusfile_listfile_readenv_read
Logs
logs_listlogs_taillogs_greplogs_last_error
System
sys_infosys_disksys_memorysys_topphp_versionphp_extensions
Laravel cache artifacts
cache_status
Database read-only
database_connectionsdatabase_schemadatabase_query(SELECT,SHOW,EXPLAIN, andDESCRIBEonly)
Break-glass mutations
Mutations are disabled by default and double-gated locally and remotely:
- local MCP config:
LARAVEL_PROD_ENABLE_MUTATIONS=1 - remote runner config:
LARAVEL_DIAG_ENABLE_MUTATIONS=1
Mutation tools:
artisan_optimize_clearartisan_config_cacheartisan_queue_restartartisan_queue_retryartisan_pulse_restart
SSH hardening
The installer writes authorized keys in this form:
command="/usr/local/bin/laravel-diag",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA...This makes OpenSSH run only the diagnostic runner for that key, even if the client asks for a shell or another command.
Development
npm install
npm run typecheck
npm run build
node dist/cli.js --helpVersioning
Current version: 0.1.0.
Keep these version values in sync before every release:
package.jsonpackage-lock.jsonsrc/cli.tssrc/index.ts
The release verifier checks the package name, public package status, semver format, GitHub release tag, prerelease/stable release type, CLI version, and MCP server version:
npm run release:verifyFor a release candidate, use a semver prerelease version such as 0.1.0 and publish the GitHub Release as a prerelease. For a stable release, use a plain semver version such as 0.0.1 and publish the GitHub Release as a stable release.
Release
Release checklist:
Update the package version and matching source versions.
Run local verification:
npm ci npm run release:verify npm run typecheck npm run build npm pack --dry-runCommit the release changes.
Create and publish a GitHub Release whose tag is exactly
v<package.json version>, for examplev0.1.0.
The CI workflow runs on pull requests and pushes to main. It installs dependencies, verifies release metadata, typechecks, builds, and checks the package contents with npm pack --dry-run.
Publishing
Publishing is handled by .github/workflows/npm-publish.yml when a GitHub Release is published.
The publish workflow:
- verifies that the release tag matches
v<package.json version>; - verifies that GitHub prereleases use semver prerelease versions;
- verifies that stable GitHub releases use stable semver versions;
- runs
npm run typecheck; - checks package contents with
npm pack --dry-run; - publishes to npm with provenance using
npm publish --access public --provenance.
GitHub prereleases are published to npm with the next dist-tag. Stable GitHub releases are published with the latest dist-tag.
The workflow expects npm trusted publishing/OIDC to be configured for @alex-engvall/laravel-debug-mcp because it uses id-token: write and does not read an npm token from repository secrets.
