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 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`) |
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }