audited-mcp 1.0.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.
- package/README.md +46 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +236 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# audited-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [audited.xyz](https://audited.xyz) — AI-powered security audits.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description | Auth required |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `list_reports` | List public audit reports (filter by project type) | No |
|
|
10
|
+
| `get_report` | Get full markdown report by slug | No |
|
|
11
|
+
| `get_findings` | Get findings with severity, description, and fixes | No |
|
|
12
|
+
| `start_audit` | Start a new audit from a GitHub URL | Yes |
|
|
13
|
+
| `check_status` | Check audit generation progress | Yes |
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"audited": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "audited-mcp"],
|
|
27
|
+
"env": {
|
|
28
|
+
"AUDITED_API_KEY": "your-api-key"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Claude Code
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
claude mcp add audited npx audited-mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Environment Variables
|
|
42
|
+
|
|
43
|
+
| Variable | Required | Description |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `AUDITED_API_KEY` | For `start_audit` | API key from audited.xyz |
|
|
46
|
+
| `AUDITED_URL` | No | Override base URL (default: `https://audited.xyz`) |
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
const BASE_URL = process.env.AUDITED_URL || "https://audited.xyz";
|
|
6
|
+
const API_KEY = process.env.AUDITED_API_KEY || "";
|
|
7
|
+
// --- API helpers ---
|
|
8
|
+
async function apiGet(path) {
|
|
9
|
+
const headers = {
|
|
10
|
+
"Accept": "application/json",
|
|
11
|
+
};
|
|
12
|
+
if (API_KEY)
|
|
13
|
+
headers["Authorization"] = `Bearer ${API_KEY}`;
|
|
14
|
+
const res = await fetch(`${BASE_URL}${path}`, { headers });
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
const body = await res.text();
|
|
17
|
+
throw new Error(`API ${res.status}: ${body}`);
|
|
18
|
+
}
|
|
19
|
+
return res.json();
|
|
20
|
+
}
|
|
21
|
+
async function apiPost(path, body) {
|
|
22
|
+
const headers = {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
"Accept": "application/json",
|
|
25
|
+
};
|
|
26
|
+
if (API_KEY)
|
|
27
|
+
headers["Authorization"] = `Bearer ${API_KEY}`;
|
|
28
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers,
|
|
31
|
+
body: JSON.stringify(body),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
throw new Error(`API ${res.status}: ${text}`);
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
// --- Tools ---
|
|
40
|
+
const tools = [
|
|
41
|
+
{
|
|
42
|
+
name: "list_reports",
|
|
43
|
+
description: "List public audit reports on audited.xyz. Returns project names, slugs, findings counts, and project types.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
type: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Filter by project type (solidity, rust, go, c, javascript, python, rails, java, etc.)",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "get_report",
|
|
56
|
+
description: "Get a full audit report by slug. Returns the markdown report content, findings, and metadata.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
slug: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "The audit report slug (e.g., 'curl', 'redis', 'uniswap-v4')",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["slug"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "get_findings",
|
|
70
|
+
description: "Get findings for a specific audit report. Returns severity, title, description, location, and recommended fix for each finding.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
slug: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "The audit report slug",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
required: ["slug"],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "start_audit",
|
|
84
|
+
description: "Start a new security audit on audited.xyz. Requires a GitHub repository URL. Returns the audit ID for status polling. Requires an API key.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
github_url: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "GitHub repository URL to audit (e.g., 'https://github.com/owner/repo')",
|
|
91
|
+
},
|
|
92
|
+
project_name: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Name for the audit (optional, auto-detected from URL)",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["github_url"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "check_status",
|
|
102
|
+
description: "Check the status of an ongoing audit. Returns status (generating, completed, failed) and progress details.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
audit_id: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "The audit ID or slug to check",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ["audit_id"],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
// --- Tool handlers ---
|
|
116
|
+
async function handleListReports(args) {
|
|
117
|
+
const params = args.type ? `?type=${encodeURIComponent(args.type)}` : "";
|
|
118
|
+
const data = await apiGet(`/api/v1/audits/public_list${params}`);
|
|
119
|
+
if (!data.audits || data.audits.length === 0) {
|
|
120
|
+
return "No public audit reports found.";
|
|
121
|
+
}
|
|
122
|
+
const lines = data.audits.map((a) => {
|
|
123
|
+
const findings = a.findings_summary || {};
|
|
124
|
+
const parts = Object.entries(findings)
|
|
125
|
+
.filter(([_, count]) => count > 0)
|
|
126
|
+
.map(([sev, count]) => `${count} ${sev}`)
|
|
127
|
+
.join(", ");
|
|
128
|
+
return `- **${a.project_name}** (${a.slug}) — ${a.project_type || "unknown"} — ${parts || "no findings"} — https://audited.xyz/report/${a.slug}`;
|
|
129
|
+
});
|
|
130
|
+
return `## Public Audit Reports (${data.audits.length})\n\n${lines.join("\n")}`;
|
|
131
|
+
}
|
|
132
|
+
async function handleGetReport(args) {
|
|
133
|
+
const data = await apiGet(`/api/v1/audits/${encodeURIComponent(args.slug)}/report`);
|
|
134
|
+
if (!data.report) {
|
|
135
|
+
return `No report found for slug: ${args.slug}`;
|
|
136
|
+
}
|
|
137
|
+
return data.report;
|
|
138
|
+
}
|
|
139
|
+
async function handleGetFindings(args) {
|
|
140
|
+
const data = await apiGet(`/api/v1/audits/${encodeURIComponent(args.slug)}/findings`);
|
|
141
|
+
if (!data.findings || data.findings.length === 0) {
|
|
142
|
+
return `No findings for ${args.slug}.`;
|
|
143
|
+
}
|
|
144
|
+
const lines = data.findings.map((f) => {
|
|
145
|
+
return `### ${f.finding_id} [${f.severity.toUpperCase()}] ${f.title}\n**Location:** ${f.location}\n**Description:** ${f.description}\n**Impact:** ${f.impact || "N/A"}\n**Recommendation:** ${f.recommendation || "N/A"}\n${f.suggested_fix ? `**Fix:**\n\`\`\`\n${f.suggested_fix}\n\`\`\`` : ""}`;
|
|
146
|
+
});
|
|
147
|
+
return `## Findings for ${args.slug} (${data.findings.length})\n\n${lines.join("\n\n---\n\n")}`;
|
|
148
|
+
}
|
|
149
|
+
async function handleStartAudit(args) {
|
|
150
|
+
if (!API_KEY) {
|
|
151
|
+
return "Error: AUDITED_API_KEY environment variable is required to start audits. Get one at https://audited.xyz/api/docs";
|
|
152
|
+
}
|
|
153
|
+
const body = { github_url: args.github_url };
|
|
154
|
+
if (args.project_name)
|
|
155
|
+
body.name = args.project_name;
|
|
156
|
+
const data = await apiPost("/api/v1/audits", body);
|
|
157
|
+
return `Audit started!\n- **ID:** ${data.id}\n- **Slug:** ${data.slug}\n- **Status:** ${data.status}\n\nPoll status with: check_status("${data.slug}")`;
|
|
158
|
+
}
|
|
159
|
+
async function handleCheckStatus(args) {
|
|
160
|
+
const data = await apiGet(`/api/v1/audits/${encodeURIComponent(args.audit_id)}`);
|
|
161
|
+
let result = `## Audit Status: ${data.project_name || args.audit_id}\n\n`;
|
|
162
|
+
result += `- **Status:** ${data.status}\n`;
|
|
163
|
+
result += `- **Slug:** ${data.slug}\n`;
|
|
164
|
+
if (data.status === "completed") {
|
|
165
|
+
result += `- **Findings:** ${data.findings_count || 0}\n`;
|
|
166
|
+
result += `- **Report:** https://audited.xyz/report/${data.slug}\n`;
|
|
167
|
+
}
|
|
168
|
+
else if (data.status === "failed") {
|
|
169
|
+
result += `- **Error:** ${data.error || "Unknown error"}\n`;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
result += `- **Progress:** Audit is being generated. Check back in a few minutes.\n`;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
// --- Server setup ---
|
|
177
|
+
const server = new Server({ name: "audited", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
178
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
179
|
+
tools,
|
|
180
|
+
}));
|
|
181
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
182
|
+
const { name, arguments: args } = request.params;
|
|
183
|
+
try {
|
|
184
|
+
let result;
|
|
185
|
+
switch (name) {
|
|
186
|
+
case "list_reports":
|
|
187
|
+
result = await handleListReports(args);
|
|
188
|
+
break;
|
|
189
|
+
case "get_report":
|
|
190
|
+
result = await handleGetReport(args);
|
|
191
|
+
break;
|
|
192
|
+
case "get_findings":
|
|
193
|
+
result = await handleGetFindings(args);
|
|
194
|
+
break;
|
|
195
|
+
case "start_audit":
|
|
196
|
+
result = await handleStartAudit(args);
|
|
197
|
+
break;
|
|
198
|
+
case "check_status":
|
|
199
|
+
result = await handleCheckStatus(args);
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
203
|
+
}
|
|
204
|
+
return { content: [{ type: "text", text: result }] };
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Resources: expose the llms.txt and public report list
|
|
214
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
215
|
+
resources: [
|
|
216
|
+
{
|
|
217
|
+
uri: "audited://info",
|
|
218
|
+
name: "audited.xyz overview",
|
|
219
|
+
description: "Overview of audited.xyz platform, features, and public reports",
|
|
220
|
+
mimeType: "text/plain",
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
}));
|
|
224
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
225
|
+
if (request.params.uri === "audited://info") {
|
|
226
|
+
const res = await fetch(`${BASE_URL}/llms.txt`);
|
|
227
|
+
const text = await res.text();
|
|
228
|
+
return {
|
|
229
|
+
contents: [{ uri: "audited://info", text, mimeType: "text/plain" }],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
233
|
+
});
|
|
234
|
+
// Start
|
|
235
|
+
const transport = new StdioServerTransport();
|
|
236
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "audited-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for audited.xyz — AI-powered security audits",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"audited-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./build/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc && node build/index.js",
|
|
13
|
+
"prepublishOnly": "tsc"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"build"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"security",
|
|
21
|
+
"audit",
|
|
22
|
+
"smart-contracts",
|
|
23
|
+
"audited"
|
|
24
|
+
],
|
|
25
|
+
"author": "zack.eth",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
+
"zod": "^3.22.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"typescript": "^5.3.0",
|
|
33
|
+
"@types/node": "^20.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|