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/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/index.ts
9
+ // src/prompts/package_evaluation.ts
10
10
  import { z } from "zod/v4";
11
- function registerPrompts(server) {
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: z.string().optional().describe("The version you are about to release, e.g. 1.2.0")
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 z2 } from "zod/v4";
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": "hound-mcp/0.1.0" }
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": "hound-mcp/0.1.0"
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": "hound-mcp/0.1.0" }
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: z2.string().describe("Advisory ID \u2014 e.g. GHSA-rv95-896h-c2vc, CVE-2024-29041, or any OSV 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 z3 } from "zod/v4";
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: z3.string().describe("Package name"),
414
- version: z3.string().describe("Package version"),
415
- ecosystem: z3.enum(ECOSYSTEM_VALUES).default("npm").describe("Package ecosystem (default: npm)")
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 z4 } from "zod/v4";
508
- var ECOSYSTEM_VALUES2 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
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: z4.enum(ECOSYSTEM_VALUES2).default("npm").describe("Package ecosystem (default: npm)"),
565
- packages: z4.array(z4.string()).optional().describe(
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.find((v) => v.isDefault) ?? pkg.versions[0];
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 z5 } from "zod/v4";
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: z5.string().describe("Package name"),
652
- version: z5.string().describe("Package version"),
653
- ecosystem: z5.enum(ECOSYSTEM_VALUES3).default("npm").describe("Package ecosystem (default: npm)"),
654
- maxDepth: z5.number().int().min(1).max(10).default(3).describe("Maximum depth to display (default: 3, max: 10)")
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.relationType === "DIRECT").length;
674
- const indirectCount = nodes.filter((n) => n.relationType === "INDIRECT").length;
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.key.name}@${node.key.version}${errorSuffix}`);
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 z6 } from "zod/v4";
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: z6.string().describe("Package name to check"),
760
- ecosystem: z6.enum(ECOSYSTEM_VALUES4).default("npm").describe("Package ecosystem (default: npm)")
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 z7 } from "zod/v4";
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: z7.string().describe("Full text content of the lockfile"),
962
- lockfile_name: z7.string().describe(
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 z8 } from "zod/v4";
1085
- var ECOSYSTEM_VALUES5 = ["npm", "pypi", "go", "maven", "cargo", "nuget", "rubygems"];
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.find((v) => v.isDefault) ?? pkg.versions[pkg.versions.length - 1];
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: z8.string().describe("First package name"),
1166
- package_b: z8.string().describe("Second package name"),
1167
- ecosystem: z8.enum(ECOSYSTEM_VALUES5).default("npm").describe("Package ecosystem (default: npm)")
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 z9 } from "zod/v4";
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 (COPYLEFT_LICENSES2.has(license)) {
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: z9.string().describe("Full text content of the lockfile"),
1357
- lockfile_name: z9.string().describe("Filename: package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.lock, go.sum"),
1358
- policy: z9.enum(["permissive", "copyleft", "none"]).default("permissive").describe(
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 z10 } from "zod/v4";
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: z10.string().describe("Package name"),
1505
- version: z10.string().optional().describe("Package version (defaults to latest)"),
1506
- ecosystem: z10.enum(ECOSYSTEM_VALUES6).default("npm").describe("Package ecosystem (default: npm)")
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.find((v) => v.isDefault) ?? pkg2.versions[pkg2.versions.length - 1];
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) => COPYLEFT_LICENSES3.has(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 z11 } from "zod/v4";
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: z11.string().describe("Package name"),
1715
- version: z11.string().describe("Package version"),
1716
- ecosystem: z11.enum(ECOSYSTEM_VALUES7).default("npm").describe("Package ecosystem (default: npm)")
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) => COPYLEFT_LICENSES4.has(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) => COPYLEFT_LICENSES4.has(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 z12 } from "zod/v4";
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: z12.string().describe("Package name (e.g. express, lodash)"),
1851
- version: z12.string().describe("Current vulnerable version (e.g. 4.17.20)"),
1852
- ecosystem: z12.enum(ECOSYSTEM_VALUES8).default("npm").describe("Package ecosystem (default: npm)")
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[candidates.length - 1] ?? version;
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[safeVersions.length - 1] ?? "";
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[safeVersions.length - 1] !== latestVersion) {
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 z13 } from "zod/v4";
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: z13.string().describe("Package name (e.g. express, lodash)"),
1951
- version: z13.string().describe("Package version (e.g. 4.18.2)"),
1952
- ecosystem: z13.enum(ECOSYSTEM_VALUES9).default("npm").describe("Package ecosystem (default: npm)")
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.0";
1997
+ var SERVER_VERSION = "0.2.3";
2031
1998
  function createServer() {
2032
1999
  const server = new McpServer({
2033
2000
  name: SERVER_NAME,