dehashed-mcp
by jstjep00
README.md
# dehashed-mcp
An MCP (Model Context Protocol) server that wraps the [DeHashed](https://dehashed.com) breach-data API and exposes every available endpoint as an MCP tool — with **server-side API key management**, **structured Docker logging**, and a **pluggable RBAC/ABAC authorization engine** that enforces access control at the individual tool call level.
---
## Key design decisions
| Concern | Decision |
|---|---|
| **API key** | Held exclusively in the server process (env var). Never passed to or returned by any tool. |
| **Transport** | Not decided here — wire stdio, HTTP/SSE, or a gRPC proxy on top. |
| **MCP auth** | Not decided here — add bearer token / OAuth / mTLS at the transport layer. |
| **Tool-level authz** | RBAC + ABAC via a hot-reloadable YAML policy file. Per-tool, per-tenant, per-domain-scope, per-page-size. |
| **Logging** | structlog → JSON → stdout/stderr → `docker logs` / any log driver. |
---
## Project layout
```
dehashed-mcp/
├── src/dehashed_mcp/
│ ├── __init__.py
│ ├── main.py # FastMCP app + entrypoint
│ ├── config.py # Pydantic Settings (reads env vars)
│ ├── client.py # Async DeHashed HTTP client
│ ├── models.py # Request/response Pydantic models
│ ├── tools.py # All 17 MCP tool definitions
│ ├── authz.py # RBAC/ABAC engine
│ └── logging_config.py # structlog → Docker logs
├── config/
│ └── policy.yaml # Default authorization policy
├── tests/
│ ├── conftest.py
│ ├── test_authz.py
│ ├── test_client.py
│ └── test_models.py
├── Dockerfile
├── docker-compose.yml
├── docker-entrypoint.sh
├── pyproject.toml
└── .env.example
```
---
## Quick start
### 1. Configure
```bash
cp .env.example .env
# Edit .env — set DEHASHED_API_KEY at minimum
```
### 2. Build and run (stdio — Claude Desktop / any MCP host)
```bash
docker compose build
docker compose run --rm -i dehashed-mcp
```
### 3. Run with HTTP/SSE transport
```bash
MCP_TRANSPORT=http docker compose up
# Server listens on http://localhost:8000
```
### 4. View logs
```bash
docker logs -f dehashed-mcp
# Output is newline-delimited JSON
```
---
## Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `DEHASHED_API_KEY` | **yes** | — | Your DeHashed API key |
| `DEHASHED_EMAIL` | only for v1 | — | Account email (V1 Basic Auth only) |
| `DEHASHED_AUTH_MODE` | no | `v2_key` | `v2_key` or `v1_basic` |
| `DEHASHED_BASE_URL` | no | `https://api.dehashed.com` | API base URL |
| `LOG_LEVEL` | no | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` |
| `MCP_TRANSPORT` | no | `stdio` | `stdio` or `http` |
| `PORT` | no | `8000` | HTTP listen port (http transport only) |
| `AUTHZ_POLICY_PATH` | no | `/config/policy.yaml` | Path to policy file |
| `AUTHZ_STRICT_TENANT` | no | `true` | Reject unknown tenants when `true` |
---
## Available tools (17)
| Tool | Description | Tags |
|---|---|---|
| `search` | Raw query string search | read, raw_query |
| `search_by_field` | Field + value + match mode | read |
| `search_email` | Email exact search | read |
| `search_username` | Username contains search | read |
| `search_password` | Plaintext password exact search | read, sensitive |
| `search_hashed_password` | Hash exact search | read, sensitive |
| `search_ip` | IP address exact search | read |
| `search_name` | Name contains search | read |
| `search_vin` | VIN exact search | read |
| `search_address` | Address contains search | read |
| `search_phone` | Phone contains search | read |
| `search_domain` | Domain contains search | read |
| `search_database` | Breach/database name search | read |
| `search_license_plate` | License plate exact search | read |
| `search_crypto_address` | Cryptocurrency address exact search | read |
| `get_balance` | Retrieve remaining API credits | read, account |
| `paginate_all` | Auto-paginate and aggregate | read, bulk, raw_query |
### Query match modes (`search_by_field`, `search`)
| Mode | Syntax | Notes |
|---|---|---|
| `contains` | `field:value` | Default loose match |
| `exact` | `field:"value"` | Quoted exact match |
| `regex` | `field:/pattern/` | ⚠ Reported broken in V2 (May 2025) |
| `wildcard` | `field:"val*ue"` | `?` = single char, `*` = multi ⚠ May be broken in V2 |
---
## Authorization — RBAC/ABAC policy
Tool-level authorization is enforced **before** any DeHashed API call. The policy is a YAML file (`config/policy.yaml`) that can be hot-mounted into the container.
### Policy structure
```yaml
tenants:
<tenant_id>:
roles:
<role_name>:
allow: [<tool_name>, ...] # or ["*"] for all tools
deny: [<tool_name>, ...] # always wins over allow
resource_scopes:
domains: [acme.com, ...] # restrict domain/email tools
attributes:
max_page_size: 500 # cap the `size` param
max_pages: 3 # cap paginate_all max_pages
default: # fallback (strict_tenant=false only)
roles: ...
```
### CallerContext injection
The MCP metadata dict passed with each tool call is the contract between your transport/auth layer and the authz engine:
```python
# What your transport layer injects into _meta:
{
"tenant": "acme",
"roles": ["analyst"],
"attributes": {} # optional per-call ABAC overrides
}
```
At the transport layer (bearer token, OAuth, mTLS, API key validation), you decode the identity and populate this dict before the tool handler runs. The server enforces it on every call.
### Hot-reload policy
Send `SIGUSR1` or call `authz.get_policy().reload()` to reload the policy file without restarting the server.
---
## Transport configuration
This server is transport-agnostic. Choose your transport at deploy time:
### stdio (default)
Best for Claude Desktop integration:
```json
{
"mcpServers": {
"dehashed": {
"command": "docker",
"args": ["run", "--rm", "-i",
"-e", "DEHASHED_API_KEY=xxx",
"dehashed-mcp:latest"]
}
}
}
```
### HTTP / SSE
Run with `MCP_TRANSPORT=http`. The FastMCP app is also exported as an ASGI `app` object for direct uvicorn/gunicorn use:
```bash
uvicorn dehashed_mcp.main:app --host 0.0.0.0 --port 8000
```
Add your auth middleware (bearer token validation, OAuth introspection, etc.) as ASGI middleware on top.
### gRPC
Put a gRPC-to-HTTP/2 transcoding proxy (e.g., Envoy, grpc-gateway) in front of the HTTP transport.
---
## Development
```bash
# Install with dev extras
pip install -e ".[dev,http]"
# Run tests
pytest
# Lint
ruff check src tests
black --check src tests
mypy src
```
---
## Logging
All log output is **newline-delimited JSON** sent to stdout. Docker captures it via the configured log driver (`json-file` by default in `docker-compose.yml`).
```bash
docker logs dehashed-mcp | jq .
```
Example log line:
```json
{
"event": "dehashed.search.ok",
"total": 42,
"returned": 100,
"balance": 95,
"service": "dehashed-mcp",
"version": "0.1.0",
"level": "info",
"timestamp": "2025-03-09T12:00:00.000Z"
}
```
API keys, passwords, and tokens are automatically redacted from all log lines by the `_redact_api_key` processor in `logging_config.py`.
---
## Credits and depletion
Each tool call that hits the DeHashed API costs **1 credit**. `paginate_all` costs 1 credit per page. Monitor balance via `get_balance` or by watching the `balance` field in search results logged at `INFO` level. Policy `max_pages` attributes help prevent runaway credit consumption.
---
## License
MIT
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues