threat-model-mcp
Allows interaction with local LLMs running on Ollama to query and analyze a STRIDE threat register via MCP.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@threat-model-mcpwhat are the critical threats?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.jsonThe 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 environmentBoth .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 JWKSOnly 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.serverOr 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 |
| Compact list of every threat (id, title, STRIDE, severity, component, status). |
| Full record for one id, including its |
| Threats whose STRIDE set contains the category. Accepts full names or shorthand ( |
| Threats at a severity level (case-insensitive). |
| Threats whose component matches a substring. |
| Free-text search across the core fields and every |
| Mitigation, severity, priority, and the threat's whole |
| Counts by STRIDE, severity, priority, group, status; the highest-severity ids; and |
| Serialise the register: |
| Your |
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.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Sumanharapanahalli/mcp-microsoft-threat-modeling'
If you have feedback or need assistance with the MCP directory API, please join our Discord server