@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 CHANGED
@@ -55,22 +55,27 @@ Then invoke one of the plugin's slash commands:
55
55
 
56
56
  ### OpenCode
57
57
 
58
- Add the package name to the project or global `opencode.json` and restart
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
- "plugin": ["@bergabruh/code-scanner"]
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
- OpenCode installs the package through Bun. The package starts its bundled,
69
- dependency-free Python scanner bridge itself; no `cwd`, MCP block, or copied
70
- `.opencode` assets are needed. Ask OpenCode to run a workspace security scan;
71
- it receives the `mnogovid_code_scanner` tool and must still request every
72
- recorded consent. `python3` and individual scanner executables remain system
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.0",
4
- "description": "OpenCode plugin for consent-gated workspace security scanning.",
3
+ "version": "0.1.1",
4
+ "description": "MCP server package for consent-gated workspace security scanning.",
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-code-scanner": "./bin/mnogovid-code-scanner.mjs"
8
+ },
10
9
  "files": [
11
- "opencode.js",
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 opencode.js"
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
- }