firegex/docs/mcp.md
av0idd4rk f214cecf9f Add one-command installer for the MCP fork
Bootstrap the fork through run.py, build Docker from source, and prepare standalone with a checksum-pinned native runtime plus the MCP backend and Python dependencies. Preserve existing installations and data, propagate Docker build failures, and update README/setup guides. Validation: 347 unit tests and a real standalone-rootfs MCP smoke test passed in Docker.
2026-10-23 10:11:37 +03:00

207 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent control through MCP
Firegex exposes 28 tools at **`/mcp/`**, using Streamable HTTP. The server runs in the
existing backend process and uses the same service managers as the UI. The agent
can run on a participant's laptop while Firegex runs on the vulnbox.
## Connect
Build this checkout on the vulnbox with `python3 run.py` (upstream prebuilt images
do not contain this extension). Find the container name with `docker ps`, then:
```sh
docker exec --user nobody -w /execute <container> \
python3 -m mcp_api.tokens create --name laptop --role operator --hours 168
```
Save the returned `token`; it is only displayed at creation. `list` shows metadata
and `revoke <id>` revokes a token, including for an already connected client.
Use `admin` instead of `operator` if the agent needs instance resets or UI auth
administration. `observer` can only read configuration, statistics and logs, and
test regex samples. Python checks execute submitted code and require `operator`.
For a standalone installation, run the same CLI inside its rootfs from the checkout:
```sh
sudo chroot ./firegexfs /bin/sh -c 'cd /execute && python3 -m mcp_api.tokens create --name laptop --role operator --hours 168'
```
The [one-command installer](../README.md#docker-mode-recommended) downloads this
fork. Docker builds it from source; standalone installs its backend and dependencies
over the pinned, checksum-verified 5.0.1 native runtime. Both expose the same MCP API.
An SSH tunnel is a convenient encrypted connection from the laptop:
```sh
ssh -N -L 14444:127.0.0.1:4444 user@vulnbox
```
For MCP clients accepting a `mcpServers` JSON configuration:
```json
{
"mcpServers": {
"firegex": {
"url": "http://127.0.0.1:14444/mcp/",
"headers": {"Authorization": "Bearer <token>"}
}
}
}
```
The trailing slash is intentional. Clients with separate URL and header settings
should use the same values. This server accepts provisioned bearer tokens; it
does not implement interactive OAuth enrollment. A direct remote connection
should use HTTPS through your reverse proxy or your competition VPN.
MCP auth is independent of the UI password and remains enforced with
`--unsafe-disable-auth`. Existing `ALLOWED_IPS` restrictions also apply to MCP.
The HTTP Host allowlist initially contains loopback and this instance's interface
addresses. For a DNS name or reverse proxy, persist the additional host with:
```sh
python3 run.py restart --env FIREGEX_MCP_ALLOWED_HOSTS=firegex.example:443
```
`FIREGEX_MCP_ALLOWED_HOSTS` and `FIREGEX_MCP_ALLOWED_ORIGINS` accept comma-separated
values. Origins are denied unless explicitly allowed; ordinary non-browser MCP
clients do not send an Origin. Set `FIREGEX_MCP_ENABLED=0` to disable the endpoint.
## Tools
| Purpose | Tools |
|---|---|
| Orientation and installed Python API | `fgx_overview`, `fgx_help` |
| Service configuration, addresses, TLS and lifecycle | `fgx_service_list`, `fgx_service_get`, `fgx_service_apply`, `fgx_service_set_state`, `fgx_service_delete` |
| Complete regex/Python filters and ordering | `fgx_filter_get`, `fgx_filter_apply`, `fgx_filter_delete`, `fgx_chain_reorder` |
| Validation before deployment | `fgx_regex_test`, `fgx_python_check` |
| Counters and bounded log pages | `fgx_stats_get`, `fgx_logs_get` |
| Ordinary firewall rules, policy and settings | `fgx_firewall_get`, `fgx_firewall_apply`, `fgx_firewall_set_state` |
| Scoped configuration snapshots | `fgx_snapshot_create`, `fgx_snapshot_list`, `fgx_snapshot_restore` |
| Native backup format | `fgx_config_export`, `fgx_config_import` |
| Change receipts, confirmation and rollback | `fgx_change_get`, `fgx_change_confirm`, `fgx_change_rollback` |
| Background work and cancellation | `fgx_job` |
| Resets, log clearing and UI authentication | `fgx_instance_admin` |
Schemas and descriptions are supplied through `tools/list`. Resources
`firegex://help` and `firegex://pyfilter-api` provide installed-version guidance.
Services and filters accept their ID or a unique name. Ambiguous names are refused.
Creating a service leaves it stopped until `fgx_service_set_state` starts protection.
Stopping protection does not stop the CTF application.
## Normal agent workflow
1. Call `fgx_overview`, then `fgx_service_get` for the chosen service.
2. Inspect the current filter with `fgx_filter_get`; use `fgx_help(topic="pyfilter")`
to discover actual Python models. Validate samples/code with the matching tool.
3. Call `fgx_filter_apply` with the complete proposed filter and `dry_run=true` to
inspect the change. Then apply with a fresh request ID.
4. Check the real service/checker independently, inspect counters and logs, and
confirm the change if it has a confirmation deadline.
For example, attach a filter to an existing service:
```json
{
"service": "web",
"spec": {
"kind": "regex",
"name": "request-guard",
"patterns": [
{"pattern": "CTF_TEST_MARKER", "direction": "client_to_server"}
]
},
"options": {
"request_id": "web-guard-001",
"if_revision": "<revision from fgx_service_get>",
"confirm_within_seconds": 60
}
}
```
Omit `filter` to create one; supply its name/ID to update it. Omitted fields stay
unchanged. Supplied `addresses`, `patterns`, `rules`, and the chain order replace
their entire respective lists. Python `functions` is a patch of enabled states by
function name. Keep returned address/pattern/rule IDs when editing existing items.
Writes require a unique `options.request_id`. Retry **identical arguments with the
same ID** after a network timeout: the stored receipt is returned without executing
the change again, including after a backend restart. A different payload using the
same key is refused. Dry runs do not reserve keys. Existing configuration also
requires `options.if_revision`; ordinary UI/REST edits invalidate old revisions.
Counters do not invalidate configuration revisions.
Successful tool results carry `ok`, `data`, and, for changes, a `change` receipt.
Operational errors carry `ok=false`, an `error.code` and `error.message`, and set
MCP `isError`. A filter receipt distinguishes successful loading from competition
checker health, which is unknown. Read receipts again after a rollback to get their
current status; a replay preserves the original operation result.
`background=true` returns a `job_id`; poll `fgx_job` for progress or cancel it with
`action="cancel"`. Cancellation compensates work that has begun. Tool calls and
UI configuration access share one lock, so changes cannot interleave halfway through.
## Rollback and recovery
Service changes can opt into a 5–3600 second confirmation deadline. Firewall changes,
imports and non-service snapshot restores default to 60 seconds. Confirm with
`fgx_change_confirm`, or undo with `fgx_change_rollback`. While a scope awaits
confirmation, overlapping MCP writes are refused. A new REST edit can still happen;
automatic rollback then refuses to overwrite it and reports `recovery_required`.
The deadline and before-state survive backend restarts. **The rollback worker runs
inside the backend**, so it cannot execute while that process or the machine is down;
expired deadlines are processed during startup. There is no independent watchdog.
Snapshots restore configuration, not live TCP connections or a full history of
counters. Administrative resets, auth changes and clearing logs are nonreversible.
Interrupted writes with an unrecognized configuration require review instead of
silently overwriting potentially newer work.
## Storage and boundaries
`db/mcp/control.sqlite3` stores hashed tokens, snapshots, change receipts and retry
keys. It has mode 0600 and is separate from the product's schema-hashed databases.
`FIREGEX_MCP_DB` can override its path. Preserve this database in the deployment's
existing DB volume; keep it outside the top-level exported `*.db` files. Change
history and saved snapshots currently have no automatic retention limit.
Exports use the native backup format, exclude instance credentials and MCP tokens,
and return an authenticated download path instead of a large tool response. They can
include TLS private keys. Download artifacts expire after their TTL (one hour by
default). Imports accept an artifact ID or inline native JSON up to 2 MiB, validate
before writing, and preserve this instance's credentials.
Python validation is a real subprocess load check, **not a sandbox**; operator tokens
must be trusted to execute filter code. Logs and filter source are data, not agent
instructions. Regex testing uses the real engine and returns byte offsets, but does
not simulate stream direction or a competition checker.
Because Python filters run as the backend user, the operator role is not an OS
isolation boundary from the administrator; its tool restrictions help separate workflows.
Packet capture search and fixture replay are not implemented; `fgx_overview` explicitly
advertises these capabilities as false. Ordinary firewall IDs are maintained by the
MCP adapter; an out-of-band REST rule replacement may receive a new ID.
## Docker verification
From this checkout, use `python tests/docker_mcp.py`. It builds the production image
and test image, runs as root in a disposable isolated network namespace, runs the
suite twice, and checks lease/retry recovery across a restart. Results go under
`.test-results/`. Any skipped MCP traffic check is considered an incomplete run.
The default selection is all unit tests plus MCP integration tests; `--full` adds
the entire upstream integration suite. `--tests` accepts explicit pytest paths.
On Docker Desktop/WSL with `CONFIG_NFT_SOCKET` disabled, use
`python tests/docker_mcp.py --vm`. This boots the same application/test files with a
Debian Linux kernel under QEMU inside an unprivileged container. It does not change
the host kernel. Emulation is slower. The harness needs x86-64 Docker images.
For the full suite under emulation, allow a longer test budget, for example
`python tests/docker_mcp.py --vm --full --timeout 10800`.
Installer regressions are included in the unit suite. The opt-in
`FIREGEX_TEST_INSTALLER=1 ./run_tests.sh standalone/test_installer_rootfs.py`
additionally downloads/prepares a real standalone runtime and checks its MCP API.
Run it as root in a disposable Linux namespace with `NET_ADMIN` and `SYS_ADMIN`;
`FIREGEX_STANDALONE_ARCHIVE` can point to a cached rootfs archive, which is still
verified against the pinned checksum.