MCP server
- Endpoint
https://malwagon.com/mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "lookup_hash",
"arguments": {
"sha256": "9c1f0b6d2ae74318bd50c7a9e6f34182"
"bb0d5e7c94a2f61038de7b5c0a91f2d4"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "..." }],
"structuredContent": {
"sha256": "9c1f0b6d...0a91f2d4",
"known": true,
"scans": [
{ "scan_id": 4127,
"verdict": "malicious",
"score": 88 }
]
}
}
}
What it is for
An analyst working with an AI client should not have to be the wire between the two. The MCP server exposes this platform's analysis as tools the client can call itself: it looks a hash up, searches an indicator across the corpus, queues a scan and reads the finished report, and it does that with the same identity, the same plan limits and the same privacy rules a person would have.
It is a second surface over the work the REST API already does, not a second platform. A scan submitted through a tool call is the same scan, in the same queue, on the same machines, and it produces the same report.
It is included with Team and Enterprise plans, and it authenticates with a bearer token like the REST API does. See pricing.
The tools
Five, each carrying the scope it costs. A token issued read can call the four that read; only a submit token can queue an analysis, and a client is told which it holds before it tries.
| Tool | Scope | What it answers |
|---|---|---|
| lookup_hash | read | Find analyses of a known SHA-256 digest. |
| get_report | read | The derived analysis report for one scan: verdict, capabilities, behaviour summary, observed operations and defanged indicators. |
| search_indicator | read | Find scans where an indicator was observed: an IP, a domain, a URL, a mutex, a registry key, a hash or a JA3/JA4 fingerprint. |
| poll_scan | read | The current status of one scan, for polling after submit_scan. |
| submit_scan | submit | Queue a new analysis of a target that can be named as text: a SHA-256 to look up, a URL to visit, a command to run, or a package to install. |
Connect a client
Two values do all the work: the endpoint and a bearer token. Where a client keeps them differs; what it sends does not.
2026-07-28 and no earlier one. That revision has no handshake and puts the protocol version and the client's capabilities in every request, so a client built for an earlier revision is refused on its first call with -32602 rather than connecting and behaving oddly. If your client has not adopted it yet, run the small stdio bridge below: it speaks your client's revision on one side and this one on the other, and it holds no policy of its own. The token, the plan and the scope are still decided here.{
"mcpServers": {
"malwagon": {
"url": "https://malwagon.com/mcp",
"headers": {
"Authorization": "Bearer mwg_a1b2c3d4_kZq7Xn2R9tLpWv0sYcB4hJdF6gMaE3uNbT1xQfV5rDo"
}
}
}
}
2026-07-28: Claude Desktop keeps a block like it in its own configuration file, and Claude Code registers the same server from the command line. Where the setting lives is the client's business and its documentation is the authority on that; the URL and the header are ours. Until the client adopts the revision, point it at the bridge instead and keep the same two values there.Server URL https://malwagon.com/mcp Authorization Bearer mwg_a1b2c3d4_kZq7Xn2R9tLpWv0sYcB4hJdF6gMaE3uNbT1xQfV5rDo
2026-07-28, and a local bridge cannot stand in for it the way it can for a desktop client.{
"mcpServers": {
"malwagon": {
"command": "python3",
"args": ["/path/to/mcp_bridge.py"],
"env": {
"MWG_MCP_URL": "https://malwagon.com/mcp",
"MWG_MCP_TOKEN": "mwg_a1b2c3d4_kZq7Xn2R9tLpWv0sYcB4hJdF6gMaE3uNbT1xQfV5rDo"
}
}
}
}
curl -sS -X POST "https://malwagon.com/mcp" \
-H "Authorization: Bearer $MWG_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/list" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list",
"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'
What a client sends
Revision 2026-07-28 has no handshake. Every request states its own protocol version and client capabilities, and the server answers each one on its own terms, so a dropped connection costs nothing to resume.
In params._meta
io.modelcontextprotocol/protocolVersionio.modelcontextprotocol/clientInfoio.modelcontextprotocol/clientCapabilities
The prefix is reserved by the specification, so these exact strings are the contract rather than a convention.
Mirrored into headers
MCP-Protocol-VersionMcp-MethodMcp-Name
They must agree with the body. A header that contradicts what it mirrors is refused rather than reconciled, because a proxy that rewrote one of them is a thing the server cannot see.
What a call passes through
Four gates, in this order, and each one is answered before the next is asked. A call that fails any of them never reaches the analysis pipeline.
- 01 Envelope One JSON-RPC request, the declared revision, and every mirrored header agreeing with the body it mirrors.
- 02 Token Resolved to an account on every call, never cached between them, so a revoked token stops at the next one.
- 03 Plan and scope The plan has to include this surface and the token's scope has to cover this tool. Both are read live.
- 04 Budget Reads and submissions carry separate allowances; a submission also spends a credit, and one that cannot be charged is refused rather than queued.
Error codes
Standard JSON-RPC codes, plus the two ranges the specification reserves. Nothing outside these is emitted, and each carries one meaning.
| Code | Meaning |
|---|---|
| -32700 | The body is not JSON. |
| -32600 | Valid JSON, but not a JSON-RPC request. |
| -32601 | No such method on this server. |
| -32602 | The params are wrong for the method named. |
| -32603 | The server failed while handling a valid call. |
| -32020 | A mirrored header disagrees with the body it mirrors. |
| -32021 | The call needs a client capability this client did not declare. |
| -32022 | The declared revision is not one this server speaks. |
| -31001 | No usable token, or a plan that does not include this surface. |
| -31002 | The token is valid and its scope does not cover this tool. |
What it will not do
It never carries a sample. No tool accepts file bytes and none returns them. A submission names its target as text, a digest, a URL, a command line or a package, and a report comes back as derived findings. The bytes stay on the hardware that ran them.
A read token cannot be talked into writing. Scope is checked per tool call against the token, not against the session, so a client that has been persuaded to try submit_scan with a read token is refused rather than asked to confirm.
It has no privileges of its own. The token belongs to an account, and every call is answered with that account's plan, its credits and its visibility. A tool call cannot read a scan the person holding the token could not open in the console.
It is rate limited like everything else. Reads and submissions carry their own budgets, and a spent budget comes back as a tool error inside a successful response rather than as a transport failure, because a client that cannot read the difference will retry the wrong one.