Skip to content

AI MCP Read API — Overview

An internal HTTP service running in guinness-ai-v2 that exposes DocumentDB design and code data (design / code collections) to the MCP Server. It provides separate list/detail endpoints for design and code, protected by service-to-service authentication (X-AI-Service-Token). Deployed as a Lambda Function URL or private HTTP integration accessible only within the VPC; no external access is possible.

Queue: None — HTTP only (triggered by HTTP GET requests from the MCP Server) Maximum execution time: Under 5 seconds per request Source: guinness-ai-v2/apps/mcp-api/ (new app)


1. Tech Stack

Layer Technology
Runtime Python 3.12 on AWS Lambda
AI Framework None — pure data retrieval, no LLM calls
Database Amazon DocumentDB (MongoDB-compatible) — design / code collections, owned by AI
Storage None — no S3 access
Queue None — HTTP only
Authentication X-AI-Service-Token header (shared secret, constant-time comparison)
Deployment Lambda Function URL, VPC-internal only; security group allows inbound from MCP Lambda only
RDB No access — does not touch PostgreSQL (database isolation is respected)

2. Why This App Exists

Replaces direct DocumentDB access from the MCP Server and enforces database isolation: backend owns Postgres, AI owns DocumentDB.

Item Before (V1) After (V2)
DocumentDB reads from MCP Direct connection from TypeScript MCP HTTP GET to this API
DB isolation Violated — backend reads AI's DocumentDB Respected — backend calls HTTP, AI reads its own DB
Deployment coupling MCP requires DocumentDB TLS certs + connection MCP only needs an HTTP client + service token

3. Request Flow

flowchart TD
    A["MCP Server<br/>(guinness-backend)"] -->|"HTTP GET /internal/designs or /internal/code<br/>X-AI-Service-Token"| B["認証ミドルウェア<br/>(定数時間比較)"]
    B -->|無効| C["401 Unauthorized"]
    B -->|有効| D{"クエリパラメータ<br/>でルーティング"}
    D -->|/internal/designs| E["design.find / find_one"]
    D -->|/internal/code| F["code.find / find_one"]
    E --> G["レスポンスエンベロープ構築"]
    F --> H["合計件数取得<br/>ページネーションレスポンス構築"]
    G --> I["{ ok: true, data: {...} }"]
    H --> I
    E -->|見つからない| J["{ ok: false, error: NOT_FOUND }"]

    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333
    style I fill:#bfb,stroke:#333
    style C fill:#fbb,stroke:#333
    style J fill:#fbb,stroke:#333

4. DocumentDB Query Patterns

Four read-only query patterns are selected by path. None returns vector embeddings; every vector_embedding field is excluded from projections.

Pattern 1: List designs by project_id

filter = {"project_id": pid}
if organization_id is not None:
    filter["organization_id"] = organization_id
design_collection.find(filter).skip(offset).limit(limit)
Property Value
Endpoint GET /internal/designs?project_id=...
Collection DESIGN_TABLE_NAME (env var, default design)
Projection _id, organization_id, project_id, name, visual.metadata.image_url, non-vector metadata
When empty Returns { ok: true, data: { total: 0, items: [] } }

Pattern 2: Lookup design by design_id

design_collection.find_one({"_id": design_id})
Property Value
Collection DESIGN_TABLE_NAME (env var, default design)
Operation find_one
Filter {"_id": design_id}
Returned fields design document metadata without vector_embedding fields
When None Returns { ok: false, error: { code: "NOT_FOUND" } }

Pattern 3: List code by project_id

filter = {"project_id": pid}
if organization_id is not None:
    filter["organization_id"] = organization_id
total = code_collection.count_documents(filter)
cursor = (
    code_collection.find(filter)
    .projection({
        "_id": 1,
        "name": 1,
        "visual.metadata.image_url": 1,
    })
    .skip(offset)
    .limit(limit)
)
Property Value
Collection CODE_TABLE_NAME (env var, default code)
Operation find + count_documents
Filter {"project_id": pid} plus organization_id when provided
Projection _id, organization_id, project_id, name, type, based_on, visual.metadata.image_url
Pagination .skip(offset).limit(limit)
Defaults limit=20, offset=0
Max limit 100 (hard cap)
When empty Returns { ok: true, data: { total: 0, items: [] } } — not an error

Pattern 4: Lookup code by code_id

code_collection.find_one({"_id": code_id})
Property Value
Endpoint GET /internal/code/{code_id}
Collection CODE_TABLE_NAME (env var, default code)
Operation find_one
Filter {"_id": code_id}
Returned fields code document metadata, source_code, and css_code; no vector_embedding fields
When None Returns { ok: false, error: { code: "NOT_FOUND" } }

5. Deployment Model

flowchart LR
    subgraph VPC["VPC (ap-northeast-1)"]
        MCP["MCP Lambda<br/>SG: lambda-mcp"]
        API["AI Read API Lambda<br/>SG: lambda-mcp-api<br/>Function URL (VPC 内部)"]
        DOCDB[(DocumentDB<br/>design + code コレクション)]
    end

    MCP -- "HTTP GET<br/>X-AI-Service-Token" --> API
    API -- "クエリ" --> DOCDB
Property Value
Runtime Python 3.12
Handler Lambda Function URL (no API Gateway, no CloudFront)
Network VPC-internal only — cannot be called from outside
Security group inbound MCP Lambda SG only
Security group outbound DocumentDB SG
Memory 256 MB (read-only, no heavy processing)
Timeout 30 seconds

6. Response Envelope

All responses (success and error) use a consistent JSON envelope:

// Success
{
  "ok": true,
  "data": { ... },
  "error": null
}

// Error
{
  "ok": false,
  "data": null,
  "error": { "code": "NOT_FOUND", "message": "Design not found" }
}

The MCP Server converts this envelope into an MCP tool response: - ok: true → structuredContent in the MCP response - ok: false → isError: true in the MCP response


7. Authentication

Service-to-service authentication using a shared secret:

flowchart LR
    MCP["MCP Server"] -->|"X-AI-Service-Token: <secret>"| API["AI Read API"]
    API -->|"hmac.compare_digest<br/>(定数時間)"| ENV["AI_SERVICE_TOKEN<br/>環境変数"]
Property Value
Header name X-AI-Service-Token
Validation hmac.compare_digest(token, AI_SERVICE_TOKEN) — constant-time comparison
On failure HTTP 401, { ok: false, error: { code: "UNAUTHORIZED" } }
Token source Shared secret — same value set in env vars for both MCP Lambda and AI Read API Lambda
Infrastructure random_password Terraform resource or AWS Secrets Manager

8. Module Structure

apps/mcp-api/src/mcp_api/
  handler.py    # Lambda entry point — HTTP handler (Function URL event parsing)
  router.py     # Route definitions — /internal/designs and /internal/code
  service.py    # Business logic — parameter validation, response construction
  repo.py       # DocumentDB I/O — design / code collection queries
  schemas.py    # Request/response Pydantic models
  config.py     # pydantic-settings — AI_SERVICE_TOKEN, DocumentDB connection

Reused from packages: - packages/utils/documentdb.py — DocumentDB connection helper (lazy initialization, reused between invocations) - packages/observability/ — Structured JSON logging (init_logger("ai-mcp"))


9. Environment Variables

Variable Description Example / Default
AI_SERVICE_TOKEN Shared secret for internal authentication (Terraform-generated)
DOCUMENTDB_CONNECTION_STRING DocumentDB connection string mongodb://user:pass@host:27017/?tls=true
DOCUMENTDB_NAME DocumentDB database name guinness_v2
DESIGN_TABLE_NAME design collection name design
CODE_TABLE_NAME code collection name code