Coverage for mcp/tools/nodepools.py: 97%

52 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-15 15:07 +0000

1"""Karpenter NodePool management MCP tools (read-only).""" 

2 

3import asyncio 

4 

5import cli_runner 

6from audit import audit_logged 

7from server import mcp 

8 

9 

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

11@audit_logged 

12async def nodepools_list(region: str | None = None, cluster: str | None = None) -> str: 

13 """`gco nodepools list` — list Karpenter NodePools in a cluster. 

14 

15 Args: 

16 region: AWS region to query. 

17 cluster: EKS cluster name (defaults to ``gco-<region>``). 

18 """ 

19 args = ["nodepools", "list"] 

20 if region: 

21 args += ["-r", region] 

22 if cluster: 

23 args += ["--cluster", cluster] 

24 return await asyncio.to_thread(cli_runner._run_cli, *args) 

25 

26 

27@mcp.tool(tags={"safe", "nodepools"}) 

28@audit_logged 

29async def nodepools_describe(nodepool_name: str, region: str, cluster: str | None = None) -> str: 

30 """`gco nodepools describe` — describe a single NodePool. 

31 

32 Args: 

33 nodepool_name: NodePool name. 

34 region: AWS region. 

35 cluster: EKS cluster name (defaults to ``gco-<region>``). 

36 """ 

37 args = ["nodepools", "describe", nodepool_name, "-r", region] 

38 if cluster: 

39 args += ["--cluster", cluster] 

40 return await asyncio.to_thread(cli_runner._run_cli, *args) 

41 

42 

43# ============================================================================= 

44# Mutating tools (low-risk) 

45# ============================================================================= 

46 

47 

48@mcp.tool(tags={"low-risk", "nodepools"}) 

49@audit_logged 

50async def nodepools_create_odcr( 

51 name: str, 

52 region: str, 

53 instance_type: str, 

54 capacity_reservation_id: str, 

55 cluster: str | None = None, 

56 count: int = 1, 

57 taints: list[str] | None = None, 

58 labels: dict[str, str] | None = None, 

59) -> str: 

60 """`gco nodepools create-odcr` — create a Karpenter NodePool tied to an ODCR. 

61 

62 Args: 

63 name: NodePool name. 

64 region: AWS region. 

65 instance_type: EC2 instance type the NodePool will provision. 

66 capacity_reservation_id: EC2 Capacity Reservation ID (``cr-...``) or ODCR group ARN. 

67 cluster: EKS cluster name (defaults to ``gco-<region>``). 

68 count: Initial node count target. 

69 taints: Optional taints formatted as ``key=value:effect``; one per ``--taint`` flag. 

70 labels: Optional ``key=value`` labels; one per ``--label`` flag. 

71 """ 

72 args = [ 

73 "nodepools", 

74 "create-odcr", 

75 name, 

76 "-r", 

77 region, 

78 "--instance-type", 

79 instance_type, 

80 "--capacity-reservation-id", 

81 capacity_reservation_id, 

82 "--count", 

83 str(count), 

84 ] 

85 if cluster: 

86 args += ["--cluster", cluster] 

87 if taints: 

88 for taint in taints: 

89 args += ["--taint", taint] 

90 if labels: 

91 for key, value in labels.items(): 

92 args += ["--label", f"{key}={value}"] 

93 return await asyncio.to_thread(cli_runner._run_cli, *args) 

94 

95 

96# ============================================================================= 

97# Destructive tools — gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS 

98# ============================================================================= 

99 

100 

101import contextlib # noqa: E402 

102 

103from feature_flags import FLAG_DESTRUCTIVE_OPERATIONS, is_enabled # noqa: E402 

104 

105 

106async def _ctx_warning(message: str) -> None: 

107 """Emit ``ctx.warning(...)`` from inside a tool body, no-op when no Context.""" 

108 try: 

109 from fastmcp.server.dependencies import get_context 

110 

111 ctx = get_context() 

112 except Exception: 

113 return 

114 with contextlib.suppress(Exception): 

115 await ctx.warning(message) 

116 

117 

118if is_enabled(FLAG_DESTRUCTIVE_OPERATIONS): 

119 

120 @mcp.tool(tags={"destructive", "nodepools"}) 

121 @audit_logged 

122 async def delete_nodepool(nodepool_name: str, region: str, cluster: str | None = None) -> str: 

123 """[gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS] destructive. 

124 

125 `gco nodepools delete` — delete a Karpenter NodePool. 

126 Cannot be undone — the NodePool, its EC2NodeClass, and any nodes 

127 currently provisioned through it are removed. 

128 

129 Args: 

130 nodepool_name: NodePool name. 

131 region: AWS region. 

132 cluster: EKS cluster name (defaults to ``gco-<region>``). 

133 """ 

134 await _ctx_warning( 

135 f"Deleting NodePool {nodepool_name!r} in {region} — this cannot be undone." 

136 ) 

137 args = ["nodepools", "delete", nodepool_name, "-r", region, "-y"] 

138 if cluster: 

139 args += ["--cluster", cluster] 

140 return await asyncio.to_thread(cli_runner._run_cli, *args)