agentaudit 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +121 -123
  2. package/index.mjs +193 -72
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,10 +4,11 @@
4
4
 
5
5
  **Security scanner for AI packages**
6
6
 
7
- Scan MCP servers, agent skills, and AI tools for vulnerabilities — from the terminal or via MCP.
7
+ Scan MCP servers, agent skills, and AI tools for vulnerabilities.
8
+ MCP server for agents + standalone CLI for humans.
8
9
 
9
10
  [![npm](https://img.shields.io/npm/v/agentaudit?style=flat-square&color=00C853)](https://www.npmjs.com/package/agentaudit)
10
- [![Trust Registry](https://img.shields.io/badge/Registry-agentaudit.dev-00C853?style=flat-square)](https://agentaudit.dev)
11
+ [![Registry](https://img.shields.io/badge/Registry-agentaudit.dev-00C853?style=flat-square)](https://agentaudit.dev)
11
12
  [![License](https://img.shields.io/badge/License-AGPL_3.0-F9A825?style=flat-square)](LICENSE)
12
13
 
13
14
  </div>
@@ -17,76 +18,100 @@ Scan MCP servers, agent skills, and AI tools for vulnerabilities — from the te
17
18
  ## Quick Start
18
19
 
19
20
  ```bash
20
- # Install globally
21
- npm install -g agentaudit
21
+ npx agentaudit discover
22
+ ```
23
+
24
+ That's it. Finds all MCP servers on your machine and checks them against the security registry.
22
25
 
23
- # Setup (register + get API key — free, one-time)
24
- agentaudit setup
26
+ ```
27
+ AgentAudit v3.2.0
28
+ Security scanner for AI packages
25
29
 
26
- # Scan a repo
27
- agentaudit scan https://github.com/owner/repo
30
+ • Scanning Claude Desktop ~/.claude/mcp.json found 2 servers
28
31
 
29
- # Scan multiple repos
30
- agentaudit scan repo1 repo2 repo3
32
+ ├── fastmcp-demo npm:fastmcp
33
+ │ SAFE Risk 0 ✔ official https://agentaudit.dev/skills/fastmcp
34
+ └── my-tool npm:some-mcp-tool
35
+ ⚠ not audited Run: agentaudit audit <source-url>
31
36
 
32
- # Check if a package has been audited
33
- agentaudit check fastmcp
37
+ ────────────────────────────────────────────────────────
38
+ Summary 2 servers across 1 config
39
+
40
+ ✔ 1 audited
41
+ ⚠ 1 not audited
34
42
  ```
35
43
 
36
- Or run without installing:
44
+ ## Install
37
45
 
38
46
  ```bash
39
- npx agentaudit scan https://github.com/owner/repo
47
+ npm install -g agentaudit # global install
48
+ # or use directly:
49
+ npx agentaudit <command>
40
50
  ```
41
51
 
42
- ## What it does
52
+ ---
43
53
 
44
- ```
45
- ◉ google-workspace-mcp https://github.com/taylorwilsdon/google_workspace_mcp
46
- │ Python mcp-server 31 files scanned in 1.0s
47
-
48
- ├── tool drive_service ✔ ok
49
- ├── tool docs_service ✔ ok
50
- ├── tool start_google_auth ✔ ok
51
- └── tool set_enabled_tools ✔ ok
52
-
53
- │ Findings (2) static analysis — may include false positives
54
- ├── ● MEDIUM Potential hardcoded secret
55
- │ .env.oauth21:9 SECRET="your-google-client-secret"
56
- └── ● MEDIUM Potential path traversal
57
- auth/credential_store.py:123
58
-
59
- └── registry LOW Risk 10 https://agentaudit.dev/skills/google-workspace-mcp
60
- ```
54
+ ## Commands
61
55
 
62
- **Detects:**
63
- - 🔴 Prompt injection & tool poisoning
64
- - 🔴 Shell command injection
65
- - 🔴 SQL injection
66
- - 🟡 Hardcoded secrets
67
- - 🟡 SSL/TLS verification disabled
68
- - 🟡 Path traversal
69
- - 🟡 Unsafe YAML/pickle deserialization
70
- - 🔵 Wildcard CORS
71
- - 🔵 Undisclosed telemetry
56
+ | Command | What it does | Speed |
57
+ |---------|-------------|-------|
58
+ | `discover` | Find local MCP servers + check registry | ⚡ instant |
59
+ | `check <name>` | Look up a package in the registry | ⚡ instant |
60
+ | `scan <url>` | Quick static analysis (regex-based, local) | 🔵 ~2s |
61
+ | `audit <url>` | **Deep LLM-powered security audit** | 🔴 ~30s |
62
+ | `setup` | Register + configure API key | interactive |
72
63
 
73
- **Plus** registry lookup — shows if a package has already been officially audited on [agentaudit.dev](https://agentaudit.dev).
64
+ ### `scan` vs `audit`
65
+
66
+ | | `scan` | `audit` |
67
+ |--|--------|---------|
68
+ | **How** | Regex pattern matching | LLM 3-pass: UNDERSTAND → DETECT → CLASSIFY |
69
+ | **Speed** | ~2 seconds | ~30 seconds |
70
+ | **Depth** | Surface-level patterns | Semantic code understanding |
71
+ | **Needs LLM API key** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
72
+ | **Uploads to registry** | No | Yes (with `agentaudit setup`) |
73
+
74
+ ### Examples
75
+
76
+ ```bash
77
+ # Discover all MCP servers on your machine
78
+ npx agentaudit discover
79
+
80
+ # Quick static scan
81
+ npx agentaudit scan https://github.com/owner/repo
82
+
83
+ # Deep LLM-powered audit
84
+ export ANTHROPIC_API_KEY=sk-ant-...
85
+ npx agentaudit audit https://github.com/owner/repo
86
+
87
+ # Export code + audit prompt for manual LLM review
88
+ npx agentaudit audit https://github.com/owner/repo --export
89
+
90
+ # Registry lookup
91
+ npx agentaudit check fastmcp
92
+
93
+ # Register for an API key (free)
94
+ npx agentaudit setup
95
+ ```
74
96
 
75
97
  ---
76
98
 
77
99
  ## MCP Server
78
100
 
79
- Use AgentAudit as an MCP server in Claude Desktop, Cursor, Windsurf, or any MCP client. Your AI agent gets three tools:
101
+ Add AgentAudit to your AI editor. Your agent gets 4 tools:
80
102
 
81
- | Tool | Description |
82
- |------|-------------|
83
- | `audit_package` | Clone a repo, return source code + audit prompt for deep LLM analysis |
84
- | `submit_report` | Upload completed audit report to [agentaudit.dev](https://agentaudit.dev) |
85
- | `check_package` | Look up a package in the registry |
103
+ | MCP Tool | Description |
104
+ |----------|-------------|
105
+ | `discover_servers` | Find all locally installed MCP servers, check registry status |
106
+ | `audit_package` | Clone a repo → return source code + audit prompt you analyze → `submit_report` |
107
+ | `submit_report` | Upload your audit report to [agentaudit.dev](https://agentaudit.dev) |
108
+ | `check_package` | Quick registry lookup for a package |
86
109
 
87
- ### Claude Desktop / Claude Code
110
+ ### Setup
88
111
 
89
- `~/.claude/mcp.json`:
112
+ One-line config — works with `npx`, no manual clone needed:
113
+
114
+ **Claude Desktop** (`~/.claude/mcp.json`):
90
115
  ```json
91
116
  {
92
117
  "mcpServers": {
@@ -98,9 +123,7 @@ Use AgentAudit as an MCP server in Claude Desktop, Cursor, Windsurf, or any MCP
98
123
  }
99
124
  ```
100
125
 
101
- ### Cursor
102
-
103
- `.cursor/mcp.json`:
126
+ **Cursor** (`.cursor/mcp.json`):
104
127
  ```json
105
128
  {
106
129
  "mcpServers": {
@@ -112,9 +135,7 @@ Use AgentAudit as an MCP server in Claude Desktop, Cursor, Windsurf, or any MCP
112
135
  }
113
136
  ```
114
137
 
115
- ### Windsurf
116
-
117
- `~/.codeium/windsurf/mcp_config.json`:
138
+ **Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
118
139
  ```json
119
140
  {
120
141
  "mcpServers": {
@@ -126,100 +147,77 @@ Use AgentAudit as an MCP server in Claude Desktop, Cursor, Windsurf, or any MCP
126
147
  }
127
148
  ```
128
149
 
129
- > **That's it.** No manual clone, no path config. `npx` handles everything.
130
-
131
- ### How the MCP audit works
150
+ ### How an audit works via MCP
132
151
 
133
152
  ```
134
- Agent calls audit_package("https://github.com/owner/repo")
135
-
136
- MCP Server clones repo, collects source files (max 300KB)
137
-
138
- Returns source code + 3-pass audit methodology
139
-
140
- Agent's LLM analyzes code (UNDERSTAND → DETECT → CLASSIFY)
141
-
142
- Agent calls submit_report(findings)
143
-
144
- Report published at agentaudit.dev/skills/{slug}
145
- ```
146
-
147
- ---
148
-
149
- ## Setup & Authentication
150
-
151
- ```bash
152
- agentaudit setup
153
+ You: "Audit the blender-mcp server"
154
+
155
+ Agent calls discover_servers finds blender-mcp
156
+
157
+ Agent calls check_package("blender-mcp") not in registry
158
+
159
+ Agent calls audit_package("https://github.com/user/blender-mcp")
160
+
161
+ MCP server clones repo, returns source code + audit methodology
162
+
163
+ Agent's LLM analyzes code (3-pass: UNDERSTAND → DETECT → CLASSIFY)
164
+
165
+ Agent calls submit_report(findings_json)
166
+
167
+ Report published at agentaudit.dev/skills/blender-mcp
153
168
  ```
154
169
 
155
- Interactive wizard — choose:
156
- 1. **Register new agent** (free) → API key created automatically
157
- 2. **Enter existing API key** → if you already have one
170
+ ### Authentication
158
171
 
159
- Credentials are stored in `~/.config/agentaudit/credentials.json` (survives reinstalls).
172
+ Run `npx agentaudit setup` once. Both CLI and MCP server find credentials automatically:
160
173
 
161
- The MCP server finds credentials automatically from:
162
174
  1. `AGENTAUDIT_API_KEY` environment variable
163
- 2. `~/.config/agentaudit/credentials.json`
175
+ 2. `~/.config/agentaudit/credentials.json` (created by `setup`)
164
176
 
165
- **Scanning and checking work without a key.** Only submitting reports requires authentication.
177
+ **`discover`, `scan`, and `check` work without a key.** Only `audit`/`submit_report` need one.
166
178
 
167
179
  ---
168
180
 
169
- ## CLI Reference
170
-
171
- ```
172
- agentaudit discover Find local MCP servers + check registry
173
- agentaudit scan <url> [url...] Quick static scan (regex, ~2s)
174
- agentaudit audit <url> [url...] Deep LLM-powered audit (~30s)
175
- agentaudit check <name> Look up package in registry
176
- agentaudit setup Register + configure API key
177
- ```
178
-
179
- ### `scan` vs `audit`
180
-
181
- | | `scan` | `audit` |
182
- |--|--------|---------|
183
- | **How** | Regex-based static analysis | LLM 3-pass analysis (UNDERSTAND → DETECT → CLASSIFY) |
184
- | **Speed** | ~2 seconds | ~30 seconds |
185
- | **Depth** | Pattern matching | Semantic code understanding |
186
- | **Needs API key** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
187
- | **Upload to registry** | No | Yes (with `agentaudit setup`) |
188
-
189
- Use `scan` for quick checks, `audit` for thorough analysis.
181
+ ## What it detects
190
182
 
191
- ### Examples
183
+ ### Static scan (`scan`)
192
184
 
193
- ```bash
194
- # Discover all MCP servers on your machine
195
- agentaudit discover
185
+ - 🔴 Prompt injection & tool poisoning
186
+ - 🔴 Shell command injection
187
+ - 🔴 SQL injection
188
+ - 🔴 Unsafe deserialization (pickle, YAML)
189
+ - 🟡 Hardcoded secrets
190
+ - 🟡 SSL/TLS verification disabled
191
+ - 🟡 Path traversal
192
+ - 🔵 Wildcard CORS
193
+ - 🔵 Undisclosed telemetry
196
194
 
197
- # Quick scan
198
- agentaudit scan https://github.com/jlowin/fastmcp
195
+ ### Deep audit (`audit` / MCP `audit_package`)
199
196
 
200
- # Deep audit (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)
201
- agentaudit audit https://github.com/jlowin/fastmcp
197
+ Everything above, plus:
202
198
 
203
- # Export audit for manual LLM review (no API key needed)
204
- agentaudit audit https://github.com/owner/repo --export
199
+ - 🔴 Multi-file attack chains (credential harvest exfiltration)
200
+ - 🔴 Agent manipulation (impersonation, capability escalation, jailbreaks)
201
+ - 🔴 MCP-specific: tool description injection, resource traversal, unpinned npx
202
+ - 🟡 Persistence mechanisms (crontab, shell RC, git hooks, systemd)
203
+ - 🟡 Obfuscation (base64 exec, zero-width chars, ANSI escapes)
204
+ - 🟡 Context pollution & indirect prompt injection
205
205
 
206
- # Check registry
207
- agentaudit check mongodb-mcp-server
208
- ```
206
+ 50+ detection patterns across 8 categories. [Full pattern list →](https://github.com/starbuck100/agentaudit-skill)
209
207
 
210
208
  ---
211
209
 
212
210
  ## Requirements
213
211
 
214
212
  - **Node.js 18+**
215
- - **Git** (for cloning repos during scan)
213
+ - **Git** (for cloning repos)
216
214
 
217
215
  ---
218
216
 
219
217
  ## Related
220
218
 
221
- - [agentaudit.dev](https://agentaudit.dev) — Trust registry & audit reports
222
- - [agentaudit-skill](https://github.com/starbuck100/agentaudit-skill) — Full agent skill with gate scripts, detection patterns & peer review
219
+ - **[agentaudit.dev](https://agentaudit.dev)** — Trust registry with 400+ audit reports
220
+ - **[agentaudit-skill](https://github.com/starbuck100/agentaudit-skill)** — Full agent skill with gate scripts, detection patterns & peer review system
223
221
 
224
222
  ---
225
223
 
package/index.mjs CHANGED
@@ -2,25 +2,20 @@
2
2
  /**
3
3
  * AgentAudit MCP Server
4
4
  *
5
- * Provides security audit capabilities via Model Context Protocol.
5
+ * Security audit capabilities via Model Context Protocol.
6
6
  *
7
7
  * Tools:
8
- * - audit_package: Clone a repo, read source files, return with audit prompt
9
- * - submit_report: Upload a completed audit report to agentaudit.dev
10
- * - check_package: Look up a package in the AgentAudit registry
8
+ * - discover_servers Find locally installed MCP servers + check registry status
9
+ * - audit_package Clone a repo, return source code + audit prompt for LLM analysis
10
+ * - submit_report Upload a completed audit report to agentaudit.dev
11
+ * - check_package Look up a package in the AgentAudit registry
11
12
  *
12
13
  * Usage:
13
- * node mcp-server/index.mjs
14
+ * npx agentaudit (starts MCP server via stdio)
15
+ * node index.mjs (same)
14
16
  *
15
- * Configure in Claude/Cursor/etc:
16
- * {
17
- * "mcpServers": {
18
- * "agentaudit": {
19
- * "command": "node",
20
- * "args": ["path/to/agentaudit/mcp-server/index.mjs"]
21
- * }
22
- * }
23
- * }
17
+ * Configure in Claude/Cursor/Windsurf:
18
+ * { "mcpServers": { "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } } }
24
19
  */
25
20
 
26
21
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -37,8 +32,8 @@ import { fileURLToPath } from 'url';
37
32
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
38
33
  const SKILL_DIR = path.resolve(__dirname);
39
34
  const REGISTRY_URL = 'https://agentaudit.dev';
40
- const MAX_FILE_SIZE = 50_000; // 50KB per file
41
- const MAX_TOTAL_SIZE = 300_000; // 300KB total code
35
+ const MAX_FILE_SIZE = 50_000;
36
+ const MAX_TOTAL_SIZE = 300_000;
42
37
  const SKIP_DIRS = new Set([
43
38
  'node_modules', '.git', '__pycache__', '.venv', 'venv', 'dist', 'build',
44
39
  '.next', '.nuxt', 'coverage', '.pytest_cache', '.mypy_cache', 'vendor',
@@ -61,7 +56,7 @@ const PRIORITY_FILES = [
61
56
  'Makefile', 'Dockerfile', 'docker-compose.yml',
62
57
  ];
63
58
 
64
- // ── Helpers ──────────────────────────────────────────────
59
+ // ── Credentials ─────────────────────────────────────────
65
60
 
66
61
  function loadApiKey() {
67
62
  if (process.env.AGENTAUDIT_API_KEY) return process.env.AGENTAUDIT_API_KEY;
@@ -84,39 +79,32 @@ function loadApiKey() {
84
79
 
85
80
  function loadAuditPrompt() {
86
81
  const promptPath = path.join(SKILL_DIR, 'prompts', 'audit-prompt.md');
87
- if (fs.existsSync(promptPath)) {
88
- return fs.readFileSync(promptPath, 'utf8');
89
- }
82
+ if (fs.existsSync(promptPath)) return fs.readFileSync(promptPath, 'utf8');
90
83
  return 'ERROR: audit-prompt.md not found at ' + promptPath;
91
84
  }
92
85
 
86
+ // ── File Collection ─────────────────────────────────────
87
+
93
88
  function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0 }) {
94
89
  if (totalSize.bytes >= MAX_TOTAL_SIZE) return collected;
95
-
96
90
  let entries;
97
91
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
98
92
  catch { return collected; }
99
-
100
- // Sort: priority files first
101
93
  entries.sort((a, b) => {
102
94
  const aP = PRIORITY_FILES.includes(a.name) ? 0 : 1;
103
95
  const bP = PRIORITY_FILES.includes(b.name) ? 0 : 1;
104
96
  return aP - bP || a.name.localeCompare(b.name);
105
97
  });
106
-
107
98
  for (const entry of entries) {
108
99
  if (totalSize.bytes >= MAX_TOTAL_SIZE) break;
109
-
110
100
  const relPath = basePath ? `${basePath}/${entry.name}` : entry.name;
111
101
  const fullPath = path.join(dir, entry.name);
112
-
113
102
  if (entry.isDirectory()) {
114
103
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
115
104
  collectFiles(fullPath, relPath, collected, totalSize);
116
105
  } else {
117
106
  const ext = path.extname(entry.name).toLowerCase();
118
107
  if (SKIP_EXTENSIONS.has(ext)) continue;
119
-
120
108
  try {
121
109
  const stat = fs.statSync(fullPath);
122
110
  if (stat.size > MAX_FILE_SIZE) {
@@ -124,24 +112,22 @@ function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0
124
112
  continue;
125
113
  }
126
114
  if (stat.size === 0) continue;
127
-
128
115
  const content = fs.readFileSync(fullPath, 'utf8');
129
116
  totalSize.bytes += content.length;
130
117
  collected.push({ path: relPath, content });
131
- } catch {
132
- // Binary or unreadable — skip
133
- }
118
+ } catch {}
134
119
  }
135
120
  }
136
121
  return collected;
137
122
  }
138
123
 
124
+ // ── Repo Helpers ────────────────────────────────────────
125
+
139
126
  function cloneRepo(sourceUrl) {
140
127
  const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
141
128
  try {
142
129
  execSync(`git clone --depth 1 "${sourceUrl}" "${tmpDir}/repo" 2>/dev/null`, {
143
- timeout: 30_000,
144
- stdio: 'pipe',
130
+ timeout: 30_000, stdio: 'pipe',
145
131
  });
146
132
  return path.join(tmpDir, 'repo');
147
133
  } catch (err) {
@@ -150,30 +136,95 @@ function cloneRepo(sourceUrl) {
150
136
  }
151
137
 
152
138
  function cleanupRepo(repoPath) {
153
- try {
154
- execSync(`rm -rf "${path.dirname(repoPath)}"`, { stdio: 'pipe' });
155
- } catch {}
139
+ try { execSync(`rm -rf "${path.dirname(repoPath)}"`, { stdio: 'pipe' }); } catch {}
156
140
  }
157
141
 
158
142
  function slugFromUrl(url) {
159
- // https://github.com/owner/repo → owner-repo or just repo
160
143
  const match = url.match(/github\.com\/([^/]+)\/([^/.\s]+)/);
161
144
  if (match) return match[2].toLowerCase().replace(/[^a-z0-9-]/g, '-');
162
145
  return url.replace(/[^a-z0-9]/gi, '-').toLowerCase().slice(0, 60);
163
146
  }
164
147
 
148
+ // ── Discover local MCP configs ──────────────────────────
149
+
150
+ function discoverMcpServers() {
151
+ const home = process.env.HOME || process.env.USERPROFILE || '';
152
+ const candidates = [
153
+ { platform: 'Claude Desktop', path: path.join(home, '.claude', 'mcp.json') },
154
+ { platform: 'Claude Desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') },
155
+ { platform: 'Claude Desktop', path: path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json') },
156
+ { platform: 'Claude Desktop', path: path.join(home, '.config', 'claude', 'claude_desktop_config.json') },
157
+ { platform: 'Cursor', path: path.join(home, '.cursor', 'mcp.json') },
158
+ { platform: 'Windsurf', path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json') },
159
+ { platform: 'VS Code', path: path.join(home, '.vscode', 'mcp.json') },
160
+ ];
161
+
162
+ const results = [];
163
+
164
+ for (const c of candidates) {
165
+ if (!fs.existsSync(c.path)) {
166
+ results.push({ platform: c.platform, config_path: c.path, status: 'not found', servers: [] });
167
+ continue;
168
+ }
169
+ let content;
170
+ try { content = JSON.parse(fs.readFileSync(c.path, 'utf8')); }
171
+ catch { results.push({ platform: c.platform, config_path: c.path, status: 'parse error', servers: [] }); continue; }
172
+
173
+ const serverMap = content.mcpServers || content.servers || {};
174
+ const servers = [];
175
+ for (const [name, cfg] of Object.entries(serverMap)) {
176
+ const allArgs = [cfg.command, ...(cfg.args || [])].filter(Boolean).join(' ');
177
+ const npxMatch = allArgs.match(/npx\s+(?:-y\s+)?(@?[a-z0-9][\w./-]*)/i);
178
+ const pyMatch = allArgs.match(/(?:uvx|pip run|python -m)\s+(@?[a-z0-9][\w./-]*)/i);
179
+ servers.push({
180
+ name,
181
+ command: cfg.command || null,
182
+ args: cfg.args || [],
183
+ npm_package: npxMatch?.[1] || null,
184
+ pip_package: pyMatch?.[1] || null,
185
+ });
186
+ }
187
+ results.push({ platform: c.platform, config_path: c.path, status: 'found', server_count: servers.length, servers });
188
+ }
189
+
190
+ return results;
191
+ }
192
+
193
+ async function checkRegistry(slug) {
194
+ try {
195
+ const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(slug)}`, {
196
+ signal: AbortSignal.timeout(5000),
197
+ });
198
+ if (res.ok) return await res.json();
199
+ } catch {}
200
+ return null;
201
+ }
202
+
165
203
  // ── MCP Server ───────────────────────────────────────────
166
204
 
167
205
  const server = new Server(
168
- { name: 'agentaudit', version: '1.0.0' },
206
+ { name: 'agentaudit', version: '3.2.0' },
169
207
  { capabilities: { tools: {} } }
170
208
  );
171
209
 
172
210
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
173
211
  tools: [
212
+ {
213
+ name: 'discover_servers',
214
+ description: 'Find all locally installed MCP servers by scanning config files (Claude Desktop, Cursor, Windsurf, VS Code). Returns the list of configured servers with their names, commands, and package sources. Use this to see what MCP servers are installed, then check each against the registry with check_package, or audit them with audit_package.',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ check_registry: {
219
+ type: 'boolean',
220
+ description: 'If true, also check each discovered server against the AgentAudit registry (default: true)',
221
+ },
222
+ },
223
+ },
224
+ },
174
225
  {
175
226
  name: 'audit_package',
176
- description: 'Clone a repository and prepare it for security audit. Returns the source code and audit instructions. You (the agent) then analyze the code following the audit prompt and call submit_report with the results.',
227
+ description: 'Deep security audit: clone a repository and prepare it for LLM-powered analysis. Returns the source code and a 3-pass audit methodology (UNDERSTAND → DETECT → CLASSIFY). You (the agent) then analyze the code following the instructions and call submit_report with your findings. This is a DEEP audit — use check_package first for a quick registry lookup.',
177
228
  inputSchema: {
178
229
  type: 'object',
179
230
  properties: {
@@ -187,13 +238,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
187
238
  },
188
239
  {
189
240
  name: 'submit_report',
190
- description: 'Submit a completed security audit report to the AgentAudit registry (agentaudit.dev). Call this after you have analyzed the code from audit_package.',
241
+ description: 'Submit a completed security audit report to the AgentAudit registry (agentaudit.dev). Call this after you have analyzed the code from audit_package. The report becomes publicly available and helps other agents make install decisions.',
191
242
  inputSchema: {
192
243
  type: 'object',
193
244
  properties: {
194
245
  report: {
195
246
  type: 'object',
196
- description: 'The audit report JSON object. Must include: skill_slug, source_url, risk_score (0-100), result (safe|caution|unsafe), findings (array), findings_count, max_severity.',
247
+ description: 'The audit report JSON object. Required fields: skill_slug, source_url, risk_score (0-100), result (safe|caution|unsafe), findings (array), findings_count, max_severity, package_type.',
197
248
  },
198
249
  },
199
250
  required: ['report'],
@@ -201,7 +252,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
201
252
  },
202
253
  {
203
254
  name: 'check_package',
204
- description: 'Look up a package in the AgentAudit security registry. Returns the latest audit results if available.',
255
+ description: 'Quick registry lookup: check if a package has already been audited on agentaudit.dev. Returns the latest audit results (risk score, findings, official status) if available. Use this before audit_package to avoid duplicate work.',
205
256
  inputSchema: {
206
257
  type: 'object',
207
258
  properties: {
@@ -218,39 +269,99 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
218
269
 
219
270
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
220
271
  const { name, arguments: args } = request.params;
221
-
272
+
222
273
  switch (name) {
274
+
275
+ // ── discover_servers ──────────────────────────────────
276
+ case 'discover_servers': {
277
+ const doRegistryCheck = args.check_registry !== false;
278
+ const configs = discoverMcpServers();
279
+ const foundConfigs = configs.filter(c => c.status === 'found');
280
+ const allServers = foundConfigs.flatMap(c => c.servers.map(s => ({ ...s, platform: c.platform })));
281
+
282
+ let text = `# Discovered MCP Servers\n\n`;
283
+ text += `Scanned ${configs.length} config locations. Found ${foundConfigs.length} config(s) with ${allServers.length} server(s).\n\n`;
284
+
285
+ for (const config of configs) {
286
+ if (config.status === 'not found') continue;
287
+ text += `## ${config.platform}\n`;
288
+ text += `Config: \`${config.config_path}\`\n\n`;
289
+
290
+ if (config.servers.length === 0) {
291
+ text += `No servers configured.\n\n`;
292
+ continue;
293
+ }
294
+
295
+ for (const srv of config.servers) {
296
+ const slug = srv.npm_package?.replace(/^@/, '').replace(/\//g, '-')
297
+ || srv.pip_package?.replace(/[^a-z0-9-]/gi, '-')
298
+ || srv.name.toLowerCase().replace(/[^a-z0-9-]/gi, '-');
299
+
300
+ text += `### ${srv.name}\n`;
301
+ text += `- Command: \`${[srv.command, ...srv.args].join(' ')}\`\n`;
302
+ if (srv.npm_package) text += `- npm: ${srv.npm_package}\n`;
303
+ if (srv.pip_package) text += `- pip: ${srv.pip_package}\n`;
304
+
305
+ if (doRegistryCheck) {
306
+ const regData = await checkRegistry(slug);
307
+ if (regData) {
308
+ const risk = regData.risk_score ?? regData.latest_risk_score ?? 0;
309
+ const official = regData.has_official_audit ? ' (official)' : '';
310
+ text += `- **Registry: ✅ Audited** — Risk ${risk}/100${official}\n`;
311
+ text += `- Report: ${REGISTRY_URL}/skills/${slug}\n`;
312
+ } else {
313
+ text += `- **Registry: ⚠️ Not audited** — no audit report found\n`;
314
+ text += `- To audit: call \`audit_package\` with the source URL\n`;
315
+ }
316
+ }
317
+ text += `\n`;
318
+ }
319
+ }
320
+
321
+ if (allServers.length === 0) {
322
+ text += `No MCP servers found. Config locations searched:\n`;
323
+ text += `- Claude Desktop: ~/.claude/mcp.json\n`;
324
+ text += `- Cursor: ~/.cursor/mcp.json\n`;
325
+ text += `- Windsurf: ~/.codeium/windsurf/mcp_config.json\n`;
326
+ text += `- VS Code: ~/.vscode/mcp.json\n`;
327
+ }
328
+
329
+ return { content: [{ type: 'text', text }] };
330
+ }
331
+
332
+ // ── audit_package ─────────────────────────────────────
223
333
  case 'audit_package': {
224
334
  const { source_url } = args;
225
335
  if (!source_url || !source_url.startsWith('http')) {
226
336
  return { content: [{ type: 'text', text: 'Error: source_url must be a valid HTTP(S) URL' }] };
227
337
  }
228
-
338
+
229
339
  let repoPath;
230
340
  try {
231
341
  repoPath = cloneRepo(source_url);
232
342
  const files = collectFiles(repoPath);
233
343
  const slug = slugFromUrl(source_url);
234
344
  const auditPrompt = loadAuditPrompt();
235
-
236
- // Build the response
345
+
237
346
  let codeBlock = '';
238
347
  for (const file of files) {
239
348
  codeBlock += `\n### FILE: ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n`;
240
349
  }
241
-
350
+
242
351
  const response = [
243
- `# Security Audit Request`,
352
+ `# Security Audit: ${slug}`,
244
353
  ``,
245
- `**Package:** ${slug}`,
246
354
  `**Source:** ${source_url}`,
247
355
  `**Files collected:** ${files.length}`,
248
356
  ``,
249
- `## Instructions`,
357
+ `## Your Task`,
358
+ ``,
359
+ `1. Analyze the source code below using the 3-pass audit methodology`,
360
+ `2. Call \`submit_report\` with your findings as JSON`,
250
361
  ``,
251
- `Analyze the source code below following the audit methodology. After your analysis, call the \`submit_report\` tool with your findings as a JSON object.`,
362
+ `## Report Format`,
252
363
  ``,
253
- `The report JSON must include:`,
364
+ `Your report JSON must include:`,
254
365
  '```json',
255
366
  `{`,
256
367
  ` "skill_slug": "${slug}",`,
@@ -285,7 +396,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
285
396
  ``,
286
397
  codeBlock,
287
398
  ].join('\n');
288
-
399
+
289
400
  return { content: [{ type: 'text', text: response }] };
290
401
  } catch (err) {
291
402
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
@@ -293,27 +404,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
293
404
  if (repoPath) cleanupRepo(repoPath);
294
405
  }
295
406
  }
296
-
407
+
408
+ // ── submit_report ─────────────────────────────────────
297
409
  case 'submit_report': {
298
410
  const { report } = args;
299
411
  if (!report || typeof report !== 'object') {
300
412
  return { content: [{ type: 'text', text: 'Error: report must be a JSON object' }] };
301
413
  }
302
-
414
+
303
415
  const apiKey = loadApiKey();
304
416
  if (!apiKey) {
305
- return { content: [{ type: 'text', text: 'Error: No API key configured. Set AGENTAUDIT_API_KEY or register first.' }] };
417
+ return { content: [{ type: 'text', text: 'Error: No API key configured. Run `npx agentaudit setup` or set AGENTAUDIT_API_KEY.' }] };
306
418
  }
307
-
308
- // Validate required fields
419
+
309
420
  const required = ['skill_slug', 'source_url', 'risk_score', 'result'];
310
421
  for (const field of required) {
311
422
  if (report[field] == null) {
312
423
  return { content: [{ type: 'text', text: `Error: Missing required field "${field}" in report` }] };
313
424
  }
314
425
  }
315
-
316
- // Auto-fix findings
426
+
317
427
  if (!Array.isArray(report.findings)) report.findings = [];
318
428
  report.findings_count = report.findings.length;
319
429
  if (!report.max_severity) {
@@ -324,7 +434,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
324
434
  return fi < mi ? f.severity : max;
325
435
  }, 'none');
326
436
  }
327
-
437
+
328
438
  try {
329
439
  const res = await fetch(`${REGISTRY_URL}/api/reports`, {
330
440
  method: 'POST',
@@ -335,13 +445,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
335
445
  body: JSON.stringify(report),
336
446
  signal: AbortSignal.timeout(60_000),
337
447
  });
338
-
448
+
339
449
  const body = await res.text();
340
450
  let data;
341
451
  try { data = JSON.parse(body); } catch { data = { raw: body }; }
342
-
452
+
343
453
  if (res.ok) {
344
- return { content: [{ type: 'text', text: `Report submitted successfully!\nReport ID: ${data.report_id || 'unknown'}\nURL: ${REGISTRY_URL}/skills/${report.skill_slug}\n\n${JSON.stringify(data, null, 2)}` }] };
454
+ return { content: [{ type: 'text', text: `✅ Report submitted!\n\nReport ID: ${data.report_id || 'unknown'}\nURL: ${REGISTRY_URL}/skills/${report.skill_slug}\nRisk: ${report.risk_score}/100 (${report.result})\nFindings: ${report.findings_count}` }] };
345
455
  } else {
346
456
  return { content: [{ type: 'text', text: `Upload failed (HTTP ${res.status}): ${JSON.stringify(data, null, 2)}` }] };
347
457
  }
@@ -349,31 +459,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
349
459
  return { content: [{ type: 'text', text: `Upload error: ${err.message}` }] };
350
460
  }
351
461
  }
352
-
462
+
463
+ // ── check_package ─────────────────────────────────────
353
464
  case 'check_package': {
354
465
  const { package_name } = args;
355
466
  if (!package_name) {
356
467
  return { content: [{ type: 'text', text: 'Error: package_name is required' }] };
357
468
  }
358
-
469
+
359
470
  try {
360
471
  const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(package_name)}`, {
361
472
  signal: AbortSignal.timeout(10_000),
362
473
  });
363
-
474
+
364
475
  if (res.status === 404) {
365
- return { content: [{ type: 'text', text: `Package "${package_name}" not found in registry. It may not have been audited yet. Use audit_package to audit it.` }] };
476
+ return { content: [{ type: 'text', text: `Package "${package_name}" not found in registry.\n\nIt hasn't been audited yet. To audit it:\n1. Find the source URL (GitHub repo)\n2. Call audit_package with the URL\n3. Analyze the code\n4. Call submit_report with your findings` }] };
366
477
  }
367
-
478
+
368
479
  const data = await res.json();
369
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
480
+ const risk = data.risk_score ?? data.latest_risk_score ?? 'unknown';
481
+ const official = data.has_official_audit ? '✅ Officially audited' : 'Community audit';
482
+
483
+ let summary = `# ${package_name}\n\n`;
484
+ summary += `**Risk Score:** ${risk}/100\n`;
485
+ summary += `**Status:** ${official}\n`;
486
+ if (data.source_url) summary += `**Source:** ${data.source_url}\n`;
487
+ summary += `**Registry:** ${REGISTRY_URL}/skills/${package_name}\n\n`;
488
+ summary += `## Full Data\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``;
489
+
490
+ return { content: [{ type: 'text', text: summary }] };
370
491
  } catch (err) {
371
492
  return { content: [{ type: 'text', text: `Registry lookup failed: ${err.message}` }] };
372
493
  }
373
494
  }
374
-
495
+
375
496
  default:
376
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
497
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}. Available: discover_servers, audit_package, submit_report, check_package` }] };
377
498
  }
378
499
  });
379
500
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {