@cyclonedx/cdxgen 12.3.0 → 12.3.2

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.
Files changed (121) hide show
  1. package/README.md +15 -5
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +241 -81
  4. package/bin/repl.js +138 -0
  5. package/data/rules/ai-agent-governance.yaml +249 -0
  6. package/data/rules/dependency-sources.yaml +41 -0
  7. package/data/rules/mcp-servers.yaml +304 -0
  8. package/data/rules/package-integrity.yaml +123 -0
  9. package/lib/audit/index.js +353 -29
  10. package/lib/audit/index.poku.js +247 -7
  11. package/lib/audit/reporters.js +26 -0
  12. package/lib/audit/scoring.js +262 -13
  13. package/lib/audit/scoring.poku.js +179 -0
  14. package/lib/audit/targets.js +391 -2
  15. package/lib/audit/targets.poku.js +416 -3
  16. package/lib/cli/index.js +588 -45
  17. package/lib/cli/index.poku.js +735 -1
  18. package/lib/evinser/evinser.js +8 -5
  19. package/lib/helpers/agentFormulationParser.js +318 -0
  20. package/lib/helpers/aiInventory.js +262 -0
  21. package/lib/helpers/aiInventory.poku.js +111 -0
  22. package/lib/helpers/analyzer.js +1769 -0
  23. package/lib/helpers/analyzer.poku.js +284 -3
  24. package/lib/helpers/auditCategories.js +76 -0
  25. package/lib/helpers/ciParsers/githubActions.js +140 -16
  26. package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
  27. package/lib/helpers/communityAiConfigParser.js +672 -0
  28. package/lib/helpers/communityAiConfigParser.poku.js +63 -0
  29. package/lib/helpers/depsUtils.js +108 -0
  30. package/lib/helpers/depsUtils.poku.js +72 -1
  31. package/lib/helpers/display.js +325 -3
  32. package/lib/helpers/display.poku.js +301 -0
  33. package/lib/helpers/formulationParsers.js +28 -0
  34. package/lib/helpers/formulationParsers.poku.js +504 -1
  35. package/lib/helpers/jsonLike.js +102 -0
  36. package/lib/helpers/jsonLike.poku.js +34 -0
  37. package/lib/helpers/mcp.js +248 -0
  38. package/lib/helpers/mcp.poku.js +101 -0
  39. package/lib/helpers/mcpConfigParser.js +656 -0
  40. package/lib/helpers/mcpConfigParser.poku.js +126 -0
  41. package/lib/helpers/mcpDiscovery.js +84 -0
  42. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  43. package/lib/helpers/protobom.js +3 -3
  44. package/lib/helpers/provenanceUtils.js +29 -4
  45. package/lib/helpers/provenanceUtils.poku.js +29 -3
  46. package/lib/helpers/registryProvenance.js +210 -0
  47. package/lib/helpers/registryProvenance.poku.js +144 -0
  48. package/lib/helpers/rustFormulationParser.js +330 -0
  49. package/lib/helpers/source.js +21 -2
  50. package/lib/helpers/source.poku.js +38 -0
  51. package/lib/helpers/utils.js +1331 -83
  52. package/lib/helpers/utils.poku.js +599 -188
  53. package/lib/helpers/vsixutils.js +12 -4
  54. package/lib/helpers/vsixutils.poku.js +34 -0
  55. package/lib/managers/binary.js +36 -12
  56. package/lib/managers/binary.poku.js +68 -0
  57. package/lib/managers/docker.js +59 -9
  58. package/lib/managers/docker.poku.js +61 -0
  59. package/lib/managers/piptree.js +12 -7
  60. package/lib/managers/piptree.poku.js +44 -0
  61. package/lib/stages/postgen/annotator.js +2 -1
  62. package/lib/stages/postgen/annotator.poku.js +15 -0
  63. package/lib/stages/postgen/auditBom.js +20 -6
  64. package/lib/stages/postgen/auditBom.poku.js +694 -1
  65. package/lib/stages/postgen/postgen.js +262 -11
  66. package/lib/stages/postgen/postgen.poku.js +306 -2
  67. package/lib/stages/postgen/ruleEngine.js +49 -1
  68. package/lib/stages/postgen/spdxConverter.poku.js +70 -0
  69. package/lib/stages/pregen/pregen.js +6 -4
  70. package/package.json +1 -1
  71. package/types/bin/repl.d.ts.map +1 -1
  72. package/types/lib/audit/index.d.ts.map +1 -1
  73. package/types/lib/audit/reporters.d.ts.map +1 -1
  74. package/types/lib/audit/scoring.d.ts.map +1 -1
  75. package/types/lib/audit/targets.d.ts +12 -0
  76. package/types/lib/audit/targets.d.ts.map +1 -1
  77. package/types/lib/cli/index.d.ts +2 -8
  78. package/types/lib/cli/index.d.ts.map +1 -1
  79. package/types/lib/evinser/evinser.d.ts.map +1 -1
  80. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  81. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  82. package/types/lib/helpers/aiInventory.d.ts +23 -0
  83. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  84. package/types/lib/helpers/analyzer.d.ts +10 -0
  85. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  86. package/types/lib/helpers/auditCategories.d.ts +12 -0
  87. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  88. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  89. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  90. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  91. package/types/lib/helpers/depsUtils.d.ts +8 -0
  92. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  93. package/types/lib/helpers/display.d.ts +17 -1
  94. package/types/lib/helpers/display.d.ts.map +1 -1
  95. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  96. package/types/lib/helpers/jsonLike.d.ts +4 -0
  97. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  98. package/types/lib/helpers/mcp.d.ts +29 -0
  99. package/types/lib/helpers/mcp.d.ts.map +1 -0
  100. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  101. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  102. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  103. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  104. package/types/lib/helpers/provenanceUtils.d.ts +5 -3
  105. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
  106. package/types/lib/helpers/registryProvenance.d.ts +9 -0
  107. package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
  108. package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
  109. package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
  110. package/types/lib/helpers/source.d.ts.map +1 -1
  111. package/types/lib/helpers/utils.d.ts +31 -1
  112. package/types/lib/helpers/utils.d.ts.map +1 -1
  113. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  114. package/types/lib/managers/binary.d.ts.map +1 -1
  115. package/types/lib/managers/docker.d.ts.map +1 -1
  116. package/types/lib/managers/piptree.d.ts.map +1 -1
  117. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  118. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  119. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  120. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  121. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
@@ -1,10 +1,14 @@
1
1
  import { lstatSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import process from "node:process";
4
+ import { URL } from "node:url";
4
5
 
5
6
  import { parse } from "@babel/parser";
6
7
  import traverse from "@babel/traverse";
7
8
 
9
+ import { classifyMcpReference } from "./mcp.js";
10
+ import { isLocalHost, sanitizeMcpRefToken } from "./mcpDiscovery.js";
11
+
8
12
  const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
9
13
  ? process.env.ASTGEN_IGNORE_DIRS.split(",")
10
14
  : [
@@ -1355,3 +1359,1768 @@ export const detectExtensionCapabilities = (src, deep = false) => {
1355
1359
  }
1356
1360
  return { capabilities: capabilityList, indicators: indicatorMap };
1357
1361
  };
1362
+
1363
+ const MCP_STDIO_TRANSPORT_NAMES = new Set(["StdioServerTransport"]);
1364
+ const MCP_HTTP_TRANSPORT_NAMES = new Set([
1365
+ "NodeStreamableHTTPServerTransport",
1366
+ "StreamableHTTPServerTransport",
1367
+ ]);
1368
+ const MCP_STDIO_CLIENT_TRANSPORT_NAMES = new Set(["StdioClientTransport"]);
1369
+ const MCP_HTTP_CLIENT_TRANSPORT_NAMES = new Set([
1370
+ "NodeStreamableHTTPClientTransport",
1371
+ "StreamableHTTPClientTransport",
1372
+ "SSEClientTransport",
1373
+ ]);
1374
+ const MCP_AUTH_HELPERS = new Set([
1375
+ "requireBearerAuth",
1376
+ "mcpAuthMetadataRouter",
1377
+ "createProtectedResourceMetadataRouter",
1378
+ "setupAuthServer",
1379
+ ]);
1380
+ const MCP_APP_FACTORIES = new Set(["createMcpExpressApp"]);
1381
+ const MCP_CLIENT_CONSTRUCTOR_NAMES = new Set([
1382
+ "Client",
1383
+ "MCPClient",
1384
+ "McpClient",
1385
+ ]);
1386
+ const MCP_ROUTE_METHODS = new Set([
1387
+ "all",
1388
+ "delete",
1389
+ "get",
1390
+ "patch",
1391
+ "post",
1392
+ "put",
1393
+ "use",
1394
+ ]);
1395
+ const MCP_CAPABILITY_FLAGS = new Set(["listChanged", "subscribe"]);
1396
+ const MCP_CLIENT_USAGE_METHODS = new Set([
1397
+ "callTool",
1398
+ "complete",
1399
+ "connect",
1400
+ "getPrompt",
1401
+ "listPrompts",
1402
+ "listResources",
1403
+ "listTools",
1404
+ "readResource",
1405
+ "subscribe",
1406
+ ]);
1407
+ const MCP_NETWORK_METHODS = new Set([
1408
+ "fetch",
1409
+ "get",
1410
+ "patch",
1411
+ "post",
1412
+ "put",
1413
+ "request",
1414
+ ]);
1415
+ const MCP_PROVIDER_IMPORT_PATTERNS = [
1416
+ ["anthropic", /^(?:@anthropic-ai\/sdk|anthropic)$/i],
1417
+ ["openai", /^(?:openai|@openai\/agents)$/i],
1418
+ [
1419
+ "google",
1420
+ /^(?:@google\/genai|@google\/generative-ai|google-generativeai)$/i,
1421
+ ],
1422
+ ["mistral", /^@mistralai\/mistralai$/i],
1423
+ ["deepseek", /^deepseek$/i],
1424
+ ["ollama", /^ollama$/i],
1425
+ ["groq", /^groq-sdk$/i],
1426
+ ];
1427
+ const MCP_PROVIDER_HOST_PATTERNS = [
1428
+ ["anthropic", /(?:^|\.)anthropic\.com$/i],
1429
+ ["openai", /(?:^|\.)openai\.com$/i],
1430
+ ["google", /(?:^|\.)googleapis\.com$/i],
1431
+ ["google", /(?:^|\.)generativelanguage\.googleapis\.com$/i],
1432
+ ["mistral", /(?:^|\.)mistral\.ai$/i],
1433
+ ["deepseek", /(?:^|\.)deepseek\.com$/i],
1434
+ ["ollama", /(?:^|\.)ollama\.com$/i],
1435
+ ["groq", /(?:^|\.)groq\.com$/i],
1436
+ ];
1437
+
1438
+ const providerFamilyFromImportSource = (sourceValue) => {
1439
+ if (!sourceValue) {
1440
+ return undefined;
1441
+ }
1442
+ return MCP_PROVIDER_IMPORT_PATTERNS.find(([, pattern]) =>
1443
+ pattern.test(sourceValue),
1444
+ )?.[0];
1445
+ };
1446
+
1447
+ const providerFamilyFromHost = (hostname) => {
1448
+ if (!hostname) {
1449
+ return undefined;
1450
+ }
1451
+ return MCP_PROVIDER_HOST_PATTERNS.find(([, pattern]) =>
1452
+ pattern.test(hostname),
1453
+ )?.[0];
1454
+ };
1455
+
1456
+ const modelFamilyFromModelName = (modelName) => {
1457
+ const normalized = String(modelName || "").toLowerCase();
1458
+ if (!normalized) {
1459
+ return undefined;
1460
+ }
1461
+ if (normalized.includes("claude")) {
1462
+ return "claude";
1463
+ }
1464
+ if (normalized.includes("gpt") || /^o[13](?:$|[-:])/u.test(normalized)) {
1465
+ return "gpt";
1466
+ }
1467
+ if (normalized.includes("gemini")) {
1468
+ return "gemini";
1469
+ }
1470
+ if (normalized.includes("llama")) {
1471
+ return "llama";
1472
+ }
1473
+ if (normalized.includes("mistral")) {
1474
+ return "mistral";
1475
+ }
1476
+ if (normalized.includes("command")) {
1477
+ return "command";
1478
+ }
1479
+ if (normalized.includes("deepseek")) {
1480
+ return "deepseek";
1481
+ }
1482
+ if (normalized.includes("qwen")) {
1483
+ return "qwen";
1484
+ }
1485
+ return normalized.split(/[:/,-]/u)[0] || undefined;
1486
+ };
1487
+
1488
+ const providerFamilyFromModelName = (modelName) => {
1489
+ const normalized = String(modelName || "").toLowerCase();
1490
+ if (normalized.includes("claude")) {
1491
+ return "anthropic";
1492
+ }
1493
+ if (normalized.includes("gpt") || /^o[13](?:$|[-:])/u.test(normalized)) {
1494
+ return "openai";
1495
+ }
1496
+ if (normalized.includes("gemini")) {
1497
+ return "google";
1498
+ }
1499
+ if (normalized.includes("mistral")) {
1500
+ return "mistral";
1501
+ }
1502
+ if (normalized.includes("deepseek")) {
1503
+ return "deepseek";
1504
+ }
1505
+ if (normalized.includes("llama")) {
1506
+ return "meta";
1507
+ }
1508
+ return undefined;
1509
+ };
1510
+
1511
+ const addUniqueProperty = (properties, name, value) => {
1512
+ if (value === undefined || value === null || value === "") {
1513
+ return;
1514
+ }
1515
+ if (properties.some((prop) => prop.name === name && prop.value === value)) {
1516
+ return;
1517
+ }
1518
+ properties.push({ name, value });
1519
+ };
1520
+
1521
+ const rootMemberName = (value) => String(value || "").split(".")[0];
1522
+
1523
+ const classifyUrlValue = (urlValue) => {
1524
+ if (!urlValue || typeof urlValue !== "string") {
1525
+ return undefined;
1526
+ }
1527
+ if (urlValue.startsWith("/")) {
1528
+ return {
1529
+ exposureType: "local-only",
1530
+ hostname: undefined,
1531
+ isMcpEndpoint: urlValue.toLowerCase().includes("/mcp"),
1532
+ isPublic: false,
1533
+ normalized: urlValue,
1534
+ providerFamily: undefined,
1535
+ };
1536
+ }
1537
+ try {
1538
+ const parsed = new URL(urlValue);
1539
+ const hostname = parsed.hostname.toLowerCase();
1540
+ const isPublic = !isLocalHost(hostname);
1541
+ return {
1542
+ exposureType: isPublic ? "networked-public" : "local-only",
1543
+ hostname,
1544
+ isMcpEndpoint:
1545
+ parsed.pathname.toLowerCase().includes("/mcp") ||
1546
+ hostname.includes("modelcontextprotocol"),
1547
+ isPublic,
1548
+ normalized: parsed.toString(),
1549
+ providerFamily: providerFamilyFromHost(hostname),
1550
+ };
1551
+ } catch {
1552
+ return undefined;
1553
+ }
1554
+ };
1555
+
1556
+ const getUrlStringValue = (node, urlLiteralByAlias) => {
1557
+ const literalValue = getLiteralStringValue(node);
1558
+ if (literalValue) {
1559
+ return literalValue;
1560
+ }
1561
+ if (node?.type === "Identifier") {
1562
+ return urlLiteralByAlias.get(node.name);
1563
+ }
1564
+ if (
1565
+ node?.type === "NewExpression" &&
1566
+ node.callee?.type === "Identifier" &&
1567
+ node.callee.name === "URL"
1568
+ ) {
1569
+ return getLiteralStringValue(node.arguments?.[0]);
1570
+ }
1571
+ return undefined;
1572
+ };
1573
+
1574
+ const recordMcpProvider = (serviceInfo, providerName) => {
1575
+ if (!providerName) {
1576
+ return;
1577
+ }
1578
+ serviceInfo.providerNames.add(providerName);
1579
+ serviceInfo.providerFamilies.add(providerName);
1580
+ };
1581
+
1582
+ const recordMcpModel = (serviceInfo, modelName) => {
1583
+ if (!modelName) {
1584
+ return;
1585
+ }
1586
+ serviceInfo.modelNames.add(modelName);
1587
+ const modelFamily = modelFamilyFromModelName(modelName);
1588
+ if (modelFamily) {
1589
+ serviceInfo.modelFamilies.add(modelFamily);
1590
+ }
1591
+ const providerFamily = providerFamilyFromModelName(modelName);
1592
+ if (providerFamily) {
1593
+ serviceInfo.providerFamilies.add(providerFamily);
1594
+ }
1595
+ };
1596
+
1597
+ const recordUrlUsage = (
1598
+ serviceInfo,
1599
+ urlValue,
1600
+ usageSignal,
1601
+ trackEndpoint = false,
1602
+ ) => {
1603
+ const classified = classifyUrlValue(urlValue);
1604
+ if (!classified) {
1605
+ return;
1606
+ }
1607
+ if (trackEndpoint || classified.isMcpEndpoint) {
1608
+ serviceInfo.endpoints.add(classified.normalized);
1609
+ }
1610
+ if (classified.hostname) {
1611
+ serviceInfo.outboundHosts.add(classified.hostname);
1612
+ if (classified.providerFamily) {
1613
+ serviceInfo.providerFamilies.add(classified.providerFamily);
1614
+ }
1615
+ }
1616
+ if (classified.isMcpEndpoint) {
1617
+ serviceInfo.serviceKinds.add("client");
1618
+ }
1619
+ if (classified.isPublic) {
1620
+ serviceInfo.publicNetwork = true;
1621
+ }
1622
+ if (classified.exposureType === "local-only") {
1623
+ serviceInfo.localOnlySignals += 1;
1624
+ }
1625
+ if (usageSignal) {
1626
+ serviceInfo.usageSignals.add(usageSignal);
1627
+ }
1628
+ };
1629
+
1630
+ const mcpFileBaseName = (fileRelativeLoc) =>
1631
+ basename(fileRelativeLoc).replace(
1632
+ /\.(js|jsx|cjs|mjs|ts|tsx|vue|svelte)$/i,
1633
+ "",
1634
+ );
1635
+
1636
+ const objectExpressionProperty = (astNode, propertyName) => {
1637
+ if (!astNode || astNode.type !== "ObjectExpression") {
1638
+ return undefined;
1639
+ }
1640
+ return (astNode.properties || []).find((prop) => {
1641
+ if (prop.type !== "ObjectProperty") {
1642
+ return false;
1643
+ }
1644
+ return getMemberExpressionPropertyName(prop.key) === propertyName;
1645
+ });
1646
+ };
1647
+
1648
+ const objectExpressionStringValue = (astNode, propertyName) =>
1649
+ getLiteralStringValue(objectExpressionProperty(astNode, propertyName)?.value);
1650
+
1651
+ const ensureMcpService = (servicesByKey, file, fileRelativeLoc) => {
1652
+ if (!servicesByKey.has(fileRelativeLoc)) {
1653
+ servicesByKey.set(fileRelativeLoc, {
1654
+ file,
1655
+ fileRelativeLoc,
1656
+ name: `${mcpFileBaseName(fileRelativeLoc)}-mcp-server`,
1657
+ version: "latest",
1658
+ description: undefined,
1659
+ endpoints: new Set(),
1660
+ transports: new Set(),
1661
+ capabilities: new Set(),
1662
+ capabilityFlags: new Map(),
1663
+ sdkImports: new Set(),
1664
+ officialSdk: undefined,
1665
+ authenticated: undefined,
1666
+ xTrustBoundary: undefined,
1667
+ modelNames: new Set(),
1668
+ modelFamilies: new Set(),
1669
+ providerNames: new Set(),
1670
+ providerFamilies: new Set(),
1671
+ outboundHosts: new Set(),
1672
+ usageSignals: new Set(),
1673
+ serviceKinds: new Set(),
1674
+ authModes: new Set(),
1675
+ publicNetwork: false,
1676
+ localOnlySignals: 0,
1677
+ authMetadata: new Map(),
1678
+ primitives: [],
1679
+ sourceLine: undefined,
1680
+ });
1681
+ }
1682
+ return servicesByKey.get(fileRelativeLoc);
1683
+ };
1684
+
1685
+ const registerCapabilityObject = (serviceInfo, capabilitiesNode) => {
1686
+ if (!capabilitiesNode || capabilitiesNode.type !== "ObjectExpression") {
1687
+ return;
1688
+ }
1689
+ for (const prop of capabilitiesNode.properties || []) {
1690
+ if (prop.type !== "ObjectProperty") {
1691
+ continue;
1692
+ }
1693
+ const capabilityName = getMemberExpressionPropertyName(prop.key);
1694
+ if (!capabilityName) {
1695
+ continue;
1696
+ }
1697
+ serviceInfo.capabilities.add(capabilityName);
1698
+ if (prop.value?.type !== "ObjectExpression") {
1699
+ continue;
1700
+ }
1701
+ for (const nestedProp of prop.value.properties || []) {
1702
+ if (nestedProp.type !== "ObjectProperty") {
1703
+ continue;
1704
+ }
1705
+ const nestedName = getMemberExpressionPropertyName(nestedProp.key);
1706
+ if (!nestedName || !MCP_CAPABILITY_FLAGS.has(nestedName)) {
1707
+ continue;
1708
+ }
1709
+ if (nestedProp.value?.type === "BooleanLiteral") {
1710
+ serviceInfo.capabilityFlags.set(
1711
+ `${capabilityName}.${nestedName}`,
1712
+ String(nestedProp.value.value),
1713
+ );
1714
+ }
1715
+ }
1716
+ }
1717
+ };
1718
+
1719
+ const recordMcpSdkImport = (serviceInfo, sourceValue) => {
1720
+ const classification = classifyMcpReference(sourceValue);
1721
+ if (!classification.isMcp) {
1722
+ return;
1723
+ }
1724
+ serviceInfo.sdkImports.add(sourceValue);
1725
+ serviceInfo.usageSignals.add("mcp-sdk-import");
1726
+ if (classification.isOfficial) {
1727
+ serviceInfo.officialSdk = true;
1728
+ } else if (typeof serviceInfo.officialSdk === "undefined") {
1729
+ serviceInfo.officialSdk = false;
1730
+ }
1731
+ };
1732
+
1733
+ const recordMcpModelProperty = (serviceInfo, propertyNode) => {
1734
+ const propertyName = getMemberExpressionPropertyName(propertyNode?.key);
1735
+ if (!propertyName) {
1736
+ return;
1737
+ }
1738
+ if (["model", "modelName"].includes(propertyName)) {
1739
+ const modelValue = getLiteralStringValue(propertyNode.value);
1740
+ recordMcpModel(serviceInfo, modelValue);
1741
+ }
1742
+ if (["provider", "providerName"].includes(propertyName)) {
1743
+ const providerValue = getLiteralStringValue(propertyNode.value);
1744
+ recordMcpProvider(serviceInfo, providerValue);
1745
+ }
1746
+ if (
1747
+ ["endpoint", "baseUrl", "baseURL", "resourceServerUrl", "url"].includes(
1748
+ propertyName,
1749
+ )
1750
+ ) {
1751
+ const urlValue = getLiteralStringValue(propertyNode.value);
1752
+ if (urlValue) {
1753
+ recordUrlUsage(serviceInfo, urlValue, "configured-url");
1754
+ }
1755
+ }
1756
+ };
1757
+
1758
+ const extractAuthMetadata = (astNode) => {
1759
+ if (!astNode || astNode.type !== "ObjectExpression") {
1760
+ return {};
1761
+ }
1762
+ return {
1763
+ authorizationEndpoint: objectExpressionStringValue(
1764
+ astNode,
1765
+ "authorization_endpoint",
1766
+ ),
1767
+ issuer: objectExpressionStringValue(astNode, "issuer"),
1768
+ tokenEndpoint: objectExpressionStringValue(astNode, "token_endpoint"),
1769
+ };
1770
+ };
1771
+
1772
+ const extractServerDefinition = (astNode) => {
1773
+ const name = objectExpressionStringValue(astNode, "name");
1774
+ const version = objectExpressionStringValue(astNode, "version");
1775
+ const description =
1776
+ objectExpressionStringValue(astNode, "instructions") ||
1777
+ objectExpressionStringValue(astNode, "description");
1778
+ return { description, name, version };
1779
+ };
1780
+
1781
+ const recordAuthMetadata = (serviceInfo, metadata) => {
1782
+ if (!metadata || typeof metadata !== "object") {
1783
+ return;
1784
+ }
1785
+ if (metadata.authorizationEndpoint) {
1786
+ serviceInfo.authMetadata.set(
1787
+ "authorization_endpoint",
1788
+ metadata.authorizationEndpoint,
1789
+ );
1790
+ }
1791
+ if (metadata.issuer) {
1792
+ serviceInfo.authMetadata.set("issuer", metadata.issuer);
1793
+ }
1794
+ if (metadata.tokenEndpoint) {
1795
+ serviceInfo.authMetadata.set("token_endpoint", metadata.tokenEndpoint);
1796
+ }
1797
+ };
1798
+
1799
+ const primitiveComponentForMcp = (serviceInfo, primitive) => {
1800
+ const primitiveName = primitive.name || primitive.uri || primitive.role;
1801
+ const serviceToken = sanitizeMcpRefToken(serviceInfo.name);
1802
+ const primitiveToken = sanitizeMcpRefToken(primitiveName);
1803
+ const primitiveRef = `urn:mcp:${primitive.role}:${serviceToken}:${primitiveToken}`;
1804
+ const properties = [
1805
+ { name: "SrcFile", value: serviceInfo.file },
1806
+ { name: "cdx:mcp:role", value: primitive.role },
1807
+ { name: "cdx:mcp:serviceRef", value: serviceInfo["bom-ref"] },
1808
+ ];
1809
+ if (primitive.description) {
1810
+ addUniqueProperty(properties, "cdx:mcp:description", primitive.description);
1811
+ }
1812
+ if (primitive.uri) {
1813
+ addUniqueProperty(properties, "cdx:mcp:resourceUri", primitive.uri);
1814
+ }
1815
+ if (primitive.sourceLine) {
1816
+ addUniqueProperty(
1817
+ properties,
1818
+ "cdx:mcp:sourceLine",
1819
+ String(primitive.sourceLine),
1820
+ );
1821
+ }
1822
+ if (primitive.annotations) {
1823
+ addUniqueProperty(
1824
+ properties,
1825
+ "cdx:mcp:toolAnnotations",
1826
+ JSON.stringify(primitive.annotations),
1827
+ );
1828
+ }
1829
+ return {
1830
+ "bom-ref": primitiveRef,
1831
+ description:
1832
+ primitive.description ||
1833
+ `${primitive.role} exposed by ${serviceInfo.name || "mcp-server"}`,
1834
+ name: primitiveName,
1835
+ properties,
1836
+ scope: "required",
1837
+ tags: ["mcp", `mcp-${primitive.role}`],
1838
+ type: "application",
1839
+ version: serviceInfo.version || "latest",
1840
+ };
1841
+ };
1842
+
1843
+ const inferMcpServiceType = (serviceInfo) => {
1844
+ const hasServerSignals =
1845
+ serviceInfo.serviceKinds.has("server") ||
1846
+ serviceInfo.primitives.length > 0 ||
1847
+ serviceInfo.capabilities.size > 0;
1848
+ const hasClientSignals =
1849
+ serviceInfo.serviceKinds.has("client") ||
1850
+ serviceInfo.outboundHosts.size > 0 ||
1851
+ serviceInfo.usageSignals.has("client-constructor");
1852
+ if (hasServerSignals && hasClientSignals) {
1853
+ return "gateway";
1854
+ }
1855
+ if (hasServerSignals) {
1856
+ return "server";
1857
+ }
1858
+ if (hasClientSignals) {
1859
+ return "client";
1860
+ }
1861
+ return "endpoint";
1862
+ };
1863
+
1864
+ const inferMcpUsageConfidence = (serviceInfo) => {
1865
+ if (
1866
+ serviceInfo.usageSignals.has("server-constructor") ||
1867
+ serviceInfo.usageSignals.has("client-constructor") ||
1868
+ serviceInfo.usageSignals.has("registered-tool") ||
1869
+ serviceInfo.usageSignals.has("registered-resource")
1870
+ ) {
1871
+ return "high";
1872
+ }
1873
+ if (
1874
+ serviceInfo.outboundHosts.size ||
1875
+ serviceInfo.providerFamilies.size ||
1876
+ serviceInfo.modelNames.size
1877
+ ) {
1878
+ return "medium";
1879
+ }
1880
+ return "low";
1881
+ };
1882
+
1883
+ const inferMcpExposureType = (serviceInfo) => {
1884
+ if (serviceInfo.publicNetwork) {
1885
+ return "networked-public";
1886
+ }
1887
+ if (serviceInfo.endpoints.size) {
1888
+ return "local-only";
1889
+ }
1890
+ if (serviceInfo.outboundHosts.size) {
1891
+ return "outbound-only";
1892
+ }
1893
+ return "code-only";
1894
+ };
1895
+
1896
+ const inferMcpAuthMode = (serviceInfo) => {
1897
+ const authModes = new Set(serviceInfo.authModes);
1898
+ if (serviceInfo.authMetadata.size) {
1899
+ authModes.add("oauth-metadata");
1900
+ }
1901
+ if (serviceInfo.authenticated === true && !authModes.size) {
1902
+ authModes.add("authenticated");
1903
+ }
1904
+ if (
1905
+ serviceInfo.authenticated === false &&
1906
+ serviceInfo.transports.has("streamable-http")
1907
+ ) {
1908
+ authModes.add("none");
1909
+ }
1910
+ return Array.from(authModes).sort().join(",");
1911
+ };
1912
+
1913
+ const inferMcpReviewNeeded = (serviceInfo, serviceType, exposureType) =>
1914
+ serviceInfo.officialSdk === false ||
1915
+ exposureType === "networked-public" ||
1916
+ serviceType === "gateway" ||
1917
+ (serviceType === "client" && serviceInfo.outboundHosts.size > 0);
1918
+
1919
+ const serviceObjectForMcp = (serviceInfo) => {
1920
+ const serviceName = serviceInfo.name || "mcp-server";
1921
+ const serviceVersion = serviceInfo.version || "latest";
1922
+ const serviceRef = `urn:service:mcp:${sanitizeMcpRefToken(serviceName)}:${sanitizeMcpRefToken(serviceVersion)}`;
1923
+ const properties = [{ name: "SrcFile", value: serviceInfo.file }];
1924
+ const serviceType = inferMcpServiceType(serviceInfo);
1925
+ const usageConfidence = inferMcpUsageConfidence(serviceInfo);
1926
+ const exposureType = inferMcpExposureType(serviceInfo);
1927
+ const authMode = inferMcpAuthMode(serviceInfo);
1928
+ const reviewNeeded = inferMcpReviewNeeded(
1929
+ serviceInfo,
1930
+ serviceType,
1931
+ exposureType,
1932
+ );
1933
+ addUniqueProperty(properties, "cdx:mcp:serviceType", serviceType);
1934
+ addUniqueProperty(
1935
+ properties,
1936
+ "cdx:mcp:inventorySource",
1937
+ "source-code-analysis",
1938
+ );
1939
+ addUniqueProperty(properties, "cdx:mcp:usageConfidence", usageConfidence);
1940
+ addUniqueProperty(properties, "cdx:mcp:exposureType", exposureType);
1941
+ if (serviceInfo.transports.size) {
1942
+ addUniqueProperty(
1943
+ properties,
1944
+ "cdx:mcp:transport",
1945
+ Array.from(serviceInfo.transports).sort().join(","),
1946
+ );
1947
+ }
1948
+ addUniqueProperty(
1949
+ properties,
1950
+ "cdx:mcp:officialSdk",
1951
+ serviceInfo.officialSdk === true ? "true" : "false",
1952
+ );
1953
+ for (const capability of Array.from(serviceInfo.capabilities).sort()) {
1954
+ addUniqueProperty(properties, `cdx:mcp:capabilities:${capability}`, "true");
1955
+ }
1956
+ for (const [flagName, flagValue] of Array.from(
1957
+ serviceInfo.capabilityFlags.entries(),
1958
+ ).sort((a, b) => a[0].localeCompare(b[0]))) {
1959
+ addUniqueProperty(
1960
+ properties,
1961
+ `cdx:mcp:capabilities:${flagName}`,
1962
+ flagValue,
1963
+ );
1964
+ }
1965
+ if (serviceInfo.sdkImports.size) {
1966
+ addUniqueProperty(
1967
+ properties,
1968
+ "cdx:mcp:sdkImports",
1969
+ Array.from(serviceInfo.sdkImports).sort().join(","),
1970
+ );
1971
+ }
1972
+ if (serviceInfo.modelNames.size) {
1973
+ addUniqueProperty(
1974
+ properties,
1975
+ "cdx:mcp:modelNames",
1976
+ Array.from(serviceInfo.modelNames).sort().join(","),
1977
+ );
1978
+ }
1979
+ if (serviceInfo.modelFamilies.size) {
1980
+ addUniqueProperty(
1981
+ properties,
1982
+ "cdx:mcp:modelFamilies",
1983
+ Array.from(serviceInfo.modelFamilies).sort().join(","),
1984
+ );
1985
+ }
1986
+ if (serviceInfo.providerNames.size) {
1987
+ addUniqueProperty(
1988
+ properties,
1989
+ "cdx:mcp:providerNames",
1990
+ Array.from(serviceInfo.providerNames).sort().join(","),
1991
+ );
1992
+ }
1993
+ if (serviceInfo.providerFamilies.size) {
1994
+ addUniqueProperty(
1995
+ properties,
1996
+ "cdx:mcp:providerFamilies",
1997
+ Array.from(serviceInfo.providerFamilies).sort().join(","),
1998
+ );
1999
+ }
2000
+ if (serviceInfo.outboundHosts.size) {
2001
+ addUniqueProperty(
2002
+ properties,
2003
+ "cdx:mcp:outboundHosts",
2004
+ Array.from(serviceInfo.outboundHosts).sort().join(","),
2005
+ );
2006
+ }
2007
+ if (serviceInfo.usageSignals.size) {
2008
+ addUniqueProperty(
2009
+ properties,
2010
+ "cdx:mcp:usageSignals",
2011
+ Array.from(serviceInfo.usageSignals).sort().join(","),
2012
+ );
2013
+ }
2014
+ if (authMode) {
2015
+ addUniqueProperty(properties, "cdx:mcp:authMode", authMode);
2016
+ }
2017
+ if (reviewNeeded) {
2018
+ addUniqueProperty(properties, "cdx:mcp:reviewNeeded", "true");
2019
+ }
2020
+ for (const [metadataKey, metadataValue] of Array.from(
2021
+ serviceInfo.authMetadata.entries(),
2022
+ ).sort((a, b) => a[0].localeCompare(b[0]))) {
2023
+ addUniqueProperty(properties, `cdx:mcp:auth:${metadataKey}`, metadataValue);
2024
+ }
2025
+ addUniqueProperty(
2026
+ properties,
2027
+ "cdx:mcp:toolCount",
2028
+ String(
2029
+ serviceInfo.primitives.filter((item) => item.role === "tool").length,
2030
+ ),
2031
+ );
2032
+ addUniqueProperty(
2033
+ properties,
2034
+ "cdx:mcp:promptCount",
2035
+ String(
2036
+ serviceInfo.primitives.filter((item) => item.role === "prompt").length,
2037
+ ),
2038
+ );
2039
+ addUniqueProperty(
2040
+ properties,
2041
+ "cdx:mcp:resourceCount",
2042
+ String(
2043
+ serviceInfo.primitives.filter((item) =>
2044
+ ["resource", "resource-template"].includes(item.role),
2045
+ ).length,
2046
+ ),
2047
+ );
2048
+ if (serviceInfo.sourceLine) {
2049
+ addUniqueProperty(
2050
+ properties,
2051
+ "cdx:mcp:sourceLine",
2052
+ String(serviceInfo.sourceLine),
2053
+ );
2054
+ }
2055
+ serviceInfo["bom-ref"] = serviceRef;
2056
+ return {
2057
+ "bom-ref": serviceRef,
2058
+ authenticated: serviceInfo.authenticated,
2059
+ description: serviceInfo.description,
2060
+ endpoints: Array.from(serviceInfo.endpoints).sort(),
2061
+ group: "mcp",
2062
+ name: serviceName,
2063
+ properties,
2064
+ version: serviceVersion,
2065
+ "x-trust-boundary": serviceInfo.xTrustBoundary,
2066
+ };
2067
+ };
2068
+
2069
+ const buildMcpInventoryFromServices = (servicesByKey) => {
2070
+ const services = [];
2071
+ const components = [];
2072
+ const dependencies = [];
2073
+ for (const serviceInfo of servicesByKey.values()) {
2074
+ if (
2075
+ !serviceInfo.primitives.length &&
2076
+ !serviceInfo.transports.size &&
2077
+ !serviceInfo.endpoints.size &&
2078
+ !serviceInfo.capabilities.size &&
2079
+ !serviceInfo.sourceLine &&
2080
+ !serviceInfo.outboundHosts.size &&
2081
+ !serviceInfo.usageSignals.size
2082
+ ) {
2083
+ continue;
2084
+ }
2085
+ if (
2086
+ serviceInfo.endpoints.size &&
2087
+ !serviceInfo.transports.has("stdio") &&
2088
+ !serviceInfo.transports.size
2089
+ ) {
2090
+ serviceInfo.transports.add("streamable-http");
2091
+ }
2092
+ if (
2093
+ serviceInfo.transports.has("streamable-http") &&
2094
+ typeof serviceInfo.authenticated === "undefined"
2095
+ ) {
2096
+ serviceInfo.authenticated = false;
2097
+ }
2098
+ const service = serviceObjectForMcp(serviceInfo);
2099
+ services.push(service);
2100
+ const providedRefs = [];
2101
+ for (const primitive of serviceInfo.primitives) {
2102
+ const component = primitiveComponentForMcp(serviceInfo, primitive);
2103
+ components.push(component);
2104
+ providedRefs.push(component["bom-ref"]);
2105
+ }
2106
+ if (providedRefs.length) {
2107
+ dependencies.push({
2108
+ ref: service["bom-ref"],
2109
+ dependsOn: [],
2110
+ provides: providedRefs.sort(),
2111
+ });
2112
+ }
2113
+ }
2114
+ return { components, dependencies, services };
2115
+ };
2116
+
2117
+ // Capture groups:
2118
+ // 1 = module path in `from x import y`
2119
+ // 2 = imported symbols in `from x import y`
2120
+ // 3 = imported modules in `import x, y`
2121
+ const PYTHON_IMPORT_PATTERN =
2122
+ /^\s*(?:from\s+([a-zA-Z0-9_.]+)\s+import\s+([^\n#]+)|import\s+([^\n#]+))/gmu;
2123
+ const PYTHON_DECORATOR_PATTERN = /@([a-zA-Z_][a-zA-Z0-9_]*)\.(\w+)\s*\(/gmu;
2124
+ const PYTHON_STDIO_PATTERN = /\bstdio_server\s*\(/u;
2125
+ const PYTHON_HTTP_TRANSPORT_PATTERN = /\b(streamable|sse|http)\b/iu;
2126
+
2127
+ const PYTHON_DECORATOR_ROLE_MAP = new Map([
2128
+ ["call_tool", "tool"],
2129
+ ["get_prompt", "prompt"],
2130
+ ["list_prompts", "prompt"],
2131
+ ["list_resources", "resource"],
2132
+ ["list_tools", "tool"],
2133
+ ["prompt", "prompt"],
2134
+ ["read_resource", "resource"],
2135
+ ["resource", "resource"],
2136
+ ["resource_template", "resource-template"],
2137
+ ["tool", "tool"],
2138
+ ]);
2139
+
2140
+ const lineNumberForIndex = (text, index) =>
2141
+ text.slice(0, index).split("\n").length || 1;
2142
+
2143
+ const extractPythonNamedString = (argumentText, key) => {
2144
+ const directPattern = new RegExp(`${key}\\s*=\\s*["']([^"'\\n]+)["']`, "u");
2145
+ const directMatch = argumentText.match(directPattern);
2146
+ if (directMatch?.[1]) {
2147
+ return directMatch[1];
2148
+ }
2149
+ const wrappedPattern = new RegExp(
2150
+ `${key}\\s*=\\s*[a-zA-Z_][a-zA-Z0-9_.]*\\(\\s*["']([^"'\\n]+)["']`,
2151
+ "u",
2152
+ );
2153
+ return argumentText.match(wrappedPattern)?.[1];
2154
+ };
2155
+
2156
+ const extractFirstPythonString = (argumentText) =>
2157
+ argumentText.match(/^\s*["']([^"'\n]+)["']/u)?.[1];
2158
+
2159
+ const extractPythonCallArguments = (raw, alias) => {
2160
+ const aliasPattern = new RegExp(
2161
+ `(\\w+)\\s*=\\s*${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`,
2162
+ "gmu",
2163
+ );
2164
+ const calls = [];
2165
+ for (const match of raw.matchAll(aliasPattern)) {
2166
+ let callStart = -1;
2167
+ for (let index = match.index; index < raw.length; index++) {
2168
+ if (raw[index] === "(") {
2169
+ callStart = index;
2170
+ break;
2171
+ }
2172
+ }
2173
+ if (callStart === -1) {
2174
+ continue;
2175
+ }
2176
+ let depth = 0;
2177
+ let callEnd = -1;
2178
+ for (let index = callStart; index < raw.length; index++) {
2179
+ if (raw[index] === "(") {
2180
+ depth += 1;
2181
+ } else if (raw[index] === ")") {
2182
+ depth -= 1;
2183
+ if (depth === 0) {
2184
+ callEnd = index;
2185
+ break;
2186
+ }
2187
+ }
2188
+ }
2189
+ if (callEnd === -1) {
2190
+ continue;
2191
+ }
2192
+ calls.push({
2193
+ argumentText: raw.slice(callStart + 1, callEnd),
2194
+ index: match.index,
2195
+ serviceVarName: match[1],
2196
+ });
2197
+ }
2198
+ return calls;
2199
+ };
2200
+
2201
+ const extractPythonFunctionCalls = (raw, callName) => {
2202
+ const callPattern = new RegExp(
2203
+ `${callName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`,
2204
+ "gmu",
2205
+ );
2206
+ const calls = [];
2207
+ for (const match of raw.matchAll(callPattern)) {
2208
+ const callStart = match.index + match[0].lastIndexOf("(");
2209
+ let depth = 0;
2210
+ let callEnd = -1;
2211
+ for (let index = callStart; index < raw.length; index++) {
2212
+ if (raw[index] === "(") {
2213
+ depth += 1;
2214
+ } else if (raw[index] === ")") {
2215
+ depth -= 1;
2216
+ if (depth === 0) {
2217
+ callEnd = index;
2218
+ break;
2219
+ }
2220
+ }
2221
+ }
2222
+ if (callEnd === -1) {
2223
+ continue;
2224
+ }
2225
+ calls.push({
2226
+ argumentText: raw.slice(callStart + 1, callEnd),
2227
+ index: match.index,
2228
+ });
2229
+ }
2230
+ return calls;
2231
+ };
2232
+
2233
+ const parsePythonImports = (raw) => {
2234
+ const imports = [];
2235
+ for (const match of raw.matchAll(PYTHON_IMPORT_PATTERN)) {
2236
+ const fromSource = match[1];
2237
+ const fromImports = match[2];
2238
+ const directImports = match[3];
2239
+ if (fromSource && fromImports) {
2240
+ for (const importEntry of fromImports.split(",")) {
2241
+ const [importedName, localName] = importEntry
2242
+ .trim()
2243
+ .split(/\s+as\s+/u)
2244
+ .map((value) => value?.trim());
2245
+ if (importedName) {
2246
+ imports.push({
2247
+ importedName,
2248
+ localName: localName || importedName,
2249
+ sourceValue: fromSource,
2250
+ });
2251
+ }
2252
+ }
2253
+ continue;
2254
+ }
2255
+ for (const importEntry of (directImports || "").split(",")) {
2256
+ const [sourceValue, localName] = importEntry
2257
+ .trim()
2258
+ .split(/\s+as\s+/u)
2259
+ .map((value) => value?.trim());
2260
+ if (sourceValue) {
2261
+ imports.push({
2262
+ importedName: sourceValue.split(".").pop(),
2263
+ localName: localName || sourceValue.split(".").pop(),
2264
+ sourceValue,
2265
+ });
2266
+ }
2267
+ }
2268
+ }
2269
+ return imports;
2270
+ };
2271
+
2272
+ const registerPythonPrimitive = (
2273
+ serviceInfo,
2274
+ role,
2275
+ name,
2276
+ description,
2277
+ uri,
2278
+ sourceLine,
2279
+ ) => {
2280
+ if (!role) {
2281
+ return;
2282
+ }
2283
+ const primitiveName =
2284
+ name ||
2285
+ uri ||
2286
+ `${role}-${serviceInfo.primitives.filter((item) => item.role === role).length + 1}`;
2287
+ serviceInfo.primitives.push({
2288
+ description,
2289
+ name: primitiveName,
2290
+ role,
2291
+ sourceLine,
2292
+ uri,
2293
+ });
2294
+ if (role === "tool") {
2295
+ serviceInfo.usageSignals.add("registered-tool");
2296
+ }
2297
+ if (["resource", "resource-template"].includes(role)) {
2298
+ serviceInfo.usageSignals.add("registered-resource");
2299
+ }
2300
+ };
2301
+
2302
+ /**
2303
+ * Detect MCP server inventory from Python source using import and decorator heuristics.
2304
+ *
2305
+ * @param {string} src Absolute or relative path to the project source directory
2306
+ * @param {boolean} deep When true, also scans nested paths more aggressively
2307
+ * @returns {{components: Object[], dependencies: Object[], services: Object[]}}
2308
+ */
2309
+ export const detectPythonMcpInventory = (src, deep = false) => {
2310
+ const servicesByKey = new Map();
2311
+ let srcFiles = [];
2312
+ try {
2313
+ srcFiles = getAllFiles(deep, src, ".py");
2314
+ } catch {
2315
+ return { components: [], dependencies: [], services: [] };
2316
+ }
2317
+ for (const file of srcFiles) {
2318
+ let raw;
2319
+ try {
2320
+ raw = readFileSync(file, "utf-8");
2321
+ } catch {
2322
+ continue;
2323
+ }
2324
+ const fileRelativeLoc = relative(src, file);
2325
+ const serverConstructorAliases = new Set(["Server", "FastMCP"]);
2326
+ const importEntries = parsePythonImports(raw);
2327
+ let fileHasMcpImports = false;
2328
+ for (const importEntry of importEntries) {
2329
+ const sourceValue = importEntry.sourceValue;
2330
+ const classification = classifyMcpReference(sourceValue);
2331
+ if (!classification.isMcp && !sourceValue.startsWith("mcp")) {
2332
+ continue;
2333
+ }
2334
+ fileHasMcpImports = true;
2335
+ const serviceInfo = ensureMcpService(
2336
+ servicesByKey,
2337
+ file,
2338
+ fileRelativeLoc,
2339
+ );
2340
+ if (sourceValue.startsWith("mcp")) {
2341
+ serviceInfo.sdkImports.add(sourceValue);
2342
+ serviceInfo.usageSignals.add("mcp-sdk-import");
2343
+ serviceInfo.officialSdk = true;
2344
+ } else {
2345
+ recordMcpSdkImport(serviceInfo, sourceValue);
2346
+ }
2347
+ if (
2348
+ sourceValue.startsWith("mcp.server") ||
2349
+ sourceValue === "fastmcp" ||
2350
+ /server/i.test(importEntry.importedName || "") ||
2351
+ /server/i.test(importEntry.localName || "")
2352
+ ) {
2353
+ serverConstructorAliases.add(importEntry.localName);
2354
+ }
2355
+ }
2356
+ for (const alias of serverConstructorAliases) {
2357
+ for (const match of extractPythonCallArguments(raw, alias)) {
2358
+ const serviceVarName = match.serviceVarName;
2359
+ const argumentText = match.argumentText || "";
2360
+ const serviceInfo = ensureMcpService(
2361
+ servicesByKey,
2362
+ file,
2363
+ fileRelativeLoc,
2364
+ );
2365
+ serviceInfo.name =
2366
+ extractPythonNamedString(argumentText, "name") ||
2367
+ extractFirstPythonString(argumentText) ||
2368
+ serviceInfo.name;
2369
+ serviceInfo.version =
2370
+ extractPythonNamedString(argumentText, "version") ||
2371
+ serviceInfo.version;
2372
+ serviceInfo.description =
2373
+ extractPythonNamedString(argumentText, "instructions") ||
2374
+ extractPythonNamedString(argumentText, "description") ||
2375
+ serviceInfo.description;
2376
+ serviceInfo.serviceKinds.add("server");
2377
+ serviceInfo.usageSignals.add("server-constructor");
2378
+ serviceInfo.sourceLine = lineNumberForIndex(raw, match.index);
2379
+ for (const decoratorMatch of raw.matchAll(PYTHON_DECORATOR_PATTERN)) {
2380
+ if (decoratorMatch[1] !== serviceVarName) {
2381
+ continue;
2382
+ }
2383
+ const primitiveRole = PYTHON_DECORATOR_ROLE_MAP.get(
2384
+ decoratorMatch[2],
2385
+ );
2386
+ if (!primitiveRole) {
2387
+ continue;
2388
+ }
2389
+ if (primitiveRole === "tool") {
2390
+ serviceInfo.capabilities.add("tools");
2391
+ } else if (primitiveRole === "prompt") {
2392
+ serviceInfo.capabilities.add("prompts");
2393
+ } else {
2394
+ serviceInfo.capabilities.add("resources");
2395
+ }
2396
+ }
2397
+ }
2398
+ }
2399
+ if (PYTHON_STDIO_PATTERN.test(raw)) {
2400
+ const serviceInfo = ensureMcpService(
2401
+ servicesByKey,
2402
+ file,
2403
+ fileRelativeLoc,
2404
+ );
2405
+ serviceInfo.transports.add("stdio");
2406
+ } else if (fileHasMcpImports && PYTHON_HTTP_TRANSPORT_PATTERN.test(raw)) {
2407
+ const serviceInfo = ensureMcpService(
2408
+ servicesByKey,
2409
+ file,
2410
+ fileRelativeLoc,
2411
+ );
2412
+ serviceInfo.transports.add("streamable-http");
2413
+ }
2414
+ const primitivePatterns = [
2415
+ ["mtypes.Tool", "tool"],
2416
+ ["mtypes.Prompt", "prompt"],
2417
+ ["mtypes.Resource", "resource"],
2418
+ ];
2419
+ for (const [callName, role] of primitivePatterns) {
2420
+ for (const match of extractPythonFunctionCalls(raw, callName)) {
2421
+ const serviceInfo = ensureMcpService(
2422
+ servicesByKey,
2423
+ file,
2424
+ fileRelativeLoc,
2425
+ );
2426
+ registerPythonPrimitive(
2427
+ serviceInfo,
2428
+ role,
2429
+ extractPythonNamedString(match.argumentText || "", "name"),
2430
+ extractPythonNamedString(match.argumentText || "", "description"),
2431
+ extractPythonNamedString(match.argumentText || "", "uri"),
2432
+ lineNumberForIndex(raw, match.index),
2433
+ );
2434
+ }
2435
+ }
2436
+ if (fileHasMcpImports) {
2437
+ ensureMcpService(servicesByKey, file, fileRelativeLoc);
2438
+ }
2439
+ }
2440
+ return buildMcpInventoryFromServices(servicesByKey);
2441
+ };
2442
+
2443
+ /**
2444
+ * Detect MCP server inventory from JavaScript/TypeScript source using AST analysis.
2445
+ *
2446
+ * @param {string} src Absolute or relative path to the project source directory
2447
+ * @param {boolean} deep When true, also scans nested paths more aggressively
2448
+ * @returns {{components: Object[], dependencies: Object[], services: Object[]}}
2449
+ */
2450
+ export const detectMcpInventory = (src, deep = false) => {
2451
+ const servicesByKey = new Map();
2452
+ let srcFiles = [];
2453
+ try {
2454
+ srcFiles = [
2455
+ ...getAllFiles(deep, src, ".js"),
2456
+ ...getAllFiles(deep, src, ".jsx"),
2457
+ ...getAllFiles(deep, src, ".cjs"),
2458
+ ...getAllFiles(deep, src, ".mjs"),
2459
+ ...getAllFiles(deep, src, ".ts"),
2460
+ ...getAllFiles(deep, src, ".tsx"),
2461
+ ...getAllFiles(deep, src, ".vue"),
2462
+ ...getAllFiles(deep, src, ".svelte"),
2463
+ ];
2464
+ } catch {
2465
+ return { components: [], dependencies: [], services: [] };
2466
+ }
2467
+ for (const file of srcFiles) {
2468
+ const fileRelativeLoc = relative(src, file);
2469
+ const serverConstructorAliases = new Set(["McpServer", "MCPServer"]);
2470
+ const clientConstructorAliases = new Set(MCP_CLIENT_CONSTRUCTOR_NAMES);
2471
+ const authHelperAliases = new Set(MCP_AUTH_HELPERS);
2472
+ const httpTransportAliases = new Set(MCP_HTTP_TRANSPORT_NAMES);
2473
+ const stdioTransportAliases = new Set(MCP_STDIO_TRANSPORT_NAMES);
2474
+ const httpClientTransportAliases = new Set(MCP_HTTP_CLIENT_TRANSPORT_NAMES);
2475
+ const stdioClientTransportAliases = new Set(
2476
+ MCP_STDIO_CLIENT_TRANSPORT_NAMES,
2477
+ );
2478
+ const appFactoryAliases = new Set(MCP_APP_FACTORIES);
2479
+ const appAliases = new Set();
2480
+ const clientAliases = new Set();
2481
+ const providerAliases = new Map();
2482
+ const transportAliasKinds = new Map();
2483
+ const urlLiteralByAlias = new Map();
2484
+ const authMetadataByAlias = new Map();
2485
+ let fileHasMcpImports = false;
2486
+ try {
2487
+ const ast = parse(fileToParseableCode(file), babelParserOptions);
2488
+ traverse.default(ast, {
2489
+ ImportDeclaration: (path) => {
2490
+ const sourceValue = getLiteralStringValue(path?.node?.source);
2491
+ const classification = classifyMcpReference(sourceValue);
2492
+ const providerFamily = providerFamilyFromImportSource(sourceValue);
2493
+ if (classification.isMcp) {
2494
+ fileHasMcpImports = true;
2495
+ const serviceInfo = ensureMcpService(
2496
+ servicesByKey,
2497
+ file,
2498
+ fileRelativeLoc,
2499
+ );
2500
+ recordMcpSdkImport(serviceInfo, sourceValue);
2501
+ }
2502
+ for (const specifier of path.node.specifiers || []) {
2503
+ const localName = specifier.local?.name;
2504
+ if (!localName) {
2505
+ continue;
2506
+ }
2507
+ const importedName =
2508
+ specifier.type === "ImportSpecifier"
2509
+ ? specifier.imported?.name
2510
+ : localName;
2511
+ if (
2512
+ classification.isMcp &&
2513
+ (sourceValue?.includes("/server") ||
2514
+ /server/i.test(localName) ||
2515
+ importedName === "McpServer" ||
2516
+ importedName === "Server")
2517
+ ) {
2518
+ serverConstructorAliases.add(localName);
2519
+ }
2520
+ if (
2521
+ classification.isMcp &&
2522
+ (sourceValue?.includes("/client") ||
2523
+ /client/i.test(localName) ||
2524
+ importedName === "Client")
2525
+ ) {
2526
+ clientConstructorAliases.add(localName);
2527
+ }
2528
+ if (MCP_AUTH_HELPERS.has(importedName)) {
2529
+ authHelperAliases.add(localName);
2530
+ }
2531
+ if (MCP_HTTP_TRANSPORT_NAMES.has(importedName)) {
2532
+ httpTransportAliases.add(localName);
2533
+ }
2534
+ if (MCP_STDIO_TRANSPORT_NAMES.has(importedName)) {
2535
+ stdioTransportAliases.add(localName);
2536
+ }
2537
+ if (MCP_HTTP_CLIENT_TRANSPORT_NAMES.has(importedName)) {
2538
+ httpClientTransportAliases.add(localName);
2539
+ }
2540
+ if (MCP_STDIO_CLIENT_TRANSPORT_NAMES.has(importedName)) {
2541
+ stdioClientTransportAliases.add(localName);
2542
+ }
2543
+ if (MCP_APP_FACTORIES.has(importedName)) {
2544
+ appFactoryAliases.add(localName);
2545
+ }
2546
+ if (sourceValue === "express") {
2547
+ appFactoryAliases.add(localName);
2548
+ }
2549
+ if (providerFamily) {
2550
+ providerAliases.set(localName, providerFamily);
2551
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2552
+ if (serviceInfo) {
2553
+ recordMcpProvider(serviceInfo, providerFamily);
2554
+ serviceInfo.usageSignals.add("provider-sdk-import");
2555
+ }
2556
+ }
2557
+ }
2558
+ },
2559
+ VariableDeclarator: (path) => {
2560
+ const idNode = path?.node?.id;
2561
+ const initNode = unwrapAwait(path?.node?.init);
2562
+ if (!idNode || !initNode) {
2563
+ return;
2564
+ }
2565
+ if (
2566
+ initNode.type === "CallExpression" &&
2567
+ initNode.callee?.type === "Identifier" &&
2568
+ initNode.callee.name === "require"
2569
+ ) {
2570
+ const sourceValue = getLiteralStringValue(initNode.arguments?.[0]);
2571
+ const classification = classifyMcpReference(sourceValue);
2572
+ const providerFamily = providerFamilyFromImportSource(sourceValue);
2573
+ if (classification.isMcp) {
2574
+ fileHasMcpImports = true;
2575
+ const serviceInfo = ensureMcpService(
2576
+ servicesByKey,
2577
+ file,
2578
+ fileRelativeLoc,
2579
+ );
2580
+ recordMcpSdkImport(serviceInfo, sourceValue);
2581
+ if (idNode.type === "Identifier") {
2582
+ if (
2583
+ sourceValue?.includes("/server") ||
2584
+ /server/i.test(idNode.name)
2585
+ ) {
2586
+ serverConstructorAliases.add(idNode.name);
2587
+ }
2588
+ if (
2589
+ sourceValue?.includes("/client") ||
2590
+ /client/i.test(idNode.name)
2591
+ ) {
2592
+ clientConstructorAliases.add(idNode.name);
2593
+ }
2594
+ }
2595
+ for (const moduleName of getNamedImportsFromObjectPattern(
2596
+ idNode,
2597
+ )) {
2598
+ if (MCP_AUTH_HELPERS.has(moduleName)) {
2599
+ authHelperAliases.add(moduleName);
2600
+ }
2601
+ if (MCP_HTTP_TRANSPORT_NAMES.has(moduleName)) {
2602
+ httpTransportAliases.add(moduleName);
2603
+ }
2604
+ if (MCP_STDIO_TRANSPORT_NAMES.has(moduleName)) {
2605
+ stdioTransportAliases.add(moduleName);
2606
+ }
2607
+ if (MCP_HTTP_CLIENT_TRANSPORT_NAMES.has(moduleName)) {
2608
+ httpClientTransportAliases.add(moduleName);
2609
+ }
2610
+ if (MCP_STDIO_CLIENT_TRANSPORT_NAMES.has(moduleName)) {
2611
+ stdioClientTransportAliases.add(moduleName);
2612
+ }
2613
+ if (MCP_APP_FACTORIES.has(moduleName)) {
2614
+ appFactoryAliases.add(moduleName);
2615
+ }
2616
+ if (sourceValue === "express") {
2617
+ appFactoryAliases.add(moduleName);
2618
+ }
2619
+ if (
2620
+ sourceValue?.includes("/server") ||
2621
+ /server/i.test(moduleName) ||
2622
+ moduleName === "McpServer"
2623
+ ) {
2624
+ serverConstructorAliases.add(moduleName);
2625
+ }
2626
+ if (
2627
+ sourceValue?.includes("/client") ||
2628
+ /client/i.test(moduleName) ||
2629
+ moduleName === "Client"
2630
+ ) {
2631
+ clientConstructorAliases.add(moduleName);
2632
+ }
2633
+ }
2634
+ }
2635
+ if (providerFamily && idNode.type === "Identifier") {
2636
+ providerAliases.set(idNode.name, providerFamily);
2637
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2638
+ if (serviceInfo) {
2639
+ recordMcpProvider(serviceInfo, providerFamily);
2640
+ serviceInfo.usageSignals.add("provider-sdk-import");
2641
+ }
2642
+ }
2643
+ }
2644
+ if (
2645
+ idNode.type === "Identifier" &&
2646
+ initNode.type === "NewExpression" &&
2647
+ initNode.callee?.type === "Identifier" &&
2648
+ serverConstructorAliases.has(initNode.callee.name)
2649
+ ) {
2650
+ const serviceInfo = ensureMcpService(
2651
+ servicesByKey,
2652
+ file,
2653
+ fileRelativeLoc,
2654
+ );
2655
+ const serverDefinition = extractServerDefinition(
2656
+ initNode.arguments?.[0],
2657
+ );
2658
+ serviceInfo.name = serverDefinition.name || serviceInfo.name;
2659
+ serviceInfo.version =
2660
+ serverDefinition.version || serviceInfo.version;
2661
+ serviceInfo.description =
2662
+ serverDefinition.description || serviceInfo.description;
2663
+ serviceInfo.sourceLine =
2664
+ serviceInfo.sourceLine || path.node.loc?.start?.line;
2665
+ serviceInfo.serviceKinds.add("server");
2666
+ serviceInfo.usageSignals.add("server-constructor");
2667
+ const capabilityNode =
2668
+ objectExpressionProperty(initNode.arguments?.[1], "capabilities")
2669
+ ?.value ||
2670
+ (initNode.arguments?.[1]?.type === "ObjectExpression"
2671
+ ? initNode.arguments[1]
2672
+ : undefined);
2673
+ registerCapabilityObject(serviceInfo, capabilityNode);
2674
+ }
2675
+ if (
2676
+ idNode.type === "Identifier" &&
2677
+ initNode.type === "CallExpression" &&
2678
+ initNode.callee?.type === "Identifier" &&
2679
+ appFactoryAliases.has(initNode.callee.name)
2680
+ ) {
2681
+ appAliases.add(idNode.name);
2682
+ }
2683
+ if (
2684
+ idNode.type === "Identifier" &&
2685
+ initNode.type === "NewExpression" &&
2686
+ initNode.callee?.type === "Identifier" &&
2687
+ clientConstructorAliases.has(initNode.callee.name)
2688
+ ) {
2689
+ const serviceInfo = ensureMcpService(
2690
+ servicesByKey,
2691
+ file,
2692
+ fileRelativeLoc,
2693
+ );
2694
+ clientAliases.add(idNode.name);
2695
+ serviceInfo.serviceKinds.add("client");
2696
+ serviceInfo.sourceLine =
2697
+ serviceInfo.sourceLine || path.node.loc?.start?.line;
2698
+ serviceInfo.usageSignals.add("client-constructor");
2699
+ }
2700
+ if (
2701
+ idNode.type === "Identifier" &&
2702
+ initNode.type === "NewExpression" &&
2703
+ initNode.callee?.type === "Identifier" &&
2704
+ providerAliases.has(initNode.callee.name) &&
2705
+ servicesByKey.has(fileRelativeLoc)
2706
+ ) {
2707
+ const serviceInfo = ensureMcpService(
2708
+ servicesByKey,
2709
+ file,
2710
+ fileRelativeLoc,
2711
+ );
2712
+ recordMcpProvider(
2713
+ serviceInfo,
2714
+ providerAliases.get(initNode.callee.name),
2715
+ );
2716
+ serviceInfo.serviceKinds.add("client");
2717
+ serviceInfo.usageSignals.add("provider-sdk-client");
2718
+ }
2719
+ if (
2720
+ idNode.type === "Identifier" &&
2721
+ initNode.type === "NewExpression" &&
2722
+ initNode.callee?.type === "Identifier"
2723
+ ) {
2724
+ if (httpTransportAliases.has(initNode.callee.name)) {
2725
+ transportAliasKinds.set(idNode.name, "streamable-http");
2726
+ }
2727
+ if (stdioTransportAliases.has(initNode.callee.name)) {
2728
+ transportAliasKinds.set(idNode.name, "stdio");
2729
+ }
2730
+ if (httpClientTransportAliases.has(initNode.callee.name)) {
2731
+ transportAliasKinds.set(idNode.name, "streamable-http");
2732
+ const serviceInfo = ensureMcpService(
2733
+ servicesByKey,
2734
+ file,
2735
+ fileRelativeLoc,
2736
+ );
2737
+ serviceInfo.serviceKinds.add("client");
2738
+ serviceInfo.usageSignals.add("client-http-transport");
2739
+ recordUrlUsage(
2740
+ serviceInfo,
2741
+ getUrlStringValue(initNode.arguments?.[0], urlLiteralByAlias),
2742
+ "outbound-mcp-endpoint",
2743
+ );
2744
+ }
2745
+ if (stdioClientTransportAliases.has(initNode.callee.name)) {
2746
+ transportAliasKinds.set(idNode.name, "stdio");
2747
+ const serviceInfo = ensureMcpService(
2748
+ servicesByKey,
2749
+ file,
2750
+ fileRelativeLoc,
2751
+ );
2752
+ serviceInfo.serviceKinds.add("client");
2753
+ serviceInfo.usageSignals.add("client-stdio-transport");
2754
+ }
2755
+ if (initNode.callee.name === "URL") {
2756
+ const urlValue = getLiteralStringValue(initNode.arguments?.[0]);
2757
+ if (urlValue) {
2758
+ urlLiteralByAlias.set(idNode.name, urlValue);
2759
+ const classifiedUrl = classifyUrlValue(urlValue);
2760
+ if (
2761
+ servicesByKey.has(fileRelativeLoc) ||
2762
+ classifiedUrl?.isMcpEndpoint ||
2763
+ classifiedUrl?.providerFamily
2764
+ ) {
2765
+ const serviceInfo = ensureMcpService(
2766
+ servicesByKey,
2767
+ file,
2768
+ fileRelativeLoc,
2769
+ );
2770
+ recordUrlUsage(serviceInfo, urlValue, "configured-url", true);
2771
+ if (urlValue.includes("/mcp")) {
2772
+ serviceInfo.transports.add("streamable-http");
2773
+ }
2774
+ }
2775
+ }
2776
+ }
2777
+ }
2778
+ if (
2779
+ idNode.type === "Identifier" &&
2780
+ initNode.type === "ObjectExpression" &&
2781
+ (objectExpressionProperty(initNode, "authorization_endpoint") ||
2782
+ objectExpressionProperty(initNode, "token_endpoint") ||
2783
+ objectExpressionProperty(initNode, "issuer"))
2784
+ ) {
2785
+ authMetadataByAlias.set(idNode.name, extractAuthMetadata(initNode));
2786
+ }
2787
+ if (idNode.type === "Identifier") {
2788
+ const literalValue = getLiteralStringValue(initNode);
2789
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2790
+ if (literalValue && serviceInfo) {
2791
+ if (["model", "modelName"].includes(idNode.name)) {
2792
+ recordMcpModel(serviceInfo, literalValue);
2793
+ }
2794
+ if (["provider", "providerName"].includes(idNode.name)) {
2795
+ recordMcpProvider(serviceInfo, literalValue);
2796
+ }
2797
+ if (
2798
+ [
2799
+ "endpoint",
2800
+ "mcpEndpoint",
2801
+ "resourceServerUrl",
2802
+ "url",
2803
+ ].includes(idNode.name)
2804
+ ) {
2805
+ recordUrlUsage(serviceInfo, literalValue, "configured-url");
2806
+ }
2807
+ }
2808
+ if (
2809
+ providerAliases.has(idNode.name) &&
2810
+ servicesByKey.has(fileRelativeLoc)
2811
+ ) {
2812
+ const serviceInfo = ensureMcpService(
2813
+ servicesByKey,
2814
+ file,
2815
+ fileRelativeLoc,
2816
+ );
2817
+ recordMcpProvider(serviceInfo, providerAliases.get(idNode.name));
2818
+ }
2819
+ }
2820
+ },
2821
+ ObjectProperty: (path) => {
2822
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2823
+ if (!serviceInfo) {
2824
+ return;
2825
+ }
2826
+ recordMcpModelProperty(serviceInfo, path.node);
2827
+ },
2828
+ NewExpression: (path) => {
2829
+ if (path?.node?.callee?.type !== "Identifier") {
2830
+ return;
2831
+ }
2832
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2833
+ if (!serviceInfo) {
2834
+ return;
2835
+ }
2836
+ if (httpTransportAliases.has(path.node.callee.name)) {
2837
+ serviceInfo.transports.add("streamable-http");
2838
+ }
2839
+ if (stdioTransportAliases.has(path.node.callee.name)) {
2840
+ serviceInfo.transports.add("stdio");
2841
+ }
2842
+ if (httpClientTransportAliases.has(path.node.callee.name)) {
2843
+ serviceInfo.serviceKinds.add("client");
2844
+ serviceInfo.usageSignals.add("client-http-transport");
2845
+ recordUrlUsage(
2846
+ serviceInfo,
2847
+ getUrlStringValue(path.node.arguments?.[0], urlLiteralByAlias),
2848
+ "outbound-mcp-endpoint",
2849
+ );
2850
+ }
2851
+ if (stdioClientTransportAliases.has(path.node.callee.name)) {
2852
+ serviceInfo.serviceKinds.add("client");
2853
+ serviceInfo.usageSignals.add("client-stdio-transport");
2854
+ }
2855
+ },
2856
+ CallExpression: (path) => {
2857
+ const callNode = path?.node;
2858
+ if (!callNode) {
2859
+ return;
2860
+ }
2861
+ const serviceInfo = servicesByKey.get(fileRelativeLoc);
2862
+ const callee = callNode.callee;
2863
+ if (
2864
+ callee.type === "Identifier" &&
2865
+ authHelperAliases.has(callee.name)
2866
+ ) {
2867
+ const ensuredService = ensureMcpService(
2868
+ servicesByKey,
2869
+ file,
2870
+ fileRelativeLoc,
2871
+ );
2872
+ ensuredService.authenticated = true;
2873
+ ensuredService.xTrustBoundary = true;
2874
+ ensuredService.usageSignals.add("auth-helper");
2875
+ if (callee.name === "requireBearerAuth") {
2876
+ ensuredService.authModes.add("bearer");
2877
+ }
2878
+ if (callee.name === "mcpAuthMetadataRouter") {
2879
+ ensuredService.authModes.add("oauth-metadata");
2880
+ }
2881
+ if (callee.name === "createProtectedResourceMetadataRouter") {
2882
+ ensuredService.authModes.add("protected-resource-metadata");
2883
+ }
2884
+ if (
2885
+ callNode.arguments?.[0]?.type === "ObjectExpression" &&
2886
+ callee.name === "mcpAuthMetadataRouter"
2887
+ ) {
2888
+ const authMetadataNode = objectExpressionProperty(
2889
+ callNode.arguments[0],
2890
+ "oauthMetadata",
2891
+ )?.value;
2892
+ if (authMetadataNode?.type === "Identifier") {
2893
+ recordAuthMetadata(
2894
+ ensuredService,
2895
+ authMetadataByAlias.get(authMetadataNode.name),
2896
+ );
2897
+ } else {
2898
+ recordAuthMetadata(
2899
+ ensuredService,
2900
+ extractAuthMetadata(authMetadataNode),
2901
+ );
2902
+ }
2903
+ const resourceServerNode = objectExpressionProperty(
2904
+ callNode.arguments[0],
2905
+ "resourceServerUrl",
2906
+ )?.value;
2907
+ if (
2908
+ resourceServerNode?.type === "Identifier" &&
2909
+ urlLiteralByAlias.has(resourceServerNode.name)
2910
+ ) {
2911
+ recordUrlUsage(
2912
+ ensuredService,
2913
+ urlLiteralByAlias.get(resourceServerNode.name),
2914
+ "configured-url",
2915
+ true,
2916
+ );
2917
+ }
2918
+ }
2919
+ return;
2920
+ }
2921
+ if (callee.type === "Identifier") {
2922
+ if (serviceInfo && MCP_NETWORK_METHODS.has(callee.name)) {
2923
+ recordUrlUsage(
2924
+ serviceInfo,
2925
+ getUrlStringValue(callNode.arguments?.[0], urlLiteralByAlias),
2926
+ "outbound-network",
2927
+ );
2928
+ }
2929
+ return;
2930
+ }
2931
+ if (callee.type !== "MemberExpression") {
2932
+ return;
2933
+ }
2934
+ const objectName = getMemberChainString(callee.object);
2935
+ const methodName = getMemberExpressionPropertyName(callee.property);
2936
+ if (!methodName) {
2937
+ return;
2938
+ }
2939
+ if (methodName === "connect" && serviceInfo) {
2940
+ const transportArg = callNode.arguments?.[0];
2941
+ if (clientAliases.has(objectName)) {
2942
+ serviceInfo.serviceKinds.add("client");
2943
+ serviceInfo.usageSignals.add("client-connect");
2944
+ }
2945
+ if (transportArg?.type === "Identifier") {
2946
+ const transportKind = transportAliasKinds.get(transportArg.name);
2947
+ if (transportKind) {
2948
+ serviceInfo.transports.add(transportKind);
2949
+ }
2950
+ } else if (
2951
+ transportArg?.type === "NewExpression" &&
2952
+ transportArg.callee?.type === "Identifier"
2953
+ ) {
2954
+ if (httpTransportAliases.has(transportArg.callee.name)) {
2955
+ serviceInfo.transports.add("streamable-http");
2956
+ }
2957
+ if (stdioTransportAliases.has(transportArg.callee.name)) {
2958
+ serviceInfo.transports.add("stdio");
2959
+ }
2960
+ if (httpClientTransportAliases.has(transportArg.callee.name)) {
2961
+ serviceInfo.serviceKinds.add("client");
2962
+ serviceInfo.usageSignals.add("client-connect");
2963
+ recordUrlUsage(
2964
+ serviceInfo,
2965
+ getUrlStringValue(
2966
+ transportArg.arguments?.[0],
2967
+ urlLiteralByAlias,
2968
+ ),
2969
+ "outbound-mcp-endpoint",
2970
+ );
2971
+ }
2972
+ }
2973
+ }
2974
+ if (
2975
+ serviceInfo &&
2976
+ ["registerPrompt", "registerResource", "registerTool"].includes(
2977
+ methodName,
2978
+ )
2979
+ ) {
2980
+ const primitive = {
2981
+ annotations: undefined,
2982
+ description: undefined,
2983
+ name: undefined,
2984
+ role:
2985
+ methodName === "registerTool"
2986
+ ? "tool"
2987
+ : methodName === "registerPrompt"
2988
+ ? "prompt"
2989
+ : "resource",
2990
+ sourceLine: callNode.loc?.start?.line,
2991
+ uri: undefined,
2992
+ };
2993
+ primitive.name = getLiteralStringValue(callNode.arguments?.[0]);
2994
+ if (methodName === "registerTool") {
2995
+ primitive.description = objectExpressionStringValue(
2996
+ callNode.arguments?.[1],
2997
+ "description",
2998
+ );
2999
+ const annotationsNode = objectExpressionProperty(
3000
+ callNode.arguments?.[1],
3001
+ "annotations",
3002
+ )?.value;
3003
+ if (annotationsNode?.type === "ObjectExpression") {
3004
+ const annotations = {};
3005
+ for (const prop of annotationsNode.properties || []) {
3006
+ if (prop.type !== "ObjectProperty") {
3007
+ continue;
3008
+ }
3009
+ const keyName = getMemberExpressionPropertyName(prop.key);
3010
+ const boolValue =
3011
+ prop.value?.type === "BooleanLiteral"
3012
+ ? prop.value.value
3013
+ : undefined;
3014
+ const stringValue = getLiteralStringValue(prop.value);
3015
+ if (keyName && typeof boolValue === "boolean") {
3016
+ annotations[keyName] = boolValue;
3017
+ } else if (keyName && stringValue) {
3018
+ annotations[keyName] = stringValue;
3019
+ }
3020
+ }
3021
+ if (Object.keys(annotations).length) {
3022
+ primitive.annotations = annotations;
3023
+ }
3024
+ }
3025
+ serviceInfo.capabilities.add("tools");
3026
+ } else if (methodName === "registerPrompt") {
3027
+ primitive.description = objectExpressionStringValue(
3028
+ callNode.arguments?.[1],
3029
+ "description",
3030
+ );
3031
+ serviceInfo.capabilities.add("prompts");
3032
+ } else if (methodName === "registerResource") {
3033
+ primitive.uri = getLiteralStringValue(callNode.arguments?.[1]);
3034
+ primitive.description = objectExpressionStringValue(
3035
+ callNode.arguments?.[2],
3036
+ "description",
3037
+ );
3038
+ if (
3039
+ primitive.uri?.includes("{") ||
3040
+ primitive.uri?.includes("}")
3041
+ ) {
3042
+ primitive.role = "resource-template";
3043
+ }
3044
+ serviceInfo.capabilities.add("resources");
3045
+ }
3046
+ if (primitive.name || primitive.uri) {
3047
+ serviceInfo.primitives.push(primitive);
3048
+ }
3049
+ serviceInfo.serviceKinds.add("server");
3050
+ serviceInfo.usageSignals.add(`registered-${primitive.role}`);
3051
+ return;
3052
+ }
3053
+ if (serviceInfo && MCP_CLIENT_USAGE_METHODS.has(methodName)) {
3054
+ serviceInfo.serviceKinds.add("client");
3055
+ serviceInfo.usageSignals.add(`client-${methodName}`);
3056
+ }
3057
+ if (
3058
+ serviceInfo &&
3059
+ appAliases.has(objectName) &&
3060
+ MCP_ROUTE_METHODS.has(methodName)
3061
+ ) {
3062
+ const routePath = getLiteralStringValue(callNode.arguments?.[0]);
3063
+ if (routePath?.includes("/mcp")) {
3064
+ recordUrlUsage(serviceInfo, routePath, "route-endpoint", true);
3065
+ serviceInfo.transports.add("streamable-http");
3066
+ serviceInfo.serviceKinds.add("server");
3067
+ }
3068
+ if (
3069
+ routePath?.includes("/.well-known/oauth-authorization-server") ||
3070
+ routePath?.includes("/.well-known/oauth-protected-resource")
3071
+ ) {
3072
+ serviceInfo.authenticated = true;
3073
+ serviceInfo.xTrustBoundary = true;
3074
+ serviceInfo.authModes.add("oauth-metadata");
3075
+ recordUrlUsage(serviceInfo, routePath, "auth-discovery", true);
3076
+ }
3077
+ for (const arg of callNode.arguments || []) {
3078
+ if (
3079
+ arg?.type === "Identifier" &&
3080
+ authHelperAliases.has(arg.name)
3081
+ ) {
3082
+ serviceInfo.authenticated = true;
3083
+ serviceInfo.xTrustBoundary = true;
3084
+ serviceInfo.authModes.add("bearer");
3085
+ }
3086
+ if (
3087
+ arg?.type === "CallExpression" &&
3088
+ arg.callee?.type === "Identifier" &&
3089
+ authHelperAliases.has(arg.callee.name)
3090
+ ) {
3091
+ serviceInfo.authenticated = true;
3092
+ serviceInfo.xTrustBoundary = true;
3093
+ serviceInfo.authModes.add("bearer");
3094
+ }
3095
+ }
3096
+ }
3097
+ if (!serviceInfo) {
3098
+ return;
3099
+ }
3100
+ const outboundUrl = getUrlStringValue(
3101
+ callNode.arguments?.[0],
3102
+ urlLiteralByAlias,
3103
+ );
3104
+ const objectRootName = rootMemberName(objectName);
3105
+ if (providerAliases.has(objectRootName)) {
3106
+ recordMcpProvider(serviceInfo, providerAliases.get(objectRootName));
3107
+ serviceInfo.serviceKinds.add("client");
3108
+ serviceInfo.usageSignals.add("provider-sdk-call");
3109
+ }
3110
+ if (outboundUrl && MCP_NETWORK_METHODS.has(methodName)) {
3111
+ recordUrlUsage(serviceInfo, outboundUrl, "outbound-network");
3112
+ }
3113
+ if (outboundUrl?.includes("/mcp")) {
3114
+ recordUrlUsage(serviceInfo, outboundUrl, "outbound-mcp-endpoint");
3115
+ }
3116
+ },
3117
+ });
3118
+ } catch {
3119
+ // Skip parse failures and continue scanning
3120
+ }
3121
+ if (fileHasMcpImports && !servicesByKey.has(fileRelativeLoc)) {
3122
+ ensureMcpService(servicesByKey, file, fileRelativeLoc);
3123
+ }
3124
+ }
3125
+ return buildMcpInventoryFromServices(servicesByKey);
3126
+ };