Coverage for gco_mcp/resources/docs.py: 93.61%
371 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-30 21:22 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-30 21:22 +0000
1"""Documentation resources (docs:// scheme) for the GCO MCP server."""
3import re
4from pathlib import Path
6from cli_runner import PROJECT_ROOT # runtime-resolved checkout root (uvx-safe)
7from server import mcp
9DOCS_DIR = PROJECT_ROOT / "docs"
10EXAMPLES_DIR = PROJECT_ROOT / "examples"
11ADR_DIR = DOCS_DIR / "adr"
13# ---------------------------------------------------------------------------
14# Example metadata — used by both the index and the per-example resource to
15# give the LLM rich context about what each manifest does and how to adapt it.
16# ---------------------------------------------------------------------------
18EXAMPLE_METADATA: dict[str, dict[str, str | list[str]]] = {
19 "simple-job": {
20 "category": "Jobs & Training",
21 "summary": "Basic Kubernetes Job that runs a command and completes. Start here to verify your cluster.",
22 "gpu": "no",
23 "opt_in": "",
24 "submission": "gco jobs submit-sqs examples/simple-job.yaml --region us-east-1",
25 "keywords": ["simple", "hello", "starter", "basic", "smoke test"],
26 "instance_types": [],
27 "use_cases": [
28 "verify cluster setup",
29 "smoke test a new region",
30 "minimal job example",
31 ],
32 "related": ["gpu-job", "sqs-job-submission"],
33 },
34 "gpu-job": {
35 "category": "Jobs & Training",
36 "summary": "Requests GPU resources and runs on GPU-enabled nodes.",
37 "gpu": "NVIDIA",
38 "opt_in": "",
39 "submission": "gco jobs submit-sqs examples/gpu-job.yaml --region us-east-1",
40 "keywords": [
41 "gpu",
42 "nvidia",
43 "cuda",
44 "single gpu",
45 "nvidia.com/gpu",
46 "g5",
47 "g6",
48 "g4dn",
49 "tolerations",
50 ],
51 "instance_types": ["g5.xlarge", "g6.xlarge", "g4dn.xlarge"],
52 "use_cases": [
53 "run a single GPU workload",
54 "test GPU node provisioning",
55 "smoke test CUDA",
56 ],
57 "related": ["multi-gpu-training", "gpu-timeslicing-job", "simple-job"],
58 },
59 "gpu-timeslicing-job": {
60 "category": "Jobs & Training",
61 "summary": "Fractional GPU via NVIDIA time-slicing — multiple pods share one physical GPU.",
62 "gpu": "NVIDIA (time-sliced)",
63 "opt_in": "NVIDIA device plugin time-slicing ConfigMap",
64 "submission": "kubectl apply -f examples/gpu-timeslicing-job.yaml",
65 "keywords": ["gpu", "timeslicing", "fractional", "shared gpu", "nvidia"],
66 "instance_types": ["g5.xlarge", "g6.xlarge"],
67 "use_cases": [
68 "share one GPU between multiple pods",
69 "lower cost for small inference workloads",
70 ],
71 "related": ["gpu-job", "multi-gpu-training"],
72 },
73 "multi-gpu-training": {
74 "category": "Jobs & Training",
75 "summary": "PyTorch DistributedDataParallel (DDP) across multiple GPUs with indexed pods and headless service.",
76 "gpu": "NVIDIA",
77 "opt_in": "",
78 "submission": "kubectl apply -f examples/multi-gpu-training.yaml",
79 "keywords": [
80 "ddp",
81 "distributed",
82 "pytorch",
83 "multi gpu",
84 "training",
85 "torchrun",
86 "nccl",
87 "indexed pods",
88 "headless service",
89 ],
90 "instance_types": ["g5.12xlarge", "g6.12xlarge", "p4d.24xlarge"],
91 "use_cases": [
92 "distributed PyTorch DDP training",
93 "scale a training job across multiple GPUs",
94 ],
95 "related": ["gpu-job", "efa-distributed-training", "megatrain-sft-job"],
96 },
97 "efa-distributed-training": {
98 "category": "Jobs & Training",
99 "summary": "Elastic Fabric Adapter (EFA) for high-bandwidth inter-node communication (up to 3.2 Tbps on P5, 28.8 Tbps on P6e). For p4d/p5/p5e/p5en/p6-b200/p6-b300/p6e-gb200/trn instances.",
100 "gpu": "NVIDIA + EFA",
101 "opt_in": "",
102 "submission": "gco jobs submit-direct examples/efa-distributed-training.yaml -r us-east-1",
103 "keywords": ["efa", "elastic fabric adapter", "distributed", "nccl", "high bandwidth"],
104 "instance_types": [
105 "p4d.24xlarge",
106 "p5.48xlarge",
107 "trn1.32xlarge",
108 "trn2.48xlarge",
109 ],
110 "use_cases": [
111 "multi-node distributed training over EFA",
112 "high-bandwidth NCCL all-reduce",
113 "large-scale model pretraining",
114 ],
115 "related": ["multi-gpu-training", "trainium-job", "megatrain-sft-job"],
116 },
117 "megatrain-sft-job": {
118 "category": "Jobs & Training",
119 "summary": "SFT fine-tuning of Qwen2.5-1.5B on a single GPU using MegaTrain. Downloads weights to EFS.",
120 "gpu": "NVIDIA",
121 "opt_in": "",
122 "submission": "gco jobs submit-direct examples/megatrain-sft-job.yaml -r us-east-1",
123 "keywords": ["sft", "fine-tuning", "qwen", "megatrain", "llm training"],
124 "instance_types": ["g5.xlarge", "g5.12xlarge", "g6.xlarge"],
125 "use_cases": [
126 "supervised fine-tuning of an LLM",
127 "single-GPU SFT on Qwen",
128 "fine-tune a small open-source model",
129 ],
130 "related": ["multi-gpu-training", "model-download-job", "efa-distributed-training"],
131 },
132 "model-download-job": {
133 "category": "Jobs & Training",
134 "summary": "Pre-downloads HuggingFace model weights to shared EFS for inference endpoints.",
135 "gpu": "no",
136 "opt_in": "",
137 "submission": "kubectl apply -f examples/model-download-job.yaml",
138 "keywords": ["huggingface", "download", "weights", "model cache", "efs"],
139 "instance_types": [],
140 "use_cases": [
141 "stage HuggingFace weights on EFS",
142 "warm a model cache before serving",
143 ],
144 "related": ["megatrain-sft-job", "inference-vllm", "efs-output-job"],
145 },
146 "sqs-job-submission": {
147 "category": "Jobs & Training",
148 "summary": "Demonstrates SQS-based submission (recommended). Contains CPU and GPU job examples.",
149 "gpu": "optional",
150 "opt_in": "",
151 "submission": "gco jobs submit-sqs examples/sqs-job-submission.yaml --region us-east-1",
152 "keywords": ["sqs", "submission", "queue", "broker"],
153 "instance_types": [],
154 "use_cases": [
155 "submit jobs through the SQS queue",
156 "queue-based job submission pattern",
157 ],
158 "related": ["simple-job", "gpu-job", "keda-scaled-job"],
159 },
160 "trainium-job": {
161 "category": "Accelerator Jobs",
162 "summary": "AWS Trainium instance with Neuron SDK. Lower cost than GPU for training.",
163 "gpu": "Trainium",
164 "opt_in": "",
165 "submission": "gco jobs submit examples/trainium-job.yaml --region us-east-1",
166 "keywords": ["trainium", "neuron", "trn1", "trn2", "training accelerator"],
167 "instance_types": ["trn1.2xlarge", "trn1.32xlarge", "trn2.48xlarge"],
168 "use_cases": [
169 "lower-cost training on AWS silicon",
170 "train with the Neuron SDK",
171 ],
172 "related": ["inferentia-job", "efa-distributed-training"],
173 },
174 "inferentia-job": {
175 "category": "Accelerator Jobs",
176 "summary": "AWS Inferentia2 with Neuron SDK. Optimized for low-cost, high-throughput inference.",
177 "gpu": "Inferentia",
178 "opt_in": "",
179 "submission": "gco jobs submit examples/inferentia-job.yaml --region us-east-1",
180 "keywords": ["inferentia", "neuron", "inf2", "inference accelerator"],
181 "instance_types": ["inf2.xlarge", "inf2.8xlarge", "inf2.24xlarge", "inf2.48xlarge"],
182 "use_cases": [
183 "low-cost inference on AWS silicon",
184 "high-throughput batch inference",
185 ],
186 "related": ["trainium-job", "inference-vllm"],
187 },
188 "inference-vllm": {
189 "category": "Inference Serving",
190 "summary": "vLLM OpenAI-compatible LLM serving with PagedAttention.",
191 "gpu": "NVIDIA",
192 "opt_in": "",
193 "submission": "gco inference deploy my-llm -i vllm/vllm-openai:v0.24.0 --gpu-count 1",
194 "keywords": [
195 "vllm",
196 "openai",
197 "openai-compatible",
198 "llm serving",
199 "pagedattention",
200 "inference",
201 "completions",
202 "chat completions",
203 "v1/chat/completions",
204 "model server",
205 "llama",
206 "qwen",
207 "mistral",
208 ],
209 "instance_types": ["g5.xlarge", "g5.12xlarge", "g6.xlarge"],
210 "use_cases": [
211 "serve an LLM with an OpenAI-compatible API",
212 "high-throughput LLM inference",
213 "deploy a chat completions endpoint",
214 ],
215 "related": ["inference-tgi", "inference-sglang", "inference-triton", "model-download-job"],
216 },
217 "inference-tgi": {
218 "category": "Inference Serving",
219 "summary": "HuggingFace Text Generation Inference — optimized transformer serving.",
220 "gpu": "NVIDIA",
221 "opt_in": "",
222 "submission": "gco jobs submit-direct examples/inference-tgi.yaml -r us-east-1",
223 "keywords": ["tgi", "huggingface", "text generation", "llm serving"],
224 "instance_types": ["g5.xlarge", "g5.12xlarge", "g6.xlarge"],
225 "use_cases": [
226 "serve HuggingFace LLMs with TGI",
227 "transformer text-generation endpoint",
228 ],
229 "related": ["inference-vllm", "inference-sglang", "inference-torchserve"],
230 },
231 "inference-triton": {
232 "category": "Inference Serving",
233 "summary": "NVIDIA Triton Inference Server — multi-framework (PyTorch, TensorFlow, ONNX).",
234 "gpu": "NVIDIA",
235 "opt_in": "",
236 "submission": "gco jobs submit-direct examples/inference-triton.yaml -r us-east-1",
237 "keywords": ["triton", "nvidia", "multi-framework", "onnx", "tensorflow", "inference"],
238 "instance_types": ["g5.xlarge", "g6.xlarge"],
239 "use_cases": [
240 "multi-framework inference serving",
241 "serve ONNX or TensorFlow models",
242 ],
243 "related": ["inference-vllm", "inference-torchserve"],
244 },
245 "inference-torchserve": {
246 "category": "Inference Serving",
247 "summary": "PyTorch TorchServe model serving.",
248 "gpu": "NVIDIA",
249 "opt_in": "",
250 "submission": "gco jobs submit-direct examples/inference-torchserve.yaml -r us-east-1",
251 "keywords": ["torchserve", "pytorch", "model serving"],
252 "instance_types": ["g5.xlarge", "g6.xlarge"],
253 "use_cases": [
254 "serve a PyTorch model with TorchServe",
255 ],
256 "related": ["inference-triton", "inference-vllm"],
257 },
258 "inference-sglang": {
259 "category": "Inference Serving",
260 "summary": "SGLang high-throughput serving with RadixAttention for prefix caching.",
261 "gpu": "NVIDIA",
262 "opt_in": "",
263 "submission": "gco jobs submit-direct examples/inference-sglang.yaml -r us-east-1",
264 "keywords": ["sglang", "radixattention", "prefix caching", "llm serving"],
265 "instance_types": ["g5.xlarge", "g5.12xlarge"],
266 "use_cases": [
267 "high-throughput LLM serving with prefix caching",
268 "serve LLMs with structured output",
269 ],
270 "related": ["inference-vllm", "inference-tgi"],
271 },
272 "efs-output-job": {
273 "category": "Storage & Persistence",
274 "summary": "Writes output to shared EFS storage. Results persist after pod termination.",
275 "gpu": "no",
276 "opt_in": "",
277 "submission": "gco jobs submit-direct examples/efs-output-job.yaml --region us-east-1 -n gco-jobs",
278 "keywords": ["efs", "shared storage", "persistent", "output"],
279 "instance_types": [],
280 "use_cases": [
281 "persist job output to EFS",
282 "share data between pods via EFS",
283 ],
284 "related": ["fsx-lustre-job", "model-download-job", "cluster-shared-bucket-upload-job"],
285 },
286 "fsx-lustre-job": {
287 "category": "Storage & Persistence",
288 "summary": "FSx for Lustre high-performance parallel storage (1000+ GB/s throughput).",
289 "gpu": "no",
290 "opt_in": "FSx (gco stacks fsx enable -y)",
291 "submission": "gco jobs submit-direct examples/fsx-lustre-job.yaml --region us-east-1 -n gco-jobs",
292 "keywords": ["fsx", "lustre", "parallel storage", "high throughput", "hpc"],
293 "instance_types": [],
294 "use_cases": [
295 "high-throughput parallel storage for training",
296 "stream large datasets to GPU nodes",
297 ],
298 "related": ["efs-output-job", "multi-gpu-training", "efa-distributed-training"],
299 },
300 "valkey-cache-job": {
301 "category": "Caching & Databases",
302 "summary": "Valkey Serverless cache for K/V caching, prompt caching, session state, feature stores.",
303 "gpu": "no",
304 "opt_in": 'Valkey ("valkey": {"enabled": true} in cdk.json)',
305 "submission": "gco jobs submit-direct examples/valkey-cache-job.yaml -r us-east-1",
306 "keywords": ["valkey", "redis", "cache", "kv store", "session state"],
307 "instance_types": [],
308 "use_cases": [
309 "cache prompts or session state",
310 "use Valkey from a job",
311 "feature store backed by Valkey",
312 ],
313 "related": ["aurora-pgvector-job"],
314 },
315 "aurora-pgvector-job": {
316 "category": "Caching & Databases",
317 "summary": "Aurora Serverless v2 PostgreSQL with pgvector for RAG and semantic search.",
318 "gpu": "no",
319 "opt_in": 'Aurora ("aurora_pgvector": {"enabled": true} in cdk.json)',
320 "submission": "gco jobs submit-direct examples/aurora-pgvector-job.yaml -r us-east-1",
321 "keywords": ["aurora", "pgvector", "postgres", "rag", "vector database", "embeddings"],
322 "instance_types": [],
323 "use_cases": [
324 "RAG with pgvector",
325 "semantic search backed by Postgres",
326 "store embeddings in Aurora",
327 ],
328 "related": ["valkey-cache-job", "analytics-database-export-job"],
329 },
330 "cluster-shared-bucket-upload-job": {
331 "category": "Storage & Persistence",
332 "summary": "Uploads a file to the always-on Cluster_Shared_Bucket using the gco-cluster-shared-bucket ConfigMap via envFrom. Works with analytics disabled.",
333 "gpu": "no",
334 "opt_in": "",
335 "submission": "gco jobs submit-direct examples/cluster-shared-bucket-upload-job.yaml -r us-east-1",
336 "keywords": ["s3", "shared bucket", "upload", "configmap"],
337 "instance_types": [],
338 "use_cases": [
339 "upload artifacts to the shared S3 bucket",
340 "share files across regions via S3",
341 ],
342 "related": ["efs-output-job", "analytics-s3-upload-job"],
343 },
344 "analytics-s3-upload-job": {
345 "category": "Analytics",
346 "summary": "Publishes a dataset snapshot plus schema manifest to Cluster_Shared_Bucket under analytics-data/ so a SageMaker Studio notebook can read it.",
347 "gpu": "no",
348 "opt_in": 'Analytics ("analytics_environment": {"enabled": true} in cdk.json)',
349 "submission": "gco jobs submit-direct examples/analytics-s3-upload-job.yaml -r us-east-1",
350 "keywords": ["analytics", "s3", "sagemaker", "dataset", "schema"],
351 "instance_types": [],
352 "use_cases": [
353 "publish a dataset for a SageMaker Studio notebook",
354 "share an analytics snapshot via S3",
355 ],
356 "related": ["analytics-database-export-job", "cluster-shared-bucket-upload-job"],
357 },
358 "analytics-database-export-job": {
359 "category": "Analytics",
360 "summary": "Exports rows from the regional Aurora pgvector cluster to Cluster_Shared_Bucket as CSV for a SageMaker Studio notebook to analyse.",
361 "gpu": "no",
362 "opt_in": 'Aurora + Analytics ("aurora_pgvector.enabled" and "analytics_environment.enabled" in cdk.json)',
363 "submission": "gco jobs submit-direct examples/analytics-database-export-job.yaml -r us-east-1",
364 "keywords": ["analytics", "aurora", "csv", "export", "sagemaker"],
365 "instance_types": [],
366 "use_cases": [
367 "export Aurora rows to S3 as CSV",
368 "feed a SageMaker Studio notebook from Postgres",
369 ],
370 "related": ["aurora-pgvector-job", "analytics-s3-upload-job"],
371 },
372 "volcano-gang-job": {
373 "category": "Schedulers",
374 "summary": "Volcano gang scheduling — all pods scheduled together or none. Master + workers topology.",
375 "gpu": "no",
376 "opt_in": "",
377 "submission": "kubectl apply -f examples/volcano-gang-job.yaml",
378 "keywords": ["volcano", "gang scheduling", "batch", "scheduler"],
379 "instance_types": [],
380 "use_cases": [
381 "schedule all pods at once or none",
382 "MPI-style master + workers topology",
383 ],
384 "related": ["kueue-job", "yunikorn-job", "slurm-cluster-job"],
385 },
386 "kueue-job": {
387 "category": "Schedulers",
388 "summary": "Kueue job queueing with ClusterQueue, LocalQueue, ResourceFlavors, and fair-sharing.",
389 "gpu": "optional",
390 "opt_in": "",
391 "submission": "kubectl apply -f examples/kueue-job.yaml",
392 "keywords": ["kueue", "queueing", "fair sharing", "scheduler", "clusterqueue"],
393 "instance_types": [],
394 "use_cases": [
395 "queue jobs with quotas and fair sharing",
396 "multi-tenant batch scheduling",
397 ],
398 "related": ["volcano-gang-job", "yunikorn-job"],
399 },
400 "yunikorn-job": {
401 "category": "Schedulers",
402 "summary": "Apache YuniKorn app-aware scheduling with hierarchical queues and gang scheduling.",
403 "gpu": "no",
404 "opt_in": 'YuniKorn ("helm": {"yunikorn": {"enabled": true}} in cdk.json)',
405 "submission": "kubectl apply -f examples/yunikorn-job.yaml",
406 "keywords": ["yunikorn", "scheduler", "hierarchical queues", "gang scheduling"],
407 "instance_types": [],
408 "use_cases": [
409 "app-aware scheduling with hierarchical queues",
410 "YuniKorn-style gang scheduling",
411 ],
412 "related": ["kueue-job", "volcano-gang-job"],
413 },
414 "keda-scaled-job": {
415 "category": "Schedulers",
416 "summary": "KEDA ScaledJob — custom SQS-triggered autoscaling. Template for custom consumers.",
417 "gpu": "no",
418 "opt_in": "",
419 "submission": "kubectl apply -f examples/keda-scaled-job.yaml",
420 "keywords": ["keda", "scaledjob", "autoscaling", "sqs", "event driven"],
421 "instance_types": [],
422 "use_cases": [
423 "scale jobs from SQS queue depth",
424 "event-driven job autoscaling",
425 ],
426 "related": ["sqs-job-submission"],
427 },
428 "slurm-cluster-job": {
429 "category": "Schedulers",
430 "summary": "Slinky Slurm Operator — sbatch submission on Kubernetes for HPC workloads.",
431 "gpu": "no",
432 "opt_in": 'Slurm ("helm": {"slurm": {"enabled": true}} in cdk.json)',
433 "submission": "kubectl apply -f examples/slurm-cluster-job.yaml",
434 "keywords": ["slurm", "hpc", "sbatch", "slinky"],
435 "instance_types": [],
436 "use_cases": [
437 "submit sbatch jobs on Kubernetes",
438 "run HPC workloads with Slurm",
439 ],
440 "related": ["volcano-gang-job"],
441 },
442 "ray-cluster": {
443 "category": "Distributed Computing",
444 "summary": "KubeRay RayCluster for distributed training, tuning, and serving. Auto-scaling workers.",
445 "gpu": "no",
446 "opt_in": "",
447 "submission": "kubectl apply -f examples/ray-cluster.yaml",
448 "keywords": ["ray", "kuberay", "distributed", "tune", "serve"],
449 "instance_types": [],
450 "use_cases": [
451 "stand up a Ray cluster on EKS",
452 "distributed training and tuning with Ray",
453 ],
454 "related": ["multi-gpu-training", "pipeline-dag"],
455 },
456 "pipeline-dag": {
457 "category": "DAG Pipelines",
458 "summary": "Multi-step pipeline with dependency ordering. Preprocess → Train via shared EFS.",
459 "gpu": "no",
460 "opt_in": "",
461 "submission": "gco dag run examples/pipeline-dag.yaml -r us-east-1",
462 "keywords": ["dag", "pipeline", "workflow", "dependencies"],
463 "instance_types": [],
464 "use_cases": [
465 "run a multi-step ML pipeline",
466 "chain preprocess and train jobs",
467 ],
468 "related": ["dag-step-preprocess", "dag-step-train", "ray-cluster"],
469 },
470 "dag-step-preprocess": {
471 "category": "DAG Pipelines",
472 "summary": "DAG step 1: generates training data on shared EFS.",
473 "gpu": "no",
474 "opt_in": "",
475 "submission": "(used by pipeline-dag.yaml)",
476 "keywords": ["dag", "preprocess", "step", "pipeline"],
477 "instance_types": [],
478 "use_cases": [
479 "preprocessing step of a pipeline",
480 "generate training data for a downstream step",
481 ],
482 "related": ["pipeline-dag", "dag-step-train"],
483 },
484 "dag-step-train": {
485 "category": "DAG Pipelines",
486 "summary": "DAG step 2: reads preprocess output, trains model, writes artifacts to EFS.",
487 "gpu": "no",
488 "opt_in": "",
489 "submission": "(used by pipeline-dag.yaml)",
490 "keywords": ["dag", "train", "step", "pipeline"],
491 "instance_types": [],
492 "use_cases": [
493 "training step of a pipeline",
494 "consume preprocess output and train",
495 ],
496 "related": ["pipeline-dag", "dag-step-preprocess"],
497 },
498}
501# ---------------------------------------------------------------------------
502# Doc metadata — used by ``find_docs`` and the docs:// discovery resources to
503# describe every markdown file under ``docs/``. Indexed by basename without
504# extension (e.g. ``ARCHITECTURE``). The vocabulary in ``topics`` is kept
505# small and consistent so topic-based search across docs stays predictable.
506# ---------------------------------------------------------------------------
508DOC_METADATA: dict[str, dict[str, str | list[str]]] = {
509 "ANALYTICS": {
510 "summary": "Optional SageMaker Studio + EMR Serverless analytics environment, enabled via a single cdk.json toggle.",
511 "topics": ["analytics", "storage", "customization", "gpu"],
512 "keywords": [
513 "sagemaker studio",
514 "emr serverless",
515 "cognito",
516 "data science",
517 "notebook",
518 "presigned url",
519 "studio domain",
520 "analytics environment",
521 "user pool",
522 ],
523 "related": ["CLUSTER_SHARED_BUCKET", "CUSTOMIZATION"],
524 },
525 "API": {
526 "summary": (
527 "Reference for every GCO HTTP surface: the control plane (manifests, "
528 "jobs, queue, templates, webhooks, cost), cross-region aggregation, "
529 "inference, health and observability, plus which paths each API "
530 "Gateway exposes and the cluster-internal surfaces."
531 ),
532 "topics": ["api", "cli", "jobs", "inference", "webhooks", "templates"],
533 "keywords": [
534 "rest",
535 "manifest processor",
536 "inference proxy",
537 "health monitor",
538 "endpoints",
539 "auth",
540 "hmac request envelope",
541 "api gateway",
542 "sigv4",
543 "openapi",
544 "submit job",
545 "api surface",
546 ],
547 "related": ["CLI", "ARCHITECTURE"],
548 },
549 "ARCHITECTURE": {
550 "summary": "Deep dive into the multi-region infrastructure, security layers, data flow, and scale characteristics.",
551 "topics": [
552 "architecture",
553 "concepts",
554 "security",
555 "multi-region",
556 "eks",
557 "capacity",
558 "inference",
559 "gpu",
560 "monitoring",
561 "deployment",
562 "nodepools",
563 "storage",
564 "images",
565 "cost",
566 "networking",
567 ],
568 "keywords": [
569 "multi-region",
570 "eks",
571 "vpc",
572 "global accelerator",
573 "data flow",
574 "control plane",
575 "data plane",
576 "regional stack",
577 "global stack",
578 "iam",
579 "kms",
580 "high level design",
581 "blast radius",
582 ],
583 "related": ["CONCEPTS", "CUSTOMIZATION", "API"],
584 },
585 "CLI": {
586 "summary": "Complete command-line interface reference for the gco CLI across jobs, queues, stacks, capacity, inference, and more.",
587 "topics": [
588 "cli",
589 "api",
590 "jobs",
591 "capacity",
592 "inference",
593 "cost",
594 "gpu",
595 "multi-region",
596 "images",
597 "nodepools",
598 "deployment",
599 ],
600 "keywords": [
601 "gco",
602 "command-line",
603 "subcommand",
604 "submit job",
605 "stacks deploy",
606 "stacks destroy",
607 "capacity status",
608 "ai_recommend",
609 "reserve_capacity",
610 "images build",
611 "models upload",
612 ],
613 "related": ["API", "RUNBOOKS"],
614 },
615 "CLUSTER_SHARED_BUCKET": {
616 "summary": "Reference for the always-on Cluster_Shared_Bucket — the S3 bucket every regional cluster can read and write by default.",
617 "topics": ["storage", "concepts", "multi-region", "security"],
618 "keywords": [
619 "s3",
620 "shared bucket",
621 "cross-region",
622 "configmap",
623 "envFrom",
624 "kms",
625 "iam grant",
626 "bucket policy",
627 "always-on",
628 ],
629 "related": ["ANALYTICS", "ARCHITECTURE"],
630 },
631 "CONCEPTS": {
632 "summary": "Fundamental concepts behind GCO — what it is, the problems it solves, and how the key components fit together.",
633 "topics": [
634 "concepts",
635 "architecture",
636 "multi-region",
637 "capacity",
638 "gpu",
639 "eks",
640 "jobs",
641 "inference",
642 ],
643 "keywords": [
644 "what is gco",
645 "fundamentals",
646 "components",
647 "global queue",
648 "capacity orchestration",
649 "ai/ml workloads",
650 "gpu allocation",
651 "regional clusters",
652 ],
653 "related": ["ARCHITECTURE", "README"],
654 },
655 "COST_MONITORING": {
656 "summary": "Cost monitoring and cost-aware scheduling — per-region OpenCost with a Grafana cost dashboard, scheduled Parquet cost reports to S3, cross-region Athena analytics, the /api/v1/cost API, and spot price-gated central-queue dispatch.",
657 "topics": [
658 "cost",
659 "monitoring",
660 "observability",
661 "queue",
662 "customization",
663 "api",
664 "cli",
665 ],
666 "keywords": [
667 "opencost",
668 "cost allocation",
669 "athena",
670 "glue",
671 "parquet",
672 "cost reports",
673 "cost dashboard",
674 "spot price",
675 "max spot price",
676 "cost-aware scheduling",
677 "namespace cost",
678 "finops",
679 ],
680 "related": ["MONITORING", "CUSTOMIZATION", "API"],
681 },
682 "CUSTOMIZATION": {
683 "summary": "How to customize GCO — deployment regions, EKS configuration, GPU nodepools, and more.",
684 "topics": [
685 "customization",
686 "architecture",
687 "gpu",
688 "eks",
689 "nodepools",
690 "storage",
691 "multi-region",
692 "deployment",
693 ],
694 "keywords": [
695 "cdk.json",
696 "regions",
697 "addons",
698 "instance types",
699 "fsx",
700 "valkey",
701 "aurora",
702 "feature toggles",
703 "queue processor",
704 "helm charts",
705 "image registry config",
706 ],
707 "related": ["ARCHITECTURE", "ANALYTICS"],
708 },
709 "FORKING": {
710 "summary": (
711 "Take GCO into your own repository: repoint badges, clone URLs, the "
712 "GitHub Pages site, and the OIDC trust-policy subject with "
713 "scripts/migrate_fork.py, plus the follow-ups it cannot decide."
714 ),
715 "topics": ["forking", "migration", "repository", "ci", "oidc"],
716 "keywords": [
717 "fork",
718 "migrate",
719 "own repository",
720 "rename repo",
721 "badges",
722 "oidc trust policy",
723 "github pages",
724 "codeowners",
725 "solution id",
726 "upstream remote",
727 ],
728 "related": ["CUSTOMIZATION", "MAINTENANCE"],
729 },
730 "IMAGE_MIRROR": {
731 "summary": "Mirror third-party container images (chiefly Volcano's docker.io images) into the project's gco/* ECR so the cluster pulls from same-account ECR instead of a rate-limited upstream.",
732 "topics": ["images", "customization", "deployment", "eks", "schedulers"],
733 "keywords": [
734 "ecr",
735 "mirror",
736 "docker hub",
737 "docker.io",
738 "volcano",
739 "pull-through cache",
740 "multi-arch",
741 "image_registry",
742 "rate limit",
743 "skopeo",
744 "buildx",
745 ],
746 "related": ["VOLCANO", "CUSTOMIZATION"],
747 },
748 "INFERENCE": {
749 "summary": "Deploy and manage multi-region GPU inference endpoints, including model weight management and supported frameworks.",
750 "topics": [
751 "inference",
752 "architecture",
753 "gpu",
754 "multi-region",
755 "cost",
756 "images",
757 "monitoring",
758 ],
759 "keywords": [
760 "vllm",
761 "tgi",
762 "triton",
763 "torchserve",
764 "sglang",
765 "endpoints",
766 "canary",
767 "rolling update",
768 "model weights",
769 "global accelerator",
770 "openai-compatible",
771 "inference monitor",
772 ],
773 "related": ["ARCHITECTURE", "RUNBOOKS"],
774 },
775 "KEDA": {
776 "summary": "KEDA event-driven autoscaling integration — scales workloads from external sources like SQS, Kafka, and Prometheus.",
777 "topics": ["schedulers", "jobs", "autoscaling"],
778 "keywords": [
779 "keda",
780 "scaledjob",
781 "scaledobject",
782 "sqs trigger",
783 "event-driven",
784 "kafka",
785 "prometheus",
786 "queue depth",
787 ],
788 "related": ["SCHEDULERS", "VOLCANO"],
789 },
790 "KUBERAY": {
791 "summary": "KubeRay operator integration — runs Ray distributed computing workloads on Kubernetes for training, tuning, and serving.",
792 "topics": ["schedulers", "jobs", "gpu", "training", "distributed"],
793 "keywords": [
794 "kuberay",
795 "ray",
796 "raycluster",
797 "rayjob",
798 "rayservice",
799 "ray tune",
800 "ray train",
801 "ray serve",
802 "distributed",
803 ],
804 "related": ["SCHEDULERS", "VOLCANO"],
805 },
806 "KUEUE": {
807 "summary": "Kueue integration for Kubernetes-native job queueing with resource quotas, fair sharing, and priority scheduling.",
808 "topics": ["schedulers", "jobs"],
809 "keywords": [
810 "kueue",
811 "clusterqueue",
812 "localqueue",
813 "resourceflavor",
814 "quota",
815 "fair sharing",
816 "priority",
817 "preemption",
818 ],
819 "related": ["SCHEDULERS", "VOLCANO", "YUNIKORN"],
820 },
821 "LEARNING_PATH": {
822 "summary": "Staged, hands-on onboarding path for users new to GCO or Kubernetes — a Kubernetes primer, cost-boundary milestones, and role-based tracks that sequence the other guides.",
823 "topics": [
824 "concepts",
825 "quickstart",
826 "cli",
827 "jobs",
828 "inference",
829 "schedulers",
830 ],
831 "keywords": [
832 "learning path",
833 "onboarding",
834 "getting started",
835 "tutorial",
836 "curriculum",
837 "new user",
838 "beginner",
839 "kubernetes",
840 "first job",
841 "role based",
842 ],
843 "related": ["CONCEPTS", "CLI", "SCHEDULERS"],
844 },
845 "LIVE_RELEASE_VALIDATION": {
846 "summary": "Local operator deploy-test-destroy harness for exact-commit live validation, guaranteed cleanup, and manually attached pull request reports.",
847 "topics": [
848 "deployment",
849 "runbooks",
850 "security",
851 "automation",
852 "multi-region",
853 ],
854 "keywords": [
855 "live release validation",
856 "local script",
857 "dedicated validation account",
858 "exact commit",
859 "deploy test destroy",
860 "checkpoint",
861 "resume",
862 "kms pending deletion",
863 "manual pull request upload",
864 "pull request comment",
865 ],
866 "related": ["RUNBOOKS", "MAINTENANCE", "CLI"],
867 },
868 "MAINTENANCE": {
869 "summary": "Routine upkeep — adding instance types to the nodepool lists, EKS Kubernetes version upgrades, base-image security-epoch refreshes, CVE-suppression renewals, and acting on the monthly dependency scan.",
870 "topics": [
871 "customization",
872 "deployment",
873 "eks",
874 "nodepools",
875 "images",
876 ],
877 "keywords": [
878 "maintenance",
879 "upkeep",
880 "upgrade",
881 "eks version",
882 "kubernetes version",
883 "instance types",
884 "instance family",
885 "dependency scan",
886 "deps-scan",
887 "security epoch",
888 "cve suppression",
889 "kubectl",
890 "helm",
891 "addon versions",
892 "requirements-lock",
893 ],
894 "related": ["CUSTOMIZATION", "RUNBOOKS", "ARCHITECTURE"],
895 },
896 "MISSION": {
897 "summary": "Goal-directed iteration loop — declare a directive, criteria, tool allowlist, and budget; runs deterministic five-phase iterations until a verdict.",
898 "topics": [
899 "concepts",
900 "cli",
901 "api",
902 "automation",
903 "feature-flags",
904 ],
905 "keywords": [
906 "mission",
907 "directive",
908 "criteria",
909 "verdict",
910 "iteration",
911 "goal directed",
912 "autonomous loop",
913 "sampling",
914 "sandbox",
915 "predicate",
916 "checkpoint",
917 "budget",
918 "final report",
919 ],
920 "related": ["CLI", "ARCHITECTURE", "RUNBOOKS"],
921 },
922 "MONITORING": {
923 "summary": "Self-hosted per-cluster observability (kube-prometheus-stack: Prometheus + Alertmanager + Grafana), on by default, with private port-forward access and the gco monitoring CLI.",
924 "topics": [
925 "monitoring",
926 "observability",
927 "gpu",
928 "schedulers",
929 "cli",
930 "concepts",
931 "cost",
932 ],
933 "keywords": [
934 "prometheus",
935 "grafana",
936 "alertmanager",
937 "kube-prometheus-stack",
938 "dcgm",
939 "servicemonitor",
940 "podmonitor",
941 "dashboards",
942 "port-forward",
943 "ssm tunnel",
944 "gco monitoring",
945 "credential rotation",
946 "cluster observability",
947 ],
948 "related": ["ARCHITECTURE", "RUNBOOKS", "CLI", "INFERENCE"],
949 },
950 "README": {
951 "summary": "Documentation index — the top-level guide map for the rest of the docs/ tree.",
952 "topics": ["concepts", "multi-region", "gpu", "capacity", "inference", "quickstart"],
953 "keywords": [
954 "index",
955 "overview",
956 "guide map",
957 "documentation",
958 "table of contents",
959 "getting started",
960 ],
961 "related": ["CONCEPTS", "ARCHITECTURE"],
962 },
963 "RUNBOOKS": {
964 "summary": "Operational runbooks — step-by-step procedures for common operational scenarios with symptoms, diagnosis, and resolution.",
965 "topics": [
966 "runbooks",
967 "troubleshooting",
968 "jobs",
969 "inference",
970 "capacity",
971 "monitoring",
972 "deployment",
973 ],
974 "keywords": [
975 "incident response",
976 "operational procedures",
977 "stuck job",
978 "endpoint down",
979 "capacity exhausted",
980 "stack rollback",
981 "playbook",
982 "diagnose",
983 "remediation",
984 ],
985 "related": ["TROUBLESHOOTING", "CLI"],
986 },
987 "SCHEDULERS": {
988 "summary": "Comparison and overview of the six supported scheduling and orchestration tools — Volcano, Kueue, KubeRay, KEDA, Slurm, YuniKorn.",
989 "topics": ["schedulers", "concepts", "jobs", "gpu"],
990 "keywords": [
991 "volcano",
992 "kueue",
993 "kuberay",
994 "keda",
995 "slurm",
996 "yunikorn",
997 "gang scheduling",
998 "batch scheduler",
999 "scheduler comparison",
1000 "queueing",
1001 ],
1002 "related": ["VOLCANO", "KUEUE", "KUBERAY"],
1003 },
1004 "SLURM_OPERATOR": {
1005 "summary": "Slinky Slurm Operator integration — runs sbatch, srun, and salloc inside an EKS cluster for HPC workflows.",
1006 "topics": ["schedulers", "jobs", "hpc"],
1007 "keywords": [
1008 "slurm",
1009 "slinky",
1010 "sbatch",
1011 "srun",
1012 "salloc",
1013 "hpc",
1014 "scientific computing",
1015 "mpi",
1016 ],
1017 "related": ["SCHEDULERS", "VOLCANO"],
1018 },
1019 "TROUBLESHOOTING": {
1020 "summary": "Troubleshooting guide — common installation, deployment, kubectl, and pod issues with their resolutions.",
1021 "topics": [
1022 "troubleshooting",
1023 "runbooks",
1024 "deployment",
1025 "eks",
1026 "jobs",
1027 "inference",
1028 "capacity",
1029 ],
1030 "keywords": [
1031 "kubectl",
1032 "pod crashloop",
1033 "imagepullbackoff",
1034 "stack rollback",
1035 "deployment failed",
1036 "credentials",
1037 "vpc",
1038 "nodepool not scaling",
1039 "common errors",
1040 "fix",
1041 ],
1042 "related": ["RUNBOOKS", "CLI"],
1043 },
1044 "VOLCANO": {
1045 "summary": "Volcano batch scheduler integration — gang scheduling, fair-share queuing, and job lifecycle management for AI/ML and HPC.",
1046 "topics": ["schedulers", "jobs", "gpu", "hpc"],
1047 "keywords": [
1048 "volcano",
1049 "gang scheduling",
1050 "vcjob",
1051 "podgroup",
1052 "queue",
1053 "fair share",
1054 "job lifecycle",
1055 "batch",
1056 ],
1057 "related": ["SCHEDULERS", "KUEUE", "YUNIKORN"],
1058 },
1059 "YUNIKORN": {
1060 "summary": "Apache YuniKorn integration — multi-tenant scheduler with hierarchical queues and gang scheduling.",
1061 "topics": ["schedulers", "jobs"],
1062 "keywords": [
1063 "yunikorn",
1064 "hierarchical queues",
1065 "multi-tenant",
1066 "gang scheduling",
1067 "app-aware scheduling",
1068 "fair share",
1069 ],
1070 "related": ["SCHEDULERS", "KUEUE", "VOLCANO"],
1071 },
1072}
1075# ---------------------------------------------------------------------------
1076# Root-doc metadata — searchable project-level guidance that deliberately lives
1077# at the repository root rather than under ``docs/``. Keep this separate from
1078# ``DOC_METADATA`` so the strict 1:1 metadata-to-``docs/*.md`` invariant remains
1079# meaningful. Root docs use static ``docs://gco/{name}`` resources.
1080# ---------------------------------------------------------------------------
1082ROOT_DOC_METADATA: dict[str, dict[str, str | list[str]]] = {
1083 "TENETS": {
1084 "path": "TENETS.md",
1085 "summary": (
1086 "Prioritized project tenets and north-star guidance for safety, truth, "
1087 "security, global capacity orchestration, automation, operations, cost, "
1088 "and maintainability."
1089 ),
1090 "topics": [
1091 "concepts",
1092 "architecture",
1093 "security",
1094 "automation",
1095 "multi-region",
1096 "deployment",
1097 "cost",
1098 ],
1099 "keywords": [
1100 "tenets",
1101 "north star",
1102 "principles",
1103 "decision framework",
1104 "project ethos",
1105 "safety",
1106 "truth",
1107 "reversibility",
1108 "least privilege",
1109 "capacity policy",
1110 "deterministic automation",
1111 "definition of done",
1112 ],
1113 "related": ["ARCHITECTURE", "MAINTENANCE", "LIVE_RELEASE_VALIDATION"],
1114 }
1115}
1118# ---------------------------------------------------------------------------
1119# Package-doc metadata — used by ``find_docs`` and the
1120# ``docs://gco/packages/...`` resources to describe the package-level READMEs
1121# that live next to the code (under ``gco_mcp/``) rather than in ``docs/``. These
1122# are developer-facing internals guides (how a package is structured and how to
1123# customize it), kept in a catalog separate from ``DOC_METADATA`` so the strict
1124# 1:1 ``docs/*.md`` invariant stays intact. Each entry is keyed by a stable
1125# slug and carries a ``path`` relative to the project root. A ``related`` entry
1126# may reference either another package slug or a ``DOC_METADATA`` key.
1127# ---------------------------------------------------------------------------
1129PACKAGE_DOC_METADATA: dict[str, dict[str, str | list[str]]] = {
1130 "mcp-server": {
1131 "path": "gco_mcp/README.md",
1132 "summary": "GCO MCP server guide — setup across MCP clients, feature-flag gating, and the full tool and resource catalog.",
1133 "topics": ["mcp", "concepts", "feature-flags", "customization"],
1134 "keywords": [
1135 "mcp server",
1136 "fastmcp",
1137 "stdio",
1138 "feature flags",
1139 "gco_enable",
1140 "kiro",
1141 "claude desktop",
1142 "cursor",
1143 "tool search",
1144 "available tools",
1145 "resources",
1146 ],
1147 "related": ["mcp-tools", "mcp-resources", "CLI", "MISSION"],
1148 },
1149 "mcp-tools": {
1150 "path": "gco_mcp/tools/README.md",
1151 "summary": "How MCP tools are defined — one module per domain, the @mcp.tool + audit_logged pattern, and how to add a new tool.",
1152 "topics": ["mcp", "customization"],
1153 "keywords": [
1154 "tool",
1155 "mcp.tool",
1156 "audit_logged",
1157 "cli_runner",
1158 "adding a tool",
1159 "tool module",
1160 "domain",
1161 ],
1162 "related": ["mcp-server", "mcp-resources"],
1163 },
1164 "mcp-resources": {
1165 "path": "gco_mcp/resources/README.md",
1166 "summary": "MCP resource modules by URI scheme (docs://, source://, k8s://, …) and how to add a new resource group.",
1167 "topics": ["mcp", "customization"],
1168 "keywords": [
1169 "resource",
1170 "mcp.resource",
1171 "uri scheme",
1172 "docs scheme",
1173 "source scheme",
1174 "resource index",
1175 "adding a resource",
1176 ],
1177 "related": ["mcp-server", "mcp-tools"],
1178 },
1179 "mcp-mission": {
1180 "path": "gco_mcp/mission/README.md",
1181 "summary": "Mission package internals — the five-phase goal-directed loop, deterministic verdict cascade, sandboxes, and how to extend each piece.",
1182 "topics": ["mcp", "automation", "customization", "concepts"],
1183 "keywords": [
1184 "mission",
1185 "engine",
1186 "verdict",
1187 "five-phase loop",
1188 "sandbox",
1189 "predicate",
1190 "sampling",
1191 "criteria",
1192 "customize mission",
1193 "module map",
1194 ],
1195 "related": ["MISSION", "mcp-metric-readers", "mcp-mission-judge", "mcp-server"],
1196 },
1197 "mcp-metric-readers": {
1198 "path": "gco_mcp/metric_readers/README.md",
1199 "summary": "Pure helpers behind the read-only metric-reader tools — and how to add an aggregation mode, file format, error code, or whole new reader source.",
1200 "topics": ["mcp", "metrics", "customization", "automation"],
1201 "keywords": [
1202 "metric reader",
1203 "cloudwatch",
1204 "aggregation mode",
1205 "file format",
1206 "parquet",
1207 "jsonl",
1208 "error code",
1209 "metrics_result",
1210 "customize metrics",
1211 "local root",
1212 ],
1213 "related": ["mcp-mission-judge", "mcp-mission", "MISSION", "mcp-server"],
1214 },
1215 "mcp-mission-judge": {
1216 "path": "gco_mcp/mission_judge/README.md",
1217 "summary": "LLM-as-judge progress scoring internals — the versioned rubric, deterministic prompt, score parsing, and how to customize the scoring for your use case.",
1218 "topics": ["mcp", "metrics", "customization", "automation"],
1219 "keywords": [
1220 "semantic progress",
1221 "judge",
1222 "rubric",
1223 "prompt",
1224 "score parsing",
1225 "progress_score",
1226 "gco_enable_semantic_progress",
1227 "llm as judge",
1228 "customize rubric",
1229 ],
1230 "related": ["mcp-metric-readers", "mcp-mission", "MISSION", "mcp-server"],
1231 },
1232}
1235@mcp.resource("docs://gco/index")
1236def docs_index() -> str:
1237 """List all available GCO documentation, examples, and configuration resources."""
1238 sections = ["# GCO Resource Index\n"]
1239 sections.append("## Project Overview")
1240 sections.append("- `docs://gco/README` — Project README and overview")
1241 sections.append("- `docs://gco/QUICKSTART` — Quick start guide (deploy in under 60 minutes)")
1242 sections.append("- `docs://gco/TENETS` — Prioritized project tenets and north-star guidance")
1243 sections.append("- `docs://gco/CONTRIBUTING` — Contributing guide\n")
1245 sections.append("## Documentation")
1246 sections.append(
1247 "- `find_docs(query=..., topic=..., limit=...)` tool — search the docs catalog by topic and free-text query"
1248 )
1249 sections.append(
1250 "- `docs://gco/docs/by-topic/{topic}` — list every doc tagged with a given topic phrase"
1251 )
1252 sections.append(
1253 "- `docs://gco/docs/by-related/{doc_name}` — list every doc related to the given doc"
1254 )
1255 for f in sorted(DOCS_DIR.glob("*.md")):
1256 sections.append(f"- `docs://gco/docs/{f.stem}` — {f.stem}")
1258 sections.append("\n## Architecture Decision Records")
1259 sections.append(
1260 "Append-only log of significant architectural decisions — the context, "
1261 "the decision, and its consequences."
1262 )
1263 sections.append("- `docs://gco/adr/index` — every ADR with its status (directory-driven)")
1264 sections.append("- `docs://gco/adr/README` — when to write an ADR and the authoring process")
1265 sections.append("- `docs://gco/adr/template` — the blank ADR template")
1266 sections.append("- `docs://gco/adr/{id}` — read one ADR by four-digit id (e.g. `0001`)")
1268 sections.append("\n## Package Internals")
1269 sections.append(
1270 "Developer-facing guides to the code packages under `gco_mcp/` — structure and "
1271 "how to customize each. Also searchable via `find_docs`."
1272 )
1273 for name, meta in PACKAGE_DOC_METADATA.items():
1274 sections.append(f"- `docs://gco/packages/{name}` — {meta.get('summary', '')}")
1276 sections.append("\n## Example Manifests")
1277 sections.append("- `docs://gco/examples/README` — Examples overview and usage guide")
1278 sections.append(
1279 "- `docs://gco/examples/guide` — How to create new job manifests (patterns & metadata)"
1280 )
1282 sections.append("\n### Discovery")
1283 sections.append(
1284 "- `find_examples(query=..., category=..., gpu=..., opt_in=..., limit=...)` tool — "
1285 "search the catalog by keyword and filters"
1286 )
1287 sections.append(
1288 "- `docs://gco/examples/by-category/{category}` — list every example in a given category"
1289 )
1290 sections.append(
1291 "- `docs://gco/examples/by-use-case/{use_case}` — list every example matching a use-case phrase"
1292 )
1293 sections.append(
1294 "- `docs://gco/examples/{name}` — full manifest plus metadata header for a single example\n"
1295 )
1297 # Categorize examples
1298 categories: dict[str, list[str]] = {}
1299 for f in sorted(EXAMPLES_DIR.glob("*.yaml")):
1300 name = f.stem
1301 meta = EXAMPLE_METADATA.get(name, {})
1302 cat_value = meta.get("category", "Other")
1303 cat = cat_value if isinstance(cat_value, str) else "Other"
1304 summary_value = meta.get("summary", name)
1305 summary = summary_value if isinstance(summary_value, str) else name
1306 entry = f"- `docs://gco/examples/{name}` — {summary}"
1307 categories.setdefault(cat, []).append(entry)
1309 for cat, entries in categories.items():
1310 sections.append(f"### {cat}")
1311 sections.extend(entries)
1312 sections.append("")
1314 sections.append("## Live State")
1315 sections.append(
1316 "- `gco://jobs/{region}/{job_name}` — live YAML for a Kubernetes Job in the "
1317 "explicitly selected regional EKS cluster"
1318 )
1319 sections.append(
1320 "- `gco://inference/{endpoint_name}` — desired-state record for an inference endpoint "
1321 "from the DynamoDB store"
1322 )
1323 sections.append(
1324 "- `gco://k8s/{region}/{namespace}/{kind}/{name}` — live YAML for any Kubernetes "
1325 "resource from the explicitly selected regional EKS cluster"
1326 )
1327 sections.append(
1328 "- `gco://cluster/{region}/topology` — Karpenter NodePools plus Pending pods snapshot for one region"
1329 )
1330 sections.append(
1331 "- `costs://gco/summary/{days_window}` — cost summary for the given day window (positive integer)"
1332 )
1333 sections.append("- `tasks://gco/{task_id}` — current status of a FastMCP background task by ID")
1334 sections.append(
1335 "- `mission://sessions/{session_id}` — Mission session state, report, or audit replay "
1336 "when `GCO_ENABLE_MISSION=true`"
1337 )
1338 sections.append("")
1340 sections.append("## Other Resource Groups")
1341 sections.append("- `k8s://gco/manifests/index` — Kubernetes manifests deployed to EKS")
1342 sections.append("- `iam://gco/policies/index` — IAM policy templates")
1343 sections.append("- `infra://gco/index` — Dockerfiles, Helm charts, CI/CD config")
1344 sections.append("- `ci://gco/index` — GitHub Actions workflows, composite actions, templates")
1345 sections.append(
1346 "- `images://gco/index` — ECR repositories, tags, images, and replication status"
1347 )
1348 sections.append("- `source://gco/index` — Source code browser")
1349 sections.append("- `demos://gco/index` — Demo walkthroughs and presentation materials")
1350 sections.append("- `clients://gco/index` — API client examples (Python, curl, AWS CLI)")
1351 sections.append("- `scripts://gco/index` — Utility scripts")
1352 sections.append("- `tests://gco/index` — Test suite documentation and patterns")
1353 sections.append(
1354 "- `config://gco/index` — authoritative CDK configuration, MCP feature flags, and environment variables"
1355 )
1356 sections.append("- `mcp://gco/tools/index` — live registered-tool catalog")
1357 sections.append("- `mcp://gco/resources/index` — live static-resource and template catalog")
1358 sections.append("- `mcp://gco/feature-flags` — authoritative feature-gate mapping")
1359 return "\n".join(sections)
1362@mcp.resource("docs://gco/README")
1363def readme_resource() -> str:
1364 """The main project README with overview and quickstart information."""
1365 return (PROJECT_ROOT / "README.md").read_text()
1368@mcp.resource("docs://gco/QUICKSTART")
1369def quickstart_resource() -> str:
1370 """Quick start guide — get running in under 60 minutes."""
1371 path = PROJECT_ROOT / "QUICKSTART.md"
1372 if not path.is_file(): 1372 ↛ 1373line 1372 didn't jump to line 1373 because the condition on line 1372 was never true
1373 return "QUICKSTART.md not found."
1374 return path.read_text()
1377@mcp.resource("docs://gco/TENETS")
1378def tenets_resource() -> str:
1379 """Prioritized project tenets and north-star decision guidance."""
1380 meta = ROOT_DOC_METADATA["TENETS"]
1381 rel_path = str(meta["path"])
1382 path = PROJECT_ROOT / rel_path
1383 if not path.is_file(): 1383 ↛ 1384line 1383 didn't jump to line 1384 because the condition on line 1383 was never true
1384 return f"{rel_path} not found."
1385 content = path.read_text()
1386 topics = meta.get("topics", [])
1387 related = meta.get("related", [])
1388 header_lines = []
1389 if isinstance(topics, list) and topics: 1389 ↛ 1391line 1389 didn't jump to line 1391 because the condition on line 1389 was always true
1390 header_lines.append(f"<!-- Topics: {', '.join(str(t) for t in topics)} -->")
1391 if isinstance(related, list) and related: 1391 ↛ 1393line 1391 didn't jump to line 1393 because the condition on line 1391 was always true
1392 header_lines.append(f"<!-- Related: {', '.join(str(r) for r in related)} -->")
1393 return "\n".join(header_lines) + "\n\n" + content
1396@mcp.resource("docs://gco/CONTRIBUTING")
1397def contributing_resource() -> str:
1398 """Contributing guide — how to contribute to the project."""
1399 path = PROJECT_ROOT / "CONTRIBUTING.md"
1400 if not path.is_file(): 1400 ↛ 1401line 1400 didn't jump to line 1401 because the condition on line 1400 was never true
1401 return "CONTRIBUTING.md not found."
1402 return path.read_text()
1405@mcp.resource("docs://gco/docs/{doc_name}")
1406def doc_resource(doc_name: str) -> str:
1407 """Read a documentation file by name (e.g. ARCHITECTURE, CLI, INFERENCE).
1409 Prepends an HTML-comment header with ``Topics:`` and ``Related:`` lines
1410 pulled from ``DOC_METADATA`` so an LLM consuming the rendered markdown
1411 sees the doc's classification without it bleeding into the rendered
1412 output. HTML comments are used rather than ``#`` because docs are
1413 markdown — Python-style comments would render as text.
1414 """
1415 path = DOCS_DIR / f"{doc_name}.md"
1416 if not path.is_file():
1417 available = [f.stem for f in DOCS_DIR.glob("*.md")]
1418 return f"Document '{doc_name}' not found. Available: {', '.join(available)}"
1419 content = path.read_text()
1420 meta = DOC_METADATA.get(doc_name, {})
1421 header_lines = []
1422 topics = meta.get("topics", [])
1423 if isinstance(topics, list) and topics: 1423 ↛ 1425line 1423 didn't jump to line 1425 because the condition on line 1423 was always true
1424 header_lines.append(f"<!-- Topics: {', '.join(str(t) for t in topics)} -->")
1425 related = meta.get("related", [])
1426 if isinstance(related, list) and related: 1426 ↛ 1428line 1426 didn't jump to line 1428 because the condition on line 1426 was always true
1427 header_lines.append(f"<!-- Related: {', '.join(str(r) for r in related)} -->")
1428 if header_lines: 1428 ↛ 1430line 1428 didn't jump to line 1430 because the condition on line 1428 was always true
1429 return "\n".join(header_lines) + "\n\n" + content
1430 return content
1433@mcp.resource("docs://gco/packages/{package_name}")
1434def package_doc_resource(package_name: str) -> str:
1435 """Read a package-level README by slug (e.g. mcp-mission, mcp-metric-readers).
1437 Serves the developer-facing internals guides catalogued in
1438 ``PACKAGE_DOC_METADATA`` — the README files that live next to the code
1439 under ``gco_mcp/`` rather than in ``docs/``. Prepends an HTML-comment header
1440 with ``Topics:`` and ``Related:`` lines (mirroring :func:`doc_resource`)
1441 so a consuming LLM sees the classification without it bleeding into the
1442 rendered markdown. An unknown slug returns the literal ``Package doc 'X'
1443 not found. Available: ...`` string so callers can recover.
1444 """
1445 meta = PACKAGE_DOC_METADATA.get(package_name)
1446 if meta is None:
1447 available = ", ".join(sorted(PACKAGE_DOC_METADATA.keys()))
1448 return f"Package doc '{package_name}' not found. Available: {available}"
1449 rel_path = str(meta.get("path", ""))
1450 path = PROJECT_ROOT / rel_path
1451 if not path.is_file(): 1451 ↛ 1452line 1451 didn't jump to line 1452 because the condition on line 1451 was never true
1452 return f"Package doc '{package_name}' file not found at '{rel_path}'."
1453 content = path.read_text()
1454 header_lines = []
1455 topics = meta.get("topics", [])
1456 if isinstance(topics, list) and topics: 1456 ↛ 1458line 1456 didn't jump to line 1458 because the condition on line 1456 was always true
1457 header_lines.append(f"<!-- Topics: {', '.join(str(t) for t in topics)} -->")
1458 related = meta.get("related", [])
1459 if isinstance(related, list) and related: 1459 ↛ 1461line 1459 didn't jump to line 1461 because the condition on line 1459 was always true
1460 header_lines.append(f"<!-- Related: {', '.join(str(r) for r in related)} -->")
1461 if header_lines: 1461 ↛ 1463line 1461 didn't jump to line 1463 because the condition on line 1461 was always true
1462 return "\n".join(header_lines) + "\n\n" + content
1463 return content
1466@mcp.resource("docs://gco/examples/README")
1467def examples_readme_resource() -> str:
1468 """Examples README — overview of all example manifests with usage instructions."""
1469 path = EXAMPLES_DIR / "README.md"
1470 if not path.is_file(): 1470 ↛ 1471line 1470 didn't jump to line 1471 because the condition on line 1470 was never true
1471 return "Examples README.md not found."
1472 return path.read_text()
1475@mcp.resource("docs://gco/examples/guide")
1476def examples_guide_resource() -> str:
1477 """How to create new job manifests — patterns, metadata, and best practices.
1479 Use this resource when you need to write a new Kubernetes manifest for GCO.
1480 It provides the metadata for every existing example so you can pick the
1481 closest one as a starting point and adapt it.
1482 """
1483 lines = ["# GCO Example Manifest Guide\n"]
1484 lines.append("Use this guide to create new Kubernetes manifests for GCO. Pick the closest")
1485 lines.append("existing example as a starting point, then adapt it.\n")
1486 lines.append("## All Examples with Metadata\n")
1487 lines.append("| Example | Category | Keywords | GPU | Opt-in | How to Submit |")
1488 lines.append("|---------|----------|----------|-----|--------|---------------|")
1489 for name, meta in EXAMPLE_METADATA.items():
1490 gpu = meta.get("gpu", "no")
1491 opt_in = meta.get("opt_in", "—") or "—"
1492 submission = meta.get("submission", "")
1493 keywords = meta.get("keywords", [])
1494 keywords_cell = ", ".join(keywords) if isinstance(keywords, list) and keywords else "—"
1495 lines.append(
1496 f"| `{name}` | {meta['category']} | {keywords_cell} | {gpu} | {opt_in} | "
1497 f"`{submission}` |"
1498 )
1500 lines.append("\n## Common Patterns\n")
1501 lines.append("### Namespace")
1502 lines.append(
1503 "All GCO jobs use `namespace: gco-jobs`. Inference uses `namespace: gco-inference`.\n"
1504 )
1505 lines.append("### Security Context (required)")
1506 lines.append("```yaml")
1507 lines.append("securityContext:")
1508 lines.append(" runAsNonRoot: true")
1509 lines.append(" runAsUser: 1000")
1510 lines.append(" runAsGroup: 1000")
1511 lines.append("containers:")
1512 lines.append("- securityContext:")
1513 lines.append(" allowPrivilegeEscalation: false")
1514 lines.append(" capabilities:")
1515 lines.append(' drop: ["ALL"]')
1516 lines.append("```\n")
1517 lines.append("### GPU Resources")
1518 lines.append("```yaml")
1519 lines.append("resources:")
1520 lines.append(" requests:")
1521 lines.append(' nvidia.com/gpu: "1"')
1522 lines.append(" limits:")
1523 lines.append(' nvidia.com/gpu: "1"')
1524 lines.append("tolerations:")
1525 lines.append("- key: nvidia.com/gpu")
1526 lines.append(" operator: Equal")
1527 lines.append(' value: "true"')
1528 lines.append(" effect: NoSchedule")
1529 lines.append("```\n")
1530 lines.append("### EFS Shared Storage")
1531 lines.append("```yaml")
1532 lines.append("volumeMounts:")
1533 lines.append("- name: shared-storage")
1534 lines.append(" mountPath: /mnt/gco")
1535 lines.append("volumes:")
1536 lines.append("- name: shared-storage")
1537 lines.append(" persistentVolumeClaim:")
1538 lines.append(" claimName: gco-shared-storage")
1539 lines.append("```\n")
1540 lines.append("### Prevent Node Consolidation (long-running jobs)")
1541 lines.append("```yaml")
1542 lines.append("metadata:")
1543 lines.append(" annotations:")
1544 lines.append(' karpenter.sh/do-not-disrupt: "true"')
1545 lines.append("```\n")
1546 lines.append("### Submission Methods")
1547 lines.append("1. **SQS (recommended):** `gco jobs submit-sqs <manifest> --region <region>`")
1548 lines.append("2. **API Gateway:** `gco jobs submit <manifest>`")
1549 lines.append("3. **Direct kubectl:** `gco jobs submit-direct <manifest> -r <region>`")
1550 lines.append("4. **kubectl apply:** `kubectl apply -f <manifest>`")
1551 return "\n".join(lines)
1554@mcp.resource("docs://gco/examples/{example_name}")
1555def example_resource(example_name: str) -> str:
1556 """Read an example manifest by name, with metadata context for creating similar jobs.
1558 Returns the raw YAML manifest preceded by a metadata header that describes
1559 what the example does, its requirements, and how to submit it.
1560 """
1561 path = EXAMPLES_DIR / f"{example_name}.yaml"
1562 if not path.is_file():
1563 available = [f.stem for f in EXAMPLES_DIR.glob("*.yaml")]
1564 return f"Example '{example_name}' not found. Available: {', '.join(available)}"
1566 meta = EXAMPLE_METADATA.get(example_name, {})
1567 header_lines = []
1568 if meta: 1568 ↛ 1594line 1568 didn't jump to line 1594 because the condition on line 1568 was always true
1569 header_lines.append(f"# Example: {example_name}")
1570 header_lines.append(f"# Category: {meta.get('category', 'Unknown')}")
1571 header_lines.append(f"# Summary: {meta.get('summary', '')}")
1572 if meta.get("gpu", "no") != "no":
1573 header_lines.append(f"# GPU/Accelerator: {meta['gpu']}")
1574 if meta.get("opt_in"):
1575 header_lines.append(f"# Opt-in required: {meta['opt_in']}")
1576 header_lines.append(
1577 f"# Submit with: {meta.get('submission', 'kubectl apply -f examples/' + example_name + '.yaml')}"
1578 )
1579 keywords = meta.get("keywords", [])
1580 if isinstance(keywords, list) and keywords: 1580 ↛ 1582line 1580 didn't jump to line 1582 because the condition on line 1580 was always true
1581 header_lines.append(f"# Keywords: {', '.join(keywords)}")
1582 instance_types = meta.get("instance_types", [])
1583 if isinstance(instance_types, list) and instance_types:
1584 header_lines.append(f"# Instance Types: {', '.join(instance_types)}")
1585 use_cases = meta.get("use_cases", [])
1586 if isinstance(use_cases, list) and use_cases: 1586 ↛ 1588line 1586 didn't jump to line 1588 because the condition on line 1586 was always true
1587 header_lines.append(f"# Use Cases: {', '.join(use_cases)}")
1588 related = meta.get("related", [])
1589 if isinstance(related, list) and related: 1589 ↛ 1591line 1589 didn't jump to line 1591 because the condition on line 1589 was always true
1590 header_lines.append(f"# Related: {', '.join(related)}")
1591 header_lines.append("#")
1592 header_lines.append("# --- Manifest begins below ---\n")
1594 manifest = path.read_text()
1595 if header_lines: 1595 ↛ 1597line 1595 didn't jump to line 1597 because the condition on line 1595 was always true
1596 return "\n".join(header_lines) + manifest
1597 return manifest
1600@mcp.resource("docs://gco/examples/by-category/{category}")
1601def examples_by_category_resource(category: str) -> str:
1602 """List examples grouped by category.
1604 Returns a markdown listing of every example in the given category. Match
1605 is case-insensitive against the entry's ``category`` field. When the
1606 category is not recognised, returns the literal "Category 'X' not found.
1607 Available: ..." string so callers can recover.
1608 """
1609 matches = [
1610 (name, meta)
1611 for name, meta in EXAMPLE_METADATA.items()
1612 if str(meta.get("category", "")).lower() == category.lower()
1613 ]
1614 if not matches:
1615 available = sorted({str(m.get("category", "")) for m in EXAMPLE_METADATA.values()})
1616 return f"Category '{category}' not found. Available: {', '.join(available)}"
1617 lines = [f"# Examples in category: {category}\n"]
1618 for name, meta in sorted(matches):
1619 lines.append(f"- `docs://gco/examples/{name}` — {meta.get('summary', '')}")
1620 return "\n".join(lines)
1623@mcp.resource("docs://gco/examples/by-use-case/{use_case}")
1624def examples_by_use_case_resource(use_case: str) -> str:
1625 """List examples whose use_cases include the given phrase (case-insensitive).
1627 Substring match against every entry in each example's ``use_cases`` list.
1628 When nothing matches, returns the literal "No examples match use case
1629 'X'." string with a pointer to ``find_examples`` for broader search.
1630 """
1631 needle = use_case.lower()
1632 matches: list[tuple[str, dict[str, str | list[str]]]] = []
1633 for name, meta in EXAMPLE_METADATA.items():
1634 ucs = meta.get("use_cases", [])
1635 if isinstance(ucs, list) and any(needle in str(uc).lower() for uc in ucs):
1636 matches.append((name, meta))
1637 if not matches:
1638 return (
1639 f"No examples match use case '{use_case}'. "
1640 "Try `find_examples(query=...)` for broader search."
1641 )
1642 lines = [f"# Examples matching use case: {use_case}\n"]
1643 for name, meta in sorted(matches):
1644 lines.append(f"- `docs://gco/examples/{name}` — {meta.get('summary', '')}")
1645 return "\n".join(lines)
1648@mcp.resource("docs://gco/docs/by-topic/{topic}")
1649def docs_by_topic_resource(topic: str) -> str:
1650 """List docs whose topics include the given phrase (case-insensitive).
1652 Substring match against every entry in each doc's ``topics`` list. When
1653 nothing matches, returns the literal ``Topic 'X' not found. Available:
1654 ...`` string with the union of every known topic so callers can recover.
1655 """
1656 needle = topic.lower()
1657 matches: list[tuple[str, dict[str, str | list[str]]]] = []
1658 for name, meta in DOC_METADATA.items():
1659 topics = meta.get("topics", [])
1660 if isinstance(topics, list) and any(needle in str(t).lower() for t in topics):
1661 matches.append((name, meta))
1662 if not matches:
1663 available = sorted(
1664 {
1665 str(t)
1666 for meta in DOC_METADATA.values()
1667 for t in (meta.get("topics", []) if isinstance(meta.get("topics"), list) else [])
1668 }
1669 )
1670 return f"Topic '{topic}' not found. Available: {', '.join(available)}"
1671 lines = [f"# Docs matching topic: {topic}\n"]
1672 for name, meta in sorted(matches):
1673 lines.append(f"- `docs://gco/docs/{name}` — {meta.get('summary', '')}")
1674 return "\n".join(lines)
1677@mcp.resource("docs://gco/docs/by-related/{doc_name}")
1678def docs_by_related_resource(doc_name: str) -> str:
1679 """List docs related to ``doc_name``.
1681 Combines two views of the bidirectional relation: every doc that lists
1682 ``doc_name`` in its own ``related`` field (referenced by) and every doc
1683 ``doc_name`` itself lists (references). Unknown names return the literal
1684 ``Doc 'X' not found. Available: ...`` string.
1685 """
1686 if doc_name not in DOC_METADATA:
1687 available = sorted(DOC_METADATA.keys())
1688 return f"Doc '{doc_name}' not found. Available: {', '.join(available)}"
1690 referenced_by: list[str] = []
1691 for name, meta in DOC_METADATA.items():
1692 related = meta.get("related", [])
1693 if isinstance(related, list) and doc_name in related:
1694 referenced_by.append(name)
1696 references: list[str] = []
1697 referenced_self = DOC_METADATA[doc_name].get("related", [])
1698 if isinstance(referenced_self, list): 1698 ↛ 1701line 1698 didn't jump to line 1701 because the condition on line 1698 was always true
1699 references = [str(r) for r in referenced_self]
1701 lines = [f"# Docs related to {doc_name}\n"]
1702 if references: 1702 ↛ 1708line 1702 didn't jump to line 1708 because the condition on line 1702 was always true
1703 lines.append("## Referenced by this doc")
1704 for ref in sorted(set(references)):
1705 meta = DOC_METADATA.get(ref, {})
1706 lines.append(f"- `docs://gco/docs/{ref}` — {meta.get('summary', '')}")
1707 lines.append("")
1708 if referenced_by: 1708 ↛ 1713line 1708 didn't jump to line 1713 because the condition on line 1708 was always true
1709 lines.append("## Docs that reference this one")
1710 for ref in sorted(set(referenced_by)):
1711 meta = DOC_METADATA.get(ref, {})
1712 lines.append(f"- `docs://gco/docs/{ref}` — {meta.get('summary', '')}")
1713 return "\n".join(lines)
1716# ---------------------------------------------------------------------------
1717# Architecture Decision Records (ADRs) — docs://gco/adr/*
1718#
1719# The ADR catalog under ``docs/adr/`` is directory-driven: both the index and
1720# the per-record resource derive everything from the files on disk, so
1721# recording a new decision needs no change here. ``NNNN-title.md`` files are the
1722# records; ``README.md`` (process guide) and ``template.md`` (blank form) are
1723# guides, not records, and are excluded from the record listing.
1724# ---------------------------------------------------------------------------
1726_ADR_ID_RE = re.compile(r"^\d{4}-")
1727_ADR_TITLE_PREFIX_RE = re.compile(r"^\d+\.\s*")
1730def _adr_record_files() -> list[Path]:
1731 """Return the numbered ADR record files (``NNNN-*.md``), sorted by id."""
1732 if not ADR_DIR.is_dir(): 1732 ↛ 1733line 1732 didn't jump to line 1733 because the condition on line 1732 was never true
1733 return []
1734 return sorted(p for p in ADR_DIR.glob("*.md") if _ADR_ID_RE.match(p.name))
1737def _parse_adr(path: Path) -> dict[str, str]:
1738 """Extract ``id``, ``title``, and ``status`` from an ADR markdown file.
1740 Title is the first level-1 heading with any ``NNNN.`` numeric prefix
1741 stripped; status is read from the ``- **Status:** ...`` metadata line. Both
1742 fall back to sensible defaults so a malformed file still lists rather than
1743 breaking the index.
1744 """
1745 title = ""
1746 status = ""
1747 for raw in path.read_text(encoding="utf-8").splitlines():
1748 line = raw.strip()
1749 if not title and line.startswith("# "):
1750 title = _ADR_TITLE_PREFIX_RE.sub("", line[2:].strip())
1751 continue
1752 if not status:
1753 # Normalize "- **Status:** Accepted" to "status: accepted" so the
1754 # label is matched regardless of list marker or emphasis.
1755 plain = line.lstrip("-* ").replace("*", "")
1756 if plain.lower().startswith("status"):
1757 status = plain[len("status") :].lstrip(": ").strip().rstrip(".")
1758 return {
1759 "id": path.stem,
1760 "title": title or path.stem,
1761 "status": status or "Unknown",
1762 }
1765def _resolve_adr(adr_id: str) -> Path | None:
1766 """Resolve an ADR request to a file under ``docs/adr/``, or ``None``.
1768 Accepts a full filename stem (``0001-record-architecture-decisions``,
1769 ``README``, ``template``) or a numeric id in any zero-padding (``1`` ->
1770 ``0001``). Rejects anything containing a path separator or ``..`` so a
1771 crafted id cannot escape the directory; because the sanitized candidate has
1772 no separators, the joined path is always a direct child of ``docs/adr/``.
1773 """
1774 candidate = adr_id.strip()
1775 if not candidate or "/" in candidate or "\\" in candidate or ".." in candidate:
1776 return None
1777 if candidate.isdigit():
1778 want = candidate.zfill(4)
1779 return next((p for p in _adr_record_files() if p.name[:4] == want), None)
1780 path = ADR_DIR / f"{candidate}.md"
1781 return path if path.is_file() else None
1784@mcp.resource("docs://gco/adr/index")
1785def adr_index_resource() -> str:
1786 """List every Architecture Decision Record with its id, title, and status.
1788 Directory-driven: scans ``docs/adr/`` for numbered ``NNNN-title.md`` records
1789 at read time, so a newly added ADR appears here with no code change. The
1790 process guide and the blank template are available at
1791 ``docs://gco/adr/README`` and ``docs://gco/adr/template``.
1792 """
1793 lines = ["# Architecture Decision Records\n"]
1794 lines.append(
1795 "Append-only log of significant architectural decisions (context, "
1796 "decision, consequences). Read the process at `docs://gco/adr/README` "
1797 "and start a new record from `docs://gco/adr/template`.\n"
1798 )
1799 files = _adr_record_files()
1800 if not files:
1801 lines.append("_No ADRs have been recorded yet._")
1802 return "\n".join(lines)
1803 for path in files:
1804 meta = _parse_adr(path)
1805 lines.append(f"- `docs://gco/adr/{path.name[:4]}` — {meta['title']} ({meta['status']})")
1806 return "\n".join(lines)
1809@mcp.resource("docs://gco/adr/{adr_id}")
1810def adr_resource(adr_id: str) -> str:
1811 """Read a single ADR by id, filename stem, or guide name.
1813 Accepts a four-digit id (``0001``), a full stem
1814 (``0001-record-architecture-decisions``), or the ``README`` / ``template``
1815 guides. An unknown id returns the literal ``ADR 'X' not found. Available:
1816 ...`` string listing the numeric ids so callers can recover.
1817 """
1818 path = _resolve_adr(adr_id)
1819 if path is None:
1820 available = ", ".join(p.name[:4] for p in _adr_record_files()) or "none"
1821 return f"ADR '{adr_id}' not found. Available: {available}"
1822 return path.read_text(encoding="utf-8")