内容
# 机等级验证
在采取行动之前,根据合约验证 AI 生成的工件:
- **`json_schema`** — 验证 `artifact` 是否符合 JSON Schema(收集所有错误)。
- **`openapi_response`** — 验证响应体是否符合 OpenAPI 规范中给定的 `path` + `method` + `status` 的响应模式。
- **`sql`** — 在给定方言中检查 SQL 字符串的语法错误。
每次检查都返回一个 **裁决**,而不是错误:`{valid, errors, latency_ms}`,
HTTP 200 状态码,无论工件是否有效。只有真正错误的请求(错误的键、不支持的类型、格式错误的正文、超出限制)才会收到特定的 HTTP 错误。
实时服务于 **https://api.machinegrade.dev** — 免费层,自我服务密钥,
30 秒内即可试用(以下为第一个示例)。基于 Hono 构建;相同的代码库在 Cloudflare Workers(生产环境)和普通 Node(本地开发)上运行,并采用 MIT 许可协议,如果您更愿意自行托管。
## 为什么
生成 JSON、API 响应或 SQL 的代理需要快速、廉价、机器可检查的通过/失败检查,然后再发送结果 — 比完整的 LLM-as-judge 调用更便宜、更确定。
## 在本地运行
```bash
npm install
npm run dev
# 机等级验证监听 http://localhost:8787
```
## 3 个可运行的示例
### 1. curl
```bash
# 获取 API 密钥(实时服务 — 直接使用)
curl -s -X POST https://api.machinegrade.dev/keys \
-H 'content-type: application/json' \
-d '{"email": "you@example.com"}'
# => {"key":"sk_..."}
# 验证 JSON 工件是否符合 JSON Schema
curl -s -X POST https://api.machinegrade.dev/v1/validate \
-H 'content-type: application/json' \
-H 'X-Api-Key: sk_...' \
-d '{
"type": "json_schema",
"artifact": {"name": "Ada", "age": 30},
"contract": {
"schema": {
"type": "object",
"required": ["name", "age"],
"properties": {"name": {"type": "string"}, "age": {"type": "number"}}
}
}
}'
# => {"valid":true,"errors":[],"latency_ms":1}
```
### 2. Python(requests)
```python
import requests
base = "http://localhost:8787"
key = requests.post(f"{base}/keys", json={"email": "you@example.com"}).json()["key"]
resp = requests.post(
f"{base}/v1/validate",
headers={"X-Api-Key": key},
json={
"type": "sql",
"artifact": "SELECT id, name FROM users WHERE id = 1",
"contract": {"dialect": "mysql"},
},
)
print(resp.status_code, resp.headers.get("X-Calls-Remaining"), resp.json())
```
### 3. MCP 配置片段
`mcp/server.ts` 公开一个名为 `validate` 的工具,该工具转发到
`POST /v1/validate`。将 MCP 兼容的客户端指向它:
```json
{
"mcpServers": {
"machinegrade-validate": {
"command": "npx",
"args": ["tsx", "mcp/server.ts"],
"cwd": "/path/to/validate",
"env": {
"SANDBOX_URL": "http://localhost:8787",
"SANDBOX_API_KEY": "sk_..."
}
}
}
}
```
## 远程连接
生产服务还通过流式 HTTP 直接公开一个 MCP 端点:
```
POST https://api.machinegrade.dev/mcp
```
它与上述 stdio 适配器具有相同的 `validate` 工具。
`initialize` 和 `tools/list` 无需密钥即可工作(发现是匿名的);
`tools/call` 需要 `X-Api-Key`(通过 `POST /keys` 发行,与 REST API 相同 — 免费层和限制共享)。
使用 Claude Code:
```bash
claude mcp add --transport http validate https://api.machinegrade.dev/mcp --header "X-Api-Key: sk_..."
```
stdio 适配器通过 npm(`@machinegrade/validate`,参见上文)仍然可用于本地/离线使用或不支持 HTTP 传输的客户端。
## Claude Desktop:一键安装
从
[Releases](https://github.com/machinegrade/validate/releases/latest) 下载最新的 `.mcpb` 包并双击它。Claude Desktop 在安装过程中请求 API 密钥,并将其存储在 OS 密钥链(macOS 密钥链,Windows 凭证管理器)中,而不是存储在配置文件中 — 没有任何内容存储在 `claude_desktop_config.json` 中。
首先发行一个免费密钥(500 次调用/月):
```bash
curl -s -X POST https://api.machinegrade.dev/keys \
-H 'content-type: application/json' \
-d '{"email": "you@example.com"}'
```
该包是一个轻薄的 stdio 客户端,用于托管 API:~34 KB,零捆绑依赖。如果您自行托管,请将扩展的 **API base URL** 设置指向您自己的部署,工具将与之通信。
## API
在 [`public/openapi.yaml`](./public/openapi.yaml) 中查看完整合约,或
在服务运行时查看 [`/v1/manifest`](http://localhost:8787/v1/manifest) 以获取机器可读的摘要(类型、限制、定价、错误代码)。 [`/llms.txt`](./public/llms.txt) 是 LLM 代理的简要指针。
| 端点 | 输入 | 输出 |
|---|---|---|
| `POST /keys` | `{email}` | `{key}` |
| `POST /v1/validate` | 头部 `X-Api-Key`,主体 `{type, artifact, contract?}` | 裁决,头部 `X-Calls-Remaining` |
| `GET /v1/manifest` | — | 能力清单 |
| `GET /stats` | 头部 `X-Admin-Token` | 通道:keys_issued, active_callers, repeat_callers_7d, limit_hits, paid_requests |
| `POST /v1/paid-request` | 头部 `X-Api-Key` | 记录付费访问兴趣 |
| `GET /openapi.yaml`, `GET /llms.txt` | — | 静态文档 |
| `POST /mcp` | MCP 流式 HTTP,头部 `X-Api-Key` 用于 `tools/call` | 参见上面的“远程连接” |
## 定价
- **免费层:** 每月 500 次调用,每个密钥每分钟 60 次调用速率限制。
- **付费层:** 超出免费层的 EUR 0.002/次调用 — **即将开放**。
通过 `POST /v1/paid-request` 请求付费访问(需要 `X-Api-Key`);
当它上线时,您将收到通知。
## 错误
每个错误都是有类型的 JSON — `{code, message, hint, docs_url}` — 而不是自由格式字符串:
| 代码 | HTTP 状态码 | 何时 |
|---|---|---|
| `INVALID_KEY` | 401 | `X-Api-Key` 缺失或未知 |
| `LIMIT_EXCEEDED` | 402 | 超出免费层每月限制(500 次调用) |
| `UNSUPPORTED_TYPE` | 400 | `type` 不是 `json_schema`、`openapi_response` 或 `sql` |
| `MALFORMED_INPUT` | 400 | 请求体不匹配文档格式 |
| `RATE_LIMITED` | 429 | 每分钟调用次数超过 60 次 |
**裁决** (`{valid, errors, latency_ms}`) 永远不是错误 — 无效工件是正常、预期的结果,返回 HTTP 200 状态码。
## 发送工件
`artifact` 必须是 JSON **值**,而不是 JSON 编码字符串:
```jsonc
{"type": "json_schema", "artifact": {"name": "Ada"}, "contract": {"schema": {"type": "object"}}} // 正确
{"type": "json_schema", "artifact": "{\"name\": \"Ada\"}", "contract": {"schema": {"type": "object"}}} // 错误
```
对于 `type: "sql"`,工件 *是* 字符串 — 语句本身。
由于 MCP 调用者通常会字符串化值(并且通过 Claude Desktop 直到工具模式声明 `artifact` 类型之前都是这样),服务会窄化容忍错误的形式:如果 `artifact` 是字符串,类型是 `json_schema` 或 `openapi_response`,并且合约的 **顶级模式声明排除字符串**,则在验证之前将字符串解码为 JSON,裁决携带 `"decoded_from_string": true`。该字段是附加的;`{valid, errors, latency_ms}` 不变。
它故意不解码其他内容,因为字符串工件通常是合法的:
| 发送 | 合约模式 | 结果 |
|---|---|---|
| `"42"` | `{"type": "number"}` | 解码为 `42`,`valid: true`,`decoded_from_string: true` |
| `"42"` | `{"type": "string"}` | 不变,`valid: true` |
| `"{\"a\":1}"` | `{"type": "string"}` | 不变,`valid: true` |
| `"{\"a\":1}"` | `{"required": ["a"]}` | 不变 — 没有顶级 `type`,意图未知 |
## 存储
`src/storage.ts` 定义了一个 `Storage` 接口,具有两个实现:
- `MemoryStorage` — 完整的内存实现,用于 `npm run dev` 和测试套件。
- `D1Storage` — 真实的 Cloudflare D1 绑定,支持 `schema.sql`(`keys`、`events` 表)。在生产环境中使用;Workers 入口点在 `src/index.ts` 中从 `DB` 绑定构建。
使用以下命令将 `schema.sql` 应用到新的 D1 数据库:
```bash
wrangler d1 execute machinegrade-validate-db --file=schema.sql # 本地
wrangler d1 execute machinegrade-validate-db --file=schema.sql --remote # 生产环境
```
## 测试
```bash
npm test # vitest 运行,进程内通过 app.request(),MemoryStorage
npm run typecheck # tsc --noEmit
```
测试覆盖:密钥发行、每个验证器的成功 + 失败用例、有类型的 401/400/402/429 错误、计量限制(测试中也可注入,因此无需循环数百个真实请求),以及 `/stats` 通道计数。
## 部署
该服务在 Cloudflare Workers(Hono + D1 + Workers Static Assets)上运行。要在新的 Cloudflare 账户上自行托管:
```bash
wrangler d1 create machinegrade-validate-db # 复制返回的 database_id 到 wrangler.toml
wrangler d1 execute machinegrade-validate-db --file=schema.sql --remote
wrangler secret put ADMIN_TOKEN
wrangler deploy
```
然后通过 Cloudflare 仪表板或 `wrangler` 将自定义域(例如 `api.machinegrade.dev`)绑定到 Worker。CI 可以在 `CLOUDFLARE_API_TOKEN` 和 `CLOUDFLARE_ACCOUNT_ID` 仓库密钥设置并启用 `.github/workflows/ci.yml` 中的 `deploy` 作业后,在推送到 `main` 时部署。
关于 Workers 端口的两件事值得注意:
- `GET /openapi.yaml` 和 `GET /llms.txt` 由 `ASSETS` 绑定(`wrangler.toml` 中的 `[assets]`,指向 `public/`)提供 — Cloudflare 直接提供它们,无需调用 Worker。`src/index.ts` 中的路由是本地 Node 开发/测试的备用方案,因为没有 ASSETS 绑定。
- `json_schema` 和 `openapi_response` 验证器使用 `@cfworker/json-schema`,而不是 `ajv`:ajv 通过 `new Function(...)` 编译模式,而 Workers 运行时不允许这样做,并且模式此处动态到达每个请求(来自调用者),因此不能在构建时预编译。
## 状态
早期阶段,坦率地说:该服务实时运行,免费层使用是真实的,我们正在衡量它是否值得付费层。可以依靠的是:
- API 合约(`/v1/validate` 请求/响应形状、有类型的错误代码、裁决语义)是稳定的 — 只有版本号路径(`/v2/...`)才会发生破坏性更改,而不会静默进行。
- 免费层(每月 500 次调用)保持不变。
- 如果我们最终停止该服务,密钥将在公告后 90 天内继续有效,验证器在此仓库中是开源的 — 您可以自行托管相同的行为。
反馈和集成故事是您现在可以给我们的最有价值的东西:打开一个问题或使用 `POST /v1/paid-request` 如果您需要的调用次数超过免费层。