@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 CHANGED
@@ -88,22 +88,27 @@ selected report directory, then invoke:
88
88
 
89
89
  ### OpenCode
90
90
 
91
- Add the package name to the project or global `opencode.json` and restart
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
- "plugin": ["@bergabruh/system-scanner"]
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
- OpenCode installs the package through Bun. The package starts its bundled,
102
- dependency-free Python scanner bridge itself; no `cwd`, MCP block, or copied
103
- `.opencode` assets are needed. Ask OpenCode to assess the local Linux host; it
104
- receives the `mnogovid_system_scanner` tool and must still request every
105
- recorded consent. `python3` and individual scanner executables remain system
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.0",
4
- "description": "OpenCode plugin for consent-gated Linux host security assessment.",
3
+ "version": "0.1.1",
4
+ "description": "MCP server package for consent-gated Linux host security assessment.",
5
5
  "license": "Apache-2.0",
6
- "type": "module",
7
- "main": "./opencode.js",
8
- "module": "./opencode.js",
9
- "exports": "./opencode.js",
6
+ "bin": {
7
+ "mnogovid-system-scanner": "./bin/mnogovid-system-scanner.mjs"
8
+ },
10
9
  "files": [
11
- "opencode.js",
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 opencode.js"
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
- }