pathhound-mcp
Allows querying and analyzing Active Directory attack graphs stored in a Neo4j database (BloodHound CE), including shortest paths to Tier 0, blast radius from a principal, choke points, and remediation steps.
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., "@pathhound-mcpshow shortest paths to Domain Admins from user jdoe"
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.
pathhound-mcp
An MCP (Model Context Protocol) server that lets an LLM reason over an Active Directory attack graph already collected by BloodHound — shortest paths to Tier 0, blast radius from a principal, the highest-leverage choke points to remediate, and defender-facing remediation text — with every action scoped to an authorized engagement and logged.
Authorized penetration-testing use only. This server never touches a live domain, never collects data, and never runs attacks. Collection is done separately (SharpHound /
bloodhound-python); this tool only reads and reasons over the resulting graph in Neo4j. See SECURITY.md for the full intended-use boundary.
What it does
Turns a collected graph into explainable answers. Ask "what are the shortest paths to Domain Admins," "if I own this account what becomes reachable," or "which edges should we fix first" and get structured results.
Read-only and scoped by design. It refuses to start without an engagement scope, refuses to query domains outside that scope, rejects write/destructive Cypher, and appends every call to an audit log.
Defender-facing output.
explain_edgeandremediation_for_pathproduce report-ready remediation, not exploit commands.
Tools
Tool | What it returns |
| Domains/forests in the graph, with node counts and an in-scope flag. |
| Rows from a read-only Cypher query. Write clauses are rejected unless |
| Shortest attack paths to Tier 0 / high-value targets — from |
| Blast radius from a principal, summarized by node type, highlighting any Tier 0 reachability. |
| The edges appearing in the most distinct shortest paths to Tier 0, each with the number of paths removing it would break. |
| Plain-language reason a BloodHound edge (e.g. |
| For a path (given as a read-only Cypher query returning a path |
| Headline counts for reporting: principals, owned, high-value, distinct paths to Tier 0, and the top choke points. |
Resources
Resource | Contents |
| The active scope (engagement id, authorization reference, allowed domains, write flag) and the Neo4j target with the password masked. |
| A curated, versioned set of named read-only Cypher queries (Kerberoastable, unconstrained delegation, AS-REP roastable, paths to Domain Admins, ACL abuse from a principal, …), each validated read-only on load. |
Optional write tools (off by default)
Two tools — mark_owned(principal, confirm) and set_high_value(principal, confirm) — set BloodHound's owned / Tier 0 markers (and nothing else) so the
path tools treat those nodes accordingly. They only exist when allow_writes: true is set in scope.yaml; each call also requires an explicit confirm=true,
an in-scope principal, and is audited. They never delete or drop. Leave
allow_writes: false (the default) for a strictly read-only deployment.
Related MCP server: EMBA-MCP
Requirements
Python 3.11+
A BloodHound CE Neo4j instance reachable over Bolt (the graph is collected and ingested separately — see the quickstart below)
Install
# With uv (preferred):
uv venv && uv pip install -e ".[dev]"
# Or with pip:
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt && pip install -e .Configure
Two pieces of configuration: the engagement scope (a file) and the Neo4j connection (environment variables — credentials are never hardcoded).
1. Engagement scope — copy the example and edit it:
cp scope.example.yaml scope.yaml# scope.yaml
engagement_id: "ENG-0001" # names the audit log file
authorization_ref: "SoW-2026-ACME-001 / JIRA-1234"
allowed_domains: # only these may be queried
- "CORP.LOCAL"
- "CHILD.CORP.LOCAL"
allow_writes: false # keep false unless you need the write-flag tools
max_rows: 500 # run_cypher row capThe server refuses to start if scope.yaml is missing or allowed_domains
is empty — this is intentional; there are no unscoped queries. Domain names are
matched case-insensitively against BloodHound's uppercase FQDNs.
2. Neo4j connection — via environment variables (or a .env; see
.env.example):
export NEO4J_URI="bolt://127.0.0.1:7687"
export NEO4J_USER="neo4j"
export NEO4J_PASSWORD="…"
export NEO4J_DATABASE="neo4j" # optional, defaults to neo4j
# export PATHHOUND_SCOPE="/abs/path/scope.yaml" # optional; defaults to ./scope.yamlRun
As a standalone stdio MCP server:
pathhound-mcp # or: python -m pathhound_mcp.serverRegister with an MCP client
Point your client at the console script (use the absolute path from your venv,
e.g. /path/to/pathhound-mcp/.venv/bin/pathhound-mcp) and pass the connection +
scope through its environment. Example (Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"pathhound": {
"command": "/path/to/pathhound-mcp/.venv/bin/pathhound-mcp",
"env": {
"NEO4J_URI": "bolt://127.0.0.1:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "…",
"PATHHOUND_SCOPE": "/path/to/pathhound-mcp/scope.yaml"
}
}
}
}Example questions to ask the LLM
Once connected, an operator can ask things like:
"List the domains in the graph and how big each is." →
list_domains"Which accounts are Kerberoastable?" →
run_cypherwith thekerberoastable_userslibrary query"Show the shortest attack paths to Tier 0." →
shortest_paths_to_tier0"If I compromise
alice@corp.local, what becomes reachable?" →reachable_from"What are the top 10 choke points we should fix first?" →
find_choke_points"Why is
AddKeyCredentialLinkdangerous, and how do we fix it?" →explain_edge"Give me remediation steps for this attack path." →
remediation_for_path"Summarize the engagement for the report." →
engagement_summary
Quickstart: collect into BloodHound, then point this at Neo4j
This server only reads an already-collected graph; collection happens separately and outside this tool.
Collect the domain with SharpHound or
bloodhound-pythonduring your authorized engagement. You get a set of JSON files (or a zip) — from a lab like GOAD (GOAD-Light ~16 GB, full GOAD ~32 GB) or a real engagement.Stand up BloodHound CE and ingest the data. The official compose stack runs Neo4j (Bolt on
127.0.0.1:7687), Postgres, and the BloodHound UI on:8080:docker compose -f /path/to/docker-compose.yml up -d # open http://localhost:8080, log in, and upload the collection zip # (Administration → File Ingest). Wait for ingest + post-processing.If the Neo4j volume is already ingested, you can start just the graph:
docker compose -f /path/to/docker-compose.yml up -d graph-db.Point pathhound-mcp at that Neo4j (see Configure) and run it. Your
allowed_domainsinscope.yamlshould match the domains in the collection (e.g.CORP.LOCAL).
BloodHound schema compatibility
Newer BloodHound CE tags Tier 0 with the Tag_Tier_Zero node label and
owned principals with Tag_Owned, while older versions used the highvalue
/ owned properties (and CE also sets system_tags: admin_tier_0). pathhound
recognizes all of these, so Tier 0 / owned reasoning works across versions.
Tests
pytest # unit tests — guardrails, scope, audit, Cypher guard, tool logicThe suite is fully database-free: the read-only guardrail, scope enforcement,
audit logging, row-capping, and path/reporting logic are all exercised against
stubs, so pytest runs in a fraction of a second with no Neo4j. (The tools have
also been validated end-to-end against a real BloodHound CE graph.)
Project layout
src/pathhound_mcp/
├── server.py # FastMCP app: tool/resource registration + startup guard
├── config.py # loads/validates scope.yaml (pydantic); Neo4j from env
├── guardrails.py # read-only Cypher guard + scope enforcement
├── audit.py # append-only JSONL audit logger (@audited decorator)
├── graph.py # Neo4j connection + read helpers (the only DB access)
├── schema.py # attack-edge set + Tier 0 / owned predicates
├── models.py # pydantic result models
├── cypher_library.json / edge_library.json # curated read-only queries + edge KB
└── tools/ # inventory, query, paths, reporting, edgesaudit/, scope.yaml, and .env are gitignored — treat the audit log and the
collected graph as engagement-sensitive.
Safety model
See SECURITY.md for the intended-use boundary and the guarantees: scope-at-startup, read-only by default, scope enforcement, append-only audit log, and no live-domain capability.
License
MIT — see LICENSE.
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/iabdullah215/pathhound-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server