コンテンツにスキップ

AI MCP Read API — 概要

guinness-ai-v2 で動作し、guinness-backend の MCP Server に DocumentDB の design/code data(design / code コレクション)を公開する内部 HTTP サービス。design と code の list/detail endpoint を分けて提供し、サービス間認証(X-AI-Service-Token)で保護される。VPC 内部のみの Lambda Function URL または private HTTP integration としてデプロイされ、外部からのアクセスは不可。

キュー: なし — HTTP のみ(MCP Server からの HTTP GET リクエストでトリガー) 最大実行時間: リクエストあたり 5 秒未満 ソース: guinness-ai-v2/apps/mcp-api/(新規アプリ)


1. 技術スタック

レイヤ 採用技術
ランタイム Python 3.12 on AWS Lambda
AI フレームワーク なし — 純粋なデータ取得、LLM 呼び出しなし
データベース Amazon DocumentDB(MongoDB 互換)— design / code コレクション、AI 管轄
ストレージ なし — S3 アクセスなし
キュー なし — HTTP のみ
認証 X-AI-Service-Token ヘッダー(共有シークレット、定数時間比較)
デプロイ Lambda Function URL、VPC 内部のみ、セキュリティグループは MCP Lambda からのインバウンドのみ許可
RDB アクセスなし — PostgreSQL には接触しない(データベース分離を遵守)

2. このアプリが存在する理由

MCP Server の直接 DocumentDB アクセスを置き換え、データベース分離を実現する:バックエンドは Postgres を管轄、AI は DocumentDB を管轄。

項目 従来(V1) 変更後(V2)
MCP からの DocumentDB 読み取り TypeScript MCP から直接接続 この API への HTTP GET
DB 分離 違反 — バックエンドが AI の DocumentDB を読み取り 遵守 — バックエンドは HTTP を呼び出し、AI は自身の DB を読み取り
デプロイ結合 MCP に DocumentDB TLS 証明書 + 接続が必要 MCP は HTTP クライアント + サービストークンのみ必要

3. リクエストフロー

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 クエリパターン

path で選択される 4 つの読み取り専用クエリパターン。いずれもベクトル埋め込みを返さない(すべての vector_embedding フィールドはプロジェクションから除外)。

パターン 1: project_id による design list

filter = {"project_id": pid}
if organization_id is not None:
    filter["organization_id"] = organization_id
design_collection.find(filter).skip(offset).limit(limit)
プロパティ 値
Endpoint GET /internal/designs?project_id=...
コレクション DESIGN_TABLE_NAME(環境変数、デフォルト design)
プロジェクション _id, organization_id, project_id, name, visual.metadata.image_url, non-vector metadata
結果が空の場合 { ok: true, data: { total: 0, items: [] } } を返す

パターン 2: design_id による design lookup

design_collection.find_one({"_id": design_id})
プロパティ 値
コレクション DESIGN_TABLE_NAME(環境変数、デフォルト design)
オペレーション find_one
フィルタ {"_id": design_id}
取得フィールド vector_embedding を除いた design document metadata
None の場合 { ok: false, error: { code: "NOT_FOUND" } } を返す

パターン 3: project_id による code list

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)
)
プロパティ 値
コレクション CODE_TABLE_NAME(環境変数、デフォルト code)
オペレーション find + count_documents
フィルタ {"project_id": pid}。指定時は organization_id を追加
プロジェクション _id, organization_id, project_id, name, type, based_on, visual.metadata.image_url
ページネーション .skip(offset).limit(limit)
デフォルト limit=20, offset=0
limit の最大値 100(ハードキャップ)
結果が空の場合 { ok: true, data: { total: 0, items: [] } } を返す — エラーではない

パターン 4: code_id による code lookup

code_collection.find_one({"_id": code_id})
プロパティ 値
Endpoint GET /internal/code/{code_id}
コレクション CODE_TABLE_NAME(環境変数、デフォルト code)
オペレーション find_one
フィルタ {"_id": code_id}
取得フィールド code document metadata、source_code、css_code。vector_embedding は返さない
None の場合 { ok: false, error: { code: "NOT_FOUND" } } を返す

5. デプロイメントモデル

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
プロパティ 値
ランタイム Python 3.12
ハンドラ Lambda Function URL(API Gateway なし、CloudFront なし)
ネットワーク VPC 内部のみ — 外部からの呼び出し不可
セキュリティグループ インバウンド MCP Lambda の SG のみ
セキュリティグループ アウトバウンド DocumentDB の SG
メモリ 256 MB(読み取り専用、重い処理なし)
タイムアウト 30 秒

6. レスポンスエンベロープ

すべてのレスポンス(成功・エラー)は一貫した JSON エンベロープを使用する:

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

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

MCP Server はこのエンベロープを MCP ツールレスポンスに変換する: - ok: true → MCP レスポンスの structuredContent - ok: false → MCP レスポンスの isError: true


7. 認証

共有シークレットによるサービス間認証:

flowchart LR
    MCP["MCP Server"] -->|"X-AI-Service-Token: <secret>"| API["AI Read API"]
    API -->|"hmac.compare_digest<br/>(定数時間)"| ENV["AI_SERVICE_TOKEN<br/>環境変数"]
プロパティ 値
ヘッダー名 X-AI-Service-Token
バリデーション hmac.compare_digest(token, AI_SERVICE_TOKEN) — 定数時間比較
失敗時 HTTP 401, { ok: false, error: { code: "UNAUTHORIZED" } }
トークンソース 共有シークレット — MCP Lambda と AI Read API Lambda の環境変数に同じ値を設定
インフラ random_password Terraform リソースまたは AWS Secrets Manager

8. モジュール構成

apps/mcp-api/src/mcp_api/
  handler.py    # Lambda エントリ — HTTP ハンドラ(Function URL イベントパース)
  router.py     # ルート定義 — /internal/designs and /internal/code
  service.py    # ビジネスロジック — パラメータバリデーション、レスポンス構築
  repo.py       # DocumentDB I/O — design / code コレクションクエリ
  schemas.py    # リクエスト/レスポンス Pydantic モデル
  config.py     # pydantic-settings — AI_SERVICE_TOKEN, DocumentDB 接続

packages からの再利用: - packages/utils/documentdb.py — DocumentDB 接続ヘルパー(遅延初期化、呼び出し間で再利用) - packages/observability/ — 構造化 JSON ロギング(init_logger("ai-mcp"))


9. 環境変数

変数 説明 例 / デフォルト
AI_SERVICE_TOKEN 内部認証用共有シークレット (Terraform 生成)
DOCUMENTDB_CONNECTION_STRING DocumentDB 接続文字列 mongodb://user:pass@host:27017/?tls=true
DOCUMENTDB_NAME DocumentDB データベース名 guinness_v2
DESIGN_TABLE_NAME design コレクション名 design
CODE_TABLE_NAME code コレクション名 code