@7h3/protocol 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +6 -1317
  2. package/agentAdapter.d.ts +26 -0
  3. package/auditLog.d.ts +54 -0
  4. package/capability.d.ts +67 -0
  5. package/cborCodec.d.ts +34 -0
  6. package/conformanceVectors.d.ts +20 -0
  7. package/encryption.d.ts +85 -0
  8. package/envelopeCbor.d.ts +33 -0
  9. package/frameworkAdapters.d.ts +72 -0
  10. package/gateway.d.ts +53 -0
  11. package/grpcBinding.d.ts +29 -0
  12. package/httpBinding.d.ts +50 -0
  13. package/index.d.ts +39 -0
  14. package/index.js +3670 -0
  15. package/keyInfra.d.ts +64 -0
  16. package/keyRegistry.d.ts +9 -0
  17. package/keyRotation.d.ts +20 -0
  18. package/mcpGateway.d.ts +37 -0
  19. package/mcpTransports.d.ts +62 -0
  20. package/mcpWrapper.d.ts +83 -0
  21. package/otel.d.ts +65 -0
  22. package/package.json +13 -121
  23. package/policyEnforcer.d.ts +50 -0
  24. package/policyTelemetryFeedback.d.ts +11 -0
  25. package/{src/protocol.d.ts → protocol.d.ts} +6 -2
  26. package/protocolAgent.d.ts +58 -0
  27. package/protocolBinary.d.ts +8 -0
  28. package/protocolCapabilities.d.ts +24 -0
  29. package/protocolReplay.d.ts +35 -0
  30. package/protocolTransport.d.ts +73 -0
  31. package/queueBinding.d.ts +43 -0
  32. package/rateLimiter.d.ts +18 -0
  33. package/{src/redisClient.ts → redisClient.d.ts} +25 -75
  34. package/replayStores.d.ts +98 -0
  35. package/revocation.d.ts +71 -0
  36. package/routePolicy.d.ts +30 -0
  37. package/runtimePolicy.d.ts +24 -0
  38. package/runtimePolicyManager.d.ts +15 -0
  39. package/runtimePolicyPresets.d.ts +11 -0
  40. package/signedResponse.d.ts +21 -0
  41. package/stream.d.ts +59 -0
  42. package/telemetry.d.ts +79 -0
  43. package/webhookBinding.d.ts +30 -0
  44. package/wsBinding.d.ts +52 -0
  45. package/.dockerignore +0 -19
  46. package/.github/dependabot.yml +0 -32
  47. package/.github/workflows/ci.yml +0 -31
  48. package/.github/workflows/publish.yml +0 -59
  49. package/.github/workflows/scorecard.yml +0 -37
  50. package/7h3.example.yaml +0 -125
  51. package/CHANGELOG.md +0 -152
  52. package/CONTRIBUTING.md +0 -82
  53. package/Dockerfile +0 -73
  54. package/GOVERNANCE.md +0 -62
  55. package/SECURITY.md +0 -70
  56. package/bench-results/replay-cache-full-1777891033256.json +0 -10
  57. package/bench-results/replay-cache-full-1777896317488.json +0 -10
  58. package/bench-results/replay-cache-full-1777900993184.json +0 -10
  59. package/bench-results/replay-cache-full-1777901019285.json +0 -10
  60. package/bench-results/replay-cache-quick-1777870170126.json +0 -10
  61. package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
  62. package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
  63. package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
  64. package/bench-results/wire-codecs-full-1777891019803.json +0 -93
  65. package/bench-results/wire-codecs-full-1777896260964.json +0 -93
  66. package/bench-results/wire-codecs-full-1777901004247.json +0 -93
  67. package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
  68. package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
  69. package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
  70. package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
  71. package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
  72. package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
  73. package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
  74. package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
  75. package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
  76. package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
  77. package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
  78. package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
  79. package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
  80. package/bin/7h3.ts +0 -406
  81. package/conformance/7h3_v0_1.json +0 -77
  82. package/conformance/7h3_v0_1_binary.json +0 -20
  83. package/conformance/aip_v0_1_binary.json +0 -20
  84. package/docker-compose.yaml +0 -77
  85. package/docs/ADOPTION_PLAN.md +0 -120
  86. package/docs/AGENTS.md +0 -77
  87. package/docs/AIP_RFC_v0.1.md +0 -97
  88. package/docs/AI_DECISION_CARD.md +0 -122
  89. package/docs/AI_RUNTIME_POLICY.json +0 -126
  90. package/docs/AI_RUNTIME_POLICY.yaml +0 -110
  91. package/docs/BACKPRESSURE_TUNING.md +0 -65
  92. package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
  93. package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
  94. package/docs/BINARY_CODEC_BENCH.md +0 -23
  95. package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
  96. package/docs/CLOCK_SKEW_POLICY.md +0 -30
  97. package/docs/DISTRIBUTED_REPLAY.md +0 -142
  98. package/docs/FUZZ_CAMPAIGN.md +0 -121
  99. package/docs/GATEWAY.md +0 -195
  100. package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
  101. package/docs/KEY_REVOCATION.md +0 -69
  102. package/docs/MCP_WRAPPER.md +0 -159
  103. package/docs/MIGRATION_GUIDE.md +0 -40
  104. package/docs/OPERATORS.md +0 -184
  105. package/docs/PERF_REGRESSION_POLICY.md +0 -34
  106. package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
  107. package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
  108. package/docs/RELEASE_GATE.md +0 -25
  109. package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
  110. package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
  111. package/docs/TELEMETRY.md +0 -41
  112. package/docs/THREAT_MODEL.md +0 -89
  113. package/docs/VERSIONING_POLICY.md +0 -30
  114. package/docs/assets/banner-github.png +0 -0
  115. package/docs/assets/banner.png +0 -0
  116. package/docs/assets/banner.svg +0 -123
  117. package/eslint.config.js +0 -15
  118. package/fuzz/ts/harness-decode.ts +0 -136
  119. package/fuzz/ts/harness-verify.ts +0 -121
  120. package/fuzz/ts/run.ts +0 -35
  121. package/mcp-server/README.md +0 -38
  122. package/mcp-server/package-lock.json +0 -1187
  123. package/mcp-server/package.json +0 -35
  124. package/mcp-server/src/index.ts +0 -236
  125. package/mcp-server/tsconfig.json +0 -14
  126. package/scripts/aip-framework-quickstart.ts +0 -110
  127. package/scripts/aip-mcp-gateway.ts +0 -38
  128. package/scripts/aip-mcp-wrap-demo.ts +0 -72
  129. package/scripts/aip-quickstart.ts +0 -60
  130. package/scripts/bench-diff.ts +0 -118
  131. package/scripts/bench-protocol-e2e.ts +0 -937
  132. package/scripts/bench-protocol-openloop.ts +0 -1397
  133. package/scripts/bench-replay-cache.ts +0 -76
  134. package/scripts/bench-signature-profiles.ts +0 -180
  135. package/scripts/bench-wire-codecs.ts +0 -161
  136. package/scripts/build-binary-conformance.ts +0 -36
  137. package/scripts/build-release-dashboard.ts +0 -175
  138. package/scripts/canary-rollout.ts +0 -38
  139. package/scripts/mcpGatewayCli.test.ts +0 -116
  140. package/scripts/prepare-aip-package.ts +0 -88
  141. package/scripts/regen-conformance-sigs.ts +0 -18
  142. package/scripts/release-gate.ts +0 -19
  143. package/scripts/validate-runtime-policy.ts +0 -18
  144. package/sdk/browser/index.test.ts +0 -162
  145. package/sdk/browser/index.ts +0 -257
  146. package/sdk/browser/package.json +0 -13
  147. package/sdk/go/cbor.go +0 -551
  148. package/sdk/go/cbor_test.go +0 -232
  149. package/sdk/go/encryption.go +0 -280
  150. package/sdk/go/encryption_test.go +0 -318
  151. package/sdk/go/go.mod +0 -7
  152. package/sdk/go/go.sum +0 -4
  153. package/sdk/go/http.go +0 -135
  154. package/sdk/go/protocol.go +0 -324
  155. package/sdk/go/protocol_test.go +0 -334
  156. package/sdk/go/replay.go +0 -121
  157. package/sdk/go/replay_test.go +0 -149
  158. package/sdk/go/webhook.go +0 -136
  159. package/sdk/pq/package-lock.json +0 -1358
  160. package/sdk/pq/package.json +0 -42
  161. package/sdk/pq/src/index.test.ts +0 -143
  162. package/sdk/pq/src/index.ts +0 -166
  163. package/sdk/pq/tsconfig.json +0 -14
  164. package/sdk/pq/vitest.config.ts +0 -7
  165. package/sdk/python/README.md +0 -18
  166. package/sdk/python/protocol_7h3/__init__.py +0 -46
  167. package/sdk/python/protocol_7h3/encryption.py +0 -252
  168. package/sdk/python/protocol_7h3/http.py +0 -212
  169. package/sdk/python/protocol_7h3/keys.py +0 -149
  170. package/sdk/python/protocol_7h3/pq.py +0 -244
  171. package/sdk/python/protocol_7h3/protocol.py +0 -525
  172. package/sdk/python/protocol_7h3/queue.py +0 -118
  173. package/sdk/python/protocol_7h3/replay.py +0 -98
  174. package/sdk/python/protocol_7h3/webhook.py +0 -116
  175. package/sdk/python/pyproject.toml +0 -40
  176. package/sdk/python/tests/test_conformance.py +0 -110
  177. package/sdk/python/tests/test_encryption.py +0 -206
  178. package/sdk/python/tests/test_http.py +0 -305
  179. package/sdk/python/tests/test_keys.py +0 -417
  180. package/sdk/python/tests/test_queue.py +0 -120
  181. package/sdk/python/tests/test_webhook.py +0 -345
  182. package/sdk/rust/Cargo.lock +0 -371
  183. package/sdk/rust/Cargo.toml +0 -25
  184. package/sdk/rust/README.md +0 -31
  185. package/sdk/rust/fuzz/Cargo.toml +0 -29
  186. package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
  187. package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
  188. package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
  189. package/sdk/rust/src/http.rs +0 -145
  190. package/sdk/rust/src/keys.rs +0 -161
  191. package/sdk/rust/src/lib.rs +0 -688
  192. package/sdk/rust/src/queue.rs +0 -79
  193. package/sdk/rust/src/webhook.rs +0 -86
  194. package/sdk/rust/tests/conformance.rs +0 -148
  195. package/sdk/rust/tests/gateway.rs +0 -130
  196. package/sdk/rust/tests/http_webhook_queue.rs +0 -201
  197. package/sdk/rust/tests/keys.rs +0 -189
  198. package/sdk/threshold/index.d.ts +0 -68
  199. package/sdk/threshold/index.d.ts.map +0 -1
  200. package/sdk/threshold/index.js +0 -254
  201. package/sdk/threshold/package-lock.json +0 -1361
  202. package/sdk/threshold/package.json +0 -39
  203. package/sdk/threshold/src/index.d.ts +0 -68
  204. package/sdk/threshold/src/index.d.ts.map +0 -1
  205. package/sdk/threshold/src/index.js +0 -254
  206. package/sdk/threshold/src/index.test.ts +0 -238
  207. package/sdk/threshold/src/index.ts +0 -355
  208. package/sdk/threshold/tsconfig.json +0 -19
  209. package/sdk/threshold/vitest.config.ts +0 -12
  210. package/src/agentAdapter.test.ts +0 -48
  211. package/src/agentAdapter.ts +0 -56
  212. package/src/auditLog.test.ts +0 -145
  213. package/src/auditLog.ts +0 -147
  214. package/src/capability.test.ts +0 -504
  215. package/src/capability.ts +0 -380
  216. package/src/cborCodec.test.ts +0 -263
  217. package/src/cborCodec.ts +0 -339
  218. package/src/conformance.test.ts +0 -136
  219. package/src/conformanceVectors.ts +0 -99
  220. package/src/encryption.test.ts +0 -206
  221. package/src/encryption.ts +0 -245
  222. package/src/envelopeCbor.ts +0 -140
  223. package/src/frameworkAdapters.test.ts +0 -290
  224. package/src/frameworkAdapters.ts +0 -261
  225. package/src/gateway.test.ts +0 -343
  226. package/src/gateway.ts +0 -246
  227. package/src/grpcBinding.test.ts +0 -211
  228. package/src/grpcBinding.ts +0 -103
  229. package/src/httpBinding.test.ts +0 -376
  230. package/src/httpBinding.ts +0 -189
  231. package/src/index.ts +0 -39
  232. package/src/keyInfra.test.ts +0 -278
  233. package/src/keyInfra.ts +0 -228
  234. package/src/keyRegistry.ts +0 -59
  235. package/src/keyRotation.test.ts +0 -78
  236. package/src/keyRotation.ts +0 -72
  237. package/src/mcpGateway.test.ts +0 -129
  238. package/src/mcpGateway.ts +0 -250
  239. package/src/mcpTransports.test.ts +0 -92
  240. package/src/mcpTransports.ts +0 -169
  241. package/src/mcpWrapper.test.ts +0 -179
  242. package/src/mcpWrapper.ts +0 -206
  243. package/src/otel.ts +0 -136
  244. package/src/policyEnforcer.test.ts +0 -99
  245. package/src/policyEnforcer.ts +0 -169
  246. package/src/policyTelemetryFeedback.test.ts +0 -25
  247. package/src/policyTelemetryFeedback.ts +0 -38
  248. package/src/protocol.bench.ts +0 -37
  249. package/src/protocol.d.ts.map +0 -1
  250. package/src/protocol.js +0 -294
  251. package/src/protocol.test.ts +0 -155
  252. package/src/protocol.ts +0 -414
  253. package/src/protocolAgent.test.ts +0 -105
  254. package/src/protocolAgent.ts +0 -169
  255. package/src/protocolBinary.test.ts +0 -165
  256. package/src/protocolBinary.ts +0 -312
  257. package/src/protocolCapabilities.ts +0 -70
  258. package/src/protocolFuzz.advanced.test.ts +0 -235
  259. package/src/protocolFuzz.test.ts +0 -111
  260. package/src/protocolNegative.test.ts +0 -97
  261. package/src/protocolReplay.test.ts +0 -71
  262. package/src/protocolReplay.ts +0 -194
  263. package/src/protocolTransport.test.ts +0 -556
  264. package/src/protocolTransport.ts +0 -483
  265. package/src/queueBinding.test.ts +0 -130
  266. package/src/queueBinding.ts +0 -102
  267. package/src/rateLimiter.test.ts +0 -96
  268. package/src/rateLimiter.ts +0 -46
  269. package/src/redisIntegration.test.ts +0 -134
  270. package/src/replayStores.test.ts +0 -273
  271. package/src/replayStores.ts +0 -215
  272. package/src/revocation.test.ts +0 -98
  273. package/src/revocation.ts +0 -0
  274. package/src/routePolicy.test.ts +0 -87
  275. package/src/routePolicy.ts +0 -72
  276. package/src/runtimePolicy.test.ts +0 -49
  277. package/src/runtimePolicy.ts +0 -81
  278. package/src/runtimePolicyManager.test.ts +0 -29
  279. package/src/runtimePolicyManager.ts +0 -50
  280. package/src/runtimePolicyPresets.ts +0 -43
  281. package/src/signedResponse.test.ts +0 -111
  282. package/src/signedResponse.ts +0 -83
  283. package/src/stream.test.ts +0 -254
  284. package/src/stream.ts +0 -417
  285. package/src/telemetry.test.ts +0 -251
  286. package/src/telemetry.ts +0 -299
  287. package/src/webhookBinding.test.ts +0 -144
  288. package/src/webhookBinding.ts +0 -115
  289. package/src/wsBinding.test.ts +0 -221
  290. package/src/wsBinding.ts +0 -200
  291. package/tsconfig.json +0 -15
  292. package/tsconfig.lib.json +0 -23
  293. package/vite.lib.config.ts +0 -16
  294. package/vitest.config.ts +0 -11
package/src/mcpGateway.ts DELETED
@@ -1,250 +0,0 @@
1
- import { createAipAgentAdapter } from './agentAdapter'
2
- import { bootstrapRuntimePolicyEnforcer, type BootstrapRuntimePolicyEnforcerOptions } from './policyEnforcer'
3
- import {
4
- createRawTaskFromJsonRpc,
5
- type JsonRpcPolicyEvent,
6
- toJsonRpcResponse,
7
- type JsonRpcBridgeOptions,
8
- type JsonRpcRequestLike,
9
- } from './frameworkAdapters'
10
- import { type WireEnvelope } from './protocolTransport'
11
-
12
- export interface AipMcpGatewayOptions {
13
- sharedSecret: string
14
- gatewayAgentId?: string
15
- workerAgentId?: string
16
- allowedMethods?: readonly string[]
17
- workerCapabilityPrefix?: string
18
- methodToCapability?: JsonRpcBridgeOptions['methodToCapability']
19
- authorizeMethod?: JsonRpcBridgeOptions['authorizeMethod']
20
- rateLimitKey?: JsonRpcBridgeOptions['rateLimitKey']
21
- rateLimiter?: JsonRpcBridgeOptions['rateLimiter']
22
- onAuditEvent?: (event: AipMcpGatewayAuditEvent) => void | Promise<void>
23
- gatewayWireFormat?: 'json' | 'compact' | 'binary'
24
- workerWireFormat?: 'json' | 'compact' | 'binary'
25
- runtimePolicy?: {
26
- enabled?: boolean
27
- options?: BootstrapRuntimePolicyEnforcerOptions
28
- concurrencyHint?: number
29
- latencySensitive?: boolean
30
- compatibilityFirst?: boolean
31
- }
32
- }
33
-
34
- export interface AipMcpGatewayRuntime {
35
- handleLine: (line: string) => Promise<string | null>
36
- }
37
-
38
- export interface AipMcpGatewayAuditEvent {
39
- phase: 'request_received' | 'policy' | 'verification_failed' | 'request_success' | 'request_error'
40
- id?: string | number | null
41
- method?: string
42
- code?: number
43
- message?: string
44
- policy?: JsonRpcPolicyEvent
45
- timestampMs: number
46
- }
47
-
48
- const DEFAULT_ALLOWED_METHODS = ['tools/call', 'resources/read', 'prompts/get']
49
-
50
- function jsonString(value: unknown): string {
51
- return JSON.stringify(value)
52
- }
53
-
54
- function createErrorResponse(id: string | number | null, code: number, message: string): string {
55
- return jsonString({
56
- jsonrpc: '2.0',
57
- id,
58
- error: { code, message },
59
- })
60
- }
61
-
62
- function parseRequest(raw: string): JsonRpcRequestLike | null {
63
- const parsed = JSON.parse(raw) as Partial<JsonRpcRequestLike>
64
- if (parsed.jsonrpc !== '2.0') return null
65
- if (typeof parsed.method !== 'string' || parsed.method.length === 0) return null
66
- if (typeof parsed.id !== 'string' && typeof parsed.id !== 'number') return null
67
-
68
- return {
69
- jsonrpc: '2.0',
70
- id: parsed.id,
71
- method: parsed.method,
72
- params: parsed.params,
73
- }
74
- }
75
-
76
- function mapBridgeErrorCode(message: string): number {
77
- if (message.includes('not allowed')) return -32601
78
- if (message.includes('not authorized')) return -32001
79
- if (message.includes('rate-limited')) return -32002
80
- return -32603
81
- }
82
-
83
- export function createAipMcpGatewayRuntime(options: AipMcpGatewayOptions): AipMcpGatewayRuntime {
84
- const gatewayAgentId = options.gatewayAgentId ?? 'agent.gateway'
85
- const workerAgentId = options.workerAgentId ?? 'agent.worker'
86
- const allowedMethods = options.allowedMethods ?? DEFAULT_ALLOWED_METHODS
87
- const workerCapabilityPrefix = options.workerCapabilityPrefix ?? 'mcp.'
88
- const gatewayWireFormat = options.gatewayWireFormat ?? 'compact'
89
- const workerWireFormat = options.workerWireFormat ?? 'compact'
90
-
91
- async function emitAuditEvent(event: Omit<AipMcpGatewayAuditEvent, 'timestampMs'>): Promise<void> {
92
- if (options.onAuditEvent) {
93
- await options.onAuditEvent({
94
- ...event,
95
- timestampMs: Date.now(),
96
- })
97
- }
98
- }
99
-
100
- const gateway = createAipAgentAdapter({
101
- agentId: gatewayAgentId,
102
- outboundSecret: options.sharedSecret,
103
- sharedSecrets: { [workerAgentId]: options.sharedSecret },
104
- wireFormat: gatewayWireFormat,
105
- })
106
-
107
- const worker = createAipAgentAdapter({
108
- agentId: workerAgentId,
109
- outboundSecret: options.sharedSecret,
110
- sharedSecrets: { [gatewayAgentId]: options.sharedSecret },
111
- wireFormat: workerWireFormat,
112
- onTask: async (envelope) => ({
113
- intent: 'RESULT',
114
- content: jsonString({
115
- ok: true,
116
- capability: envelope.body.capability,
117
- content: envelope.body.content,
118
- }),
119
- capability: envelope.body.capability?.startsWith(workerCapabilityPrefix)
120
- ? envelope.body.capability
121
- : `${workerCapabilityPrefix}${envelope.body.capability ?? 'unknown'}`,
122
- }),
123
- })
124
-
125
- return {
126
- async handleLine(line: string): Promise<string | null> {
127
- const raw = line.trim()
128
- if (!raw) return null
129
-
130
- let id: string | number | null = null
131
- try {
132
- const request = parseRequest(raw)
133
- if (!request) {
134
- await emitAuditEvent({
135
- phase: 'request_error',
136
- code: -32600,
137
- message: 'Invalid Request',
138
- })
139
- return createErrorResponse(null, -32600, 'Invalid Request')
140
- }
141
- id = request.id
142
- await emitAuditEvent({
143
- phase: 'request_received',
144
- id: request.id,
145
- method: request.method,
146
- })
147
-
148
- const bridgeOptions: JsonRpcBridgeOptions = {
149
- recipient: workerAgentId,
150
- allowedMethods,
151
- methodToCapability: options.methodToCapability,
152
- authorizeMethod: options.authorizeMethod,
153
- rateLimitKey: options.rateLimitKey,
154
- rateLimiter: options.rateLimiter,
155
- onPolicyEvent: async (event) => {
156
- await emitAuditEvent({
157
- phase: 'policy',
158
- id: request.id,
159
- method: request.method,
160
- policy: event,
161
- })
162
- },
163
- }
164
-
165
- const outbound = await createRawTaskFromJsonRpc(gateway, request, bridgeOptions)
166
- let rawResponse: WireEnvelope | null = null
167
- await worker.handleRaw(outbound, async (responseRaw) => {
168
- rawResponse = responseRaw
169
- })
170
- if (!rawResponse) {
171
- await emitAuditEvent({
172
- phase: 'verification_failed',
173
- id: request.id,
174
- method: request.method,
175
- code: -32603,
176
- message: 'AIP worker did not produce a response',
177
- })
178
- return createErrorResponse(id, -32603, 'AIP worker did not produce a response')
179
- }
180
-
181
- const verified = await gateway.receiveRaw(rawResponse)
182
- if (!verified.ok || !verified.received) {
183
- await emitAuditEvent({
184
- phase: 'verification_failed',
185
- id: request.id,
186
- method: request.method,
187
- code: -32603,
188
- message: 'AIP verification failed',
189
- })
190
- return createErrorResponse(id, -32603, 'AIP verification failed')
191
- }
192
-
193
- await emitAuditEvent({
194
- phase: 'request_success',
195
- id: request.id,
196
- method: request.method,
197
- })
198
-
199
- return jsonString(toJsonRpcResponse(verified.received, request.id))
200
- } catch (error: unknown) {
201
- const message = error instanceof Error ? error.message : 'Internal error'
202
- const code = mapBridgeErrorCode(message)
203
- await emitAuditEvent({
204
- phase: 'request_error',
205
- id,
206
- code,
207
- message,
208
- })
209
- return createErrorResponse(id, code, message)
210
- }
211
- },
212
- }
213
- }
214
-
215
- export async function createAipMcpGatewayRuntimeWithPolicy(options: AipMcpGatewayOptions): Promise<AipMcpGatewayRuntime> {
216
- if (!options.runtimePolicy?.enabled) {
217
- return createAipMcpGatewayRuntime(options)
218
- }
219
-
220
- const { enforcer } = await bootstrapRuntimePolicyEnforcer(options.runtimePolicy.options)
221
- enforcer.assertInvariant({
222
- signatureVerification: true,
223
- canonicalization: true,
224
- replayDefense: true,
225
- ttlClockSkewEnforcement: true,
226
- })
227
-
228
- const mode = enforcer.selectMode({
229
- concurrency: options.runtimePolicy.concurrencyHint ?? 1,
230
- latencySensitive: options.runtimePolicy.latencySensitive,
231
- compatibilityFirst: options.runtimePolicy.compatibilityFirst,
232
- })
233
-
234
- const wireFormat = mode.includes('binary') ? 'binary' : 'compact'
235
- return createAipMcpGatewayRuntime({
236
- ...options,
237
- gatewayWireFormat: wireFormat,
238
- workerWireFormat: wireFormat,
239
- gatewayAgentId: options.gatewayAgentId,
240
- workerAgentId: options.workerAgentId,
241
- onAuditEvent: async (event) => {
242
- if (options.onAuditEvent) {
243
- await options.onAuditEvent({
244
- ...event,
245
- message: event.message ?? `policy-mode=${mode};wire-format=${wireFormat}`,
246
- })
247
- }
248
- },
249
- })
250
- }
@@ -1,92 +0,0 @@
1
- import http from 'node:http'
2
- import type { AddressInfo } from 'node:net'
3
- import { PassThrough } from 'node:stream'
4
- import { afterEach, describe, expect, it } from 'vitest'
5
- import { signEnvelopeHmac } from './protocol'
6
- import {
7
- createMcpClientCodec,
8
- wrapMcpClient,
9
- wrapMcpServer,
10
- type JsonRpcRequest,
11
- type McpSecurityOptions,
12
- } from './mcpWrapper'
13
- import { createHttpMcpClient, createHttpMcpHandler, createStdioMcpClient, serveMcpOverStdio } from './mcpTransports'
14
-
15
- const SECRET = 'mcp-shared-secret'
16
-
17
- const serverOptions: McpSecurityOptions = {
18
- selfAgentId: 'agent.server',
19
- sign: (envelope) => signEnvelopeHmac(envelope, SECRET, 'server-k1'),
20
- receive: { secretResolver: async () => SECRET },
21
- wireFormat: 'json',
22
- }
23
-
24
- const clientOptions: McpSecurityOptions = {
25
- selfAgentId: 'agent.client',
26
- peerAgentId: 'agent.server',
27
- sign: (envelope) => signEnvelopeHmac(envelope, SECRET, 'client-k1'),
28
- receive: { secretResolver: async () => SECRET },
29
- wireFormat: 'json',
30
- }
31
-
32
- const echo = async (req: JsonRpcRequest) => ({ jsonrpc: '2.0' as const, id: req.id, result: { echo: req.params } })
33
-
34
- describe('stdio transport adapter', () => {
35
- const cleanups: Array<() => void> = []
36
- afterEach(() => {
37
- for (const c of cleanups.splice(0)) c()
38
- })
39
-
40
- it('round-trips signed MCP messages over newline-delimited streams', async () => {
41
- const clientToServer = new PassThrough()
42
- const serverToClient = new PassThrough()
43
-
44
- const server = serveMcpOverStdio(wrapMcpServer(echo, serverOptions), { input: clientToServer, output: serverToClient })
45
- const client = createStdioMcpClient({ input: serverToClient, output: clientToServer })
46
- cleanups.push(() => server.close(), () => client.close())
47
-
48
- const call = wrapMcpClient(client.send, clientOptions)
49
-
50
- const first = await call({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'planner' } })
51
- const second = await call({ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'deploy' } })
52
-
53
- expect(first).toEqual({ jsonrpc: '2.0', id: 1, result: { echo: { name: 'planner' } } })
54
- expect(second).toEqual({ jsonrpc: '2.0', id: 2, result: { echo: { name: 'deploy' } } })
55
- })
56
- })
57
-
58
- describe('http transport adapter', () => {
59
- let server: http.Server | undefined
60
- afterEach(async () => {
61
- if (server) await new Promise<void>((resolve) => server!.close(() => resolve()))
62
- server = undefined
63
- })
64
-
65
- it('round-trips signed MCP messages over a real HTTP server', async () => {
66
- server = http.createServer(createHttpMcpHandler(wrapMcpServer(echo, serverOptions)))
67
- await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
68
- const { port } = server.address() as AddressInfo
69
-
70
- const client = createHttpMcpClient({ url: `http://127.0.0.1:${port}` })
71
- const call = wrapMcpClient(client.send, clientOptions)
72
-
73
- const response = await call({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'planner' } })
74
-
75
- expect(response).toEqual({ jsonrpc: '2.0', id: 1, result: { echo: { name: 'planner' } } })
76
- })
77
-
78
- it('rejects an unsigned/invalid request over HTTP and returns a signed JSON-RPC error', async () => {
79
- server = http.createServer(createHttpMcpHandler(wrapMcpServer(echo, serverOptions)))
80
- await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
81
- const { port } = server.address() as AddressInfo
82
-
83
- const client = createHttpMcpClient({ url: `http://127.0.0.1:${port}` })
84
- const codec = createMcpClientCodec(clientOptions)
85
-
86
- // a body that is not a valid signed envelope
87
- const responseRaw = await client.send('{"not":"an envelope"}')
88
- const response = await codec.decodeResponse(responseRaw)
89
-
90
- expect(response.error).toBeDefined()
91
- })
92
- })
@@ -1,169 +0,0 @@
1
- import readline from 'node:readline'
2
- import type { IncomingMessage, ServerResponse } from 'node:http'
3
- import type { Readable, Writable } from 'node:stream'
4
- import type { WireEnvelope } from './protocolTransport'
5
-
6
- /**
7
- * Transport adapters for the MCP hardening wrapper.
8
- *
9
- * The wrapper (`wrapMcpServer` / `wrapMcpClient`) is transport-agnostic: it
10
- * operates on `WireEnvelope`s. These adapters connect it to real transports —
11
- * newline-delimited stdio and HTTP — so a secured server is deployable as-is.
12
- *
13
- * Note: the stdio adapter frames one envelope per line and therefore supports
14
- * the string wire formats (`json`, `compact`). For `binary`, use HTTP.
15
- */
16
-
17
- type WireHandler = (raw: WireEnvelope) => Promise<WireEnvelope>
18
-
19
- function assertStringWire(raw: WireEnvelope): string {
20
- if (typeof raw !== 'string') {
21
- throw new Error('stdio adapter supports json/compact (string) wire formats; use the HTTP adapter for binary')
22
- }
23
- return raw
24
- }
25
-
26
- export interface StdioServerHandle {
27
- close(): void
28
- }
29
-
30
- /**
31
- * Serves a wrapped MCP handler over newline-delimited streams (default
32
- * `process.stdin` / `process.stdout`). Lines are processed sequentially so
33
- * responses are emitted in request order.
34
- */
35
- export function serveMcpOverStdio(
36
- handle: WireHandler,
37
- options: { input?: Readable; output?: Writable; onError?: (error: unknown) => void } = {},
38
- ): StdioServerHandle {
39
- const input = options.input ?? process.stdin
40
- const output = options.output ?? process.stdout
41
- const rl = readline.createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY })
42
-
43
- let chain: Promise<void> = Promise.resolve()
44
- const onLine = (line: string): void => {
45
- const raw = line.trim()
46
- if (!raw) return
47
- chain = chain.then(async () => {
48
- try {
49
- const response = await handle(raw)
50
- output.write(`${assertStringWire(response)}\n`)
51
- } catch (error) {
52
- options.onError?.(error)
53
- }
54
- })
55
- }
56
-
57
- rl.on('line', onLine)
58
- return {
59
- close(): void {
60
- rl.off('line', onLine)
61
- rl.close()
62
- },
63
- }
64
- }
65
-
66
- export interface StdioClientHandle {
67
- send: WireHandler
68
- close(): void
69
- }
70
-
71
- /**
72
- * Creates a stdio client `send` function for `wrapMcpClient`. Requests and
73
- * responses are correlated by order over the single pipe (one envelope per line).
74
- */
75
- export function createStdioMcpClient(options: { input: Readable; output: Writable }): StdioClientHandle {
76
- const rl = readline.createInterface({ input: options.input, crlfDelay: Number.POSITIVE_INFINITY })
77
- const pending: Array<(line: string) => void> = []
78
-
79
- const onLine = (line: string): void => {
80
- const raw = line.trim()
81
- if (!raw) return
82
- const resolve = pending.shift()
83
- if (resolve) resolve(raw)
84
- }
85
- rl.on('line', onLine)
86
-
87
- return {
88
- send(raw: WireEnvelope): Promise<WireEnvelope> {
89
- const line = assertStringWire(raw)
90
- return new Promise<WireEnvelope>((resolve) => {
91
- pending.push(resolve)
92
- options.output.write(`${line}\n`)
93
- })
94
- },
95
- close(): void {
96
- rl.off('line', onLine)
97
- rl.close()
98
- },
99
- }
100
- }
101
-
102
- /**
103
- * Creates a `node:http`-compatible request handler that runs the wrapped MCP
104
- * handler. The request body is the wire envelope; the response body is the
105
- * signed reply envelope.
106
- */
107
- export function createHttpMcpHandler(
108
- handle: WireHandler,
109
- options: { binary?: boolean } = {},
110
- ): (req: IncomingMessage, res: ServerResponse) => void {
111
- return (req, res) => {
112
- const chunks: Buffer[] = []
113
- req.on('data', (chunk: Buffer) => chunks.push(chunk))
114
- req.on('error', () => {
115
- res.writeHead(400)
116
- res.end()
117
- })
118
- req.on('end', () => {
119
- void (async () => {
120
- try {
121
- const buffer = Buffer.concat(chunks)
122
- const raw: WireEnvelope = options.binary ? new Uint8Array(buffer) : buffer.toString('utf8')
123
- const response = await handle(raw)
124
- if (typeof response === 'string') {
125
- res.writeHead(200, { 'content-type': 'application/json' })
126
- res.end(response)
127
- } else {
128
- res.writeHead(200, { 'content-type': 'application/octet-stream' })
129
- res.end(Buffer.from(response))
130
- }
131
- } catch (error) {
132
- res.writeHead(500, { 'content-type': 'application/json' })
133
- res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32603, message: error instanceof Error ? error.message : 'internal error' } }))
134
- }
135
- })()
136
- })
137
- }
138
- }
139
-
140
- export interface HttpClientHandle {
141
- send: WireHandler
142
- }
143
-
144
- /**
145
- * Creates an HTTP client `send` function for `wrapMcpClient`. POSTs the request
146
- * envelope to `url` and returns the response envelope. Uses the global `fetch`
147
- * by default; inject `fetchImpl` for tests or custom agents.
148
- */
149
- export function createHttpMcpClient(options: {
150
- url: string
151
- fetchImpl?: typeof fetch
152
- binary?: boolean
153
- headers?: Record<string, string>
154
- }): HttpClientHandle {
155
- const doFetch = options.fetchImpl ?? fetch
156
- const contentType = options.binary ? 'application/octet-stream' : 'application/json'
157
-
158
- return {
159
- async send(raw: WireEnvelope): Promise<WireEnvelope> {
160
- const response = await doFetch(options.url, {
161
- method: 'POST',
162
- headers: { 'content-type': contentType, ...options.headers },
163
- body: typeof raw === 'string' ? raw : Buffer.from(raw),
164
- })
165
- if (options.binary) return new Uint8Array(await response.arrayBuffer())
166
- return response.text()
167
- },
168
- }
169
- }
@@ -1,179 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { createEnvelope, signEnvelopeHmac } from './protocol'
3
- import { InMemoryReplayCache } from './protocolReplay'
4
- import { encodeEnvelope, type WireEnvelope } from './protocolTransport'
5
- import {
6
- createMcpClientCodec,
7
- wrapMcpClient,
8
- wrapMcpServer,
9
- type JsonRpcRequest,
10
- type McpSecurityOptions,
11
- } from './mcpWrapper'
12
-
13
- const SECRET = 'mcp-shared-secret'
14
-
15
- function serverOptions(extra: Partial<McpSecurityOptions> = {}): McpSecurityOptions {
16
- return {
17
- selfAgentId: 'agent.server',
18
- sign: (envelope) => signEnvelopeHmac(envelope, SECRET, 'server-k1'),
19
- receive: { secretResolver: async () => SECRET },
20
- wireFormat: 'json',
21
- ...extra,
22
- }
23
- }
24
-
25
- function clientOptions(extra: Partial<McpSecurityOptions> = {}): McpSecurityOptions {
26
- return {
27
- selfAgentId: 'agent.client',
28
- peerAgentId: 'agent.server',
29
- sign: (envelope) => signEnvelopeHmac(envelope, SECRET, 'client-k1'),
30
- receive: { secretResolver: async () => SECRET },
31
- wireFormat: 'json',
32
- ...extra,
33
- }
34
- }
35
-
36
- const echo: (req: JsonRpcRequest) => Promise<{ jsonrpc: '2.0'; id: JsonRpcRequest['id']; result: unknown }> = async (req) => ({
37
- jsonrpc: '2.0',
38
- id: req.id,
39
- result: { echo: req.params },
40
- })
41
-
42
- describe('wrapMcpServer + wrapMcpClient round-trip', () => {
43
- it('delivers a signed request, runs the handler, and returns a verified result', async () => {
44
- const server = wrapMcpServer(echo, serverOptions())
45
- const call = wrapMcpClient((raw) => server(raw), clientOptions())
46
-
47
- const response = await call({ jsonrpc: '2.0', id: 7, method: 'tools/call', params: { name: 'planner' } })
48
-
49
- expect(response).toEqual({ jsonrpc: '2.0', id: 7, result: { echo: { name: 'planner' } } })
50
- })
51
-
52
- it('hands the underlying handler PLAIN json-rpc (no envelope, no signature)', async () => {
53
- const handler = vi.fn(echo)
54
- const server = wrapMcpServer(handler, serverOptions())
55
- const call = wrapMcpClient((raw) => server(raw), clientOptions())
56
-
57
- await call({ jsonrpc: '2.0', id: 1, method: 'resources/read', params: { uri: 'file://x' } })
58
-
59
- expect(handler).toHaveBeenCalledOnce()
60
- expect(handler).toHaveBeenCalledWith({ jsonrpc: '2.0', id: 1, method: 'resources/read', params: { uri: 'file://x' } })
61
- })
62
- })
63
-
64
- describe('tamper detection', () => {
65
- function tamperContent(raw: WireEnvelope): WireEnvelope {
66
- const env = JSON.parse(raw as string)
67
- env.body.content = `${env.body.content} TAMPERED`
68
- return JSON.stringify(env)
69
- }
70
-
71
- it('a request tampered after signing is rejected by the server (handler never runs)', async () => {
72
- const handler = vi.fn(echo)
73
- const server = wrapMcpServer(handler, serverOptions())
74
- const codec = createMcpClientCodec(clientOptions())
75
-
76
- const { raw } = await codec.encodeRequest({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'x' } })
77
- const response = await codec.decodeResponse(await server(tamperContent(raw)))
78
-
79
- expect(handler).not.toHaveBeenCalled()
80
- expect(response.error).toBeDefined()
81
- expect(response.result).toBeUndefined()
82
- })
83
- })
84
-
85
- describe('replay detection', () => {
86
- it('rejects the second delivery of an identical signed request', async () => {
87
- const replayCache = new InMemoryReplayCache()
88
- const handler = vi.fn(echo)
89
- const server = wrapMcpServer(handler, serverOptions({ receive: { secretResolver: async () => SECRET, replayCache } }))
90
- const codec = createMcpClientCodec(clientOptions())
91
-
92
- const { raw } = await codec.encodeRequest({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} })
93
- const first = await codec.decodeResponse(await server(raw))
94
- const second = await codec.decodeResponse(await server(raw))
95
-
96
- expect(first.result).toBeDefined()
97
- expect(second.error).toBeDefined()
98
- expect(handler).toHaveBeenCalledOnce()
99
- })
100
-
101
- it('defaults to replay protection even when no cache is configured', async () => {
102
- const handler = vi.fn(echo)
103
- const server = wrapMcpServer(handler, serverOptions()) // no replayCache provided
104
- const codec = createMcpClientCodec(clientOptions())
105
-
106
- const { raw } = await codec.encodeRequest({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} })
107
- await codec.decodeResponse(await server(raw))
108
- const replayed = await codec.decodeResponse(await server(raw))
109
-
110
- expect(replayed.error).toBeDefined()
111
- expect(handler).toHaveBeenCalledOnce()
112
- })
113
- })
114
-
115
- describe('response authenticity', () => {
116
- it('client rejects a response whose signature does not verify', async () => {
117
- const server = wrapMcpServer(echo, serverOptions())
118
- const codec = createMcpClientCodec(clientOptions({ receive: { secretResolver: async () => 'WRONG-SECRET' } }))
119
-
120
- const { raw } = await codec.encodeRequest({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} })
121
- const responseRaw = await server(raw)
122
-
123
- await expect(codec.decodeResponse(responseRaw)).rejects.toThrow(/verif/i)
124
- })
125
- })
126
-
127
- describe('recipient binding (cross-server relay defense)', () => {
128
- it('server rejects an envelope addressed to a different recipient', async () => {
129
- const handler = vi.fn(echo)
130
- const server = wrapMcpServer(handler, serverOptions({ selfAgentId: 'agent.server' }))
131
- // a validly-signed request addressed to 'agent.other', relayed to this server
132
- const misaddressed = await signEnvelopeHmac(
133
- createEnvelope({
134
- sender: 'agent.client',
135
- recipient: 'agent.other',
136
- intent: 'TASK',
137
- content: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} }),
138
- }),
139
- SECRET,
140
- 'client-k1',
141
- )
142
- const responseRaw = await server(encodeEnvelope(misaddressed, 'json'))
143
-
144
- // decode the error with a codec that matches the real server identity
145
- const codec = createMcpClientCodec(clientOptions({ peerAgentId: 'agent.server' }))
146
- const response = await codec.decodeResponse(responseRaw)
147
-
148
- expect(handler).not.toHaveBeenCalled()
149
- expect(response.error).toBeDefined()
150
- })
151
- })
152
-
153
- describe('sender binding (response spoofing defense)', () => {
154
- it('client rejects a validly-signed response whose sender is not the expected peer', async () => {
155
- const codec = createMcpClientCodec(clientOptions({ peerAgentId: 'agent.server' }))
156
- // a response signed with the shared secret but by a DIFFERENT agent id
157
- const spoofed = await signEnvelopeHmac(
158
- createEnvelope({ sender: 'agent.evil', recipient: 'agent.client', intent: 'RESULT', content: JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'pwned' }) }),
159
- SECRET,
160
- 'evil-k1',
161
- )
162
- const spoofedRaw = encodeEnvelope(spoofed, 'json')
163
-
164
- await expect(codec.decodeResponse(spoofedRaw)).rejects.toThrow(/sender|peer/i)
165
- })
166
- })
167
-
168
- describe('correlation binding (response substitution defense)', () => {
169
- it('client rejects a response whose correlationId does not match the request', async () => {
170
- const server = wrapMcpServer(echo, serverOptions())
171
- const codec = createMcpClientCodec(clientOptions())
172
-
173
- const { raw } = await codec.encodeRequest({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} })
174
- const responseRaw = await server(raw)
175
-
176
- // decode while asserting it answers a DIFFERENT request id
177
- await expect(codec.decodeResponse(responseRaw, { expectCorrelationId: 'some-other-message-id' })).rejects.toThrow(/correlation/i)
178
- })
179
- })