misp-mcp
Connects MISP to MCP clients, enabling threat indicator lookups and submissions in plain language.
README
<p align="center"> <picture> <source media="(prefers-color-scheme: light)" srcset="assets/banner-light.svg"> <img src="assets/banner-dark.svg" alt="MISP MCP - Malware Information Sharing Platform" width="820"> </picture> </p>
<p align="center"> Ask about a threat indicator in plain language and get an answer from MISP. </p>
<p align="center"> <img src="https://img.shields.io/badge/version-1.4.0-informational" alt="version 1.4.0"> <img src="https://img.shields.io/badge/license-Apache--2.0-lightgrey" alt="license Apache-2.0"> <img src="https://img.shields.io/badge/python-3.10%2B-informational" alt="Python 3.10+"> <img src="https://img.shields.io/badge/tests-65%20passing-lightgrey" alt="65 tests passing"> </p>
misp-mcp connects MISP to any MCP client (Claude Desktop, Claude Code, Cursor, and others). You ask in plain language, the client calls MISP, you get the answer. No MISP UI, no REST calls by hand. Nineteen tools read from MISP - indicators, events, feeds, the galaxy / taxonomy / tag knowledge base, warninglist checks, and worker/job health - and two add indicators (single + bulk), all under your own MISP key.
Connect to the hosted server
The server runs at
https://misp.example.com/mcp. There is nothing to install; point your MCP client at it with your own MISP key. You must be on the your corporate network or VPN (the endpoint is not on the public internet).
1. Get your MISP key (one time)
- Open
https://misp.example.comand log in. - Top-right menu → My Profile → Auth Keys → Add authentication key.
- Comment it
misp-mcp <your-name>, then copy the key - MISP shows it once. A read-only key is enough for lookups.
Keep the key private - every query runs as you.
2. Connect your MCP client
This is a standard remote MCP server over streamable HTTP, so any MCP-capable client works - Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Continue, Zed, VS Code (Copilot/MCP), Goose, and others. Whatever the client, it needs the same three things:
- Transport: HTTP (streamable HTTP / remote MCP)
- URL:
https://misp.example.com/mcp - Two headers:
X-MISP-Key: YOUR_KEY_HEREandX-MISP-User: you@example.com
Generic config - most clients read a JSON block like this (the key names
vary slightly by client - mcpServers, servers, or mcp.servers; check your
client's docs, the values are identical):
{
"mcpServers": {
"misp": {
"type": "http",
"url": "https://misp.example.com/mcp",
"headers": {
"X-MISP-Key": "YOUR_KEY_HERE",
"X-MISP-User": "you@example.com"
}
}
}
}
<details> <summary><b>Per-client examples</b></summary>
Claude Code (one command in a terminal):
claude mcp add --transport http misp https://misp.example.com/mcp \
--scope user \
--header "X-MISP-Key: YOUR_KEY_HERE" \
--header "X-MISP-User: you@example.com"
Claude Desktop / Cursor / Windsurf - add the generic mcpServers block
above to the client's MCP JSON config, then fully restart the app.
VS Code (Copilot MCP) - in .vscode/mcp.json or user settings, under
"servers":
{
"servers": {
"misp": {
"type": "http",
"url": "https://misp.example.com/mcp",
"headers": {
"X-MISP-Key": "YOUR_KEY_HERE",
"X-MISP-User": "you@example.com"
}
}
}
}
Cline / Continue / Zed / Goose and other clients - use the same URL,
http transport, and the two headers, in whatever config format the client
uses. Anything that speaks remote MCP over HTTP and can send custom headers
will work; the two X-MISP-* headers are the only server-specific part.
Any client that cannot send custom HTTP headers is not supported (the key must
ride in X-MISP-Key).
</details>
3. Check it works
Reachability check (client-agnostic) - should print 401, which proves you can
reach the endpoint and that auth is required:
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://misp.example.com/mcp
If your client has a way to list MCP servers, confirm misp shows connected
(Claude Code: claude mcp list → misp ... ✓ Connected; other clients show it
in their MCP/tools panel).
Then ask your assistant these - if they answer from MISP, you're set:
| Ask this | You should get |
|---|---|
Is MISP healthy? |
reachable, MISP + server version |
Look up 102.130.113.9 in MISP. |
hits (Tor / DDoS), detection-flagged |
Look up 45.9.148.99 in MISP. |
"not seen in MISP" (not "safe") |
Triage these against MISP: 8.8.8.8, evil.com, <a hash> |
per-indicator verdicts |
Not working?
- 401 / timeout when asking → you're not on your network / VPN.
claude mcp listnot "Connected" → re-check the key and headers.- Tools missing, stale, or the assistant says "no such tool" → reconnect (below).
Reconnect / restart it
MCP clients cache the tool list when they connect. If the server stops
responding, shows the wrong tools, or was updated, the fix in every client is
the same: fully quit and reopen the app (not just the conversation or
window - an in-app /mcp reconnect or a new chat is often not enough). If tools
are still stale, remove the misp server from the config, save, reopen, add it
back, and reopen again.
New tools added on the server only appear after this full restart.
<details> <summary><b>Claude Code re-register</b></summary>
claude mcp remove misp
claude mcp add --transport http misp https://misp.example.com/mcp \
--scope user \
--header "X-MISP-Key: YOUR_KEY_HERE" \
--header "X-MISP-User: you@example.com"
Then quit Claude Code completely and reopen it. Check with claude mcp list →
misp ... ✓ Connected, and /mcp shows the current tools.
</details>
What you can ask
"Look up 102.130.113.9 in MISP."
"Triage these 30 IOCs from the report."
"What else showed up in the same event as evil[.]com?"
"Review the last 30 days of IOC submissions - who added what."
"Is MISP healthy? How many feeds are on?"
Behind the scenes the client calls a tool and gets structured JSON, e.g. for a lookup:
{
"ioc": "102.130.113.9",
"ioc_type": "ipv4",
"total_hits": 6,
"summary": {
"seen_in_misp": true,
"event_count": 6,
"detection_flagged": true,
"max_threat_level": "Medium",
"restricted_hits": 0
},
"hits": [
{ "event_id": "16989", "event_info": "Tor exit nodes feed",
"attribute_type": "ip-dst", "value": "102.130.113.9",
"to_ids": true, "restricted": false }
]
}
Tools
| Tool | What it does | |
|---|---|---|
misp_lookup_ioc |
read | Sightings of one IPv4/IPv6, domain, URL, or hash, with a verdict |
misp_lookup_iocs |
read | Triage many indicators in one call |
misp_correlate_ioc |
read | Other indicators in the same event, for pivoting |
misp_get_event |
read | One event: info, tags, attributes |
misp_search_events |
read | Search events by title, tag, or date |
misp_feed_stats |
read | How many feeds exist and which are on |
misp_instance_status |
read | Reachability + auth check; run first when a tool fails |
misp_review_submissions |
read | Audit recent submissions: what was added, by whom, what's detection-flagged |
misp_lookup_galaxy |
read | Threat actors, malware, tools, ATT&CK techniques by name or synonym |
misp_list_galaxies |
read | Galaxy types available on the instance |
misp_list_taxonomies |
read | Taxonomies (TLP, kill-chain, PAP) and whether each is enabled |
misp_get_taxonomy |
read | One taxonomy's tags and their meanings |
misp_search_tags |
read | Find tag definitions by name |
misp_get_object |
read | One MISP object (grouped attributes, e.g. a file object) |
misp_get_attribute |
read | One attribute by id, with its event |
misp_search_attributes |
read | Search attributes by type, category, tag, to_ids, or event (paginated) |
misp_check_warninglist |
read | Flag IOCs that hit known-good / noise lists (false-positive control) |
misp_worker_status |
read | Background worker / queue health (admin key) |
misp_jobs |
read | Recent background jobs + failures and why (admin key) |
misp_submit_ioc |
write | Add a new indicator (needs a write-capable key) |
misp_submit_iocs |
write | Bulk: validate + add many indicators (dry-run preview first) |
Paste indicators however you have them - defanged forms (1.2.3[.]4,
hxxp://evil[.]com) are cleaned up automatically. Private/reserved IPs are
rejected (not routable, can't identify an external threat).
Security
- Your key is the authorization. MISP checks it on every call and attributes the action to you. A read-only key cannot write; only write-capable keys (the security team) can add indicators.
- Guarded write path. Submissions are rate-limited, well-known / first-party
infrastructure can never be submitted, and the submitter is read from MISP
itself - not a value the caller sets (so
misp_review_submissionsshows who actually added each IOC). - MISP content is data, not instructions. Don't submit an indicator from a lookup without checking it yourself.
- Keys stay private. No shared key on the server; the key rides in a header over TLS. Logs never contain keys or IOC values.
Architecture
The shape is the same on any host: callers reach misp-mcp over HTTPS through a
TLS-terminating load balancer (or the process's own cert), and misp-mcp calls
your MISP. It holds no credential of its own - every request carries the
caller's own X-MISP-Key, which MISP validates and attributes to that user.
MCP client (any client, on an allowed network / VPN)
│
│ HTTPS + header X-MISP-Key: <your key>
▼
Load balancer TLS termination · ingress limited to allowed CIDRs
│ HTTP :8080 (private)
▼
misp-mcp container / VM, no stored secret
│ HTTPS
▼
your MISP instance
- TLS terminates at the load balancer; misp-mcp serves plain HTTP on
:8080behind it (or give the process its own cert, see DEPLOY.md). - Ingress is scoped to your caller networks; the endpoint is not public.
- Two common topologies: run misp-mcp standalone in front of a separate
MISP (the Terraform module below), or co-locate it beside an existing
MISP behind one load balancer with a
/mcp*path rule so MISP is untouched.
Host it (local, self-host, or cloud)
- Docker (fastest): run the published image against your own MISP.
Bind stays on localhost; front it with your own TLS proxy for remote use (the key is a bearer credential).docker run -d -p 127.0.0.1:8080:8080 \ -e MCP_TRANSPORT=http -e MCP_HOST=0.0.0.0 \ -e MISP_URL=https://misp.example.com \ -e MISP_MCP_ALLOW_INSECURE_BIND=true \ ghcr.io/indranilroy99/misp-mcp:latest # or: MISP_URL=https://misp.example.com docker compose up -d - Local (in your MCP client, against your own MISP): install and add the
misp-mcpbinary - see ONBOARDING.md. - Self-host for a team (HTTP server behind TLS, EC2/VM + systemd): DEPLOY.md.
- Cloud, any provider (AWS / GCP / Azure guidance): CLOUD.md.
- AWS, Terraform - ready modules behind an internal ALB with TLS, in two
flavors sharing one networking module: Fargate (serverless, no VM) or
EC2 (managed VM, SSM access): deploy/terraform/. Fill
in a
.tfvars,terraform apply, and it prints the/mcpendpoint.
Automated local setup (from source):
git clone https://github.com/indranilroy99/misp-mcp.git
cd misp-mcp
./install.sh
<details> <summary><b>All settings</b></summary>
| Setting | Mode | Default | Meaning |
|---|---|---|---|
MISP_URL |
both | required | MISP base URL |
MISP_API_KEY |
local | required | your key (local mode) |
MCP_TRANSPORT |
both | stdio |
stdio for local, http for hosted |
MCP_HOST |
hosted | 127.0.0.1 |
bind address |
MCP_PORT |
hosted | 8080 |
port |
MISP_VERIFY_TLS |
both | true |
set false only for a self-signed lab |
MISP_MCP_SHOW_RESTRICTED |
both | true |
false turns on server-side TLP hiding |
MISP_SUBMISSION_EVENT_ID |
both | required for writes | event that misp_submit_ioc writes to |
MISP_MCP_PROTECTED_DOMAINS |
both | empty | your own domains that can never be submitted |
MISP_MCP_SUBMIT_RATE |
both | 20 |
max submissions per key per minute |
MISP_MCP_TLS_CERT / MISP_MCP_TLS_KEY |
hosted | none | serve HTTPS directly |
MISP_MCP_ALLOW_INSECURE_BIND |
hosted | false |
allow a public plain-HTTP bind (TLS on a proxy) |
</details>
<details> <summary><b>Development</b></summary>
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest tests/ -q # 65 tests
misp_mcp/
server.py the 21 tools and the MCP server
client.py talks to the MISP REST API (read + write)
config.py reads settings from the environment
http_app.py hosted mode: header auth + web server
context.py carries your identity through one request
validators.py cleans, checks, and safelists indicators
Dependencies are pinned in pyproject.toml: mcp, httpx, pydantic,
uvicorn, starlette.
</details>
<details> <summary><b>Security reporting & license</b></summary>
Report vulnerabilities privately - see SECURITY.md. Licensed under Apache-2.0 (LICENSE). Contributions welcome - see CONTRIBUTING.md.
</details>
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.