@nuguardai/nuguard 0.5.7
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 +89 -0
- package/bin/nuguard-mcp.js +72 -0
- package/bin/nuguard-mcp.test.js +206 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @nuguardai/nuguard
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@nuguardai/nuguard)
|
|
4
|
+
[](https://pypi.org/project/nuguard/)
|
|
5
|
+
[](https://github.com/NuGuardAI/nuguard/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
NuGuard is an open-source AI application security CLI. This npm package provides the `nuguard-mcp` launcher, which wires NuGuard as a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server so AI-native tools (Claude Desktop, VS Code, Cursor, etc.) can invoke NuGuard capabilities directly.
|
|
8
|
+
|
|
9
|
+
The underlying CLI is a Python package. This launcher automatically locates or installs it so you don't have to manage the Python environment manually.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @nuguardai/nuguard
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install globally:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @nuguardai/nuguard
|
|
21
|
+
nuguard-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What the launcher does
|
|
25
|
+
|
|
26
|
+
`nuguard-mcp` tries the following in order, using whichever succeeds first:
|
|
27
|
+
|
|
28
|
+
1. `nuguard-mcp` already on `PATH` (pip-installed globally or in an active venv)
|
|
29
|
+
2. `python3 -m nuguard.mcp` (nuguard installed but scripts not on `PATH`)
|
|
30
|
+
3. `uvx --from nuguard[mcp] nuguard-mcp` (uv available)
|
|
31
|
+
4. `pip install nuguard[mcp]` then `python -m nuguard.mcp` (one-time bootstrap)
|
|
32
|
+
|
|
33
|
+
## MCP configuration
|
|
34
|
+
|
|
35
|
+
### Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to `claude_desktop_config.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"nuguard": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@nuguardai/nuguard"]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### VS Code (`.vscode/mcp.json`)
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"servers": {
|
|
55
|
+
"nuguard": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "@nuguardai/nuguard"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
- Node.js 18+
|
|
66
|
+
- Python 3.12+ (or `uv` for zero-setup bootstrap)
|
|
67
|
+
|
|
68
|
+
## CLI capabilities
|
|
69
|
+
|
|
70
|
+
Once connected, the MCP server exposes NuGuard's full pipeline:
|
|
71
|
+
|
|
72
|
+
| Command | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `nuguard sbom` | Generate an AI Bill of Materials from source code |
|
|
75
|
+
| `nuguard analyze` | Static analysis of an AI-SBOM for security risks |
|
|
76
|
+
| `nuguard policy` | Validate a cognitive policy document against a scan |
|
|
77
|
+
| `nuguard behavior` | Behavioral testing against a live AI application |
|
|
78
|
+
| `nuguard redteam` | Adversarial red-teaming with scenario-driven attacks |
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- [GitHub](https://github.com/NuGuardAI/nuguard)
|
|
83
|
+
- [Documentation](https://nuguardai.github.io/nuguard/)
|
|
84
|
+
- [PyPI](https://pypi.org/project/nuguard/)
|
|
85
|
+
- [Plugin guide](https://nuguardai.github.io/nuguard/plugin-guide.html)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
Apache-2.0
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Launcher for nuguard-mcp. Tries runners in order of least friction:
|
|
4
|
+
* 1. nuguard-mcp already on PATH (pip-installed globally or in active venv)
|
|
5
|
+
* 2. python/python3 -m nuguard.mcp (nuguard installed but scripts not on PATH)
|
|
6
|
+
* 3. uvx --from nuguard[mcp] nuguard-mcp (uv available)
|
|
7
|
+
* 4. pip install nuguard[mcp] then python -m nuguard.mcp (one-time setup)
|
|
8
|
+
*/
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
const { spawn, spawnSync } = require("child_process");
|
|
12
|
+
|
|
13
|
+
function which(cmd) {
|
|
14
|
+
const isWin = process.platform === "win32";
|
|
15
|
+
const r = spawnSync(isWin ? "where" : "which", [cmd], { encoding: "utf8", stdio: "pipe" });
|
|
16
|
+
return r.status === 0 && r.stdout.trim().length > 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function pyHasNuguardMcp(python) {
|
|
20
|
+
const r = spawnSync(python, ["-c", "import nuguard.mcp"], { encoding: "utf8", stdio: "pipe" });
|
|
21
|
+
return r.status === 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function run(command, args) {
|
|
25
|
+
const child = spawn(command, [...args], { stdio: "inherit" });
|
|
26
|
+
child.on("error", (err) => {
|
|
27
|
+
process.stderr.write(`nuguard-mcp: failed to start '${command}': ${err.message}\n`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const extra = process.argv.slice(2);
|
|
34
|
+
|
|
35
|
+
// 1. Already pip-installed and on PATH
|
|
36
|
+
if (which("nuguard-mcp")) {
|
|
37
|
+
return run("nuguard-mcp", extra);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Python with nuguard already installed
|
|
41
|
+
const python = which("python3") ? "python3" : which("python") ? "python" : null;
|
|
42
|
+
if (python && pyHasNuguardMcp(python)) {
|
|
43
|
+
return run(python, ["-m", "nuguard.mcp", ...extra]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3. uvx
|
|
47
|
+
if (which("uvx")) {
|
|
48
|
+
return run("uvx", ["--from", "nuguard[mcp]", "nuguard-mcp", ...extra]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 4. pip install then run
|
|
52
|
+
if (python) {
|
|
53
|
+
process.stderr.write("nuguard-mcp: installing via pip (one-time setup)...\n");
|
|
54
|
+
const install = spawnSync(
|
|
55
|
+
python, ["-m", "pip", "install", "--quiet", "nuguard[mcp]"],
|
|
56
|
+
{ stdio: "inherit" }
|
|
57
|
+
);
|
|
58
|
+
if (install.status === 0) {
|
|
59
|
+
return run(python, ["-m", "nuguard.mcp", ...extra]);
|
|
60
|
+
}
|
|
61
|
+
process.stderr.write("nuguard-mcp: pip install failed — try: pip install nuguard[mcp]\n");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
"nuguard-mcp: no suitable Python runtime found.\n" +
|
|
67
|
+
"Install via one of:\n" +
|
|
68
|
+
" pip install nuguard[mcp]\n" +
|
|
69
|
+
" uvx --from nuguard[mcp] nuguard-mcp\n" +
|
|
70
|
+
"See https://github.com/NuGuardAI/nuguard for details.\n"
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for nuguard-mcp.js runner detection logic.
|
|
4
|
+
*
|
|
5
|
+
* Run with: node npm/bin/nuguard-mcp.test.js
|
|
6
|
+
*
|
|
7
|
+
* Uses Node's built-in assert module — no external test framework needed.
|
|
8
|
+
*/
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
const assert = require("assert");
|
|
12
|
+
const { spawnSync } = require("child_process");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const WRAPPER = path.resolve(__dirname, "nuguard-mcp.js");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run the wrapper with a mocked environment via a child node process.
|
|
23
|
+
* We inject a tiny shim that overrides `which`, `pyHasNuguardMcp`, and
|
|
24
|
+
* `spawnSync` (for pip install) before the real module logic runs.
|
|
25
|
+
*
|
|
26
|
+
* Returns { stdout, stderr, exitCode }.
|
|
27
|
+
*/
|
|
28
|
+
function runWithMocks({ whichResults = {}, pyHasNuguard = false, pipOk = true } = {}) {
|
|
29
|
+
const shim = `
|
|
30
|
+
"use strict";
|
|
31
|
+
const cp = require("child_process");
|
|
32
|
+
const original_spawnSync = cp.spawnSync.bind(cp);
|
|
33
|
+
|
|
34
|
+
// Intercept spawnSync used by which() and pip install
|
|
35
|
+
cp.spawnSync = function(cmd, args, opts) {
|
|
36
|
+
const isWin = process.platform === "win32";
|
|
37
|
+
const whichCmd = isWin ? "where" : "which";
|
|
38
|
+
|
|
39
|
+
if (cmd === whichCmd) {
|
|
40
|
+
const target = args[0];
|
|
41
|
+
const found = ${JSON.stringify(whichResults)}[target] === true;
|
|
42
|
+
return { status: found ? 0 : 1, stdout: found ? target : "", stderr: "" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// python -c "import nuguard.mcp"
|
|
46
|
+
if (args && args[0] === "-c" && args[1] === "import nuguard.mcp") {
|
|
47
|
+
return { status: ${pyHasNuguard ? 0 : 1} };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// pip install
|
|
51
|
+
if (args && args[0] === "-m" && args[1] === "pip") {
|
|
52
|
+
return { status: ${pipOk ? 0 : 1} };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { status: 1, stdout: "", stderr: "" };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Intercept spawn used by run() — capture what would be launched
|
|
59
|
+
const cp2 = require("child_process");
|
|
60
|
+
const original_spawn = cp2.spawn.bind(cp2);
|
|
61
|
+
cp2.spawn = function(cmd, args, opts) {
|
|
62
|
+
// Print what would be launched and exit cleanly
|
|
63
|
+
process.stdout.write(JSON.stringify({ cmd, args }) + "\\n");
|
|
64
|
+
return {
|
|
65
|
+
on: function(event, cb) {
|
|
66
|
+
if (event === "exit") cb(0);
|
|
67
|
+
if (event === "error") {}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
require(${JSON.stringify(WRAPPER)});
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const result = spawnSync(process.execPath, ["--eval", shim], {
|
|
77
|
+
encoding: "utf8",
|
|
78
|
+
stdio: "pipe",
|
|
79
|
+
timeout: 5000,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let launched = null;
|
|
83
|
+
if (result.stdout && result.stdout.trim()) {
|
|
84
|
+
try { launched = JSON.parse(result.stdout.trim()); } catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
launched,
|
|
89
|
+
stderr: result.stderr || "",
|
|
90
|
+
exitCode: result.status,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Tests
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
let passed = 0;
|
|
99
|
+
let failed = 0;
|
|
100
|
+
|
|
101
|
+
function test(name, fn) {
|
|
102
|
+
try {
|
|
103
|
+
fn();
|
|
104
|
+
console.log(` ✓ ${name}`);
|
|
105
|
+
passed++;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(` ✗ ${name}`);
|
|
108
|
+
console.error(` ${err.message}`);
|
|
109
|
+
failed++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log("\nnuguard-mcp.js runner detection\n");
|
|
114
|
+
|
|
115
|
+
// 1. nuguard-mcp already on PATH → run it directly
|
|
116
|
+
test("uses nuguard-mcp directly when it is on PATH", () => {
|
|
117
|
+
const { launched } = runWithMocks({ whichResults: { "nuguard-mcp": true } });
|
|
118
|
+
assert.ok(launched, "should have launched something");
|
|
119
|
+
assert.strictEqual(launched.cmd, "nuguard-mcp");
|
|
120
|
+
assert.deepStrictEqual(launched.args, []);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 2. python3 with nuguard installed → python3 -m nuguard.mcp
|
|
124
|
+
test("uses python3 -m nuguard.mcp when nuguard-mcp not on PATH but python3 has it", () => {
|
|
125
|
+
const { launched } = runWithMocks({
|
|
126
|
+
whichResults: { python3: true },
|
|
127
|
+
pyHasNuguard: true,
|
|
128
|
+
});
|
|
129
|
+
assert.ok(launched);
|
|
130
|
+
assert.strictEqual(launched.cmd, "python3");
|
|
131
|
+
assert.ok(launched.args.includes("-m"), "should include -m flag");
|
|
132
|
+
assert.ok(launched.args.includes("nuguard.mcp"), "should target nuguard.mcp");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 3. python (not python3) with nuguard installed
|
|
136
|
+
test("falls back to python (not python3) when python3 not available", () => {
|
|
137
|
+
const { launched } = runWithMocks({
|
|
138
|
+
whichResults: { python: true },
|
|
139
|
+
pyHasNuguard: true,
|
|
140
|
+
});
|
|
141
|
+
assert.ok(launched);
|
|
142
|
+
assert.strictEqual(launched.cmd, "python");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 4. uvx available, nuguard not pip-installed
|
|
146
|
+
test("uses uvx when nuguard not pip-installed but uvx is available", () => {
|
|
147
|
+
const { launched } = runWithMocks({
|
|
148
|
+
whichResults: { uvx: true },
|
|
149
|
+
pyHasNuguard: false,
|
|
150
|
+
});
|
|
151
|
+
assert.ok(launched);
|
|
152
|
+
assert.strictEqual(launched.cmd, "uvx");
|
|
153
|
+
assert.ok(launched.args.includes("--from"));
|
|
154
|
+
assert.ok(launched.args.includes("nuguard[mcp]"));
|
|
155
|
+
assert.ok(launched.args.includes("nuguard-mcp"));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// 5. pip install fallback when python available but nuguard not installed
|
|
159
|
+
test("pip-installs nuguard then launches python -m nuguard.mcp when only python present", () => {
|
|
160
|
+
const { launched, stderr } = runWithMocks({
|
|
161
|
+
whichResults: { python3: true },
|
|
162
|
+
pyHasNuguard: false,
|
|
163
|
+
pipOk: true,
|
|
164
|
+
});
|
|
165
|
+
assert.ok(launched, "should launch after pip install");
|
|
166
|
+
assert.strictEqual(launched.cmd, "python3");
|
|
167
|
+
assert.ok(launched.args.includes("nuguard.mcp"));
|
|
168
|
+
assert.ok(stderr.includes("installing via pip"), "should mention pip install");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// 6. No Python, no uvx → exit 1 with helpful message
|
|
172
|
+
test("exits with code 1 and helpful message when no runtime found", () => {
|
|
173
|
+
const { launched, stderr, exitCode } = runWithMocks({
|
|
174
|
+
whichResults: {},
|
|
175
|
+
pyHasNuguard: false,
|
|
176
|
+
});
|
|
177
|
+
assert.strictEqual(launched, null, "should not launch anything");
|
|
178
|
+
assert.ok(stderr.includes("pip install") || stderr.includes("Python"), "stderr should contain install hint");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// 7. pip install fails → exit 1
|
|
182
|
+
test("exits with code 1 when pip install fails", () => {
|
|
183
|
+
const { launched } = runWithMocks({
|
|
184
|
+
whichResults: { python3: true },
|
|
185
|
+
pyHasNuguard: false,
|
|
186
|
+
pipOk: false,
|
|
187
|
+
});
|
|
188
|
+
assert.strictEqual(launched, null, "should not launch after failed pip install");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 8. nuguard-mcp takes precedence over python + uvx
|
|
192
|
+
test("nuguard-mcp on PATH takes precedence over python and uvx", () => {
|
|
193
|
+
const { launched } = runWithMocks({
|
|
194
|
+
whichResults: { "nuguard-mcp": true, python3: true, uvx: true },
|
|
195
|
+
pyHasNuguard: true,
|
|
196
|
+
});
|
|
197
|
+
assert.ok(launched);
|
|
198
|
+
assert.strictEqual(launched.cmd, "nuguard-mcp", "nuguard-mcp should win over python/uvx");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Summary
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed\n`);
|
|
206
|
+
process.exit(failed > 0 ? 1 : 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nuguardai/nuguard",
|
|
3
|
+
"version": "0.5.7",
|
|
4
|
+
"description": "AI Application Security — SBOM generation, static analysis, behavioral validation, and adversarial red-team testing for AI agents and LLM-powered applications.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"ai-security",
|
|
8
|
+
"sbom",
|
|
9
|
+
"red-team",
|
|
10
|
+
"llm",
|
|
11
|
+
"ai-agents"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/NuGuardAI/nuguard",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/NuGuardAI/nuguard/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/NuGuardAI/nuguard.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": "NuGuard <info@nuguard.ai>",
|
|
23
|
+
"bin": {
|
|
24
|
+
"nuguard-mcp": "bin/nuguard-mcp.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
}
|
|
37
|
+
}
|