Qeaas Mcp

eu.qeaas由 qeaas.eu 發布官方發布者v1.0.0更新於 Oct 24, 2026

QRNG-seeded random bytes, seeds, and ML-KEM-768 keys with signed provenance receipts.

已驗證Streamable HTTP可網頁執行開發者工具安全與監控

概覽

AI 產生的概覽

遠端 MCP 伺服器,讓助理取得以量子熵播種的隨機位元組、公平骰子、可驗證來源憑證,以及 ML-KEM-768 後量子金鑰示範。

可以做什麼
取得隨機位元組:匿名 base64 輸出最多 64 位元組,或以金鑰取得 32 至 4096 位元組的十六進位/base64 輸出,並附簽章來源憑證與請求 ID。擲骰子:採拒絕取樣,並回傳每次擲骰所用的 DRBG 位元組。驗證來源:依請求 ID 或憑證簽章核驗,或取得簽章公開金鑰以便離線驗證。執行後量子示範:產生 ML-KEM-768 金鑰對,或對指定公開金鑰執行封裝。
適用情境
適合需要可稽核隨機性、用於示範或抽獎的公平骰子,或快速示範帶來源憑證的 ML-KEM-768 金鑰產生與封裝。一般應用隨機數不需要使用。
執行需求
遠端 streamable HTTP 端點 api.qeaas.eu,無需本機安裝。匿名工具無需憑證。random_bytes、kem_keypair 與 kem_encapsulate 需要 X-API-Key 標頭,README 說明須以電子郵件向營運方申請。需要網路存取該服務。
安裝前請注意
需金鑰的工具要提供 X-API-Key 密鑰,服務依金鑰計算配額並限流。KEM 工具明確為示範用途:密鑰僅在示範流程中回傳,README 警告底層程式庫為教學用途且非恆定時間,不適合產生正式環境金鑰材料。服務可看到每個請求並記錄用量與簽發中繼資料,但聲明不儲存輸出位元組。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Qeaas Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/eu.qeaas%2Fqeaas-mcp/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "qeaas": {
      "type": "http",
      "url": "https://api.qeaas.eu/mcp"
    }
  }
}

README

Quantum Entropy-as-a-Service (Q-EaaS) — API Reference

Want an API key? Email [email protected].

FastAPI service that turns preloaded QRNG (quantum random number generator) bits into served randomness, dice rolls, cryptographic seeds, and post-quantum ML-KEM key material — each issue carrying a signed, offline-verifiable provenance receipt.

Monorepo layout: api/ (this document — FastAPI service), web/ (Next.js explainer + demo), shared/ (docs, spikes), claude/ (epic build plans). The web app is a thin client over the API; everything substantive happens here.

Honest framing, stated once and meant everywhere. QRNG does not "defeat quantum attackers." Raw QRNG bits are never served. They are entropy that seeds a standards HMAC-DRBG (NIST SP 800-90A). Every byte this API returns — /random, /dice, /v1/random/bytes, ML-KEM seeds — is DRBG output. The quantum part is the entropy source; any quantum resistance comes from ML-KEM-768 (FIPS 203), not from the randomness being "quantum."


1. Architecture at a glance

             ┌──────────── 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) │                             └────────────────────────────────────────┘
  • Stateless / serverless. Each request opens a Postgres connection and closes it (db.connect); there is no long-lived pool object. DRBG state is never read-modify-written per request — see §4.
  • Two datastores, two different jobs — see §3.
  • CORS is locked to the origins in WEB_ORIGIN (comma-separated).
  • Every response carries an X-Quantum-Entropy: healthy|degraded header (added by middleware in main.py).

Source map (api/qeaas/):

ModuleResponsibility
main.pyFastAPI app, all route handlers, CORS, entropy header middleware
generation.pyThe single served-randomness choke point + provenance metadata builder
keyed_drbg.pyServerless-safe DRBG wrapper: reseed schedule, Redis-counter mixing
drbg.pyPure NIST SP 800-90A HMAC-DRBG (SHA-256)
pool.pyEntropy pool: parse 0/1, AES-256-GCM encrypt/decrypt, HKDF sub-keys, burn()
db.pyParameterized psycopg helpers for all five tables
redis_client.pyLazy Upstash client: atomic counter + rate-limit ops
gate.pyLow-entropy gate (entropy_level, require_entropy dependency)
auth.pyAdmin-token guard, API-key hashing + validation
ratelimit.pyPer-IP / per-key rate limits, anon daily ceiling, per-key quota
dice.pyRejection-sampled dice (no modulo bias)
kem.pyQRNG-seeded ML-KEM-768 keygen + encapsulation
receipts.pyEd25519 receipt signing + verification
errors.pyFlat {"error": "<slug>"} envelope for every failure
schemas.pyPydantic request/response models

2. The randomness pipeline (what fires, in order)

Every served byte flows through one choke point, generation.random_bytes(n) → keyed_drbg.output(n). Nothing bypasses it.

A. Ingest (admin, offline of the request path). POST /admin/ingest → pool.ingest_bits_bytes() → pool.parse_bits_bytes() packs the 0/1 text MSB-first into bytes (discarding a trailing partial byte, rejecting any non-0/1 char) → pool.encrypt_chunk() AES-256-GCM-encrypts it → db.insert_pool_chunk() stores (ciphertext, nonce, tag, plaintext_len, source_label) in entropy_pool. Plaintext never touches disk; the upload buffer is burn()-ed.

B. Serve (per request). keyed_drbg.output(n):

  1. maybe_reseed() — loads the warm root-key cache (_load_cache), bootstrapping the root key from the pool on very first use (_bootstrap_root_key). If the reseed schedule is due (§4), pool.pull_reseed_material(32) decrypts the next unconsumed 32-byte slice from entropy_pool, encrypts it as the new drbg_root row, advances consumed_offset, and burn()s the plaintext.
  2. redis_client.incr_counter() — atomic INCR drbg:counter, returns a globally unique integer.
  3. A fresh HmacDrbg is instantiated with the decrypted root_key and generate(n, additional = counter.to_bytes(8) + additional) is called — the counter guarantees two concurrent requests can never produce identical output even with identical root key.
  4. db.bump_outputs_since_reseed() increments the reseed odometer.

C. Wrap (per endpoint). generation.new_issue_meta(size) mints a request_id (uuid4), reads entropy_epoch (= drbg_root.reseed_counter), timestamps, and calls receipts.sign(...) to produce the Ed25519 receipt. Keyed endpoints then write db.insert_usage_log() (abuse spotting) and db.insert_issue_log() (metadata-only provenance — no output bytes are ever stored).

See the deep-dive comment at the end of this file / in the chat analysis for the exact call graph.


3. Why two databases

They protect against completely different failure modes and have opposite consistency needs.

Postgres (Neon) — the durable secret store & audit ledger

Holds everything that must survive and must be transactional & durable. Five tables:

TableHoldsNotes
entropy_poolAES-256-GCM ciphertext of QRNG bits + nonce, tag, plaintext_len, consumed_offset, source_labelThe vault. No plaintext-bytes column exists. consumed_offset tracks how much of each chunk has been spent.
drbg_rootAES-256-GCM ciphertext of the current DRBG root seed + reseed_counter, outputs_since_reseed, rotated_atThe live DRBG key, rotated ("reseeded") from the pool on schedule. reseed_counter == entropy_epoch in receipts and drbg_reseeds in /health.
api_keyskey_hash (HMAC-SHA256, peppered), owner, tier, daily_quota_bytes, revoked, created_atPlaintext keys never stored. Read fresh every request so revocation is instant.
usage_log(ts, principal, endpoint, nbytes)Abuse-spotting ledger for keyed issues. Anon /random//dice are not logged here.
issue_log(request_id, principal, endpoint, size, epoch_id, ts)Metadata-only provenance. No output-bytes column exists, ever — verify provenance, not the secret.

Durability matters: lose the pool ciphertext or the root key and the service can no longer prove provenance or reseed. Postgres is ACID; this is where correctness lives.

Redis (Upstash) — the ephemeral atomic counter & throttle

Holds only fast, disposable, atomically-mutated counters that would be a correctness hazard in Postgres under serverless concurrency:

KeyPurposeOp
drbg:counterOutput uniqueness. Monotonic integer mixed into every DRBG call so two concurrent serverless invocations can't emit the same bytes.INCR
rl:ip:{ip}:{minute}Per-IP rate limit (anon routes)INCR + EXPIRE 60
rl:key:{hash}:{minute}Per-key rate limitINCR + EXPIRE 60
anon:daily:{day}Global anon output ceiling (5 MB/day)INCRBY + EXPIRE, DECRBY on refund
quota:key:{hash}:{day}Per-key daily byte quotaINCRBY + EXPIRE, DECRBY on refund

Why not put these in Postgres? On stateless serverless you cannot safely read-modify-write shared state per request — two concurrent requests would race. Redis INCR/INCRBY are atomic, so there is no read-modify-write. These values are meant to expire (per-minute, per-day) and losing them is harmless — throttling fails open on a Redis outage (logs a warning, allows the request), because the reseed-frequency floor (§4) still guarantees the pool can't be drained.

One-line summary: Postgres = durable secrets + audit that must be correct forever; Redis = ephemeral atomic counters that must be fast and race-free but are disposable.


4. The DRBG & bit-drain protection

  • HMAC-DRBG (SHA-256), NIST SP 800-90A §10.1.2 (drbg.py). Not thread-safe — each request gets its own instance.
  • Serverless-safe wrapper (keyed_drbg.py): output(n) = HMAC-DRBG(root_key, INCR(drbg:counter), n).
  • Reseed schedule. root_key rotates from the pool on whichever fires first:
    • a fixed 15-minute interval (RESEED_INTERVAL_SECONDS), or
    • an output count ≥ RESEED_OUTPUT_LIMIT (100 000) but only if ≥ RESEED_MIN_INTERVAL_SECONDS (5 minutes) has elapsed. Each reseed pulls RESEED_PULL_BYTES (32) from the pool.
  • Bit-drain is impossible via traffic. Served bytes are always DRBG output, never raw pool bits, and the 5-minute floor means no request volume can rotate the key faster than wall-clock time. Pool drain is a function of time, never traffic.
  • Low-entropy gate. Below THRESHOLD (64 KiB) of unconsumed pool bytes, /health reports degraded and all gated routes 503 low_quantum_entropy (gate.require_entropy).

5. Cryptographic key hierarchy

One env-held MASTER_KEY (256-bit hex). pool.derive_subkey(name) (HKDF-SHA256, RFC 5869 one-block Expand) derives exactly four named sub-keys — there are no other secrets:

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 is authenticated: a flipped ciphertext byte fails the tag check and decryption raises — the service fails closed on tampered storage. Sensitive plaintext (decrypted slices, the warm root-key cache, upload buffers) is held in bytearray and best-effort burn()-ed after use (CPython cannot guarantee zeroization; serverless teardown keeps in-process secrets short-lived by design).


6. Authentication

MechanismHeaderApplies to
None (anonymous)—/health, /random, /dice, /v1/verify, /v1/pubkey
API keyX-API-Key: <plaintext>/v1/random/bytes, /v1/kem/*
Admin tokenX-Admin-Token: <token>/admin/*
  • API keys: hash_api_key(key) = HMAC-SHA256(pepper, key); only the hash is stored. require_api_key reads the row fresh every request, so revoked = true takes effect immediately. Missing header → 401 missing_api_key; unknown/revoked → 401 invalid_api_key.
  • Admin token: constant-time compared (hmac.compare_digest) against ADMIN_TOKEN. Wrong/missing → 401 unauthorized.

7. Rate limits, quotas, tiers

All counters are atomic Redis ops; fail open on a Redis error.

  • Anon per-IP: 60 req/min on /random and /dice (ANON_IP_PER_MIN).

  • Anon global daily: 5 MB/day output ceiling on /random (ANON_DAILY_BYTES).

  • Per-key (/v1/*), tier-driven:

    TierDaily quotaRate limit
    default256 KB/day120 req/min
    iot10 MB/day600 req/min
    trusted500 MB/day1 200 req/min

    A key's explicit daily_quota_bytes overrides the tier default; NULL falls back to it. Rate is checked before quota (cheaper to reject). An over-quota request's bytes are refunded (DECRBY) so a rejection never burns quota it didn't use.

  • Every 429 carries a Retry-After header (seconds until the window resets).


8. Error envelope

Every failure returns a flat JSON body: {"error": "<slug>"} (see errors.py). Common slugs:

StatusSlugMeaning
401unauthorized / missing_api_key / invalid_api_keyauth failure
404not_founde.g. revoke of an unknown key hash
413file_too_largeingest upload > 10 MB
422bad_requestvalidation / malformed input (also FastAPI validation errors)
429rate_limited / daily_limit_reached / quota_exceededthrottled (has Retry-After)
503low_quantum_entropypool below 64 KiB, gated route blocked
500dice_sampling_failedrejection sampling exhausted its draw cap (extremely unlikely)

9. Endpoint reference

GET /health — anonymous

Usage: uptime checks and monitoring dashboards — confirms the pool/DRBG isn't degraded before you rely on it for real traffic. Liveness + pool/DRBG status. Never gated.

jsonc
{ "status": "ok", "quantum_entropy_level": "healthy",  // or "degraded"  "pool_bytes_remaining": 699000, "drbg_reseeds": 3, "uptime": 1234.5 }

Fires: db.get_root_key, db.pool_bytes_remaining, gate.entropy_level.

GET /random?bytes=<1..64> — anonymous, ungated

Usage: quick no-signup random bytes for demos, small scripts, or anything lightweight that doesn't need a key. DRBG-derived random bytes, base64. Survives a degraded pool. Rate-limited per IP + anon daily.

jsonc
{ "bytes": 32, "format": "base64", "data": "<base64>" }

Fires: ratelimit.check_ip_rate → ratelimit.check_anon_daily → generation.random_bytes. Errors: 422 (bytes out of range), 429 rate_limited / daily_limit_reached.

POST /dice — anonymous, ungated

Usage: fair dice rolls for tabletop bots and giveaways, where players can audit every DRBG byte drawn behind a roll. Rejection-sampled dice (no modulo bias). Rate-limited per IP. Request: { "sides": 2..100 (default 6), "count": 1..6 (default 1) }

jsonc
{ "sides": 6, "count": 2, "rolls": [3, 6], "format": "base64",  "bytes_used": "<base64>", "bytes_count": 2 }

bytes_used / bytes_count echo every DRBG byte drawn for the roll — accepted and rejected — so the web dice player's "bytes behind this roll" toggle is literal. Still DRBG output, never raw QRNG bits. Fires: ratelimit.check_ip_rate → dice.roll (loops keyed_drbg.output(1)). Errors: 422 (out of range), 429, 500 dice_sampling_failed.

GET /v1/random/bytes?size=<32..4096>&format=<hex|base64> — API key, gated

Usage: the main developer integration — production-grade random bytes paired with a signed receipt to prove provenance later. Canonical developer endpoint. Per-key rate limit + daily quota, then usage + issue logs.

jsonc
{ "request_id": "…", "format": "hex", "data": "…",  "entropy_epoch": 3, "timestamp": "2026-…Z", "receipt": "qeaas1.<payload>.<sig>" }

Fires: require_entropy (gate) → require_api_key → ratelimit.enforce_key → generation.issue_v1 (→ random_bytes + new_issue_meta + receipts.sign) → db.insert_usage_log + db.insert_issue_log. Errors: 401, 429 rate_limited/quota_exceeded, 503 low_quantum_entropy, 422.

POST /v1/verify — anonymous, per-IP rate-limited

Usage: proving a given output really came from this service, useful for audits or disputes, without ever re-exposing the underlying bytes. Verify provenance, not the secret. Request: { "request_id"?: str, "receipt"?: str } (at least one required — else 422).

  • With a receipt: signature is cryptographically verified; response resolves size, entropy_epoch, timestamp, and the pool's qrng_source_labels.
  • With only a request_id: plain issue_log lookup (the note says so honestly).
  • Tampered/forged receipt → verified: false, provenance: null.
jsonc
{ "request_id": "…", "verified": true,  "provenance": { "size": 64, "entropy_epoch": 3, "timestamp": "…",                  "qrng_source_labels": ["fez", "marrakesh"] },  "note": "receipt signature verified cryptographically" }

Fires: ratelimit.check_ip_rate → receipts.verify (→ receipts.verify_receipt and/or db.get_issue_log, db.get_pool_source_labels). Never an oracle — it never accepts or compares an output value.

GET /v1/pubkey — anonymous

Usage: verifying receipts fully offline using the published signing key, with no call back to this service needed. Published Ed25519 receipt-signing public key, for external offline verification.

jsonc
{ "algorithm": "Ed25519", "format": "base64", "public_key": "<base64 32-byte raw>" }

POST /v1/kem/keypair — API key, gated

Usage: generating post-quantum ML-KEM-768 key material for testing; real deployments should keygen client-side, this route is a demo path. QRNG-seeded ML-KEM-768 (FIPS 203) keygen. Request: { "include_secret_key"?: false }. public_key (ek) always returned; secret_key (dk) only in the demo flow, with a loud "demo only" note (real keygen happens client-side; the secret key never leaves the holder).

jsonc
{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64",  "public_key": "<ek base64>", "secret_key": null,  "entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }

Fires: gate → require_api_key → ratelimit.enforce_key → kem.generate_keypair (generation.random_bytes(64) → ML_KEM_768.key_derive) → new_issue_meta → usage/issue logs.

POST /v1/kem/encapsulate — API key, gated

Usage: demoing the encapsulation half of ML-KEM-768 against a public key you already hold; no server-side decapsulation route exists. Encapsulate against a supplied public_key. Request: { "public_key": "<ek base64>", "include_shared_secret"?: false }. ciphertext always returned; shared_secret + an illustrative HKDF-derived demo_key only with include_shared_secret. There is no server-side decapsulate route — decapsulation is client-side, on the holder of dk.

jsonc
{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64",  "ciphertext": "<base64>", "shared_secret": null, "demo_key": null,  "entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }

Fires: gate → require_api_key → ratelimit.enforce_key → kem.encapsulate (generation.random_bytes(32) → ML_KEM_768._encaps_internal) → new_issue_meta → logs. Malformed/wrong-length ek → 422 bad_request.

kyber-py is educational and not constant-time — correct for a thesis demo, not production (which would use liboqs on a persistent host).

POST /admin/ingest — admin token

Usage: operators refilling the entropy pool from a fresh batch of raw QRNG bits, encrypted before it ever touches disk. Multipart .txt upload of 0/1 characters (≤ 10 MB) → refills the pool. Parses in memory, encrypts, stores; no plaintext ever touches disk; upload buffer is burned.

jsonc
{ "ingested": true, "bytes_added": 87500, "pool_bytes_remaining": 786500 }

Fires: require_admin → pool.ingest_bits_bytes (→ parse_bits_bytes → encrypt_chunk → db.insert_pool_chunk) → pool.burn. Errors: 401, 413 file_too_large, 422 (non-.txt or non-0/1 content).

POST /admin/purge — admin token

Usage: operators wiping the entropy pool clean for rotation, incident response, or test resets — an irreversible action. Deletes all stored pool chunks. Irreversible — the pool must be re-ingested afterward.

jsonc
{ "purged": true, "chunks_removed": 42, "pool_bytes_remaining": 0 }

Fires: require_admin → db.purge_pool().

POST /admin/keys — admin token

Usage: operators provisioning a brand-new API key for a developer or customer, shown once at mint time. Mint an API key (same logic as scripts/mint_key.py). The plaintext key is returned once. Request: { "owner": str, "tier"?: "default", "daily_quota_bytes"?: int|null }.

jsonc
{ "api_key": "<plaintext — shown once>", "owner": "devtest",  "tier": "default", "daily_quota_bytes": null }

Fires: require_admin → secrets.token_urlsafe(32) → auth.hash_api_key → db.insert_api_key.

POST /admin/keys/revoke — admin token

Usage: operators killing a compromised or offboarded key immediately, effective on the very next request. Instant revocation. Request: { "key_hash": str }.

jsonc
{ "key_hash": "…", "revoked": true }

Fires: require_admin → db.revoke_api_key. Unknown hash → 404 not_found.


10. Local development

bash
cd apipython -m venv venv && source venv/bin/activatepip install -r requirements.txtcp .env.example .env         # fill DATABASE_URL, REDIS_URL, MASTER_KEY, ADMIN_TOKEN

Every route touches Postgres and most touch Redis — with empty .env values every request 500s. For a disposable local backend (Docker Postgres + Redis, auto-applies all SQL migrations):

bash
eval "$(./scripts/dev_db_up.sh --print-env)"    # starts qeaas-pg + qeaas-redis, exports envuvicorn main:app --reload --port 8000 --env-file .env

Seed the pool (any 0/1 text exercises the pipeline locally — real deploys ingest actual QRNG output):

bash
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 seedpython -m scripts.mint_key --owner devtest --tier default    # prints the plaintext key onceuvicorn main:app --port 8000

Smoke test:

bash
curl -s localhost:8000/healthcurl -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

Tear down: pkill -f "uvicorn main:app" && ./scripts/dev_db_down.sh.

Environment variables

KeyPurpose
DATABASE_URLNeon pooled Postgres connection string
REDIS_URLUpstash rediss:// (TLS) connection string
MASTER_KEY256-bit hex (secrets.token_hex(32)) — root of the sub-key hierarchy (§5)
ADMIN_TOKENBearer token for /admin/* (secrets.token_urlsafe(32))
WEB_ORIGINComma-separated CORS-allowed origins (default http://localhost:3000)

Rate limits, quotas, tiers, and reseed timing are module constants (ratelimit.py, keyed_drbg.py), not env vars.

Operational scripts (api/scripts/)

ScriptDoes
ingest_bits.pyParse + encrypt + store a 0/1 .txt into the pool
mint_key.py / revoke_key.pyCLI key lifecycle (mirrors the admin routes)
kem_roundtrip.py / kem_handshake.pyProve keygen → encaps → decaps recovers the same shared secret against the live API
scan_persistence.pyRead-only invariant scan: no plaintext-sensitive columns/values persisted
dev_db_up.sh / dev_db_down.shDisposable Docker Postgres + Redis for local dev

11. Deployment (summary)

Production is two separate Vercel projects (qeaas-api → qrng-eaas/api, qeaas-web → qrng-eaas/web), not one — GET /dice (web page) vs POST /dice (API) collide on one domain and path-based rewrites can't route by HTTP method. qrng-eaas/api/vercel.json pins the Python 3.13 runtime and rewrites every path to main.py. Provision Neon (run sql/001…005 in order), Upstash (regional, rediss:// TCP URL), then set per-project env vars (§10). Full runbook, troubleshooting, and the EPIC 7–11 write-ups (seed-quality report, networking demo, provenance, secure-storage burn lifecycle, theming) live in claude/plans/ and shared/docs/.


12. Agent-first integration (EPIC 13)

The service is discoverable by AI agents and correctly indexable by classic + LLM crawlers. Canonical domains: web https://qeaas.eu, API https://api.qeaas.eu.

Discovery documents (API):

URLWhat
/.well-known/agent.jsonMachine-readable agent manifest: framing, auth, capabilities, cross-links
/.well-known/ai-plugin.jsonMinimal ChatGPT-plugin-style manifest → /openapi.json
/.well-known/mcp.jsonMCP endpoint, transport, protocol version
/v1/agent/toolsTool descriptors — one per callable endpoint, with JSON Schema (from qeaas/schemas.py)
/v1/agent/manifestFull onboarding doc; ?profile=<http|openai-tools|anthropic-tools|mcp> for a framework-shaped quickstart
/openapi.json + /docsEnriched OpenAPI (tags, servers, ApiKeyAuth header scheme)
POST /mcpFull MCP server (Streamable-HTTP JSON-RPC 2.0): tools, resources, prompts, handshake

Web maps: /llms.txt, /llms-full.txt, /agents page, sitemap.xml, robots.txt (full AI-crawler allowlist), manifest.webmanifest, and site-wide JSON-LD.

Getting a key: public endpoints (health, random, dice, verify, pubkey) need no key. Developer/KEM endpoints require the header X-API-Key (admin-minted, revocable, quota-metered).

One catalog, no drift: qeaas/agent.py holds a single ENDPOINTS catalog; agent.json, tool descriptors, the manifest, and the MCP tool list all derive from it, and schemas come from model_json_schema().

Base URLs from env: API reads PUBLIC_API_URL / PUBLIC_WEB_URL (localhost defaults, qeaas/urls.py); web reads NEXT_PUBLIC_API_URL / NEXT_PUBLIC_WEB_URL (lib/urls.ts). WEB_ORIGIN (CORS) must include https://qeaas.eu.

Worked example:

bash
# 1. an agent finds the manifestcurl -s https://api.qeaas.eu/.well-known/agent.json | jq
# 2. follows to its framework-shaped quickstartcurl -s "https://api.qeaas.eu/v1/agent/manifest?profile=openai-tools" | jq
# 3. calls a public endpoint, or (with a key) a developer onecurl -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/mcpcurl -s -X POST https://api.qeaas.eu/mcp -H 'content-type: application/json' \  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq

來源:qrng-eaas/README.md,提交 2c39801

工具

6

random

DRBG-derived random bytes (base64), anonymous and rate-limited. Capped at 64 bytes/request; powers the public dice player.

輸入參數bytes

dice

Rejection-sampled dice rolls (no modulo bias), anonymous and rate-limited. Echoes the DRBG bytes drawn for transparency.

輸入參數countsides

random_bytes

Canonical developer endpoint: DRBG-derived bytes in hex or base64 with a signed provenance receipt. Requires X-API-Key; 32..4096 bytes; per-key rate limit + daily quota.

輸入參數size必填format

verify

Verify the provenance of an issued value from its request_id and/or receipt. Provenance only -- never a value-confirmation oracle. Anonymous, rate-limited.

輸入參數receiptrequest_id

kem_keypair

Generate an ML-KEM-768 (Kyber, post-quantum) keypair seeded from the QRNG->DRBG chain. Public key always returned; secret key only for the demo flow (loud demo-only note). Requires X-API-Key.

輸入參數include_secret_key

kem_encapsulate

Encapsulate against a supplied ML-KEM-768 public key to produce a ciphertext (and, for the demo, the shared secret). Requires X-API-Key.

輸入參數public_key必填include_shared_secret

版本歷史

1
  1. v1.0.0最新Sep 16, 2026

評分

已滿足適用檢查項的 90%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

    請在儲存庫或註冊中心條目中宣告授權條款。