@7h3/protocol 0.4.0 → 0.5.1

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 (248) hide show
  1. package/README.md +6 -323
  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 +3662 -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 -79
  23. package/policyEnforcer.d.ts +50 -0
  24. package/policyTelemetryFeedback.d.ts +11 -0
  25. package/protocol.d.ts +66 -0
  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 -92
  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 -385
  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.png +0 -0
  115. package/eslint.config.js +0 -15
  116. package/fuzz/ts/harness-decode.ts +0 -136
  117. package/fuzz/ts/harness-verify.ts +0 -121
  118. package/fuzz/ts/run.ts +0 -35
  119. package/mcp-server/README.md +0 -38
  120. package/mcp-server/package-lock.json +0 -1187
  121. package/mcp-server/package.json +0 -35
  122. package/mcp-server/src/index.ts +0 -236
  123. package/mcp-server/tsconfig.json +0 -14
  124. package/scripts/aip-framework-quickstart.ts +0 -110
  125. package/scripts/aip-mcp-gateway.ts +0 -38
  126. package/scripts/aip-mcp-wrap-demo.ts +0 -72
  127. package/scripts/aip-quickstart.ts +0 -60
  128. package/scripts/bench-diff.ts +0 -118
  129. package/scripts/bench-protocol-e2e.ts +0 -937
  130. package/scripts/bench-protocol-openloop.ts +0 -1397
  131. package/scripts/bench-replay-cache.ts +0 -76
  132. package/scripts/bench-signature-profiles.ts +0 -180
  133. package/scripts/bench-wire-codecs.ts +0 -161
  134. package/scripts/build-binary-conformance.ts +0 -36
  135. package/scripts/build-release-dashboard.ts +0 -175
  136. package/scripts/canary-rollout.ts +0 -38
  137. package/scripts/mcpGatewayCli.test.ts +0 -116
  138. package/scripts/prepare-aip-package.ts +0 -88
  139. package/scripts/regen-conformance-sigs.ts +0 -18
  140. package/scripts/release-gate.ts +0 -19
  141. package/scripts/validate-runtime-policy.ts +0 -18
  142. package/sdk/browser/index.test.ts +0 -162
  143. package/sdk/browser/index.ts +0 -257
  144. package/sdk/browser/package.json +0 -13
  145. package/sdk/go/go.mod +0 -3
  146. package/sdk/go/http.go +0 -135
  147. package/sdk/go/protocol.go +0 -324
  148. package/sdk/go/protocol_test.go +0 -334
  149. package/sdk/go/webhook.go +0 -136
  150. package/sdk/python/README.md +0 -18
  151. package/sdk/python/protocol_7h3/__init__.py +0 -46
  152. package/sdk/python/protocol_7h3/http.py +0 -212
  153. package/sdk/python/protocol_7h3/keys.py +0 -149
  154. package/sdk/python/protocol_7h3/protocol.py +0 -525
  155. package/sdk/python/protocol_7h3/queue.py +0 -118
  156. package/sdk/python/protocol_7h3/webhook.py +0 -116
  157. package/sdk/python/pyproject.toml +0 -40
  158. package/sdk/python/tests/test_conformance.py +0 -110
  159. package/sdk/python/tests/test_http.py +0 -305
  160. package/sdk/python/tests/test_keys.py +0 -417
  161. package/sdk/python/tests/test_queue.py +0 -120
  162. package/sdk/python/tests/test_webhook.py +0 -345
  163. package/sdk/rust/Cargo.lock +0 -371
  164. package/sdk/rust/Cargo.toml +0 -25
  165. package/sdk/rust/README.md +0 -31
  166. package/sdk/rust/fuzz/Cargo.toml +0 -29
  167. package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
  168. package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
  169. package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
  170. package/sdk/rust/src/http.rs +0 -145
  171. package/sdk/rust/src/keys.rs +0 -161
  172. package/sdk/rust/src/lib.rs +0 -688
  173. package/sdk/rust/src/queue.rs +0 -79
  174. package/sdk/rust/src/webhook.rs +0 -86
  175. package/sdk/rust/tests/conformance.rs +0 -148
  176. package/sdk/rust/tests/gateway.rs +0 -130
  177. package/sdk/rust/tests/http_webhook_queue.rs +0 -201
  178. package/sdk/rust/tests/keys.rs +0 -189
  179. package/src/agentAdapter.test.ts +0 -48
  180. package/src/agentAdapter.ts +0 -56
  181. package/src/auditLog.test.ts +0 -145
  182. package/src/auditLog.ts +0 -147
  183. package/src/conformance.test.ts +0 -136
  184. package/src/conformanceVectors.ts +0 -99
  185. package/src/frameworkAdapters.test.ts +0 -290
  186. package/src/frameworkAdapters.ts +0 -261
  187. package/src/gateway.test.ts +0 -343
  188. package/src/gateway.ts +0 -171
  189. package/src/grpcBinding.test.ts +0 -211
  190. package/src/grpcBinding.ts +0 -103
  191. package/src/httpBinding.test.ts +0 -376
  192. package/src/httpBinding.ts +0 -163
  193. package/src/index.ts +0 -32
  194. package/src/keyInfra.test.ts +0 -278
  195. package/src/keyInfra.ts +0 -228
  196. package/src/keyRegistry.ts +0 -59
  197. package/src/keyRotation.test.ts +0 -78
  198. package/src/keyRotation.ts +0 -72
  199. package/src/mcpGateway.test.ts +0 -129
  200. package/src/mcpGateway.ts +0 -250
  201. package/src/mcpTransports.test.ts +0 -92
  202. package/src/mcpTransports.ts +0 -169
  203. package/src/mcpWrapper.test.ts +0 -179
  204. package/src/mcpWrapper.ts +0 -206
  205. package/src/policyEnforcer.test.ts +0 -99
  206. package/src/policyEnforcer.ts +0 -169
  207. package/src/policyTelemetryFeedback.test.ts +0 -25
  208. package/src/policyTelemetryFeedback.ts +0 -38
  209. package/src/protocol.bench.ts +0 -37
  210. package/src/protocol.test.ts +0 -155
  211. package/src/protocol.ts +0 -413
  212. package/src/protocolAgent.test.ts +0 -105
  213. package/src/protocolAgent.ts +0 -169
  214. package/src/protocolBinary.test.ts +0 -165
  215. package/src/protocolBinary.ts +0 -312
  216. package/src/protocolCapabilities.ts +0 -70
  217. package/src/protocolFuzz.advanced.test.ts +0 -235
  218. package/src/protocolFuzz.test.ts +0 -111
  219. package/src/protocolNegative.test.ts +0 -97
  220. package/src/protocolReplay.test.ts +0 -71
  221. package/src/protocolReplay.ts +0 -194
  222. package/src/protocolTransport.test.ts +0 -556
  223. package/src/protocolTransport.ts +0 -483
  224. package/src/queueBinding.test.ts +0 -130
  225. package/src/queueBinding.ts +0 -102
  226. package/src/rateLimiter.test.ts +0 -96
  227. package/src/rateLimiter.ts +0 -46
  228. package/src/redisIntegration.test.ts +0 -134
  229. package/src/replayStores.test.ts +0 -141
  230. package/src/replayStores.ts +0 -82
  231. package/src/revocation.test.ts +0 -98
  232. package/src/revocation.ts +0 -0
  233. package/src/routePolicy.test.ts +0 -87
  234. package/src/routePolicy.ts +0 -72
  235. package/src/runtimePolicy.test.ts +0 -49
  236. package/src/runtimePolicy.ts +0 -81
  237. package/src/runtimePolicyManager.test.ts +0 -29
  238. package/src/runtimePolicyManager.ts +0 -50
  239. package/src/runtimePolicyPresets.ts +0 -43
  240. package/src/signedResponse.test.ts +0 -111
  241. package/src/signedResponse.ts +0 -83
  242. package/src/webhookBinding.test.ts +0 -144
  243. package/src/webhookBinding.ts +0 -115
  244. package/src/wsBinding.test.ts +0 -221
  245. package/src/wsBinding.ts +0 -100
  246. package/tsconfig.json +0 -15
  247. package/tsconfig.lib.json +0 -23
  248. package/vite.lib.config.ts +0 -16
@@ -1,72 +0,0 @@
1
- import type { ProtocolEnvelope, SignatureVerificationMaterial } from './protocol'
2
-
3
- export type KeyStatus = 'active' | 'verify-only' | 'revoked'
4
-
5
- export interface KeyRecord {
6
- sender: string
7
- keyId: string
8
- alg: SignatureVerificationMaterial['alg']
9
- status: KeyStatus
10
- notBeforeMs: number
11
- notAfterMs: number
12
- material: SignatureVerificationMaterial
13
- }
14
-
15
- export class RollingKeyring {
16
- private readonly records: KeyRecord[]
17
-
18
- constructor(records: KeyRecord[] = []) {
19
- this.records = [...records]
20
- }
21
-
22
- add(record: KeyRecord): void {
23
- this.records.push(record)
24
- }
25
-
26
- revoke(sender: string, keyId: string, revokedAtMs = Date.now()): boolean {
27
- const record = this.records.find((entry) => entry.sender === sender && entry.keyId === keyId)
28
- if (!record) return false
29
- record.status = 'revoked'
30
- record.notAfterMs = Math.min(record.notAfterMs, revokedAtMs)
31
- return true
32
- }
33
-
34
- selectSigningKey(sender: string, alg: SignatureVerificationMaterial['alg'], nowMs = Date.now()): KeyRecord | null {
35
- const candidates = this.records
36
- .filter(
37
- (entry) =>
38
- entry.sender === sender &&
39
- entry.alg === alg &&
40
- entry.status === 'active' &&
41
- entry.notBeforeMs <= nowMs &&
42
- nowMs < entry.notAfterMs,
43
- )
44
- .sort((a, b) => b.notBeforeMs - a.notBeforeMs)
45
- return candidates[0] ?? null
46
- }
47
-
48
- resolveVerificationMaterial(
49
- signature: NonNullable<ProtocolEnvelope['signature']>,
50
- sender: string,
51
- nowMs = Date.now(),
52
- ): SignatureVerificationMaterial | undefined {
53
- const candidates = this.records
54
- .filter(
55
- (entry) =>
56
- entry.sender === sender &&
57
- entry.keyId === signature.keyId &&
58
- entry.alg === signature.alg &&
59
- entry.status !== 'revoked' &&
60
- entry.notBeforeMs <= nowMs &&
61
- nowMs < entry.notAfterMs,
62
- )
63
- .sort((a, b) => b.notBeforeMs - a.notBeforeMs)
64
-
65
- return candidates[0]?.material
66
- }
67
- }
68
-
69
- export function createKeyringSignatureResolver(keyring: RollingKeyring, nowMsProvider: () => number = () => Date.now()) {
70
- return async (signature: NonNullable<ProtocolEnvelope['signature']>, sender: string): Promise<SignatureVerificationMaterial | undefined> =>
71
- keyring.resolveVerificationMaterial(signature, sender, nowMsProvider())
72
- }
@@ -1,129 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { createAipMcpGatewayRuntime } from './mcpGateway'
3
-
4
- describe('AIP MCP gateway runtime', () => {
5
- it('returns JSON-RPC result for allowed method', async () => {
6
- const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
7
-
8
- const line = JSON.stringify({
9
- jsonrpc: '2.0',
10
- id: 1,
11
- method: 'tools/call',
12
- params: { name: 'planner' },
13
- })
14
-
15
- const responseLine = await runtime.handleLine(line)
16
- expect(responseLine).not.toBeNull()
17
- const response = JSON.parse(responseLine ?? '{}') as {
18
- jsonrpc: string
19
- id: number
20
- result?: { ok: boolean; capability: string }
21
- error?: { code: number; message: string }
22
- }
23
-
24
- expect(response.jsonrpc).toBe('2.0')
25
- expect(response.id).toBe(1)
26
- expect(response.error).toBeUndefined()
27
- expect(response.result?.ok).toBe(true)
28
- expect(response.result?.capability).toBe('mcp.tools/call')
29
- })
30
-
31
- it('returns invalid request for malformed JSON-RPC line', async () => {
32
- const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
33
- const responseLine = await runtime.handleLine('{"bad": true}')
34
- const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
35
-
36
- expect(response.error?.code).toBe(-32600)
37
- expect(response.error?.message).toBe('Invalid Request')
38
- })
39
-
40
- it('returns method not found for disallowed method', async () => {
41
- const runtime = createAipMcpGatewayRuntime({
42
- sharedSecret: 'gateway-test-secret',
43
- allowedMethods: ['tools/call'],
44
- })
45
-
46
- const line = JSON.stringify({
47
- jsonrpc: '2.0',
48
- id: 'x',
49
- method: 'resources/read',
50
- params: {},
51
- })
52
-
53
- const responseLine = await runtime.handleLine(line)
54
- const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
55
-
56
- expect(response.error?.code).toBe(-32601)
57
- expect(response.error?.message).toContain("is not allowed")
58
- })
59
-
60
- it('returns authorization error when policy hook blocks request', async () => {
61
- const runtime = createAipMcpGatewayRuntime({
62
- sharedSecret: 'gateway-test-secret',
63
- authorizeMethod: async () => false,
64
- })
65
-
66
- const line = JSON.stringify({
67
- jsonrpc: '2.0',
68
- id: 'auth-2',
69
- method: 'tools/call',
70
- params: {},
71
- })
72
-
73
- const responseLine = await runtime.handleLine(line)
74
- const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
75
-
76
- expect(response.error?.code).toBe(-32001)
77
- expect(response.error?.message).toContain('not authorized')
78
- })
79
-
80
- it('returns rate-limit error when limiter blocks request', async () => {
81
- const runtime = createAipMcpGatewayRuntime({
82
- sharedSecret: 'gateway-test-secret',
83
- rateLimiter: {
84
- consume: async () => false,
85
- },
86
- })
87
-
88
- const line = JSON.stringify({
89
- jsonrpc: '2.0',
90
- id: 'rl-3',
91
- method: 'tools/call',
92
- params: {},
93
- })
94
-
95
- const responseLine = await runtime.handleLine(line)
96
- const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
97
-
98
- expect(response.error?.code).toBe(-32002)
99
- expect(response.error?.message).toContain('rate-limited')
100
- })
101
-
102
- it('ignores empty lines', async () => {
103
- const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
104
- await expect(runtime.handleLine(' ')).resolves.toBeNull()
105
- })
106
-
107
- it('emits audit events for policy and request phases', async () => {
108
- const phases: string[] = []
109
- const runtime = createAipMcpGatewayRuntime({
110
- sharedSecret: 'gateway-test-secret',
111
- onAuditEvent: async (event) => {
112
- phases.push(event.phase)
113
- },
114
- })
115
-
116
- const line = JSON.stringify({
117
- jsonrpc: '2.0',
118
- id: 'audit-1',
119
- method: 'tools/call',
120
- params: { name: 'planner' },
121
- })
122
-
123
- const responseLine = await runtime.handleLine(line)
124
- expect(responseLine).not.toBeNull()
125
- expect(phases).toContain('request_received')
126
- expect(phases).toContain('policy')
127
- expect(phases).toContain('request_success')
128
- })
129
- })
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
- })