# 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 \ 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 ` 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 "} } } } ``` 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": "", "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.