关于 qeaas-mcp
qeaas-mcp 是面向需要随机数、密码学种子或 ML-KEM-768 密钥材料的应用的 FastAPI 服务。它以导入的 QRNG 熵为 HMAC-DRBG 提供种子,并为输出附上可验证的签名来源凭证。
主要功能
- 01导入并加密存储 QRNG 熵
- 02使用 HMAC-DRBG 生成随机字节
- 03提供掷骰结果和密码学种子
- 04生成及封装 ML-KEM-768 密钥
- 05签发 Ed25519 来源凭证
适用场景
- 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 | 否 |
详细介绍
量子熵即服务(Q-EaaS)— API 参考
需要 API 密钥?请发送邮件至 [email protected]。
FastAPI 服务,可将预加载的 **QRNG(量子随机数生成器)**比特转换为提供的随机数、骰子点数、加密种子和后量子 ML-KEM 密钥材料——每次生成都会附带一份经过签名、可离线验证的来源凭证。
单体仓库布局:api/(本文档 — FastAPI 服务)、web/(Next.js 说明与演示)、shared/(文档、实验项目)、claude/(史诗级构建计划)。Web 应用是 API 上的轻量客户端;所有实质性工作都在这里完成。
坦诚说明一次,并适用于所有内容。 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。 - 两个数据存储,各司其职——参见 §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 | 为全部五张表提供参数化 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)。没有任何路径可以绕过它。
A. 写入(管理员操作,不在请求路径中)。
POST /admin/ingest → pool.ingest_bits_bytes() → pool.parse_bits_bytes() 将 0/1
文本按最高有效位优先的顺序打包为字节(丢弃末尾不完整的字节,拒绝任何非 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)— 计数器确保即使根密钥相同,并发请求也绝不可能生成相同输出。 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. 为什么使用两个数据库
它们用于防范完全不同的故障模式,并且对一致性的需求也截然相反。
Postgres(Neon)——持久化密钥库和审计账本
存储所有必须保留且必须具备事务性和持久性的数据。共五张表:
| 表 | 存储内容 | 备注 |
|---|---|---|
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 中会带来正确性风险:
| Key | Purpose | Op |
|---|---|---|
drbg:counter | 输出唯一性。 每次 DRBG 调用都会混入一个单调递增的整数,确保两个并发的无服务器调用不会生成相同字节。 | INCR |
rl:ip:{ip}:{minute} | 按 IP 限制速率(匿名路由) | INCR + EXPIRE 60 |
rl:key:{hash}:{minute} | 按密钥限制速率 | INCR + EXPIRE 60 |
anon:daily:{day} | 匿名输出全局上限(每天 5 MB) | INCRBY + EXPIRE,退款时使用 DECRBY |
quota:key:{hash}:{day} | 按密钥设置每日字节配额 | INCRBY + EXPIRE,退款时使用 DECRBY |
为什么不把这些数据放在 Postgres 中?在无状态的无服务器环境中,无法安全地为每个请求对共享状态执行读取-修改-写入——两个并发请求会产生竞争。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 位十六进制)。pool.derive_subkey(name)(HKDF-SHA256,RFC 5869 单块 Expand)恰好派生四个具名子密钥——没有其他密钥:
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. 身份验证
| Mechanism | Header | Applies to |
|---|---|---|
| 无(匿名) | — | /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。 - 管理员令牌:使用常量时间比较(
hmac.compare_digest)与ADMIN_TOKEN进行校验。错误或缺失 →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 }
触发:gate → 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;仅在 include_shared_secret 为真时返回 shared_secret 和示例性的 HKDF 派生 demo_key。
服务端不存在解封装路由——解封装由持有 dk 的客户端完成。
{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64",
"ciphertext": "<base64>", "shared_secret": null, "demo_key": null,
"entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }
触发:gate → 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 位十六进制值(secrets.token_hex(32))——子密钥层级的根密钥(§5) |
ADMIN_TOKEN | /admin/* 的 Bearer 令牌(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. 部署(摘要)
生产环境使用两个独立的 Vercel 项目(qeaas-api → qrng-eaas/api、qeaas-web → qrng-eaas/web),而非一个——GET /dice(网页)和 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. Agent 优先集成(EPIC 13)
AI Agent 可以发现此服务,并且传统爬虫和大模型爬虫都能正确索引此服务。
规范域名:Web:https://qeaas.eu,API:https://api.qeaas.eu。
发现文档(API):
| URL | 内容 |
|---|---|
/.well-known/agent.json | 机器可读的 Agent 清单:定位、身份验证、功能、交叉链接 |
/.well-known/ai-plugin.json | 精简的 ChatGPT 插件风格清单 → /openapi.json |
/.well-known/mcp.json | MCP 端点、传输方式、协议版本 |
/v1/agent/tools | 工具描述符——每个可调用端点一个,包含 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 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。