Skip to content
Conduit

mcp server

firewatch

Firewatch is local telemetry for AI debugging. It captures what Laravel Nightwatch's sensors collect into a local SQLite file and lets an AI assistant query it over the Model Context Protocol.

claudiodekker/firewatchcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 21, 2026

CI Packagist Version

About Firewatch

Firewatch is local telemetry for AI debugging. It captures what Laravel Nightwatch's sensors collect into a local SQLite file and lets an AI assistant query it over the Model Context Protocol. There is no dashboard, and nothing leaves your machine.

For production monitoring, use Laravel Nightwatch, the hosted service whose sensors Firewatch is built on. Firewatch stays off in production, and an application that reports to Nightwatch there keeps doing so.

Quick start

composer require --dev claudiodekker/firewatch
claude mcp add firewatch -- php artisan firewatch:server

Use the app in local, then ask the assistant what happened. There is nothing to ask until the app has handled a request, command, job or task.

Ask your assistant

QuestionTool
What is wrong with this app right now?overview
Which routes are slowest?rank
Is there an N+1 anywhere?detect
What did that failing request do, query by query?execution
What did this user hit?actor
Did my change make it slower?compare
Is this job getting slower over the week?trend
Which queries run most often?query

Requirements

  • PHP 8.3 or newer
  • Laravel 12.41.1 or newer, or Laravel 13
  • laravel/nightwatch ^1.30.2
  • ext-sqlite3 with SQLite 3.41.0 or newer

Install

composer require --dev claudiodekker/firewatch is the whole install. There is no install command, migration, publish step or .env line. The provider is auto-discovered and registers Nightwatch itself.

Nightwatch arrives as a dependency of Firewatch, so it is dev-only unless your application requires laravel/nightwatch itself. An application that reports to the hosted service in production already does, and is unaffected. If your own laravel/nightwatch constraint excludes ^1.30.2, the install fails until you raise it.

Publishing the configuration is optional: php artisan vendor:publish --tag=firewatch-config.

Connect an assistant

Every client runs php artisan firewatch:server in the application's environment, with the project root as working directory. Where a client does not guarantee the working directory, use the absolute form: <php> /absolute/path/artisan firewatch:server.

ClientConfigurationNote
Claude Code.mcp.json or ~/.claude.json--scope project writes a committable .mcp.json
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonabsolute paths only; quit and reopen fully after editing
Cursor.cursor/mcp.json or ~/.cursor/mcp.jsonmcpServers with type: "stdio"; no documented cwd, so an absolute path
VS Code.vscode/mcp.jsontop-level servers, not mcpServers, with cwd
Codex~/.codex/config.toml, or .codex/config.toml in a trusted project[mcp_servers.firewatch] with command, args, cwd
Sail / Dockerthe client's own filecommand vendor/bin/sail, args artisan firewatch:server
Herd, Valet, local PHPthe client's own fileuse the project's PHP binary, so ext-sqlite3 and the SQLite version match the application

Claude Code uses the command from the quick start. Claude Desktop:

{"mcpServers": {"firewatch": {"command": "/absolute/path/to/php", "args": ["/absolute/path/to/project/artisan", "firewatch:server"]}}}

Cursor:

{"mcpServers": {"firewatch": {"type": "stdio", "command": "php", "args": ["/absolute/path/to/project/artisan", "firewatch:server"]}}}

VS Code:

{"servers": {"firewatch": {"type": "stdio", "command": "php", "args": ["artisan", "firewatch:server"], "cwd": "${workspaceFolder}"}}}

Codex:

[mcp_servers.firewatch]
command = "php"
args = ["artisan", "firewatch:server"]
cwd = "/absolute/path/to/project"

The server must share a kernel and filesystem with the application. A server on the host reading a bind-mounted store file from a container is unsupported. Restart the assistant session after composer update or .env changes. Queue workers, Octane and Horizon processes read configuration once at boot and need a restart after a configuration change. Run by hand in a terminal, the server waits for a protocol message on stdin (end with Ctrl-D). That is how to look for stray output before the first message.

Local development only

  • Firewatch captures only when APP_ENV is in FIREWATCH_ENVIRONMENTS (default local,testing). Elsewhere it registers nothing and Nightwatch is untouched.
  • composer install --no-dev leaves it absent.
  • Never list production or prod. If you do, firewatch:doctor flags it.
  • It vetoes every Nightwatch transmit, so no hosted reporting happens from a capture environment.

What is captured and stored

  • Every execution (request, command, job, scheduled task) is recorded in the capture environments. Nightwatch's sampling and filtering cost controls are overridden. In-code opt-outs (Nightwatch::ignore(), pause(), dontSample() and reject* callbacks) are honored.
  • Nothing is transmitted. Every Nightwatch transmit is cancelled.
  • Firewatch never redacts URL query strings, SQL, query bindings, log messages, context and extra, exception messages, the Laravel Context or outgoing-request query strings. For redaction, use Nightwatch's redact* callbacks, which always apply, and the opt-in capture.redact_payload_fields and capture.redact_headers, which are empty by default.
  • A request's payload is recorded only for a 500 response by default (capture.request_payload). Request headers are recorded for every request. Response headers and bodies are never recorded.
  • The store is storage/firewatch/firewatch-{environment}.sqlite (for example firewatch-local.sqlite), mode 0600 inside a 0700 directory that carries its own VCS ignore file. firewatch:server, firewatch:doctor and firewatch:clear work on the store of the environment they run in.
  • Do not commit, share or attach the store file.

Locally, the value that redaction would hide is usually the answer the assistant needs.

Tools

ToolAnswers
overviewWhat is wrong right now, across everything; by_deploy breaks the slowest groups down per deploy, which locally is the git commit
rankWhich groups are worst by a measure
detectNamed problem shapes with evidence
occurrencesIndividual records, filtered and ordered
executionOne request, command, job attempt or task in full
traceA trace's executions and its queued-job lineage
actorOne signed-in person's work
compareBefore against after, per group
trendA measure over equal time buckets
queryRead-only SQL over the store: one statement, 10 s limit, run in an isolated child process
describeSchema, store facts, deploys, units, examples
fingerprintThe group id of something read in source, including a throw by its class, file and line

Detectors: n-plus-one, database-bound, failing-routes, failing-jobs, queue-latency, failing-tasks, exception-clusters, error-logs, failing-http, cache, memory.

When an empty answer means blind, not clean

  • Console processes other than a test suite record no request records.
  • An outgoing HTTP call that received no response (timeout, connection failure) leaves no record.
  • A request's payload is stored only for a 500 response.
  • lazy_loads, hydrated_models, files_read and files_written are always 0, and mail and notification failed is always false. Zero does not mean none: use the n-plus-one detector.
  • Jobs on the sync connection have no attempt record.
  • Logs written straight to a named channel are not captured.
  • Work still running is absent. An execution is recorded when it finishes.
  • Anything the application opted out of (pause, ignore, reject callbacks, never-sample) and processes killed before flushing are absent.
  • Under Octane the request bootstrap stage is zero, so do not compare durations across Octane and PHP-FPM.
  • A location in a compiled Blade view names its template beside it, but the line is the compiled file's. After view:clear the template reads null.
  • Exceptions the application does not report are absent, as are vendor commands and framework cache keys on Nightwatch's default exclusion lists.
  • Mail sent by a notification is recorded as a notification, not as mail.
  • A memory peak is the whole process's peak since the sensor last reset it. Requests, scheduled tasks and job attempts reset it, but a command reports its whole process, so compare executions to each other, not to an absolute number.

Answers state these themselves, and clean means "none among what was captured". An empty or clean answer spells each one out. A rank, occurrences, detect or compare answer that lists what it found can name a blind spot by id instead, and describe holds every sentence.

Configuration

KeyEnvDefaultMeaning
enabledFIREWATCH_ENABLEDtruethe only on/off switch
environmentsFIREWATCH_ENVIRONMENTSlocal,testingenvironments where Firewatch captures
databaseFIREWATCH_DATABASEunsetstore path, not under public/; unset, storage/firewatch/firewatch-{environment}.sqlite
busy_timeoutFIREWATCH_BUSY_TIMEOUT300milliseconds a write waits for a lock, 0 to 5000
retention.ageFIREWATCH_RETENTION_AGE7dmaximum record age (30m, 12h, 7d, 2w)
retention.recordsFIREWATCH_RETENTION_RECORDS100000maximum record count
deployFIREWATCH_DEPLOYunsetdeploy label; unset, Nightwatch's own label, then the commit git HEAD points to
capture.logsFIREWATCH_CAPTURE_LOGStruecapture logs
capture.testsFIREWATCH_CAPTURE_TESTStruerecord each request and command a test runs as an execution
capture.request_payloadFIREWATCH_CAPTURE_REQUEST_PAYLOADtruepayload for 500 responses
capture.redact_payload_fieldsFIREWATCH_REDACT_PAYLOAD_FIELDSemptypayload fields to redact
capture.redact_headersFIREWATCH_REDACT_HEADERSemptyheaders to redact
budgetsnoneemptyperformance budgets

The environment variable beats the published file, which beats the default. The published file reads each variable, and a literal you write there replaces it. An invalid value falls back to its default and never stops capture. Issues are reported once per console process and by firewatch:doctor.

A specific budget beats a global one. Durations are in milliseconds and memory in MB:

'budgets' => [
    ['type' => 'request', 'methods' => ['POST'], 'path' => 'checkout/*', 'duration' => 800, 'memory' => 64],
    ['type' => 'scheduled-task', 'duration' => 5000],
],

Raise busy_timeout when the app and several workers write at once. Dropped batches are recorded, and answers and firewatch:doctor say how many records were lost. The failure file keeps its newest 100 lines, so once it is full the count reads "at least". Each application instance also writes its first store failure to the PHP error log. Two stores must not share a directory.

Retention and clearing

Records are kept for 7 days and 100,000 records, with a 512 MiB size backstop. The oldest go first, and only the capturing process prunes. Nothing clears automatically or from the assistant.

php artisan firewatch:clear clears everything. --type=<type> clears one record type, --drop drops and rebuilds the store, and --force skips the confirmation. Answers tell the assistant what was pruned or cleared, so a shortened history is never mistaken for a quiet one.

Commands and diagnostics

CommandOptionsPurpose
firewatch:server--list, --jsonthe stdio MCP server; --list prints the tools without a session, --json with it
firewatch:doctor--jsonchecks the install, configuration and store; --json for machines
firewatch:clear--type, --drop, --forceclears the store

firewatch:doctor prints [ok], [warn], [fail] or [info] per check, with a one-line fix for warnings and failures. It exits 1 only when a check fails and changes nothing. The command is registered only in a capture environment, so a missing command means Firewatch is off there.

SymptomFix
Command "firewatch:doctor" is not defined (or any firewatch: command)The environment is not in FIREWATCH_ENVIRONMENTS
The client shows the server as failedRun php artisan firewatch:doctor
The assistant sees no dataRun php artisan firewatch:doctor and read its store-activity and mode lines
Garbled or failed handshakeApplication providers print output at boot: run the launch command by hand and look for output before the first message
Behavior changed after upgradingRestart the assistant session

Compatibility

The verified Nightwatch line is 1.30 (any 1.30.x). A higher minor works, and answers say it is unverified. Firewatch also runs on a SQLite release that has the write-ahead-log reset bug. It takes a write lock that avoids the bug, and firewatch:doctor shows the release and suggests an upgrade. Linux, macOS and Windows are supported. On Windows firewatch:doctor shows file modes as "not applicable".

In a capture environment Firewatch takes over Nightwatch's registration, so hosted Nightwatch and Firewatch do not run in the same environment. In production Firewatch is absent and Nightwatch behaves as normal. For production monitoring, use Nightwatch itself.

Uninstall and upgrade

To uninstall, run composer remove --dev claudiodekker/firewatch. Then delete the store directory (storage/firewatch/ by default: the store, its lock file and its ignore file) and any client entry. Nightwatch is removed too unless the application requires it directly.

To upgrade, run composer update, then restart the assistant session. A release that changes the store schema rebuilds the store in place on the next write and history is not carried over, so there is nothing to migrate.

Contributing and testing

  • Run the suite with vendor/bin/pest.
  • The scenario tests drive the real Nightwatch sensors.
  • A pull request that changes behavior updates the README and the changelog.

Changelog and license

The changelog and the license live in CHANGELOG.md and LICENSE.md.

↑↓ navigate openesc close