mcp-vulnerabilities 1.0.0__tar.gz

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 (75) hide show
  1. mcp_vulnerabilities-1.0.0/PKG-INFO +110 -0
  2. mcp_vulnerabilities-1.0.0/README.md +99 -0
  3. mcp_vulnerabilities-1.0.0/pyproject.toml +28 -0
  4. mcp_vulnerabilities-1.0.0/setup.cfg +4 -0
  5. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/__init__.py +51 -0
  6. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/__init__.py +14 -0
  7. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/fixer.py +164 -0
  8. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/matcher.py +493 -0
  9. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/parsers.py +179 -0
  10. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/reporters.py +150 -0
  11. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/catalog.py +148 -0
  12. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/cli.py +800 -0
  13. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/__init__.py +17 -0
  14. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/cve_json5.py +246 -0
  15. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/ghsa.py +217 -0
  16. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/markdown_cve.py +334 -0
  17. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/nvd.py +251 -0
  18. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/osv_dev.py +206 -0
  19. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/verity.py +120 -0
  20. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/deduplicator.py +275 -0
  21. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/__init__.py +8 -0
  22. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/enricher.py +97 -0
  23. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/github_curated.py +103 -0
  24. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/glama.py +74 -0
  25. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/npm.py +80 -0
  26. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/orchestrator.py +112 -0
  27. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/pypi.py +85 -0
  28. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/registries.py +105 -0
  29. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/smithery.py +85 -0
  30. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/feed.py +223 -0
  31. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/filter.py +158 -0
  32. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/models.py +346 -0
  33. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/notifier.py +124 -0
  34. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/pipeline.py +776 -0
  35. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/__init__.py +0 -0
  36. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/client.py +83 -0
  37. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/fingerprinter.py +104 -0
  38. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/transport.py +135 -0
  39. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/site_builder.py +137 -0
  40. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/snapshot.py +328 -0
  41. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/state.py +116 -0
  42. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/transitive.py +296 -0
  43. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/validator.py +186 -0
  44. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/PKG-INFO +110 -0
  45. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/SOURCES.txt +73 -0
  46. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/dependency_links.txt +1 -0
  47. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/entry_points.txt +2 -0
  48. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/requires.txt +5 -0
  49. mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/top_level.txt +1 -0
  50. mcp_vulnerabilities-1.0.0/tests/test_acceptance_ghsa_delta.py +151 -0
  51. mcp_vulnerabilities-1.0.0/tests/test_acceptance_index_preservation.py +234 -0
  52. mcp_vulnerabilities-1.0.0/tests/test_acceptance_pypi_versioning.py +48 -0
  53. mcp_vulnerabilities-1.0.0/tests/test_acceptance_range_integrity.py +189 -0
  54. mcp_vulnerabilities-1.0.0/tests/test_acceptance_real_repositories.py +172 -0
  55. mcp_vulnerabilities-1.0.0/tests/test_audit.py +308 -0
  56. mcp_vulnerabilities-1.0.0/tests/test_catalog_state.py +105 -0
  57. mcp_vulnerabilities-1.0.0/tests/test_cli_search.py +84 -0
  58. mcp_vulnerabilities-1.0.0/tests/test_cve_json5_converter.py +83 -0
  59. mcp_vulnerabilities-1.0.0/tests/test_discovery_providers.py +151 -0
  60. mcp_vulnerabilities-1.0.0/tests/test_feed.py +69 -0
  61. mcp_vulnerabilities-1.0.0/tests/test_hub_discovery.py +75 -0
  62. mcp_vulnerabilities-1.0.0/tests/test_mcp_relevance_filter.py +107 -0
  63. mcp_vulnerabilities-1.0.0/tests/test_notifier.py +84 -0
  64. mcp_vulnerabilities-1.0.0/tests/test_osv_converters.py +171 -0
  65. mcp_vulnerabilities-1.0.0/tests/test_osv_models.py +133 -0
  66. mcp_vulnerabilities-1.0.0/tests/test_osv_pipeline.py +479 -0
  67. mcp_vulnerabilities-1.0.0/tests/test_osv_sync_state.py +57 -0
  68. mcp_vulnerabilities-1.0.0/tests/test_osv_template_conformance.py +182 -0
  69. mcp_vulnerabilities-1.0.0/tests/test_osv_validator.py +199 -0
  70. mcp_vulnerabilities-1.0.0/tests/test_probe.py +145 -0
  71. mcp_vulnerabilities-1.0.0/tests/test_sarif_reporter.py +141 -0
  72. mcp_vulnerabilities-1.0.0/tests/test_site_builder.py +65 -0
  73. mcp_vulnerabilities-1.0.0/tests/test_snapshot.py +164 -0
  74. mcp_vulnerabilities-1.0.0/tests/test_transitive.py +211 -0
  75. mcp_vulnerabilities-1.0.0/tests/test_vulnerability_varieties.py +190 -0
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-vulnerabilities
3
+ Version: 1.0.0
4
+ Summary: Open-Source OSV-Compliant Model Context Protocol (MCP) Vulnerability Advisory Database
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: packaging>=24.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
10
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
11
+
12
+ # Open-Source MCP Vulnerability Advisory Database
13
+
14
+ [![Daily MCP Vulnerability Ingestion & Sync](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml/badge.svg)](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml)
15
+ [![Advisories Count](https://img.shields.io/badge/advisories-500%2B-red.svg)](data/vulnerabilities)
16
+ [![OSV Schema 1.6.0](https://img.shields.io/badge/schema-OSV%201.6.0-blue.svg)](https://ossf.github.io/osv-schema/)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
18
+
19
+ An open-source, automated **Open Source Vulnerability (OSV 1.6)** compliant security advisory database specifically curated for the **Model Context Protocol (MCP)** ecosystem.
20
+
21
+ ## Sources
22
+ - **CVE List v5**: Official CVE JSON 5.0 records.
23
+ - **GitHub Security Advisories (GHSA)**: Live GraphQL/REST security alerts.
24
+ - **OSV.dev**: Unified open-source vulnerability database.
25
+ - **National Vulnerability Database (NVD)**: CVE 2.0 vulnerability metrics and CVSS scores.
26
+
27
+ ## Fast Consumption
28
+
29
+ You can fetch the complete vulnerability database snapshot in a single GET request:
30
+ ```bash
31
+ curl -sL https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz | gzip -d > vulnerabilities.json
32
+ ```
33
+
34
+ ### Python
35
+ ```python
36
+ import gzip, json, urllib.request
37
+
38
+ url = "https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz"
39
+ with urllib.request.urlopen(url) as resp:
40
+ with gzip.GzipFile(fileobj=resp) as gz:
41
+ data = json.load(gz)
42
+
43
+ print(f"Loaded {data['total_vulnerabilities']} MCP security advisories.")
44
+ ```
45
+
46
+ ## CI/CD Integration
47
+
48
+ You can easily prevent vulnerable MCP server dependencies from entering your production environment or being configured into AI client settings by adding this official, reusable GitHub Action to your CI workflows.
49
+
50
+ ### Audit AI Client Configurations
51
+ ```yaml
52
+ - name: Audit MCP Servers
53
+ uses: JMartynov/mcp-vulnerabilities@v1
54
+ with:
55
+ config-path: ".claude/claude_desktop_config.json"
56
+ fail-on-severity: "HIGH"
57
+ ```
58
+
59
+ ### Audit Dependency Manifests
60
+ ```yaml
61
+ - name: Audit Transitive Dependencies
62
+ uses: JMartynov/mcp-vulnerabilities@v1
63
+ with:
64
+ manifest-path: "package.json"
65
+ fail-on-severity: "HIGH"
66
+ ```
67
+
68
+ ## CLI Usage
69
+ ```bash
70
+ # Ingest latest advisories from live APIs
71
+ python -m mcp_vulnerabilities.cli sync --live-api
72
+
73
+ # Validate OSV schema compliance
74
+ python -m mcp_vulnerabilities.cli validate --dir data/vulnerabilities
75
+
76
+ # Compile compressed snapshot
77
+ python -m mcp_vulnerabilities.cli snapshot
78
+ ```
79
+
80
+ ## Documentation
81
+
82
+ - [Specification](docs/SPECIFICATION.md): Details the OSV 1.6 schema extensions, semantics, and vulnerability invariants.
83
+ - [Implementation](docs/IMPLEMENTATION.md): Architecture blueprint covering ingestion pipelines, state machines, and acceptance tests.
84
+
85
+ ## Testing
86
+
87
+ Execute the acceptance test suite to validate the end-to-end functionality:
88
+
89
+ ```bash
90
+ uv run pytest -v tests/test_acceptance_*.py
91
+ ```
92
+
93
+ ## Publishing to PyPI
94
+
95
+ ### Final Optional Step: Tagging v1.0.0 for PyPI
96
+ When you're ready to publish the inaugural release to PyPI:
97
+ 1. Ensure your [PyPI account](https://pypi.org/manage/account/publishing/) has a Pending Publisher configured:
98
+ - **Owner**: `JMartynov`
99
+ - **Repository**: `mcp-vulnerabilities`
100
+ - **Workflow name**: `release.yaml`
101
+ - **Environment**: `pypi` (or Any)
102
+ 2. Create and push the release tag:
103
+ ```bash
104
+ git tag -a v1.0.0 -m "Release v1.0.0: Inaugural open-source MCP vulnerability database & CLI"
105
+ git push origin v1.0.0
106
+ ```
107
+
108
+ ## License
109
+ MIT
110
+
@@ -0,0 +1,99 @@
1
+ # Open-Source MCP Vulnerability Advisory Database
2
+
3
+ [![Daily MCP Vulnerability Ingestion & Sync](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml/badge.svg)](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml)
4
+ [![Advisories Count](https://img.shields.io/badge/advisories-500%2B-red.svg)](data/vulnerabilities)
5
+ [![OSV Schema 1.6.0](https://img.shields.io/badge/schema-OSV%201.6.0-blue.svg)](https://ossf.github.io/osv-schema/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+
8
+ An open-source, automated **Open Source Vulnerability (OSV 1.6)** compliant security advisory database specifically curated for the **Model Context Protocol (MCP)** ecosystem.
9
+
10
+ ## Sources
11
+ - **CVE List v5**: Official CVE JSON 5.0 records.
12
+ - **GitHub Security Advisories (GHSA)**: Live GraphQL/REST security alerts.
13
+ - **OSV.dev**: Unified open-source vulnerability database.
14
+ - **National Vulnerability Database (NVD)**: CVE 2.0 vulnerability metrics and CVSS scores.
15
+
16
+ ## Fast Consumption
17
+
18
+ You can fetch the complete vulnerability database snapshot in a single GET request:
19
+ ```bash
20
+ curl -sL https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz | gzip -d > vulnerabilities.json
21
+ ```
22
+
23
+ ### Python
24
+ ```python
25
+ import gzip, json, urllib.request
26
+
27
+ url = "https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz"
28
+ with urllib.request.urlopen(url) as resp:
29
+ with gzip.GzipFile(fileobj=resp) as gz:
30
+ data = json.load(gz)
31
+
32
+ print(f"Loaded {data['total_vulnerabilities']} MCP security advisories.")
33
+ ```
34
+
35
+ ## CI/CD Integration
36
+
37
+ You can easily prevent vulnerable MCP server dependencies from entering your production environment or being configured into AI client settings by adding this official, reusable GitHub Action to your CI workflows.
38
+
39
+ ### Audit AI Client Configurations
40
+ ```yaml
41
+ - name: Audit MCP Servers
42
+ uses: JMartynov/mcp-vulnerabilities@v1
43
+ with:
44
+ config-path: ".claude/claude_desktop_config.json"
45
+ fail-on-severity: "HIGH"
46
+ ```
47
+
48
+ ### Audit Dependency Manifests
49
+ ```yaml
50
+ - name: Audit Transitive Dependencies
51
+ uses: JMartynov/mcp-vulnerabilities@v1
52
+ with:
53
+ manifest-path: "package.json"
54
+ fail-on-severity: "HIGH"
55
+ ```
56
+
57
+ ## CLI Usage
58
+ ```bash
59
+ # Ingest latest advisories from live APIs
60
+ python -m mcp_vulnerabilities.cli sync --live-api
61
+
62
+ # Validate OSV schema compliance
63
+ python -m mcp_vulnerabilities.cli validate --dir data/vulnerabilities
64
+
65
+ # Compile compressed snapshot
66
+ python -m mcp_vulnerabilities.cli snapshot
67
+ ```
68
+
69
+ ## Documentation
70
+
71
+ - [Specification](docs/SPECIFICATION.md): Details the OSV 1.6 schema extensions, semantics, and vulnerability invariants.
72
+ - [Implementation](docs/IMPLEMENTATION.md): Architecture blueprint covering ingestion pipelines, state machines, and acceptance tests.
73
+
74
+ ## Testing
75
+
76
+ Execute the acceptance test suite to validate the end-to-end functionality:
77
+
78
+ ```bash
79
+ uv run pytest -v tests/test_acceptance_*.py
80
+ ```
81
+
82
+ ## Publishing to PyPI
83
+
84
+ ### Final Optional Step: Tagging v1.0.0 for PyPI
85
+ When you're ready to publish the inaugural release to PyPI:
86
+ 1. Ensure your [PyPI account](https://pypi.org/manage/account/publishing/) has a Pending Publisher configured:
87
+ - **Owner**: `JMartynov`
88
+ - **Repository**: `mcp-vulnerabilities`
89
+ - **Workflow name**: `release.yaml`
90
+ - **Environment**: `pypi` (or Any)
91
+ 2. Create and push the release tag:
92
+ ```bash
93
+ git tag -a v1.0.0 -m "Release v1.0.0: Inaugural open-source MCP vulnerability database & CLI"
94
+ git push origin v1.0.0
95
+ ```
96
+
97
+ ## License
98
+ MIT
99
+
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mcp-vulnerabilities"
7
+ version = "1.0.0"
8
+ description = "Open-Source OSV-Compliant Model Context Protocol (MCP) Vulnerability Advisory Database"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "packaging>=24.0",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ dev = [
17
+ "pytest>=8.0.0",
18
+ "ruff>=0.4.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ mcp-vuln = "mcp_vulnerabilities.cli:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,51 @@
1
+ """Open Source Vulnerability (OSV v1.6.0) ingestion and normalization engine."""
2
+
3
+ from mcp_vulnerabilities.converters.ghsa import GhsaConverter
4
+ from mcp_vulnerabilities.converters.markdown_cve import MarkdownAdvisoryConverter
5
+ from mcp_vulnerabilities.converters.nvd import NvdConverter
6
+ from mcp_vulnerabilities.converters.osv_dev import OsvDevConverter
7
+ from mcp_vulnerabilities.converters.verity import VerityCatalogConverter
8
+ from mcp_vulnerabilities.deduplicator import OsvDeduplicator
9
+ from mcp_vulnerabilities.models import (
10
+ AffectedPackage,
11
+ DatabaseSpecificMcp,
12
+ EventSpec,
13
+ OsvVulnerability,
14
+ PackageSpec,
15
+ RangeSpec,
16
+ RangeType,
17
+ ReferenceSpec,
18
+ ReferenceType,
19
+ SeveritySpec,
20
+ SeverityType,
21
+ build_purl,
22
+ parse_purl,
23
+ )
24
+ from mcp_vulnerabilities.pipeline import McpVulnerabilityPipeline, PipelineResult
25
+ from mcp_vulnerabilities.validator import OsvValidationError, OsvValidator
26
+
27
+ __all__ = [
28
+ "AffectedPackage",
29
+ "DatabaseSpecificMcp",
30
+ "EventSpec",
31
+ "GhsaConverter",
32
+ "MarkdownAdvisoryConverter",
33
+ "McpVulnerabilityPipeline",
34
+ "NvdConverter",
35
+ "OsvDeduplicator",
36
+ "OsvDevConverter",
37
+ "OsvValidationError",
38
+ "OsvValidator",
39
+ "OsvVulnerability",
40
+ "PackageSpec",
41
+ "PipelineResult",
42
+ "RangeSpec",
43
+ "RangeType",
44
+ "ReferenceSpec",
45
+ "ReferenceType",
46
+ "SeveritySpec",
47
+ "SeverityType",
48
+ "VerityCatalogConverter",
49
+ "build_purl",
50
+ "parse_purl",
51
+ ]
@@ -0,0 +1,14 @@
1
+ """Audit subpackage for MCP client configs and installed packages."""
2
+
3
+ from .fixer import ClientConfigFixer
4
+ from .matcher import AuditFinding, AuditReport, VulnerabilityMatcher
5
+ from .parsers import ClientConfigParser, DiscoveredClientServer
6
+
7
+ __all__ = [
8
+ "AuditFinding",
9
+ "AuditReport",
10
+ "ClientConfigFixer",
11
+ "ClientConfigParser",
12
+ "DiscoveredClientServer",
13
+ "VulnerabilityMatcher",
14
+ ]
@@ -0,0 +1,164 @@
1
+ """Automated remediation for vulnerable MCP servers in client configurations."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import re
7
+ import shutil
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from packaging.version import InvalidVersion, Version
13
+
14
+ from .matcher import VulnerabilityMatcher, AuditFinding
15
+ from .parsers import ClientConfigParser
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ClientConfigFixer:
21
+ """Identifies and remediates vulnerable MCP servers in client configurations."""
22
+
23
+ def __init__(self, matcher: VulnerabilityMatcher):
24
+ self.matcher = matcher
25
+
26
+ def _calculate_minimum_safe_version(self, findings: list[AuditFinding]) -> str | None:
27
+ """Calculates the highest fixed version among all findings."""
28
+ if not findings:
29
+ return None
30
+
31
+ max_version: Version | None = None
32
+ best_version_str: str | None = None
33
+
34
+ for f in findings:
35
+ if not f.fixed_version:
36
+ continue
37
+
38
+ try:
39
+ v = Version(f.fixed_version)
40
+ if max_version is None or v > max_version:
41
+ max_version = v
42
+ best_version_str = f.fixed_version
43
+ except InvalidVersion:
44
+ # If we can't parse semver, just take the first one or continue
45
+ if max_version is None:
46
+ best_version_str = f.fixed_version
47
+
48
+ return best_version_str
49
+
50
+ def fix_config(self, config_path: Path, dry_run: bool = False) -> dict[str, Any]:
51
+ """Fixes a client configuration file by replacing vulnerable versions."""
52
+ if not config_path.exists():
53
+ raise FileNotFoundError(f"Configuration file not found: {config_path}")
54
+
55
+ servers = ClientConfigParser.parse_file(config_path)
56
+ with open(config_path, "r", encoding="utf-8") as f:
57
+ data = json.load(f)
58
+
59
+ report = self.matcher.audit_servers(servers)
60
+
61
+ # Group findings by server name
62
+ findings_by_server: dict[str, list[AuditFinding]] = {}
63
+ for f in report.findings:
64
+ findings_by_server.setdefault(f.server_name, []).append(f)
65
+
66
+ changes_made = []
67
+
68
+ # We need to find the specific mcpServers key or use root
69
+ raw_servers = data.get("mcpServers") or data.get("mcp_servers") or data.get("servers")
70
+
71
+
72
+ if not isinstance(raw_servers, dict):
73
+ if all(isinstance(v, dict) and ("command" in v or "args" in v) for v in data.values()):
74
+ raw_servers = data
75
+
76
+ else:
77
+ return {"status": "no_servers_found"}
78
+
79
+ for server in servers:
80
+ findings = findings_by_server.get(server.server_name)
81
+ if not findings:
82
+ continue
83
+
84
+ safe_version = self._calculate_minimum_safe_version(findings)
85
+ if not safe_version:
86
+ logger.warning(f"No safe version found for {server.server_name}, skipping.")
87
+ continue
88
+
89
+ # If the current version is already >= safe_version (e.g. unpinned), skip
90
+ try:
91
+ if server.version and Version(server.version) >= Version(safe_version):
92
+ continue
93
+ except InvalidVersion:
94
+ pass
95
+
96
+ old_version = server.version or "unpinned"
97
+
98
+ # Find the args in the original dict
99
+ srv_dict = raw_servers.get(server.server_name)
100
+ if not srv_dict or not isinstance(srv_dict, dict):
101
+ continue
102
+
103
+ args = srv_dict.get("args", [])
104
+ new_args = list(args)
105
+
106
+ # We need to rewrite the args
107
+ # Simple approach: find the arg that contains the package name and old version
108
+ for i, arg in enumerate(new_args):
109
+ if server.package_name in str(arg):
110
+ # Rewrite this arg based on ecosystem
111
+ if server.ecosystem == "npm":
112
+ if "@" in server.package_name and server.package_name.startswith("@"):
113
+ # scoped package
114
+ new_args[i] = f"{server.package_name}@{safe_version}"
115
+ else:
116
+ new_args[i] = f"{server.package_name}@{safe_version}"
117
+ elif server.ecosystem == "PyPI":
118
+ # We use == for pipx/uvx
119
+ if "==" in arg or ">=" in arg:
120
+ new_args[i] = re.sub(r"(==|>=|<=|>|<|~=|@).*", f"=={safe_version}", arg)
121
+ else:
122
+ new_args[i] = f"{arg}=={safe_version}"
123
+ elif server.ecosystem == "Docker":
124
+ if ":" in arg:
125
+ pkg = arg.rsplit(":", 1)[0]
126
+ new_args[i] = f"{pkg}:{safe_version}"
127
+ else:
128
+ new_args[i] = f"{arg}:{safe_version}"
129
+ break
130
+
131
+ srv_dict["args"] = new_args
132
+ changes_made.append({
133
+ "server_name": server.server_name,
134
+ "package_name": server.package_name,
135
+ "old_version": old_version,
136
+ "new_version": safe_version
137
+ })
138
+
139
+ if not changes_made:
140
+ return {"status": "no_changes_needed", "changes": []}
141
+
142
+ if not dry_run:
143
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
144
+ backup_path = config_path.with_name(f"{config_path.name}.bak.{timestamp}")
145
+ shutil.copy2(config_path, backup_path)
146
+
147
+ # Atomically write
148
+ tmp_path = config_path.with_name(f"{config_path.name}.tmp")
149
+ with open(tmp_path, "w", encoding="utf-8") as f:
150
+ json.dump(data, f, indent=2)
151
+ f.write("\n")
152
+ os.replace(tmp_path, config_path)
153
+
154
+ return {
155
+ "status": "success",
156
+ "changes": changes_made,
157
+ "backup_path": str(backup_path)
158
+ }
159
+
160
+ return {
161
+ "status": "dry_run",
162
+ "changes": changes_made
163
+ }
164
+