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 |