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

234 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-10-28 16:19 +0000

1"""Infrastructure stack management MCP tools.""" 

2 

3from __future__ import annotations 

4 

5import asyncio 

6from typing import Any 

7 

8import cli_runner 

9from audit import audit_logged 

10 

11# FastMCP's Progress / Context dependencies are optional from this 

12# module's perspective — when ``fastmcp[tasks]`` is reachable they 

13# inject real instances per call; otherwise the gated long-running 

14# tools still register but rely on caller-provided fakes (the test path). 

15from fastmcp.server.dependencies import CurrentContext, Progress 

16 

17# TaskConfig opts the gated stack-lifecycle tools into the MCP tasks 

18# extension (SEP-2663, registered in gco_mcp/server.py) with 

19# ``mode="optional"`` — clients that support the task protocol receive a 

20# task ID immediately and poll for progress, while clients without 

21# task-protocol support fall back to inline execution with progress 

22# streamed through FastMCP's Progress dependency. Required-mode would lock 

23# out clients that don't speak the task protocol (e.g. the GCO MCP 

24# orchestrator's ``call_tool`` proxy), and these tools are useful enough 

25# that the inline fallback is worth keeping. 

26from fastmcp.utilities.tasks import TaskConfig 

27from feature_flags import ( 

28 FLAG_CONFIG_MANAGEMENT, 

29 FLAG_INFRASTRUCTURE_DEPLOY, 

30 FLAG_INFRASTRUCTURE_DESTROY, 

31 is_enabled, 

32) 

33from server import mcp 

34 

35from tools._long_task import _run_long_task 

36 

37_TASK_CONFIG_OPTIONAL = TaskConfig(mode="optional") 

38 

39 

40def _expected_stack_count_for_all() -> int | None: 

41 """Return the number of stacks ``deploy-all`` / ``destroy-all`` will touch. 

42 

43 Reads ``cdk.json``'s ``context.deployment_regions`` and counts the 

44 fixed-position stacks (gco-global, gco-api-gateway, gco-monitoring) 

45 plus one per regional region. Returns ``None`` when the config is 

46 unreadable or empty so the caller falls back to indeterminate 

47 progress instead of an inaccurate total. 

48 

49 The count drives ``progress.set_total(...)`` so MCP clients render 

50 a real percentage during a multi-stack deploy or destroy. 

51 """ 

52 try: 

53 from cli.config import _load_cdk_json 

54 except Exception: # best-effort 

55 return None 

56 try: 

57 cdk_regions = _load_cdk_json() 

58 except Exception: # best-effort 

59 return None 

60 # _load_cdk_json only ever returns a dict (it falls back to ``{}`` when 

61 # the file is missing or the block is not a mapping), so a shape check 

62 # here would be dead code; the keys are what need checking. 

63 if "regional" not in cdk_regions: 

64 return None 

65 regional = cdk_regions["regional"] 

66 if not isinstance(regional, list): 

67 return None 

68 # Three fixed stacks (global / api-gateway / monitoring) plus one 

69 # per regional region. Analytics is opt-in and omitted from the 

70 # baseline count — when enabled it adds one more stack but 

71 # under-reporting is preferable to over-reporting (the progress 

72 # bar rolls over rather than stopping at 95 %). 

73 return 3 + len(regional) 

74 

75 

76@mcp.tool(tags={"safe", "stacks"}) 

77@audit_logged 

78def list_stacks() -> str: 

79 """List all GCO CDK stacks.""" 

80 return cli_runner._run_cli("stacks", "list") 

81 

82 

83@mcp.tool(tags={"safe", "stacks"}) 

84@audit_logged 

85def stack_status(stack_name: str, region: str) -> str: 

86 """Get detailed status of a CloudFormation stack. 

87 

88 Args: 

89 stack_name: Stack name (e.g. gco-us-east-1). 

90 region: AWS region. 

91 """ 

92 return cli_runner._run_cli("stacks", "status", stack_name, "-r", region) 

93 

94 

95@mcp.tool(tags={"low-risk", "stacks"}) 

96@audit_logged 

97def setup_cluster_access(cluster: str | None = None, region: str | None = None) -> str: 

98 """Configure kubectl access to a GCO EKS cluster. 

99 

100 Updates kubeconfig, creates an EKS access entry for your IAM principal, 

101 and associates the cluster admin policy. Handles assumed roles automatically. 

102 

103 Args: 

104 cluster: Cluster name (default: <project_name>-{region}). 

105 region: AWS region (default: first deployment region from cdk.json). 

106 """ 

107 args = ["stacks", "access"] 

108 if cluster: 

109 args.extend(["-c", cluster]) 

110 if region: 

111 args.extend(["-r", region]) 

112 return cli_runner._run_cli(*args) 

113 

114 

115@mcp.tool(tags={"safe", "stacks"}) 

116@audit_logged 

117def fsx_status() -> str: 

118 """Check FSx for Lustre configuration status.""" 

119 return cli_runner._run_cli("stacks", "fsx", "status") 

120 

121 

122# ============================================================================= 

123# Read-only inspection tools (async) 

124# ============================================================================= 

125 

126 

127@mcp.tool(tags={"safe", "stacks"}) 

128@audit_logged 

129async def stack_diff(stack_name: str | None = None) -> str: 

130 """`gco stacks diff` — show CloudFormation diff for a stack. 

131 

132 Args: 

133 stack_name: Stack to diff. If omitted, diffs all stacks. 

134 """ 

135 args = ["stacks", "diff"] 

136 if stack_name: 

137 args.append(stack_name) 

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

139 

140 

141@mcp.tool(tags={"safe", "stacks"}) 

142@audit_logged 

143async def stack_outputs(stack_name: str, region: str) -> str: 

144 """`gco stacks outputs` — fetch CloudFormation outputs for a stack. 

145 

146 Args: 

147 stack_name: Stack name (e.g. gco-us-east-1). 

148 region: AWS region. 

149 """ 

150 return await asyncio.to_thread( 

151 cli_runner._run_cli, "stacks", "outputs", stack_name, "-r", region 

152 ) 

153 

154 

155@mcp.tool(tags={"safe", "stacks"}) 

156@audit_logged 

157async def stack_synth(stack_name: str | None = None, quiet: bool = True) -> str: 

158 """`gco stacks synth` — synthesize CloudFormation templates from CDK. 

159 

160 Args: 

161 stack_name: Stack to synthesize. If omitted, synthesizes all stacks. 

162 quiet: When True, pass ``--quiet`` to suppress verbose CDK output. 

163 """ 

164 args = ["stacks", "synth"] 

165 if stack_name: 

166 args.append(stack_name) 

167 if quiet: 

168 args.append("--quiet") 

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

170 

171 

172@mcp.tool(tags={"safe", "stacks"}) 

173@audit_logged 

174async def addons_status(region: str | None = None, all_regions: bool = False) -> str: 

175 """`gco stacks addons status` — show per-chart Helm add-on status from SSM. 

176 

177 Args: 

178 region: Region to inspect. Omit for the first deployment region. 

179 all_regions: Inspect every configured deployment region. 

180 """ 

181 args = ["stacks", "addons", "status"] 

182 if all_regions: 

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

184 elif region: 

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

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

187 

188 

189@mcp.tool(tags={"safe", "stacks"}) 

190@audit_logged 

191async def valkey_status() -> str: 

192 """`gco stacks valkey status` — show Valkey cache stack status.""" 

193 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "valkey", "status") 

194 

195 

196@mcp.tool(tags={"safe", "stacks"}) 

197@audit_logged 

198async def aurora_status() -> str: 

199 """`gco stacks aurora status` — show Aurora database stack status.""" 

200 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "aurora", "status") 

201 

202 

203@mcp.tool(tags={"safe", "stacks"}) 

204@audit_logged 

205async def eks_capabilities_status(region: str | None = None, all_regions: bool = False) -> str: 

206 """`gco stacks capabilities status` — configured vs. live EKS Capabilities. 

207 

208 Reports, per AWS-managed capability type (ACK, kro), whether cdk.json 

209 ``eks_capabilities`` enables it for the region, whether it is attached to 

210 the cluster, its status/version, and any drift between configuration and 

211 the cluster. 

212 

213 Args: 

214 region: Region to inspect. Omit for the first deployment region. 

215 all_regions: Inspect every configured deployment region. 

216 """ 

217 args = ["stacks", "capabilities", "status"] 

218 if all_regions: 

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

220 elif region: 

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

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

223 

224 

225# ============================================================================= 

226# Mutating cdk.json toggles (low-risk) 

227# ============================================================================= 

228 

229 

230@mcp.tool(tags={"low-risk", "stacks"}) 

231@audit_logged 

232async def enable_fsx() -> str: 

233 """`gco stacks fsx enable` — flip FSx Lustre on in cdk.json. 

234 

235 Note: this only edits the cdk.json toggle. The change does not take effect 

236 until ``gco stacks deploy-all`` runs to provision the FSx file system. 

237 """ 

238 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "fsx", "enable", "-y") 

239 

240 

241@mcp.tool(tags={"low-risk", "stacks"}) 

242@audit_logged 

243async def disable_fsx() -> str: 

244 """`gco stacks fsx disable` — flip FSx Lustre off in cdk.json. 

245 

246 Note: this only edits the cdk.json toggle. The change does not take effect 

247 until ``gco stacks deploy-all`` runs to remove the FSx file system. 

248 """ 

249 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "fsx", "disable", "-y") 

250 

251 

252@mcp.tool(tags={"low-risk", "stacks"}) 

253@audit_logged 

254async def enable_valkey() -> str: 

255 """`gco stacks valkey enable` — flip Valkey Serverless on in cdk.json. 

256 

257 Note: this only edits the cdk.json toggle. The change does not take effect 

258 until ``gco stacks deploy-all`` runs to provision the Valkey cache. 

259 """ 

260 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "valkey", "enable", "-y") 

261 

262 

263@mcp.tool(tags={"low-risk", "stacks"}) 

264@audit_logged 

265async def disable_valkey() -> str: 

266 """`gco stacks valkey disable` — flip Valkey Serverless off in cdk.json. 

267 

268 Note: this only edits the cdk.json toggle. The change does not take effect 

269 until ``gco stacks deploy-all`` runs to remove the Valkey cache. 

270 """ 

271 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "valkey", "disable", "-y") 

272 

273 

274@mcp.tool(tags={"low-risk", "stacks"}) 

275@audit_logged 

276async def enable_aurora() -> str: 

277 """`gco stacks aurora enable` — flip Aurora pgvector on in cdk.json. 

278 

279 Note: this only edits the cdk.json toggle. The change does not take effect 

280 until ``gco stacks deploy-all`` runs to provision the Aurora cluster. 

281 """ 

282 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "aurora", "enable", "-y") 

283 

284 

285@mcp.tool(tags={"low-risk", "stacks"}) 

286@audit_logged 

287async def disable_aurora() -> str: 

288 """`gco stacks aurora disable` — flip Aurora pgvector off in cdk.json. 

289 

290 Note: this only edits the cdk.json toggle. The change does not take effect 

291 until ``gco stacks deploy-all`` runs to remove the Aurora cluster. 

292 """ 

293 return await asyncio.to_thread(cli_runner._run_cli, "stacks", "aurora", "disable", "-y") 

294 

295 

296# ============================================================================= 

297# Long-running stack lifecycle tools — gated by GCO_ENABLE_INFRASTRUCTURE_DEPLOY 

298# ============================================================================= 

299# 

300# deploy_stack / deploy_all / bootstrap_cdk drive CDK lifecycle operations 

301# that exceed the short-running ``cli_runner._run_cli`` 120-second timeout. 

302# They run via ``_run_long_task`` so progress streams back through the 

303# FastMCP Progress dependency and clients can poll task status through 

304# the standard MCP task protocol. 

305 

306if is_enabled(FLAG_INFRASTRUCTURE_DEPLOY): 

307 

308 @mcp.tool(tags={"infrastructure", "stacks"}) 

309 @audit_logged 

310 async def addons_install(region: str | None = None, all_regions: bool = False) -> str: 

311 """[gated by GCO_ENABLE_INFRASTRUCTURE_DEPLOY] infrastructure mutation. 

312 

313 `gco stacks addons install` — start an idempotent Helm add-on 

314 re-convergence from the deployment input persisted in SSM. The command 

315 starts each region's installer state machine and returns immediately; 

316 inspect progress with ``addons_status``. 

317 

318 Args: 

319 region: Region to re-converge. Omit for the first deployment region. 

320 all_regions: Re-converge every configured deployment region. 

321 """ 

322 args = ["stacks", "addons", "install"] 

323 if all_regions: 

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

325 elif region: 

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

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

328 

329 @mcp.tool(tags={"infrastructure", "stacks"}, task=_TASK_CONFIG_OPTIONAL) 

330 @audit_logged 

331 async def deploy_stack( 

332 stack_name: str, 

333 yes: bool = True, 

334 outputs_file: str | None = None, 

335 tags: list[str] | None = None, 

336 *, 

337 ctx: Any = CurrentContext(), 

338 progress: Any = Progress(), 

339 ) -> str: 

340 """[gated by GCO_ENABLE_INFRASTRUCTURE_DEPLOY] long-running. 

341 

342 `gco stacks deploy` — deploy a single CDK stack to AWS. 

343 

344 Typical wall-clock: 15-30 minutes per regional stack. Clients that 

345 speak FastMCP's task protocol can receive a task ID immediately 

346 and poll `tasks://gco/{task_id}` for progress; clients that don't 

347 run the tool inline with progress streamed through the FastMCP 

348 Progress dependency. Cancellation sends SIGTERM to the running 

349 CDK process and partial CloudFormation state may remain — inspect 

350 via stack_status or the AWS console. 

351 

352 Args: 

353 stack_name: Stack to deploy (e.g. ``gco-us-east-1``). 

354 yes: Skip approval prompts (passes ``-y``). Defaults to True. 

355 outputs_file: Optional path to write stack outputs JSON. 

356 tags: Optional list of ``key=value`` tag strings applied to the stack. 

357 """ 

358 argv = [ 

359 "gco", 

360 "stacks", 

361 "deploy", 

362 stack_name, 

363 ] 

364 if yes: 

365 argv.append("-y") 

366 if outputs_file: 

367 argv += ["--outputs-file", outputs_file] 

368 for tag in tags or []: 

369 argv += ["--tag", tag] 

370 return await _run_long_task( 

371 argv, 

372 ctx=ctx, 

373 progress=progress, 

374 is_stack_op=True, 

375 total_units=1, 

376 ) 

377 

378 @mcp.tool(tags={"infrastructure", "stacks"}, task=_TASK_CONFIG_OPTIONAL) 

379 @audit_logged 

380 async def deploy_all( 

381 yes: bool = True, 

382 outputs_file: str | None = None, 

383 tags: list[str] | None = None, 

384 parallel: bool = False, 

385 max_workers: int | None = None, 

386 *, 

387 ctx: Any = CurrentContext(), 

388 progress: Any = Progress(), 

389 ) -> str: 

390 """[gated by GCO_ENABLE_INFRASTRUCTURE_DEPLOY] long-running. 

391 

392 `gco stacks deploy-all` — deploy every CDK stack in dependency order. 

393 

394 Typical wall-clock: 30-60 minutes for a fresh multi-region deploy. 

395 Clients that speak FastMCP's task protocol can receive a task ID 

396 immediately and poll `tasks://gco/{task_id}` for progress; clients 

397 that don't run the tool inline with progress streamed through the 

398 FastMCP Progress dependency. Cancellation sends SIGTERM to the 

399 running CDK process and partial CloudFormation state may remain — 

400 inspect via stack_status or the AWS console. 

401 

402 Args: 

403 yes: Skip approval prompts (passes ``-y``). Defaults to True. 

404 outputs_file: Optional path to write stack outputs JSON. 

405 tags: Optional list of ``key=value`` tag strings applied to every stack. 

406 parallel: Deploy regional stacks concurrently when True. 

407 max_workers: Cap on parallel deployments when ``parallel=True``. 

408 """ 

409 argv = [ 

410 "gco", 

411 "stacks", 

412 "deploy-all", 

413 ] 

414 if yes: 

415 argv.append("-y") 

416 if outputs_file: 

417 argv += ["--outputs-file", outputs_file] 

418 for tag in tags or []: 

419 argv += ["--tag", tag] 

420 if parallel: 

421 argv.append("--parallel") 

422 if max_workers is not None: 

423 argv += ["--max-workers", str(max_workers)] 

424 return await _run_long_task( 

425 argv, 

426 ctx=ctx, 

427 progress=progress, 

428 is_stack_op=True, 

429 total_units=_expected_stack_count_for_all(), 

430 ) 

431 

432 @mcp.tool(tags={"infrastructure", "stacks"}, task=_TASK_CONFIG_OPTIONAL) 

433 @audit_logged 

434 async def bootstrap_cdk( 

435 region: str, 

436 account: str | None = None, 

437 *, 

438 ctx: Any = CurrentContext(), 

439 progress: Any = Progress(), 

440 ) -> str: 

441 """[gated by GCO_ENABLE_INFRASTRUCTURE_DEPLOY] long-running. 

442 

443 `gco stacks bootstrap` — bootstrap CDK in an AWS account/region. 

444 

445 Typical wall-clock: 2-5 minutes. Required before any stack can be 

446 deployed to a new account/region. Clients that speak FastMCP's 

447 task protocol can receive a task ID immediately and poll 

448 `tasks://gco/{task_id}` for progress; clients that don't run the 

449 tool inline with progress streamed through the FastMCP Progress 

450 dependency. Cancellation sends SIGTERM to the running CDK process 

451 and partial CloudFormation state may remain — inspect via 

452 stack_status or the AWS console. 

453 

454 Args: 

455 region: Target AWS region. 

456 account: Optional AWS account ID. Defaults to the caller's account. 

457 """ 

458 argv = ["gco", "stacks", "bootstrap", "--region", region] 

459 if account: 

460 argv += ["--account", account] 

461 return await _run_long_task( 

462 argv, 

463 ctx=ctx, 

464 progress=progress, 

465 is_stack_op=True, 

466 total_units=1, 

467 ) 

468 

469 

470# ============================================================================= 

471# Long-running stack lifecycle tools — gated by GCO_ENABLE_INFRASTRUCTURE_DESTROY 

472# ============================================================================= 

473 

474if is_enabled(FLAG_INFRASTRUCTURE_DESTROY): 

475 

476 @mcp.tool(tags={"infrastructure", "stacks"}, task=_TASK_CONFIG_OPTIONAL) 

477 @audit_logged 

478 async def destroy_stack( 

479 stack_name: str, 

480 yes: bool = True, 

481 retain_volumes: bool = False, 

482 *, 

483 ctx: Any = CurrentContext(), 

484 progress: Any = Progress(), 

485 ) -> str: 

486 """[gated by GCO_ENABLE_INFRASTRUCTURE_DESTROY] long-running. 

487 

488 `gco stacks destroy` — destroy a single CDK stack. 

489 

490 Typical wall-clock: 5-20 minutes per stack. Clients that speak 

491 FastMCP's task protocol can receive a task ID immediately and 

492 poll `tasks://gco/{task_id}` for progress; clients that don't 

493 run the tool inline with progress streamed through the FastMCP 

494 Progress dependency. Cancellation sends SIGTERM to the running 

495 CDK process and partial CloudFormation state may remain — 

496 inspect via stack_status or the AWS console before retrying. 

497 

498 Args: 

499 stack_name: Stack to destroy (e.g. ``gco-us-east-1``). 

500 yes: Skip the confirmation prompt (passes ``-y``). Defaults to True. 

501 retain_volumes: Report the cluster's orphaned EBS volumes instead 

502 of deleting them (passes ``--retain-volumes``). Defaults to 

503 False, matching the CLI: deleting an EKS cluster does not 

504 delete the volumes its CSI driver provisioned, so they bill 

505 indefinitely with nothing able to reattach them. 

506 """ 

507 argv = ["gco", "stacks", "destroy", stack_name] 

508 if yes: 

509 argv.append("-y") 

510 if retain_volumes: 

511 argv.append("--retain-volumes") 

512 return await _run_long_task( 

513 argv, 

514 ctx=ctx, 

515 progress=progress, 

516 is_stack_op=True, 

517 total_units=1, 

518 ) 

519 

520 @mcp.tool(tags={"infrastructure", "stacks"}, task=_TASK_CONFIG_OPTIONAL) 

521 @audit_logged 

522 async def destroy_all( 

523 yes: bool = True, 

524 parallel: bool = False, 

525 max_workers: int | None = None, 

526 retain_volumes: bool = False, 

527 keep_control_plane: bool = False, 

528 *, 

529 ctx: Any = CurrentContext(), 

530 progress: Any = Progress(), 

531 ) -> str: 

532 """[gated by GCO_ENABLE_INFRASTRUCTURE_DESTROY] long-running. 

533 

534 `gco stacks destroy-all` — destroy every CDK stack in reverse dependency order, 

535 or with keep_control_plane=True only the workload tier (scale to zero Regions). 

536 

537 Typical wall-clock: 20-40 minutes for a multi-region teardown. 

538 Clients that speak FastMCP's task protocol can receive a task 

539 ID immediately and poll `tasks://gco/{task_id}` for progress; 

540 clients that don't run the tool inline with progress streamed 

541 through the FastMCP Progress dependency. Cancellation sends 

542 SIGTERM to the running CDK process and partial CloudFormation 

543 state may remain — inspect via stack_status or the AWS console 

544 before retrying. 

545 

546 Args: 

547 yes: Skip the confirmation prompt (passes ``-y``). Defaults to True. 

548 parallel: Destroy regional stacks concurrently when True. 

549 max_workers: Cap on parallel destructions when ``parallel=True``. 

550 retain_volumes: Report each cluster's orphaned EBS volumes instead 

551 of deleting them (passes ``--retain-volumes``). Defaults to 

552 False, matching the CLI. 

553 keep_control_plane: Destroy only the regional API bridges and 

554 regional stacks and leave the global, API Gateway and 

555 monitoring stacks standing (passes ``--keep-control-plane``). 

556 Regional data (EFS/FSx, Valkey, Aurora, in-cluster volumes) is 

557 still deleted. Defaults to False, matching the CLI. 

558 """ 

559 argv = ["gco", "stacks", "destroy-all"] 

560 if yes: 

561 argv.append("-y") 

562 if parallel: 

563 argv.append("--parallel") 

564 if max_workers is not None: 

565 argv += ["--max-workers", str(max_workers)] 

566 if retain_volumes: 

567 argv.append("--retain-volumes") 

568 if keep_control_plane: 

569 argv.append("--keep-control-plane") 

570 return await _run_long_task( 

571 argv, 

572 ctx=ctx, 

573 progress=progress, 

574 is_stack_op=True, 

575 total_units=_expected_stack_count_for_all(), 

576 ) 

577 

578 

579# ============================================================================= 

580# Managed deployment configuration — disabled by default. 

581# Set GCO_ENABLE_CONFIG_MANAGEMENT=true to enable. 

582# ============================================================================= 

583# These tools edit cdk.json on the MCP host through the managed-config 

584# engine (cli/managed_config.py): validated against the same rules CDK 

585# synth enforces, atomic, idempotent, and audited. They never deploy — 

586# an explicit deploy_stack / deploy_all call (separately gated by 

587# GCO_ENABLE_INFRASTRUCTURE_DEPLOY) is still required for a topology 

588# change to reach AWS. Installed (uvx/pip) servers resolve a read-only 

589# packaged cdk.json; the engine refuses those with guidance rather than 

590# half-working, so these tools are useful from a GCO checkout. 

591 

592if is_enabled(FLAG_CONFIG_MANAGEMENT): 

593 

594 @mcp.tool(tags={"safe", "stacks"}) 

595 @audit_logged 

596 def list_deployment_regions() -> str: 

597 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

598 

599 Show the deployment-region topology configured in cdk.json. 

600 

601 Reports the global/api_gateway/monitoring Regions, the workload 

602 (regional) Region list, the resolved AWS partition, and the cdk.json 

603 path backing the answer. Works on a broken configuration too — the 

604 partition_error field explains what CDK synth would reject. 

605 """ 

606 return cli_runner._run_cli("stacks", "regions", "list") 

607 

608 @mcp.tool(tags={"low-risk", "stacks"}) 

609 @audit_logged 

610 def add_deployment_region(region: str) -> str: 

611 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

612 

613 Add a workload Region to cdk.json deployment_regions.regional. 

614 

615 Config-only and idempotent: the Region must be SDK-known and share 

616 the AWS partition of the already-configured Regions; re-adding a 

617 present Region is a reported no-op. No stack is deployed — follow 

618 up with deploy_all to apply the topology (a deploy_stack of 

619 only the new regional stack skips its API bridge and the monitoring 

620 update). Existing inference endpoints keep their Region lists; add 

621 the new Region to one with add_inference_region. 

622 

623 Args: 

624 region: AWS Region name to add (e.g. us-west-2). 

625 """ 

626 return cli_runner._run_cli("stacks", "regions", "add", region, "-y") 

627 

628 @mcp.tool(tags={"low-risk", "stacks"}) 

629 @audit_logged 

630 def remove_deployment_region(region: str) -> str: 

631 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

632 

633 Remove a workload Region from cdk.json deployment_regions.regional. 

634 

635 Config-only and idempotent: the resulting list must stay valid 

636 (SDK-known, unique Regions in one AWS partition). Removing the last 

637 Region is allowed and leaves a control-plane-only topology (global, 

638 API Gateway, and monitoring stacks with no workload cluster); 

639 removing an absent Region is a reported no-op. 

640 A deployed stack for the removed Region is NOT destroyed, and once 

641 the Region leaves cdk.json it is outside the CDK app, where 

642 destroy_stack cannot reach it. Retire a deployed Region first with the 

643 procedure in docs/CUSTOMIZATION.md (Removing a Region). 

644 

645 Args: 

646 region: AWS Region name to remove (e.g. us-west-2). 

647 """ 

648 return cli_runner._run_cli("stacks", "regions", "remove", region, "-y") 

649 

650 @mcp.tool(tags={"low-risk", "stacks"}) 

651 @audit_logged 

652 def set_deployment_region(role: str, region: str) -> str: 

653 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

654 

655 Set a control-plane Region scalar in cdk.json deployment_regions. 

656 

657 Config-only and idempotent: the Region must be SDK-known and keep 

658 the whole topology (all three scalars plus the workload list) in one 

659 AWS partition. Already-deployed stacks are not moved or destroyed — 

660 the next deploy creates the stack in the new Region. 

661 

662 Args: 

663 role: Which scalar to set: "global", "api_gateway", or "monitoring". 

664 region: AWS Region name (e.g. us-east-2). 

665 """ 

666 return cli_runner._run_cli("stacks", "regions", "set", role, region, "-y") 

667 

668 @mcp.tool(tags={"low-risk", "stacks"}) 

669 @audit_logged 

670 def set_eks_endpoint_access(mode: str, cidrs: list[str] | None = None) -> str: 

671 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

672 

673 Set cdk.json eks_cluster.endpoint_access (the EKS API endpoint mode). 

674 

675 Config-only and synth-time: no stack is deployed, and `gco stacks 

676 status` reports the configured-vs-live endpoint as drift until a 

677 deploy converges it. PUBLIC_AND_PRIVATE requires an explicit CIDR 

678 allowlist — the CLI refuses to widen control-plane access without 

679 one, and an internet-open endpoint must be spelled out as 0.0.0.0/0. 

680 PRIVATE needs no CIDRs (use cluster_tunnel_command / `gco cluster 

681 tunnel` for laptop access, plus an access entry via `gco stacks 

682 access`). 

683 

684 Args: 

685 mode: "PRIVATE" or "PUBLIC_AND_PRIVATE". 

686 cidrs: CIDR allowlist entries for the public endpoint 

687 (e.g. ["203.0.113.7/32"]). Required for PUBLIC_AND_PRIVATE. 

688 """ 

689 args = ["stacks", "eks", "endpoint", "set", mode] 

690 for cidr in cidrs or []: 

691 args.extend(["--cidr", cidr]) 

692 args.append("-y") 

693 return cli_runner._run_cli(*args) 

694 

695 @mcp.tool(tags={"low-risk", "stacks"}) 

696 @audit_logged 

697 def set_mission_default_model(model_id: str) -> str: 

698 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

699 

700 Set cdk.json bedrock.mission_default_model_id (Mission sampling). 

701 

702 Config-only and idempotent. The capacity advisor and gco autopilot 

703 have their own keys (set_capacity_advisor_default_model and 

704 set_claude_code_default_model). Model and inference-profile IDs are 

705 free-form (custom profiles, marketplace models); validation mirrors 

706 the runtime reader (non-empty, no surrounding whitespace). Sibling 

707 settings (bedrock.generation_reasoning, the other model keys) are preserved; 

708 explicit --bedrock-model-id / env overrides still take precedence 

709 at run time. 

710 

711 Args: 

712 model_id: Bedrock model or inference-profile ID 

713 (e.g. us.amazon.nova-2-lite-v1:0). 

714 """ 

715 return cli_runner._run_cli("stacks", "bedrock", "set-mission-model", model_id, "-y") 

716 

717 @mcp.tool(tags={"low-risk", "stacks"}) 

718 @audit_logged 

719 def set_capacity_advisor_default_model(model_id: str) -> str: 

720 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

721 

722 Set cdk.json bedrock.capacity_advisor_default_model_id. 

723 

724 The default model for gco capacity ai-recommend and gco capacity 

725 predict. Config-only and idempotent; Mission sampling and gco 

726 autopilot have their own keys (set_mission_default_model and 

727 set_claude_code_default_model). Validation mirrors the runtime 

728 reader (non-empty, no surrounding whitespace). Sibling settings 

729 (bedrock.generation_reasoning, the other model keys) are preserved; explicit 

730 --model overrides still take precedence at run time. 

731 

732 Args: 

733 model_id: Bedrock model or inference-profile ID 

734 (e.g. us.amazon.nova-2-lite-v1:0). 

735 """ 

736 return cli_runner._run_cli( 

737 "stacks", "bedrock", "set-capacity-advisor-model", model_id, "-y" 

738 ) 

739 

740 @mcp.tool(tags={"low-risk", "stacks"}) 

741 @audit_logged 

742 def set_claude_code_default_model(model_id: str) -> str: 

743 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

744 

745 Set cdk.json bedrock.claude_code_default_model_id (autopilot model). 

746 

747 The session model gco autopilot hands to Claude Code, independent of 

748 the mission_default_model_id and capacity_advisor_default_model_id 

749 knobs consumed by Mission sampling and the capacity advisor. 

750 Config-only and idempotent; validation mirrors the runtime reader 

751 (non-empty, no surrounding whitespace). Sibling settings are 

752 preserved; explicit --model / GCO_AUTOPILOT_MODEL overrides still 

753 take precedence at launch time. 

754 

755 Args: 

756 model_id: Bedrock model or inference-profile ID 

757 (e.g. us.anthropic.claude-sonnet-4-6). 

758 """ 

759 return cli_runner._run_cli("stacks", "bedrock", "set-claude-code-model", model_id, "-y") 

760 

761 @mcp.tool(tags={"low-risk", "stacks"}) 

762 @audit_logged 

763 def set_codex_default_model(model_id: str) -> str: 

764 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

765 

766 Set cdk.json bedrock.codex_default_model_id. 

767 

768 Config-only and idempotent. The reviewed 

769 bedrock.codex.reasoning_effort sibling is preserved; review the pair 

770 together when changing model families. Explicit --model, 

771 GCO_AUTOPILOT_CODEX_MODEL, and GCO_AUTOPILOT_MODEL overrides still 

772 take precedence at launch. 

773 

774 Args: 

775 model_id: Bedrock model or inference-profile ID 

776 (e.g. global.openai.<model-id>). 

777 """ 

778 return cli_runner._run_cli("stacks", "bedrock", "set-codex-model", model_id, "-y") 

779 

780 @mcp.tool(tags={"low-risk", "stacks"}) 

781 @audit_logged 

782 def set_codex_reasoning_effort(reasoning_effort: str) -> str: 

783 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

784 

785 Set cdk.json bedrock.codex.reasoning_effort. 

786 

787 Allowed values are minimal, low, medium, high, and xhigh. The setting 

788 applies only to the canonical Codex model; explicit model overrides 

789 intentionally omit canonical reasoning. 

790 

791 Args: 

792 reasoning_effort: Reviewed canonical Codex reasoning effort. 

793 """ 

794 return cli_runner._run_cli( 

795 "stacks", 

796 "bedrock", 

797 "set-codex-reasoning-effort", 

798 reasoning_effort, 

799 "-y", 

800 ) 

801 

802 @mcp.tool(tags={"low-risk", "stacks"}) 

803 @audit_logged 

804 def set_opencode_default_model(model_id: str) -> str: 

805 """[gated by GCO_ENABLE_CONFIG_MANAGEMENT] 

806 

807 Set cdk.json bedrock.opencode_default_model_id. 

808 

809 Config-only and idempotent. OpenCode has no engine-owned reasoning 

810 sibling, so this single key is the whole default. Explicit --model, 

811 GCO_AUTOPILOT_OPENCODE_MODEL, and GCO_AUTOPILOT_MODEL overrides still 

812 take precedence at launch. 

813 

814 Args: 

815 model_id: Bedrock model or inference-profile ID 

816 (e.g. global.moonshotai.<model-id>). 

817 """ 

818 return cli_runner._run_cli("stacks", "bedrock", "set-opencode-model", model_id, "-y")