@bergabruh/code-scanner 0.1.0 → 0.1.1
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 +13 -8
- package/bin/mnogovid-code-scanner.mjs +14 -0
- package/package.json +7 -11
- package/opencode.js +0 -115
package/README.md
CHANGED
|
@@ -55,22 +55,27 @@ Then invoke one of the plugin's slash commands:
|
|
|
55
55
|
|
|
56
56
|
### OpenCode
|
|
57
57
|
|
|
58
|
-
Add the
|
|
58
|
+
Add the MCP server to the project or global `opencode.json` and restart
|
|
59
59
|
OpenCode:
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
63
|
"$schema": "https://opencode.ai/config.json",
|
|
64
|
-
"
|
|
64
|
+
"mcp": {
|
|
65
|
+
"mnogovid-code-scanner": {
|
|
66
|
+
"type": "local",
|
|
67
|
+
"command": ["npx", "--yes", "@bergabruh/code-scanner"],
|
|
68
|
+
"enabled": true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
prerequisites and are never installed by the package.
|
|
74
|
+
`npx` installs and starts the bundled Python MCP server; no absolute path or
|
|
75
|
+
copied `.opencode` assets are needed. OpenCode exposes its tools with the
|
|
76
|
+
`mnogovid-code-scanner_` prefix and still requests every recorded consent.
|
|
77
|
+
`python3` and individual scanner executables remain system prerequisites and
|
|
78
|
+
are never installed by the package.
|
|
74
79
|
|
|
75
80
|
## Choose a mode
|
|
76
81
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
const script = fileURLToPath(new URL("../scripts/security_mcp.py", import.meta.url))
|
|
6
|
+
const child = spawn(process.env.PYTHON ?? "python3", [script], { stdio: "inherit" })
|
|
7
|
+
|
|
8
|
+
child.on("error", (error) => {
|
|
9
|
+
process.stderr.write(`Unable to start Mnogovid Code Scanner: ${error.message}\n`)
|
|
10
|
+
process.exitCode = 1
|
|
11
|
+
})
|
|
12
|
+
child.on("exit", (code, signal) => {
|
|
13
|
+
process.exitCode = code ?? (signal ? 1 : 0)
|
|
14
|
+
})
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bergabruh/code-scanner",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server package for consent-gated workspace security scanning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"exports": "./opencode.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mnogovid-code-scanner": "./bin/mnogovid-code-scanner.mjs"
|
|
8
|
+
},
|
|
10
9
|
"files": [
|
|
11
|
-
"
|
|
10
|
+
"bin/*.mjs",
|
|
12
11
|
"scripts/*.py",
|
|
13
12
|
"README.md",
|
|
14
13
|
"LICENSE"
|
|
@@ -19,11 +18,8 @@
|
|
|
19
18
|
"security",
|
|
20
19
|
"sast"
|
|
21
20
|
],
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@opencode-ai/plugin": "^1.18.11"
|
|
24
|
-
},
|
|
25
21
|
"scripts": {
|
|
26
|
-
"check": "node --check
|
|
22
|
+
"check": "node --check bin/mnogovid-code-scanner.mjs"
|
|
27
23
|
},
|
|
28
24
|
"homepage": "https://github.com/BergaBruh/mnogovid",
|
|
29
25
|
"repository": {
|
package/opencode.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process"
|
|
2
|
-
import { fileURLToPath } from "node:url"
|
|
3
|
-
import { tool } from "@opencode-ai/plugin"
|
|
4
|
-
|
|
5
|
-
const METHODS = new Set([
|
|
6
|
-
"security_catalog",
|
|
7
|
-
"security_doctor",
|
|
8
|
-
"security_bootstrap",
|
|
9
|
-
"security_plan",
|
|
10
|
-
"security_virtual_run",
|
|
11
|
-
"security_run",
|
|
12
|
-
"security_ingest",
|
|
13
|
-
"security_start_run",
|
|
14
|
-
"security_record_run",
|
|
15
|
-
"security_finalize_run",
|
|
16
|
-
"security_advisory_lookup",
|
|
17
|
-
"security_ai_triage_payload",
|
|
18
|
-
])
|
|
19
|
-
|
|
20
|
-
class PythonMcpBridge {
|
|
21
|
-
constructor(script) {
|
|
22
|
-
this.nextId = 1
|
|
23
|
-
this.pending = new Map()
|
|
24
|
-
this.buffer = ""
|
|
25
|
-
this.queue = Promise.resolve()
|
|
26
|
-
this.stderr = ""
|
|
27
|
-
this.child = spawn("python3", [script], { stdio: ["pipe", "pipe", "pipe"] })
|
|
28
|
-
this.child.stdout.setEncoding("utf8")
|
|
29
|
-
this.child.stdout.on("data", (chunk) => this.receive(chunk))
|
|
30
|
-
this.child.stderr.setEncoding("utf8")
|
|
31
|
-
this.child.stderr.on("data", (chunk) => {
|
|
32
|
-
this.stderr = (this.stderr + chunk).slice(-4096)
|
|
33
|
-
})
|
|
34
|
-
this.child.on("error", (error) => this.failAll(error))
|
|
35
|
-
this.child.on("exit", (code, signal) => {
|
|
36
|
-
this.failAll(new Error(`Python scanner exited (${signal ?? code ?? "unknown"})${this.stderr ? `: ${this.stderr}` : ""}`))
|
|
37
|
-
})
|
|
38
|
-
process.once("exit", () => this.child.kill())
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
receive(chunk) {
|
|
42
|
-
this.buffer += chunk
|
|
43
|
-
let newline
|
|
44
|
-
while ((newline = this.buffer.indexOf("\n")) >= 0) {
|
|
45
|
-
const line = this.buffer.slice(0, newline)
|
|
46
|
-
this.buffer = this.buffer.slice(newline + 1)
|
|
47
|
-
if (!line.trim()) continue
|
|
48
|
-
try {
|
|
49
|
-
const message = JSON.parse(line)
|
|
50
|
-
const pending = this.pending.get(message.id)
|
|
51
|
-
if (!pending) continue
|
|
52
|
-
this.pending.delete(message.id)
|
|
53
|
-
if (message.error) pending.reject(new Error(message.error.message ?? "Python scanner protocol error"))
|
|
54
|
-
else pending.resolve(message.result)
|
|
55
|
-
} catch (error) {
|
|
56
|
-
this.failAll(new Error(`Invalid response from Python scanner: ${error.message}`))
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
failAll(error) {
|
|
62
|
-
for (const { reject } of this.pending.values()) reject(error)
|
|
63
|
-
this.pending.clear()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
call(method, args) {
|
|
67
|
-
const request = () => new Promise((resolve, reject) => {
|
|
68
|
-
if (this.child.exitCode !== null || this.child.killed) {
|
|
69
|
-
reject(new Error("Python scanner is unavailable; verify that python3 is installed."))
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
const id = this.nextId++
|
|
73
|
-
this.pending.set(id, { resolve, reject })
|
|
74
|
-
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method: "tools/call", params: { name: method, arguments: args } })}\n`)
|
|
75
|
-
})
|
|
76
|
-
const result = this.queue.then(request, request)
|
|
77
|
-
this.queue = result.catch(() => undefined)
|
|
78
|
-
return result
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function parseArguments(raw) {
|
|
83
|
-
try {
|
|
84
|
-
const value = JSON.parse(raw)
|
|
85
|
-
if (!value || Array.isArray(value) || typeof value !== "object") throw new Error("must be a JSON object")
|
|
86
|
-
return value
|
|
87
|
-
} catch (error) {
|
|
88
|
-
throw new Error(`argumentsJson ${error.message}`)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export const MnogovidCodeScanner = async () => {
|
|
93
|
-
const script = fileURLToPath(new URL("./scripts/security_mcp.py", import.meta.url))
|
|
94
|
-
const bridge = new PythonMcpBridge(script)
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
tool: {
|
|
98
|
-
mnogovid_code_scanner: tool({
|
|
99
|
-
description: "Call one consent-gated Mnogovid Code Scanner operation. Valid operations: security_catalog, security_doctor, security_bootstrap, security_plan, security_virtual_run, security_run, security_ingest, security_start_run, security_record_run, security_finalize_run, security_advisory_lookup, security_ai_triage_payload. Pass argumentsJson as a JSON object. The Python core validates workspace paths, recorded lifecycle and consent before any scanner starts.",
|
|
100
|
-
args: {
|
|
101
|
-
operation: tool.schema.string(),
|
|
102
|
-
argumentsJson: tool.schema.string(),
|
|
103
|
-
},
|
|
104
|
-
async execute({ operation, argumentsJson }) {
|
|
105
|
-
if (!METHODS.has(operation)) return `Unknown Mnogovid Code Scanner operation: ${operation}`
|
|
106
|
-
try {
|
|
107
|
-
return JSON.stringify(await bridge.call(operation, parseArguments(argumentsJson)))
|
|
108
|
-
} catch (error) {
|
|
109
|
-
return JSON.stringify({ error: error.message })
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
},
|
|
114
|
-
}
|
|
115
|
-
}
|