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

184 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-09-14 22:07 +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, region: str | None = None) -> str: 

24 """Describe an EC2 instance type's compute characteristics. 

25 

26 Resolved live from ec2:DescribeInstanceTypes on every call — no checked-in 

27 specification table — so a newly launched accelerator family is reported as 

28 soon as it ships. 

29 

30 Returns vCPUs/cores/threads, memory, every accelerator class (NVIDIA GPU, 

31 AWS Neuron, Inferentia, media, FPGA) with per-model counts and memory, EFA 

32 and network limits, local NVMe and EBS characteristics, placement-group 

33 support, purchase options (spot / capacity-block), and platform 

34 capabilities. GPU memory is reported as a total across devices. 

35 

36 Carries no pricing. Use check_capacity or spot_prices for cost signals. 

37 

38 DescribeInstanceTypes is region-scoped, so a type is described only where it 

39 is offered; if it is missing, try another region or recommend_region. 

40 

41 Args: 

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

43 region: Region to describe it in. Defaults to the configured region. 

44 """ 

45 args = ["capacity", "instance-info", instance_type] 

46 if region: 

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

48 return cli_runner._run_cli(*args) 

49 

50 

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

52@audit_logged 

53def recommend_capacity( 

54 instance_type: str, 

55 region: str, 

56 fault_tolerance: str = "medium", 

57) -> str: 

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

59 

60 Args: 

61 instance_type: EC2 instance type to evaluate. 

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

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

64 """ 

65 return cli_runner._run_cli( 

66 "capacity", 

67 "recommend", 

68 "-i", 

69 instance_type, 

70 "-r", 

71 region, 

72 "-f", 

73 fault_tolerance, 

74 ) 

75 

76 

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

78@audit_logged 

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

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

81 

82 Args: 

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

84 """ 

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

86 if region: 

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

88 return cli_runner._run_cli(*args) 

89 

90 

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

92@audit_logged 

93def recommend_region( 

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

95) -> str: 

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

97 

98 Args: 

99 gpu: Whether the workload requires GPUs. 

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

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

102 gpu_count: Number of GPUs required for the workload. 

103 """ 

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

105 if gpu: 

106 args.append("--gpu") 

107 if instance_type: 

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

109 if gpu_count: 

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

111 return cli_runner._run_cli(*args) 

112 

113 

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

115@audit_logged 

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

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

118 

119 Args: 

120 instance_type: EC2 instance type. 

121 region: AWS region. 

122 """ 

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

124 

125 

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

127@audit_logged 

128def ai_recommend( 

129 workload: str, 

130 instance_type: str | None = None, 

131 region: str | None = None, 

132 gpu: bool = False, 

133 min_gpus: int = 0, 

134 min_memory_gb: int = 0, 

135 fault_tolerance: str = "low", 

136 max_cost: float | None = None, 

137 model: str | None = None, 

138) -> str: 

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

140 

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

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

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

144 

145 Requires AWS credentials with bedrock:InvokeModel permission and the 

146 specified model enabled in your account. 

147 

148 Args: 

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

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

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

152 gpu: Whether the workload requires GPUs. 

153 min_gpus: Minimum number of GPUs required. 

154 min_memory_gb: Minimum GPU memory in GB. 

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

156 max_cost: Maximum acceptable cost per hour in USD. 

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

158 `cdk.json` `context.bedrock.capacity_advisor_default_model_id`, 

159 the advisor's own knob (Mission sampling has a separate 

160 `mission_default_model_id`). 

161 """ 

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

163 if instance_type: 

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

165 if region: 

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

167 if gpu: 

168 args.append("--gpu") 

169 if min_gpus > 0: 

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

171 if min_memory_gb > 0: 

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

173 if fault_tolerance != "low": 

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

175 if max_cost is not None: 

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

177 if model: 

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

179 return cli_runner._run_cli(*args) 

180 

181 

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

183@audit_logged 

184def list_reservations( 

185 instance_type: str | None = None, 

186 region: str | None = None, 

187) -> str: 

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

189 

190 Shows all active capacity reservations with utilization details. 

191 

192 Args: 

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

194 region: Filter by specific region. 

195 """ 

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

197 if instance_type: 

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

199 if region: 

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

201 return cli_runner._run_cli(*args) 

202 

203 

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

205@audit_logged 

206def reservation_check( 

207 instance_type: str, 

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

209 count: int = 1, 

210 include_blocks: bool = True, 

211 block_duration: int = 24, 

212 block_duration_days: int | None = None, 

213 earliest_start: str | None = None, 

214 latest_start: str | None = None, 

215) -> str: 

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

217 

218 Checks existing On-Demand Capacity Reservations and purchasable Capacity 

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

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

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

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

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

224 find_capacity_blocks instead. 

225 

226 Args: 

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

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

229 omit to check all deployed regions. 

230 count: Minimum number of instances needed. 

231 include_blocks: Whether to include Capacity Block offerings. 

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

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

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

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

236 """ 

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

238 for r in regions or []: 

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

240 if not include_blocks: 

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

242 if block_duration != 24: 

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

244 if block_duration_days is not None: 

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

246 if earliest_start: 

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

248 if latest_start: 

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

250 return cli_runner._run_cli(*args) 

251 

252 

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

254@audit_logged 

255def find_capacity_blocks( 

256 instance_type: str, 

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

258 count: int = 1, 

259 duration_days: int | None = None, 

260 duration_hours: int | None = None, 

261 min_duration_days: int | None = None, 

262 max_duration_days: int | None = None, 

263 min_duration_hours: int | None = None, 

264 max_duration_hours: int | None = None, 

265 earliest_start: str | None = None, 

266 latest_start: str | None = None, 

267 find_longest: bool = False, 

268) -> str: 

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

270 

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

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

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

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

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

276 

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

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

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

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

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

282 than silently returning nothing. 

283 

284 Args: 

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

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

287 count: Instances per block. 

288 duration_days / duration_hours: A single target duration. 

289 min_duration_days / max_duration_days: Duration range bounds in days. 

290 min_duration_hours / max_duration_hours: Duration range bounds in hours. 

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

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

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

294 """ 

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

296 for r in regions or []: 

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

298 if count != 1: 

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

300 if duration_days is not None: 

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

302 if duration_hours is not None: 

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

304 if min_duration_days is not None: 

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

306 if max_duration_days is not None: 

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

308 if min_duration_hours is not None: 

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

310 if max_duration_hours is not None: 

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

312 if earliest_start: 

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

314 if latest_start: 

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

316 if find_longest: 

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

318 return cli_runner._run_cli(*args) 

319 

320 

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

322@audit_logged 

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

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

325 

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

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

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

329 

330 Args: 

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

332 region: AWS region. 

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

334 """ 

335 return cli_runner._run_cli( 

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

337 ) 

338 

339 

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

341@audit_logged 

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

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

344 

345 Args: 

346 instance_type: EC2 instance type. 

347 region: AWS region. 

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

349 """ 

350 return cli_runner._run_cli( 

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

352 ) 

353 

354 

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

356@audit_logged 

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

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

359 

360 Args: 

361 instance_type: EC2 instance type. 

362 region: AWS region. 

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

364 """ 

365 return cli_runner._run_cli( 

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

367 ) 

368 

369 

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

371@audit_logged 

372def capacity_predict( 

373 instance_type: str, 

374 region: str | None = None, 

375 hours: int = 168, 

376 all_regions: bool = False, 

377) -> str: 

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

379 

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

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

382 

383 Args: 

384 instance_type: EC2 instance type. 

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

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

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

388 """ 

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

390 if all_regions: 

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

392 elif region: 

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

394 return cli_runner._run_cli(*args) 

395 

396 

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

398@audit_logged 

399def find_capacity_reservations( 

400 instance_type: str | None = None, 

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

402 count: int = 1, 

403 state: str = "active", 

404 pricing: bool = True, 

405) -> str: 

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

407 

408 The ODCR counterpart to find_capacity_blocks: it searches every requested 

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

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

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

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

413 list_reservations' plain per-region aggregation. 

414 

415 Args: 

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

417 to return every reservation. 

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

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

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

421 pricing: Enrich reservations with On-Demand pricing. 

422 """ 

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

424 if instance_type: 

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

426 for r in regions or []: 

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

428 if count != 1: 

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

430 if state != "active": 

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

432 if not pricing: 

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

434 return cli_runner._run_cli(*args) 

435 

436 

437# Capacity purchasing / creation — disabled by default. 

438# Set GCO_ENABLE_CAPACITY_PURCHASE=true to enable. 

439if is_enabled(FLAG_CAPACITY_PURCHASE): 

440 

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

442 @audit_logged 

443 def reserve_capacity( 

444 offering_id: str, 

445 region: str, 

446 dry_run: bool = False, 

447 ) -> str: 

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

449 

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

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

452 

453 Args: 

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

455 region: AWS region where the offering exists. 

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

457 """ 

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

459 if dry_run: 

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

461 return cli_runner._run_cli(*args) 

462 

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

464 @audit_logged 

465 def create_reservation( 

466 instance_type: str, 

467 region: str, 

468 availability_zone: str, 

469 count: int = 1, 

470 platform: str = "Linux/UNIX", 

471 tenancy: str = "default", 

472 match_criteria: str = "open", 

473 end_date: str | None = None, 

474 ebs_optimized: bool = False, 

475 dry_run: bool = False, 

476 ) -> str: 

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

478 

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

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

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

482 

483 Args: 

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

485 region: AWS region. 

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

487 count: Number of instances to reserve. 

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

489 tenancy: "default" or "dedicated". 

490 match_criteria: "open" or "targeted". 

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

492 ebs_optimized: Reserve EBS-optimized capacity. 

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

494 """ 

495 args = [ 

496 "capacity", 

497 "create-reservation", 

498 "-i", 

499 instance_type, 

500 "-r", 

501 region, 

502 "-z", 

503 availability_zone, 

504 "-c", 

505 str(count), 

506 ] 

507 if platform != "Linux/UNIX": 

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

509 if tenancy != "default": 

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

511 if match_criteria != "open": 

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

513 if end_date: 

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

515 if ebs_optimized: 

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

517 if dry_run: 

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

519 return cli_runner._run_cli(*args) 

520 

521 

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

523# Set GCO_ENABLE_DESTRUCTIVE_OPERATIONS=true to enable. 

524if is_enabled(FLAG_DESTRUCTIVE_OPERATIONS): 

525 

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

527 @audit_logged 

528 def cancel_reservation( 

529 reservation_id: str, 

530 region: str, 

531 dry_run: bool = False, 

532 ) -> str: 

533 """[gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS] destructive. 

534 

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

536 

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

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

539 running against the reservation are not terminated. 

540 

541 Args: 

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

543 region: AWS region where the reservation exists. 

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

545 """ 

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

547 if dry_run: 

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

549 return cli_runner._run_cli(*args)