fooddb-jp 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fooddb_jp-0.2.0/.env.example +12 -0
- fooddb_jp-0.2.0/.github/FUNDING.yml +1 -0
- fooddb_jp-0.2.0/.gitignore +25 -0
- fooddb_jp-0.2.0/BILLING.md +150 -0
- fooddb_jp-0.2.0/DEPLOY_PLAN.md +195 -0
- fooddb_jp-0.2.0/LICENSE +36 -0
- fooddb_jp-0.2.0/MCP_BILLING_PLAN.md +244 -0
- fooddb_jp-0.2.0/PKG-INFO +199 -0
- fooddb_jp-0.2.0/README.md +170 -0
- fooddb_jp-0.2.0/README_JA.md +205 -0
- fooddb_jp-0.2.0/README_ja.md +170 -0
- fooddb_jp-0.2.0/TODO.md +147 -0
- fooddb_jp-0.2.0/api.py +503 -0
- fooddb_jp-0.2.0/billing.py +280 -0
- fooddb_jp-0.2.0/mcp_server.py +217 -0
- fooddb_jp-0.2.0/pyproject.toml +52 -0
- fooddb_jp-0.2.0/scripts/build_aliases.py +332 -0
- fooddb_jp-0.2.0/scripts/build_nutrient_master.py +294 -0
- fooddb_jp-0.2.0/scripts/convert.py +344 -0
- fooddb_jp-0.2.0/scripts/convert_all.py +504 -0
- fooddb_jp-0.2.0/scripts/legacy/convert_table_all.py +160 -0
- fooddb_jp-0.2.0/scripts/legacy/food_metadata.py +42 -0
- fooddb_jp-0.2.0/scripts/legacy/generate_header_full.py +58 -0
- fooddb_jp-0.2.0/scripts/legacy/xlsx_to_jsonl.py +83 -0
- fooddb_jp-0.2.0/scripts/migrate_food_names.py +170 -0
- fooddb_jp-0.2.0/scripts/quality_check.py +336 -0
- fooddb_jp-0.2.0/scripts/setup_stripe_products.py +119 -0
- fooddb_jp-0.2.0/scripts/test_food_name_parse.py +115 -0
- fooddb_jp-0.2.0/static/index.html +650 -0
- fooddb_jp-0.2.0/uv.lock +1239 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# fooddb-jp 環境変数
|
|
2
|
+
# ⚠️ このファイルは .gitignore に追加してください
|
|
3
|
+
|
|
4
|
+
# Stripe (テストモード)
|
|
5
|
+
STRIPE_SECRET_KEY=sk_test_XXXXXXXX
|
|
6
|
+
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXX
|
|
7
|
+
STRIPE_PRICE_DEVELOPER=price_1TCy31BtmXXkKBDksjW5nlzY
|
|
8
|
+
STRIPE_PRICE_PRO=price_1TCy31BtmXXkKBDkwYFXiLgw
|
|
9
|
+
|
|
10
|
+
# fooddb-jp
|
|
11
|
+
FOODDB_ADMIN_KEY=fdb_admin_XXXXXXXX
|
|
12
|
+
FOODDB_BASE_URL=https://fooddb.navii.online
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: [takakix2]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.venv/
|
|
5
|
+
.uv/
|
|
6
|
+
|
|
7
|
+
# IDE
|
|
8
|
+
.vscode/
|
|
9
|
+
.idea/
|
|
10
|
+
|
|
11
|
+
# OS
|
|
12
|
+
.DS_Store
|
|
13
|
+
Thumbs.db
|
|
14
|
+
|
|
15
|
+
# Temporary
|
|
16
|
+
*.bak
|
|
17
|
+
*.tmp
|
|
18
|
+
|
|
19
|
+
# Large files (distributed via GitHub Releases)
|
|
20
|
+
fooddb.sqlite
|
|
21
|
+
data/raw/
|
|
22
|
+
data/output/
|
|
23
|
+
|
|
24
|
+
# Secrets
|
|
25
|
+
.env
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# BILLING.md — fooddb-jp 課金アーキテクチャ
|
|
2
|
+
|
|
3
|
+
> 内部ドキュメント。Stripe 連携の構成、Webhook フロー、API キー管理の仕組み。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## アーキテクチャ概要
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ユーザー
|
|
11
|
+
↓ ランディングページ「Subscribe →」
|
|
12
|
+
Stripe Checkout(Stripe ホスト)
|
|
13
|
+
↓ 決済完了
|
|
14
|
+
Stripe Webhook → POST /billing/webhook
|
|
15
|
+
↓ checkout.session.completed
|
|
16
|
+
API キー自動発行 → api_keys テーブルに INSERT
|
|
17
|
+
↓
|
|
18
|
+
ユーザーが MCP config に API キーを設定
|
|
19
|
+
↓
|
|
20
|
+
MCP / REST API リクエスト
|
|
21
|
+
↓ Authorization: Bearer fdb_xxx
|
|
22
|
+
check_rate_limit() → api_keys.requests_today +1
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 料金プラン
|
|
28
|
+
|
|
29
|
+
| プラン | 月額 | 日次上限 | 対象 |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| Free | ¥0 | 100 req/日 | 評価用。API キー不要 |
|
|
32
|
+
| Developer | $9.99 | 10,000 req/日 | 個人開発者・小規模アプリ |
|
|
33
|
+
| Pro | $29.99 | 100,000 req/日 | 商用アプリ・チーム |
|
|
34
|
+
| Admin | — | 無制限 | 内部用(MCP サーバー等) |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 環境変数
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# .env に設定
|
|
42
|
+
STRIPE_SECRET_KEY=sk_test_xxxx # Stripe Secret Key
|
|
43
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxxx # Webhook Signing Secret
|
|
44
|
+
STRIPE_PRICE_DEVELOPER=price_xxxx # Developer プランの Price ID
|
|
45
|
+
STRIPE_PRICE_PRO=price_xxxx # Pro プランの Price ID
|
|
46
|
+
FOODDB_ADMIN_KEY=fdb_admin_xxxx # 管理 API キー
|
|
47
|
+
FOODDB_BASE_URL=https://fooddb.navii.online
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Stripe 構成
|
|
53
|
+
|
|
54
|
+
### Products & Prices(setup_stripe_products.py で作成済み)
|
|
55
|
+
|
|
56
|
+
| Product | Price ID | 金額 | Billing |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| fooddb-jp Developer | `price_xxx` | $9.99/月 | Recurring (monthly) |
|
|
59
|
+
| fooddb-jp Pro | `price_xxx` | $29.99/月 | Recurring (monthly) |
|
|
60
|
+
|
|
61
|
+
### Webhook Events(/billing/webhook で処理)
|
|
62
|
+
|
|
63
|
+
| Event | 処理内容 |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `checkout.session.completed` | API キーを自動発行、`api_keys` テーブルに INSERT |
|
|
66
|
+
| `customer.subscription.deleted` | `label=stripe:{subscription_id}` のキーを DELETE |
|
|
67
|
+
| `customer.subscription.updated` | ログ記録(プラン変更・未払い検知) |
|
|
68
|
+
| `invoice.payment_failed` | ログ記録(要通知実装) |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## API キー管理
|
|
73
|
+
|
|
74
|
+
### テーブル構造(api_keys)
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
CREATE TABLE api_keys (
|
|
78
|
+
key TEXT PRIMARY KEY, -- fdb_{hex32}
|
|
79
|
+
plan TEXT NOT NULL DEFAULT 'developer',
|
|
80
|
+
label TEXT DEFAULT '', -- "stripe:{subscription_id}" or manual label
|
|
81
|
+
email TEXT DEFAULT '',
|
|
82
|
+
created_at TEXT NOT NULL,
|
|
83
|
+
requests_today INTEGER DEFAULT 0,
|
|
84
|
+
last_reset TEXT NOT NULL -- "YYYY-MM-DD"
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### レート制限の仕組み
|
|
89
|
+
|
|
90
|
+
1. リクエスト受信 → `check_rate_limit()` が `Authorization: Bearer` ヘッダーを確認
|
|
91
|
+
2. API キーあり → `api_keys` テーブルで plan と requests_today を照合
|
|
92
|
+
3. `last_reset` が今日でなければ → `requests_today = 0` にリセット
|
|
93
|
+
4. `requests_today >= limit` なら → **429 Too Many Requests**
|
|
94
|
+
5. OK なら → `requests_today += 1`
|
|
95
|
+
6. API キーなし → Free ティア(100 req/日、IP ベース制限は未実装)
|
|
96
|
+
|
|
97
|
+
### 管理 API
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# キー発行
|
|
101
|
+
curl -X POST https://fooddb.navii.online/admin/keys \
|
|
102
|
+
-H "X-Admin-Key: $FOODDB_ADMIN_KEY" \
|
|
103
|
+
-G -d "plan=developer" -d "label=test-key" -d "email=user@example.com"
|
|
104
|
+
|
|
105
|
+
# キー一覧
|
|
106
|
+
curl https://fooddb.navii.online/admin/keys \
|
|
107
|
+
-H "X-Admin-Key: $FOODDB_ADMIN_KEY"
|
|
108
|
+
|
|
109
|
+
# キー削除
|
|
110
|
+
curl -X DELETE https://fooddb.navii.online/admin/keys/fdb_xxx \
|
|
111
|
+
-H "X-Admin-Key: $FOODDB_ADMIN_KEY"
|
|
112
|
+
|
|
113
|
+
# 使用量確認(ユーザー向け)
|
|
114
|
+
curl "https://fooddb.navii.online/billing/usage?api_key=fdb_xxx"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## MCP からの課金フロー
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
MCP Server (ユーザーのマシン)
|
|
123
|
+
↓ FOODDB_API_KEY=fdb_xxx
|
|
124
|
+
↓ httpx GET /foods/search/オートミール
|
|
125
|
+
↓ Authorization: Bearer fdb_xxx
|
|
126
|
+
FastAPI (Mac Mini)
|
|
127
|
+
↓ check_rate_limit()
|
|
128
|
+
↓ api_keys.requests_today += 1
|
|
129
|
+
↓ 応答
|
|
130
|
+
MCP Server
|
|
131
|
+
↓ JSON → テキスト整形
|
|
132
|
+
AI エージェント
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**MCP 経由でも REST API 経由でも、同一の認証・課金パスを通る。**
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## TODO
|
|
140
|
+
|
|
141
|
+
- [ ] Free ティアの IP ベースレート制限実装
|
|
142
|
+
- [ ] 決済成功ページで API キーを表示する HTML ページ
|
|
143
|
+
- [ ] メール通知(SendGrid 連携)で API キーを送信
|
|
144
|
+
- [ ] 使用量ダッシュボード(`/billing/dashboard` — HTML)
|
|
145
|
+
- [ ] 未払い時の自動キー停止(`invoice.payment_failed` → キー無効化)
|
|
146
|
+
- [ ] Stripe Customer Portal(キャンセル・プラン変更のセルフサービス)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
*作成: 2026-03-21*
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# fooddb-jp 公開デプロイ実装計画書
|
|
2
|
+
|
|
3
|
+
> Cloudflare Tunnel で Mac Mini 上の FastAPI を公開し、無料/有料ティアで提供する。
|
|
4
|
+
> Marrow 課金基盤のリハーサルを兼ねる。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. アーキテクチャ
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
[ユーザー] → [Cloudflare CDN/WAF]
|
|
12
|
+
↓
|
|
13
|
+
[Cloudflare Tunnel]
|
|
14
|
+
↓
|
|
15
|
+
[Mac Mini (192.168.1.10)]
|
|
16
|
+
↓
|
|
17
|
+
[FastAPI :8800] ← 既に動作中
|
|
18
|
+
↓
|
|
19
|
+
[SQLite (43MB)]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### なぜ Tunnel か(D1 ではなく)
|
|
23
|
+
|
|
24
|
+
| 比較項目 | D1 方式 | Tunnel 方式 |
|
|
25
|
+
|----------|---------|-------------|
|
|
26
|
+
| コード書き直し | Python → TypeScript (Hono) | **不要**(FastAPI そのまま) |
|
|
27
|
+
| FTS5 互換性 | D1 で使えるか不明 | **問題なし**(ローカル SQLite) |
|
|
28
|
+
| Marrow との共通性 | Workers 側のみ | **アーキテクチャ全体が同じ** |
|
|
29
|
+
| レイテンシ | エッジ実行で最速 | Tunnel 経由(数十ms追加) |
|
|
30
|
+
| 可用性 | Cloudflare 管理 | Mac Mini に依存 |
|
|
31
|
+
|
|
32
|
+
**結論**: Marrow のリハーサルが目的なので、Tunnel 方式が最適。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. 技術スタック
|
|
37
|
+
|
|
38
|
+
| レイヤー | 技術 | 役割 |
|
|
39
|
+
|----------|------|------|
|
|
40
|
+
| CDN/WAF | Cloudflare | DDoS防御、SSL終端、キャッシュ |
|
|
41
|
+
| Tunnel | cloudflared | Mac Mini への暗号化トンネル |
|
|
42
|
+
| API Server | FastAPI (api.py) | REST API(既存) |
|
|
43
|
+
| DB | SQLite (fooddb.sqlite) | データストア(既存) |
|
|
44
|
+
| レート制限 | Cloudflare WAF Rules | IP/APIキー単位 |
|
|
45
|
+
| 課金 | Stripe | Webhook → APIキー発行 |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. 実装ステップ
|
|
50
|
+
|
|
51
|
+
### Phase A: Tunnel セットアップ + 公開 ✅ 完了(2026-03-19)
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
✅ 1. cloudflared v2026.3.0 確認済み(/usr/local/bin/cloudflared)
|
|
55
|
+
✅ 2. Cloudflare 認証完了(navii.online)
|
|
56
|
+
✅ 3. Tunnel 作成: fooddb-jp (ID: 1addee5b-0685-4ea8-a7a6-ded0b7af7d2a)
|
|
57
|
+
✅ 4. DNS 設定: fooddb.navii.online → CNAME
|
|
58
|
+
✅ 5. config.yml: ~/.cloudflared/config.yml
|
|
59
|
+
✅ 6. 動作確認: https://fooddb.navii.online/ — 4コネクション (nrt09/12/16)
|
|
60
|
+
□ 7. systemd で常駐化(Mac Mini 移行時に実施)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**起動コマンド(手動):**
|
|
64
|
+
```bash
|
|
65
|
+
# FastAPI
|
|
66
|
+
cd ~/Documents/MyKnowledge_vault/50_Projects/fooddb-jp
|
|
67
|
+
uv run uvicorn api:app --port 8800
|
|
68
|
+
|
|
69
|
+
# Tunnel
|
|
70
|
+
cloudflared tunnel run fooddb-jp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Phase B: レート制限 + APIキー ✅ 完了(2026-03-19)
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
✅ 8. FastAPI に認証ミドルウェア実装
|
|
77
|
+
- Authorization: Bearer fdb_xxxxx
|
|
78
|
+
- キーの検証: ローカル SQLite テーブル
|
|
79
|
+
- キー無し = Free ティア(100 req/日)
|
|
80
|
+
|
|
81
|
+
✅ 9. APIキー管理テーブル + 管理エンドポイント
|
|
82
|
+
- POST /admin/keys — キー発行
|
|
83
|
+
- GET /admin/keys — キー一覧
|
|
84
|
+
- DELETE /admin/keys/{key} — キー無効化
|
|
85
|
+
- X-Admin-Key ヘッダーでマスター認証
|
|
86
|
+
|
|
87
|
+
✅ 10. 3ティア料金設定(Free / Developer / Pro)
|
|
88
|
+
- 日次リセット(UTC 00:00)
|
|
89
|
+
- 429 Too Many Requests 返却
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Phase C: Stripe 課金連携 ✅ 完了(2026-03-20)
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
✅ 11. Stripe Product / Price 作成
|
|
96
|
+
- Product: fooddb-jp API
|
|
97
|
+
- Developer: $9.99/mo (price_1TCy31BtmXXkKBDksjW5nlzY)
|
|
98
|
+
- Pro: $29.99/mo (price_1TCy31BtmXXkKBDkwYFXiLgw)
|
|
99
|
+
|
|
100
|
+
✅ 12. Stripe Checkout Webhook
|
|
101
|
+
- checkout.session.completed → api_keys テーブルにキー発行
|
|
102
|
+
- customer.subscription.deleted → キー無効化
|
|
103
|
+
|
|
104
|
+
✅ 13. 課金エンドポイント追加(FastAPI)
|
|
105
|
+
POST /billing/checkout → Stripe Checkout URL生成
|
|
106
|
+
GET /billing/success → 決済成功ページ
|
|
107
|
+
GET /billing/cancel → キャンセルページ
|
|
108
|
+
GET /billing/usage → 使用量確認
|
|
109
|
+
|
|
110
|
+
✅ 14. Webhook 受信
|
|
111
|
+
POST /billing/webhook → Stripe Signature 検証 + キー管理
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Phase D: 公開準備(1セッション)
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
□ 15. ドメイン設定
|
|
118
|
+
- DNS + SSL は Cloudflare 自動
|
|
119
|
+
|
|
120
|
+
□ 16. ドキュメント
|
|
121
|
+
- OpenAPI / Swagger(FastAPI 自動生成)
|
|
122
|
+
- 登録手順・コード例
|
|
123
|
+
|
|
124
|
+
□ 17. GitHub Public Repo
|
|
125
|
+
- SQLite + スクリプト = Public
|
|
126
|
+
- API キーは別管理
|
|
127
|
+
|
|
128
|
+
□ 18. 告知(Product Hunt / Hacker News / Zenn)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 4. 料金プラン
|
|
134
|
+
|
|
135
|
+
| プラン | 月額 | レート制限 | 認証 |
|
|
136
|
+
|--------|------|-----------|------|
|
|
137
|
+
| **Free** | ¥0 | 100 req/日 | 不要 |
|
|
138
|
+
| **Developer** | $9.99/mo | 10,000 req/日 | APIキー |
|
|
139
|
+
| **Pro** | $29.99/mo | 100,000 req/日 | APIキー |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 5. Marrow へのフィードバック項目
|
|
144
|
+
|
|
145
|
+
| 項目 | fooddb-jp で検証 | Marrow に適用 |
|
|
146
|
+
|------|-----------------|---------------|
|
|
147
|
+
| **Tunnel セットアップ** | 手順・信頼性・レイテンシ | 同じ構成を流用 |
|
|
148
|
+
| **常駐化** | systemd/launchd の設定 | Marrow も同様に常駐 |
|
|
149
|
+
| **Cloudflare WAF** | レート制限の粒度 | APIプラン制御 |
|
|
150
|
+
| **Stripe Webhook** | 決済 → キー発行フロー | 課金フロー全体 |
|
|
151
|
+
| **可用性** | Mac Mini ダウン時の挙動 | 運用ポリシーの判断材料 |
|
|
152
|
+
| **レイテンシ** | Tunnel 経由の実測値 | SLA 設計の根拠 |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 6. 投資対効果
|
|
157
|
+
|
|
158
|
+
| コスト | 金額 |
|
|
159
|
+
|--------|------|
|
|
160
|
+
| Cloudflare Free プラン | ¥0 |
|
|
161
|
+
| Tunnel | ¥0(Free プランに含む) |
|
|
162
|
+
| ドメイン | $10/年 |
|
|
163
|
+
| Stripe 手数料 | 3.6% + ¥40/件 |
|
|
164
|
+
| Mac Mini 電気代 | 既存インフラ |
|
|
165
|
+
| **合計追加費用** | **≒ ¥100/月** |
|
|
166
|
+
|
|
167
|
+
**損益分岐点**: Developer プラン 1件で黒字
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 7. リスク・注意事項
|
|
172
|
+
|
|
173
|
+
1. **Mac Mini の可用性**
|
|
174
|
+
- 停電・再起動時に API ダウン
|
|
175
|
+
- UPS + launchd 自動復帰で軽減
|
|
176
|
+
- Cloudflare が 502 を返すので、カスタムエラーページを設定
|
|
177
|
+
|
|
178
|
+
2. **Tunnel のレイテンシ**
|
|
179
|
+
- ローカル実測: 3-5ms(同一筐体ループバック)
|
|
180
|
+
- 外部からの実測は未実施(日本国内: 10-30ms 追加の見込み)
|
|
181
|
+
- 読み取り専用 + 軽量クエリなので許容範囲
|
|
182
|
+
|
|
183
|
+
3. **データ更新**
|
|
184
|
+
- 成分表は年1回更新(文科省)
|
|
185
|
+
- SQLite を差し替えて FastAPI 再起動のみ
|
|
186
|
+
|
|
187
|
+
4. **著作権**
|
|
188
|
+
- 政府統計データ → 出典明記で自由利用可
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
*作成: 2026-03-18*
|
|
193
|
+
*改訂: 2026-03-19(D1方式 → Tunnel方式に変更、Phase A 完了、Phase B 完了)*
|
|
194
|
+
*改訂: 2026-03-20(Phase C 完了、GitHub 公開完了)*
|
|
195
|
+
*Status: Phase A+B+C 完了 → Phase D(ドキュメント・告知)待ち*
|
fooddb_jp-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 takakix2
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Data License
|
|
26
|
+
|
|
27
|
+
The food composition data included in this project is sourced from the
|
|
28
|
+
"Standard Tables of Food Composition in Japan" (日本食品標準成分表)
|
|
29
|
+
published by the Ministry of Education, Culture, Sports, Science and
|
|
30
|
+
Technology (MEXT), Japan.
|
|
31
|
+
|
|
32
|
+
This data is available under the Government of Japan Standard Terms of Use
|
|
33
|
+
(Version 2.0), which is compatible with Creative Commons Attribution 4.0
|
|
34
|
+
International (CC BY 4.0).
|
|
35
|
+
|
|
36
|
+
Attribution: 文部科学省「日本食品標準成分表(八訂)増補2023年」
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# MCP → REST API 統合計画(課金ゲートウェイ化)
|
|
2
|
+
|
|
3
|
+
> MCP Server が SQLite を直接読む構造を廃止し、REST API 経由に切り替える。
|
|
4
|
+
> これにより MCP 経由の利用にも認証・レート制限・課金が適用される。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 現状の問題
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
REST API → FastAPI (api.py) → 認証 ✅ → レート制限 ✅ → 課金 ✅ → SQLite
|
|
12
|
+
MCP Server → mcp_server.py → 直接 SQLite 読み取り ❌ 課金されない
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 影響
|
|
16
|
+
- MCP 経由の利用が課金対象外
|
|
17
|
+
- 検索ロジックが api.py と mcp_server.py に二重実装
|
|
18
|
+
- エイリアス検索・FTS5 ロジックの同期コスト
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. 目標アーキテクチャ
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
MCP Server (mcp_server.py)
|
|
26
|
+
↓ HTTP (httpx)
|
|
27
|
+
REST API (FastAPI, api.py) — https://fooddb.navii.online/
|
|
28
|
+
↓ 認証 + レート制限 + 課金
|
|
29
|
+
SQLite (fooddb.sqlite)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 原則
|
|
33
|
+
- **MCP Server=薄いラッパー**(REST API の結果を LLM 向けに整形するだけ)
|
|
34
|
+
- **ビジネスロジック=REST API に一本化**
|
|
35
|
+
- **MCP Server は API キーを持つ**(環境変数 `FOODDB_API_KEY` で注入)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. MCP ツール ↔ REST エンドポイント マッピング
|
|
40
|
+
|
|
41
|
+
| MCP ツール | 現在の実装 | 対応 REST エンドポイント |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `search_food(query, limit)` | 直接 SQL(4段フォールバック) | `GET /foods/search/{query}?limit=N` |
|
|
44
|
+
| `get_food_nutrients(food_number, table)` | 直接 SQL(JOIN) | `GET /foods/{food_number}?table=T` |
|
|
45
|
+
| `calculate_nutrition(foods_and_amounts)` | 直接 SQL(JOIN + 集計) | `GET /calculate?foods=1004:100,12004:60` |
|
|
46
|
+
| `nutrient_ranking(tag, limit)` | 直接 SQL(ORDER BY) | `GET /ranking/{tag}?limit=N` |
|
|
47
|
+
| `list_food_groups()` | 直接 SQL(GROUP BY) | `GET /groups` |
|
|
48
|
+
|
|
49
|
+
**すべてのツールに対応するエンドポイントが既に存在する。** コードの書き換えのみで済む。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 4. 実装ステップ
|
|
54
|
+
|
|
55
|
+
### Step 1: MCP Server 用 API キーの発行
|
|
56
|
+
|
|
57
|
+
Mac Mini 上で実行:
|
|
58
|
+
```bash
|
|
59
|
+
curl -X POST https://fooddb.navii.online/admin/keys \
|
|
60
|
+
-H "X-Admin-Key: $FOODDB_ADMIN_KEY" \
|
|
61
|
+
-G -d "plan=admin" -d "label=mcp-server-internal"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
→ 発行されたキーを `.env` に追加:
|
|
65
|
+
```
|
|
66
|
+
FOODDB_API_KEY=fdb_xxxxxxxxxxxxxxxx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Step 2: mcp_server.py の書き換え
|
|
70
|
+
|
|
71
|
+
**Before(直接 DB アクセス):**
|
|
72
|
+
```python
|
|
73
|
+
import sqlite3
|
|
74
|
+
|
|
75
|
+
DB_PATH = Path(__file__).parent / "fooddb.sqlite"
|
|
76
|
+
|
|
77
|
+
def get_db():
|
|
78
|
+
conn = sqlite3.connect(str(DB_PATH))
|
|
79
|
+
conn.row_factory = sqlite3.Row
|
|
80
|
+
return conn
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
def search_food(query: str, limit: int = 10) -> str:
|
|
84
|
+
db = get_db()
|
|
85
|
+
rows = db.execute("SELECT ... WHERE food_name LIKE ?", ...)
|
|
86
|
+
# 100行以上の SQL ロジック
|
|
87
|
+
db.close()
|
|
88
|
+
return formatted_result
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**After(REST API 経由):**
|
|
92
|
+
```python
|
|
93
|
+
import httpx
|
|
94
|
+
import os
|
|
95
|
+
|
|
96
|
+
API_BASE = os.environ.get("FOODDB_API_URL", "https://fooddb.navii.online")
|
|
97
|
+
API_KEY = os.environ.get("FOODDB_API_KEY", "")
|
|
98
|
+
|
|
99
|
+
def _client() -> httpx.Client:
|
|
100
|
+
headers = {}
|
|
101
|
+
if API_KEY:
|
|
102
|
+
headers["Authorization"] = f"Bearer {API_KEY}"
|
|
103
|
+
return httpx.Client(base_url=API_BASE, headers=headers, timeout=10.0)
|
|
104
|
+
|
|
105
|
+
@mcp.tool()
|
|
106
|
+
def search_food(query: str, limit: int = 10) -> str:
|
|
107
|
+
"""食品名であいまい検索する。例: 'オートミール', '鶏むね', '豆腐'"""
|
|
108
|
+
with _client() as c:
|
|
109
|
+
res = c.get(f"/foods/search/{query}", params={"limit": limit})
|
|
110
|
+
res.raise_for_status()
|
|
111
|
+
data = res.json()
|
|
112
|
+
|
|
113
|
+
if not data:
|
|
114
|
+
return f"「{query}」に一致する食品は見つかりませんでした。"
|
|
115
|
+
|
|
116
|
+
lines = [f"検索結果 ({len(data)}件):"]
|
|
117
|
+
for food in data:
|
|
118
|
+
parts = [f"{food['food_number']}: {food['food_name']}"]
|
|
119
|
+
meta = []
|
|
120
|
+
if food.get("group_name"): meta.append(food["group_name"])
|
|
121
|
+
if food.get("category"): meta.append(f"<{food['category']}>")
|
|
122
|
+
if meta: parts.append(f"({', '.join(meta)})")
|
|
123
|
+
lines.append(" ".join(parts))
|
|
124
|
+
return "\n".join(lines)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Step 3: 各ツールの書き換え
|
|
128
|
+
|
|
129
|
+
| ツール | API コール | 整形 |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `search_food` | `GET /foods/search/{query}` | JSON → テキスト一覧 |
|
|
132
|
+
| `get_food_nutrients` | `GET /foods/{number}?table=T` | JSON → 成分表テキスト |
|
|
133
|
+
| `calculate_nutrition` | `GET /calculate?foods=...` | JSON → 栄養素合計テキスト |
|
|
134
|
+
| `nutrient_ranking` | `GET /ranking/{tag}?limit=N` | JSON → ランキングテキスト |
|
|
135
|
+
| `list_food_groups` | `GET /groups` | JSON → グループ一覧テキスト |
|
|
136
|
+
|
|
137
|
+
### Step 4: REST API 側の微調整
|
|
138
|
+
|
|
139
|
+
現在の REST API レスポンスを確認し、MCP から使いやすい形になっているか検証。
|
|
140
|
+
不足があれば追加:
|
|
141
|
+
|
|
142
|
+
- [ ] `/foods/search/{query}` — カテゴリ・サブカテゴリ情報が含まれるか確認
|
|
143
|
+
- [ ] `/calculate` — 主要栄養素の優先表示順が API 側にもあるか確認
|
|
144
|
+
- [ ] エラーレスポンスの統一(404, 400 のフォーマット)
|
|
145
|
+
|
|
146
|
+
### Step 5: 依存関係の更新
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
uv add httpx # mcp_server.py の新しい依存
|
|
150
|
+
uv remove sqlite3 # 不要になる(標準ライブラリなので実際は不要)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Step 6: テスト
|
|
154
|
+
|
|
155
|
+
1. **ユニットテスト**: MCP ツールが正しい REST エンドポイントを呼ぶか
|
|
156
|
+
2. **統合テスト**: MCP 経由で検索 → API キーのカウンタが増加するか
|
|
157
|
+
3. **レート制限テスト**: Free キーで 100 回超えたら MCP も 429 になるか
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# 統合テスト例
|
|
161
|
+
uv run python -c "
|
|
162
|
+
from mcp_server import search_food
|
|
163
|
+
result = search_food('米', 3)
|
|
164
|
+
print(result)
|
|
165
|
+
"
|
|
166
|
+
# → API の requests_today が +1 されていることを確認
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Step 7: Mac Mini へのデプロイ
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Mac Mini にSSH
|
|
173
|
+
ssh takaki@192.168.1.10
|
|
174
|
+
|
|
175
|
+
# コード更新
|
|
176
|
+
cd ~/projects/fooddb-jp
|
|
177
|
+
git pull
|
|
178
|
+
|
|
179
|
+
# .env に API キーを追加
|
|
180
|
+
echo 'FOODDB_API_KEY=fdb_xxx' >> .env
|
|
181
|
+
|
|
182
|
+
# サービス再起動(systemd)
|
|
183
|
+
sudo systemctl restart fooddb-api
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 5. 削除されるコード
|
|
189
|
+
|
|
190
|
+
mcp_server.py から削除される部分:
|
|
191
|
+
- `sqlite3` import と `DB_PATH` 定義
|
|
192
|
+
- `get_db()` 関数
|
|
193
|
+
- 各ツール内の SQL クエリ(合計 ~150行)
|
|
194
|
+
- エイリアス交差検索ロジック(api.py に一本化済み)
|
|
195
|
+
|
|
196
|
+
**mcp_server.py は ~260行 → ~100行 に縮小**
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 6. 副次効果
|
|
201
|
+
|
|
202
|
+
| Before | After |
|
|
203
|
+
|---|---|
|
|
204
|
+
| MCP は SQLite を直接読む(課金外) | **全アクセスが課金対象** |
|
|
205
|
+
| 検索ロジックが2箇所に存在 | **api.py に一本化** |
|
|
206
|
+
| mcp_server.py が DB ファイルに依存 | **URL だけ知っていれば動く** |
|
|
207
|
+
| Mac Mini 上でしか MCP が動かない | **どこからでも REST 経由で接続可能** |
|
|
208
|
+
| MCP の食品検索品質 ≠ API の検索品質 | **同一のロジックを共有** |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 7. Marrow への水平展開
|
|
213
|
+
|
|
214
|
+
この「MCP = 薄いラッパー / REST API = 課金ゲートウェイ」パターンは、
|
|
215
|
+
Marrow の MCP Server にもそのまま適用できる:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Marrow MCP Server
|
|
219
|
+
↓ HTTP
|
|
220
|
+
Marrow REST API (Cloudflare Workers)
|
|
221
|
+
↓ 認証 + 課金 (Stripe)
|
|
222
|
+
Marrow Gateway (Crawler Engine)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
fooddb-jp をこのパターンの **最初の実証例** として確立する。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 8. 工数見積
|
|
230
|
+
|
|
231
|
+
| ステップ | 工数 |
|
|
232
|
+
|---|---|
|
|
233
|
+
| Step 1: API キー発行 | 1分 |
|
|
234
|
+
| Step 2-3: mcp_server.py 書き換え | 30分 |
|
|
235
|
+
| Step 4: REST API 微調整 | 15分 |
|
|
236
|
+
| Step 5: 依存関係更新 | 5分 |
|
|
237
|
+
| Step 6: テスト | 15分 |
|
|
238
|
+
| Step 7: デプロイ | 10分 |
|
|
239
|
+
| **合計** | **~1.5時間** |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
*作成: 2026-03-21*
|
|
244
|
+
*Status: 計画完了 → 実装待ち*
|