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

181 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-30 21:22 +0000

1"""Capacity checking and recommendation MCP tools.""" 

2 

3import cli_runner 

4from audit import audit_logged 

5from feature_flags import FLAG_CAPACITY_PURCHASE, FLAG_DESTRUCTIVE_OPERATIONS, is_enabled 

6from server import mcp 

7 

8 

9@mcp.tool(tags={"safe", "capacity"}) 

10@audit_logged 

11def check_capacity(instance_type: str, region: str) -> str: 

12 """Check spot and on-demand capacity for a specific instance type. 

13 

14 Args: 

15 instance_type: EC2 instance type (e.g. g4dn.xlarge, g5.2xlarge, p4d.24xlarge). 

16 region: AWS region to check. 

17 """ 

18 return cli_runner._run_cli("capacity", "check", "-i", instance_type, "-r", region) 

19 

20 

21@mcp.tool(tags={"safe", "capacity"}) 

22@audit_logged 

23def instance_info(instance_type: str) -> str: 

24 """Get hardware and pricing metadata for an EC2 instance type. 

25 

26 Args: 

27 instance_type: EC2 instance type (for example, g5.2xlarge or p5.48xlarge). 

28 """ 

29 return cli_runner._run_cli("capacity", "instance-info", instance_type) 

30 

31 

32@mcp.tool(tags={"safe", "capacity"}) 

33@audit_logged 

34def recommend_capacity( 

35 instance_type: str, 

36 region: str, 

37 fault_tolerance: str = "medium", 

38) -> str: 

39 """Recommend spot or on-demand capacity for a workload. 

40 

41 Args: 

42 instance_type: EC2 instance type to evaluate. 

43 region: AWS region in which the workload will run. 

44 fault_tolerance: Interruption tolerance: ``high``, ``medium``, or ``low``. 

45 """ 

46 return cli_runner._run_cli( 

47 "capacity", 

48 "recommend", 

49 "-i", 

50 instance_type, 

51 "-r", 

52 region, 

53 "-f", 

54 fault_tolerance, 

55 ) 

56 

57 

58@mcp.tool(tags={"safe", "capacity"}) 

59@audit_logged 

60def capacity_status(region: str | None = None) -> str: 

61 """View capacity status across all deployed regions. 

62 

63 Args: 

64 region: Specific region, or omit for all regions. 

65 """ 

66 args = ["capacity", "status"] 

67 if region: 

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

69 return cli_runner._run_cli(*args) 

70 

71 

72@mcp.tool(tags={"safe", "capacity"}) 

73@audit_logged 

74def recommend_region( 

75 gpu: bool = False, instance_type: str | None = None, gpu_count: int = 0 

76) -> str: 

77 """Get optimal region recommendation based on capacity. 

78 

79 Args: 

80 gpu: Whether the workload requires GPUs. 

81 instance_type: Specific instance type to check. When provided, uses weighted 

82 multi-signal scoring (spot placement scores, pricing, queue depth, etc.). 

83 gpu_count: Number of GPUs required for the workload. 

84 """ 

85 args = ["capacity", "recommend-region"] 

86 if gpu: 

87 args.append("--gpu") 

88 if instance_type: 

89 args += ["-i", instance_type] 

90 if gpu_count: 

91 args += ["--gpu-count", str(gpu_count)] 

92 return cli_runner._run_cli(*args) 

93 

94 

95@mcp.tool(tags={"safe", "capacity"}) 

96@audit_logged 

97def spot_prices(instance_type: str, region: str) -> str: 

98 """Get current spot prices for an instance type. 

99 

100 Args: 

101 instance_type: EC2 instance type. 

102 region: AWS region. 

103 """ 

104 return cli_runner._run_cli("capacity", "spot-prices", "-i", instance_type, "-r", region) 

105 

106 

107@mcp.tool(tags={"safe", "capacity"}) 

108@audit_logged 

109def ai_recommend( 

110 workload: str, 

111 instance_type: str | None = None, 

112 region: str | None = None, 

113 gpu: bool = False, 

114 min_gpus: int = 0, 

115 min_memory_gb: int = 0, 

116 fault_tolerance: str = "low", 

117 max_cost: float | None = None, 

118 model: str | None = None, 

119) -> str: 

120 """Get AI-powered capacity recommendation using Amazon Bedrock. 

121 

122 Gathers comprehensive capacity data (spot scores, pricing, cluster 

123 utilization, queue depth) and sends it to an LLM for analysis. 

124 Returns a recommended region, instance type, capacity type, and reasoning. 

125 

126 Requires AWS credentials with bedrock:InvokeModel permission and the 

127 specified model enabled in your account. 

128 

129 Args: 

130 workload: Description of the workload (e.g. "Fine-tuning a 20B parameter LLM"). 

131 instance_type: Specific instance type(s) to consider (e.g. "p4d.24xlarge"). 

132 region: Specific region(s) to consider (e.g. "us-east-1"). 

133 gpu: Whether the workload requires GPUs. 

134 min_gpus: Minimum number of GPUs required. 

135 min_memory_gb: Minimum GPU memory in GB. 

136 fault_tolerance: Tolerance for interruptions ("low", "medium", "high"). 

137 max_cost: Maximum acceptable cost per hour in USD. 

138 model: Bedrock model ID to use for analysis. Omit to use 

139 `cdk.json` `context.bedrock.default_model_id`. Mission and the 

140 capacity advisor consume that one shared configuration value. 

141 """ 

142 args = ["capacity", "ai-recommend", "-w", workload] 

143 if instance_type: 

144 args += ["-i", instance_type] 

145 if region: 

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

147 if gpu: 

148 args.append("--gpu") 

149 if min_gpus > 0: 

150 args += ["--min-gpus", str(min_gpus)] 

151 if min_memory_gb > 0: 

152 args += ["--min-memory-gb", str(min_memory_gb)] 

153 if fault_tolerance != "low": 

154 args += ["--fault-tolerance", fault_tolerance] 

155 if max_cost is not None: 

156 args += ["--max-cost", str(max_cost)] 

157 if model: 

158 args += ["--model", model] 

159 return cli_runner._run_cli(*args) 

160 

161 

162@mcp.tool(tags={"safe", "capacity"}) 

163@audit_logged 

164def list_reservations( 

165 instance_type: str | None = None, 

166 region: str | None = None, 

167) -> str: 

168 """List On-Demand Capacity Reservations (ODCRs) across regions. 

169 

170 Shows all active capacity reservations with utilization details. 

171 

172 Args: 

173 instance_type: Filter by instance type (e.g. p5.48xlarge). 

174 region: Filter by specific region. 

175 """ 

176 args = ["capacity", "reservations"] 

177 if instance_type: 

178 args += ["-i", instance_type] 

179 if region: 

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

181 return cli_runner._run_cli(*args) 

182 

183 

184@mcp.tool(tags={"safe", "capacity"}) 

185@audit_logged 

186def reservation_check( 

187 instance_type: str, 

188 regions: list[str] | None = None, 

189 count: int = 1, 

190 include_blocks: bool = True, 

191 block_duration: int = 24, 

192 block_duration_days: int | None = None, 

193 earliest_start: str | None = None, 

194 latest_start: str | None = None, 

195) -> str: 

196 """Check ODCR and Capacity Block availability for an instance type. 

197 

198 Checks existing On-Demand Capacity Reservations and purchasable Capacity 

199 Blocks for ML. By default it searches a 24h block soonest-available, but you 

200 can widen the search: pass several regions to fan out in parallel, set a 

201 start-date window (earliest_start / latest_start) to ask for blocks starting 

202 near a date, and set the block duration in hours or days. For a full 

203 duration-range sweep that returns one ranked, de-duplicated report, use 

204 find_capacity_blocks instead. 

205 

206 Args: 

207 instance_type: GPU instance type (e.g. p4d.24xlarge, p5.48xlarge, p6-b200). 

208 regions: Regions to check in parallel (any regions, not just deployed); 

209 omit to check all deployed regions. 

210 count: Minimum number of instances needed. 

211 include_blocks: Whether to include Capacity Block offerings. 

212 block_duration: Capacity Block duration in hours (default 24). 

213 block_duration_days: Capacity Block duration in days (overrides hours). 

214 earliest_start: Earliest block start date (YYYY-MM-DD or ISO datetime). 

215 latest_start: Latest block start date (YYYY-MM-DD or ISO datetime). 

216 """ 

217 args = ["capacity", "reservation-check", "-i", instance_type, "-c", str(count)] 

218 for r in regions or []: 

219 args += ["-r", r] 

220 if not include_blocks: 

221 args.append("--no-blocks") 

222 if block_duration != 24: 

223 args += ["--block-duration", str(block_duration)] 

224 if block_duration_days is not None: 

225 args += ["--block-duration-days", str(block_duration_days)] 

226 if earliest_start: 

227 args += ["--earliest-start", earliest_start] 

228 if latest_start: 

229 args += ["--latest-start", latest_start] 

230 return cli_runner._run_cli(*args) 

231 

232 

233@mcp.tool(tags={"safe", "capacity"}) 

234@audit_logged 

235def find_capacity_blocks( 

236 instance_type: str, 

237 regions: list[str] | None = None, 

238 count: int = 1, 

239 duration_days: int | None = None, 

240 duration_hours: int | None = None, 

241 min_duration_days: int | None = None, 

242 max_duration_days: int | None = None, 

243 min_duration_hours: int | None = None, 

244 max_duration_hours: int | None = None, 

245 earliest_start: str | None = None, 

246 latest_start: str | None = None, 

247 find_longest: bool = False, 

248) -> str: 

249 """Find EC2 Capacity Blocks across regions x durations x a start-date window. 

250 

251 This is the one-call sweep for "where and when can I get N of this GPU 

252 instance for D days?". It searches every requested region and every valid 

253 Capacity Block duration in the range, in parallel, then returns a single 

254 consolidated, de-duplicated, ranked report (cheapest per-GPU-hour first) with 

255 per-hour and per-GPU-hour pricing and the longest available block. 

256 

257 AWS allows durations in 1-day increments up to 14 days, then 7-day increments 

258 up to 182 days; a duration range is expanded to those discrete values 

259 automatically. Friendly names are normalized (p6-b200 -> p6-b200.48xlarge, 

260 p6-b300 -> p6-b300.48xlarge), and UltraServer-only families (the Grace- 

261 Blackwell GB200/GB300 superchips / P6e-GB UltraServers) are flagged rather 

262 than silently returning nothing. 

263 

264 Args: 

265 instance_type: GPU instance type or alias (e.g. p6-b200, p5.48xlarge). 

266 regions: Regions to search (any regions; defaults to deployed regions). 

267 count: Instances per block. 

268 duration_days / duration_hours: A single target duration. 

269 min_duration_days / max_duration_days: Duration range bounds in days. 

270 min_duration_hours / max_duration_hours: Duration range bounds in hours. 

271 earliest_start: Earliest block start (YYYY-MM-DD or ISO datetime). 

272 latest_start: Latest block start (YYYY-MM-DD or ISO datetime). 

273 find_longest: Sweep the duration ladder and surface the longest block. 

274 """ 

275 args = ["capacity", "find-blocks", "-i", instance_type] 

276 for r in regions or []: 

277 args += ["-r", r] 

278 if count != 1: 

279 args += ["-c", str(count)] 

280 if duration_days is not None: 

281 args += ["--duration-days", str(duration_days)] 

282 if duration_hours is not None: 

283 args += ["--duration-hours", str(duration_hours)] 

284 if min_duration_days is not None: 

285 args += ["--min-duration-days", str(min_duration_days)] 

286 if max_duration_days is not None: 

287 args += ["--max-duration-days", str(max_duration_days)] 

288 if min_duration_hours is not None: 

289 args += ["--min-duration-hours", str(min_duration_hours)] 

290 if max_duration_hours is not None: 

291 args += ["--max-duration-hours", str(max_duration_hours)] 

292 if earliest_start: 

293 args += ["--earliest-start", earliest_start] 

294 if latest_start: 

295 args += ["--latest-start", latest_start] 

296 if find_longest: 

297 args.append("--find-longest") 

298 return cli_runner._run_cli(*args) 

299 

300 

301@mcp.tool(tags={"safe", "capacity"}) 

302@audit_logged 

303def capacity_history_show(instance_type: str, region: str, hours: int = 168) -> str: 

304 """Show the recorded capacity time-series for an instance type in a region. 

305 

306 Requires the historical capacity surface (an optional add-on to the global 

307 stack, enabled by default). Returns spot score, spot price, AZ coverage, 

308 queue depth, and capacity-block availability over the window. 

309 

310 Args: 

311 instance_type: EC2 instance type (e.g. g5.xlarge, p5.48xlarge). 

312 region: AWS region. 

313 hours: Hours of history to show (default 168 = 7 days). 

314 """ 

315 return cli_runner._run_cli( 

316 "capacity", "history", "show", "-i", instance_type, "-r", region, "-H", str(hours) 

317 ) 

318 

319 

320@mcp.tool(tags={"safe", "capacity"}) 

321@audit_logged 

322def capacity_history_stats(instance_type: str, region: str, hours: int = 168) -> str: 

323 """Show p25/p50/p75/min/max/stddev per capacity metric over a time window. 

324 

325 Args: 

326 instance_type: EC2 instance type. 

327 region: AWS region. 

328 hours: Hours of history to summarize (default 168 = 7 days). 

329 """ 

330 return cli_runner._run_cli( 

331 "capacity", "history", "stats", "-i", instance_type, "-r", region, "-H", str(hours) 

332 ) 

333 

334 

335@mcp.tool(tags={"safe", "capacity"}) 

336@audit_logged 

337def capacity_history_patterns(instance_type: str, region: str, hours: int = 168) -> str: 

338 """Show a day-of-week by hour heatmap of average spot placement scores. 

339 

340 Args: 

341 instance_type: EC2 instance type. 

342 region: AWS region. 

343 hours: Hours of history to analyze (default 168 = 7 days). 

344 """ 

345 return cli_runner._run_cli( 

346 "capacity", "history", "patterns", "-i", instance_type, "-r", region, "-H", str(hours) 

347 ) 

348 

349 

350@mcp.tool(tags={"safe", "capacity"}) 

351@audit_logged 

352def capacity_predict( 

353 instance_type: str, 

354 region: str | None = None, 

355 hours: int = 168, 

356 all_regions: bool = False, 

357) -> str: 

358 """Predict the best time to acquire capacity from historical patterns (Bedrock). 

359 

360 Uses the historical capacity surface plus Amazon Bedrock to recommend the 

361 day/hour windows with the best spot availability and pricing. 

362 

363 Args: 

364 instance_type: EC2 instance type. 

365 region: AWS region. Omit when all_regions is true. 

366 hours: Hours of history to analyze (default 168 = 7 days). 

367 all_regions: Predict across every region that has data for the type. 

368 """ 

369 args = ["capacity", "predict", "-i", instance_type, "-H", str(hours)] 

370 if all_regions: 

371 args.append("--all-regions") 

372 elif region: 

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

374 return cli_runner._run_cli(*args) 

375 

376 

377@mcp.tool(tags={"safe", "capacity"}) 

378@audit_logged 

379def find_capacity_reservations( 

380 instance_type: str | None = None, 

381 regions: list[str] | None = None, 

382 count: int = 1, 

383 state: str = "active", 

384 pricing: bool = True, 

385) -> str: 

386 """Find existing ODCRs across regions in one parallel, ranked report. 

387 

388 The ODCR counterpart to find_capacity_blocks: it searches every requested 

389 region in parallel, normalizes friendly instance-type aliases (p6-b200 -> 

390 p6-b200.48xlarge), enriches each reservation with On-Demand pricing, and 

391 ranks them most-available-first (then cheapest per-GPU-hour). Use this to 

392 answer "where do I already have free reserved capacity?" rather than 

393 list_reservations' plain per-region aggregation. 

394 

395 Args: 

396 instance_type: Instance type or alias to filter by (e.g. p6-b200); omit 

397 to return every reservation. 

398 regions: Regions to search in parallel (any regions; defaults to deployed). 

399 count: Minimum available instances to consider the search satisfied. 

400 state: Reservation state filter ("active" default; "all" for any state). 

401 pricing: Enrich reservations with On-Demand pricing. 

402 """ 

403 args = ["capacity", "find-reservations"] 

404 if instance_type: 

405 args += ["-i", instance_type] 

406 for r in regions or []: 

407 args += ["-r", r] 

408 if count != 1: 

409 args += ["-c", str(count)] 

410 if state != "active": 

411 args += ["--state", state] 

412 if not pricing: 

413 args.append("--no-pricing") 

414 return cli_runner._run_cli(*args) 

415 

416 

417# Capacity purchasing / creation — disabled by default. 

418# Set GCO_ENABLE_CAPACITY_PURCHASE=true to enable. 

419if is_enabled(FLAG_CAPACITY_PURCHASE): 

420 

421 @mcp.tool(tags={"cost-incurring", "capacity"}) 

422 @audit_logged 

423 def reserve_capacity( 

424 offering_id: str, 

425 region: str, 

426 dry_run: bool = False, 

427 ) -> str: 

428 """Purchase a Capacity Block offering by its ID. 

429 

430 Use reservation_check first to find available offerings and their IDs, 

431 then purchase with this tool. Use dry_run=True to validate without purchasing. 

432 

433 Args: 

434 offering_id: Capacity Block offering ID (cb-xxx) from reservation_check. 

435 region: AWS region where the offering exists. 

436 dry_run: If True, validate the offering without purchasing (no cost). 

437 """ 

438 args = ["capacity", "reserve", "-o", offering_id, "-r", region] 

439 if dry_run: 

440 args.append("--dry-run") 

441 return cli_runner._run_cli(*args) 

442 

443 @mcp.tool(tags={"cost-incurring", "capacity"}) 

444 @audit_logged 

445 def create_reservation( 

446 instance_type: str, 

447 region: str, 

448 availability_zone: str, 

449 count: int = 1, 

450 platform: str = "Linux/UNIX", 

451 tenancy: str = "default", 

452 match_criteria: str = "open", 

453 end_date: str | None = None, 

454 ebs_optimized: bool = False, 

455 dry_run: bool = False, 

456 ) -> str: 

457 """Create a new On-Demand Capacity Reservation (ODCR). 

458 

459 The ODCR counterpart to reserve_capacity. Reserves On-Demand capacity for 

460 an instance type in a specific AZ. Charges accrue for the reserved 

461 capacity until it is cancelled — use dry_run=True to validate first. 

462 

463 Args: 

464 instance_type: EC2 instance type or alias (e.g. p5.48xlarge, p6-b200). 

465 region: AWS region. 

466 availability_zone: Target AZ (e.g. us-east-1a). 

467 count: Number of instances to reserve. 

468 platform: Instance platform/OS (default "Linux/UNIX"). 

469 tenancy: "default" or "dedicated". 

470 match_criteria: "open" or "targeted". 

471 end_date: Optional end date (YYYY-MM-DD or ISO); omit for unlimited. 

472 ebs_optimized: Reserve EBS-optimized capacity. 

473 dry_run: If True, validate without creating (no cost). 

474 """ 

475 args = [ 

476 "capacity", 

477 "create-reservation", 

478 "-i", 

479 instance_type, 

480 "-r", 

481 region, 

482 "-z", 

483 availability_zone, 

484 "-c", 

485 str(count), 

486 ] 

487 if platform != "Linux/UNIX": 

488 args += ["--platform", platform] 

489 if tenancy != "default": 

490 args += ["--tenancy", tenancy] 

491 if match_criteria != "open": 

492 args += ["--match-criteria", match_criteria] 

493 if end_date: 

494 args += ["--end-date", end_date] 

495 if ebs_optimized: 

496 args.append("--ebs-optimized") 

497 if dry_run: 

498 args.append("--dry-run") 

499 return cli_runner._run_cli(*args) 

500 

501 

502# Capacity reservation cancellation — destructive, disabled by default. 

503# Set GCO_ENABLE_DESTRUCTIVE_OPERATIONS=true to enable. 

504if is_enabled(FLAG_DESTRUCTIVE_OPERATIONS): 

505 

506 @mcp.tool(tags={"destructive", "capacity"}) 

507 @audit_logged 

508 def cancel_reservation( 

509 reservation_id: str, 

510 region: str, 

511 dry_run: bool = False, 

512 ) -> str: 

513 """[gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS] destructive. 

514 

515 Cancel an On-Demand Capacity Reservation, releasing its capacity. 

516 

517 Stops On-Demand charges for the reserved capacity. Only ODCRs can be 

518 cancelled; a Capacity Block runs for its fixed term. Instances already 

519 running against the reservation are not terminated. 

520 

521 Args: 

522 reservation_id: Capacity Reservation ID (cr-xxx) to cancel. 

523 region: AWS region where the reservation exists. 

524 dry_run: If True, validate the cancellation without cancelling. 

525 """ 

526 args = ["capacity", "cancel-reservation", "-o", reservation_id, "-r", region, "-y"] 

527 if dry_run: 

528 args.append("--dry-run") 

529 return cli_runner._run_cli(*args)