threat-model-mcp
Serves any STRIDE threat model to MCP-capable LLMs, enabling natural language querying of threat registers and export to the Microsoft Threat Modeling Tool.
README
threat-model-mcp
Serve any STRIDE threat model to any MCP-capable LLM — Claude Code, Gemini CLI, a local Ollama / LM Studio client, or a custom agent — and export it to the Microsoft Threat Modeling Tool.
Point the server at your threat register. Then ask an agent "what are the critical threats?", "how do I mitigate the auth bypass?", or "which threats touch the database?" and it answers from your register instead of guessing.
pip install -e .
threat-model-mcp --data ./my-threat-model.json
The server ships with no threat data of its own. Your model is yours; nothing is bundled, inferred, or phoned home. A worked example is included so you have something to point at on day one.
Why this exists
Threat models live in spreadsheets that nobody reads. An LLM that can query the register — rather than being handed a 40-page PDF and asked to remember it — turns the model into something you consult during code review, incident response, or design.
And because the register is structured, the same data can be exported straight into the Microsoft Threat Modeling Tool, so it stays usable by people who don't work in a chat window.
Install
Requires Python >= 3.10. The only runtime dependency is the mcp SDK.
git clone <this-repo> threat-model-mcp
cd threat-model-mcp
pip install -e .
Verify:
python -m pytest # the full test suite
threat-model-mcp --data threat_model_mcp/examples/example-model.json --check
--check validates the dataset, prints a summary, and exits without serving. Use it in
CI to catch a malformed register before an agent ever sees it.
Point it at your data
The dataset is required — there is no implicit default, because silently serving somebody else's threat model would be worse than a startup failure.
threat-model-mcp --data ./my-model.json # explicit flag
THREAT_MODEL_PATH=./my-model.csv threat-model-mcp # or the environment
Both .json and .csv work. A CSV register — the natural output of a spreadsheet
review — is normalised on load, so there is no build step and no generated artifact to
keep in sync.
The smallest dataset that works:
{
"meta": { "name": "My Service" },
"threats": [
{
"id": "T-1",
"stride": "Spoofing/EoP",
"component": "Auth service",
"description": "Tokens are decoded but never verified",
"severity": "Critical",
"mitigation": "Verify the signature against the IdP JWKS"
}
]
}
Or the same thing as CSV:
ID,STRIDE,Element,Threat,Risk,Mitigation
T-1,Spoofing/EoP,Auth service,Tokens are decoded but never verified,Critical,Verify the signature against the IdP JWKS
Only id, stride, and one of title/description are required. See
docs/DATA-SCHEMA.md for the full contract, and
threat_model_mcp/examples/ for a complete worked model.
Your domain fields survive
Any column or key the schema doesn't recognise is preserved in the threat's extra
dict, searchable and returned by the tools. A medical-device team can carry
FDA_Objective, IEC62443_FR and Safety_14971; an appsec team carries OWASP and
CWE; nothing is hardcoded and nothing is dropped.
Boolean-ish values (yes/no/true/false) become real booleans, and
coverage_summary() counts them automatically:
"by_extra": { "handles_pii": 3 }
The server has never heard of handles_pii. It counted it because it is a flag.
Register it with an LLM
The server speaks MCP over stdio, so any compliant client can spawn it.
Claude Code
claude mcp add threat-model \
--env THREAT_MODEL_PATH=/abs/path/to/my-model.json \
-- python -m threat_model_mcp.server
Or drop a .mcp.json in your project (an example ships in this repo) and run claude
in that directory:
{
"mcpServers": {
"threat-model": {
"command": "python",
"args": ["-m", "threat_model_mcp.server"],
"env": { "THREAT_MODEL_PATH": "./my-model.json" }
}
}
}
Gemini CLI
Add the same block to ~/.gemini/settings.json (or a project .gemini/settings.json),
then run gemini.
Any other MCP client
{
"command": "python",
"args": ["-m", "threat_model_mcp.server", "--data", "/abs/path/to/my-model.json"]
}
The client performs the standard handshake, calls tools/list, and invokes any tool via
tools/call. The server's name and its instructions — the description the LLM reads to
decide when to reach for these tools — are generated from your dataset's own metadata,
including any caveats you attached to it.
Tool reference
| Tool | Description |
|---|---|
list_threats() |
Compact list of every threat (id, title, STRIDE, severity, component, status). |
get_threat(threat_id) |
Full record for one id, including its extra fields. Unknown id → {"error": ...}. |
threats_by_stride(category) |
Threats whose STRIDE set contains the category. Accepts full names or shorthand (EoP, DoS, I, ...). |
threats_by_severity(level) |
Threats at a severity level (case-insensitive). |
threats_by_component(component) |
Threats whose component matches a substring. |
search_threats(query) |
Free-text search across the core fields and every extra value. |
get_mitigations(threat_id) |
Mitigation, severity, priority, and the threat's whole extra bag. |
coverage_summary() |
Counts by STRIDE, severity, priority, group, status; the highest-severity ids; and by_extra flag counts. |
export(fmt) |
Serialise the register: json | csv | md | tm7. |
model_info() |
Your meta block: name, description, scope, method, standards, status, caveats. |
STRIDE is multi-valued. A threat can carry several categories, so it answers to each
of them, and the per-STRIDE counts in coverage_summary() sum to more than the total.
That is correct, not a bug.
Every tool is read-only. The server never writes to your dataset.
Microsoft Threat Modeling Tool export
export("tm7") emits a .tm7 model — the data-flow diagram plus every threat as a
threat instance — for the official Microsoft Threat Modeling Tool.
The diagram is data, not code. Declare it in your dataset's optional meta.dfd
block:
"dfd": {
"elements": [
{ "id": "EI1", "kind": "external", "label": "Browser client", "x": 40, "y": 40 },
{ "id": "P1", "kind": "process", "label": "P1 API gateway" },
{ "id": "DS1", "kind": "store", "label": "DS1 Notes database" }
],
"flows": [
{ "source": "EI1", "target": "P1", "label": "HTTPS request" },
{ "source": "P1", "target": "DS1", "label": "read/write" }
]
}
Coordinates are optional — anything without x/y is auto-laid-out on a grid, banded by
kind (externals on top, processes in the middle, stores at the bottom). If you omit the
dfd block entirely, one Process element is inferred per distinct component value, so
the export still works on a bare register.
Each threat is attached to an element by matching its component against the element ids
and labels; set extra.dfd_element to route it explicitly.
Caveat. The Threat Modeling Tool is a Windows desktop app with no CLI or API, and the
.tm7format isDataContractSerializerXML whose stencil GUIDs reference the loaded KnowledgeBase. This emitter produces a well-formed model of the correct shape using the stock SDL generic type ids, but it is authored from the schema and not tool-validated. Open the output in TMT and Save As to let the tool normalise it before relying on it.
Use it as a library
ThreatModel has no dependency on the mcp SDK, so you can query a register from any
Python program:
from threat_model_mcp import ThreatModel
model = ThreatModel.from_path("my-model.csv")
print(model.coverage_summary())
print(model.threats_by_stride("EoP"))
open("model.tm7", "w").write(model.export("tm7"))
Contributing
See CONTRIBUTING.md. The short version: query logic goes in
model.py, server.py stays a thin wrapper, and nothing writes to stdout — that is the
MCP stdio channel.
License
MIT.
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.