Tenable Identity Exposure MCP Server
# Tenable Identity Exposure MCP Server
An [MCP](https://modelcontextprotocol.io) server exposing the **Tenable Identity Exposure** (TIE, formerly Tenable.ad) REST API as tools for LLM clients. Built on `FastMCP` from the official `mcp` SDK.
Verified end-to-end against a live TIE SaaS instance (`v3.120.1`).
> ⚠️ **Disclaimer:** This is **not an officially supported Tenable project**. It was
> built with assistance from Claude — treat its output as a starting point and
> validate responses against the Tenable console before acting on them.
## Tools
| Tool | Description |
|---|---|
| `tie_catalog` | List available resources + the filter-expression grammar — call this first |
| `tie_request` | Raw HTTP call to any endpoint (`method`, `path`, `params`, `body`) |
| `tie_resource_action` | Generic CRUD (`list`/`get`/`create`/`update`/`delete`) on flat resources |
| `tie_recent_activity` | **Unified IoE+IoA timeline for the last N hours (one call)** |
| `tie_find_ad_object` | **Resolve an AD object by name/identifier** (`by=sAMAccountName\|cn\|upn\|dn\|sid\|guid`) |
| `tie_get_ad_object` | Get one object's full attributes by `directory_id` + `ad_object_id` (the only working get-by-id) |
| `tie_account_report` | **One-shot account exposures + privileges** (resolve → deviances → key attrs) |
| `tie_profiles` | List security profiles (IoE/IoA data is profile-scoped) |
| `tie_scores` | Per-directory security scores for a profile |
| `tie_topology` | AD topology (domains, forests, trusts) for a profile |
| `tie_attacks` | IoA attack instances, slim by default (`resource_type` + `resource_value`; `actor`/`include_template`) |
| `tie_attacks_aggregate` | Counts by attack type + distinct source IPs/hosts/usernames over a window |
| `tie_attack_types_summary` | Compact attack-type list (id, codeName, name, criticity, mitre) |
| `tie_checkers_summary` | Compact IoE checker list (no description blobs) |
| `tie_alerts` | Alerts for a profile |
| `tie_deviances` | IoE deviant AD objects for a checker within a **time window** |
| `tie_deviances_bulk` | All active deviances in a few cursor-paginated calls |
| `tie_deviances_by_checker` | Full IoE deviances for a checker (no date filter) |
| `tie_deviances_by_directory` | Full IoE deviances for a directory (no date filter) |
| `tie_search_events` | Search AD security events in a date range (slim + paginated) |
| `tie_search_ad_objects` | *Deprecated* — delegates to `tie_find_ad_object` |
| `tie_whoami` | Current user identity, roles, permissions |
### Response envelope
List-returning tools wrap their output in a consistent envelope so an empty result
is never ambiguous:
```json
{ "count": 0, "page": 1, "perPage": 25, "hasMore": false,
"appliedFilters": { "...": "..." }, "warnings": ["..."], "results": [] }
```
Always check `warnings` — an unrecognized filter, a defaulted profile, or a
substring-vs-exact match is reported there rather than silently dropped.
### Finding AD objects & filter grammar
- There is **no** `GET /api/ad-objects?search=` — the API rejects it. Resolve accounts
with `tie_find_ad_object` (backed by the event stream, TIE's only server-side
object-attribute search), then use `tie_get_ad_object` for full attributes.
- The filter `expression` used by `tie_deviances` and `tie_search_events` is a
predicate map: `{"attributeName": "substring"}` (case-insensitive *contains*),
multiple keys ANDed, combined with `{"AND":[…]}` / `{"OR":[…]}`. `{}` matches all.
The `{"attribute","operator","value"}` form **silently matches nothing** — the
tools now warn when they see it.
### Time windows, profiles, and token budget
- Time-aware tools (`tie_recent_activity`, `tie_deviances`, `tie_attacks_aggregate`)
accept a relative `hours=N` window or explicit `date_start`/`date_end`. All timestamps are **UTC**.
- IoE/IoA data is **profile-scoped**. The API does not expose which profile your
console has selected, so pass `profile_id` explicitly — use `tie_profiles` to list
them. If omitted, tools default to profile `1` and add a warning to `warnings`.
- By default, deviance/object/attack results are **slimmed** (descriptions rendered,
oversized values dropped, attack `vector.template` omitted). Pass `verbose=true`
(or `include_template=true` for attacks) for the full raw payload.
## Configuration
Set via environment variables (or `--tie-url` / `--tie-api-key` flags):
| Variable | Description |
|---|---|
| `TIE_URL` | Base URL, e.g. `https://your-host.tenable.ad` |
| `TIE_API_KEY` | API key (TIE console → **System → Configuration → API key**) |
| `TIE_VERIFY_SSL` | `true` (default) or `false` for self-signed certs |
Auth uses the `X-API-Key` header.
## Install & run
```bash
python -m venv .venv && . .venv/bin/activate
pip install -e .
cp .env.example .env # fill in TIE_URL and TIE_API_KEY
# stdio (Claude Desktop / Claude Code)
set -a; . ./.env; set +a
tenable-tie-mcp
# or network transports
tenable-tie-mcp --transport sse --port 8000
tenable-tie-mcp --transport http --port 8000
```
### Running with uv / uvx (alternative to a manual venv)
[uv](https://docs.astral.sh/uv/) can manage the environment for you. `uv` is the
full tool; `uvx` (alias for `uv tool run`) runs a package in a throwaway env, like
`npx`. Both need `uv` installed (`brew install uv`).
```bash
# uv run: resolves deps from pyproject.toml into a managed .venv, then runs
uv run tenable-tie-mcp
# uvx: run ephemerally from the project path, nothing persisted
uvx --from . tenable-tie-mcp
```
## Claude Desktop / Claude Code config
Use the **full path** to the executable. Claude Desktop does not launch from your
shell, so it does not inherit your `PATH` — a bare `tenable-tie-mcp` will fail with
"command not found" unless the tool is on the system PATH (e.g. a `pipx` install).
For a venv install, point at the venv's launcher:
```json
{
"mcpServers": {
"tenable-tie": {
"command": "/absolute/path/to/tenable-ie-mcp/.venv/bin/tenable-tie-mcp",
"env": {
"TIE_URL": "https://your-host.tenable.ad",
"TIE_API_KEY": "your-key"
}
}
}
}
```
Find the exact path with `echo "$PWD/.venv/bin/tenable-tie-mcp"` from the project
root. See `claude_desktop_config.sample.json` for a complete example.
> If you installed globally with `pipx install .` (or `uv tool install`), the bare
> `"command": "tenable-tie-mcp"` works because it lands on the system PATH.
### With uv (alternative)
Point `command` at the **full path** of `uv` (`which uv`, e.g. `/opt/homebrew/bin/uv`)
and let it manage the environment:
```json
{
"mcpServers": {
"tenable-tie": {
"command": "/opt/homebrew/bin/uv",
"args": ["run", "--directory", "/absolute/path/to/tenable-ie-mcp", "tenable-tie-mcp"],
"env": {
"TIE_URL": "https://your-host.tenable.ad",
"TIE_API_KEY": "your-key"
}
}
}
}
```
## Docker
```bash
docker compose up --build # reads TIE_URL / TIE_API_KEY from environment
```
## Example prompts
- "Show me IoE and IoA activity in the last 12 hours." → `tie_recent_activity`
- "Find the account SECURITYPEN and show its exposures and privileges." → `tie_account_report`
- "Resolve the user administrator to its object id and DN." → `tie_find_ad_object`
- "Which security profiles exist?" → `tie_profiles`
- "List the monitored directories and their security scores."
- "Show the latest IoA attacks against directory 8." → `tie_attacks`
- "Which source hosts and accounts are behind attacks on directory 8?" → `tie_attacks_aggregate`
- "What IoE deviances appeared for checker 15 in the last day?" → `tie_deviances`
- "Show unread alerts for profile 2."
## Notes
- Every list tool returns the `{count, hasMore, appliedFilters, warnings, results}`
envelope — **read `warnings`** (defaulted profile, unrecognized filter, truncation).
- `profile_id` defaults to `1` when omitted, and the tool warns; pass it (or
`profile_name` where supported) to target your console's active profile.
- AD-object lookup is via `tie_find_ad_object` / `tie_get_ad_object`; the flat
`GET /api/ad-objects/{id}` and `?search=` routes do not work and are not used.
- Attacks must be scoped: `resource_type` ∈ `infrastructure|directory|hostname|ip`
and `resource_value` is the id or name/ip. `search` matches source host/IP only —
use `actor` to filter by the acting account in the attack vector.
- Non-JSON responses are returned as `{"content_type": ..., "text": ...}` rather
than crashing.
- **Scoring and prioritization:** TIE checkers carry a `remediationCost` (easy / medium /
hard) but no native asset-level severity score. If your workflow requires
**AES (Asset Exposure Score)** or **ACR (Asset Criticality Rating)** — for example,
to rank affected identities by business risk — connect your environment to
[Tenable One](https://www.tenable.com/products/tenable-one). Tenable One aggregates
data across TIE, Tenable Vulnerability Management, and other sources to produce
unified AES/ACR scores that can be surfaced here via the API.
TDQS
Scored across 15 tools
Most tools have clearly distinct purposes, but there is slight overlap among tie_deviances, tie_deviances_by_checker, and tie_deviances_by_directory. However, descriptions clarify that they differ by date filtering and scoping, so ambiguity is minimal.
All tools use the consistent 'tie_' prefix and follow snake_case naming. Naming patterns are predictable: nouns for entity listings (tie_alerts, tie_attacks) and verb_noun for search actions (tie_search_ad_objects). No mixed conventions.
15 tools is well-scoped for the Tenable Identity Exposure domain. Each tool covers a distinct aspect (alerts, attacks, deviances, profiles, search, topology) without unnecessary duplication or missing core functionality.
The tool set comprehensively covers the API surface: CRUD via tie_resource_action, specific queries for alerts, attacks, deviances, scores, topology, and search. The tie_recent_activity tool fills a gap by merging IoE and IoA timelines. No obvious missing operations.