Skip to main content
Glama
Sumanharapanahalli

threat-model-mcp

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 .tm7 format is DataContractSerializer XML 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.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

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