Skip to content

Latest commit

 

History

History
270 lines (193 loc) · 8.56 KB

File metadata and controls

270 lines (193 loc) · 8.56 KB

MCP Config Inventory

DocumentationScanning

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.

# 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):

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

# 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:

{
  "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

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

{
  "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:

# 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.


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
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.


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