hound-mcp 0.2.1 → 0.2.3
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 +60 -169
- package/dist/index.js +139 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,45 +6,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
// src/server.ts
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
|
|
9
|
-
// src/prompts/
|
|
9
|
+
// src/prompts/package_evaluation.ts
|
|
10
10
|
import { z } from "zod/v4";
|
|
11
|
-
function
|
|
12
|
-
server.registerPrompt(
|
|
13
|
-
"security_audit",
|
|
14
|
-
{
|
|
15
|
-
description: "Run a full security audit on the current project's dependencies. Scans for vulnerabilities, license issues, and typosquat risks across your entire dependency tree.",
|
|
16
|
-
argsSchema: {
|
|
17
|
-
ecosystem: z.string().optional().describe("Package ecosystem (npm, pypi, cargo, etc). Auto-detected if omitted.")
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
({ ecosystem }) => {
|
|
21
|
-
const ecoNote = ecosystem ? ` The project uses ${ecosystem}.` : "";
|
|
22
|
-
return {
|
|
23
|
-
messages: [
|
|
24
|
-
{
|
|
25
|
-
role: "user",
|
|
26
|
-
content: {
|
|
27
|
-
type: "text",
|
|
28
|
-
text: `Please run a comprehensive security audit on this project's dependencies.${ecoNote}
|
|
29
|
-
|
|
30
|
-
Follow these steps:
|
|
31
|
-
1. Use \`hound_popular\` to check the most commonly used packages in this ecosystem for known vulnerabilities \u2014 this gives a quick baseline.
|
|
32
|
-
2. For any specific packages you can identify in the project, use \`hound_vulns\` to check each one for CVEs and advisories.
|
|
33
|
-
3. Use \`hound_inspect\` on the 3-5 most critical dependencies to check their licenses, OpenSSF Scorecard, and GitHub health.
|
|
34
|
-
4. For any package names that look unusual or unfamiliar, use \`hound_typosquat\` to check for potential typosquatting.
|
|
35
|
-
5. If any vulnerabilities are found, use \`hound_advisories\` to get full details and fix guidance.
|
|
36
|
-
|
|
37
|
-
Summarize findings as:
|
|
38
|
-
- **Critical / High** vulnerabilities that need immediate attention
|
|
39
|
-
- **License risks** (copyleft licenses, unknown licenses)
|
|
40
|
-
- **Health concerns** (abandoned packages, low Scorecard scores)
|
|
41
|
-
- **Recommended actions** with specific version upgrades where available`
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
);
|
|
11
|
+
function packageEvaluationRegisterPrompt(server) {
|
|
48
12
|
server.registerPrompt(
|
|
49
13
|
"package_evaluation",
|
|
50
14
|
{
|
|
@@ -80,12 +44,17 @@ Give me a clear **GO / NO-GO / CONDITIONAL** recommendation with reasoning. If c
|
|
|
80
44
|
};
|
|
81
45
|
}
|
|
82
46
|
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/prompts/pre_release_check.ts
|
|
50
|
+
import { z as z2 } from "zod/v4";
|
|
51
|
+
function preReleaseCheckRegisterPrompt(server) {
|
|
83
52
|
server.registerPrompt(
|
|
84
53
|
"pre_release_check",
|
|
85
54
|
{
|
|
86
55
|
description: "Run a pre-release dependency scan before shipping. Checks for vulnerabilities and license issues that could block a release.",
|
|
87
56
|
argsSchema: {
|
|
88
|
-
version:
|
|
57
|
+
version: z2.string().optional().describe("The version you are about to release, e.g. 1.2.0")
|
|
89
58
|
}
|
|
90
59
|
},
|
|
91
60
|
({ version }) => {
|
|
@@ -119,8 +88,56 @@ End with a clear **SAFE TO RELEASE** or **BLOCKED \u2014 fix these issues first*
|
|
|
119
88
|
);
|
|
120
89
|
}
|
|
121
90
|
|
|
91
|
+
// src/prompts/security_audit.ts
|
|
92
|
+
import { z as z3 } from "zod/v4";
|
|
93
|
+
function securityAuditRegisterPrompt(server) {
|
|
94
|
+
server.registerPrompt(
|
|
95
|
+
"security_audit",
|
|
96
|
+
{
|
|
97
|
+
description: "Run a full security audit on the current project's dependencies. Scans for vulnerabilities, license issues, and typosquat risks across your entire dependency tree.",
|
|
98
|
+
argsSchema: {
|
|
99
|
+
ecosystem: z3.string().optional().describe("Package ecosystem (npm, pypi, cargo, etc). Auto-detected if omitted.")
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
({ ecosystem }) => {
|
|
103
|
+
const ecoNote = ecosystem ? ` The project uses ${ecosystem}.` : "";
|
|
104
|
+
return {
|
|
105
|
+
messages: [
|
|
106
|
+
{
|
|
107
|
+
role: "user",
|
|
108
|
+
content: {
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `Please run a comprehensive security audit on this project's dependencies.${ecoNote}
|
|
111
|
+
|
|
112
|
+
Follow these steps:
|
|
113
|
+
1. Use \`hound_popular\` to check the most commonly used packages in this ecosystem for known vulnerabilities \u2014 this gives a quick baseline.
|
|
114
|
+
2. For any specific packages you can identify in the project, use \`hound_vulns\` to check each one for CVEs and advisories.
|
|
115
|
+
3. Use \`hound_inspect\` on the 3-5 most critical dependencies to check their licenses, OpenSSF Scorecard, and GitHub health.
|
|
116
|
+
4. For any package names that look unusual or unfamiliar, use \`hound_typosquat\` to check for potential typosquatting.
|
|
117
|
+
5. If any vulnerabilities are found, use \`hound_advisories\` to get full details and fix guidance.
|
|
118
|
+
|
|
119
|
+
Summarize findings as:
|
|
120
|
+
- **Critical / High** vulnerabilities that need immediate attention
|
|
121
|
+
- **License risks** (copyleft licenses, unknown licenses)
|
|
122
|
+
- **Health concerns** (abandoned packages, low Scorecard scores)
|
|
123
|
+
- **Recommended actions** with specific version upgrades where available`
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/prompts/index.ts
|
|
133
|
+
function registerPrompts(server) {
|
|
134
|
+
securityAuditRegisterPrompt(server);
|
|
135
|
+
packageEvaluationRegisterPrompt(server);
|
|
136
|
+
preReleaseCheckRegisterPrompt(server);
|
|
137
|
+
}
|
|
138
|
+
|
|
122
139
|
// src/tools/advisories.ts
|
|
123
|
-
import { z as
|
|
140
|
+
import { z as z4 } from "zod/v4";
|
|
124
141
|
|
|
125
142
|
// src/api/depsdev.ts
|
|
126
143
|
var BASE_URL = "https://api.deps.dev/v3";
|
|
@@ -136,7 +153,7 @@ var ECOSYSTEM_MAP = {
|
|
|
136
153
|
async function get(path) {
|
|
137
154
|
const url = `${BASE_URL}${path}`;
|
|
138
155
|
const res = await fetch(url, {
|
|
139
|
-
headers: { "User-Agent":
|
|
156
|
+
headers: { "User-Agent": `hound-mcp/${"0.2.3"}` }
|
|
140
157
|
});
|
|
141
158
|
if (!res.ok) {
|
|
142
159
|
const body = await res.text().catch(() => "");
|
|
@@ -201,7 +218,7 @@ async function post(path, body) {
|
|
|
201
218
|
method: "POST",
|
|
202
219
|
headers: {
|
|
203
220
|
"Content-Type": "application/json",
|
|
204
|
-
"User-Agent":
|
|
221
|
+
"User-Agent": `hound-mcp/${"0.2.3"}`
|
|
205
222
|
},
|
|
206
223
|
body: JSON.stringify(body)
|
|
207
224
|
});
|
|
@@ -246,7 +263,7 @@ async function queryVulnsBatch(packages) {
|
|
|
246
263
|
async function getVuln(vulnId) {
|
|
247
264
|
const url = `${BASE_URL2}/vulns/${encodeURIComponent(vulnId)}`;
|
|
248
265
|
const res = await fetch(url, {
|
|
249
|
-
headers: { "User-Agent":
|
|
266
|
+
headers: { "User-Agent": `hound-mcp/${"0.2.3"}` }
|
|
250
267
|
});
|
|
251
268
|
if (!res.ok) {
|
|
252
269
|
const text = await res.text().catch(() => "");
|
|
@@ -307,7 +324,7 @@ function register(server) {
|
|
|
307
324
|
{
|
|
308
325
|
description: "Get full details for a security advisory by ID (GHSA, CVE, or OSV ID). Returns title, severity, affected versions, fix versions, and references.",
|
|
309
326
|
inputSchema: {
|
|
310
|
-
id:
|
|
327
|
+
id: z4.string().describe("Advisory ID \u2014 e.g. GHSA-rv95-896h-c2vc, CVE-2024-29041, or any OSV ID")
|
|
311
328
|
}
|
|
312
329
|
},
|
|
313
330
|
async ({ id }) => {
|
|
@@ -402,17 +419,21 @@ function register(server) {
|
|
|
402
419
|
}
|
|
403
420
|
|
|
404
421
|
// src/tools/inspect.ts
|
|
405
|
-
import { z as
|
|
422
|
+
import { z as z5 } from "zod/v4";
|
|
423
|
+
|
|
424
|
+
// src/constants/ecosystems.ts
|
|
406
425
|
var ECOSYSTEM_VALUES = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
426
|
+
|
|
427
|
+
// src/tools/inspect.ts
|
|
407
428
|
function register2(server) {
|
|
408
429
|
return server.registerTool(
|
|
409
430
|
"hound_inspect",
|
|
410
431
|
{
|
|
411
432
|
description: "Get a comprehensive profile of a package version: licenses, vulnerabilities, OpenSSF scorecard, GitHub stats, and dependency count \u2014 all in one call.",
|
|
412
433
|
inputSchema: {
|
|
413
|
-
name:
|
|
414
|
-
version:
|
|
415
|
-
ecosystem:
|
|
434
|
+
name: z5.string().describe("Package name"),
|
|
435
|
+
version: z5.string().describe("Package version"),
|
|
436
|
+
ecosystem: z5.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
416
437
|
}
|
|
417
438
|
},
|
|
418
439
|
async ({ name, version, ecosystem }) => {
|
|
@@ -504,8 +525,14 @@ function scorecardGrade(score) {
|
|
|
504
525
|
}
|
|
505
526
|
|
|
506
527
|
// src/tools/popular.ts
|
|
507
|
-
import { z as
|
|
508
|
-
|
|
528
|
+
import { z as z6 } from "zod/v4";
|
|
529
|
+
|
|
530
|
+
// src/utils/getDefaultVersion.ts
|
|
531
|
+
function getDefaultVersion(versions) {
|
|
532
|
+
return versions.find((v) => v.isDefault) ?? versions.at(-1);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/tools/popular.ts
|
|
509
536
|
var POPULAR_DEFAULTS = {
|
|
510
537
|
npm: [
|
|
511
538
|
"express",
|
|
@@ -561,8 +588,8 @@ function register3(server) {
|
|
|
561
588
|
{
|
|
562
589
|
description: "Scan a list of popular (or user-specified) packages for known vulnerabilities. Quickly surface which widely-used packages in an ecosystem have open security issues.",
|
|
563
590
|
inputSchema: {
|
|
564
|
-
ecosystem:
|
|
565
|
-
packages:
|
|
591
|
+
ecosystem: z6.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)"),
|
|
592
|
+
packages: z6.array(z6.string()).optional().describe(
|
|
566
593
|
"Specific package names to check. If omitted, uses a curated list of popular packages for the ecosystem."
|
|
567
594
|
)
|
|
568
595
|
}
|
|
@@ -583,7 +610,7 @@ function register3(server) {
|
|
|
583
610
|
const packageResults = await Promise.allSettled(
|
|
584
611
|
names.map(async (name) => {
|
|
585
612
|
const pkg = await getPackage(eco, name);
|
|
586
|
-
const defaultVersion = pkg.versions
|
|
613
|
+
const defaultVersion = getDefaultVersion(pkg.versions);
|
|
587
614
|
return {
|
|
588
615
|
name,
|
|
589
616
|
version: defaultVersion?.versionKey.version ?? "unknown"
|
|
@@ -640,18 +667,17 @@ function register3(server) {
|
|
|
640
667
|
}
|
|
641
668
|
|
|
642
669
|
// src/tools/tree.ts
|
|
643
|
-
import { z as
|
|
644
|
-
var ECOSYSTEM_VALUES3 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
670
|
+
import { z as z7 } from "zod/v4";
|
|
645
671
|
function register4(server) {
|
|
646
672
|
return server.registerTool(
|
|
647
673
|
"hound_tree",
|
|
648
674
|
{
|
|
649
675
|
description: "Show the full resolved dependency tree for a package version, including all transitive dependencies with their depth and relation type.",
|
|
650
676
|
inputSchema: {
|
|
651
|
-
name:
|
|
652
|
-
version:
|
|
653
|
-
ecosystem:
|
|
654
|
-
maxDepth:
|
|
677
|
+
name: z7.string().describe("Package name"),
|
|
678
|
+
version: z7.string().describe("Package version"),
|
|
679
|
+
ecosystem: z7.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)"),
|
|
680
|
+
maxDepth: z7.number().int().min(1).max(10).default(3).describe("Maximum depth to display (default: 3, max: 10)")
|
|
655
681
|
}
|
|
656
682
|
},
|
|
657
683
|
async ({ name, version, ecosystem, maxDepth }) => {
|
|
@@ -670,8 +696,8 @@ function register4(server) {
|
|
|
670
696
|
}
|
|
671
697
|
const nodes = deps.nodes;
|
|
672
698
|
const edges = deps.edges;
|
|
673
|
-
const directCount = nodes.filter((n) => n.
|
|
674
|
-
const indirectCount = nodes.filter((n) => n.
|
|
699
|
+
const directCount = nodes.filter((n) => n.relation === "DIRECT").length;
|
|
700
|
+
const indirectCount = nodes.filter((n) => n.relation === "INDIRECT").length;
|
|
675
701
|
const totalCount = directCount + indirectCount;
|
|
676
702
|
const children = /* @__PURE__ */ new Map();
|
|
677
703
|
for (const edge of edges) {
|
|
@@ -709,7 +735,7 @@ function renderNode(lines, nodes, children, nodeIndex, depth, maxDepth, visited)
|
|
|
709
735
|
const isRoot = depth === 0;
|
|
710
736
|
const prefix = isRoot ? "" : "\u251C\u2500\u2500 ";
|
|
711
737
|
const errorSuffix = node.errors.length > 0 ? " \u26A0\uFE0F" : "";
|
|
712
|
-
lines.push(`${indent}${prefix}${node.
|
|
738
|
+
lines.push(`${indent}${prefix}${node.versionKey.name}@${node.versionKey.version}${errorSuffix}`);
|
|
713
739
|
if (depth >= maxDepth) {
|
|
714
740
|
const childCount = children.get(nodeIndex)?.length ?? 0;
|
|
715
741
|
if (childCount > 0) {
|
|
@@ -723,8 +749,7 @@ function renderNode(lines, nodes, children, nodeIndex, depth, maxDepth, visited)
|
|
|
723
749
|
}
|
|
724
750
|
|
|
725
751
|
// src/tools/typosquat.ts
|
|
726
|
-
import { z as
|
|
727
|
-
var ECOSYSTEM_VALUES4 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
752
|
+
import { z as z8 } from "zod/v4";
|
|
728
753
|
function generateTypos(name) {
|
|
729
754
|
const variants = /* @__PURE__ */ new Set();
|
|
730
755
|
for (let i = 0; i < name.length; i++) {
|
|
@@ -756,8 +781,8 @@ function register5(server) {
|
|
|
756
781
|
{
|
|
757
782
|
description: "Check if a package name looks like a typosquat of a popular package. Generates likely typo variants and checks which ones exist in the registry.",
|
|
758
783
|
inputSchema: {
|
|
759
|
-
name:
|
|
760
|
-
ecosystem:
|
|
784
|
+
name: z8.string().describe("Package name to check"),
|
|
785
|
+
ecosystem: z8.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
761
786
|
}
|
|
762
787
|
},
|
|
763
788
|
async ({ name, ecosystem }) => {
|
|
@@ -804,7 +829,7 @@ function register5(server) {
|
|
|
804
829
|
}
|
|
805
830
|
|
|
806
831
|
// src/tools/audit.ts
|
|
807
|
-
import { z as
|
|
832
|
+
import { z as z9 } from "zod/v4";
|
|
808
833
|
|
|
809
834
|
// src/parsers/index.ts
|
|
810
835
|
function parseLockfile(filename, content) {
|
|
@@ -958,8 +983,8 @@ function register6(server) {
|
|
|
958
983
|
{
|
|
959
984
|
description: "Scan a project's lockfile for dependency risks. Parses package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.lock, or go.sum and batch-queries OSV for vulnerabilities across all dependencies.",
|
|
960
985
|
inputSchema: {
|
|
961
|
-
lockfile_content:
|
|
962
|
-
lockfile_name:
|
|
986
|
+
lockfile_content: z9.string().describe("Full text content of the lockfile"),
|
|
987
|
+
lockfile_name: z9.string().describe(
|
|
963
988
|
"Filename to determine format: package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.lock, go.sum"
|
|
964
989
|
)
|
|
965
990
|
}
|
|
@@ -1081,8 +1106,9 @@ ${icon} ${sev} (${group.length})`);
|
|
|
1081
1106
|
}
|
|
1082
1107
|
|
|
1083
1108
|
// src/tools/compare.ts
|
|
1084
|
-
import { z as
|
|
1085
|
-
|
|
1109
|
+
import { z as z10 } from "zod/v4";
|
|
1110
|
+
|
|
1111
|
+
// src/constants/licenses.ts
|
|
1086
1112
|
var COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
|
|
1087
1113
|
"GPL-2.0",
|
|
1088
1114
|
"GPL-2.0-only",
|
|
@@ -1104,11 +1130,20 @@ var COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
|
|
|
1104
1130
|
"EPL-1.0",
|
|
1105
1131
|
"EPL-2.0"
|
|
1106
1132
|
]);
|
|
1133
|
+
var NETWORK_COPYLEFT = /* @__PURE__ */ new Set([
|
|
1134
|
+
"AGPL-3.0",
|
|
1135
|
+
"AGPL-3.0-only",
|
|
1136
|
+
"AGPL-3.0-or-later",
|
|
1137
|
+
"EUPL-1.1",
|
|
1138
|
+
"EUPL-1.2"
|
|
1139
|
+
]);
|
|
1140
|
+
|
|
1141
|
+
// src/tools/compare.ts
|
|
1107
1142
|
async function gatherPackageData(eco, name) {
|
|
1108
1143
|
let defaultVersion;
|
|
1109
1144
|
try {
|
|
1110
1145
|
const pkg = await getPackage(eco, name);
|
|
1111
|
-
const defaultV = pkg.versions
|
|
1146
|
+
const defaultV = getDefaultVersion(pkg.versions);
|
|
1112
1147
|
if (!defaultV) return null;
|
|
1113
1148
|
defaultVersion = defaultV.versionKey.version;
|
|
1114
1149
|
} catch {
|
|
@@ -1162,9 +1197,9 @@ function register7(server) {
|
|
|
1162
1197
|
{
|
|
1163
1198
|
description: "Side-by-side comparison of two packages: vulnerabilities, OpenSSF Scorecard, GitHub stars, release recency, and license. Returns a recommendation.",
|
|
1164
1199
|
inputSchema: {
|
|
1165
|
-
package_a:
|
|
1166
|
-
package_b:
|
|
1167
|
-
ecosystem:
|
|
1200
|
+
package_a: z10.string().describe("First package name"),
|
|
1201
|
+
package_b: z10.string().describe("Second package name"),
|
|
1202
|
+
ecosystem: z10.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
1168
1203
|
}
|
|
1169
1204
|
},
|
|
1170
1205
|
async ({ package_a, package_b, ecosystem }) => {
|
|
@@ -1272,29 +1307,7 @@ function register7(server) {
|
|
|
1272
1307
|
}
|
|
1273
1308
|
|
|
1274
1309
|
// src/tools/license-check.ts
|
|
1275
|
-
import { z as
|
|
1276
|
-
var COPYLEFT_LICENSES2 = /* @__PURE__ */ new Set([
|
|
1277
|
-
"GPL-2.0",
|
|
1278
|
-
"GPL-2.0-only",
|
|
1279
|
-
"GPL-2.0-or-later",
|
|
1280
|
-
"GPL-3.0",
|
|
1281
|
-
"GPL-3.0-only",
|
|
1282
|
-
"GPL-3.0-or-later",
|
|
1283
|
-
"AGPL-3.0",
|
|
1284
|
-
"AGPL-3.0-only",
|
|
1285
|
-
"AGPL-3.0-or-later",
|
|
1286
|
-
"LGPL-2.0",
|
|
1287
|
-
"LGPL-2.1",
|
|
1288
|
-
"LGPL-3.0",
|
|
1289
|
-
"EUPL-1.1",
|
|
1290
|
-
"EUPL-1.2",
|
|
1291
|
-
"MPL-2.0",
|
|
1292
|
-
"OSL-3.0",
|
|
1293
|
-
"CDDL-1.0",
|
|
1294
|
-
"EPL-1.0",
|
|
1295
|
-
"EPL-2.0"
|
|
1296
|
-
]);
|
|
1297
|
-
var NETWORK_COPYLEFT = /* @__PURE__ */ new Set(["AGPL-3.0", "AGPL-3.0-only", "AGPL-3.0-or-later", "EUPL-1.1", "EUPL-1.2"]);
|
|
1310
|
+
import { z as z11 } from "zod/v4";
|
|
1298
1311
|
var POLICY_ALLOWLISTS = {
|
|
1299
1312
|
permissive: /* @__PURE__ */ new Set([
|
|
1300
1313
|
"MIT",
|
|
@@ -1337,7 +1350,7 @@ var POLICY_ALLOWLISTS = {
|
|
|
1337
1350
|
};
|
|
1338
1351
|
function classifyLicense(license) {
|
|
1339
1352
|
if (NETWORK_COPYLEFT.has(license)) return "network-copyleft";
|
|
1340
|
-
if (
|
|
1353
|
+
if (COPYLEFT_LICENSES.has(license)) {
|
|
1341
1354
|
if (license.startsWith("LGPL") || license === "MPL-2.0" || license === "EPL-1.0" || license === "EPL-2.0" || license === "CDDL-1.0") {
|
|
1342
1355
|
return "weak-copyleft";
|
|
1343
1356
|
}
|
|
@@ -1353,9 +1366,9 @@ function register8(server) {
|
|
|
1353
1366
|
{
|
|
1354
1367
|
description: "Scan a lockfile for license compliance. Resolves licenses for every dependency and flags packages that violate the chosen policy (permissive, copyleft, or none).",
|
|
1355
1368
|
inputSchema: {
|
|
1356
|
-
lockfile_content:
|
|
1357
|
-
lockfile_name:
|
|
1358
|
-
policy:
|
|
1369
|
+
lockfile_content: z11.string().describe("Full text content of the lockfile"),
|
|
1370
|
+
lockfile_name: z11.string().describe("Filename: package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.lock, go.sum"),
|
|
1371
|
+
policy: z11.enum(["permissive", "copyleft", "none"]).default("permissive").describe(
|
|
1359
1372
|
"License policy to enforce: 'permissive' (MIT/Apache/BSD only), 'copyleft' (allows GPL but not AGPL), 'none' (report only, no violations)"
|
|
1360
1373
|
)
|
|
1361
1374
|
}
|
|
@@ -1472,38 +1485,16 @@ Supported: package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo
|
|
|
1472
1485
|
}
|
|
1473
1486
|
|
|
1474
1487
|
// src/tools/preinstall.ts
|
|
1475
|
-
import { z as
|
|
1476
|
-
var ECOSYSTEM_VALUES6 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
1477
|
-
var COPYLEFT_LICENSES3 = /* @__PURE__ */ new Set([
|
|
1478
|
-
"GPL-2.0",
|
|
1479
|
-
"GPL-2.0-only",
|
|
1480
|
-
"GPL-2.0-or-later",
|
|
1481
|
-
"GPL-3.0",
|
|
1482
|
-
"GPL-3.0-only",
|
|
1483
|
-
"GPL-3.0-or-later",
|
|
1484
|
-
"AGPL-3.0",
|
|
1485
|
-
"AGPL-3.0-only",
|
|
1486
|
-
"AGPL-3.0-or-later",
|
|
1487
|
-
"LGPL-2.0",
|
|
1488
|
-
"LGPL-2.1",
|
|
1489
|
-
"LGPL-3.0",
|
|
1490
|
-
"EUPL-1.1",
|
|
1491
|
-
"EUPL-1.2",
|
|
1492
|
-
"MPL-2.0",
|
|
1493
|
-
"OSL-3.0",
|
|
1494
|
-
"CDDL-1.0",
|
|
1495
|
-
"EPL-1.0",
|
|
1496
|
-
"EPL-2.0"
|
|
1497
|
-
]);
|
|
1488
|
+
import { z as z12 } from "zod/v4";
|
|
1498
1489
|
function register9(server) {
|
|
1499
1490
|
return server.registerTool(
|
|
1500
1491
|
"hound_preinstall",
|
|
1501
1492
|
{
|
|
1502
1493
|
description: "Safety check before installing a package. Checks known vulnerabilities, typosquatting risk, abandonment, and license concerns. Returns a go/no-go verdict.",
|
|
1503
1494
|
inputSchema: {
|
|
1504
|
-
name:
|
|
1505
|
-
version:
|
|
1506
|
-
ecosystem:
|
|
1495
|
+
name: z12.string().describe("Package name"),
|
|
1496
|
+
version: z12.string().optional().describe("Package version (defaults to latest)"),
|
|
1497
|
+
ecosystem: z12.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
1507
1498
|
}
|
|
1508
1499
|
},
|
|
1509
1500
|
async ({ name, version, ecosystem }) => {
|
|
@@ -1512,7 +1503,7 @@ function register9(server) {
|
|
|
1512
1503
|
if (!resolvedVersion) {
|
|
1513
1504
|
try {
|
|
1514
1505
|
const pkg2 = await getPackage(eco, name);
|
|
1515
|
-
const defaultV = pkg2.versions
|
|
1506
|
+
const defaultV = getDefaultVersion(pkg2.versions);
|
|
1516
1507
|
resolvedVersion = defaultV?.versionKey.version ?? "";
|
|
1517
1508
|
} catch {
|
|
1518
1509
|
return {
|
|
@@ -1600,7 +1591,7 @@ function register9(server) {
|
|
|
1600
1591
|
const licenses = pkg.licenses ?? [];
|
|
1601
1592
|
if (licenses.length === 0) {
|
|
1602
1593
|
issues.push({ level: "warn", message: "License unknown" });
|
|
1603
|
-
} else if (licenses.some((l) =>
|
|
1594
|
+
} else if (licenses.some((l) => COPYLEFT_LICENSES.has(l))) {
|
|
1604
1595
|
issues.push({
|
|
1605
1596
|
level: "warn",
|
|
1606
1597
|
message: `Copyleft license detected: ${licenses.join(", ")}`
|
|
@@ -1661,29 +1652,7 @@ function register9(server) {
|
|
|
1661
1652
|
}
|
|
1662
1653
|
|
|
1663
1654
|
// src/tools/score.ts
|
|
1664
|
-
import { z as
|
|
1665
|
-
var ECOSYSTEM_VALUES7 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
1666
|
-
var COPYLEFT_LICENSES4 = /* @__PURE__ */ new Set([
|
|
1667
|
-
"GPL-2.0",
|
|
1668
|
-
"GPL-2.0-only",
|
|
1669
|
-
"GPL-2.0-or-later",
|
|
1670
|
-
"GPL-3.0",
|
|
1671
|
-
"GPL-3.0-only",
|
|
1672
|
-
"GPL-3.0-or-later",
|
|
1673
|
-
"AGPL-3.0",
|
|
1674
|
-
"AGPL-3.0-only",
|
|
1675
|
-
"AGPL-3.0-or-later",
|
|
1676
|
-
"LGPL-2.0",
|
|
1677
|
-
"LGPL-2.1",
|
|
1678
|
-
"LGPL-3.0",
|
|
1679
|
-
"EUPL-1.1",
|
|
1680
|
-
"EUPL-1.2",
|
|
1681
|
-
"MPL-2.0",
|
|
1682
|
-
"OSL-3.0",
|
|
1683
|
-
"CDDL-1.0",
|
|
1684
|
-
"EPL-1.0",
|
|
1685
|
-
"EPL-2.0"
|
|
1686
|
-
]);
|
|
1655
|
+
import { z as z13 } from "zod/v4";
|
|
1687
1656
|
function letterGrade(score) {
|
|
1688
1657
|
if (score >= 90) return "A";
|
|
1689
1658
|
if (score >= 75) return "B";
|
|
@@ -1711,9 +1680,9 @@ function register10(server) {
|
|
|
1711
1680
|
{
|
|
1712
1681
|
description: "Compute a 0-100 Hound Score for a package version combining vulnerability severity, OpenSSF Scorecard, release recency, and license risk. Returns a letter grade (A-F) with a breakdown.",
|
|
1713
1682
|
inputSchema: {
|
|
1714
|
-
name:
|
|
1715
|
-
version:
|
|
1716
|
-
ecosystem:
|
|
1683
|
+
name: z13.string().describe("Package name"),
|
|
1684
|
+
version: z13.string().describe("Package version"),
|
|
1685
|
+
ecosystem: z13.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
1717
1686
|
}
|
|
1718
1687
|
},
|
|
1719
1688
|
async ({ name, version, ecosystem }) => {
|
|
@@ -1772,7 +1741,7 @@ function register10(server) {
|
|
|
1772
1741
|
const licenses = pkg.licenses ?? [];
|
|
1773
1742
|
if (licenses.length === 0) {
|
|
1774
1743
|
licenseScore = 5;
|
|
1775
|
-
} else if (licenses.some((l) =>
|
|
1744
|
+
} else if (licenses.some((l) => COPYLEFT_LICENSES.has(l))) {
|
|
1776
1745
|
licenseScore = 8;
|
|
1777
1746
|
}
|
|
1778
1747
|
const total = vulnScore + scorecardScore + recencyScore + licenseScore;
|
|
@@ -1812,7 +1781,7 @@ function register10(server) {
|
|
|
1812
1781
|
lines.push(`\u{1F4C5} Published ${daysSince} days ago`);
|
|
1813
1782
|
if (licenses.length === 0) {
|
|
1814
1783
|
lines.push("\u26A0\uFE0F License unknown");
|
|
1815
|
-
} else if (licenses.some((l) =>
|
|
1784
|
+
} else if (licenses.some((l) => COPYLEFT_LICENSES.has(l))) {
|
|
1816
1785
|
lines.push(`\u26A0\uFE0F Copyleft license: ${licenses.join(", ")}`);
|
|
1817
1786
|
} else {
|
|
1818
1787
|
lines.push(`\u2705 License: ${licenses.join(", ")}`);
|
|
@@ -1827,8 +1796,7 @@ function register10(server) {
|
|
|
1827
1796
|
}
|
|
1828
1797
|
|
|
1829
1798
|
// src/tools/upgrade.ts
|
|
1830
|
-
import { z as
|
|
1831
|
-
var ECOSYSTEM_VALUES8 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
1799
|
+
import { z as z14 } from "zod/v4";
|
|
1832
1800
|
function parseVersion(v) {
|
|
1833
1801
|
return v.split(".").map((p) => parseInt(p.replace(/[^0-9]/g, ""), 10) || 0);
|
|
1834
1802
|
}
|
|
@@ -1847,9 +1815,9 @@ function register11(server) {
|
|
|
1847
1815
|
{
|
|
1848
1816
|
description: "Find the minimum version upgrade that resolves all known vulnerabilities for a package. Checks every published version and returns the nearest safe one.",
|
|
1849
1817
|
inputSchema: {
|
|
1850
|
-
name:
|
|
1851
|
-
version:
|
|
1852
|
-
ecosystem:
|
|
1818
|
+
name: z14.string().describe("Package name (e.g. express, lodash)"),
|
|
1819
|
+
version: z14.string().describe("Current vulnerable version (e.g. 4.17.20)"),
|
|
1820
|
+
ecosystem: z14.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
1853
1821
|
}
|
|
1854
1822
|
},
|
|
1855
1823
|
async ({ name, version, ecosystem }) => {
|
|
@@ -1886,7 +1854,7 @@ This may be the latest version already.`
|
|
|
1886
1854
|
toCheck.map((v) => ({ ecosystem: eco, name, version: v }))
|
|
1887
1855
|
);
|
|
1888
1856
|
const safeVersions = toCheck.filter((_, i) => (vulnResults[i]?.length ?? 0) === 0);
|
|
1889
|
-
const latestVersion = candidates
|
|
1857
|
+
const latestVersion = candidates.at(-1) ?? version;
|
|
1890
1858
|
const latestVulns = vulnResults[toCheck.indexOf(latestVersion)] ?? [];
|
|
1891
1859
|
const lines = [
|
|
1892
1860
|
`\u{1F50D} Safe upgrade finder: ${name} (${ecosystem})`,
|
|
@@ -1903,7 +1871,7 @@ This may be the latest version already.`
|
|
|
1903
1871
|
lines.push(" \u2022 Evaluating an alternative package via hound_compare");
|
|
1904
1872
|
} else {
|
|
1905
1873
|
const minimum = safeVersions[0] ?? "";
|
|
1906
|
-
const latest = safeVersions
|
|
1874
|
+
const latest = safeVersions.at(-1) ?? "";
|
|
1907
1875
|
lines.push(`\u2705 Safe upgrade available`);
|
|
1908
1876
|
lines.push("");
|
|
1909
1877
|
lines.push(` Minimum safe version: ${minimum}`);
|
|
@@ -1911,7 +1879,7 @@ This may be the latest version already.`
|
|
|
1911
1879
|
lines.push(` Latest safe version: ${latest}`);
|
|
1912
1880
|
}
|
|
1913
1881
|
lines.push("");
|
|
1914
|
-
if (latestVulns.length > 0 && safeVersions
|
|
1882
|
+
if (latestVulns.length > 0 && safeVersions.at(-1) !== latestVersion) {
|
|
1915
1883
|
lines.push(`\u26A0\uFE0F Latest version (${latestVersion}) still has ${latestVulns.length} known vuln(s).`);
|
|
1916
1884
|
lines.push(` Recommended: upgrade to ${latest}`);
|
|
1917
1885
|
} else {
|
|
@@ -1932,8 +1900,7 @@ This may be the latest version already.`
|
|
|
1932
1900
|
}
|
|
1933
1901
|
|
|
1934
1902
|
// src/tools/vulns.ts
|
|
1935
|
-
import { z as
|
|
1936
|
-
var ECOSYSTEM_VALUES9 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
|
|
1903
|
+
import { z as z15 } from "zod/v4";
|
|
1937
1904
|
var SEVERITY_ICON2 = {
|
|
1938
1905
|
CRITICAL: "\u{1F534}",
|
|
1939
1906
|
HIGH: "\u{1F7E0}",
|
|
@@ -1947,9 +1914,9 @@ function register12(server) {
|
|
|
1947
1914
|
{
|
|
1948
1915
|
description: "List all known vulnerabilities for a specific package version, grouped by severity with fix versions and advisory links.",
|
|
1949
1916
|
inputSchema: {
|
|
1950
|
-
name:
|
|
1951
|
-
version:
|
|
1952
|
-
ecosystem:
|
|
1917
|
+
name: z15.string().describe("Package name (e.g. express, lodash)"),
|
|
1918
|
+
version: z15.string().describe("Package version (e.g. 4.18.2)"),
|
|
1919
|
+
ecosystem: z15.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
|
|
1953
1920
|
}
|
|
1954
1921
|
},
|
|
1955
1922
|
async ({ name, version, ecosystem }) => {
|
|
@@ -2027,7 +1994,7 @@ This checks the OSV database which covers GitHub Advisory Database, NVD, and mor
|
|
|
2027
1994
|
|
|
2028
1995
|
// src/server.ts
|
|
2029
1996
|
var SERVER_NAME = "hound-mcp";
|
|
2030
|
-
var SERVER_VERSION = "0.2.
|
|
1997
|
+
var SERVER_VERSION = "0.2.3";
|
|
2031
1998
|
function createServer() {
|
|
2032
1999
|
const server = new McpServer({
|
|
2033
2000
|
name: SERVER_NAME,
|