Coverage for gco_mcp / tools / deps.py: 100.00%

17 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-09-14 22:07 +0000

1"""Dependency-maintenance tools (update scan + NodePool registry freshness).""" 

2 

3import json 

4 

5import cli_runner 

6from audit import audit_logged 

7from server import mcp 

8 

9 

10@mcp.tool(tags={"safe", "observability"}) 

11@audit_logged 

12async def deps_scan(nodepools_only: bool = False) -> str: 

13 """Generate the dependency update list the monthly deps-scan produces. 

14 

15 Runs `gco deps scan` — the same scanner behind the rolling 

16 "[Automated] Dependency updates available" GitHub issue — and returns 

17 a JSON envelope with `has_drift`, `scan_complete`, and the full 

18 Markdown report under `report_markdown`. Surfaces that need AWS 

19 credentials or missing host tools are skipped and flagged as 

20 incomplete rather than failing. 

21 

22 The full scan reaches out to PyPI, npm, container registries, GitHub, 

23 and (with credentials) AWS, and typically takes several minutes. Its 

24 Python surface pip-installs the project's extras into the server's 

25 active environment, mirroring how the CI scan runs. Requires a GCO 

26 checkout (the scanner lives under .github/scripts/). 

27 

28 Args: 

29 nodepools_only: Run only the accelerator-catalog / Karpenter 

30 NodePool freshness check (offline policy validation always; 

31 live EC2 catalog comparison when AWS credentials resolve). 

32 Fast, and the only network it may touch is EC2. 

33 """ 

34 args = ["deps", "scan"] 

35 if nodepools_only: 

36 args.append("--nodepools-only") 

37 # A full scan sweeps several registries and installs the Python extras; 

38 # give it far more headroom than the default two minutes. 

39 timeout = 300 if nodepools_only else 1800 

40 result = await cli_runner._run_cli_async(*args, timeout_seconds=timeout) 

41 # _run_cli_async returns either the command's stdout (already a JSON 

42 # envelope — the CLI honors the global --output json flag) or its own 

43 # JSON error envelope; both are valid JSON strings. Guard anyway so a 

44 # partial read never surfaces as an unparseable blob. 

45 try: 

46 json.loads(result) 

47 except TypeError, ValueError: 

48 return json.dumps({"error": "deps scan produced unparseable output", "raw": result[:2000]}) 

49 return result