@getdebug/mcp 0.2.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 +95 -0
- package/dist/api.js +66 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.js +63 -0
- package/dist/auth.js.map +1 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/get-finding.js +37 -0
- package/dist/tools/get-finding.js.map +1 -0
- package/dist/tools/list-findings.js +52 -0
- package/dist/tools/list-findings.js.map +1 -0
- package/dist/tools/list-fixes.js +47 -0
- package/dist/tools/list-fixes.js.map +1 -0
- package/dist/tools/list-projects.js +20 -0
- package/dist/tools/list-projects.js.map +1 -0
- package/dist/tools/start-scan.js +29 -0
- package/dist/tools/start-scan.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @getdebug/mcp
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for [getdebug](https://www.getdebug.dev). Lets Claude, Cursor, and any MCP-compatible AI client read your projects, findings, and proposed fixes through the same bearer token `getdebug login` already creates.
|
|
4
|
+
|
|
5
|
+
## Tools (v0.2)
|
|
6
|
+
|
|
7
|
+
- **`list_projects`** — projects in your active org, with last-run status + finding counts.
|
|
8
|
+
- **`list_findings`** — findings for a project, filterable by severity, with id / file / line / CWE / OWASP.
|
|
9
|
+
- **`get_finding`** — full details of one finding: explanation, snippet, CWE/OWASP refs, and the proposed-fix diff when one exists.
|
|
10
|
+
- **`list_fixes`** — proposed/applied fixes (optionally scoped to a project or status), with finding link + PR URL.
|
|
11
|
+
- **`start_scan`** — enqueue a fresh hosted analyze run on a project. Findings appear in ~1-2 min; re-call `list_findings` to fetch.
|
|
12
|
+
|
|
13
|
+
`apply_fix` lands in a future release. Today: when `get_finding` shows a proposed-fix diff, applying it still happens via the PR review flow on the dashboard.
|
|
14
|
+
|
|
15
|
+
## Auth
|
|
16
|
+
|
|
17
|
+
The server reads `~/.getdebug/config.json`, the same file `getdebug login` writes. You must:
|
|
18
|
+
|
|
19
|
+
1. Install the CLI: `npm i -g @getdebug/cli`
|
|
20
|
+
2. Run `getdebug login` once.
|
|
21
|
+
3. The config file must be `chmod 600` — the server refuses to load with looser perms, matching the CLI.
|
|
22
|
+
|
|
23
|
+
No new credentials, no extra OAuth, no `MCP_TOKEN` env var to manage.
|
|
24
|
+
|
|
25
|
+
## Setup — Claude Desktop
|
|
26
|
+
|
|
27
|
+
Add this to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent on your OS:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"getdebug": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@getdebug/mcp"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Restart Claude Desktop. The three tools appear under the 🔧 menu in any chat.
|
|
41
|
+
|
|
42
|
+
## Setup — Cursor
|
|
43
|
+
|
|
44
|
+
Cursor's MCP config lives at `~/.cursor/mcp.json` (or per-workspace at `.cursor/mcp.json`):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"getdebug": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "@getdebug/mcp"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Setup — any other MCP client
|
|
58
|
+
|
|
59
|
+
Transport is stdio. Spawn `getdebug-mcp` (or `npx @getdebug/mcp`); the server speaks MCP over the child's stdin/stdout. Stderr is for logs only.
|
|
60
|
+
|
|
61
|
+
## Local development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm install --filter @getdebug/mcp...
|
|
65
|
+
cd mcp
|
|
66
|
+
pnpm dev # tsx, hot-reload
|
|
67
|
+
pnpm typecheck
|
|
68
|
+
pnpm build && node dist/index.js
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Override the API base for staging or local dev:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
GETDEBUG_API_URL=http://localhost:3001 node dist/index.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
(The CLI honors the same env var — see `cli/internal/cmd/login.go`.)
|
|
78
|
+
|
|
79
|
+
## What the agent will see
|
|
80
|
+
|
|
81
|
+
Example: an agent asks *"any high-severity findings in my debug project?"* The tool flow:
|
|
82
|
+
|
|
83
|
+
1. `list_projects` → picks the project id matching "debug".
|
|
84
|
+
2. `list_findings({projectId, severity: "high", limit: 25})` → returns a list of HIGH findings with file paths + line numbers.
|
|
85
|
+
3. `get_finding({findingId})` for any the agent wants to drill into → returns explanation + snippet + proposed-fix diff.
|
|
86
|
+
|
|
87
|
+
The agent can now reference *your actual security findings* by file and line while answering questions or writing code — without you copying anything in.
|
|
88
|
+
|
|
89
|
+
## Privacy
|
|
90
|
+
|
|
91
|
+
This server is a thin client. It never touches your source files directly; it only reads what's already in your getdebug org via the same API the CLI and dashboard use. Run `getdebug login --logout` (or just delete `~/.getdebug/config.json`) to revoke access.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT.
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Thin fetch wrapper. Matches the api's `{ok,data} | {ok:false,error}`
|
|
2
|
+
// envelope (see CLAUDE.md "Errors are values"). The MCP tools surface
|
|
3
|
+
// the error message back to the agent verbatim so the user sees the
|
|
4
|
+
// real failure mode (not-signed-in, 404, etc) instead of a generic
|
|
5
|
+
// "tool failed" string.
|
|
6
|
+
export async function apiFetch(auth, path, init) {
|
|
7
|
+
const url = `${auth.apiBase}${path}`;
|
|
8
|
+
let res;
|
|
9
|
+
try {
|
|
10
|
+
res = await fetch(url, {
|
|
11
|
+
...init,
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${auth.token}`,
|
|
14
|
+
Accept: "application/json",
|
|
15
|
+
...(init?.body ? { "Content-Type": "application/json" } : {}),
|
|
16
|
+
...init?.headers,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
error: {
|
|
24
|
+
code: "network_error",
|
|
25
|
+
message: err instanceof Error ? err.message : String(err),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (res.status === 401) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: {
|
|
33
|
+
code: "unauthorized",
|
|
34
|
+
message: "Token rejected. Run `getdebug login` again to mint a fresh one.",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = text ? JSON.parse(text) : null;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: {
|
|
47
|
+
code: "malformed_response",
|
|
48
|
+
message: `${res.status} ${res.statusText}: non-JSON response`,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (parsed &&
|
|
53
|
+
typeof parsed === "object" &&
|
|
54
|
+
"ok" in parsed &&
|
|
55
|
+
typeof parsed.ok === "boolean") {
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: {
|
|
61
|
+
code: "unexpected_shape",
|
|
62
|
+
message: `${res.status}: response did not match {ok,data}|{ok,error} envelope`,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAYA,uEAAuE;AACvE,sEAAsE;AACtE,oEAAoE;AACpE,mEAAmE;AACnE,wBAAwB;AACxB,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAkB,EAClB,IAAY,EACZ,IAAkB;IAElB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;IACrC,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACrC,MAAM,EAAE,kBAAkB;gBAC1B,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7D,GAAG,IAAI,EAAE,OAAO;aACjB;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aAC1D;SACF,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,iEAAiE;aAC3E;SACF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,qBAAqB;aAC9D;SACF,CAAC;IACJ,CAAC;IAED,IACE,MAAM;QACN,OAAO,MAAM,KAAK,QAAQ;QAC1B,IAAI,IAAI,MAAM;QACd,OAAQ,MAA0B,CAAC,EAAE,KAAK,SAAS,EACnD,CAAC;QACD,OAAO,MAAsB,CAAC;IAChC,CAAC;IAED,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE;YACL,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,wDAAwD;SAC/E;KACF,CAAC;AACJ,CAAC"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const CONFIG_RELATIVE = ".getdebug/config.json";
|
|
5
|
+
const DEFAULT_API_BASE = "https://api.getdebug.dev";
|
|
6
|
+
export class AuthError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.name = "AuthError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Read ~/.getdebug/config.json. Mirrors cli/internal/config/config.go:
|
|
15
|
+
// - Token + apiBaseUrl + userEmail JSON shape
|
|
16
|
+
// - On Unix, refuses to load if the file isn't 0600 (matches Go's
|
|
17
|
+
// ErrInsecurePerms — the token is a long-lived bearer credential)
|
|
18
|
+
// - Returns an AuthError the caller can surface verbatim to the user
|
|
19
|
+
export function loadAuth() {
|
|
20
|
+
const path = join(homedir(), CONFIG_RELATIVE);
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
if (platform() !== "win32") {
|
|
24
|
+
const st = statSync(path);
|
|
25
|
+
const mode = st.mode & 0o777;
|
|
26
|
+
if ((mode & 0o077) !== 0) {
|
|
27
|
+
throw new AuthError(`~/.getdebug/config.json has insecure permissions (${mode.toString(8)}). Run \`chmod 600 ~/.getdebug/config.json\` and retry.`, "insecure_perms");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
raw = readFileSync(path, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof AuthError)
|
|
34
|
+
throw err;
|
|
35
|
+
if (err &&
|
|
36
|
+
typeof err === "object" &&
|
|
37
|
+
"code" in err &&
|
|
38
|
+
err.code === "ENOENT") {
|
|
39
|
+
throw new AuthError("Not signed in. Run `getdebug login` first — see https://www.getdebug.dev/docs.", "no_config");
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(raw);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new AuthError("~/.getdebug/config.json is not valid JSON. Delete it and run `getdebug login` again.", "malformed_config");
|
|
49
|
+
}
|
|
50
|
+
const token = typeof parsed.token === "string" ? parsed.token : "";
|
|
51
|
+
if (!token) {
|
|
52
|
+
throw new AuthError("~/.getdebug/config.json has no token. Run `getdebug login` again.", "no_token");
|
|
53
|
+
}
|
|
54
|
+
const apiBase = typeof parsed.apiBaseUrl === "string" && parsed.apiBaseUrl
|
|
55
|
+
? parsed.apiBaseUrl
|
|
56
|
+
: (process.env.GETDEBUG_API_URL ?? DEFAULT_API_BASE);
|
|
57
|
+
return {
|
|
58
|
+
apiBase: apiBase.replace(/\/+$/, ""),
|
|
59
|
+
token,
|
|
60
|
+
userEmail: typeof parsed.userEmail === "string" ? parsed.userEmail : undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAChD,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAQpD,MAAM,OAAO,SAAU,SAAQ,KAAK;IAGhB;IAFlB,YACE,OAAe,EACC,IAIF;QAEd,KAAK,CAAC,OAAO,CAAC,CAAC;QANC,SAAI,GAAJ,IAAI,CAIN;QAGd,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,uEAAuE;AACvE,gDAAgD;AAChD,oEAAoE;AACpE,sEAAsE;AACtE,uEAAuE;AACvE,MAAM,UAAU,QAAQ;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;IAE9C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,SAAS,CACjB,qDAAqD,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,yDAAyD,EAC9H,gBAAgB,CACjB,CAAC;YACJ,CAAC;QACH,CAAC;QACD,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,SAAS;YAAE,MAAM,GAAG,CAAC;QACxC,IACE,GAAG;YACH,OAAO,GAAG,KAAK,QAAQ;YACvB,MAAM,IAAI,GAAG;YACZ,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAChD,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,gFAAgF,EAChF,WAAW,CACZ,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,MAAsE,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CACjB,sFAAsF,EACtF,kBAAkB,CACnB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CACjB,mEAAmE,EACnE,UAAU,CACX,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU;QACxD,CAAC,CAAC,MAAM,CAAC,UAAU;QACnB,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,CAAC;IAEzD,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,KAAK;QACL,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;KAC/E,CAAC;AACJ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// getdebug MCP server. Exposes a small set of tools so Claude, Cursor,
|
|
3
|
+
// or any MCP-compatible client can read getdebug projects, findings,
|
|
4
|
+
// and fix proposals over the authenticated bearer token stored at
|
|
5
|
+
// ~/.getdebug/config.json by `getdebug login`.
|
|
6
|
+
//
|
|
7
|
+
// Transport: stdio. The host process spawns this binary and talks JSON-RPC
|
|
8
|
+
// over the child's stdin/stdout. Logs go to stderr only — anything written
|
|
9
|
+
// to stdout that isn't a valid MCP message corrupts the protocol.
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
14
|
+
import { AuthError, loadAuth } from "./auth.js";
|
|
15
|
+
import { listProjects } from "./tools/list-projects.js";
|
|
16
|
+
import { ListFindingsInput, listFindings } from "./tools/list-findings.js";
|
|
17
|
+
import { GetFindingInput, getFinding } from "./tools/get-finding.js";
|
|
18
|
+
import { ListFixesInput, listFixes } from "./tools/list-fixes.js";
|
|
19
|
+
import { StartScanInput, startScan } from "./tools/start-scan.js";
|
|
20
|
+
const SERVER_NAME = "getdebug";
|
|
21
|
+
const SERVER_VERSION = "0.2.0";
|
|
22
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
23
|
+
const tools = [
|
|
24
|
+
{
|
|
25
|
+
name: "list_projects",
|
|
26
|
+
description: "List the projects connected to the user's active getdebug org. Returns project ids, names, last-run status, and finding counts.",
|
|
27
|
+
inputSchema: { type: "object", properties: {} },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "list_findings",
|
|
31
|
+
description: "List security findings for a given project. Supports severity + limit filters. Returns id, severity, category, file path + line, and whether a fix is available.",
|
|
32
|
+
inputSchema: zodToJsonSchema(ListFindingsInput),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "get_finding",
|
|
36
|
+
description: "Fetch the full details of a single finding by id, including explanation, code snippet, CWE/OWASP refs, and the proposed-fix diff if one exists.",
|
|
37
|
+
inputSchema: zodToJsonSchema(GetFindingInput),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "list_fixes",
|
|
41
|
+
description: "List proposed/applied fixes, optionally scoped to a project or filtered by status. Each fix links back to its finding and the open pull request URL (if one was created).",
|
|
42
|
+
inputSchema: zodToJsonSchema(ListFixesInput),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "start_scan",
|
|
46
|
+
description: "Enqueue a fresh hosted analyze run on the given project. Returns the run id; new findings appear within ~1-2 minutes (re-call list_findings to fetch them).",
|
|
47
|
+
inputSchema: zodToJsonSchema(StartScanInput),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
51
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
52
|
+
try {
|
|
53
|
+
const auth = loadAuth();
|
|
54
|
+
const name = req.params.name;
|
|
55
|
+
const args = req.params.arguments ?? {};
|
|
56
|
+
if (name === "list_projects") {
|
|
57
|
+
const text = await listProjects(auth);
|
|
58
|
+
return { content: [{ type: "text", text }] };
|
|
59
|
+
}
|
|
60
|
+
if (name === "list_findings") {
|
|
61
|
+
const parsed = ListFindingsInput.parse(args);
|
|
62
|
+
const text = await listFindings(auth, parsed);
|
|
63
|
+
return { content: [{ type: "text", text }] };
|
|
64
|
+
}
|
|
65
|
+
if (name === "get_finding") {
|
|
66
|
+
const parsed = GetFindingInput.parse(args);
|
|
67
|
+
const text = await getFinding(auth, parsed);
|
|
68
|
+
return { content: [{ type: "text", text }] };
|
|
69
|
+
}
|
|
70
|
+
if (name === "list_fixes") {
|
|
71
|
+
const parsed = ListFixesInput.parse(args);
|
|
72
|
+
const text = await listFixes(auth, parsed);
|
|
73
|
+
return { content: [{ type: "text", text }] };
|
|
74
|
+
}
|
|
75
|
+
if (name === "start_scan") {
|
|
76
|
+
const parsed = StartScanInput.parse(args);
|
|
77
|
+
const text = await startScan(auth, parsed);
|
|
78
|
+
return { content: [{ type: "text", text }] };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err instanceof AuthError) {
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text", text: err.message }],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
95
|
+
isError: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const transport = new StdioServerTransport();
|
|
100
|
+
await server.connect(transport);
|
|
101
|
+
// Stderr-only log; stdout is reserved for protocol messages.
|
|
102
|
+
process.stderr.write(`${SERVER_NAME} mcp server v${SERVER_VERSION} ready\n`);
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "../api.js";
|
|
3
|
+
export const GetFindingInput = z.object({
|
|
4
|
+
findingId: z.string().min(1).describe("Finding id (from list_findings)."),
|
|
5
|
+
});
|
|
6
|
+
export async function getFinding(auth, args) {
|
|
7
|
+
const res = await apiFetch(auth, `/v1/findings/${encodeURIComponent(args.findingId)}`);
|
|
8
|
+
if (!res.ok) {
|
|
9
|
+
return `Error (${res.error.code}): ${res.error.message}`;
|
|
10
|
+
}
|
|
11
|
+
const f = res.data.finding;
|
|
12
|
+
const refs = [f.cwe, f.owasp].filter(Boolean).join(" · ");
|
|
13
|
+
const sections = [
|
|
14
|
+
`# ${f.title}`,
|
|
15
|
+
`**Severity:** ${f.severity} · **Category:** ${f.category}${refs ? ` · ${refs}` : ""}`,
|
|
16
|
+
`**Location:** ${f.filePath}:${f.lineStart}${f.lineEnd !== f.lineStart ? `-${f.lineEnd}` : ""}`,
|
|
17
|
+
"",
|
|
18
|
+
f.explanation,
|
|
19
|
+
];
|
|
20
|
+
if (f.snippet) {
|
|
21
|
+
sections.push("", "```", f.snippet, "```");
|
|
22
|
+
}
|
|
23
|
+
if (f.fix) {
|
|
24
|
+
sections.push("", `## Proposed fix (id=${f.fix.id}, status=${f.fix.status})`);
|
|
25
|
+
if (f.fix.diff) {
|
|
26
|
+
sections.push("```diff", f.fix.diff, "```");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
sections.push("_(no diff inline — fetch via /v1/fixes/:id for the patch)_");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (typeof f.confidence === "number") {
|
|
33
|
+
sections.push("", `_confidence: ${f.confidence.toFixed(2)}_`);
|
|
34
|
+
}
|
|
35
|
+
return sections.join("\n");
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=get-finding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-finding.js","sourceRoot":"","sources":["../../src/tools/get-finding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kCAAkC,CAAC;CAC1E,CAAC,CAAC;AA4BH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAkB,EAClB,IAAoB;IAEpB,MAAM,GAAG,GAAG,MAAM,QAAQ,CACxB,IAAI,EACJ,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CACrD,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;IACD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;IAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAa;QACzB,KAAK,CAAC,CAAC,KAAK,EAAE;QACd,iBAAiB,CAAC,CAAC,QAAQ,oBAAoB,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;QACtF,iBAAiB,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/F,EAAE;QACF,CAAC,CAAC,WAAW;KACd,CAAC;IACF,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QACV,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9E,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "../api.js";
|
|
3
|
+
export const ListFindingsInput = z.object({
|
|
4
|
+
projectId: z.string().min(1).describe("Project id (from list_projects)."),
|
|
5
|
+
severity: z
|
|
6
|
+
.enum(["critical", "high", "medium", "low", "info"])
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Filter to a single severity. Omit for all severities."),
|
|
9
|
+
limit: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(100)
|
|
14
|
+
.default(25)
|
|
15
|
+
.describe("Max number of findings to return (1-100)."),
|
|
16
|
+
});
|
|
17
|
+
export async function listFindings(auth, args) {
|
|
18
|
+
const params = new URLSearchParams({
|
|
19
|
+
projectId: args.projectId,
|
|
20
|
+
limit: String(args.limit),
|
|
21
|
+
});
|
|
22
|
+
if (args.severity)
|
|
23
|
+
params.set("severity", args.severity);
|
|
24
|
+
const res = await apiFetch(auth, `/v1/findings?${params.toString()}`);
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
return `Error (${res.error.code}): ${res.error.message}`;
|
|
27
|
+
}
|
|
28
|
+
const rows = res.data.findings;
|
|
29
|
+
if (rows.length === 0) {
|
|
30
|
+
return `No findings for project ${args.projectId}${args.severity ? ` at severity ${args.severity}` : ""}.`;
|
|
31
|
+
}
|
|
32
|
+
const header = `Showing ${rows.length}${typeof res.data.total === "number" ? ` of ${res.data.total}` : ""} findings for project ${args.projectId}.`;
|
|
33
|
+
const lines = rows.map((f) => {
|
|
34
|
+
const refs = [f.cwe, f.owasp].filter(Boolean).join(" · ");
|
|
35
|
+
const tail = [
|
|
36
|
+
f.hasFix ? "fix available" : null,
|
|
37
|
+
f.suppressed ? "suppressed" : null,
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(" · ");
|
|
41
|
+
return [
|
|
42
|
+
`- [${f.severity.toUpperCase()}] ${f.title}`,
|
|
43
|
+
` id=${f.id} · ${f.filePath}:${f.lineStart}${f.lineEnd !== f.lineStart ? `-${f.lineEnd}` : ""}`,
|
|
44
|
+
refs ? ` ${refs}` : null,
|
|
45
|
+
tail ? ` ${tail}` : null,
|
|
46
|
+
]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join("\n");
|
|
49
|
+
});
|
|
50
|
+
return [header, "", ...lines].join("\n");
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=list-findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-findings.js","sourceRoot":"","sources":["../../src/tools/list-findings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "../api.js";
|
|
3
|
+
export const ListFixesInput = z.object({
|
|
4
|
+
projectId: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Project id (from list_projects). Omit to list fixes across the whole org."),
|
|
9
|
+
status: z
|
|
10
|
+
.enum(["proposed", "applied", "rejected", "superseded", "manual"])
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Filter to a single fix status. Omit for all statuses."),
|
|
13
|
+
limit: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(100)
|
|
18
|
+
.default(25)
|
|
19
|
+
.describe("Max number of fixes to return (1-100)."),
|
|
20
|
+
});
|
|
21
|
+
export async function listFixes(auth, args) {
|
|
22
|
+
const params = new URLSearchParams({ limit: String(args.limit) });
|
|
23
|
+
if (args.projectId)
|
|
24
|
+
params.set("projectId", args.projectId);
|
|
25
|
+
if (args.status)
|
|
26
|
+
params.set("status", args.status);
|
|
27
|
+
const res = await apiFetch(auth, `/v1/fixes?${params.toString()}`);
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
return `Error (${res.error.code}): ${res.error.message}`;
|
|
30
|
+
}
|
|
31
|
+
const rows = res.data.fixes;
|
|
32
|
+
if (rows.length === 0) {
|
|
33
|
+
const scope = args.projectId ? `project ${args.projectId}` : "your org";
|
|
34
|
+
return `No fixes for ${scope}${args.status ? ` with status ${args.status}` : ""}.`;
|
|
35
|
+
}
|
|
36
|
+
const header = `Showing ${rows.length} fix${rows.length === 1 ? "" : "es"}.`;
|
|
37
|
+
const lines = rows.map((f) => {
|
|
38
|
+
const pr = f.pullRequest ? `\n PR: ${f.pullRequest.url} (${f.pullRequest.status ?? "open"})` : "";
|
|
39
|
+
return [
|
|
40
|
+
`- [${f.finding.severity.toUpperCase()} · ${f.status}] ${f.finding.title}`,
|
|
41
|
+
` fix=${f.id} · finding=${f.finding.id}`,
|
|
42
|
+
` ${f.project.name} · ${f.finding.filePath}:${f.finding.lineStart}${pr}`,
|
|
43
|
+
].join("\n");
|
|
44
|
+
});
|
|
45
|
+
return [header, "", ...lines].join("\n");
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=list-fixes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-fixes.js","sourceRoot":"","sources":["../../src/tools/list-fixes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,2EAA2E,CAAC;IACxF,MAAM,EAAE,CAAC;SACN,IAAI,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;SACjE,QAAQ,EAAE;SACV,QAAQ,CAAC,uDAAuD,CAAC;IACpE,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,wCAAwC,CAAC;CACtD,CAAC,CAAC;AA8BH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAkB,EAClB,IAAmB;IAEnB,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClE,IAAI,IAAI,CAAC,SAAS;QAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,MAAM;QAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,QAAQ,CACxB,IAAI,EACJ,aAAa,MAAM,CAAC,QAAQ,EAAE,EAAE,CACjC,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;QACxE,OAAO,gBAAgB,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;IACrF,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACnG,OAAO;YACL,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE;YAC1E,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE;YACzC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,GAAG,EAAE,EAAE;SAC1E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "../api.js";
|
|
3
|
+
export const listProjectsInputSchema = {};
|
|
4
|
+
export const ListProjectsInput = z.object({});
|
|
5
|
+
export async function listProjects(auth) {
|
|
6
|
+
const res = await apiFetch(auth, "/v1/projects");
|
|
7
|
+
if (!res.ok) {
|
|
8
|
+
return `Error (${res.error.code}): ${res.error.message}`;
|
|
9
|
+
}
|
|
10
|
+
if (res.data.projects.length === 0) {
|
|
11
|
+
return "No projects connected yet. Visit https://www.getdebug.dev/projects/new to connect a repo.";
|
|
12
|
+
}
|
|
13
|
+
const lines = res.data.projects.map((p) => {
|
|
14
|
+
const status = p.lastRunStatus ? ` · last run: ${p.lastRunStatus}` : "";
|
|
15
|
+
const findings = typeof p.findingsCount === "number" ? ` · ${p.findingsCount} findings` : "";
|
|
16
|
+
return `- ${p.name} (id=${p.id})${status}${findings}`;
|
|
17
|
+
});
|
|
18
|
+
return lines.join("\n");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=list-projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-projects.js","sourceRoot":"","sources":["../../src/tools/list-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAW,CAAC;AACnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAe9C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAkB;IACnD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAuB,IAAI,EAAE,cAAc,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,2FAA2F,CAAC;IACrG,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACxC,MAAM,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,QAAQ,GACZ,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,OAAO,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "../api.js";
|
|
3
|
+
export const StartScanInput = z.object({
|
|
4
|
+
projectId: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Project id (from list_projects). Use the id, not the project name."),
|
|
8
|
+
});
|
|
9
|
+
// Enqueues an analyze run on the hosted worker. Returns the run id; the
|
|
10
|
+
// caller (or the user via the dashboard) can poll the run for progress.
|
|
11
|
+
// MCP-side this is intentionally fire-and-forget: streaming progress over
|
|
12
|
+
// a stdio transport adds complexity and most clients don't render it.
|
|
13
|
+
// The agent can re-call list_findings after a minute or so to see the
|
|
14
|
+
// new results.
|
|
15
|
+
export async function startScan(auth, args) {
|
|
16
|
+
const res = await apiFetch(auth, `/v1/projects/${encodeURIComponent(args.projectId)}/runs`, { method: "POST", body: "{}" });
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
return `Error (${res.error.code}): ${res.error.message}`;
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
`Run ${res.data.id} enqueued (status: ${res.data.status}).`,
|
|
22
|
+
"",
|
|
23
|
+
"The worker is now cloning, parsing, and analyzing the repo.",
|
|
24
|
+
"Findings typically appear within 1-2 minutes. Re-call list_findings",
|
|
25
|
+
`(projectId="${args.projectId}") to fetch the new results, or watch`,
|
|
26
|
+
`the dashboard at https://www.getdebug.dev/projects/${args.projectId}.`,
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=start-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-scan.js","sourceRoot":"","sources":["../../src/tools/start-scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,oEAAoE,CAAC;CAClF,CAAC,CAAC;AAQH,wEAAwE;AACxE,wEAAwE;AACxE,0EAA0E;AAC1E,sEAAsE;AACtE,sEAAsE;AACtE,eAAe;AACf,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAkB,EAClB,IAAmB;IAEnB,MAAM,GAAG,GAAG,MAAM,QAAQ,CACxB,IAAI,EACJ,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EACzD,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAC/B,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;IACD,OAAO;QACL,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,sBAAsB,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI;QAC3D,EAAE;QACF,6DAA6D;QAC7D,qEAAqE;QACrE,eAAe,IAAI,CAAC,SAAS,uCAAuC;QACpE,sDAAsD,IAAI,CAAC,SAAS,GAAG;KACxE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getdebug/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Model Context Protocol server for getdebug. Lets Claude, Cursor, and any MCP-compatible AI client read your projects, findings, and fixes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"getdebug-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
19
|
+
"start": "node dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
23
|
+
"zod": "^3.23.8",
|
|
24
|
+
"zod-to-json-schema": "^3.23.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.14.0",
|
|
28
|
+
"tsx": "^4.19.0",
|
|
29
|
+
"typescript": "^5.6.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|