# MCP Config Inventory

> [Documentation](../index.md) → [Scanning](README.md)

**Inventory** discovers which MCP servers are configured on your local machine by reading client config files (Cursor, Claude Desktop, VS Code, Windsurf). It answers: *what servers are installed, how are they launched, and do any tool names collide?*

> **Want to scan a specific server?** Use `mcts scan` instead. Inventory is for auditing your local setup.

---

## In plain English

If you use Cursor, Claude Desktop, or VS Code with MCP servers, those apps store config files listing which servers are installed and how to launch them. MCTS reads these configs and shows you:

- Which MCP servers are configured
- How each server is launched (command, args, environment)
- Whether any tool names appear on multiple servers (called **tool shadowing** — a common agent confusion risk)

With `--scan`, MCTS also runs a lightweight security scan on each server's entrypoint.

With `--skills`, MCTS discovers `SKILL.md` files under well-known agent skill directories (for example `.cursor/skills`) **and** project-local paths such as `skills/` and `agent/skills`.

```bash
# Include repo-local skills/ without symlinks
mcts inventory --skills

# Additional skills tree
mcts inventory --skills --skills-dir ./skills --skills-dir ./custom-skills
```

For full prompt/instruction analysis on markdown in your repository (not only MCP `prompts/list`):

```bash
mcts scan . --surfaces prompt,instruction
mcts scan ./skills --surfaces prompt,instruction
mcts scan . --instruction-file src/agent/system_prompt.md
mcts scan . --instruction-glob 'skills/**/SKILL.md'
```

---

## Commands

```bash
# List configured servers (terminal output)
mcts inventory

# Static-scan each entrypoint + export JSON
mcts inventory --scan -o inventory.json

# Scan agent SKILL.md files for injection/exfil patterns
mcts inventory --skills -o inventory.json

# Full security scan on every discovered server (GAP-008)
mcts inventory --scan-all -o inventory-scan-all.json

# Toxic flow analysis W015–W020 across servers
mcts inventory --full-toxic-flows

# Theme for saved-notice styling only
mcts inventory --theme minimal -o inventory.json
```

### Exit codes

| Code | When |
|------|------|
| 0 | Success; no critical/high shadow findings |
| 1 | Cross-server shadow findings with **critical** or **high** severity |
| 2 | Theme/usage error |

---

## Supported clients and paths

Platform-specific paths are defined in `CLIENT_PATHS` (`inventory/discoverers.py`). Only **existing** files are read.

### macOS (`darwin`)

| Client | Config path |
|--------|-------------|
| Cursor | `~/.cursor/mcp.json` |
| VS Code | `~/.vscode/mcp.json`, `~/Library/Application Support/Code/User/settings.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |

### Linux

| Client | Config path |
|--------|-------------|
| Cursor | `~/.cursor/mcp.json` |
| VS Code | `~/.vscode/mcp.json`, `~/.config/Code/User/settings.json` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |

### Windows (`win32`)

| Client | Config path |
|--------|-------------|
| Cursor | `~/.cursor/mcp.json` |
| VS Code | `~/.vscode/mcp.json`, `~/AppData/Roaming/Code/User/settings.json` |
| Claude Desktop | `~/AppData/Roaming/Claude/claude_desktop_config.json` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |

Client name is inferred from the path string (`cursor`, `claude`, `vscode`, `windsurf`).

---

## Config parsing

MCTS reads JSON config files and extracts `mcpServers` entries:

```json
{
  "mcpServers": {
    "my-server": {
      "command": "uv",
      "args": ["run", "server.py"],
      "env": { "API_KEY": "..." }
    }
  }
}
```

For VS Code, it also supports `mcp.servers` nested under settings JSON when the `mcp` key is present.

Each entry becomes an `InventoryEntry`:

| Field | Description |
|-------|-------------|
| `client` | cursor, claude, vscode, windsurf |
| `server_name` | Key in `mcpServers` |
| `command` | Launch binary |
| `args` | Argument list |
| `env_keys` | Environment variable **names** only (values not exported) |
| `config_path` | Absolute path to source file |
| `tools` | Populated when `--scan` is used |

---

## Terminal output example

```text
MCP inventory — 2 config file(s)
  • cursor
  • claude
  [cursor] my-server (12 tools) — /Users/you/.cursor/mcp.json
  [claude] filesystem — /Users/you/Library/.../claude_desktop_config.json

Cross-server shadowing: 1 finding(s)
  • Tool name collision: read_file
```

---

## JSON export schema

```json
{
  "clients_scanned": ["cursor", "claude"],
  "config_files_found": 2,
  "entries": [
    {
      "client": "cursor",
      "server_name": "my-server",
      "command": "uv",
      "args": ["run", "server.py"],
      "config_path": "/Users/you/.cursor/mcp.json",
      "env_keys": ["API_KEY"],
      "tools": ["read_file", "write_file", "delete_all_users"]
    }
  ],
  "shadow_findings": [
    {
      "title": "Tool name collision: read_file",
      "severity": "high",
      "technique_id": "MCTS-T-1008"
    }
  ]
}
```

---

## `--scan` behavior

When `--scan` is set, `inventory/runner.py` resolves each entrypoint (preferring `.py` paths from `args`) and runs a lightweight static `Scanner` to collect tool names. This does **not** run the full analyzer suite — it is optimized for inventory breadth.

Non-Python entrypoints (Node, Go binaries) may not resolve automatically. Use explicit `mcts scan` with `--command` for those servers.

---

## Cross-server tool shadowing

`CrossServerAnalyzer.analyze_inventory()` builds a map of tool name → list of `(client, server_name)` pairs. When the same name appears on **different** servers:

- Finding severity scales with collision count and client diversity
- Technique ID: **MCTS-T-1008** (Cross-Server Tool Shadowing)
- Mitigations reference agent routing and namespacing guidance

**Why it matters:** LLM agents often select tools by name only. Two servers both exposing `read_file` may cause the agent to invoke the wrong handler — potentially crossing trust boundaries.

During `mcts scan`, the same analyzer can run when inventory context is wired into `Scanner` (future: automatic inventory hook).

---

## Scanning a discovered server

After inventory, run a full security scan:

```bash
# Static scan from config
mcts scan . --config ~/.cursor/mcp.json --server my-server

# Live probe from config
mcts scan . --config ~/.cursor/mcp.json --server my-server \
  --live --i-understand-live-risk
```

See [Live Scanning](live-scanning.md).

---

## Limitations

| Limitation | Detail |
|------------|--------|
| Local configs only | No remote fleet or enterprise MDM discovery |
| Heuristic entrypoint resolution | `--scan` may miss non-Python launch patterns |
| Four clients in original inventory | Expanded to 12+ agent clients — see [inventory.md](../scanning/inventory.md) |
| No secret values in export | Only `env_keys` names — not values |
| Ephemeral CI runners | GitHub-hosted runners typically have no user MCP configs |

---

## Planned discovery

Planned inventory and discovery gaps (GAP-095–105, GAP-218–231):

| Capability | Status | Phase | GAP |
|------------|--------|-------|-----|
| 12+ agent clients (Gemini, Codex, OpenClaw, Amazon Q…) | Shipped | 3 | GAP-095 |
| VS Code `workspaceStorage` / profiles | Missing | 3 | GAP-096 |
| Claude Code plugin + project-level globs | Missing | 3 | GAP-097 |
| Skills dirs (`.cursor/skills`, etc.) | Shipped | 2 | GAP-099 |
| `mcts inventory --skills` | Shipped | 2 | GAP-029 |
| `mcts inventory --scan-all` | Shipped | 2 | GAP-008 |
| Machine-wide scan without explicit target | Shipped | 2 | GAP-006 |
| `--scan-all-users` multi-home | Missing | 3 | GAP-021 |
| macOS codesign trust on stdio binaries | Missing | 3 | GAP-101 |
| WSL profile merge | Missing | 3 | GAP-102 |
| Shadow MCP discovery + allowlist | Missing | 2 | GAP-231 |
| CycloneDX AI-BOM export | Missing | 2–3 | GAP-106 |
| Fleet asset prefix / enterprise merge | Missing | 4 | GAP-116 |

Full list: [Feature Expansion Plan — Discovery](../more/feature-expansion-plan.md#discovery-13).

---

## Security note

Inventory reads configuration files that may **reference** secrets via environment variables. MCTS does not print env values in inventory output. Treat exported JSON like any config audit artifact.

---

## Related

- [CLI Reference — mcts inventory](../platform/cli.md#mcts-inventory)
- [Architecture — Inventory](../analysis/architecture.md#inventory-inventory)
- [Threat Taxonomy — MCTS-T-1008](../reporting/taxonomy.md)
- [Live Scanning — config launch](live-scanning.md#from-client-mcp-config)
