crukx-mcp 0.1.2 → 0.1.4

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 CHANGED
@@ -1,8 +1,12 @@
1
1
  # crukx-mcp
2
2
 
3
- Crukx MCP server reliability control plane for autonomous software engineering.
3
+ > AI-powered code reliability engine for any MCP-compatible coding agent.
4
4
 
5
- Connect Cursor, Claude Desktop, or any MCP-compatible IDE to the Crukx 7-agent swarm.
5
+ Crukx deploys a **7-agent swarm** (Stresser, Hunter, Red Teamer, Auditor, Scout, Stabilizer, Synthesizer) to audit your code for reliability, security, and correctness — directly inside your AI coding tool.
6
+
7
+ Works with **VS Code + GitHub Copilot**, **Kiro**, **Cursor**, **Claude Desktop**, **opencode**, and any other MCP-compatible client.
8
+
9
+ ---
6
10
 
7
11
  ## Install
8
12
 
@@ -10,14 +14,80 @@ Connect Cursor, Claude Desktop, or any MCP-compatible IDE to the Crukx 7-agent s
10
14
  npm install -g crukx-mcp
11
15
  ```
12
16
 
13
- ## Usage
17
+ ## Login
14
18
 
15
- ### Login
16
19
  ```bash
17
20
  crukx login
21
+ # Opens crukx.dev in your browser — sign in and return to terminal
22
+ ```
23
+
24
+ ## Add to your editor
25
+
26
+ ```bash
27
+ crukx install
28
+ # Auto-detects VS Code, Cursor, Claude Desktop, Kiro, opencode
29
+ # and configures them all in one step
30
+ ```
31
+
32
+ Restart your editor. Done — your AI agent can now use Crukx tools.
33
+
34
+ ---
35
+
36
+ ## Manual setup (if needed)
37
+
38
+ ### VS Code + GitHub Copilot
39
+
40
+ Add to `.vscode/mcp.json` in your workspace (or `~/.vscode/mcp.json` globally):
41
+
42
+ ```json
43
+ {
44
+ "servers": {
45
+ "crukx": {
46
+ "type": "stdio",
47
+ "command": "crukx",
48
+ "args": ["mcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Then in VS Code: open the Chat panel → Agent mode → select **crukx** from the tools list.
55
+
56
+ ### Kiro (Amazon)
57
+
58
+ Add to `~/.kiro/settings/mcp.json`:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "crukx": {
64
+ "command": "crukx",
65
+ "args": ["mcp"],
66
+ "env": {}
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Cursor
73
+
74
+ Add to `~/.cursor/mcp.json`:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "crukx": {
80
+ "command": "crukx",
81
+ "args": ["mcp"]
82
+ }
83
+ }
84
+ }
18
85
  ```
19
86
 
20
- ### Add to Cursor / Claude Desktop
87
+ ### Claude Desktop
88
+
89
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
90
+ or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
21
91
 
22
92
  ```json
23
93
  {
@@ -30,18 +100,113 @@ crukx login
30
100
  }
31
101
  ```
32
102
 
103
+ ### opencode
104
+
105
+ Add to `~/.config/opencode/config.json`:
106
+
107
+ ```json
108
+ {
109
+ "mcp": {
110
+ "crukx": {
111
+ "command": "crukx",
112
+ "args": ["mcp"],
113
+ "type": "local"
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### Any other MCP client
120
+
121
+ Use `stdio` transport with:
122
+ - **Command:** `crukx`
123
+ - **Args:** `["mcp"]`
124
+
125
+ ---
126
+
33
127
  ## Tools
34
128
 
35
- | Tool | Description |
36
- |------|-------------|
37
- | `swarm_audit` | Full 7-agent reliability audit |
38
- | `security_scan` | Targeted security analysis |
39
- | `reliability_score` | Quick reliability score |
40
- | `report_fetch` | Fetch audit report by ID |
41
- | `suggest_fix` | Remediation suggestions |
42
- | `crukx_health` | Check gateway connectivity |
129
+ Once connected, your AI agent can use these tools automatically:
130
+
131
+ ### `swarm_audit`
132
+ Full 7-agent reliability audit. Returns a score (0–100), grade (A–F), gate status, and per-finding fix suggestions.
133
+
134
+ ```
135
+ "Audit this diff for me"
136
+ "Review the code I just wrote"
137
+ "Run a full reliability check on this PR"
138
+ ```
139
+
140
+ ### `security_scan`
141
+ Targeted security analysis — injection attacks, hardcoded secrets, auth flaws, OWASP Top 10.
142
+
143
+ ```
144
+ "Is this code secure?"
145
+ "Check for SQL injection in this query"
146
+ "Scan my auth handler for vulnerabilities"
147
+ ```
148
+
149
+ ### `reliability_score`
150
+ Fast pass/fail gate — score + critical/high count only. No full findings list.
151
+
152
+ ```
153
+ "Quick score before I commit"
154
+ "Is this safe to merge?"
155
+ ```
156
+
157
+ ### `suggest_fix`
158
+ Concrete remediation for a specific issue or finding.
159
+
160
+ ```
161
+ "How do I fix this SQL injection?"
162
+ "Give me a fix for the finding in swarm_audit"
163
+ ```
164
+
165
+ ### `report_fetch`
166
+ Retrieve a previous audit report by ID.
167
+
168
+ ```
169
+ "Show me report abc-123"
170
+ "Fetch the last audit"
171
+ ```
172
+
173
+ ### `crukx_health`
174
+ Check gateway connectivity and auth status.
175
+
176
+ ```
177
+ "Is Crukx connected?"
178
+ "Why are the tools not working?"
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Example output
184
+
185
+ ```
186
+ # Crukx Audit Report
187
+
188
+ **Score:** 74/100 (Grade C)
189
+ **Gate:** 🟠 REVIEW — high severity issues found
190
+ **Confidence:** 91% | **Agents:** Stresser, Hunter, Red Teamer, Auditor, Scout, Stabilizer, Synthesizer
191
+ **Report ID:** `a3f9b2c1-...`
192
+
193
+ ## Severity Breakdown
194
+ | Severity | Count |
195
+ |------------|-------|
196
+ | 🔴 Critical | 0 |
197
+ | 🟠 High | 2 |
198
+ | 🟡 Medium | 5 |
199
+ | 🔵 Low | 3 |
200
+ | ⚪ Info | 1 |
201
+
202
+ ## 🟠 High Severity
203
+ - **SQL_INJECTION** — `src/db.ts:42`: User input concatenated into query
204
+ > Fix: Use parameterized queries — `db.query('SELECT * FROM users WHERE id = $1', [userId])`
205
+ ```
206
+
207
+ ---
43
208
 
44
209
  ## Links
45
210
 
46
- - [crukx.dev](https://crukx.dev)
211
+ - [crukx.dev](https://crukx.dev) — Dashboard and reports
47
212
  - [GitHub](https://github.com/Shafwansafi06/observability-hub)
@@ -0,0 +1,14 @@
1
+ import {
2
+ CONFIG_PATH,
3
+ clearAuth,
4
+ isTokenExpired,
5
+ loadAuth,
6
+ saveAuth
7
+ } from "./chunk-SC34H3N5.js";
8
+ export {
9
+ CONFIG_PATH,
10
+ clearAuth,
11
+ isTokenExpired,
12
+ loadAuth,
13
+ saveAuth
14
+ };
package/dist/bin/cli.js CHANGED
@@ -59,7 +59,7 @@ import { fileURLToPath } from "url";
59
59
  import { join as join2, dirname as pathDirname } from "path";
60
60
  var __dirname = pathDirname(fileURLToPath(import.meta.url));
61
61
  var program = new Command();
62
- program.name("crukx").description("Crukx CLI \u2014 reliability control plane for autonomous software engineering").version("0.1.2");
62
+ program.name("crukx").description("Crukx CLI \u2014 reliability control plane for autonomous software engineering").version("0.1.4");
63
63
  program.command("login").description("Authenticate with Crukx via browser").option("--api-url <url>", "Crukx gateway URL", getConfig().CRUKX_API_URL).action(async (opts) => {
64
64
  process.stderr.write("Opening browser to complete login\u2026\n");
65
65
  let startData;
@@ -137,6 +137,103 @@ Expires: ${auth.expires_at}
137
137
  API: ${auth.api_url}
138
138
  `);
139
139
  });
140
+ program.command("install").description("Auto-install Crukx into all detected MCP-compatible clients").option("--dry-run", "Show what would be changed without writing").action(async (opts) => {
141
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync: writeFileSync2 } = await import("fs");
142
+ const { homedir: homedir2 } = await import("os");
143
+ const home = homedir2();
144
+ const SERVER_ENTRY = { command: "crukx", args: ["mcp"] };
145
+ function injectMcpServers(raw, key) {
146
+ const cfg = raw ? JSON.parse(raw) : {};
147
+ cfg.mcpServers = { ...cfg.mcpServers ?? {}, [key]: SERVER_ENTRY };
148
+ return JSON.stringify(cfg, null, 2);
149
+ }
150
+ const clients = [
151
+ {
152
+ name: "VS Code (GitHub Copilot)",
153
+ configPath: join2(home, ".vscode", "mcp.json"),
154
+ detect: () => existsSync3(join2(home, ".vscode")) || existsSync3("/usr/share/code") || existsSync3("/Applications/Visual Studio Code.app"),
155
+ inject: (raw) => {
156
+ const cfg = raw ? JSON.parse(raw) : {};
157
+ cfg.servers = { ...cfg.servers ?? {}, crukx: { type: "stdio", ...SERVER_ENTRY } };
158
+ return JSON.stringify(cfg, null, 2);
159
+ }
160
+ },
161
+ {
162
+ name: "Cursor",
163
+ configPath: join2(home, ".cursor", "mcp.json"),
164
+ detect: () => existsSync3(join2(home, ".cursor")) || existsSync3("/Applications/Cursor.app"),
165
+ inject: (raw) => injectMcpServers(raw, "crukx")
166
+ },
167
+ {
168
+ name: "Claude Desktop",
169
+ configPath: process.platform === "darwin" ? join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : join2(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
170
+ detect: () => existsSync3("/Applications/Claude.app") || existsSync3(join2(home, "Library", "Application Support", "Claude")) || existsSync3(join2(home, "AppData", "Local", "AnthropicClaude")),
171
+ inject: (raw) => injectMcpServers(raw, "crukx")
172
+ },
173
+ {
174
+ name: "Kiro",
175
+ configPath: join2(home, ".kiro", "settings", "mcp.json"),
176
+ detect: () => existsSync3(join2(home, ".kiro")),
177
+ inject: (raw) => injectMcpServers(raw, "crukx")
178
+ },
179
+ {
180
+ name: "opencode",
181
+ configPath: join2(home, ".config", "opencode", "config.json"),
182
+ detect: () => existsSync3(join2(home, ".config", "opencode")),
183
+ inject: (raw) => {
184
+ const cfg = raw ? JSON.parse(raw) : {};
185
+ cfg.mcp = { ...cfg.mcp ?? {}, crukx: { ...SERVER_ENTRY, type: "local" } };
186
+ return JSON.stringify(cfg, null, 2);
187
+ }
188
+ }
189
+ ];
190
+ const detected = clients.filter((c) => c.detect());
191
+ if (detected.length === 0) {
192
+ process.stdout.write(
193
+ "No supported MCP clients detected.\n\nSupported: VS Code, Cursor, Claude Desktop, Kiro, opencode\n\nManual setup: https://crukx.dev/docs/mcp\n"
194
+ );
195
+ return;
196
+ }
197
+ process.stdout.write(`Found ${detected.length} client${detected.length > 1 ? "s" : ""}:
198
+
199
+ `);
200
+ let installed = 0;
201
+ for (const client of detected) {
202
+ const raw = existsSync3(client.configPath) ? readFileSync3(client.configPath, "utf-8") : null;
203
+ try {
204
+ const parsed = raw ? JSON.parse(raw) : {};
205
+ if (parsed?.mcpServers?.crukx || parsed?.servers?.crukx || parsed?.mcp?.crukx) {
206
+ process.stdout.write(` \u2705 ${client.name} \u2014 already configured
207
+ `);
208
+ continue;
209
+ }
210
+ } catch {
211
+ }
212
+ const newContent = client.inject(raw);
213
+ if (opts.dryRun) {
214
+ process.stdout.write(` \u{1F4DD} ${client.name} \u2014 would write: ${client.configPath}
215
+ `);
216
+ } else {
217
+ mkdirSync2(dirname2(client.configPath), { recursive: true });
218
+ writeFileSync2(client.configPath, newContent, "utf-8");
219
+ process.stdout.write(` \u2705 ${client.name} \u2014 configured
220
+ `);
221
+ installed++;
222
+ }
223
+ }
224
+ process.stdout.write("\n");
225
+ if (opts.dryRun) {
226
+ process.stdout.write("Dry run complete. Remove --dry-run to apply.\n");
227
+ } else if (installed > 0) {
228
+ process.stdout.write(
229
+ `Installed in ${installed} client${installed > 1 ? "s" : ""}. Restart your editor, then ask:
230
+ "Run a Crukx audit on this file"
231
+ `
232
+ );
233
+ } else {
234
+ process.stdout.write("All clients already configured.\n");
235
+ }
236
+ });
140
237
  program.command("mcp").description("Start the Crukx MCP server (stdio transport)").action(() => {
141
238
  const serverPath = join2(__dirname, "server.js");
142
239
  const child = spawn(process.execPath, [serverPath], {
@@ -0,0 +1,59 @@
1
+ // src/auth/index.ts
2
+ import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
3
+ import { dirname } from "path";
4
+
5
+ // src/config/index.ts
6
+ import "dotenv/config";
7
+ import { z } from "zod";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ import { readFileSync, existsSync } from "fs";
11
+ var ConfigSchema = z.object({
12
+ CRUKX_API_URL: z.string().url().default("https://crukx-gateway.salmonisland-7ebc5692.centralindia.azurecontainerapps.io"),
13
+ CRUKX_ACCESS_TOKEN: z.string().optional(),
14
+ CRUKX_WORKSPACE_ID: z.string().optional()
15
+ });
16
+ function loadLocalConfig() {
17
+ const configPath = join(homedir(), ".crukx", "config.json");
18
+ if (!existsSync(configPath)) return {};
19
+ try {
20
+ return JSON.parse(readFileSync(configPath, "utf-8"));
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+ function getConfig() {
26
+ const local = loadLocalConfig();
27
+ return ConfigSchema.parse({ ...local, ...process.env });
28
+ }
29
+ var CONFIG_PATH = join(homedir(), ".crukx", "config.json");
30
+
31
+ // src/auth/index.ts
32
+ function saveAuth(auth) {
33
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
34
+ const existing = loadAuth() ?? {};
35
+ writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, ...auth }, null, 2));
36
+ }
37
+ function loadAuth() {
38
+ if (!existsSync2(CONFIG_PATH)) return null;
39
+ try {
40
+ return JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function clearAuth() {
46
+ if (existsSync2(CONFIG_PATH)) writeFileSync(CONFIG_PATH, "{}");
47
+ }
48
+ function isTokenExpired(auth) {
49
+ return new Date(auth.expires_at) <= new Date(Date.now() + 6e4);
50
+ }
51
+
52
+ export {
53
+ getConfig,
54
+ CONFIG_PATH,
55
+ saveAuth,
56
+ loadAuth,
57
+ clearAuth,
58
+ isTokenExpired
59
+ };
package/dist/server.js CHANGED
@@ -1,81 +1,46 @@
1
+ import {
2
+ getConfig,
3
+ loadAuth
4
+ } from "./chunk-SC34H3N5.js";
5
+
1
6
  // src/server.ts
2
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
9
 
5
10
  // src/tools/index.ts
6
- import { z as z3 } from "zod";
7
-
8
- // src/auth/index.ts
9
- import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
10
- import { dirname } from "path";
11
-
12
- // src/config/index.ts
13
- import "dotenv/config";
14
- import { z } from "zod";
15
- import { homedir } from "os";
16
- import { join } from "path";
17
- import { readFileSync, existsSync } from "fs";
18
- var ConfigSchema = z.object({
19
- CRUKX_API_URL: z.string().url().default("https://crukx-gateway.salmonisland-7ebc5692.centralindia.azurecontainerapps.io"),
20
- CRUKX_ACCESS_TOKEN: z.string().optional(),
21
- CRUKX_WORKSPACE_ID: z.string().optional()
22
- });
23
- function loadLocalConfig() {
24
- const configPath = join(homedir(), ".crukx", "config.json");
25
- if (!existsSync(configPath)) return {};
26
- try {
27
- return JSON.parse(readFileSync(configPath, "utf-8"));
28
- } catch {
29
- return {};
30
- }
31
- }
32
- function getConfig() {
33
- const local = loadLocalConfig();
34
- return ConfigSchema.parse({ ...local, ...process.env });
35
- }
36
- var CONFIG_PATH = join(homedir(), ".crukx", "config.json");
37
-
38
- // src/auth/index.ts
39
- function loadAuth() {
40
- if (!existsSync2(CONFIG_PATH)) return null;
41
- try {
42
- return JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
43
- } catch {
44
- return null;
45
- }
46
- }
11
+ import { z as z2 } from "zod";
47
12
 
48
13
  // src/api/client.ts
49
- import { z as z2 } from "zod";
14
+ import { z } from "zod";
50
15
  import { randomUUID } from "crypto";
51
16
  var TIMEOUT_MS = 6e4;
52
- var AuditResultSchema = z2.object({
53
- auditId: z2.string(),
54
- findings: z2.array(z2.object({
55
- id: z2.string(),
56
- rule: z2.string(),
57
- severity: z2.enum(["critical", "high", "medium", "low", "info"]),
58
- message: z2.string(),
59
- file: z2.string(),
60
- line: z2.number(),
61
- suggestion: z2.string().optional(),
62
- confidence: z2.number(),
63
- category: z2.string()
17
+ var AuditResultSchema = z.object({
18
+ auditId: z.string(),
19
+ findings: z.array(z.object({
20
+ id: z.string(),
21
+ rule: z.string(),
22
+ severity: z.enum(["critical", "high", "medium", "low", "info"]),
23
+ message: z.string(),
24
+ file: z.string(),
25
+ line: z.number(),
26
+ suggestion: z.string().optional(),
27
+ confidence: z.number(),
28
+ category: z.string()
64
29
  })),
65
- summary: z2.object({
66
- totalFindings: z2.number(),
67
- criticalCount: z2.number(),
68
- highCount: z2.number(),
69
- mediumCount: z2.number(),
70
- lowCount: z2.number(),
71
- infoCount: z2.number(),
72
- reliabilityScore: z2.number(),
73
- confidence: z2.number()
30
+ summary: z.object({
31
+ totalFindings: z.number(),
32
+ criticalCount: z.number(),
33
+ highCount: z.number(),
34
+ mediumCount: z.number(),
35
+ lowCount: z.number(),
36
+ infoCount: z.number(),
37
+ reliabilityScore: z.number(),
38
+ confidence: z.number()
74
39
  }),
75
- metadata: z2.object({
76
- processingTimeMs: z2.number(),
77
- agentsUsed: z2.array(z2.string()),
78
- swarmId: z2.string()
40
+ metadata: z.object({
41
+ processingTimeMs: z.number(),
42
+ agentsUsed: z.array(z.string()),
43
+ swarmId: z.string()
79
44
  })
80
45
  });
81
46
  async function request(path, init = {}) {
@@ -138,120 +103,320 @@ async function healthCheck() {
138
103
  }
139
104
 
140
105
  // src/tools/index.ts
141
- function formatFindings(result) {
106
+ function gate(score, critical, high) {
107
+ if (critical > 0) return "\u{1F534} BLOCKED \u2014 critical issues must be fixed";
108
+ if (high > 0) return "\u{1F7E0} REVIEW \u2014 high severity issues found";
109
+ if (score >= 85) return "\u{1F7E2} PASSED";
110
+ if (score >= 70) return "\u{1F7E1} PASSED WITH WARNINGS";
111
+ return "\u{1F7E0} REVIEW \u2014 score below threshold";
112
+ }
113
+ function grade(score) {
114
+ if (score >= 90) return "A";
115
+ if (score >= 80) return "B";
116
+ if (score >= 70) return "C";
117
+ if (score >= 60) return "D";
118
+ return "F";
119
+ }
120
+ function formatFullAudit(result) {
142
121
  const { summary, findings, metadata } = result;
143
- const grade = summary.reliabilityScore >= 90 ? "A" : summary.reliabilityScore >= 80 ? "B" : summary.reliabilityScore >= 70 ? "C" : "D";
144
122
  const lines = [
145
- `## Crukx Reliability Score: ${summary.reliabilityScore}/100 (Grade ${grade})`,
146
- `Confidence: ${summary.confidence}% | Agents: ${metadata.agentsUsed.join(", ")} | Time: ${metadata.processingTimeMs}ms`,
123
+ `# Crukx Audit Report`,
147
124
  ``,
125
+ `**Score:** ${summary.reliabilityScore}/100 (Grade ${grade(summary.reliabilityScore)})`,
126
+ `**Gate:** ${gate(summary.reliabilityScore, summary.criticalCount, summary.highCount)}`,
127
+ `**Confidence:** ${summary.confidence}% | **Agents:** ${metadata.agentsUsed.join(", ")} | **Time:** ${metadata.processingTimeMs}ms`,
128
+ `**Report ID:** \`${result.auditId}\``,
129
+ ``,
130
+ `## Severity Breakdown`,
148
131
  `| Severity | Count |`,
149
132
  `|----------|-------|`,
150
- `| Critical | ${summary.criticalCount} |`,
151
- `| High | ${summary.highCount} |`,
152
- `| Medium | ${summary.mediumCount} |`,
153
- `| Low | ${summary.lowCount} |`,
154
- `| Info | ${summary.infoCount} |`
133
+ `| \u{1F534} Critical | ${summary.criticalCount} |`,
134
+ `| \u{1F7E0} High | ${summary.highCount} |`,
135
+ `| \u{1F7E1} Medium | ${summary.mediumCount} |`,
136
+ `| \u{1F535} Low | ${summary.lowCount} |`,
137
+ `| \u26AA Info | ${summary.infoCount} |`
155
138
  ];
156
- if (findings.length > 0) {
157
- lines.push("", "### Findings");
158
- for (const f of findings.slice(0, 20)) {
159
- lines.push(`- **[${f.severity.toUpperCase()}]** \`${f.file}:${f.line}\` \u2014 ${f.message}`);
160
- if (f.suggestion) lines.push(` > ${f.suggestion}`);
139
+ const critical = findings.filter((f) => f.severity === "critical");
140
+ const high = findings.filter((f) => f.severity === "high");
141
+ const rest = findings.filter((f) => f.severity !== "critical" && f.severity !== "high");
142
+ if (critical.length > 0) {
143
+ lines.push(``, `## \u{1F534} Critical Issues (must fix)`);
144
+ for (const f of critical) {
145
+ lines.push(``, `### ${f.rule}`);
146
+ lines.push(`**File:** \`${f.file}:${f.line}\``);
147
+ lines.push(`**Issue:** ${f.message}`);
148
+ if (f.suggestion) lines.push(`**Fix:** ${f.suggestion}`);
149
+ }
150
+ }
151
+ if (high.length > 0) {
152
+ lines.push(``, `## \u{1F7E0} High Severity`);
153
+ for (const f of high) {
154
+ lines.push(`- **${f.rule}** \u2014 \`${f.file}:${f.line}\`: ${f.message}`);
155
+ if (f.suggestion) lines.push(` > Fix: ${f.suggestion}`);
156
+ }
157
+ }
158
+ if (rest.length > 0) {
159
+ const shown = rest.slice(0, 10);
160
+ lines.push(``, `## Other Findings (${rest.length} total)`);
161
+ for (const f of shown) {
162
+ const icon = f.severity === "medium" ? "\u{1F7E1}" : f.severity === "low" ? "\u{1F535}" : "\u26AA";
163
+ lines.push(`- ${icon} **[${f.severity}]** \`${f.file}:${f.line}\` \u2014 ${f.message}`);
161
164
  }
162
- if (findings.length > 20) lines.push(` ... and ${findings.length - 20} more`);
165
+ if (rest.length > 10) lines.push(`- ... and ${rest.length - 10} more`);
166
+ }
167
+ if (findings.length === 0) {
168
+ lines.push(``, `## \u2705 No issues found`);
169
+ lines.push(`The code passed all ${metadata.agentsUsed.length} agent checks.`);
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ function formatSecurityScan(result) {
174
+ const secFindings = result.findings.filter(
175
+ (f) => f.category === "security" || f.severity === "critical" || f.severity === "high"
176
+ );
177
+ if (secFindings.length === 0) {
178
+ return [
179
+ `# Security Scan \u2014 \u2705 Clean`,
180
+ ``,
181
+ `No security issues detected. Score: ${result.summary.reliabilityScore}/100`,
182
+ `Checked by: ${result.metadata.agentsUsed.join(", ")}`
183
+ ].join("\n");
184
+ }
185
+ const lines = [
186
+ `# Security Scan \u2014 ${secFindings.length} issue${secFindings.length > 1 ? "s" : ""} found`,
187
+ ``,
188
+ `**Gate:** ${gate(result.summary.reliabilityScore, result.summary.criticalCount, result.summary.highCount)}`,
189
+ ``
190
+ ];
191
+ for (const f of secFindings) {
192
+ const icon = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : "\u{1F7E1}";
193
+ lines.push(`## ${icon} ${f.rule} [${f.severity.toUpperCase()}]`);
194
+ lines.push(`**Location:** \`${f.file}:${f.line}\``);
195
+ lines.push(`**Issue:** ${f.message}`);
196
+ if (f.suggestion) lines.push(`**Remediation:** ${f.suggestion}`);
197
+ lines.push(``);
163
198
  }
164
199
  return lines.join("\n");
165
200
  }
166
201
  function registerTools(server2) {
167
202
  server2.tool(
168
203
  "swarm_audit",
169
- "Run a full Crukx 7-agent swarm audit on code. Returns reliability score, findings, and recommendations.",
204
+ [
205
+ "Run a full Crukx 7-agent reliability audit on code.",
206
+ "",
207
+ "Deploys 6 parallel agents (Stresser, Hunter, Red Teamer, Auditor, Scout, Stabilizer)",
208
+ "then a Synthesizer to produce a reliability score (0\u2013100), severity-graded findings,",
209
+ "and actionable fix suggestions for each issue.",
210
+ "",
211
+ "Use this tool when:",
212
+ "- The user asks to review, audit, or check code quality",
213
+ "- Before merging a PR or committing significant changes",
214
+ "- When the user wants a comprehensive reliability report",
215
+ "",
216
+ "Pass the code diff, snippet, or describe the file/repo to audit.",
217
+ "Returns: score, grade (A\u2013F), gate status (PASSED/BLOCKED), and per-finding fixes."
218
+ ].join("\n"),
170
219
  {
171
- diff: z3.string().optional().describe("Unified diff or code snippet to audit"),
172
- repoPath: z3.string().optional().describe("Repository path or name for context"),
173
- focus: z3.string().optional().describe('Specific area to focus on (e.g. "security", "concurrency")')
220
+ diff: z2.string().optional().describe(
221
+ "The code to audit. Accepts: unified diff, raw code snippet, or a description of what to check. Example: paste the output of `git diff HEAD`."
222
+ ),
223
+ repoPath: z2.string().optional().describe(
224
+ 'Repository name or path for context. Example: "my-app/src/auth.ts".'
225
+ ),
226
+ focus: z2.string().optional().describe(
227
+ 'Narrow the audit to a specific concern. Examples: "security", "concurrency", "error handling", "SQL injection".'
228
+ )
174
229
  },
175
230
  async ({ diff, repoPath, focus }) => {
176
- const result = await swarmAudit({ diff, repoPath, focus });
177
- return { content: [{ type: "text", text: formatFindings(result) }] };
231
+ try {
232
+ const result = await swarmAudit({ diff, repoPath, focus });
233
+ return { content: [{ type: "text", text: formatFullAudit(result) }] };
234
+ } catch (err) {
235
+ return { content: [{ type: "text", text: `\u274C Audit failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
236
+ }
178
237
  }
179
238
  );
180
239
  server2.tool(
181
240
  "security_scan",
182
- "Run a targeted security scan using the Red Teamer and Hunter agents.",
241
+ [
242
+ "Run a targeted security analysis using the Red Teamer and Hunter agents.",
243
+ "",
244
+ "Detects: injection attacks (SQL, command, prompt), hardcoded secrets/API keys,",
245
+ "authentication and authorization flaws, insecure dependencies, XSS, CSRF,",
246
+ "path traversal, and other OWASP Top 10 vulnerabilities.",
247
+ "",
248
+ "Use this tool when:",
249
+ '- The user asks "is this secure?" or "check for vulnerabilities"',
250
+ "- Code handles user input, auth, file I/O, or external APIs",
251
+ "- You spot patterns that could be security risks",
252
+ "",
253
+ "Returns: per-vulnerability findings with severity, location, and remediation steps."
254
+ ].join("\n"),
183
255
  {
184
- diff: z3.string().describe("Code diff or snippet to scan for security issues")
256
+ diff: z2.string().describe(
257
+ "Code to scan for security issues. Accepts diffs, snippets, or full files. Example: an auth handler, API route, or database query."
258
+ )
185
259
  },
186
260
  async ({ diff }) => {
187
- const result = await swarmAudit({ diff, focus: "security vulnerabilities, injection attacks, secrets, authentication flaws" });
188
- const secFindings = result.findings.filter((f) => f.category === "security" || f.severity === "critical" || f.severity === "high");
189
- const lines = secFindings.length === 0 ? ["\u2705 No security issues detected."] : secFindings.map((f) => `- **[${f.severity.toUpperCase()}]** ${f.message}
190
- File: \`${f.file}:${f.line}\`${f.suggestion ? `
191
- Fix: ${f.suggestion}` : ""}`);
192
- return { content: [{ type: "text", text: `## Security Scan Results
193
-
194
- ${lines.join("\n")}` }] };
261
+ try {
262
+ const result = await swarmAudit({
263
+ diff,
264
+ focus: "security vulnerabilities, injection attacks, hardcoded secrets, authentication flaws, OWASP Top 10"
265
+ });
266
+ return { content: [{ type: "text", text: formatSecurityScan(result) }] };
267
+ } catch (err) {
268
+ return { content: [{ type: "text", text: `\u274C Security scan failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
269
+ }
195
270
  }
196
271
  );
197
272
  server2.tool(
198
273
  "reliability_score",
199
- "Get a reliability score for a code change without full findings detail.",
274
+ [
275
+ "Get a fast reliability score and pass/fail gate for a code change.",
276
+ "",
277
+ "Returns a score (0\u2013100), grade (A\u2013F), and gate status without the full findings list.",
278
+ "Faster than swarm_audit \u2014 use this for quick pre-commit or pre-merge checks.",
279
+ "",
280
+ "Use this tool when:",
281
+ "- The user wants a quick quality check before committing",
282
+ "- You need a pass/fail signal without detailed findings",
283
+ "- Checking if a small change is safe to merge"
284
+ ].join("\n"),
200
285
  {
201
- diff: z3.string().describe("Code diff to score")
286
+ diff: z2.string().describe(
287
+ "Code diff or snippet to score. Example: paste `git diff --staged` output."
288
+ )
202
289
  },
203
290
  async ({ diff }) => {
204
- const result = await swarmAudit({ diff });
205
- const { reliabilityScore, confidence, criticalCount, highCount } = result.summary;
206
- const passed = criticalCount === 0 && highCount === 0;
207
- const text = [
208
- `**Score:** ${reliabilityScore}/100`,
209
- `**Gate:** ${passed ? "\u2705 PASSED" : "\u274C FAILED"}`,
210
- `**Confidence:** ${confidence}%`,
211
- `**Critical:** ${criticalCount} | **High:** ${highCount}`
212
- ].join("\n");
213
- return { content: [{ type: "text", text }] };
291
+ try {
292
+ const result = await swarmAudit({ diff });
293
+ const { reliabilityScore, confidence, criticalCount, highCount, mediumCount } = result.summary;
294
+ const text = [
295
+ `# Reliability Score`,
296
+ ``,
297
+ `**Score:** ${reliabilityScore}/100 (Grade ${grade(reliabilityScore)})`,
298
+ `**Gate:** ${gate(reliabilityScore, criticalCount, highCount)}`,
299
+ `**Confidence:** ${confidence}%`,
300
+ ``,
301
+ `| \u{1F534} Critical | \u{1F7E0} High | \u{1F7E1} Medium |`,
302
+ `|-------------|---------|-----------|`,
303
+ `| ${criticalCount} | ${highCount} | ${mediumCount} |`,
304
+ ``,
305
+ criticalCount > 0 || highCount > 0 ? `Run \`swarm_audit\` for full findings and fix suggestions.` : `\u2705 No blocking issues found.`
306
+ ].join("\n");
307
+ return { content: [{ type: "text", text }] };
308
+ } catch (err) {
309
+ return { content: [{ type: "text", text: `\u274C Score failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
310
+ }
214
311
  }
215
312
  );
216
313
  server2.tool(
217
- "report_fetch",
218
- "Fetch a previously generated Crukx audit report by ID.",
314
+ "suggest_fix",
315
+ [
316
+ "Get targeted remediation suggestions for a specific code issue.",
317
+ "",
318
+ "Pass the problematic code or a description of the issue.",
319
+ "The Synthesizer agent returns concrete fix suggestions with code examples where possible.",
320
+ "",
321
+ "Use this tool when:",
322
+ '- The user asks "how do I fix this?"',
323
+ "- A swarm_audit or security_scan finding needs a detailed fix",
324
+ "- The user has a specific bug or vulnerability to remediate"
325
+ ].join("\n"),
219
326
  {
220
- report_id: z3.string().uuid().describe("Audit run ID to fetch")
327
+ issue: z2.string().describe(
328
+ "The issue to fix. Can be: a code snippet with the problem, a finding message from swarm_audit, or a plain description. Example: \"SQL query built with string concatenation: `query = 'SELECT * FROM users WHERE id = ' + userId`\"."
329
+ ),
330
+ severity: z2.enum(["critical", "high", "medium", "low"]).optional().describe(
331
+ "Severity of the issue, if known. Helps prioritize the fix approach."
332
+ )
221
333
  },
222
- async ({ report_id }) => {
223
- const report = await fetchReport(report_id);
224
- return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
334
+ async ({ issue, severity }) => {
335
+ try {
336
+ const focus = `Provide concrete remediation and fix suggestions for this issue${severity ? ` (severity: ${severity})` : ""}: ${issue}`;
337
+ const result = await swarmAudit({ diff: issue, focus });
338
+ const suggestions = result.findings.filter((f) => f.suggestion).map((f) => `### ${f.rule}
339
+ **Issue:** ${f.message}
340
+ **Fix:** ${f.suggestion}`).join("\n\n");
341
+ const text = suggestions.length > 0 ? `# Remediation Suggestions
342
+
343
+ ${suggestions}` : `# \u2705 No Issues Found
344
+
345
+ The code looks clean \u2014 no specific remediation needed.`;
346
+ return { content: [{ type: "text", text }] };
347
+ } catch (err) {
348
+ return { content: [{ type: "text", text: `\u274C suggest_fix failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
349
+ }
225
350
  }
226
351
  );
227
352
  server2.tool(
228
- "suggest_fix",
229
- "Get remediation suggestions for a specific finding or code issue.",
353
+ "report_fetch",
354
+ [
355
+ "Fetch a previously generated Crukx audit report by its ID.",
356
+ "",
357
+ "Use this tool when:",
358
+ '- The user references a past audit ("show me report abc-123")',
359
+ "- You want to retrieve findings from an earlier swarm_audit run",
360
+ "- The user wants to compare two audit runs",
361
+ "",
362
+ 'The report ID is returned by swarm_audit as "Report ID".'
363
+ ].join("\n"),
230
364
  {
231
- issue: z3.string().describe("Description of the issue or paste the problematic code"),
232
- severity: z3.enum(["critical", "high", "medium", "low"]).optional().describe("Severity level")
365
+ report_id: z2.string().describe(
366
+ 'The audit run ID to fetch. This is the UUID returned by swarm_audit in the "Report ID" field.'
367
+ )
233
368
  },
234
- async ({ issue, severity }) => {
235
- const focus = `remediation and fix suggestions for: ${issue}. Severity: ${severity ?? "unknown"}`;
236
- const result = await swarmAudit({ diff: issue, focus });
237
- const suggestions = result.findings.filter((f) => f.suggestion).map((f) => `- **${f.rule}**: ${f.suggestion}`).join("\n");
238
- return {
239
- content: [{
240
- type: "text",
241
- text: suggestions.length > 0 ? `## Remediation Suggestions
242
-
243
- ${suggestions}` : "\u2705 No specific remediation needed \u2014 code looks clean."
244
- }]
245
- };
369
+ async ({ report_id }) => {
370
+ try {
371
+ const report = await fetchReport(report_id);
372
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
373
+ } catch (err) {
374
+ return { content: [{ type: "text", text: `\u274C Report not found: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
375
+ }
246
376
  }
247
377
  );
248
378
  server2.tool(
249
379
  "crukx_health",
250
- "Check Crukx gateway connectivity and authentication status.",
380
+ [
381
+ "Check Crukx gateway connectivity and authentication status.",
382
+ "",
383
+ "Use this tool when:",
384
+ "- Other Crukx tools are returning errors",
385
+ "- The user asks if Crukx is connected or working",
386
+ "- Diagnosing auth or network issues",
387
+ "",
388
+ "Returns: gateway status, auth validity, and API URL in use."
389
+ ].join("\n"),
251
390
  {},
252
391
  async () => {
253
- const result = await healthCheck();
254
- return { content: [{ type: "text", text: `Crukx gateway: ${result.status} \u2705` }] };
392
+ try {
393
+ const result = await healthCheck();
394
+ const auth = (await import("./auth-J6323WQJ.js")).loadAuth();
395
+ const text = [
396
+ `# Crukx Gateway Status`,
397
+ ``,
398
+ `**Status:** ${result.status === "ok" ? "\u2705 Connected" : "\u274C " + result.status}`,
399
+ `**Auth:** ${auth?.access_token ? "\u2705 Authenticated" : "\u274C Not logged in \u2014 run `crukx login`"}`,
400
+ `**Workspace:** ${auth?.workspace_id ?? "unknown"}`,
401
+ `**API:** ${auth?.api_url ?? "default"}`,
402
+ `**Token expires:** ${auth?.expires_at ?? "unknown"}`
403
+ ].join("\n");
404
+ return { content: [{ type: "text", text }] };
405
+ } catch (err) {
406
+ return {
407
+ content: [{
408
+ type: "text",
409
+ text: `# Crukx Gateway Status
410
+
411
+ \u274C **Unreachable**
412
+
413
+ ${err instanceof Error ? err.message : String(err)}
414
+
415
+ Run \`crukx login\` to re-authenticate.`
416
+ }],
417
+ isError: true
418
+ };
419
+ }
255
420
  }
256
421
  );
257
422
  }
@@ -259,7 +424,51 @@ ${suggestions}` : "\u2705 No specific remediation needed \u2014 code looks clean
259
424
  // src/server.ts
260
425
  var server = new McpServer({
261
426
  name: "crukx",
262
- version: "0.1.0"
427
+ version: "0.1.3"
428
+ }, {
429
+ instructions: `You have access to Crukx \u2014 a 7-agent AI reliability engine for code quality, security, and correctness.
430
+
431
+ Use these tools proactively whenever the user:
432
+ - Asks you to review, audit, or check code
433
+ - Writes or edits code that could have security issues
434
+ - Wants a reliability or quality score before merging
435
+ - Asks about bugs, vulnerabilities, or code smells
436
+ - Needs remediation suggestions for a specific issue
437
+
438
+ ## Available tools
439
+
440
+ **swarm_audit** \u2014 Full 7-agent audit (Stresser, Hunter, Red Teamer, Auditor, Scout, Stabilizer, Synthesizer).
441
+ Pass the code diff or snippet. Returns a reliability score (0\u2013100), severity breakdown, and per-finding suggestions.
442
+ Use this for comprehensive reviews.
443
+
444
+ **security_scan** \u2014 Focused security analysis using Red Teamer + Hunter agents.
445
+ Detects injection attacks, secrets in code, auth flaws, insecure dependencies, and prompt injection.
446
+ Use this when the user asks specifically about security.
447
+
448
+ **reliability_score** \u2014 Fast pass/fail gate (score + critical/high count only).
449
+ Use this for a quick check before a commit or PR merge.
450
+
451
+ **suggest_fix** \u2014 Targeted remediation for a specific issue or code snippet.
452
+ Use this when the user wants to fix a known problem.
453
+
454
+ **report_fetch** \u2014 Retrieve a full audit report by its ID.
455
+ Use this when the user references a previous audit run.
456
+
457
+ **crukx_health** \u2014 Verify gateway connectivity and auth.
458
+ Use this if tools are returning errors or the user asks about connection status.
459
+
460
+ ## When to use which tool
461
+
462
+ | Situation | Tool |
463
+ |-----------|------|
464
+ | "Review this PR / diff" | swarm_audit |
465
+ | "Is this code secure?" | security_scan |
466
+ | "Quick score before merge" | reliability_score |
467
+ | "How do I fix this?" | suggest_fix |
468
+ | "Show me audit #abc" | report_fetch |
469
+ | "Is Crukx connected?" | crukx_health |
470
+
471
+ Always show the reliability score and gate status prominently. If critical or high findings exist, list them with their suggested fixes.`
263
472
  });
264
473
  registerTools(server);
265
474
  var transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crukx-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Crukx MCP server — reliability control plane for autonomous software engineering",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",