Docs
crossfyre mcp
An MCP server built into the crossfyre CLI. An AI agent's client, such as Cursor
or VS Code, starts it and talks to it over stdio. It gives the agent the engines on your
machine, a Crossfyre Workbench open beside it and, with an MCP key, one Crossfyre workspace,
and each of them is held to a scope that a person writes.
What it serves
It is in crossfyre 0.1.38 and later. One server process serves up to three backends, and the agent sees one list of tools:
- The engines on this machine. toolmark fingerprints a service, plessor tests endpoints for injection, and one tool sends a single HTTP request. Traffic goes only to destinations in a scope file you write. No account is needed.
- A Workbench beside it. With Agent access on, the agent reads the open project's captured traffic and sends through it, held to both the scope file and the project's scope.
- A Cloud workspace. With an MCP key from the dashboard, the agent reads one workspace's scans, findings, assets and nodes, and with a launch key it starts scans on the workspace's nodes, held to the workspace's authorised scope list and, where the workspace asks for it, to your approval.
Connect a client
The local tools need toolmark and plessor installed and their daemons running. A Cloud key works without them.
crossfyre extension install toolmark crossfyre extension install plessor crossfyre extension start toolmark crossfyre extension start plessor
Then add the server to the client. In Cursor, in ~/.cursor/mcp.json or a
project's .cursor/mcp.json:
{ "mcpServers": { "crossfyre": { "command": "crossfyre", "args": ["mcp"] } } } In VS Code, in .vscode/mcp.json:
{ "servers": { "crossfyre": { "type": "stdio", "command": "crossfyre", "args": ["mcp"] } } } Other clients take the same command, crossfyre, with the argument mcp. If the engine daemons need CFX_DAEMON_TOKEN, put it in the
server's environment in your user-level client config, not in a project file you commit.
The engines on this machine
The scope file
The server reads one file when it starts and never writes it: <data-dir>/mcp/scope.toml, which on Linux is ~/.config/crossfyre/mcp/scope.toml. The server prints the path when it starts,
and --scope-file points it at another one.
# Written by a person. crossfyre mcp reads this file when it starts and never writes it. entries = ["app.example.com", "api.example.com:8443", "*.staging.example.com", "10.20.0.0/24"] allow_writes = false # test_writes, and non-GET/HEAD/OPTIONS methods in send_http_request allow_private = false # names that resolve to private, loopback or link-local addresses max_concurrent_scans = 3 # 1..=10 max_scan_starts_per_hour = 20 # 1..=200 requests_per_second_per_host = 2 # 1..=20, for send_http_request and fingerprint
entries- Where traffic may go: a host,
host:port,*.domainfor the names under a domain but not the domain itself, an address, or a range. allow_writes- Lets a request use a method other than GET, HEAD and OPTIONS, and an injection test reach PUT, PATCH and DELETE endpoints. Each write still asks you first.
allow_private- Lets a name in
entriesreach a private, loopback or link-local address. An address you list yourself is always allowed. - The three numbers
- Scans at once and scan starts an hour, counted across both injection tools, and the pace per host for single requests and fingerprinting.
The server refuses to start if an entry does not parse, a key is unknown or a number is out of range, or, on Linux and macOS, if the file or its directory belongs to another user or can be written by others. A missing file, or one with no entries, leaves every tool that sends traffic refusing. Changes take effect when the server restarts.
Keep the file outside the project the agent works in, and deny the client edits to it. It limits what these tools send. It does not limit an agent that can also run a shell or write files, which can edit the file or run the engines directly; only the client's own permission settings limit that.
Tools
| Tool | What it does | Sends traffic |
|---|---|---|
status | Version, the loaded scope and its hash, which engine daemons are listening, the limits and recent refusals. | No |
get_scope | The scope file's entries and settings, or how to write one. | No |
check_scope | Whether URLs, hosts, host:port or ranges are in scope, and which entry admits them. | No |
fingerprint | toolmark on one URL: technologies, CVE leads by version, title and server. | Yes |
test_injection | plessor's injection pass on one URL, or up to 100 endpoints on one origin. Returns a scan handle. | Yes |
send_http_request | One request, with redirects followed only when asked. | Yes |
list_scans | Scans, newest first. | No |
get_scan | One scan's status, progress, counts and notes. | No |
list_findings | Findings, with a cursor that also follows a running scan. | No |
get_finding | One finding with up to 32 KB of evidence. | No |
cancel_scan | Stops a running scan. | No |
Confirmations
The first time a tool would send traffic, the server asks you, through the client, to confirm
that you are authorised to test everything in the scope file. It asks once per server
process, and a no holds until the server restarts. A write needs allow_writes and
its own yes: every injection test with writes on, and the first request with a write method to
each host and port. A client that cannot show the question gets the first check from the file
alone, and is refused every write.
test_injection tests POST endpoints without writes on, as plessor always has,
since POST is where most injection bugs are. Leave POST endpoints out of the list if the target
cannot take that.
A Workbench beside it
When a Crossfyre Workbench is open on the same machine with Agent access on, seven workbench_* tools are added: the project's captured history, one exchange, what is
selected and the Repeater draft, the site map, the project's scope, a replay of a captured
request with named edits, and an injection test of a captured request run as its session. A
replay is recorded in the project as a new exchange marked AG.
Agent access is off until you turn it on from the Agents chip in the Workbench's status bar, and turning it off closes every connection. Both scopes have to admit a send: the scope file first, then the open project's scope, and a project with no scope written leaves the file to decide. The bridge works on Linux and macOS.
Agent access is in the Workbench from 0.1.2. The Workbench published now is 0.1.1, which does not have it yet.
A replay goes out with the cookies and headers that were captured, so it acts as the captured
user. A GET counts as a read, so one with side effects, such as /logout, is sent
without asking.
A Cloud workspace
With an MCP key, the same server also reaches one workspace on crossfyre.io. Without a key nothing changes: the tool list and the local log are as above.
Keys
Make a key in the dashboard under Settings, then Agent access. A key starts cfm_, belongs to you, works in one workspace and expires after 7, 30 or 90 days.
It is shown once. It is one of two kinds:
- Read
- Reads the workspace's scans and their results, its verdicts, assets and nodes, and its scope. It starts nothing.
- Launch
- Also starts and stops scans on the workspace's nodes. The form shows the workspace's authorised scope list in full and asks you to confirm that you are authorised to test everything on it. The key launches only against the entries you confirmed, for as long as they stay on the list. An entry added later reaches the key only after you confirm the key again, from Review scope beside it. A workspace with an empty list cannot have a launch key.
A launch key starts nothing once your role in the workspace no longer lets you start scans,
and any key stops working when you leave its workspace. An MCP key is not the account key crossfyre login uses, and neither works in place of the other. You can hold 25
keys that still work.
Logging in
On the machine the agent runs on:
crossfyre mcp cloud login crossfyre mcp cloud status
login asks for the key without showing it, or reads one line from stdin, checks
it with crossfyre.io and saves it only if it is accepted, to cloud.toml beside the
scope file, readable only by you. The key is never taken from the command line. status shows what crossfyre.io says about the saved key. The server reads the file
when it starts, so restart it in your client after logging in. --no-cloud serves
without the Cloud tools.
With no file, the server reads the key from CROSSFYRE_MCP_CLOUD_KEY instead and
sends it only to crossfyre.io. Prefer the file: a key in a project's MCP config ends up in its
repository sooner or later. Either way the key goes nowhere but the Authorization header of
requests to crossfyre.io, and the agent never sees more of it than cfm_ and the
next 8 characters.
What the agent can do
A read key adds four tools and lets seven of the local ones take Cloud ids as well: 15 in all, not counting the Workbench's. A launch key adds six more, one per kind of scan: 21.
| Tool | With a Cloud key |
|---|---|
whoami | The key's prefix, kind and expiry, its workspace, the plan's limits, the approval policy and the scope counts. |
list_nodes, list_assets, get_asset | The workspace's nodes, never their addresses, and its assets. |
get_scope, check_scope | Also the workspace's entries, the ones a launch may target, and whether a launch at a target would be admitted. |
list_scans, get_scan | Also Cloud scans, with their progress, and launches waiting for approval. |
list_findings, get_finding | Also a Cloud scan's results and the workspace's verdicts. |
cancel_scan | Also stops a Cloud scan the key's owner started, with a launch key, or withdraws a launch still waiting for approval. |
With a launch key:
| Tool | Starts |
|---|---|
start_recon | Subdomain enumeration under one domain, passive unless the agent asks for the active pass. |
start_port_scan | Up to 20 hosts, addresses or ranges, on the top 100 or top 1,000 ports, the common web ports, or a list of up to 2,000. |
start_crawl | One URL, kept to its host. |
start_content_discovery | One URL, with a wordlist. |
start_vuln_scan | Up to 20 URLs. |
start_authz_scan | One origin, tested as the roles the agent names and as anonymous. Needs a plan with authorization testing. |
Everything about a launch beyond its targets, its nodes and those few options is fixed by the
platform, and no option lets an agent turn on write methods. What comes back is shaped before
the agent sees it: anything a scanned system or an engine wrote is under target_data, marked untrusted, with secrets masked, and names people in the
workspace wrote are under workspace_text.
What it cannot do
- Add to or change the authorised scope list, or launch beyond the entries you confirmed for the key.
- Make keys, confirm a key's scope again, or change when a launch waits for approval.
- See a node's address.
- Stop a scan someone else started.
- Approve a launch when you have a phone registered. Only the phone can.
Scope
crossfyre.io admits a launch only against the entries you confirmed for the key that are still
on the workspace's list, and judges it as it judges a launch from the dashboard: a port scan
needs its host written without a port, active recon needs *.example.com, and so on,
as Authorised scope sets out. A workspace whose list is empty refuses
every launch, with a message telling you to add the destinations you are allowed to test.
When the scope file has entries, the server also holds every Cloud launch to them before it sends anything, so the file can narrow what a key may launch but never widen it. A missing or empty file does not narrow Cloud launches.
Limits on a key
A key may ask for 10 launches an hour, refused ones included, and have 5 scans running at once. It may make 120 calls a minute. A launch is also held to your plan's limits, like any scan started from the dashboard.
Approval
The first launch in a server process asks you, through the client, to confirm that you are authorised to test everything the key may launch against, with the list shown. In a workspace that holds every launch for approval, the approval takes the place of that question.
Each workspace also decides when an agent's launch waits for a person, under Settings, then Agent access, then When an agent's launch waits. Its owner, or its team's owner or leader, sets it:
- Only launches across nodes, or that can write
- The default. A launch that would run on more than one node waits, but only when you have a phone registered to approve it; with no phone it starts at once. No launch an agent can ask for writes today.
- Every launch
- Every launch waits.
A launch that waits is yours to approve, as the key's owner:
- With a phone registered, only the phone can approve. You register one by turning on Approvals in Crossfyre Mobile's Settings and confirming it once in a browser where you are signed in. The phone shows the workspace, the action, the targets, the scope entries that allow them, the nodes, and the key, client and address the request came from, and signs your answer with a key kept in its keystore. The dashboard and the agent's client can still deny.
- With no phone registered, you can approve in the dashboard, under Settings, then Agent access, or on the launch's approval page, or in the agent's client, which sends you to that page or shows you what would start in a form. crossfyre.io records that a decision came from the agent's client, since a client can be set to answer on its own. A workspace can turn this off, so that only a phone approves.
- With no phone, in a workspace that has turned that off, the launch is refused and nothing starts.
A launch waits at most 10 minutes. crossfyre.io starts the request it stored, so nothing the agent sends after asking changes what you approve, and it checks the scope, the nodes and your role again before it starts. At most 3 launches can wait for you at once, and you can be asked at most 30 times an hour.
What is recorded
On your machine, every tool call writes a line to audit.jsonl beside the scope
file, refusals included, with secrets in the arguments masked. With a Cloud key each line also
names the backend, the key's prefix, the workspace, the approval if there was one, and
crossfyre.io's id for the call.
crossfyre.io records the calls made with a key against it and keeps them for 90 days, under the id the local line carries, and refuses a launch, a cancel or an approval it cannot record. What an agent changes is also on the workspace's Activity page, under the MCP channel and with the key that did it: the scans it started and the launches the scope list refused. Approvals asked for and how each was decided, and keys made, confirmed again and revoked, are there too.
Revoking a key
- In the dashboard: Settings, then Agent access, then Revoke beside the key. An agent using it stops on its next call.
- On the agent's machine:
crossfyre mcp cloud logout --revokerevokes the key on crossfyre.io, then deletes the file. Without--revokeit only deletes the file, and the key keeps working anywhere else it is saved. - Changing your password revokes every MCP key you hold.
A key also stops when it expires or when you leave its workspace. A revoked key cannot be restored; make a new one.
What the checks do not cover
- Requests an engine makes on its own once a Cloud scan runs, such as recon's active pass, takeover probes, redirects and the vulnerability engine's follow-ups, are fenced only where that engine fences them. A Cloud crawl is kept to its target's host.
- On this machine the engines run with redirects off and each name pinned to the address the
server checked, and
send_http_requestfollows a redirect only when asked, checking every hop. - If plessor has an OAST endpoint configured, blind payloads ask the target to call it, and that host does not have to be in the scope file.
- A client can be set to answer confirmations on its own. For the local tools the scope file is the gate, and for Cloud launches the workspace's list and your phone.
- Redaction matches names and known formats, not meaning, so a secret with an ordinary name in an ordinary format is not masked.
- Crossfyre Mobile asks for waiting launches only while it is open. The approval page works whenever you are signed in.
Every option, refusal code and audit field is in MCP.md in the toolchain's repository.