Skip to content

Feat: Add MCP Registry Plan#5

Open
modupei wants to merge 7 commits intomainfrom
issues/mcp
Open

Feat: Add MCP Registry Plan#5
modupei wants to merge 7 commits intomainfrom
issues/mcp

Conversation

@modupei
Copy link
Copy Markdown

@modupei modupei commented Dec 17, 2025

This pull request introduces the technical plan for the ZSoftly Model Context Protocol (MCP) Registry. The plan is detailed in the MCP_Registry_Plan.md document, which is added to the mcp directory.

Summary by CodeRabbit

  • New Features

    • Adds a runnable MCP Registry service with REST APIs to register, list, and search agents and manage capabilities; includes DB-backed models and API validation.
  • Documentation

    • Adds a comprehensive technical plan: API contract, lifecycle, RBAC, security model, observability, data governance, and phased rollout roadmap.
  • Chores

    • Adds container, packaging, project ignores, and full infrastructure templates for deployment (VPC, ALB, ECS/Fargate, RDS, S3, ECR, Secrets).

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

Adds a new MCP Registry design and initial FastAPI implementation with SQLAlchemy models, Pydantic schemas, CRUD and REST endpoints, Docker/pyproject scaffolding, and Terraform infra to provision VPC, ALB, ECS, RDS, ECR, S3, Secrets Manager, and IAM for deployment and operation. (34 words)

Changes

Cohort / File(s) Change Summary
Design doc
mcp/MCP_Registry_Plan.md
New technical plan specifying registry API, agent.json manifest contract, data model, security/RBAC, deployment options, observability, governance, and phased roadmap.
API & app code
mcp/mcp_registry/main.py, mcp/mcp_registry/crud.py, mcp/mcp_registry/schemas.py, mcp/mcp_registry/database.py
New FastAPI app, endpoints (agents, capabilities, search), SQLAlchemy models (Agent, Capability, association), Pydantic schemas (with forward refs), DB session/get_db, CRUD operations, JSONB openapi_spec field, unique (name,version), and startup DB init.
Packaging & container
mcp/pyproject.toml, mcp/Dockerfile, mcp/.gitignore
New pyproject (deps), Dockerfile to run Uvicorn, and comprehensive Python .gitignore.
Infrastructure (Terraform)
mcp/terraform/*
(main.tf, providers.tf, variables.tf, outputs.tf, alb.tf, ecs.tf, ecr.tf, rds.tf, s3.tf, iam.tf, secretsmanager.tf)
New Terraform configs to provision networking (VPC/subnets/IGW/NAT), ALB (HTTP→HTTPS redirect), ECS Fargate task & service, ECR repo, RDS Postgres, S3 bucket for OpenAPI specs, Secrets Manager secret/version, IAM roles/policies, provider/vars/outputs.
Misc / scaffold
(project scaffold)
Added initial repository scaffolding for building, running, and deploying the MCP Registry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer/Client
  participant API as MCP Registry (FastAPI)
  participant DB as PostgreSQL
  participant S3 as OpenAPI Specs (S3)

  rect rgb(220,230,255)
    note right of Dev: Agent registration
    Dev->>API: POST /agents (metadata + spec)
    API->>S3: PUT spec -> returns s3_uri + checksum
    S3-->>API: s3_uri + checksum
    API->>DB: INSERT agent (metadata + s3_uri + checksum + version)
    DB-->>API: 201 Created
    API-->>Dev: 201 Created (agent id, version, s3_uri)
  end

  rect rgb(230,255,220)
    note right of Dev: Discovery / retrieval
    Dev->>API: GET /agents or /search?capability=...
    API->>DB: Query/filter agents
    DB-->>API: metadata results
    alt include spec content
      API->>S3: GET spec by s3_uri
      S3-->>API: spec
      API-->>Dev: 200 (metadata + spec)
    else spec uri only
      API-->>Dev: 200 (metadata + s3_uri)
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • zsoftly/business#351 — Implements the MCP Registry design and initial code/infra artifacts requested by that issue.

Poem

🐰 I hopped through specs and built a nest,
Manifests snug and versions dressed,
Endpoints hum beneath the ground,
S3 and Postgres, safe and sound,
Hooray—registry ready, hop around! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: Add MCP Registry Plan' is partially related to the changeset. While it accurately describes the main planning document, it understates the scope—the PR also includes complete implementation code (FastAPI app, database models, CRUD operations, Dockerfile) and Terraform infrastructure-as-code for AWS deployment, not just the plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8136ff7 and 68bdc0d.

📒 Files selected for processing (1)
  • mcp/MCP_Registry_Plan.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
mcp/MCP_Registry_Plan.md

47-47: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


50-50: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


54-54: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


57-57: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


68-68: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


71-71: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


75-75: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


78-78: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


94-94: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


162-162: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


163-163: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


165-165: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


173-173: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


189-189: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


190-190: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


191-191: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


210-210: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


211-211: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


212-212: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


246-246: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


247-247: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


248-248: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


249-249: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


250-250: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


252-252: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


253-253: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


254-254: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


264-264: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


265-265: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


266-266: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


267-267: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


268-268: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


269-269: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


275-275: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


276-276: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


277-277: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


278-278: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


284-284: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


285-285: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


286-286: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


287-287: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


288-288: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

Comment thread mcp/MCP_Registry_Plan.md Outdated
Comment thread mcp/MCP_Registry_Plan.md Outdated
Comment thread mcp/MCP_Registry_Plan.md Outdated
Comment thread mcp/MCP_Registry_Plan.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
mcp/MCP_Registry_Plan.md (1)

259-291: Implementation roadmap provides a realistic path to production.

The three-phase approach (MVP, CLI/Search, Hardening) is well-scoped and achievable. Phase 1 focuses on core functionality, Phase 2 improves DX with tooling, and Phase 3 hardens for production readiness. Pilot team onboarding in Phase 1 is a smart validation step.

Minor enhancement suggestion: Consider adding observability and alerting requirements to Phase 3 explicitly (e.g., API latency/error rate SLOs, database performance metrics, failed auth attempts). These are foundational for operational confidence.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68bdc0d and 0d8fb55.

📒 Files selected for processing (1)
  • mcp/MCP_Registry_Plan.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
mcp/MCP_Registry_Plan.md

33-33: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


34-34: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


35-35: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


36-36: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


37-37: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


39-39: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


40-40: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


41-41: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


140-140: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


141-141: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


142-142: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


143-143: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


144-144: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


145-145: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


146-146: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


147-147: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


148-148: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


149-149: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


150-150: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


151-151: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


163-163: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


164-164: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


165-165: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


166-166: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


167-167: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


168-168: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


174-174: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


175-175: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


176-176: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


177-177: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


189-189: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


190-190: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


191-191: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


192-192: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


193-193: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


199-199: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


205-205: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


211-211: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


212-212: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


213-213: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


214-214: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


215-215: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


219-219: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


220-220: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


228-228: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


229-229: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


230-230: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


231-231: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


232-232: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


236-236: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


237-237: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


241-241: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


242-242: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


243-243: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


244-244: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


248-248: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


249-249: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


250-250: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


251-251: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


252-252: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


253-253: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


254-254: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


255-255: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


256-256: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


257-257: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


265-265: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


266-266: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


267-267: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


268-268: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


269-269: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


270-270: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


271-271: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


272-272: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


276-276: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


277-277: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


278-278: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


279-279: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


280-280: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


281-281: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


285-285: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


286-286: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


287-287: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


288-288: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


289-289: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


290-290: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


291-291: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (3)
mcp/MCP_Registry_Plan.md (3)

1-50: Excellent technical planning document—clear vision and well-executed.

The MCP Registry plan is comprehensive and well-structured. The distinction between a discovery service and a hosting platform is clearly articulated, and the proposed API endpoints, data model, and phased roadmap are all pragmatic and implementable.

Key strengths:

  • Clear agent.json manifest schema with all necessary fields (identity, contract, location).
  • Pragmatic architecture (FastAPI + PostgreSQL) aligned with operational simplicity.
  • Security model combining API key authentication with role-based access control (Reader/Publisher/Admin).
  • Realistic phased roadmap with MVP-first approach.

90-120: Architecture diagram and components are clear and well-explained.

The system component breakdown (REST API, PostgreSQL, Validation Logic) is sensible. The data model with indexed fields on name, version, environment, and tags (JSONB) will support efficient discovery and search operations. The use of soft deletes for dependency safety is pragmatic.


181-220: Security and multi-environment strategy is thoughtful.

The RBAC design (Reader/Publisher/Admin) is appropriate for an internal registry. The multi-environment support through the environment field in agent.json is well-considered, though you may want to document the recommended approach for environment-specific API keys as a future enhancement to enforce stricter isolation if governance requirements evolve.

The VPC-based deployment with optional external access via API Gateway is sound; consider documenting the expected access patterns (internal-only vs. conditional external access) in operational runbooks.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
mcp/MCP_Registry_Plan.md (1)

1-463: Markdown linting issues remain unresolved.

The static analysis tool reports numerous MD007 (incorrect list indentation) and MD036 (emphasis used instead of heading) violations throughout the document. These issues were noted in previous review cycles and marked as addressed in commit 0d8fb55, but they still appear in the current version (lines 161, 239, and many list items using 2–4 space indentation instead of proper nesting).

Recommendation: Run markdownlint-cli2 on the file and fix the remaining formatting violations to ensure consistency and compliance with the project's markdown standards.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d8fb55 and a76b24a.

📒 Files selected for processing (1)
  • mcp/MCP_Registry_Plan.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
mcp/MCP_Registry_Plan.md

33-33: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


34-34: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


35-35: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


36-36: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


37-37: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


39-39: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


40-40: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


41-41: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


141-141: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


142-142: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


143-143: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


144-144: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


145-145: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


146-146: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


147-147: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


148-148: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


149-149: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


150-150: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


151-151: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


152-152: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


153-153: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


161-161: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


190-190: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


191-191: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


192-192: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


193-193: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


194-194: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


195-195: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


196-196: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


197-197: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


198-198: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


199-199: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


200-200: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


201-201: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


202-202: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


203-203: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


204-204: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


210-210: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


211-211: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


212-212: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


213-213: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


220-220: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


221-221: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


222-222: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


223-223: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


224-224: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


239-239: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


242-242: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


243-243: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


244-244: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


245-245: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


248-248: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


249-249: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


250-250: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


253-253: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


254-254: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


255-255: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


256-256: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


257-257: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


258-258: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


264-264: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


270-270: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


271-271: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


272-272: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


274-274: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


276-276: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


277-277: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


278-278: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


279-279: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


294-294: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


295-295: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


296-296: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


297-297: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


298-298: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


304-304: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


305-305: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


306-306: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


307-307: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


308-308: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


309-309: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


317-317: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


318-318: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


319-319: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


320-320: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


321-321: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


325-325: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


326-326: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


330-330: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


331-331: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


332-332: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


333-333: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


337-337: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


338-338: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


339-339: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


340-340: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


341-341: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


342-342: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


343-343: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


344-344: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


345-345: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


346-346: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


387-387: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


388-388: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


389-389: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


410-410: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


411-411: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


412-412: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


413-413: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


437-437: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


438-438: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


439-439: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


440-440: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


441-441: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


442-442: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


443-443: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


444-444: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


448-448: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


449-449: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


450-450: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


451-451: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


452-452: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


453-453: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


457-457: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


458-458: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


459-459: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


460-460: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


461-461: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


462-462: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


463-463: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (1)
mcp/MCP_Registry_Plan.md (1)

157-175: Clarify environment isolation strategy—Section 4.3 vs Section 6.4 appears contradictory.

Section 4.3 makes a strong recommendation for "One Registry per Environment" (separate instances for dev, stage, prod), but Section 6.4 states "The registry will support multiple environments within a single deployed instance" and describes an environment query parameter for logical separation. These two approaches are fundamentally different and the plan should commit to one strategy.

Recommendation: Decide whether to:

  1. Implement true environment isolation (one registry per environment), in which case Section 6.4 should be removed or retitled as "Rejected Alternative."
  2. Implement logical separation (single instance with environment field), in which case Section 4.3's strong recommendation should be downgraded or contextualized as a future enhancement to be pursued only if operationally justified.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🤖 Fix all issues with AI agents
In @mcp/mcp_registry/crud.py:
- Around line 7-8: get_agents currently returns all rows including soft-deleted
ones; update it to only return agents where models.Agent.is_deleted is False by
adding a filter(models.Agent.is_deleted == False) before offset/limit; make the
same change in get_agent (e.g., include models.Agent.is_deleted == False in the
query/filter for the agent lookup by id) and in get_agents_by_capability (ensure
the capability query or join also filters models.Agent.is_deleted == False so
deleted agents are excluded).
- Around line 51-52: Update get_agents_by_capability to exclude soft-deleted
agents: modify the query that joins models.Agent.capabilities (inside
get_agents_by_capability) to add a filter that only returns non-deleted agents
(e.g., add models.Agent.deleted_at.is_(None) or models.Agent.is_deleted == False
depending on your Agent model’s soft-delete field), while keeping the existing
capability filter models.Capability.name == capability_name.
- Around line 10-33: The issue is that create_agent calls create_capability
which currently commits its own transaction, causing orphaned capability rows if
the agent commit later fails; fix by changing create_capability to not perform
its own commit/refresh but instead accept and use the passed Session to add and
return the capability (or add an optional commit flag defaulting to False), then
in create_agent ensure all new capabilities are added to the same session and
perform a single db.commit()/db.refresh(db_agent) at the end; update any callers
of create_capability to handle the new non-committing behavior or explicitly
commit when needed.

In @mcp/mcp_registry/database.py:
- Around line 19-24: The association table agent_capability_association lacks a
primary key which can allow duplicate rows and break ORM operations; update the
Table definition for agent_capability_association to declare a composite primary
key on ('agent_id', 'capability_id') by adding PrimaryKeyConstraint or marking
the Column objects as primary_key=True, ensuring the FK columns reference
agents.id and capabilities.id and prevent duplicate associations.
- Around line 12-13: The fallback DATABASE_URL variable currently contains
hardcoded credentials (DATABASE_URL = os.getenv(...,
"postgresql://user:password@localhost/mcp_registry")), which is unsafe; change
the logic to not provide a default credentialed URL: read DATABASE_URL from the
environment only (os.getenv("DATABASE_URL")), check if it is None/empty, and if
so raise an explicit error (e.g., raise RuntimeError or ValueError with a clear
message) so the process fails fast instead of silently using insecure defaults;
update any callers that assume a value accordingly and document that
DATABASE_URL is required.

In @mcp/mcp_registry/main.py:
- Line 10: Remove the module-level table-creation call
models.Base.metadata.create_all(bind=engine) so table creation only happens
inside the explicit startup handler (on_startup); locate and delete the
standalone invocation at module import while keeping the
models.Base.metadata.create_all(bind=engine) call that lives inside the
on_startup event/function to avoid redundant execution.
- Around line 15-21: There is a duplicate get_db generator here; remove the
local definition (the def get_db that instantiates SessionLocal and yields/
closes it) and instead import the existing get_db from the database module
(e.g., from database import get_db); also remove any now-unused SessionLocal
import/usage in this file and ensure all references still call get_db so the
single shared implementation is used.

In @mcp/mcp_registry/schemas.py:
- Around line 11-16: The Capability schema currently embeds a bidirectional
relation (Capability -> List["Agent"] and Agent -> List[Capability]) which can
cause infinite recursion during serialization; create lightweight response DTOs
(e.g., CapabilitySimple and AgentSimple) that omit the back-reference and
replace the nested fields with these simple types (e.g., in Agent use
capabilities: List[CapabilitySimple] = Field(default_factory=list) and in
Capability use agents: List[AgentSimple] = Field(default_factory=list)); update
CapabilityBase/AgentBase-derived classes to use orm_mode and ensure you
import/declare the new CapabilitySimple and AgentSimple types and replace any
direct circular references in codepaths that return API responses.

In @mcp/terraform/alb.tf:
- Around line 67-76: The ALB currently exposes HTTP only via
aws_lb_listener.http; for production you must add an HTTPS listener and ACM
cert: create an aws_acm_certificate (e.g., aws_acm_certificate.main) with DNS
validation records (aws_route53_record.cert_validation) and an
aws_acm_certificate_validation resource, then replace or add an aws_lb_listener
for port 443 configured with ssl_policy and certificate_arn referencing
aws_acm_certificate.main. Also update the ALB listener default_action to forward
to aws_lb_target_group.main, ensure the ALB security group allows inbound TCP
443, and update any ECS service or terraform dependencies that assume port 80 to
depend on/route to the new listener.

In @mcp/terraform/ecr.tf:
- Around line 4-16: The ECR repo resource aws_ecr_repository.main uses unsafe
prod settings: change image_tag_mutability from "MUTABLE" to "IMMUTABLE" to
prevent tag overwrites and set force_delete from true to false to avoid deleting
repositories with images; update those attributes in the aws_ecr_repository
"main" block and ensure any environments that require different settings use
variable overrides or separate Terraform workspaces for POC vs production.

In @mcp/terraform/ecs.tf:
- Around line 33-38: The DATABASE_URL is being set as a literal
"secretsmanager:${aws_secretsmanager_secret.db_credentials.name}" in the
environment block which ECS will not resolve; instead move DATABASE_URL into the
task definition's secrets array (alongside DB_CREDENTIALS) and reference the
secret ARN/secret manager key so ECS injects the actual value, or if you must
build a non-sensitive URL from RDS outputs, put the computed plain value into
environment rather than the secretsmanager: string; update the resource that
defines environment = [...] to remove DATABASE_URL and add an entry in secrets =
[...] referencing aws_secretsmanager_secret.db_credentials (or the correct
secret ARN/key) so the container receives the real DB URL.

In @mcp/terraform/main.tf:
- Around line 15-37: The aws_subnet.public and aws_subnet.private blocks index
into data.aws_availability_zones.available.names using count.index but there is
no guard that var.public_subnet_cidrs or var.private_subnet_cidrs do not exceed
the number of available AZs; add validation to the corresponding variables
(var.public_subnet_cidrs and var.private_subnet_cidrs) or a pre-check in main
using length(data.aws_availability_zones.available.names) to ensure
length(var.*_subnet_cidrs) <=
length(data.aws_availability_zones.available.names) and surface a clear error
message, or alternatively clamp indexing with min(count.index,
length(data.aws_availability_zones.available.names)-1) to avoid out-of-range
accesses.

In @mcp/terraform/s3.tf:
- Around line 4-10: The aws_s3_bucket resource "aws_s3_bucket.main" currently
lacks versioning and at-rest encryption; update that resource to enable object
versioning and configure server-side encryption. Add a "versioning" block with
"enabled = true" to track and rollback spec changes, and add a
"server_side_encryption_configuration" (or the equivalent
aws_s3_bucket_server_side_encryption_configuration resource) with an
apply_server_side_encryption_by_default rule setting sse_algorithm to "AES256"
(or "aws:kms" plus kms_master_key_id if you require KMS) so all objects are
encrypted at rest.

In @mcp/terraform/variables.tf:
- Around line 40-45: The variable declaration for db_username currently provides
a default sensitive value ("mcpadmin"), which is insecure; remove the default
assignment from the variable "db_username" so it remains required and sensitive,
ensuring callers must explicitly set it (keep type = string and sensitive =
true) and update any docs/terraform.tfvars or module callers to supply
db_username explicitly.
🧹 Nitpick comments (12)
mcp/Dockerfile (1)

1-16: Consider adding a non-root user for security.

Running containers as root is a security risk. Consider creating a non-root user and switching to it before running the application.

🔒 Proposed security improvements
 # Use an official Python runtime as a parent image
 FROM python:3.11-slim

 # Set the working directory in the container
 WORKDIR /app

+# Create a non-root user
+RUN useradd --create-home --shell /bin/bash appuser

 # Copy the pyproject.toml file and install dependencies
 COPY pyproject.toml .
 COPY ./mcp_registry ./mcp_registry
 RUN pip install --no-cache-dir .

+# Change ownership and switch to non-root user
+RUN chown -R appuser:appuser /app
+USER appuser

 # Expose the port the app runs on
 EXPOSE 80

+# Add healthcheck
+HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
+    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:80/')" || exit 1

 # Run the application
 CMD ["uvicorn", "mcp_registry.main:app", "--host", "0.0.0.0", "--port", "80"]
mcp/mcp_registry/database.py (2)

41-41: datetime.utcnow is deprecated in Python 3.12+.

Use datetime.now(timezone.utc) instead for timezone-aware datetimes.

♻️ Proposed fix
+from datetime import datetime, timezone
-from datetime import datetime

-    created_at = Column(DateTime, default=datetime.utcnow)
+    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))

6-6: declarative_base() is legacy SQLAlchemy 1.x style.

SQLAlchemy 2.0 recommends using DeclarativeBase class instead. This is optional for now but worth considering for forward compatibility.

♻️ SQLAlchemy 2.0 style
-from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import DeclarativeBase

-Base = declarative_base()
+class Base(DeclarativeBase):
+    pass
mcp/mcp_registry/main.py (1)

24-26: @app.on_event("startup") is deprecated in FastAPI.

FastAPI recommends using the lifespan context manager instead of on_event decorators.

♻️ Migration to lifespan
+from contextlib import asynccontextmanager
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    # Startup
+    create_db_and_tables()
+    yield
+    # Shutdown (if needed)

-app = FastAPI()
+app = FastAPI(lifespan=lifespan)

-@app.on_event("startup")
-def on_startup():
-    create_db_and_tables()
mcp/mcp_registry/crud.py (1)

45-45: .dict() is deprecated in Pydantic v2.

Use .model_dump() for Pydantic v2 compatibility.

♻️ Proposed fix
-    db_capability = models.Capability(**capability.dict())
+    db_capability = models.Capability(**capability.model_dump())
mcp/mcp_registry/schemas.py (1)

15-16: Consider migrating to Pydantic v2 syntax.

orm_mode = True is Pydantic v1 syntax. Pydantic v2 (bundled with FastAPI) renames this to from_attributes and requires using model_config = ConfigDict(from_attributes=True) instead.

♻️ Pydantic v2 syntax
+from pydantic import ConfigDict
+
 class Capability(CapabilityBase):
     id: int
     agents: List["Agent"] = []

-    class Config:
-        orm_mode = True
+    model_config = ConfigDict(from_attributes=True)

Also applies to: lines 38-39 (Agent class)

mcp/pyproject.toml (1)

5-11: Pinning dependency versions is recommended for production readiness.

Unpinned dependencies risk unexpected breaking changes when new versions are released. For applications, use a lockfile (poetry.lock, requirements.lock, or Pipenv.lock) to pin exact versions in production. For development, consider using looser range constraints like >=X.Y.Z,<NEXT_MAJOR to balance flexibility with stability.

📦 Example with updated version constraints
 dependencies = [
-    "fastapi",
-    "uvicorn",
-    "psycopg2-binary",
-    "SQLAlchemy",
-    "python-dotenv",
+    "fastapi>=0.120.0,<1.0.0",
+    "uvicorn>=0.35.0,<1.0.0",
+    "psycopg2-binary>=2.9.9,<3.0.0",
+    "SQLAlchemy>=2.0.40,<3.0.0",
+    "python-dotenv>=1.0.0,<2.0.0",
 ]

For production deployments, generate and commit a lockfile (e.g., using pip-tools, Poetry, or Pipenv) to ensure reproducible installs with exact pinned versions.

mcp/terraform/rds.tf (1)

4-20: Consider adding explicit egress rules.

The security group only defines ingress rules. While AWS provides a default egress rule allowing all outbound traffic, it's better to explicitly define egress rules for clarity and security hardening.

📝 Recommended improvement
   ingress {
     from_port   = 5432
     to_port     = 5432
     protocol    = "tcp"
     security_groups = [aws_security_group.app.id]
   }
+
+  egress {
+    from_port   = 0
+    to_port     = 0
+    protocol    = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }

   tags = {
     Name = "${var.project_name}-db-sg"
   }
mcp/terraform/s3.tf (2)

4-10: Consider enabling server access logging for audit trails.

Server access logging provides detailed records of requests made to the bucket, useful for security audits, compliance, and troubleshooting.

📋 Proposed addition for access logging

First, create a logging bucket:

# S3 bucket for access logs
resource "aws_s3_bucket" "logs" {
  bucket = "${var.project_name}-openapi-specs-logs"

  tags = {
    Name = "${var.project_name}-openapi-specs-logs"
  }
}

# Block public access to logs bucket
resource "aws_s3_bucket_public_access_block" "logs" {
  bucket = aws_s3_bucket.logs.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Then enable logging on the main bucket:

# Enable access logging for the main S3 bucket
resource "aws_s3_bucket_logging" "main" {
  bucket = aws_s3_bucket.main.id

  target_bucket = aws_s3_bucket.logs.id
  target_prefix = "access-logs/"
}

4-10: Optionally add a lifecycle policy for cost management.

The static analysis tool suggests adding a lifecycle configuration. For a POC, this is optional, but consider adding lifecycle rules to transition older spec versions to cheaper storage classes or expire them after a retention period.

💰 Proposed lifecycle configuration
# Lifecycle policy for the S3 bucket
resource "aws_s3_bucket_lifecycle_configuration" "main" {
  bucket = aws_s3_bucket.main.id

  rule {
    id     = "transition-old-versions"
    status = "Enabled"

    noncurrent_version_transition {
      noncurrent_days = 30
      storage_class   = "STANDARD_IA"
    }

    noncurrent_version_transition {
      noncurrent_days = 90
      storage_class   = "GLACIER"
    }

    noncurrent_version_expiration {
      noncurrent_days = 365
    }
  }
}
mcp/terraform/alb.tf (1)

31-41: Enable ALB security and operational features.

The ALB is missing several recommended features:

  • Access logging: Provides audit trails and troubleshooting data
  • Deletion protection: Prevents accidental deletion
  • drop_invalid_header_fields: Mitigates HTTP request smuggling attacks
🛡️ Proposed enhancements
 resource "aws_lb" "main" {
   name               = "${var.project_name}-alb"
   internal           = false
   load_balancer_type = "application"
   security_groups    = [aws_security_group.app.id]
   subnets            = aws_subnet.public.*.id
+  
+  enable_deletion_protection = true
+  drop_invalid_header_fields = true
 
   tags = {
     Name = "${var.project_name}-alb"
   }
 }
+
+# Enable access logging for the ALB
+resource "aws_s3_bucket" "alb_logs" {
+  bucket = "${var.project_name}-alb-logs"
+
+  tags = {
+    Name = "${var.project_name}-alb-logs"
+  }
+}
+
+resource "aws_s3_bucket_public_access_block" "alb_logs" {
+  bucket = aws_s3_bucket.alb_logs.id
+
+  block_public_acls       = true
+  block_public_policy     = true
+  ignore_public_acls      = true
+  restrict_public_buckets = true
+}
+
+resource "aws_s3_bucket_policy" "alb_logs" {
+  bucket = aws_s3_bucket.alb_logs.id
+
+  policy = jsonencode({
+    Version = "2012-10-17"
+    Statement = [
+      {
+        Sid    = "AWSLogDeliveryWrite"
+        Effect = "Allow"
+        Principal = {
+          Service = "elasticloadbalancing.amazonaws.com"
+        }
+        Action   = "s3:PutObject"
+        Resource = "${aws_s3_bucket.alb_logs.arn}/*"
+      }
+    ]
+  })
+}
+
+resource "aws_lb" "main" {
+  # ... existing configuration ...
+
+  access_logs {
+    bucket  = aws_s3_bucket.alb_logs.id
+    enabled = true
+  }
+}

Note: For a POC environment, you may set enable_deletion_protection = false to simplify teardown.

mcp/terraform/iam.tf (1)

6-18: Update IAM policy version to "2012-10-17".

The assume role policies use "Version": "2008-10-17", which is an older IAM policy version. While still functional, AWS recommends using "2012-10-17" for all new policies.

♻️ Proposed fix
   assume_role_policy = jsonencode({
-    Version = "2008-10-17",
+    Version = "2012-10-17",
     Statement = [
       {
         Action = "sts:AssumeRole",

Apply the same change to both aws_iam_role.task and aws_iam_role.task_execution.

Also applies to: 28-40

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b93b098 and 7f91a2d.

📒 Files selected for processing (18)
  • mcp/.gitignore
  • mcp/Dockerfile
  • mcp/mcp_registry/crud.py
  • mcp/mcp_registry/database.py
  • mcp/mcp_registry/main.py
  • mcp/mcp_registry/schemas.py
  • mcp/pyproject.toml
  • mcp/terraform/alb.tf
  • mcp/terraform/ecr.tf
  • mcp/terraform/ecs.tf
  • mcp/terraform/iam.tf
  • mcp/terraform/main.tf
  • mcp/terraform/outputs.tf
  • mcp/terraform/providers.tf
  • mcp/terraform/rds.tf
  • mcp/terraform/s3.tf
  • mcp/terraform/secretsmanager.tf
  • mcp/terraform/variables.tf
✅ Files skipped from review due to trivial changes (2)
  • mcp/.gitignore
  • mcp/terraform/secretsmanager.tf
🧰 Additional context used
🧬 Code graph analysis (3)
mcp/mcp_registry/schemas.py (1)
mcp/mcp_registry/database.py (2)
  • Capability (58-69)
  • Agent (26-56)
mcp/mcp_registry/crud.py (3)
mcp/mcp_registry/database.py (2)
  • Agent (26-56)
  • Capability (58-69)
mcp/mcp_registry/schemas.py (4)
  • Agent (34-39)
  • AgentCreate (31-32)
  • Capability (11-16)
  • CapabilityCreate (8-9)
mcp/mcp_registry/main.py (2)
  • create_agent (30-31)
  • create_capability (46-49)
mcp/mcp_registry/main.py (3)
mcp/mcp_registry/database.py (3)
  • get_db (71-76)
  • Agent (26-56)
  • Capability (58-69)
mcp/mcp_registry/schemas.py (4)
  • Agent (34-39)
  • AgentCreate (31-32)
  • Capability (11-16)
  • CapabilityCreate (8-9)
mcp/mcp_registry/crud.py (4)
  • create_agent (10-33)
  • get_agents (7-8)
  • get_agents_by_capability (51-52)
  • create_capability (44-49)
🪛 Checkov (3.2.334)
mcp/terraform/alb.tf

[medium] 31-41: Ensure that ALB drops HTTP headers

(CKV_AWS_131)


[medium] 67-76: Ensure ALB protocol is HTTPS

(CKV_AWS_2)


[medium] 44-64: Ensure AWS Load Balancer doesn't use HTTP protocol

(CKV_AWS_378)


[high] 67-76: Ensure that load balancer is using at least TLS 1.2

(CKV_AWS_103)

mcp/terraform/s3.tf

[medium] 4-10: Ensure that an S3 bucket has a lifecycle configuration

(CKV2_AWS_61)

mcp/terraform/rds.tf

[medium] 33-49: Ensure RDS database has IAM authentication enabled

(CKV_AWS_161)


[medium] 33-49: Ensure that AWS database instances have deletion protection enabled

(CKV_AWS_293)


[high] 33-49: Ensure RDS Performance Insights are encrypted using KMS CMKs

(CKV_AWS_354)

🪛 Ruff (0.14.10)
mcp/mcp_registry/main.py

30-30: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


35-35: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


41-41: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


47-47: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (7)
mcp/mcp_registry/schemas.py (1)

34-39: Schema is missing created_at and is_deleted fields from the database model.

The Agent response schema doesn't include created_at and is_deleted fields that are defined in the database model (mcp/mcp_registry/database.py lines 41-42). Consider whether these should be exposed in API responses.

mcp/mcp_registry/main.py (1)

29-31: The Ruff B008 warnings about Depends() in defaults are false positives for FastAPI.

Using Depends() in function argument defaults is the idiomatic FastAPI pattern for dependency injection. This static analysis rule doesn't account for FastAPI's design.

mcp/terraform/providers.tf (1)

3-15: LGTM!

The provider configuration follows Terraform best practices by using version constraints and parameterizing the region and profile through variables.

mcp/terraform/outputs.tf (1)

1-4: LGTM!

The output definition is clear and properly references the ECR repository resource.

mcp/terraform/ecs.tf (1)

17-18: Verify CPU and memory allocation for production workloads.

The task is configured with 256 CPU units (0.25 vCPU) and 512 MB memory, which are minimal Fargate allocations. Ensure these resources are sufficient for your FastAPI application with SQLAlchemy under expected load.

Consider load testing or monitoring resource utilization after deployment to determine if scaling to larger task sizes (e.g., 512 CPU / 1024 MB memory) is necessary.

mcp/terraform/iam.tf (1)

54-77: LGTM! IAM policies follow least privilege principles.

The custom task policy is properly scoped to specific resources (the Secrets Manager secret and S3 bucket objects) with only the necessary permissions (GetSecretValue, GetObject, PutObject). This follows AWS security best practices for least privilege access.

mcp/terraform/alb.tf (1)

51-59: The health check configuration is appropriate. The FastAPI application responds to GET requests on "/" with HTTP 200, matching the ALB health check matcher.

Comment thread mcp/mcp_registry/crud.py Outdated
Comment thread mcp/mcp_registry/crud.py
Comment on lines +10 to +33
def create_agent(db: Session, agent: schemas.AgentCreate):
db_agent = models.Agent(
name=agent.name,
version=agent.version,
display_name=agent.display_name,
description=agent.description,
owner=agent.owner,
endpoint=agent.endpoint,
openapi_spec=agent.openapi_spec,
environment=agent.environment,
tags=agent.tags,
openapi_spec_s3_uri=agent.openapi_spec_s3_uri,
openapi_spec_checksum=agent.openapi_spec_checksum
)
for capability_data in agent.capabilities:
capability = get_capability_by_name(db, name=capability_data.name)
if not capability:
capability = create_capability(db, capability_data)
db_agent.capabilities.append(capability)

db.add(db_agent)
db.commit()
db.refresh(db_agent)
return db_agent
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Transaction integrity issue: nested commits can leave orphan capabilities.

When create_agent calls create_capability (line 27), the capability is committed immediately (line 47). If the subsequent agent creation fails, capabilities remain in the database as orphans. Consider deferring commits until the entire operation succeeds.

♻️ Proposed fix: defer commits
+def _get_or_create_capability_no_commit(db: Session, capability_data: schemas.CapabilityCreate):
+    """Get existing capability or create new one without committing."""
+    capability = get_capability_by_name(db, name=capability_data.name)
+    if not capability:
+        capability = models.Capability(**capability_data.dict())
+        db.add(capability)
+        db.flush()  # Assign ID without committing
+    return capability
+
 def create_agent(db: Session, agent: schemas.AgentCreate):
     db_agent = models.Agent(
         name=agent.name,
         version=agent.version,
         display_name=agent.display_name,
         description=agent.description,
         owner=agent.owner,
         endpoint=agent.endpoint,
         openapi_spec=agent.openapi_spec,
         environment=agent.environment,
         tags=agent.tags,
         openapi_spec_s3_uri=agent.openapi_spec_s3_uri,
         openapi_spec_checksum=agent.openapi_spec_checksum
     )
     for capability_data in agent.capabilities:
-        capability = get_capability_by_name(db, name=capability_data.name)
-        if not capability:
-            capability = create_capability(db, capability_data)
+        capability = _get_or_create_capability_no_commit(db, capability_data)
         db_agent.capabilities.append(capability)
 
     db.add(db_agent)
     db.commit()
     db.refresh(db_agent)
     return db_agent
🤖 Prompt for AI Agents
In @mcp/mcp_registry/crud.py around lines 10 - 33, The issue is that
create_agent calls create_capability which currently commits its own
transaction, causing orphaned capability rows if the agent commit later fails;
fix by changing create_capability to not perform its own commit/refresh but
instead accept and use the passed Session to add and return the capability (or
add an optional commit flag defaulting to False), then in create_agent ensure
all new capabilities are added to the same session and perform a single
db.commit()/db.refresh(db_agent) at the end; update any callers of
create_capability to handle the new non-committing behavior or explicitly commit
when needed.

Comment thread mcp/mcp_registry/crud.py Outdated
Comment thread mcp/mcp_registry/database.py Outdated
Comment thread mcp/mcp_registry/database.py
Comment thread mcp/terraform/main.tf
Comment thread mcp/terraform/main.tf
Comment on lines +48 to +63
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
}

# NAT Gateway
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id

tags = {
Name = "${var.project_name}-nat-gw"
}

depends_on = [aws_internet_gateway.main]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider deploying one NAT Gateway per availability zone for high availability.

The current configuration uses a single NAT Gateway in the first public subnet, creating a single point of failure. If this NAT Gateway or its availability zone becomes unavailable, all private subnet workloads will lose internet connectivity, impacting the ECS tasks and RDS access to patch repositories or external APIs.

For production deployments, provision one NAT Gateway per availability zone to eliminate this risk.

♻️ Proposed fix for multi-AZ NAT Gateway setup
-# Elastic IP for NAT Gateway
-resource "aws_eip" "nat" {
-  domain = "vpc"
-}
-
-# NAT Gateway
-resource "aws_nat_gateway" "main" {
-  allocation_id = aws_eip.nat.id
-  subnet_id     = aws_subnet.public[0].id
-
-  tags = {
-    Name = "${var.project_name}-nat-gw"
-  }
-
-  depends_on = [aws_internet_gateway.main]
-}
+# Elastic IPs for NAT Gateways (one per AZ)
+resource "aws_eip" "nat" {
+  count  = length(var.public_subnet_cidrs)
+  domain = "vpc"
+
+  tags = {
+    Name = "${var.project_name}-nat-eip-${count.index + 1}"
+  }
+}
+
+# NAT Gateways (one per AZ)
+resource "aws_nat_gateway" "main" {
+  count         = length(var.public_subnet_cidrs)
+  allocation_id = aws_eip.nat[count.index].id
+  subnet_id     = aws_subnet.public[count.index].id
+
+  tags = {
+    Name = "${var.project_name}-nat-gw-${count.index + 1}"
+  }
+
+  depends_on = [aws_internet_gateway.main]
+}

Update the private route table to use the corresponding NAT Gateway:

-# Private Route Table
-resource "aws_route_table" "private" {
-  vpc_id = aws_vpc.main.id
-
-  route {
-    cidr_block = "0.0.0.0/0"
-    nat_gateway_id = aws_nat_gateway.main.id
-  }
-
-  tags = {
-    Name = "${var.project_name}-private-rt"
-  }
-}
-
-# Associate Private Subnets with Private Route Table
-resource "aws_route_table_association" "private" {
-  count          = length(var.private_subnet_cidrs)
-  subnet_id      = aws_subnet.private[count.index].id
-  route_table_id = aws_route_table.private.id
-}
+# Private Route Tables (one per AZ)
+resource "aws_route_table" "private" {
+  count  = length(var.private_subnet_cidrs)
+  vpc_id = aws_vpc.main.id
+
+  route {
+    cidr_block = "0.0.0.0/0"
+    nat_gateway_id = aws_nat_gateway.main[count.index].id
+  }
+
+  tags = {
+    Name = "${var.project_name}-private-rt-${count.index + 1}"
+  }
+}
+
+# Associate Private Subnets with their respective Private Route Tables
+resource "aws_route_table_association" "private" {
+  count          = length(var.private_subnet_cidrs)
+  subnet_id      = aws_subnet.private[count.index].id
+  route_table_id = aws_route_table.private[count.index].id
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
}
# NAT Gateway
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.project_name}-nat-gw"
}
depends_on = [aws_internet_gateway.main]
}
# Elastic IPs for NAT Gateways (one per AZ)
resource "aws_eip" "nat" {
count = length(var.public_subnet_cidrs)
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${count.index + 1}"
}
}
# NAT Gateways (one per AZ)
resource "aws_nat_gateway" "main" {
count = length(var.public_subnet_cidrs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-nat-gw-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}

Comment thread mcp/terraform/rds.tf
Comment on lines +33 to +49
resource "aws_db_instance" "main" {
allocated_storage = 20
storage_type = "gp2"
engine = "postgres"
engine_version = "18.1"
instance_class = "db.t3.micro"
db_name = "mcpdatabase"
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
skip_final_snapshot = true

tags = {
Name = "${var.project_name}-db"
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "rds.tf" -type f

Repository: zsoftly/ai-craft

Length of output: 82


🏁 Script executed:

# Check if the file exists and view its content
if [ -f "mcp/terraform/rds.tf" ]; then
  wc -l mcp/terraform/rds.tf
  echo "---"
  cat -n mcp/terraform/rds.tf | sed -n '30,55p'
else
  echo "File not found at mcp/terraform/rds.tf"
fi

Repository: zsoftly/ai-craft

Length of output: 761


🌐 Web query:

PostgreSQL 18.1 AWS RDS availability 2024 2025

💡 Result:

Short answer: PostgreSQL 18 (including 18.1) was not available on AWS RDS in 2024. AWS introduced PostgreSQL 18 Beta/Preview in 2025 and added RDS support for major version 18 (starting with 18.1) in late 2025.

Timeline (sources)

  • May 15, 2025 — PostgreSQL 18 Beta 1 available in the Amazon RDS Database Preview Environment. [1]
  • Sep 25, 2025 — PostgreSQL 18.0 available in the Amazon RDS Database Preview Environment. [2]
  • Nov 14, 2025 — Amazon RDS for PostgreSQL added support for major version 18 (starting with PostgreSQL 18.1). [3]
  • Dec 17, 2025 — Amazon Aurora PostgreSQL-Compatible Edition added PostgreSQL 18.1 in the RDS Database Preview Environment. [4]

Sources:
[1] AWS announcement — PostgreSQL 18 Beta 1 in RDS Database Preview (May 15, 2025).
[2] AWS announcement — PostgreSQL 18.0 in RDS Database Preview (Sep 25, 2025).
[3] AWS announcement — Amazon RDS for PostgreSQL now supports major version 18 (Nov 14, 2025).
[4] AWS announcement — Amazon Aurora supports PostgreSQL 18.1 in RDS Database Preview (Dec 17, 2025).


Address data loss risk from snapshot configuration and add security hardening.

The RDS configuration has a critical concern:

  1. Line 44: skip_final_snapshot = true prevents a final backup when the database is deleted, risking permanent data loss. Change to skip_final_snapshot = false with a final_snapshot_identifier to preserve data.

Additionally, consider these security enhancements:

  • Enable deletion protection to prevent accidental database deletion
  • Enable IAM database authentication for credential management
  • Enable Performance Insights with KMS encryption for monitoring
  • Export CloudWatch logs for PostgreSQL activity tracking

Note: PostgreSQL 18.1 is supported on AWS RDS (available since November 2025).

🔒 Recommended security improvements
 resource "aws_db_instance" "main" {
   allocated_storage    = 20
   storage_type         = "gp2"
   engine               = "postgres"
   engine_version       = "18.1"
   instance_class       = "db.t3.micro"
   db_name              = "mcpdatabase"
   username             = var.db_username
   password             = var.db_password
   db_subnet_group_name = aws_db_subnet_group.main.name
   vpc_security_group_ids = [aws_security_group.db.id]
-  skip_final_snapshot  = true
+  skip_final_snapshot  = false
+  final_snapshot_identifier = "${var.project_name}-final-snapshot"
+  deletion_protection  = true
+  iam_database_authentication_enabled = true
+  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
+
+  performance_insights_enabled = true
+  performance_insights_kms_key_id = aws_kms_key.rds.arn

   tags = {
     Name = "${var.project_name}-db"
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Checkov (3.2.334)

[medium] 33-49: Ensure RDS database has IAM authentication enabled

(CKV_AWS_161)


[medium] 33-49: Ensure that AWS database instances have deletion protection enabled

(CKV_AWS_293)


[high] 33-49: Ensure RDS Performance Insights are encrypted using KMS CMKs

(CKV_AWS_354)

Comment thread mcp/terraform/s3.tf
Comment thread mcp/terraform/variables.tf
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @mcp/mcp_registry/crud.py:
- Around line 44-47: create_capability currently adds a new models.Capability to
the session but never commits, so calls from the POST /capabilities/ endpoint
won't persist; fix by committing and refreshing the instance before returning:
after creating db_capability in create_capability add db.commit() and
db.refresh(db_capability) so it persists and has its generated fields populated,
or if you prefer to keep create_capability non-committing for internal use
(e.g., create_agent), ensure the POST /capabilities/ endpoint calls db.commit()
and db.refresh(db_capability) after crud.create_capability.

In @mcp/terraform/alb.tf:
- Around line 95-96: Replace the outdated ssl_policy and the hardcoded
certificate_arn: set ssl_policy to a TLS1.2+ managed policy (e.g., use
ELBSecurityPolicy-TLS-1-2-2017-01) and change certificate_arn to reference a
variable (certificate_arn = var.acm_certificate_arn); also add a variable block
named acm_certificate_arn (type = string, description = "ARN of the ACM
certificate for HTTPS") in variables.tf so deployments supply the ARN.
🧹 Nitpick comments (7)
mcp/mcp_registry/database.py (1)

43-43: Consider using timezone-aware datetime.

datetime.utcnow is deprecated in Python 3.12+. Use datetime.now(timezone.utc) for timezone-aware UTC timestamps.

♻️ Proposed fix
-from datetime import datetime
+from datetime import datetime, timezone
 
 # ... in Agent model:
-    created_at = Column(DateTime, default=datetime.utcnow)
+    created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
mcp/mcp_registry/main.py (1)

16-18: Consider using the lifespan context manager instead of deprecated @app.on_event.

@app.on_event("startup") is deprecated in FastAPI 0.93.0+ and the lifespan context manager is the recommended replacement.

♻️ Proposed fix
+from contextlib import asynccontextmanager
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    create_db_and_tables()
+    yield
+
-app = FastAPI()
+app = FastAPI(lifespan=lifespan)

-@app.on_event("startup")
-def on_startup():
-    create_db_and_tables()
mcp/mcp_registry/schemas.py (1)

14-15: Migrate to Pydantic v2 from_attributes configuration.

orm_mode = True is deprecated in Pydantic v2. Replace with from_attributes = True in the Config class at lines 14-15, 33-34, 43-44, and 50-51.

mcp/terraform/ecs.tf (2)

25-25: Avoid using :latest tag for production deployments.

Using :latest makes deployments non-reproducible and can lead to unexpected behavior when the image is updated. Consider using specific image tags (e.g., commit SHA, semantic version) for predictable deployments.

♻️ Suggested approach
-      image     = "${aws_ecr_repository.main.repository_url}:latest"
+      image     = "${aws_ecr_repository.main.repository_url}:${var.image_tag}"

Add a variable in variables.tf:

variable "image_tag" {
  description = "Docker image tag to deploy"
  type        = string
  default     = "latest"  # Override in production
}

71-71: Update dependency to reference the HTTPS listener.

The ECS service depends on aws_lb_listener.http, but the HTTP listener only redirects to HTTPS. The actual traffic forwarding to the target group happens in aws_lb_listener.https.

♻️ Proposed fix
-  depends_on = [aws_lb_listener.http]
+  depends_on = [aws_lb_listener.https]
mcp/terraform/alb.tf (1)

38-48: Consider enabling drop_invalid_header_fields for security hardening.

Per CKV_AWS_131, enabling this attribute helps prevent HTTP request smuggling and header injection attacks by dropping requests with invalid HTTP headers.

🛡️ Proposed enhancement
 resource "aws_lb" "main" {
   name               = "${var.project_name}-alb"
   internal           = false
   load_balancer_type = "application"
   security_groups    = [aws_security_group.app.id]
   subnets            = aws_subnet.public.*.id
+  drop_invalid_header_fields = true
 
   tags = {
     Name = "${var.project_name}-alb"
   }
 }
mcp/terraform/s3.tf (1)

7-17: Update to use standalone resources for versioning and encryption.

The inline versioning and server_side_encryption_configuration blocks within aws_s3_bucket are deprecated in AWS provider version 4.0+. These arguments became read-only and must be moved to separate resources for forward compatibility.

♻️ Proposed refactoring using standalone resources
 resource "aws_s3_bucket" "main" {
   bucket = "${var.project_name}-openapi-specs"
 
-  versioning {
-    enabled = true
-  }
-
-  server_side_encryption_configuration {
-    rule {
-      apply_server_side_encryption_by_default {
-        sse_algorithm = "AES256"
-      }
-    }
-  }
-
   tags = {
     Name = "${var.project_name}-openapi-specs"
   }
 }
+
+resource "aws_s3_bucket_versioning" "main" {
+  bucket = aws_s3_bucket.main.id
+
+  versioning_configuration {
+    status = "Enabled"
+  }
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
+  bucket = aws_s3_bucket.main.id
+
+  rule {
+    apply_server_side_encryption_by_default {
+      sse_algorithm = "AES256"
+    }
+  }
+}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f91a2d and c12231f.

📒 Files selected for processing (10)
  • mcp/mcp_registry/crud.py
  • mcp/mcp_registry/database.py
  • mcp/mcp_registry/main.py
  • mcp/mcp_registry/schemas.py
  • mcp/terraform/alb.tf
  • mcp/terraform/ecr.tf
  • mcp/terraform/ecs.tf
  • mcp/terraform/main.tf
  • mcp/terraform/s3.tf
  • mcp/terraform/variables.tf
🚧 Files skipped from review as they are similar to previous changes (2)
  • mcp/terraform/main.tf
  • mcp/terraform/variables.tf
🧰 Additional context used
🧬 Code graph analysis (4)
mcp/mcp_registry/database.py (1)
mcp/mcp_registry/schemas.py (2)
  • Agent (39-44)
  • Capability (46-51)
mcp/mcp_registry/main.py (3)
mcp/mcp_registry/database.py (4)
  • create_db_and_tables (80-81)
  • get_db (73-78)
  • Agent (28-58)
  • Capability (60-71)
mcp/mcp_registry/schemas.py (4)
  • Agent (39-44)
  • AgentCreate (36-37)
  • Capability (46-51)
  • CapabilityCreate (8-9)
mcp/mcp_registry/crud.py (4)
  • create_agent (10-33)
  • get_agents (7-8)
  • get_agents_by_capability (49-50)
  • create_capability (44-47)
mcp/mcp_registry/schemas.py (1)
mcp/mcp_registry/database.py (2)
  • Agent (28-58)
  • Capability (60-71)
mcp/mcp_registry/crud.py (3)
mcp/mcp_registry/database.py (2)
  • Agent (28-58)
  • Capability (60-71)
mcp/mcp_registry/schemas.py (4)
  • Agent (39-44)
  • AgentCreate (36-37)
  • Capability (46-51)
  • CapabilityCreate (8-9)
mcp/mcp_registry/main.py (2)
  • create_agent (22-23)
  • create_capability (38-41)
🪛 Checkov (3.2.334)
mcp/terraform/alb.tf

[medium] 38-48: Ensure that ALB drops HTTP headers

(CKV_AWS_131)


[high] 91-102: Ensure that load balancer is using at least TLS 1.2

(CKV_AWS_103)

mcp/terraform/s3.tf

[medium] 4-22: Ensure that an S3 bucket has a lifecycle configuration

(CKV2_AWS_61)

🪛 Ruff (0.14.10)
mcp/mcp_registry/database.py

15-15: Avoid specifying long messages outside the exception class

(TRY003)

mcp/mcp_registry/main.py

22-22: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


27-27: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


33-33: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


39-39: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

mcp/mcp_registry/crud.py

5-5: Avoid equality comparisons to False; use not models.Agent.is_deleted: for false checks

Replace with not models.Agent.is_deleted

(E712)


8-8: Avoid equality comparisons to False; use not models.Agent.is_deleted: for false checks

Replace with not models.Agent.is_deleted

(E712)


50-50: Avoid equality comparisons to False; use not models.Agent.is_deleted: for false checks

Replace with not models.Agent.is_deleted

(E712)

🔇 Additional comments (16)
mcp/mcp_registry/database.py (4)

12-15: Previously flagged issue has been addressed.

The hardcoded default credentials in the fallback DATABASE_URL have been removed. The code now properly requires the environment variable and fails explicitly if it's not set.


21-26: Previously flagged issue has been addressed.

The association table now correctly defines a composite primary key on both agent_id and capability_id, preventing duplicate associations.


28-58: Agent model looks well-structured.

The model includes appropriate indexing on frequently queried fields (name, version, environment), a unique constraint on (name, version), soft-delete support via is_deleted, and JSONB storage for the OpenAPI spec.


60-71: Capability model and utility functions are properly implemented.

The bidirectional relationship with Agent is correctly set up, and the get_db generator properly handles session lifecycle with cleanup in the finally block.

Also applies to: 73-81

mcp/mcp_registry/main.py (3)

7-8: Previously flagged issues have been addressed.

The duplicate get_db function has been removed; it's now imported from the database module. The duplicate table creation at module level has also been removed.


21-41: Endpoints correctly delegate to CRUD layer.

The static analysis warnings about Depends() in default arguments (B008) are false positives for FastAPI—this is the idiomatic pattern. The endpoints properly use dependency injection for database sessions.


44-46: Root endpoint is fine for health checks.

Simple and effective for basic liveness verification.

mcp/mcp_registry/schemas.py (2)

11-15: Previously flagged circular serialization issue has been addressed.

The use of CapabilitySimple and AgentSimple schemas (without back-references) breaks the circular dependency and prevents infinite recursion during serialization.

Also applies to: 30-34


53-54: Forward reference updates may not be needed in Pydantic v2.

In Pydantic v2, update_forward_refs() is replaced by model_rebuild(). However, since you're using CapabilitySimple and AgentSimple (already defined), forward references may not require explicit resolution.

mcp/mcp_registry/crud.py (3)

4-8: Previously flagged soft-delete filtering issue has been addressed.

The get_agent and get_agents functions now correctly filter out soft-deleted agents using is_deleted == False.

Note: The static analysis warning (E712) about == False is a false positive for SQLAlchemy—using == False is correct because SQLAlchemy column expressions don't work with Python's not operator.


10-33: Transaction handling in create_agent is now correct.

The function properly creates capabilities without committing, associates them with the agent, and commits everything in a single transaction at the end. This addresses the previous orphan-capability concern.


49-50: Previously flagged soft-delete filtering issue has been addressed.

The get_agents_by_capability function now correctly filters out soft-deleted agents.

mcp/terraform/ecr.tf (1)

4-16: LGTM! Past review concerns have been addressed.

The ECR configuration now follows production best practices:

  • image_tag_mutability = "IMMUTABLE" prevents tag overwrites
  • force_delete = false protects against accidental repository deletion
  • scan_on_push = true enables vulnerability scanning
mcp/terraform/ecs.tf (1)

33-42: Verify secret key paths for distinct environment variables.

Both DB_CREDENTIALS and DATABASE_URL reference the same secret ARN without key paths. This means both environment variables will receive the identical value (the full secret content).

If the secret contains a JSON object with multiple keys, you should specify the key path to extract distinct values:

🔧 Proposed fix if secret contains separate keys
       secrets = [
         {
           name      = "DB_CREDENTIALS"
-          valueFrom = aws_secretsmanager_secret.db_credentials.arn
+          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:credentials::"
         },
         {
           name      = "DATABASE_URL"
-          valueFrom = aws_secretsmanager_secret.db_credentials.arn
+          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:database_url::"
         }
       ]

Verify the structure of the secret stored in Secrets Manager and ensure the key paths match.

mcp/terraform/s3.tf (1)

24-49: LGTM! Security configuration is well-designed.

  • Public access is properly blocked
  • IAM policy follows least privilege with only s3:GetObject and s3:PutObject permissions
  • Task role-based access is appropriate for ECS integration

Optionally, consider adding a lifecycle configuration to manage versioned objects and control storage costs over time.

mcp/terraform/alb.tf (1)

4-35: LGTM! Security group and listener configuration properly implements HTTPS redirect.

  • Security group correctly allows both HTTP and HTTPS for the redirect flow
  • HTTP listener properly redirects to HTTPS with 301 status code
  • Target group health check configuration is reasonable

Also applies to: 51-88

Comment thread mcp/mcp_registry/crud.py
Comment on lines +44 to +47
def create_capability(db: Session, capability: schemas.CapabilityCreate):
db_capability = models.Capability(**capability.dict())
db.add(db_capability)
return db_capability
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing commit when create_capability is called directly from the API.

create_capability adds to the session but doesn't commit. This works correctly when called from create_agent (which commits), but when called directly from the POST /capabilities/ endpoint, changes won't be persisted.

🐛 Proposed fix
 def create_capability(db: Session, capability: schemas.CapabilityCreate):
     db_capability = models.Capability(**capability.dict())
     db.add(db_capability)
+    db.commit()
+    db.refresh(db_capability)
     return db_capability

Alternatively, if you want to keep create_capability non-committing for internal use, commit in the endpoint:

# In main.py
@app.post("/capabilities/", response_model=schemas.Capability)
def create_capability(
    capability: schemas.CapabilityCreate, db: Session = Depends(get_db)
):
    db_capability = crud.create_capability(db=db, capability=capability)
    db.commit()
    db.refresh(db_capability)
    return db_capability
🤖 Prompt for AI Agents
In @mcp/mcp_registry/crud.py around lines 44 - 47, create_capability currently
adds a new models.Capability to the session but never commits, so calls from the
POST /capabilities/ endpoint won't persist; fix by committing and refreshing the
instance before returning: after creating db_capability in create_capability add
db.commit() and db.refresh(db_capability) so it persists and has its generated
fields populated, or if you prefer to keep create_capability non-committing for
internal use (e.g., create_agent), ensure the POST /capabilities/ endpoint calls
db.commit() and db.refresh(db_capability) after crud.create_capability.

Comment thread mcp/terraform/alb.tf
Comment on lines +95 to +96
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/your-certificate-id" # TODO: Replace with your certificate ARN
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update SSL policy and parameterize certificate ARN.

Two issues with the HTTPS listener configuration:

  1. Outdated SSL policy: ELBSecurityPolicy-2016-08 doesn't enforce TLS 1.2 minimum, failing security best practices (CKV_AWS_103).
  2. Hardcoded certificate ARN: The placeholder needs to be replaced with a variable for deployability.
🔒 Proposed fix
-  ssl_policy        = "ELBSecurityPolicy-2016-08"
-  certificate_arn   = "arn:aws:acm:us-east-1:123456789012:certificate/your-certificate-id" # TODO: Replace with your certificate ARN
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.acm_certificate_arn

Add a variable in variables.tf:

variable "acm_certificate_arn" {
  description = "ARN of the ACM certificate for HTTPS"
  type        = string
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/your-certificate-id" # TODO: Replace with your certificate ARN
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.acm_certificate_arn
🤖 Prompt for AI Agents
In @mcp/terraform/alb.tf around lines 95 - 96, Replace the outdated ssl_policy
and the hardcoded certificate_arn: set ssl_policy to a TLS1.2+ managed policy
(e.g., use ELBSecurityPolicy-TLS-1-2-2017-01) and change certificate_arn to
reference a variable (certificate_arn = var.acm_certificate_arn); also add a
variable block named acm_certificate_arn (type = string, description = "ARN of
the ACM certificate for HTTPS") in variables.tf so deployments supply the ARN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant