qeaas-mcp について
qeaas-mcpは、乱数や暗号シード、ML-KEM-768鍵素材を必要とするアプリ向けのFastAPIサービスです。取り込んだQRNGエントロピーでHMAC-DRBGをシードし、出力に検証可能な署名付き来歴レシートを付与します。
主な機能
- 01QRNGエントロピーの取り込みと暗号化保存
- 02HMAC-DRBGによるランダムバイト生成
- 03サイコロ結果と暗号シードの提供
- 04ML-KEM-768鍵の生成とカプセル化
- 05Ed25519署名付き来歴レシート
活用シーン
- 01アプリ向けのランダムバイト発行
- 02暗号ワークフロー用シードの生成
- 03ポスト量子ML-KEM-768鍵の作成
- 04発行データの来歴検証
MCP 対応
入力ではqeaas-mcpをサーバーとしていますが、MCP固有のインターフェースや機能は説明されていません。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://api.qeaas.eu/mcpclaude mcp add --transport http eu-qeaas-qeaas https://api.qeaas.eu/mcp{"mcpServers":{"eu-qeaas-qeaas":{"type":"http","url":"https://api.qeaas.eu/mcp"}}}Cursor で開く{"servers":{"eu-qeaas-qeaas":{"type":"http","url":"https://api.qeaas.eu/mcp"}}}VS Code で開く{"mcpServers":{"eu-qeaas-qeaas":{"type":"http","url":"https://api.qeaas.eu/mcp"}}}{"mcpServers":{"eu-qeaas-qeaas":{"serverUrl":"https://api.qeaas.eu/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
6| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| bytes | integer | いいえ |
詳細
Quantum Entropy-as-a-Service (Q-EaaS) — API リファレンス
API キーが必要ですか?[email protected] までメールでお問い合わせください。
プリロードされた QRNG(量子乱数生成器) のビットを、提供用の乱数、サイコロの出目、暗号化シード、耐量子 ML-KEM キーマテリアルに変換する FastAPI サービスです。それぞれの発行には、オフラインで検証可能な署名付きの来歴レシートが付属します。
モノレポの構成:api/(このドキュメントの対象である FastAPI サービス)、web/(解説とデモを提供する Next.js アプリ)、shared/(ドキュメント、試作)、claude/(エピックのビルド計画)。Web アプリは API の薄いクライアントであり、実質的な処理はすべてここで行われます。
正確な説明を一度だけ、そして常に念頭に置いてください。 QRNG が「量子攻撃者を打ち負かす」わけではありません。生の QRNG ビットが提供されることはありません。QRNG ビットは、標準準拠の HMAC-DRBG(NIST SP 800-90A)のシードとなるエントロピーです。この API が返すすべてのバイト列(
/random、/dice、/v1/random/bytes、ML-KEM シード)は、DRBG の出力です。量子が関わるのはエントロピー源です。量子耐性をもたらすのは乱数が「量子的」であることではなく、ML-KEM-768(FIPS 203)です。
1. アーキテクチャの概要
┌──────────── admin (X-Admin-Token) ────────────┐
│ │
QRNG .txt │ POST /admin/ingest │ POST /admin/keys
(0/1 bits) ▼ ▼ POST /admin/keys/revoke
┌─────────────┐ AES-256-GCM ┌───────────────────────────────┐
│ pool.parse │ ───────────────▶ │ Postgres (Neon) │
│ + encrypt │ │ entropy_pool (ciphertext) │
└─────────────┘ │ drbg_root (ciphertext) │
│ api_keys / usage_log / │
│ issue_log │
└───────────────┬───────────────┘
│ decrypt slice on reseed
▼
client ┌───────────────────────────────────────┐
─────────▶ entropy gate │ keyed_drbg.output(n) │
request (503 if low) │ root_key ← reseeded from pool │
+ rate limit ──▶│ counter ← Redis atomic INCR │──▶ HMAC-DRBG(SHA-256)
+ API key │ HMAC-DRBG(root_key, counter) → bytes │ = served bytes
└───────────────────────────────────────┘
│
┌───────────────────────────┴───────────┐
│ Redis (Upstash) │
│ drbg:counter (output uniqueness) │
│ rl:* / quota:* / anon:* (throttling) │
└────────────────────────────────────────┘
- ステートレス/サーバーレス。 各リクエストで Postgres 接続を開き、閉じます(
db.connect)。長期間保持されるプールオブジェクトはありません。リクエストごとに DRBG の状態を読み取って書き戻すことはありません(§4 を参照)。 - 2 つのデータストアが、それぞれ異なる役割を担います(§3 を参照)。
- CORS は
WEB_ORIGINに指定されたオリジン(カンマ区切り)のみに制限されています。 - すべてのレスポンスには
X-Quantum-Entropy: healthy|degradedヘッダーが含まれます(main.pyのミドルウェアが追加)。
ソースマップ(api/qeaas/):
| モジュール | 役割 |
|---|---|
main.py | FastAPI アプリ、すべてのルートハンドラー、CORS、エントロピーヘッダーのミドルウェア |
generation.py | 提供する乱数の唯一の処理経路と来歴メタデータの構築 |
keyed_drbg.py | サーバーレス対応の DRBG ラッパー:再シードのスケジュール、Redis カウンターの混合 |
drbg.py | 純粋な NIST SP 800-90A HMAC-DRBG(SHA-256) |
pool.py | エントロピープール:0/1 の解析、AES-256-GCM による暗号化/復号、HKDF サブキー、burn() |
db.py | 5 つすべてのテーブルに対応する、パラメーター化された psycopg ヘルパー |
redis_client.py | 遅延初期化される Upstash クライアント:アトミックカウンターとレート制限操作 |
gate.py | 低エントロピー時のゲート(entropy_level、require_entropy 依存関係) |
auth.py | 管理者トークンのガード、API キーのハッシュ化と検証 |
ratelimit.py | IP/キーごとのレート制限、匿名ユーザーの日次上限、キーごとのクォータ |
dice.py | 棄却サンプリング方式のサイコロ(剰余バイアスなし) |
kem.py | QRNG シードによる ML-KEM-768 の鍵生成とカプセル化 |
receipts.py | Ed25519 によるレシートの署名と検証 |
errors.py | すべてのエラーに共通の {"error": "<slug>"} エンベロープ |
schemas.py | Pydantic のリクエスト/レスポンスモデル |
2. ランダム性のパイプライン(実行される処理と順序)
配信されるすべてのバイトは、generation.random_bytes(n) →
keyed_drbg.output(n) という1つの要所を通ります。ここを迂回する処理はありません。
A. 取り込み(管理用。リクエスト経路外で実行)。
POST /admin/ingest → pool.ingest_bits_bytes() → pool.parse_bits_bytes() が 0/1
テキストを MSB ファーストでバイトにパックします(末尾の不完全なバイトは破棄し、0/1 以外の文字は拒否します) →
pool.encrypt_chunk() が AES-256-GCM で暗号化します → db.insert_pool_chunk() が
(ciphertext, nonce, tag, plaintext_len, source_label) を entropy_pool に保存します。平文がディスクに触れることはありません。アップロードバッファは burn() されます。
B. 配信(リクエストごと)。 keyed_drbg.output(n):
maybe_reseed()— ウォーム状態のルートキューキャッシュ(_load_cache)を読み込みます。初回利用時にはプールからルートキーをブートストラップします(_bootstrap_root_key)。再シードのスケジュールが到来している場合(§4)、pool.pull_reseed_material(32)がentropy_poolから次の未消費の 32 バイトスライスを復号し、それを新しいdrbg_root行として暗号化して保存し、consumed_offsetを進め、平文をburn()します。redis_client.incr_counter()— アトミックなINCR drbg:counterを実行し、グローバルに一意な整数を返します。- 復号した
root_keyで新しいHmacDrbgをインスタンス化し、generate(n, additional = counter.to_bytes(8) + additional)を呼び出します。このカウンターにより、ルートキーが同じでも、2つの同時リクエストで同一の出力が生成されることはありません。 db.bump_outputs_since_reseed()が再シード走行距離計を加算します。
C. ラップ(エンドポイントごと)。 generation.new_issue_meta(size) が request_id(uuid4)を生成し、entropy_epoch(= drbg_root.reseed_counter)とタイムスタンプを読み取り、receipts.sign(...) を呼び出して Ed25519 レシートを生成します。キー認証が必要なエンドポイントでは、その後 db.insert_usage_log()(不正利用の検出)と db.insert_issue_log()(メタデータのみの来歴記録。出力バイトは一切保存されません)を実行します。
正確な呼び出しグラフについては、このファイル末尾の詳細コメント、またはチャット分析を参照してください。
3. 2つのデータベースを使う理由
それぞれがまったく異なる障害モードから保護し、一貫性に対する要件も正反対です。
Postgres(Neon)— 永続的な秘密情報ストア兼監査台帳
保持する必要があり、トランザクション性と永続性が求められるすべての情報を格納します。テーブルは5つです。
| テーブル | 格納内容 | 備考 |
|---|---|---|
entropy_pool | QRNG ビットの AES-256-GCM 暗号文と nonce、tag、plaintext_len、consumed_offset、source_label | 保管庫です。平文バイト用のカラムはありません。consumed_offset は、各チャンクのうちどれだけを消費したかを追跡します。 |
drbg_root | 現在の DRBG ルートシードの AES-256-GCM 暗号文と reseed_counter、outputs_since_reseed、rotated_at | 稼働中の DRBG キーです。スケジュールに従ってプールからローテーション(「再シード」)されます。reseed_counter は、レシート内の entropy_epoch および /health の drbg_reseeds に相当します。 |
api_keys | key_hash(ペッパー付き HMAC-SHA256)、owner、tier、daily_quota_bytes、revoked、created_at | 平文のキーは保存されません。失効が即時反映されるよう、リクエストごとに最新の情報を読み込みます。 |
usage_log | (ts, principal, endpoint, nbytes) | キー認証が必要な発行処理の不正利用を検出するための台帳です。匿名の /random と /dice はここに記録されません。 |
issue_log | (request_id, principal, endpoint, size, epoch_id, ts) | メタデータのみの来歴記録です。出力バイト用のカラムは一度も存在しません — 秘密情報ではなく、来歴を検証します。 |
永続性は重要です。プールの暗号文またはルートキーを失うと、サービスは来歴を証明できなくなり、再シードもできなくなります。Postgres は ACID に準拠しており、正しさを担保するのはここです。
Redis(Upstash)— 一時的なアトミックカウンターとスロットル
サーバーレス環境の並行処理で Postgres を使うと正確性に問題が生じる、高速で破棄可能なアトミック更新カウンターのみを保持します。
| キー | 用途 | 操作 |
|---|---|---|
drbg:counter | 出力の一意性。 すべての DRBG 呼び出しに単調増加する整数を混ぜることで、並行する 2 つのサーバーレス呼び出しが同じバイト列を出力するのを防ぎます。 | INCR |
rl:ip:{ip}:{minute} | IP ごとのレート制限(匿名ルート) | INCR + EXPIRE 60 |
rl:key:{hash}:{minute} | キーごとのレート制限 | INCR + EXPIRE 60 |
anon:daily:{day} | 匿名出力の全体上限(1 日 5 MB) | INCRBY + EXPIRE、返金時は DECRBY |
quota:key:{hash}:{day} | キーごとの 1 日あたりのバイトクォータ | INCRBY + EXPIRE、返金時は DECRBY |
なぜこれらを Postgres に保存しないのでしょうか? ステートレスなサーバーレス環境では、リクエストごとの共有状態を安全に読み取り・変更・書き込みできません。2 つのリクエストが並行すると競合が発生します。Redis の INCR/INCRBY はアトミックなので、読み取り・変更・書き込みが不要です。これらの値は(分単位、日単位で)期限切れになることを前提としており、失われても問題ありません。Redis の障害時にはスロットリングはフェイルオープンとなり(警告をログに記録し、リクエストを許可します)、再シード頻度の下限(§4)によってプールを枯渇させられないことが引き続き保証されます。
一行でまとめると: Postgres = 常に正確である必要がある永続的なシークレットと監査情報、Redis = 高速で競合のない処理が必要だが破棄可能な一時的アトミックカウンターです。
4. DRBG とビット枯渇の保護
- HMAC-DRBG(SHA-256)、NIST SP 800-90A §10.1.2(
drbg.py)。スレッドセーフではありません。リクエストごとに独自のインスタンスを割り当てます。 - サーバーレス対応のラッパー(
keyed_drbg.py):output(n) = HMAC-DRBG(root_key, INCR(drbg:counter), n)。 - 再シードのスケジュール。 次のいずれかが先に発生した時点で、プールから
root_keyをローテーションします。- 固定の 15 分 間隔(
RESEED_INTERVAL_SECONDS)、または - 出力数が
RESEED_OUTPUT_LIMIT(100 000)以上になった場合。ただし、RESEED_MIN_INTERVAL_SECONDS(5 分)以上が経過している場合に限ります。 再シードごとにプールからRESEED_PULL_BYTES(32)バイトを取得します。
- 固定の 15 分 間隔(
- トラフィックによるビット枯渇は不可能です。 送信されるバイト列は常に DRBG の出力であり、プールの生のビットではありません。また、5 分間の下限により、リクエスト量にかかわらず、ウォールクロック時間より速くキーがローテーションすることはありません。プールの枯渇は時間の関数であり、トラフィックの関数ではありません。
- 低エントロピーゲート。 未消費のプールバイトが
THRESHOLD(64 KiB)を下回ると、/healthはdegradedを報告し、ゲート対象のすべてのルートが503 low_quantum_entropyを返します(gate.require_entropy)。
5. 暗号鍵の階層
環境変数に保持する MASTER_KEY(256 ビットの 16 進数)を 1 つ使用します。pool.derive_subkey(name)(HKDF-SHA256、RFC 5869 の 1 ブロック Expand)で、名前付きのサブキーを正確に 4 つ導出します。これ以外にシークレットはありません。
MASTER_KEY (env / KMS only — never written to Postgres)
└── HKDF-SHA256
├── "pool-encryption-key" → entropy_pool AES-256-GCM
├── "drbg-root-encryption-key" → drbg_root.root_key AES-256-GCM
├── "api-key-pepper" → API-key hashing (HMAC-SHA256)
└── "receipt-signing-key" → Ed25519 receipt signing seed
AES-256-GCM は認証付き暗号です。暗号文のバイトが反転するとタグ検証に失敗し、復号時に例外が発生します。改ざんされたストレージに対して、サービスはフェイルクローズで動作します。機密性の高い平文(復号されたスライス、ウォーム状態のルートキーキャッシュ、アップロードバッファー)は bytearray に保持し、使用後にベストエフォートで burn() します(CPython ではゼロ化を保証できません。サーバーレスの終了処理によって、設計上、プロセス内のシークレットが短時間で破棄されます)。
6. 認証
| 方式 | ヘッダー | 対象 |
|---|---|---|
| なし(匿名) | — | /health、/random、/dice、/v1/verify、/v1/pubkey |
| API キー | X-API-Key: <plaintext> | /v1/random/bytes、/v1/kem/* |
| 管理者トークン | X-Admin-Token: <token> | /admin/* |
- API キー:
hash_api_key(key) = HMAC-SHA256(pepper, key)。保存されるのはハッシュのみです。require_api_keyはリクエストごとに最新の行を読み込むため、revoked = trueは直ちに反映されます。ヘッダーがない場合は401 missing_api_key、不明または失効済みの場合は401 invalid_api_keyです。 - 管理者トークン:
ADMIN_TOKENと定時間比較(hmac.compare_digest)します。誤りまたは未指定の場合は401 unauthorizedです。
7. レート制限、クォータ、ティア
すべてのカウンターは Redis のアトミック操作です。Redis エラー時は制限を適用しません。
-
匿名ユーザー(IP ごと):
/randomと/diceで 60 リクエスト/分(ANON_IP_PER_MIN)。 -
匿名ユーザー(全体、日次):
/randomの出力上限は 5 MB/日(ANON_DAILY_BYTES)。 -
キーごと(
/v1/*)、ティアに応じて適用:ティア 日次クォータ レート制限 default256 KB/日 120 リクエスト/分 iot10 MB/日 600 リクエスト/分 trusted500 MB/日 1 200 リクエスト/分 キーに明示的に設定された
daily_quota_bytesは、ティアのデフォルト値を上書きします。NULLの場合はデフォルト値が適用されます。レート制限はクォータより先に確認します(拒否コストを抑えるためです)。クォータ超過リクエストのバイト数は返金(DECRBY)されるため、拒否されたリクエストで未使用のクォータが消費されることはありません。 -
すべての
429レスポンスには、ウィンドウがリセットされるまでの秒数を示すRetry-Afterヘッダーが含まれます。
8. エラー形式
すべての失敗は、フラットな JSON 本文 {"error": "<slug>"} を返します(errors.py を参照)。主なスラッグは次のとおりです。
| ステータス | スラッグ | 意味 |
|---|---|---|
| 401 | unauthorized / missing_api_key / invalid_api_key | 認証失敗 |
| 404 | not_found | 例:不明なキーのハッシュを失効させようとした |
| 413 | file_too_large | 取り込みアップロードが 10 MB を超過 |
| 422 | bad_request | 検証エラー/不正な形式の入力(FastAPI の検証エラーも含む) |
| 429 | rate_limited / daily_limit_reached / quota_exceeded | 制限超過(Retry-After を含む) |
| 503 | low_quantum_entropy | プールが 64 KiB 未満のため、ゲート対象ルートをブロック |
| 500 | dice_sampling_failed | 棄却サンプリングが抽選上限に達した(発生する可能性は極めて低い) |
9. エンドポイントリファレンス
GET /health — 匿名
用途:稼働時間チェックや監視ダッシュボードで使用します。実トラフィックを送る前に、プール/DRBG が劣化していないことを確認します。 稼働状態とプール/DRBG のステータスを返します。ゲート対象にはなりません。
{ "status": "ok", "quantum_entropy_level": "healthy", // or "degraded"
"pool_bytes_remaining": 699000, "drbg_reseeds": 3, "uptime": 1234.5 }
呼び出し:db.get_root_key、db.pool_bytes_remaining、gate.entropy_level。
GET /random?bytes=<1..64> — 匿名、ゲート対象外
用途:デモや小規模なスクリプトなど、キーを必要としない軽量な用途で、登録不要ですぐにランダムバイトを取得できます。
DRBG 由来のランダムバイトを base64 で返します。プールが degraded の状態でも利用できます。IP ごとのレート制限と匿名ユーザーの日次制限が適用されます。
{ "bytes": 32, "format": "base64", "data": "<base64>" }
呼び出し:ratelimit.check_ip_rate → ratelimit.check_anon_daily → generation.random_bytes。
エラー:422(バイト数が範囲外)、429 rate_limited / daily_limit_reached。
POST /dice — 匿名、ゲート対象外
用途:卓上ゲーム用ボットやプレゼント企画で公平なサイコロを振れます。プレイヤーは、各ロールの背後で抽出されたすべての DRBG バイトを監査できます。
棄却サンプリングによるサイコロ(剰余バイアスなし)。IP ごとのレート制限が適用されます。
リクエスト:{ "sides": 2..100 (default 6), "count": 1..6 (default 1) }
{ "sides": 6, "count": 2, "rolls": [3, 6], "format": "base64",
"bytes_used": "<base64>", "bytes_count": 2 }
bytes_used / bytes_count には、ロールで抽出されたすべての DRBG バイト(採用されたものと棄却されたものの両方)がそのまま含まれます。そのため、ウェブ版サイコロの「このロールで使用されたバイト」切り替えは、文字どおりの意味になります。出力は引き続き DRBG のものであり、生の QRNG ビットではありません。呼び出し:ratelimit.check_ip_rate → dice.roll(keyed_drbg.output(1) をループ)。
エラー:422(範囲外)、429、500 dice_sampling_failed。
GET /v1/random/bytes?size=<32..4096>&format=<hex|base64> — API キー、ゲート対象
用途:開発者向けの主要な連携方法です。生成元を後から証明できる署名付きレシートと組み合わせて、本番品質のランダムバイトを取得します。 開発者向けの標準エンドポイントです。キーごとのレート制限と日次クォータを適用し、その後、使用ログと発行ログを記録します。
{ "request_id": "…", "format": "hex", "data": "…",
"entropy_epoch": 3, "timestamp": "2026-…Z", "receipt": "qeaas1.<payload>.<sig>" }
呼び出し:require_entropy(ゲート)→ require_api_key → ratelimit.enforce_key →
generation.issue_v1(→ random_bytes + new_issue_meta + receipts.sign)→
db.insert_usage_log + db.insert_issue_log。
エラー:401、429 rate_limited/quota_exceeded、503 low_quantum_entropy、422。
POST /v1/verify — 匿名、IP ごとにレート制限
用途:このサービスから実際に出力されたことを証明します。監査や紛争時に役立ち、元のバイト列を再び公開することはありません。
秘密ではなく、来歴を検証します。 リクエスト:{ "request_id"?: str, "receipt"?: str }(少なくとも
一方が必須です。指定がない場合は 422)。
receiptがある場合:署名を暗号学的に検証し、レスポンスでsize、entropy_epoch、timestamp、およびプールのqrng_source_labelsを解決します。request_idのみの場合:通常のissue_log検索を行います(noteにその旨を正直に記載します)。- 改ざんまたは偽造されたレシートの場合 →
verified: false、provenance: null。
{ "request_id": "…", "verified": true,
"provenance": { "size": 64, "entropy_epoch": 3, "timestamp": "…",
"qrng_source_labels": ["fez", "marrakesh"] },
"note": "receipt signature verified cryptographically" }
実行内容:ratelimit.check_ip_rate → receipts.verify(→ receipts.verify_receipt および/または
db.get_issue_log、db.get_pool_source_labels)。オラクルにはなりません — 出力値を受け取ったり比較したりすることはありません。
GET /v1/pubkey — 匿名
用途:公開された署名鍵を使用してレシートを完全にオフラインで検証できます。このサービスへの折り返し呼び出しは不要です。 外部でのオフライン検証用に公開されている Ed25519 レシート署名公開鍵です。
{ "algorithm": "Ed25519", "format": "base64", "public_key": "<base64 32-byte raw>" }
POST /v1/kem/keypair — API キー、ゲート制御あり
用途:テスト用の耐量子 ML-KEM-768 鍵素材を生成します。実運用環境ではクライアント側で鍵を生成してください。このルートはデモ用です。
QRNG でシードした ML-KEM-768(FIPS 203)鍵生成です。リクエスト:{ "include_secret_key"?: false }。
public_key(ek)は常に返されます。secret_key(dk)はデモフローでのみ返され、目立つように
「デモ専用」と注記されます(実際の鍵生成はクライアント側で行われ、秘密鍵が所有者の手元から出ることはありません)。
{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64",
"public_key": "<ek base64>", "secret_key": null,
"entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }
実行内容:ゲート制御 → require_api_key → ratelimit.enforce_key → kem.generate_keypair
(generation.random_bytes(64) → ML_KEM_768.key_derive)→ new_issue_meta → 使用状況/発行ログ。
POST /v1/kem/encapsulate — API キー、ゲート制御あり
用途:すでに保有している公開鍵を使って ML-KEM-768 のカプセル化部分をデモします。サーバー側のデカプセル化ルートはありません。
指定された public_key を使ってカプセル化します。リクエスト:
{ "public_key": "<ek base64>", "include_shared_secret"?: false }。
ciphertext は常に返されます。shared_secret と、説明用の HKDF 由来の demo_key は
include_shared_secret が指定された場合のみ返されます。サーバー側のデカプセル化ルートはありません — デカプセル化は
dk の所有者がクライアント側で行います。
{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64",
"ciphertext": "<base64>", "shared_secret": null, "demo_key": null,
"entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }
実行内容:ゲート制御 → require_api_key → ratelimit.enforce_key → kem.encapsulate
(generation.random_bytes(32) → ML_KEM_768._encaps_internal)→ new_issue_meta → ログ。
不正な形式または長さの異なる ek → 422 bad_request。
kyber-pyは教育目的であり、定時間ではありません — 卒業論文のデモには適していますが、本番環境向けではありません (本番環境では永続ホスト上でliboqsを使用します)。
POST /admin/ingest — 管理者トークン
用途:ディスクに触れる前に暗号化される、新しい生 QRNG ビット列のバッチでエントロピープールを補充します。
0/1 文字のマルチパート .txt アップロード(≤ 10 MB)でプールを補充します。メモリ上で解析して
暗号化し、保存します。平文がディスクに触れることはありません。アップロードバッファーは消去されます。
{ "ingested": true, "bytes_added": 87500, "pool_bytes_remaining": 786500 }
実行内容:require_admin → pool.ingest_bits_bytes(→ parse_bits_bytes → encrypt_chunk →
db.insert_pool_chunk)→ pool.burn。エラー:401、413 file_too_large、422(.txt 以外、または
0/1 以外の内容)。
POST /admin/purge — 管理者トークン
用途:ローテーション、インシデント対応、またはテストのリセットのため、エントロピープールを消去します。この操作は取り消せません。 保存されているすべてのプールチャンクを削除します。取り消しはできません。実行後はプールを再度取り込み直す必要があります。
{ "purged": true, "chunks_removed": 42, "pool_bytes_remaining": 0 }
実行内容:require_admin → db.purge_pool()。
POST /admin/keys — 管理者トークン
用途:開発者または顧客向けに新しい API キーを発行します。発行時に一度だけ表示されます。
API キーを発行します(scripts/mint_key.py と同じロジックです)。平文のキーが返されるのは一度だけです。
リクエスト:{ "owner": str, "tier"?: "default", "daily_quota_bytes"?: int|null }。
{ "api_key": "<plaintext — shown once>", "owner": "devtest",
"tier": "default", "daily_quota_bytes": null }
実行内容:require_admin → secrets.token_urlsafe(32) → auth.hash_api_key → db.insert_api_key。
POST /admin/keys/revoke — 管理者トークン
用途:侵害されたキー、または利用者の離脱に伴い無効化するキーを、直ちに無効化します。次のリクエストから反映されます。
即時に無効化します。リクエスト:{ "key_hash": str }。
{ "key_hash": "…", "revoked": true }
実行処理:require_admin → db.revoke_api_key。不明なハッシュの場合は 404 not_found を返します。
10. ローカル開発
cd api
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill DATABASE_URL, REDIS_URL, MASTER_KEY, ADMIN_TOKEN
すべてのルートで Postgres にアクセスし、ほとんどのルートで Redis にもアクセスします。.env の値が空だと、すべてのリクエストが 500 エラーになります。
使い捨てのローカルバックエンド(Docker Postgres + Redis。すべての SQL マイグレーションを自動適用)を使用する場合:
eval "$(./scripts/dev_db_up.sh --print-env)" # starts qeaas-pg + qeaas-redis, exports env
uvicorn main:app --reload --port 8000 --env-file .env
プールにシードを投入します(0/1 のテキストなら何でもローカルでパイプラインを実行できます。実際のデプロイでは、実際の QRNG 出力を取り込みます):
python3 -c "import random; random.seed(1); open('/tmp/bits.txt','w').write(''.join(random.choice('01') for _ in range(700000)))"
python scripts/ingest_bits.py /tmp/bits.txt seed
python -m scripts.mint_key --owner devtest --tier default # prints the plaintext key once
uvicorn main:app --port 8000
スモークテスト:
curl -s localhost:8000/health
curl -s "localhost:8000/random?bytes=32"
curl -s -X POST localhost:8000/dice -H 'content-type: application/json' -d '{"sides":6,"count":2}'
curl -s -H "X-API-Key: <key>" "localhost:8000/v1/random/bytes?size=64&format=hex"
open localhost:8000/docs # interactive Swagger UI, every route typed
終了するには:pkill -f "uvicorn main:app" && ./scripts/dev_db_down.sh。
環境変数
| キー | 用途 |
|---|---|
DATABASE_URL | Neon の プール接続用 Postgres 接続文字列 |
REDIS_URL | Upstash の rediss://(TLS)接続文字列 |
MASTER_KEY | 256 ビットの 16 進数(secrets.token_hex(32))— サブキー階層のルート(§5) |
ADMIN_TOKEN | /admin/* 用のベアラートークン(secrets.token_urlsafe(32)) |
WEB_ORIGIN | CORS で許可するオリジン(カンマ区切り。デフォルトは http://localhost:3000) |
レート制限、クォータ、ティア、再シードのタイミングは環境変数ではなく、モジュール定数(ratelimit.py、
keyed_drbg.py)です。
運用スクリプト(api/scripts/)
| スクリプト | 内容 |
|---|---|
ingest_bits.py | 0/1 の .txt を解析・暗号化し、プールに保存します |
mint_key.py / revoke_key.py | CLI によるキーのライフサイクル管理(管理者ルートと同等の機能) |
kem_roundtrip.py / kem_handshake.py | 稼働中の API に対して keygen → encaps → decaps を実行し、同じ共有シークレットが復元されることを確認します |
scan_persistence.py | 読み取り専用の不変条件スキャン:平文の機密列や値が永続化されていないことを確認します |
dev_db_up.sh / dev_db_down.sh | ローカル開発用の使い捨て Docker Postgres + Redis |
11. デプロイ(概要)
本番環境は 2 つの独立した Vercel プロジェクト(qeaas-api → qrng-eaas/api、qeaas-web →
qrng-eaas/web)で構成され、1 つではありません。1 つのドメインでは GET /dice(Web ページ)と
POST /dice(API)が競合し、パスベースのリライトでは HTTP メソッドによるルーティングができないためです。qrng-eaas/api/vercel.json では Python 3.13
ランタイムを固定し、すべてのパスを main.py にリライトします。Neon を用意し(sql/001…005 を順番に実行)、Upstash
(リージョナル、rediss:// TCP URL)を用意してから、プロジェクトごとの環境変数を設定します(§10)。完全版の運用手順書、トラブルシューティング、
EPIC 7–11 の資料(シード品質レポート、ネットワーキングのデモ、来歴、セキュアストレージの消去ライフサイクル、テーマ設定)は claude/plans/ と shared/docs/ にあります。
12. エージェント優先の統合(EPIC 13)
このサービスは AI エージェントから検出でき、従来型クローラーと LLM クローラーの両方で正しくインデックス化されます。
正規ドメイン:Web は https://qeaas.eu、API は https://api.qeaas.eu です。
検出用ドキュメント(API):
| URL | 内容 |
|---|---|
/.well-known/agent.json | 機械可読なエージェントマニフェスト:概要、認証、機能、相互リンク |
/.well-known/ai-plugin.json | 最小限の ChatGPT プラグイン形式のマニフェスト → /openapi.json |
/.well-known/mcp.json | MCP エンドポイント、トランスポート、プロトコルバージョン |
/v1/agent/tools | ツール記述子 — 呼び出し可能なエンドポイントごとに 1 つ。JSON Schema を含む(qeaas/schemas.py から取得) |
/v1/agent/manifest | オンボーディング用の完全なドキュメント。フレームワークに合わせたクイックスタートには ?profile=<http|openai-tools|anthropic-tools|mcp> を指定します |
/openapi.json + /docs | 拡張された OpenAPI(タグ、サーバー、ApiKeyAuth ヘッダースキーム) |
POST /mcp | 完全な MCP サーバー(Streamable-HTTP JSON-RPC 2.0):tools、resources、prompts、ハンドシェイク |
Web マップ: /llms.txt、/llms-full.txt、/agents ページ、sitemap.xml、robots.txt
(AI クローラーの完全な許可リスト)、manifest.webmanifest、およびサイト全体の JSON-LD。
キーの取得: 公開エンドポイント(health、random、dice、verify、pubkey)にはキーは不要です。
開発者向け/KEM エンドポイントには X-API-Key ヘッダーが必要です(管理者が発行し、取り消し可能で、使用量に応じてクォータが計上されます)。
単一のカタログで、ずれを防止: qeaas/agent.py に単一の ENDPOINTS カタログがあります。agent.json、
ツール記述子、マニフェスト、MCP ツールリストはすべてこのカタログから生成され、スキーマは
model_json_schema() から取得されます。
環境変数からのベース URL: API は PUBLIC_API_URL / PUBLIC_WEB_URL(デフォルトは localhost、
qeaas/urls.py)を読み取ります。Web は NEXT_PUBLIC_API_URL / NEXT_PUBLIC_WEB_URL(lib/urls.ts)を読み取ります。
WEB_ORIGIN(CORS)には https://qeaas.eu を含める必要があります。
実例:
# 1. an agent finds the manifest
curl -s https://api.qeaas.eu/.well-known/agent.json | jq
# 2. follows to its framework-shaped quickstart
curl -s "https://api.qeaas.eu/v1/agent/manifest?profile=openai-tools" | jq
# 3. calls a public endpoint, or (with a key) a developer one
curl -s "https://api.qeaas.eu/random?bytes=32"
curl -s -H "X-API-Key: $QEAAS_API_KEY" "https://api.qeaas.eu/v1/random/bytes?size=64&format=hex"
# or connect an MCP client to POST https://api.qeaas.eu/mcp
curl -s -X POST https://api.qeaas.eu/mcp -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。