A unified, security-first wire protocol for tool access and agent coordination. UAP eliminates CVE-2025-49596 and MCP tool-poisoning vulnerabilities using Ed25519-signed CapabilityCards, mandatory mTLS, Keycloak IdP authentication, and per-call ephemeral Docker sandboxing.
██╗ ██╗ █████╗ ██████╗
██║ ██║██╔══██╗██╔══██╗
██║ ██║███████║██████╔╝
██║ ██║██╔══██║██╔═══╝
╚██████╔╝██║ ██║██║
╚═════╝ ╚═╝ ╚═╝╚═╝
Universal Agent Protocol
一个统一的、安全至上的线缆协议,用于工具访问、代理协调和结构化RPC。 旨在修复CVE-2025-49596以及MCP在架构上遗留的工具投毒和沙箱逃逸漏洞。
git clone https://github.com/RajSidwadkar/UAP-protocol
cd UAP-protocol
npm install
npx tsx scripts/generate-keypair.ts
docker compose -f docker-compose.dev.yml up -d
curl http://localhost:3000/health
# {"status":"ok","version":"1.0.0"}
即启动Keycloak在:8080,UAP网关在:3000。网关强制mTLS,验证Ed25519签名的CapabilityCard,并在每次工具调用时使用临时Docker容器。除生成的密钥对外无需任何配置。
graph TD
A[客户端 / 代理 SDK] -->|mTLS + JWT + card_sig| B[UAP 网关<br/>Fastify · 端口 3000]
B --> C{8 阶段流水线}
C --> C1[1 · 帧解析<br/>UapMessageFactory]
C1 --> C2[2 · 模式验证<br/>AJV 针对 schema_ref]
C2 --> C3[3 · 令牌验证<br/>Keycloak JWKS · 最长 15 分钟]
C3 --> C4[4 · 范围强制<br/>PermissionEnforcer]
C4 --> C5[5 · 卡片验证<br/>Ed25519 签名检查]
C5 --> C6[6 · 沙箱执行<br/>Docker · CapDrop ALL · 128 MB]
C6 --> C7[7 · 审计推送<br/>AuditEventBus · 非阻塞]
C7 --> C8[8 · 响应<br/>UapResponseEnvelope]
B --> R[(服务注册表<br/>InMemory · Redis)]
B --> KC[(Keycloak IdP<br/>OAuth 2.1 · PKCE)]
B --> OT[(OpenTelemetry<br/>W3C TraceContext)]
B --> AU[(审计日志<br/>追加式 NDJSON)]
六边形架构 — 领域层零I/O依赖。每个基础设施关注点都位于类型化端口接口之后。将Docker替换为WASM、Keycloak替换为Auth0、Pino替换为SIEM导出器——领域层零影响。
CVE-2025-49596(通过未签名元数据的工具投毒)。 MCP 工具描述在发布后可变。攻击者可以在部署后向工具名称或描述中注入恶意指令——客户端无法检测篡改。UAP 将所有工具元数据放入 Ed25519 签名的 CapabilityCard 负载中。签名后的任何变更都会使签名失效,网关在执行任何操作前拒绝该卡片。
CWE-284 / 沙箱逃逸类漏洞。 MCP 在服务端进程级别运行工具。任何工具中的路径遍历或进程注入都会触及主机文件系统和网络。UAP 为每次调用创建一个全新的 Docker 容器——只读根文件系统、CapDrop: ALL、128 MB 内存上限、默认禁用网络。响应后容器即销毁。调用之间无持久攻击面。
CWE-287 / 混淆代理认证。 MCP 自身充当 OAuth 提供者,同时成为资源服务器和授权服务器。这是典型的混淆代理模式。UAP 分离这些角色:网关是纯资源服务器。Keycloak(或任何外部 IdP)是唯一权威。JWT 针对远程 JWKS 端点验证并硬性限制为 15 分钟。
每条 UAP 消息使用相同的信封。auth 块携带一个限定范围的 JWT 和代理已签名的 CapabilityCard 引用。网关在请求到达任何应用程序代码之前验证两者。
{
"uap": {
"version": "1.0",
"type": "tool_call",
"id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"trace": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
},
"auth": {
"token": "eyJhbGciOiJSUzI1NiJ9...",
"scope": ["tool:read"],
"card_sig": "ed25519:a1b2c3d4..."
}
},
"method": "tools/invoke",
"schema_ref": "uap:tool.invoke/v1",
"params": {
"tool_id": "db:query",
"input": { "sql": "SELECT 1" }
},
"ack": true
}
UAP-protocol/
├── packages/
│ ├── gateway/ @uap/gateway — 基于 Fastify 的 UAP 网关
│ ├── sdk-ts/ @uap/sdk-ts — TypeScript 客户端 + 服务端 SDK
│ └── sdk-py/ uap-sdk — Python 异步 SDK (httpx + FastAPI)
├── scripts/
│ ├── generate-keypair.ts — 本地开发用 Ed25519 密钥对
│ └── keycloak-bootstrap.ts — 幂等的 Keycloak 域设置
└── docker/
└── sandbox/ — 零信任工具执行的基础镜像
import { UapClient, ClientCredentialsTokenProvider } from "@uap/sdk-ts";
const client = new UapClient({
gatewayUrl: "https://gateway.example.com",
tokenProvider: new ClientCredentialsTokenProvider({
tokenUrl: process.env.TOKEN_URL!,
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
}),
});
const result = await client.invokeTool(
"db:query",
{ sql: "SELECT * FROM users LIMIT 10" },
["tool:read"]
);
const task = await client.delegateTask(
"summarizer",
{ text: "..." },
["task:submit"]
);
from uap_sdk.client import UapClient, UapClientOptions
from uap_sdk.token import ClientCredentialsTokenProvider
async with UapClient(UapClientOptions(
gateway_url="https://gateway.example.com",
token_provider=ClientCredentialsTokenProvider(
token_url=os.environ["TOKEN_URL"],
client_id=os.environ["CLIENT_ID"],
client_secret=os.environ["CLIENT_SECRET"],
)
)) as client:
result = await client.invoke_tool("db:query", {"sql": "SELECT 1"}, ["tool:read"])
FastAPI 中间件
from uap_sdk.middleware import uap_auth
@app.post("/summarize")
@uap_auth(scope=["task:submit"])
async def summarize(request: Request):
claims = request.state.uap_claims # 类型化的 AuthClaims
...
uap-migrate CLI 可以在 5 分钟内将任意 MCP 服务器包装为签名的 UAP CapabilityCard。幂等——对已迁移的服务器重新运行是一个空操作。
node dist/cli/migrate.js mcp http://localhost:3001 \
--issuer did:uap:my-org \
--key config/dev.privkey.hex \
--out ./uap-cards
# → ./uap-cards/did:uap:my-org.card.json
工具名称、描述和参数模式都属于 Ed25519 签名负载的一部分。签名后修改任何字段都会使签名失效——从根本上抵御工具投毒。
const signed = await signer.sign({
issuer: "did:uap:my-agent",
version: "1.0.0",
tools: [{
id: "db:query",
description: "运行一个只读 SQL 查询",
inputSchema: { type: "object", required: ["sql"] },
scopes: ["tool:read"]
}],
scopes: ["tool:read"],
issuedAt: Date.now(),
expiresAt: Date.now() + 86_400_000 * 30
});
// signed.signature === "ed25519:a1b2c3..."
分支命名:feat/<sprint>-<task>-<slug> — 例如 feat/s2-2.1-mtls-keycloak
每个变更遵循:分支 → 本地运行 npx tsc --noEmit + npx vitest run → 提交 → PR → 压缩合并 → 删除分支。本地开发无需 CI 依赖。
# 提交前运行所有测试
cd packages/gateway && npx vitest run
cd packages/sdk-ts && npx vitest run
cd packages/sdk-py && python -m pytest tests/ -v
路线图: Sprint 0 (脚手架) → Sprint 1 (模式 + 签名 + RPC) → Sprint 2 (安全核心) → Sprint 3 (网关 + 注册表) → Sprint 4 (桥接适配器) → Sprint 5 (SDK + Docker 镜像)。
UAP · 通用代理协议 · 2026
SPDX-License-Identifier: Apache-2.0
| 层级 | 内容 | 依赖 |
|---|
| Domain(领域层) | UapEnvelope、CapabilityCard、AuditEvent、Task | 无——仅标准库 |
| Application(应用层) | InvokeToolUseCase、DelegateTaskUseCase、ValidateCardUseCase | 仅领域端口 |
| Infrastructure(基础设施层) | KeycloakAuthAdapter、DockerSandboxAdapter、Ed25519SignerAdapter、PinoAuditAdapter | 应用端口 + 外部库 |
| Transport(传输层) | UAP-RPC 帧解析器、Fastify 网关、SSE/WebSocket 处理器 | 基础设施 + 领域序列化器 |
| 特性 | UAP | MCP | A2A | JSON-RPC 2.0 |
|---|
| 强制认证 | ✅ 始终 mTLS + JWT | ❌ 可选 | ⚠️ 仅 API 密钥 | ❌ 无 |
| 工具元数据完整性 | ✅ Ed25519 签名负载 | ❌ 发布后可变 | ❌ 无签名 | ❌ 无签名 |
| 沙箱模型 | ✅ 每次调用临时 Docker | ⚠️ 仅服务端级别 | ❌ 无 | ❌ 无 |
| 代理发现 | ✅ 中心辐射 · N 连接 | ❌ 直接 HTTP N² | ⚠️ 基于 DNS | ❌ 无 |
| 审计追踪 | ✅ 协议原生追加式日志 | ❌ 无 | ❌ 无 | ❌ 无 |
| 分布式追踪 | ✅ 强制 W3C TraceContext | ❌ 无 | ❌ 无 | ❌ 无 |
| 模式验证 | ✅ 传输层 AJV | ⚠️ 可选 Zod | ❌ 无 | ❌ 无 |
| 令牌生命上限 | ✅ 强制 15 分钟 | ❌ 不强制 | ❌ 不强制 | ❌ 不适用 |
| 批处理语义 | ✅ 串行 · 并行 · 事务 | ❌ 未定义 | ❌ 无 | ⚠️ 含糊 |
| 二进制负载 | ✅ 原生帧 | ❌ 仅 Base64 | ❌ 仅 Base64 | ❌ 仅 Base64 |
| 身份提供者模型 | ✅ 仅外部 IdP | ❌ 服务端自身是 OAuth 提供者 | ⚠️ 不固定 | ❌ 无 |
| 桥接适配器 | ✅ 阶段 2 附带 MCP + A2A 桥接 | ❌ 无桥接 | ❌ 无桥接 | ❌ 无桥接 |
| 水平扩展 | ✅ 无状态 · 任意负载均衡 | ❌ 粘性会话 | ⚠️ 不固定 | ❌ 无 |
| 变量 | 描述 | 默认值 |
|---|
KEYCLOAK_URL | Keycloak 基础 URL | http://localhost:8080 |
KEYCLOAK_REALM | 域名 | uap |
UAP_AUDIENCE | JWT audience 声明 | uap-gateway |
UAP_SIGNING_KEY_PATH | Ed25519 私钥十六进制路径 | config/dev.privkey.hex |
UAP_DOCKER_SOCKET | Docker 套接字路径 | /var/run/docker.sock |
UAP_AUDIT_LOG_DIR | 追加式审计 NDJSON 日志目录 | /var/log/uap |
UAP_GATEWAY_PORT | 网关监听端口 | 3000 |
UAP_MTLS_CERT | 服务器 TLS 证书 (PEM) | certs/server.crt |
UAP_MTLS_KEY | 服务器 TLS 私钥 (PEM) | certs/server.key |
UAP_MTLS_CA | 客户端证书验证的 CA 证书 | certs/ca.crt |
OTEL_EXPORTER_OTLP_ENDPOINT | OpenTelemetry 收集器端点 | http://localhost:4318 |
REDIS_URL | 多实例注册表的 Redis(可选) | — |
| 包 | 版本 | 用途 |
|---|
fastify | ^4.27 | 网关 HTTP 服务器 |
zod | ^3.23 | 信封模式与类型推断 |
@noble/curves | ^1.4 | Ed25519 签名 |
jose | ^5.4 | JWT 验证、JWKS 客户端 |
ajv | ^8.16 | 传输层参数验证 |
dockerode | ^4.0 | 零信任沙箱适配器 |
pino | ^9.2 | 追加式结构化审计日志 |
ulid | ^2.3 | 可排序的唯一 ID |
@opentelemetry/sdk-node | ^0.52 | 分布式追踪 |
ioredis | ^5.3 | 多实例服务注册表 |
@modelcontextprotocol/sdk | ^1.0 | MCP 桥接适配器 |
httpx (Python) | ^0.27 | Python SDK 的异步 HTTP 客户端 |
pydantic (Python) | ^2.7 | Python 类型验证 |