@cdxgen/cdxgen 13.0.1 → 13.2.0
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 +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +12 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +43 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
|
|
4
|
+
import * as toml from "smol-toml";
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
sanitizeBomPropertyValue,
|
|
6
8
|
sanitizeBomUrl,
|
|
@@ -30,6 +32,16 @@ const MCP_CONFIG_PATTERNS = [
|
|
|
30
32
|
"**/opencode.json",
|
|
31
33
|
"opencode.jsonc",
|
|
32
34
|
"**/opencode.jsonc",
|
|
35
|
+
".gemini/settings.json",
|
|
36
|
+
"**/.gemini/settings.json",
|
|
37
|
+
".windsurf/mcp_config.json",
|
|
38
|
+
"**/.windsurf/mcp_config.json",
|
|
39
|
+
".zed/settings.json",
|
|
40
|
+
"**/.zed/settings.json",
|
|
41
|
+
".roo/mcp.json",
|
|
42
|
+
"**/.roo/mcp.json",
|
|
43
|
+
".codex/config.toml",
|
|
44
|
+
"**/.codex/config.toml",
|
|
33
45
|
];
|
|
34
46
|
|
|
35
47
|
const LOCAL_TUNNEL_HOST_PATTERNS = [
|
|
@@ -92,11 +104,32 @@ function configFormat(filePath) {
|
|
|
92
104
|
) {
|
|
93
105
|
return "cursor";
|
|
94
106
|
}
|
|
107
|
+
if (lowerPath.includes("/.gemini/")) {
|
|
108
|
+
return "gemini";
|
|
109
|
+
}
|
|
110
|
+
if (lowerPath.includes("/.windsurf/")) {
|
|
111
|
+
return "windsurf";
|
|
112
|
+
}
|
|
113
|
+
if (lowerPath.includes("/.zed/")) {
|
|
114
|
+
return "zed";
|
|
115
|
+
}
|
|
116
|
+
if (lowerPath.includes("/.roo/")) {
|
|
117
|
+
return "roo";
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
lowerPath.includes("/.codex/") ||
|
|
121
|
+
lowerPath.endsWith(".codex/config.toml")
|
|
122
|
+
) {
|
|
123
|
+
return "codex";
|
|
124
|
+
}
|
|
95
125
|
return "generic-mcp-json";
|
|
96
126
|
}
|
|
97
127
|
|
|
98
128
|
function syntaxForFile(filePath) {
|
|
99
129
|
const lowerPath = filePath.toLowerCase();
|
|
130
|
+
if (lowerPath.endsWith(".toml")) {
|
|
131
|
+
return "toml";
|
|
132
|
+
}
|
|
100
133
|
return lowerPath.endsWith(".json") || lowerPath.endsWith(".jsonc")
|
|
101
134
|
? "json"
|
|
102
135
|
: "text";
|
|
@@ -105,7 +138,15 @@ function syntaxForFile(filePath) {
|
|
|
105
138
|
function extractServerMaps(config) {
|
|
106
139
|
const serverMaps = [];
|
|
107
140
|
for (const [key, value] of Object.entries(config || {})) {
|
|
108
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
[
|
|
143
|
+
"mcpServers",
|
|
144
|
+
"context_servers",
|
|
145
|
+
"servers",
|
|
146
|
+
"mcp",
|
|
147
|
+
"mcp_servers",
|
|
148
|
+
].includes(key)
|
|
149
|
+
) {
|
|
109
150
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
110
151
|
serverMaps.push({ configKey: key, servers: Object.entries(value) });
|
|
111
152
|
} else if (Array.isArray(value)) {
|
|
@@ -382,6 +423,106 @@ function trustProfile(officialSdk, exposureType, authPosture) {
|
|
|
382
423
|
return officialSdk ? "official-sdk+unknown" : "review-needed";
|
|
383
424
|
}
|
|
384
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Extract an explicit MCP protocol version literal from a server config.
|
|
428
|
+
*
|
|
429
|
+
* @param {Object} serverConfig Parsed server configuration
|
|
430
|
+
* @returns {string|undefined} protocol version such as `2026-07-28`
|
|
431
|
+
*/
|
|
432
|
+
function protocolVersionForConfig(serverConfig) {
|
|
433
|
+
const candidate =
|
|
434
|
+
serverConfig?.protocolVersion ||
|
|
435
|
+
serverConfig?.protocol_version ||
|
|
436
|
+
serverConfig?.["MCP-Protocol-Version"] ||
|
|
437
|
+
serverConfig?.mcpProtocolVersion;
|
|
438
|
+
if (typeof candidate === "string" && /^\d{4}-\d{2}-\d{2}$/u.test(candidate)) {
|
|
439
|
+
return candidate;
|
|
440
|
+
}
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Detect MCP features deprecated by the 2026-07-28 specification that a config
|
|
446
|
+
* still references.
|
|
447
|
+
*
|
|
448
|
+
* Detection is structural rather than free-text: the transport type, an
|
|
449
|
+
* explicit dynamic client registration endpoint, and declared capability
|
|
450
|
+
* objects are reliable signals, whereas substring scans over a stringified
|
|
451
|
+
* config fire on server names and unrelated blocks. Wire-level method names
|
|
452
|
+
* (`roots/list`, `sampling/createMessage`) are exchanged at runtime and never
|
|
453
|
+
* appear in a client config stanza, so they are intentionally not matched
|
|
454
|
+
* here; source analysis is the right place for those.
|
|
455
|
+
*
|
|
456
|
+
* @param {Object} serverConfig Parsed server configuration
|
|
457
|
+
* @param {string} transport Resolved transport
|
|
458
|
+
* @returns {string[]} sorted list of deprecated feature labels
|
|
459
|
+
*/
|
|
460
|
+
function deprecatedFeaturesForConfig(serverConfig, transport) {
|
|
461
|
+
const found = new Set();
|
|
462
|
+
// Legacy HTTP+SSE transport.
|
|
463
|
+
if (transport === "sse") {
|
|
464
|
+
found.add("sse-transport");
|
|
465
|
+
}
|
|
466
|
+
// Dynamic client registration, detected from an explicit endpoint or flag.
|
|
467
|
+
if (
|
|
468
|
+
serverConfig?.registration_endpoint ||
|
|
469
|
+
serverConfig?.auth?.registration_endpoint ||
|
|
470
|
+
serverConfig?.oauth?.registration_endpoint ||
|
|
471
|
+
serverConfig?.dynamicClientRegistration === true ||
|
|
472
|
+
serverConfig?.supportsDCR === true
|
|
473
|
+
) {
|
|
474
|
+
found.add("dynamic-client-registration");
|
|
475
|
+
}
|
|
476
|
+
// Capability objects the client explicitly declares. A capabilities block is
|
|
477
|
+
// the structured place these appear; a bare declaration of them is the
|
|
478
|
+
// deprecated shape under 2026-07-28.
|
|
479
|
+
const capabilities = serverConfig?.capabilities;
|
|
480
|
+
if (capabilities && typeof capabilities === "object") {
|
|
481
|
+
for (const feature of ["sampling", "logging", "roots"]) {
|
|
482
|
+
if (capabilities[feature] && typeof capabilities[feature] === "object") {
|
|
483
|
+
found.add(feature);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return Array.from(found).sort();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Detect Client ID Metadata Document (CIMD) usage in a server config.
|
|
492
|
+
*
|
|
493
|
+
* @param {Object} serverConfig Parsed server configuration
|
|
494
|
+
* @returns {boolean} true when CIMD is configured
|
|
495
|
+
*/
|
|
496
|
+
function usesClientIdMetadataDocument(serverConfig) {
|
|
497
|
+
return Boolean(
|
|
498
|
+
serverConfig?.clientIdMetadataDocument ||
|
|
499
|
+
serverConfig?.client_id_metadata_document ||
|
|
500
|
+
serverConfig?.clientIdMetadataDocumentEnabled ||
|
|
501
|
+
serverConfig?.client_id_metadata_document_enabled ||
|
|
502
|
+
serverConfig?.oauth?.clientIdMetadataDocument,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Detect whether a config binds tokens to a specific resource audience, per the
|
|
508
|
+
* hardened MCP 2026-07-28 authorization model.
|
|
509
|
+
*
|
|
510
|
+
* @param {Object} serverConfig Parsed server configuration
|
|
511
|
+
* @returns {boolean} true when audience-bound resource metadata is present
|
|
512
|
+
*/
|
|
513
|
+
function usesAudienceBoundTokens(serverConfig) {
|
|
514
|
+
const resourceMetadata =
|
|
515
|
+
serverConfig?.resourceMetadata || serverConfig?.oauth?.resourceMetadata;
|
|
516
|
+
if (resourceMetadata && typeof resourceMetadata === "object") {
|
|
517
|
+
return Boolean(
|
|
518
|
+
resourceMetadata.resource &&
|
|
519
|
+
(Array.isArray(resourceMetadata.authorization_servers) ||
|
|
520
|
+
resourceMetadata.authorizationServers),
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
385
526
|
function createServiceFromConfig(
|
|
386
527
|
filePath,
|
|
387
528
|
format,
|
|
@@ -511,6 +652,27 @@ function createServiceFromConfig(
|
|
|
511
652
|
if (authHints.has("oauth")) {
|
|
512
653
|
addUniqueProperty(properties, "cdx:mcp:auth:requiresOAuth", "true");
|
|
513
654
|
}
|
|
655
|
+
if (usesClientIdMetadataDocument(serverConfig)) {
|
|
656
|
+
addUniqueProperty(properties, "cdx:mcp:auth:cimd", "true");
|
|
657
|
+
}
|
|
658
|
+
if (usesAudienceBoundTokens(serverConfig)) {
|
|
659
|
+
addUniqueProperty(properties, "cdx:mcp:auth:audienceBound", "true");
|
|
660
|
+
}
|
|
661
|
+
const protocolVersion = protocolVersionForConfig(serverConfig);
|
|
662
|
+
if (protocolVersion) {
|
|
663
|
+
addUniqueProperty(properties, "cdx:mcp:protocolVersion", protocolVersion);
|
|
664
|
+
}
|
|
665
|
+
const deprecatedFeatures = deprecatedFeaturesForConfig(
|
|
666
|
+
serverConfig,
|
|
667
|
+
transport,
|
|
668
|
+
);
|
|
669
|
+
if (deprecatedFeatures.length) {
|
|
670
|
+
addUniqueProperty(
|
|
671
|
+
properties,
|
|
672
|
+
"cdx:mcp:deprecatedFeatures",
|
|
673
|
+
deprecatedFeatures.join(","),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
514
676
|
if (
|
|
515
677
|
serverConfig?.protectedResourceMetadata ||
|
|
516
678
|
serverConfig?.resourceServerUrl ||
|
|
@@ -650,7 +812,9 @@ export const mcpConfigParser = {
|
|
|
650
812
|
}
|
|
651
813
|
let configJson;
|
|
652
814
|
try {
|
|
653
|
-
configJson =
|
|
815
|
+
configJson = filePath.toLowerCase().endsWith(".toml")
|
|
816
|
+
? toml.parse(raw)
|
|
817
|
+
: parseJsonLike(raw);
|
|
654
818
|
} catch {
|
|
655
819
|
continue;
|
|
656
820
|
}
|
|
@@ -6,6 +6,17 @@ const PROVIDER_TEXT_PATTERNS = [
|
|
|
6
6
|
["deepseek", /\bdeepseek\b/i],
|
|
7
7
|
["ollama", /\bollama\b/i],
|
|
8
8
|
["groq", /\bgroq\b/i],
|
|
9
|
+
["xai", /\bgrok\b|\bx\.ai\b/i],
|
|
10
|
+
["openrouter", /\bopenrouter\b/i],
|
|
11
|
+
// "bedrock" alone is also a WordPress stack and a Minecraft edition, so
|
|
12
|
+
// require AWS context before attributing it to Amazon Bedrock.
|
|
13
|
+
["aws-bedrock", /\b(?:aws|amazon)[\s-]?bedrock\b|\bbedrock[\s-]?runtime\b/i],
|
|
14
|
+
["cerebras", /\bcerebras\b/i],
|
|
15
|
+
// Drop the bare "kimi" alias (a common given name); keep the distinctive
|
|
16
|
+
// "moonshot" vendor token.
|
|
17
|
+
["moonshot", /\bmoonshot\b/i],
|
|
18
|
+
["zhipu", /\bzhipu\b|\bglm-[0-9]/i],
|
|
19
|
+
["qwen", /\bqwen\b|\bdashscope\b/i],
|
|
9
20
|
];
|
|
10
21
|
|
|
11
22
|
const INLINE_CREDENTIAL_PATTERNS = [
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { sanitizeBomPropertyValue } from "../core/propertySanitizer.js";
|
|
5
|
+
import { parseJsonLike } from "./jsonLike.js";
|
|
6
|
+
import { isLocalHost, sanitizeMcpRefToken } from "./mcpDiscovery.js";
|
|
7
|
+
import { tryBuildPurl } from "./purl.js";
|
|
8
|
+
import { scanTextForHiddenUnicode } from "./unicodeScan.js";
|
|
9
|
+
|
|
10
|
+
const SERVER_MANIFEST_PATTERNS = ["server.json", "**/server.json"];
|
|
11
|
+
|
|
12
|
+
// registryType values understood by the official MCP Registry, mapped to the
|
|
13
|
+
// package-url ecosystem cdxgen uses when a component purl can be built.
|
|
14
|
+
const REGISTRY_TYPE_PURL_TYPES = {
|
|
15
|
+
npm: "npm",
|
|
16
|
+
pypi: "pypi",
|
|
17
|
+
nuget: "nuget",
|
|
18
|
+
oci: "oci",
|
|
19
|
+
mcpb: undefined,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function addUniqueProperty(properties, name, value) {
|
|
23
|
+
const sanitizedValue = sanitizeBomPropertyValue(name, value);
|
|
24
|
+
if (
|
|
25
|
+
sanitizedValue === undefined ||
|
|
26
|
+
sanitizedValue === null ||
|
|
27
|
+
sanitizedValue === ""
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (
|
|
32
|
+
properties.some(
|
|
33
|
+
(property) =>
|
|
34
|
+
property.name === name && property.value === String(sanitizedValue),
|
|
35
|
+
)
|
|
36
|
+
) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
properties.push({ name, value: String(sanitizedValue) });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determine whether a parsed object looks like an MCP Registry server manifest.
|
|
44
|
+
*
|
|
45
|
+
* The heuristic keeps unrelated `server.json` files (for example dev-server
|
|
46
|
+
* configuration) out of the MCP inventory by requiring the manifest to carry a
|
|
47
|
+
* server name plus at least one `packages[]` or `remotes[]` entry, or an
|
|
48
|
+
* explicit MCP Registry `$schema` reference.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} manifest Parsed JSON object
|
|
51
|
+
* @returns {boolean} true when the object resembles a registry manifest
|
|
52
|
+
*/
|
|
53
|
+
function looksLikeServerManifest(manifest) {
|
|
54
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const schema = String(manifest.$schema || "").toLowerCase();
|
|
58
|
+
if (schema.includes("modelcontextprotocol")) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const hasName = typeof manifest.name === "string" && manifest.name.length > 0;
|
|
62
|
+
const hasPackages =
|
|
63
|
+
Array.isArray(manifest.packages) && manifest.packages.length > 0;
|
|
64
|
+
const hasRemotes =
|
|
65
|
+
Array.isArray(manifest.remotes) && manifest.remotes.length > 0;
|
|
66
|
+
return hasName && (hasPackages || hasRemotes);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build a package-url string for a registry package entry when the
|
|
71
|
+
* registryType maps to a supported ecosystem and enough coordinates exist.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} pkg Registry package entry
|
|
74
|
+
* @returns {string|undefined} purl string, or undefined when not derivable
|
|
75
|
+
*/
|
|
76
|
+
function purlForPackage(pkg) {
|
|
77
|
+
const registryType = String(pkg?.registryType || pkg?.registry_type || "")
|
|
78
|
+
.trim()
|
|
79
|
+
.toLowerCase();
|
|
80
|
+
const purlType = REGISTRY_TYPE_PURL_TYPES[registryType];
|
|
81
|
+
if (!purlType) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const identifier = String(pkg?.identifier || pkg?.name || "").trim();
|
|
85
|
+
if (!identifier) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const version = pkg?.version ? String(pkg.version) : undefined;
|
|
89
|
+
let namespace;
|
|
90
|
+
let name = identifier;
|
|
91
|
+
const parts = { type: purlType, version };
|
|
92
|
+
if (purlType === "npm" && identifier.startsWith("@")) {
|
|
93
|
+
const [scope, unscoped] = identifier.split("/");
|
|
94
|
+
namespace = scope;
|
|
95
|
+
name = unscoped || identifier;
|
|
96
|
+
} else if (purlType === "pypi") {
|
|
97
|
+
// PyPI purls require the normalized project name: lowercase with runs of
|
|
98
|
+
// [-_.] collapsed to a single hyphen.
|
|
99
|
+
name = identifier.toLowerCase().replace(/[-_.]+/gu, "-");
|
|
100
|
+
} else if (purlType === "oci") {
|
|
101
|
+
// OCI purls are only meaningful with a lowercase name, a digest version,
|
|
102
|
+
// and a repository_url qualifier. A tag-only reference is not resolvable,
|
|
103
|
+
// so skip the purl rather than emit a misleading one.
|
|
104
|
+
const digest =
|
|
105
|
+
pkg?.digest || (version?.startsWith("sha256:") ? version : undefined);
|
|
106
|
+
const repositoryUrl = pkg?.registryBaseUrl || pkg?.repository_url;
|
|
107
|
+
if (!digest || !repositoryUrl) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
name = identifier.toLowerCase().split("/").pop();
|
|
111
|
+
parts.version = digest;
|
|
112
|
+
parts.qualifiers = { repository_url: String(repositoryUrl) };
|
|
113
|
+
}
|
|
114
|
+
parts.namespace = namespace;
|
|
115
|
+
parts.name = name;
|
|
116
|
+
return tryBuildPurl(parts)?.toString() || undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert one registry package entry into a CycloneDX component describing the
|
|
121
|
+
* distributable that hosts the MCP server.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} filePath Manifest path
|
|
124
|
+
* @param {string} serverName MCP server name
|
|
125
|
+
* @param {Object} pkg Registry package entry
|
|
126
|
+
* @param {number} index Package index for stable identity
|
|
127
|
+
* @returns {Object|undefined} component or undefined when not usable
|
|
128
|
+
*/
|
|
129
|
+
function componentForPackage(filePath, serverName, pkg, index) {
|
|
130
|
+
const registryType = String(pkg?.registryType || pkg?.registry_type || "")
|
|
131
|
+
.trim()
|
|
132
|
+
.toLowerCase();
|
|
133
|
+
const identifier = String(pkg?.identifier || pkg?.name || "").trim();
|
|
134
|
+
if (!registryType && !identifier) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const purl = purlForPackage(pkg);
|
|
138
|
+
const properties = [
|
|
139
|
+
{ name: "internal:SrcFile", value: filePath },
|
|
140
|
+
{ name: "cdx:mcp:inventorySource", value: "server-manifest" },
|
|
141
|
+
{ name: "cdx:registry:server:packageRegistryType", value: registryType },
|
|
142
|
+
];
|
|
143
|
+
if (pkg?.transport?.type || pkg?.transport) {
|
|
144
|
+
addUniqueProperty(
|
|
145
|
+
properties,
|
|
146
|
+
"cdx:registry:server:packageTransport",
|
|
147
|
+
typeof pkg.transport === "string" ? pkg.transport : pkg.transport?.type,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
// Record only the counts and field names the server will be handed at launch,
|
|
151
|
+
// never their values. These describe the launch surface without leaking
|
|
152
|
+
// secrets that a registry manifest may reference through them.
|
|
153
|
+
const secretInputFields = new Set();
|
|
154
|
+
for (const [inputKey, property] of [
|
|
155
|
+
["environmentVariables", "cdx:registry:server:environmentVariableCount"],
|
|
156
|
+
["runtimeArguments", "cdx:registry:server:runtimeArgumentCount"],
|
|
157
|
+
["packageArguments", "cdx:registry:server:packageArgumentCount"],
|
|
158
|
+
]) {
|
|
159
|
+
const entries = pkg?.[inputKey];
|
|
160
|
+
if (Array.isArray(entries) && entries.length) {
|
|
161
|
+
addUniqueProperty(properties, property, String(entries.length));
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const fieldName = entry?.name || entry?.variable;
|
|
164
|
+
if (
|
|
165
|
+
entry?.isSecret === true ||
|
|
166
|
+
entry?.secret === true ||
|
|
167
|
+
(typeof fieldName === "string" &&
|
|
168
|
+
/(token|secret|password|api[_-]?key|credential)/iu.test(fieldName))
|
|
169
|
+
) {
|
|
170
|
+
secretInputFields.add(String(fieldName || "secret"));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (secretInputFields.size) {
|
|
176
|
+
addUniqueProperty(
|
|
177
|
+
properties,
|
|
178
|
+
"cdx:registry:server:secretInputCount",
|
|
179
|
+
String(secretInputFields.size),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const component = {
|
|
183
|
+
"bom-ref":
|
|
184
|
+
purl ||
|
|
185
|
+
`urn:mcp:server-package:${sanitizeMcpRefToken(serverName)}:${sanitizeMcpRefToken(identifier || registryType)}:${index}`,
|
|
186
|
+
type: "application",
|
|
187
|
+
name: identifier || `${serverName}-package-${index}`,
|
|
188
|
+
properties,
|
|
189
|
+
};
|
|
190
|
+
if (purl) {
|
|
191
|
+
component.purl = purl;
|
|
192
|
+
}
|
|
193
|
+
if (pkg?.version) {
|
|
194
|
+
component.version = String(pkg.version);
|
|
195
|
+
}
|
|
196
|
+
return component;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Convert one registry remote entry into a CycloneDX service describing the
|
|
201
|
+
* remotely hosted MCP endpoint.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} filePath Manifest path
|
|
204
|
+
* @param {string} serverName MCP server name
|
|
205
|
+
* @param {Object} remote Registry remote entry
|
|
206
|
+
* @param {number} index Remote index for stable identity
|
|
207
|
+
* @returns {Object|undefined} service or undefined when no URL is present
|
|
208
|
+
*/
|
|
209
|
+
function serviceForRemote(filePath, serverName, remote, index) {
|
|
210
|
+
const url = typeof remote?.url === "string" ? remote.url : undefined;
|
|
211
|
+
if (!url) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
let hostname;
|
|
215
|
+
try {
|
|
216
|
+
hostname = new URL(url).hostname;
|
|
217
|
+
} catch {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const transport = String(remote?.type || remote?.transport || "")
|
|
221
|
+
.trim()
|
|
222
|
+
.toLowerCase();
|
|
223
|
+
const exposureType = isLocalHost(hostname)
|
|
224
|
+
? "local-only"
|
|
225
|
+
: "networked-public";
|
|
226
|
+
const properties = [
|
|
227
|
+
{ name: "internal:SrcFile", value: filePath },
|
|
228
|
+
{ name: "cdx:mcp:serviceType", value: "registry-remote" },
|
|
229
|
+
{ name: "cdx:mcp:inventorySource", value: "server-manifest" },
|
|
230
|
+
{ name: "cdx:mcp:exposureType", value: exposureType },
|
|
231
|
+
// A remote server has no resolvable local package, so its composition
|
|
232
|
+
// cannot be verified from the manifest alone.
|
|
233
|
+
{ name: "cdx:mcp:composition", value: "unknown" },
|
|
234
|
+
{ name: "cdx:mcp:reviewNeeded", value: "true" },
|
|
235
|
+
];
|
|
236
|
+
if (transport) {
|
|
237
|
+
addUniqueProperty(
|
|
238
|
+
properties,
|
|
239
|
+
"cdx:mcp:transport",
|
|
240
|
+
transport.includes("sse")
|
|
241
|
+
? "sse"
|
|
242
|
+
: transport.includes("stream") || transport.includes("http")
|
|
243
|
+
? "streamable-http"
|
|
244
|
+
: transport,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
"bom-ref": `urn:service:mcp:${sanitizeMcpRefToken(serverName)}:remote:${index}`,
|
|
249
|
+
group: "mcp",
|
|
250
|
+
name: serverName,
|
|
251
|
+
endpoints: [url],
|
|
252
|
+
properties,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Build the file component that represents the discovered manifest itself and
|
|
258
|
+
* summarizes the packages and remotes it declares.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} filePath Manifest path
|
|
261
|
+
* @param {Object} manifest Parsed manifest
|
|
262
|
+
* @param {string} raw Raw file contents
|
|
263
|
+
* @param {number} packageCount Number of package components emitted
|
|
264
|
+
* @param {number} remoteCount Number of remote services emitted
|
|
265
|
+
* @returns {Object} file component
|
|
266
|
+
*/
|
|
267
|
+
function manifestFileComponent(
|
|
268
|
+
filePath,
|
|
269
|
+
manifest,
|
|
270
|
+
raw,
|
|
271
|
+
packageCount,
|
|
272
|
+
remoteCount,
|
|
273
|
+
) {
|
|
274
|
+
const properties = [
|
|
275
|
+
{ name: "internal:SrcFile", value: filePath },
|
|
276
|
+
{ name: "cdx:file:kind", value: "mcp-server-manifest" },
|
|
277
|
+
{ name: "cdx:mcp:inventorySource", value: "server-manifest" },
|
|
278
|
+
{
|
|
279
|
+
name: "cdx:registry:server:packageCount",
|
|
280
|
+
value: String(packageCount),
|
|
281
|
+
},
|
|
282
|
+
{ name: "cdx:registry:server:remoteCount", value: String(remoteCount) },
|
|
283
|
+
];
|
|
284
|
+
addUniqueProperty(
|
|
285
|
+
properties,
|
|
286
|
+
"cdx:registry:server:name",
|
|
287
|
+
typeof manifest.name === "string" ? manifest.name : undefined,
|
|
288
|
+
);
|
|
289
|
+
addUniqueProperty(
|
|
290
|
+
properties,
|
|
291
|
+
"cdx:registry:server:version",
|
|
292
|
+
manifest.version ? String(manifest.version) : undefined,
|
|
293
|
+
);
|
|
294
|
+
const hiddenUnicodeScan = scanTextForHiddenUnicode(raw, { syntax: "json" });
|
|
295
|
+
if (hiddenUnicodeScan.hasHiddenUnicode) {
|
|
296
|
+
addUniqueProperty(properties, "cdx:file:hasHiddenUnicode", "true");
|
|
297
|
+
addUniqueProperty(
|
|
298
|
+
properties,
|
|
299
|
+
"cdx:file:hiddenUnicodeCodePoints",
|
|
300
|
+
hiddenUnicodeScan.codePoints.join(","),
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
"bom-ref": `file:${filePath}`,
|
|
305
|
+
type: "file",
|
|
306
|
+
name: basename(filePath),
|
|
307
|
+
properties,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Parser for MCP Registry server manifests (`server.json`).
|
|
313
|
+
*
|
|
314
|
+
* Converts each declared distributable package into a CycloneDX component
|
|
315
|
+
* (with a purl when the registry type maps to a supported ecosystem) and each
|
|
316
|
+
* remotely hosted endpoint into a service, alongside a file component that
|
|
317
|
+
* summarizes the manifest.
|
|
318
|
+
*
|
|
319
|
+
* @type {{id: string, patterns: string[], parse(files: string[], options?: Object): {components: Object[], services: Object[]}}}
|
|
320
|
+
*/
|
|
321
|
+
export const mcpServerManifestParser = {
|
|
322
|
+
id: "mcp-server-manifest",
|
|
323
|
+
patterns: SERVER_MANIFEST_PATTERNS,
|
|
324
|
+
parse(files, _options = {}) {
|
|
325
|
+
const components = [];
|
|
326
|
+
const services = [];
|
|
327
|
+
for (const filePath of [...new Set(files || [])]) {
|
|
328
|
+
let raw;
|
|
329
|
+
try {
|
|
330
|
+
raw = readFileSync(filePath, "utf-8");
|
|
331
|
+
} catch {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
let manifest;
|
|
335
|
+
try {
|
|
336
|
+
manifest = parseJsonLike(raw);
|
|
337
|
+
} catch {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (!looksLikeServerManifest(manifest)) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const serverName =
|
|
344
|
+
typeof manifest.name === "string" && manifest.name.length
|
|
345
|
+
? manifest.name
|
|
346
|
+
: basename(filePath);
|
|
347
|
+
const packageComponents = [];
|
|
348
|
+
const packages = Array.isArray(manifest.packages)
|
|
349
|
+
? manifest.packages
|
|
350
|
+
: [];
|
|
351
|
+
for (let index = 0; index < packages.length; index++) {
|
|
352
|
+
const component = componentForPackage(
|
|
353
|
+
filePath,
|
|
354
|
+
serverName,
|
|
355
|
+
packages[index],
|
|
356
|
+
index,
|
|
357
|
+
);
|
|
358
|
+
if (component) {
|
|
359
|
+
packageComponents.push(component);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const remoteServices = [];
|
|
363
|
+
const remotes = Array.isArray(manifest.remotes) ? manifest.remotes : [];
|
|
364
|
+
for (let index = 0; index < remotes.length; index++) {
|
|
365
|
+
const service = serviceForRemote(
|
|
366
|
+
filePath,
|
|
367
|
+
serverName,
|
|
368
|
+
remotes[index],
|
|
369
|
+
index,
|
|
370
|
+
);
|
|
371
|
+
if (service) {
|
|
372
|
+
remoteServices.push(service);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
components.push(
|
|
376
|
+
manifestFileComponent(
|
|
377
|
+
filePath,
|
|
378
|
+
manifest,
|
|
379
|
+
raw,
|
|
380
|
+
packageComponents.length,
|
|
381
|
+
remoteServices.length,
|
|
382
|
+
),
|
|
383
|
+
);
|
|
384
|
+
components.push(...packageComponents);
|
|
385
|
+
services.push(...remoteServices);
|
|
386
|
+
}
|
|
387
|
+
return { components, services };
|
|
388
|
+
},
|
|
389
|
+
};
|