diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 6520a48..5f7b491 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,686 +1,51 @@ # GitHub Actions Workflows -This directory contains CI/CD workflows for the CUDly project, providing automated testing, deployment, and operations across AWS, GCP, and Azure. +CI and release workflows for the cloud-commitments-mcp server. CI needs no cloud +credentials; `snyk-scan` reads the `SNYK_TOKEN` secret. -## 📋 Workflows Overview +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `ci.yml` | PR and push to `main`/`develop`, manual | Lint, test, build and scan | +| `pre-commit.yml` | PR and push to `main` | Runs the repo's pre-commit hooks | +| `mcp-server-json.yml` | PRs touching `server.json` or its validator, `v*` tags, manual | Validates `server.json` | +| `release.yml` | `v*` tag push | Builds and publishes a release | -| Workflow | Purpose | Trigger | Duration | -|----------|---------|---------|----------| -| [ci.yml](#ci-workflow) | Continuous Integration | PR, Push to main | ~10 min | -| [deploy-aws-lambda.yml](#aws-lambda-deployment) | Deploy to AWS Lambda | Push to main, Manual | ~8 min | -| [deploy-aws-fargate.yml](#aws-fargate-deployment) | Deploy to AWS Fargate | Manual | ~10 min | -| [deploy-gcp.yml](#gcp-deployment) | Deploy to GCP Cloud Run | Manual | ~8 min | -| [deploy-azure.yml](#azure-deployment) | Deploy to Azure Container Apps | Manual | ~10 min | -| [deploy-all.yml](#multi-cloud-deployment) | Deploy to all clouds | Manual, Release | ~15 min | -| [database-migration.yml](#database-migrations) | Run DB migrations | Manual | ~5 min | -| [rollback.yml](#rollback) | Rollback deployment | Manual | ~5 min | +## ci.yml -> **Note:** Frontend deployment is handled automatically via Terraform as part of the backend deployment workflows. +Jobs: `lint` (golangci-lint, `go vet`, cyclomatic complexity), `workflow-lint` (actionlint, +zizmor), `unit-tests` (coverage check, warns under 80%), `mcp-build` (builds +`bin/cudly-mcp` on an OS matrix), `integration-tests`, `security-scan` (govulncheck, gosec, +Trivy) and `snyk-scan`. ---- +The `ci-success` job aggregates `lint`, `workflow-lint`, `unit-tests`, `integration-tests`, +`security-scan` and `mcp-build`. It fails unless each of them reports exactly `success`, so a +skipped job cannot pass the gate. `snyk-scan` is not in that list. It skips fork PRs, and its +step is `continue-on-error`. Use `ci-success` as the required status check. -## CI Workflow +## pre-commit.yml -**File:** `ci.yml` +Installs the hook tooling (gocyclo, Trivy, git-secrets, pre-commit), self-tests the +git-secrets allowlist, then runs `pre-commit` against `.pre-commit-config.yaml`. -### Purpose +## mcp-server-json.yml -Runs comprehensive quality checks on every pull request and push to main branch. +`validate-schema` checks `server.json` against the MCP Registry schema. On `v*` tags, +`assert-version-matches-tag` also requires the `server.json` version to equal the tag. -### Jobs +## release.yml -1. **Lint** - golangci-lint, go vet -2. **Unit Tests** - Go tests with race detection, coverage reporting -3. **Integration Tests** - Tests with real PostgreSQL -4. **Docker Build** - Build and test Docker image -5. **Terraform Validate** - Validate all Terraform configs (AWS, GCP, Azure) -6. **Security Scan** - gosec, trivy, tfsec -7. **Snyk Scan** - Dependency vulnerability scanning -8. **E2E Tests** - Docker Compose end-to-end tests -9. **Cost Estimate** - Infracost cost estimation (PR only) +Pushing a `v*` tag runs, in order: `consistency-gate` (`server.json` and +`mcpb/manifest.json` versions match the tag), `test`, `goreleaser` (binaries and GitHub +Release), `mcpb` (bundle asset) and `publish-registry` (MCP Registry via GitHub OIDC). +Only one run per ref at a time. The publishing jobs use the `release` environment. -### Triggers - -- Pull requests to `main` or `develop` -- Pushes to `main` or `develop` -- Manual dispatch - -### Required Secrets - -- `SNYK_TOKEN` (optional - for Snyk scanning) -- `INFRACOST_API_KEY` (optional - for cost estimation) - -### Required Variables - -- `GO_VERSION` (default: 1.26.9) - -### Example - -```bash -# Automatically runs on PR -git push origin feature-branch - -# Or trigger manually -gh workflow run ci.yml -``` - ---- - -## AWS Lambda Deployment - -**File:** `deploy-aws-lambda.yml` - -### Purpose - -Deploy CUDly to AWS Lambda with Function URL. Serverless, event-driven platform. - -### Jobs - -1. **Prepare** - Determine environment and image tag -2. **Build & Push** - Build Docker image, push to ECR -3. **Deploy** - Deploy with Terraform -4. **Test** - Health check and smoke tests - -### Triggers - -- Push to `main` (deploys to dev) -- Release creation (deploys to prod) -- Manual dispatch with environment selection - -### Required Secrets - -- `AWS_ACCESS_KEY_ID` -- `AWS_SECRET_ACCESS_KEY` - -### Required Variables - -- `AWS_REGION` (default: us-east-1) -- `AWS_ACCOUNT_ID` -- `ECR_REPOSITORY` (default: cudly) - -### Example - -```bash -# Deploy to dev -gh workflow run deploy-aws-lambda.yml -f environment=dev - -# Push to main also deploys to dev -git push origin main - -# Deploy to prod -gh release create v1.0.0 -``` - -### Output - -- Function URL: `https://.lambda-url.us-east-1.on.aws` -- Deployment info artifact - ---- - -## AWS Fargate Deployment - -**File:** `deploy-aws-fargate.yml` - -### Purpose - -Deploy CUDly to AWS ECS Fargate with ALB. Always-on containerized platform. - -### Jobs - -1. **Build & Push** - Build Docker image, push to ECR -2. **Deploy** - Deploy with Terraform (Fargate mode) -3. **Test** - Health check verification - -### Triggers - -- Manual dispatch only - -### Required Secrets - -- Same as AWS Lambda - -### Example - -```bash -# Deploy to staging with Fargate -gh workflow run deploy-aws-fargate.yml -f environment=staging -``` - ---- - -## GCP Deployment - -**File:** `deploy-gcp.yml` - -### Purpose - -Deploy CUDly to GCP Cloud Run. Serverless container platform. - -### Jobs - -1. **Build & Deploy** - Build, push to Artifact Registry, deploy with Terraform -2. **Test** - Health check and smoke tests - -### Triggers - -- Manual dispatch -- Called by deploy-all.yml - -### Required Secrets - -- `GCP_SA_KEY` (Service Account JSON with permissions) -- `GCP_PROJECT_ID` - -### Required Variables - -- `GCP_REGION` (default: us-central1) -- `ARTIFACT_REGISTRY_REPO` (default: cudly) - -### Example - -```bash -# Deploy to GCP dev -gh workflow run deploy-gcp.yml -f environment=dev -``` - -### Output - -- Service URL: `https://cudly--uc.a.run.app` - ---- - -## Azure Deployment - -**File:** `deploy-azure.yml` - -### Purpose - -Deploy CUDly to Azure Container Apps. Serverless container platform with built-in HTTPS. - -### Jobs - -1. **Build & Deploy** - Build, push to ACR, deploy with Terraform -2. **Test** - Health check and smoke tests - -### Triggers - -- Manual dispatch -- Called by deploy-all.yml - -### Required Secrets - -- `AZURE_CREDENTIALS` (Service Principal JSON) -- `AZURE_SUBSCRIPTION_ID` - -### Required Variables - -- `AZURE_LOCATION` (default: eastus) -- `ACR_NAME` (default: cudlyacr) -- `RESOURCE_GROUP` (default: cudly-rg) - -### Example - -```bash -# Deploy to Azure staging -gh workflow run deploy-azure.yml -f environment=staging -``` - -### Output - -- App URL: `https://..azurecontainerapps.io` - ---- - -## Multi-Cloud Deployment - -**File:** `deploy-all.yml` - -### Purpose - -Orchestrate deployment to multiple cloud providers in parallel. - -### Jobs - -1. **Determine Strategy** - Choose which clouds to deploy to -2. **Deploy AWS Lambda** - Parallel deployment -3. **Deploy AWS Fargate** - Parallel deployment (optional) -4. **Deploy GCP** - Parallel deployment -5. **Deploy Azure** - Parallel deployment -6. **Notify** - Aggregate results - -### Triggers - -- Manual dispatch with provider selection -- Release creation (deploys to all clouds in prod) - -### Required Secrets - -- All secrets from individual deployment workflows - -### Deployment Options - -- `all` - Deploy to AWS, GCP, and Azure -- `aws-only` - AWS Lambda only -- `gcp-only` - GCP Cloud Run only -- `azure-only` - Azure Container Apps only -- `aws-gcp` - AWS and GCP -- `aws-azure` - AWS and Azure -- `gcp-azure` - GCP and Azure - -### Example - -```bash -# Deploy to all clouds (staging) -gh workflow run deploy-all.yml -f environment=staging -f deploy_to=all - -# Deploy to AWS and GCP (prod) -gh workflow run deploy-all.yml -f environment=prod -f deploy_to=aws-gcp - -# Automatic on release -gh release create v1.0.0 -``` - -### Benefits - -- **Disaster Recovery** - Multi-cloud redundancy -- **Cost Optimization** - Compare costs across providers -- **Testing** - Validate across all platforms -- **Global Reach** - Deploy to optimal regions per cloud - ---- - -## Database Migrations - -**File:** `database-migration.yml` - -### Purpose - -Apply or rollback database schema migrations across cloud providers. - -### Jobs - -1. **Validate** - Safety checks -2. **Migrate AWS** - Run golang-migrate on Aurora -3. **Migrate GCP** - Run golang-migrate on Cloud SQL -4. **Migrate Azure** - Run golang-migrate on Flexible Server - -### Triggers - -- Manual dispatch only (safety measure) -- Can be called by deployment workflows - -### Required Secrets - -- `DB_PASSWORD_AWS` -- `DB_PASSWORD_GCP` -- `DB_PASSWORD_AZURE` -- Cloud credentials (same as deployment workflows) - -### Required Variables - -- Database endpoints per environment - -### Migration Directions - -- `up` - Apply migrations (default) -- `down` - Rollback migrations (DANGEROUS) - -### Example - -```bash -# Apply all migrations to AWS dev -gh workflow run database-migration.yml \ - -f cloud=aws \ - -f environment=dev \ - -f direction=up - -# Rollback last 2 migrations on GCP staging -gh workflow run database-migration.yml \ - -f cloud=gcp \ - -f environment=staging \ - -f direction=down \ - -f steps=2 - -# Rollback 1 migration on AWS prod (requires typed confirmation) -gh workflow run database-migration.yml \ - -f cloud=aws \ - -f environment=prod \ - -f direction=down \ - -f steps=1 \ - -f confirm=rollback-prod - -# Apply to all clouds -gh workflow run database-migration.yml \ - -f cloud=all \ - -f environment=prod \ - -f direction=up -``` - -### Safety Features - -- **Validation** - Checks migration files exist before running -- **Explicit steps required** - `direction=down` requires an explicit positive `steps` value; `steps=0` (the default, which would run `down -all` and drop the entire schema) is rejected -- **Production confirmation** - `direction=down` on `environment=prod` additionally requires typing `rollback-prod` in the `confirm` input; omitting or mistyping it blocks the run -- **Defense in depth** - each migrate job independently re-validates the positive-steps constraint, so a future validate regression cannot reach `down -all` -- **Audit Trail** - Records all migrations in the step summary - ---- - -## Rollback - -**File:** `rollback.yml` - -### Purpose - -Quickly rollback to a previous deployment version by redeploying a known-good Docker image. - -### Jobs - -1. **Validate** - Validate image tag and construct image URI -2. **Rollback** - Confirm the image exists in the registry, then deploy it with Terraform -3. **Summary** - Create audit record - -Image existence is verified *inside* each rollback job rather than in a -standalone job. A separate verify job would have to assume the same cloud -deploy role while carrying no `environment:` binding, which is exactly the -ungated-but-credentialed shape that made the workflow exploitable. The -tradeoff is that a rollback to a nonexistent tag now fails after the -environment approval rather than before it. - -### Triggers - -- Manual dispatch only (safety measure) - -### Required Secrets - -- Cloud credentials (same as deployment workflows) - -### Example - -```bash -# Rollback AWS Lambda production to previous version -gh workflow run rollback.yml \ - -f cloud=aws-lambda \ - -f environment=prod \ - -f image_tag=sha-abc123 \ - -f reason="Critical bug in v1.2.3" - -# Rollback GCP staging -gh workflow run rollback.yml \ - -f cloud=gcp \ - -f environment=staging \ - -f image_tag=v1.2.2 -``` - -### Safety Features - -- **Image Verification** - Confirms image exists before deploying -- **Audit Trail** - Records all rollbacks (365 day retention) -- **Reason Tracking** - Requires reason for accountability -- **Manual Only** - Cannot be triggered automatically - -### Finding Image Tags - -```bash -# AWS ECR -aws ecr list-images --repository-name cudly - -# GCP Artifact Registry -gcloud artifacts docker images list -docker.pkg.dev///cudly - -# Azure ACR -az acr repository show-tags --name cudlyacr --repository cudly -``` - ---- - -## Setup Guide - -### 1. Configure GitHub Secrets - -**AWS:** - -```bash -# Create secrets -gh secret set AWS_ACCESS_KEY_ID -gh secret set AWS_SECRET_ACCESS_KEY -gh secret set DB_PASSWORD_AWS -``` - -**GCP:** - -```bash -# Create service account and download JSON -gcloud iam service-accounts create cudly-cicd --project= - -# Grant permissions -gcloud projects add-iam-policy-binding \ - --member="serviceAccount:cudly-cicd@.iam.gserviceaccount.com" \ - --role="roles/run.admin" - -# Create and download key -gcloud iam service-accounts keys create key.json \ - --iam-account=cudly-cicd@.iam.gserviceaccount.com - -# Set secrets -gh secret set GCP_SA_KEY < key.json -gh secret set GCP_PROJECT_ID -b"" -gh secret set DB_PASSWORD_GCP -``` - -**Azure:** - -```bash -# Create service principal -az ad sp create-for-rbac --name cudly-cicd --sdk-auth > azure-credentials.json - -# Set secrets -gh secret set AZURE_CREDENTIALS < azure-credentials.json -gh secret set AZURE_SUBSCRIPTION_ID -b"" -gh secret set DB_PASSWORD_AZURE -``` - -**Optional:** - -```bash -gh secret set SNYK_TOKEN -gh secret set INFRACOST_API_KEY -``` - -### 2. Configure GitHub Variables - -```bash -# AWS -gh variable set AWS_REGION -b"us-east-1" -gh variable set AWS_ACCOUNT_ID -b"123456789012" -gh variable set ECR_REPOSITORY -b"cudly" - -# GCP -gh variable set GCP_REGION -b"us-central1" -gh variable set ARTIFACT_REGISTRY_REPO -b"cudly" - -# Azure -gh variable set AZURE_LOCATION -b"eastus" -gh variable set ACR_NAME -b"cudlyacr" -gh variable set RESOURCE_GROUP -b"cudly-rg" - -# Frontend -gh variable set CLOUD_PROVIDER -b"aws" -gh variable set FRONTEND_BUCKET -b"cudly-frontend-prod" -gh variable set CLOUDFRONT_DISTRIBUTION_ID -b"E1234567890" -gh variable set API_URL -b"https://api.cudly.example.com" -``` - -### 3. Set Up Environments - -GitHub Environments provide deployment protection and environment-specific secrets: - -1. Go to **Settings** → **Environments** -2. Create environments: - - `aws-lambda-dev`, `aws-lambda-staging`, `aws-lambda-prod` - - `aws-fargate-dev`, `aws-fargate-staging`, `aws-fargate-prod` - - `gcp-dev`, `gcp-staging`, `gcp-prod` - - `azure-dev`, `azure-staging`, `azure-prod` - - `frontend-aws-dev`, etc. - -3. Configure protection rules: - - **Production**: Require approvals, restrict to main branch - - **Staging**: Optional approvals - - **Dev**: No restrictions - ---- - -## Troubleshooting - -### CI Workflow Fails - -**Unit tests fail:** +## Running the checks locally ```bash -# Run locally +make build # bin/cudly-mcp make test-unit +make vet +make lint +make ci # fmt, vet, complexity, test-unit, security-scan +pip install pre-commit && pre-commit install # then: pre-commit run --all-files ``` - -**Integration tests fail:** - -```bash -# Run with testcontainers -make test-integration -``` - -**Security scan fails:** - -```bash -# Run locally -make security-scan-all -``` - -### Deployment Fails - -**AWS - Image not found:** - -```bash -# Check ECR -aws ecr describe-images --repository-name cudly --region us-east-1 - -# Re-push image -docker push .dkr.ecr.us-east-1.amazonaws.com/cudly:latest -``` - -**GCP - Permission denied:** - -```bash -# Check service account permissions -gcloud projects get-iam-policy - -# Grant missing roles -gcloud projects add-iam-policy-binding \ - --member="serviceAccount:@.iam.gserviceaccount.com" \ - --role="roles/run.admin" -``` - -**Azure - Resource not found:** - -```bash -# Verify resource group exists -az group show --name cudly-rg - -# Create if missing -az group create --name cudly-rg --location eastus -``` - -### Database Migration Fails - -**Connection timeout:** - -- Check database security groups/firewall rules -- Verify VPN/bastion access if required -- Check database is running - -**Migration already applied:** - -```bash -# Check current version -migrate -path migrations -database version - -# Force version (use with caution) -migrate -path migrations -database force -``` - ---- - -## Best Practices - -### 1. Branch Protection - -- Require CI to pass before merging -- Require code reviews -- Restrict direct pushes to main - -### 2. Environment Strategy - -- **Dev**: Auto-deploy on push to develop branch -- **Staging**: Auto-deploy on push to main -- **Prod**: Manual approval required, deploy on release - -### 3. Rollback Strategy - -- Keep last 10 images in each registry -- Document rollback procedures -- Test rollback in staging first - -### 4. Monitoring - -- Set up CloudWatch/Cloud Logging alerts -- Monitor deployment success rates -- Track deployment frequency - -### 5. Security - -- Rotate secrets regularly -- Use environment protection rules -- Enable secret scanning -- Review security scan results - ---- - -## Metrics & Monitoring - -### Workflow Success Rate - -```bash -# View recent workflow runs -gh run list --limit 50 - -# View specific workflow -gh run list --workflow=ci.yml --limit 20 -``` - -### Deployment Frequency - -- Target: Multiple deployments per day -- Track via GitHub Actions insights - -### Mean Time to Recovery (MTTR) - -- Use rollback workflow for quick recovery -- Target: < 15 minutes - -### CI Duration - -- Unit tests: ~5 min -- Integration tests: ~3 min -- Security scans: ~2 min -- Total: ~10 min target - ---- - -## Additional Resources - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [AWS ECR Documentation](https://docs.aws.amazon.com/ecr/) -- [GCP Artifact Registry](https://cloud.google.com/artifact-registry/docs) -- [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/) -- [golang-migrate](https://github.com/golang-migrate/migrate) -- [Terraform Cloud](https://www.terraform.io/cloud) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d0975e..16fa7ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ By participating in this project, you agree to maintain a respectful and inclusi 1. **Search existing issues** - Check if the bug has already been reported 2. **Create a detailed report** including: - - CUDly version (`./cudly --version`) + - CUDly MCP server version (`bin/cudly-mcp` has no version flag: give the release tag, or `git rev-parse --short HEAD` for a source build) - Go version (`go version`) - Operating system and architecture - Cloud provider and service affected diff --git a/cmd/cudly-mcp/main.go b/cmd/cudly-mcp/main.go index 18d831a..5e2d527 100644 --- a/cmd/cudly-mcp/main.go +++ b/cmd/cudly-mcp/main.go @@ -1,10 +1,8 @@ // Command cudly-mcp runs the CUDly MCP server on stdio, exposing CUDly's // RI/SP/CUD search and purchase tools to any MCP client (e.g. Claude Code -// via ~/.claude/mcp.json). See mcp/README.md for setup and usage. +// via ~/.claude/mcp.json). See README.md for setup and usage. // -// This binary is intentionally separate from the ri-helper CLI (cmd/main.go): -// it is a local/desktop MCP server, never a Lambda handler, and is kept out -// of iac/ and terraform/ (see mcp/README.md "Deployment model"). +// This binary is a local/desktop MCP server, never a Lambda handler. package main import ( diff --git a/docs/mcp-annotations-decisions.md b/docs/mcp-annotations-decisions.md index 2fe6b83..6f85124 100644 --- a/docs/mcp-annotations-decisions.md +++ b/docs/mcp-annotations-decisions.md @@ -3,9 +3,8 @@ This repo has no existing ADR/`docs/decisions/` convention (checked at implementation time: no `docs/adr/` or `docs/decisions/` directory), so this rationale lives here instead of in a numbered ADR. -Context: `docs/plans/mcp/05-store.md` Phase A and `docs/plans/mcp/00-scope.md` -§6 (not checked into this repo -- local planning notes for the MCP -store-readiness workstream). +Context: the MCP store-readiness workstream (planning notes are not +checked into this repo). ## Why annotations at all @@ -26,7 +25,7 @@ specifically: `ReadOnlyHint bool` and `IdempotentHint bool` have no such trap (their zero value, `false`, is already the conservative reading), but every Descriptor still sets them explicitly via `readOnlyAnnotations`/`purchaseAnnotations` -(`mcp/tools/registry.go`) so the intent is legible at every call site rather +(`tools/registry.go`) so the intent is legible at every call site rather than resting on an implicit zero value. ## D1 -- destructiveHint=true on every purchase tool @@ -51,7 +50,7 @@ therefore set `DestructiveHint: boolPtr(true)`. ## A5 -- idempotentHint=false on every purchase tool, always Every purchase tool's `IdempotentHint` is `false`, unconditionally, even -though `ExecutePurchase` (`mcp/tools/purchase.go`) already derives an +though `ExecutePurchase` (`tools/purchase.go`) already derives an idempotency key from the request and refuses/dedupes a byte-identical retry. Rejected alternative: `IdempotentHint=true`, on the theory that the @@ -77,7 +76,7 @@ Rejected because: ## Per-tool annotation table -Built from `mcp/tools/registry.go`'s `readOnlyAnnotations`/ +Built from `tools/registry.go`'s `readOnlyAnnotations`/ `purchaseAnnotations` helpers and each tool's `Descriptor()`. Every tool sets `ReadOnlyHint`, `DestructiveHint`, `IdempotentHint`, and `OpenWorldHint` explicitly; none rely on the SDK's nil-defaults-to-true behavior. diff --git a/server.go b/server.go index 4cc5190..6d02e04 100644 --- a/server.go +++ b/server.go @@ -1,5 +1,5 @@ // Package mcp wires the CUDly MCP server: it registers every tool from -// mcp/tools onto a github.com/modelcontextprotocol/go-sdk/mcp.Server and +// tools/ onto a github.com/modelcontextprotocol/go-sdk/mcp.Server and // builds the cudly_list_commitment_actions catalog from those same tools' // descriptors, so the live tool set and the discoverability catalog can // never drift apart. diff --git a/server_test.go b/server_test.go index 55eb5e6..95b9f18 100644 --- a/server_test.go +++ b/server_test.go @@ -136,7 +136,7 @@ func TestRegistryNonEmpty(t *testing.T) { // without any AWS credentials configured in this test environment -- // confirming dry_run=true never reaches the real purchase path even through // the full protocol stack, not just the Go-level unit tests in -// mcp/tools/aws_ec2_ri_test.go. +// tools/aws_ec2_ri_test.go. func TestEndToEndSearchThenDryRunPurchase(t *testing.T) { t.Parallel() ctx := context.Background() @@ -252,7 +252,7 @@ func TestRealPurchaseToolsDocumentMoneyImpactAndDryRun(t *testing.T) { // TestAzureComputeRIPurchaseSchemaExcludesPartialUpfront proves the // cudly_azure_compute_ri_purchase tool's advertised payment_option enum // never offers partial-upfront: Azure has no partial-upfront billing plan -// (mcp/tools/azure_compute_ri.go rejects it at runtime as defense in depth), +// (tools/azure_compute_ri.go rejects it at runtime as defense in depth), // so the schema itself must not invite a caller to pick a value the tool // can only ever refuse. Drives the real MCP protocol (ListTools), not a // bare Go function call, so it catches a regression in what a real client diff --git a/tools/aws_savingsplans.go b/tools/aws_savingsplans.go index 994ee48..e3dc0bf 100644 --- a/tools/aws_savingsplans.go +++ b/tools/aws_savingsplans.go @@ -189,7 +189,7 @@ func savingsPlanRecommendationFromArgs(args savingsPlansPurchaseArgs) (rec commo // Account-level plan types (Compute/SageMaker/Database) ignore region // entirely, so pin the canonical one rather than honoring whatever the // caller passed. region flows into rec.Region and from there into - // idempotencyKeyFor (mcp/tools/purchase.go), so letting an IGNORED input + // idempotencyKeyFor (tools/purchase.go), so letting an IGNORED input // through would fork purchase identity: buying a $10/hr Compute SP once // without region and then re-issuing the identical call with // region="eu-west-1" (a self-correction, or a retry that fills the field diff --git a/tools/azure_compute_ri.go b/tools/azure_compute_ri.go index ccae651..2b85e21 100644 --- a/tools/azure_compute_ri.go +++ b/tools/azure_compute_ri.go @@ -173,7 +173,7 @@ func azureComputeRecommendationFromArgs(args azureComputeRIPurchaseArgs) (rec co } // payment_option defaults to no-upfront (matching the CLI's --payment - // default, cmd/main.go) when the caller omits it -- an omitted string + // default in the cloud-commitments-cli repo) when the caller omits it -- an omitted string // field arrives as "" and is never confused with an explicit, // unrecognized value (feedback_no_silent_fallbacks: the default is // applied here, explicitly, not fabricated deeper in the stack). diff --git a/tools/azure_compute_ri_test.go b/tools/azure_compute_ri_test.go index 6bcef30..7f97c00 100644 --- a/tools/azure_compute_ri_test.go +++ b/tools/azure_compute_ri_test.go @@ -146,7 +146,7 @@ func TestAzureComputeRecommendationFromArgsAcceptsNoUpfront(t *testing.T) { // TestAzureComputeRecommendationFromArgsDefaultsToNoUpfront proves omitting // payment_option defaults to no-upfront (matching the CLI's --payment -// default, cmd/main.go), not to Azure's raw API default (all-upfront) and +// default in the cloud-commitments-cli repo), not to Azure's raw API default (all-upfront) and // not to an error. func TestAzureComputeRecommendationFromArgsDefaultsToNoUpfront(t *testing.T) { t.Parallel() diff --git a/tools/enums.go b/tools/enums.go index 7b8157e..1760600 100644 --- a/tools/enums.go +++ b/tools/enums.go @@ -1,5 +1,5 @@ // Package tools implements the individual MCP tool handlers exposed by the -// CUDly MCP server (mcp/server.go), plus the shared validation and purchase +// CUDly MCP server (server.go), plus the shared validation and purchase // harness they all build on. package tools @@ -82,8 +82,7 @@ func (t TermYears) RecommendationTerm() string { return fmt.Sprintf("%dyr", int(t)) } -// SPType is the AWS Savings Plans product family (--include-sp-types in the -// CLI, cmd/main.go:112). +// SPType is the AWS Savings Plans product family. type SPType string const ( diff --git a/tools/list_commitment_actions.go b/tools/list_commitment_actions.go index b75db65..82f1327 100644 --- a/tools/list_commitment_actions.go +++ b/tools/list_commitment_actions.go @@ -47,7 +47,7 @@ type listCommitmentActionsTool struct { } // NewListCommitmentActions builds the cudly_list_commitment_actions tool -// from descriptors -- the same slice of Descriptor values mcp/server.go +// from descriptors -- the same slice of Descriptor values server.go // collects from every other tool's Descriptor() method, so this catalog is // generated from the live registry rather than hand-duplicated in code or // docs. @@ -56,7 +56,7 @@ func NewListCommitmentActions(descriptors []Descriptor) Registration { } // ListCommitmentActionsDescriptor returns the static Descriptor for -// cudly_list_commitment_actions itself. It is exported so mcp/server.go can +// cudly_list_commitment_actions itself. It is exported so server.go can // include this tool in its own catalog: the descriptors slice passed to // NewListCommitmentActions must be assembled (and thus known) before the // tool exists, so its own entry can't come from calling Descriptor() on an diff --git a/tools/registry.go b/tools/registry.go index 87644d7..0a93cc2 100644 --- a/tools/registry.go +++ b/tools/registry.go @@ -3,7 +3,7 @@ package tools import "github.com/modelcontextprotocol/go-sdk/mcp" // Descriptor is the source-of-truth metadata for one MCP tool. Every tool -// file builds one and both mcp/server.go (to know what to register) and +// file builds one and both server.go (to know what to register) and // cudly_list_commitment_actions (to know what to advertise) read it, so the // live tool set and the discoverability catalog can never drift apart -- // there is exactly one place each tool's name/description/example prompts @@ -85,10 +85,9 @@ func readOnlyAnnotations(title string, openWorld bool) *mcp.ToolAnnotations { // "purchasing is additive, not destructive" reading was considered and // rejected), OpenWorldHint true (every purchase reaches a live cloud // provider API), and IdempotentHint FALSE on every purchase tool, always -// (A5, 00-scope.md §6 decision register) -- even though ExecutePurchase -// derives an idempotency key from the request and dedupes an identical -// retry (mcp/tools/purchase.go), the hint can't express that conditional -// safety: idempotency_nonce is caller-supplied, so one optional string +// -- even though ExecutePurchase derives an idempotency key from the request +// and dedupes an identical retry (tools/purchase.go), the hint can't express +// that conditional safety: idempotency_nonce is caller-supplied, so one optional string // turns an otherwise-identical "safe retry" into a second real purchase, // and provider-side dedupe (AWS ClientToken, Azure/GCP equivalents) is // time- and scope-bounded, not permanent. The failure asymmetry only points