CyberSPF MCP server
Lets an MCP client — Claude Desktop, an editor, an agent framework — ask CyberSPF about an IP, a domain or an ASN, list what an account monitors, and pull the graded report card.
What it is, and what it deliberately is not
It runs on the customer's machine, over stdio, calling /api/v1/ over HTTPS
with their own API key.
That shape is the design, not a shortcut:
- No new inbound surface. The droplet's only ingress stays the Cloudflare tunnel. A hosted MCP endpoint would add a second one.
- No new authentication.
ClientAPIKeyAuthenticationandHasApiAccessalready gate v1. This is another client of them, not a new path in. - No new metering.
api_requests_per_dayalready counts these calls. - Nothing to operate. A process the client starts and stops.
A hosted HTTP server is worth building if customers ask for one. It is not worth building first.
Requires
A plan that includes API access, and for cyberspf_report_card, a plan that
includes the report card. Those are two separate entitlements and the server
reports them differently — a key that works for lookups and is refused for the
card is a plan limit, not a bad key.
Install
Nothing to install — uvx fetches and runs it:
uvx cyberspf-mcp
Or into an environment you manage:
pip install cyberspf-mcp
From a checkout, for development:
cd mcp_server
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
Configure
Create a key in CyberSPF under Settings → API keys, then point your client at
the server. For Claude Desktop, in claude_desktop_config.json:
{
"mcpServers": {
"cyberspf": {
"command": "uvx",
"args": ["cyberspf-mcp"],
"env": {
"CYBERSPF_API_KEY": "your key here"
}
}
}
}
That form exists because the alternative was two absolute paths. A client started
from a desktop launcher inherits no PATH and no working directory, and a relative
path there is the commonest reason a server appears in the client list and never
answers. If uvx itself is not on the launcher's PATH, give its full path.
From a checkout instead, point command at the venv's python and args at
server.py — both absolute, for the same reason.
Listing
server.json is the manifest for the official MCP Registry
(registry.modelcontextprotocol.io), published as com.cyberspf/mcp. The
registry namespaces by reverse DNS and verifies the domain, which is why the name
is the one domain we can prove.
A stdio server is listed there through a packages entry naming a package registry
and an identifier — so the PyPI package is a prerequisite for the listing, not
merely a convenience.
CYBERSPF_BASE_URL overrides the endpoint; it defaults to
https://cyberspf.com/api/v1. Useful for pointing at a staging host and for
nothing else.
The tools
| Tool | Answers |
|---|---|
cyberspf_lookup |
one IP, domain, CIDR or ASN — ordered sections of findings with severities |
cyberspf_list_monitors |
what this account watches, and when each was last checked |
cyberspf_report_card |
the graded assessment per monitored domain, with recommendations |
Three, not the eight the design sketch proposed. Two of those eight
(zone_search and anything over new-domain data) have no data behind them —
zone_domain holds zero rows since collection was paused — and the rest wrap
endpoints v1 does not expose yet. Three that work beats eight where five return
errors.
Following a result instead of stopping at it
cyberspf_lookup returns the API's own sections unflattened, on purpose. Values
that can themselves be looked up carry a type (ip, domain, asn, cidr),
so an agent can feed one back in:
look up
example.com→ take the addresses it resolves to → look each up → take the ASN announcing them → look that up
That chain is the point. Flattening the response into prose for the model would read more smoothly and would throw away the one field that makes the chain possible.
Failure messages
Every failure is phrased as something a person can act on, because an agent cannot read a stack trace and cannot retry its way out of a plan limit:
| Condition | What the tool says |
|---|---|
no CYBERSPF_API_KEY |
exits at startup, naming the setting — rather than appearing healthy and failing on first use |
| 401 | the key was rejected; check it has not been revoked |
| 403 | the key is valid, the plan does not include what was asked for |
| 429 | daily allowance used up, resets midnight UTC |
| timeout | 45s, and says a first lookup of a cold domain can be slow before concluding it is down |
A blank term is refused locally rather than sent, so it does not spend one of
the account's daily calls to be told it was blank.
Nothing here probes anything
Every tool reads what CyberSPF already stored. None of them causes a connection to the subject of the lookup, from the customer's machine or from ours — the report card in particular is built from stored data, which is what makes it safe for an agent to call in a loop. Outbound probing happens elsewhere in the product, from the dedicated prober, on its own schedule.
Metadata
Release files for cyberspf-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cyberspf_mcp-0.1.0.tar.gz | 9.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cyberspf_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.2 kB
Release files / cyberspf_mcp-0.1.0.tar.gz
| Download URL | cyberspf_mcp-0.1.0.tar.gz |
|---|---|
| Size | 9.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
666082a8fca2497ad057c772eaa10e6c49cdefce2c33683157f2e8977d6f634c
|
|
BLAKE2b-256 checksum How to use checksums |
830e8c8cfe63ddad544b097493acc3a7624933873405a5b4a2989e9a74db393d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.1
|
Release files / cyberspf_mcp-0.1.0-py3-none-any.whl
| Download URL | cyberspf_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3f09408d8c411644ccd6e1684810d954b761c24a96151d547c4c1a404e56a8ca
|
|
BLAKE2b-256 checksum How to use checksums |
573a7a3bc15cbe899963090e9dc36e5dac3a075de44b8df77ecc0d8a59463212
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.1
|