@bergabruh/system-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-system-scanner.mjs +14 -0
- package/package.json +9 -12
- package/opencode.js +0 -115
package/README.md
CHANGED
|
@@ -88,22 +88,27 @@ selected report directory, then invoke:
|
|
|
88
88
|
|
|
89
89
|
### OpenCode
|
|
90
90
|
|
|
91
|
-
Add the
|
|
91
|
+
Add the MCP server to the project or global `opencode.json` and restart
|
|
92
92
|
OpenCode:
|
|
93
93
|
|
|
94
94
|
```json
|
|
95
95
|
{
|
|
96
96
|
"$schema": "https://opencode.ai/config.json",
|
|
97
|
-
"
|
|
97
|
+
"mcp": {
|
|
98
|
+
"mnogovid-system-scanner": {
|
|
99
|
+
"type": "local",
|
|
100
|
+
"command": ["npx", "--yes", "@bergabruh/system-scanner"],
|
|
101
|
+
"enabled": true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
98
104
|
}
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
prerequisites and are never installed by the package.
|
|
107
|
+
`npx` installs and starts the bundled Python MCP server; no absolute path or
|
|
108
|
+
copied `.opencode` assets are needed. OpenCode exposes its tools with the
|
|
109
|
+
`mnogovid-system-scanner_` prefix and still requests every recorded consent.
|
|
110
|
+
`python3` and individual scanner executables remain system prerequisites and
|
|
111
|
+
are never installed by the package.
|
|
107
112
|
|
|
108
113
|
First inspect the host/tool availability without starting a scanner:
|
|
109
114
|
|
|
@@ -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/system_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 System 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,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bergabruh/system-scanner",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server package for consent-gated Linux host security assessment.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"exports": "./opencode.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mnogovid-system-scanner": "./bin/mnogovid-system-scanner.mjs"
|
|
8
|
+
},
|
|
10
9
|
"files": [
|
|
11
|
-
"
|
|
10
|
+
"bin/*.mjs",
|
|
12
11
|
"scripts/*.py",
|
|
13
12
|
"README.md",
|
|
14
|
-
"LICENSE"
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"bin/*.mjs"
|
|
15
15
|
],
|
|
16
16
|
"keywords": [
|
|
17
17
|
"opencode",
|
|
@@ -19,11 +19,8 @@
|
|
|
19
19
|
"security",
|
|
20
20
|
"linux"
|
|
21
21
|
],
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@opencode-ai/plugin": "^1.18.11"
|
|
24
|
-
},
|
|
25
22
|
"scripts": {
|
|
26
|
-
"check": "node --check
|
|
23
|
+
"check": "node --check bin/mnogovid-system-scanner.mjs"
|
|
27
24
|
},
|
|
28
25
|
"homepage": "https://github.com/BergaBruh/mnogovid",
|
|
29
26
|
"repository": { "type": "git", "url": "git+https://github.com/BergaBruh/mnogovid.git" }
|
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
|
-
"system_catalog",
|
|
7
|
-
"system_doctor",
|
|
8
|
-
"system_bootstrap",
|
|
9
|
-
"system_plan",
|
|
10
|
-
"system_virtual_run",
|
|
11
|
-
"system_run",
|
|
12
|
-
"system_ingest",
|
|
13
|
-
"system_start_run",
|
|
14
|
-
"system_record_run",
|
|
15
|
-
"system_finalize_run",
|
|
16
|
-
"system_ai_triage_payload",
|
|
17
|
-
"system_advisory_lookup",
|
|
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 MnogovidSystemScanner = async () => {
|
|
93
|
-
const script = fileURLToPath(new URL("./scripts/system_mcp.py", import.meta.url))
|
|
94
|
-
const bridge = new PythonMcpBridge(script)
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
tool: {
|
|
98
|
-
mnogovid_system_scanner: tool({
|
|
99
|
-
description: "Call one consent-gated Mnogovid System Scanner operation. Valid operations: system_catalog, system_doctor, system_bootstrap, system_plan, system_virtual_run, system_run, system_ingest, system_start_run, system_record_run, system_finalize_run, system_ai_triage_payload, system_advisory_lookup. Pass argumentsJson as a JSON object. The Python core validates report directories, 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 System 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
|
-
}
|