sealbridge 0.0.1 → 0.2.0

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 (208) hide show
  1. package/NOTICE +43 -0
  2. package/README.ja.md +397 -0
  3. package/README.md +567 -1
  4. package/dist/cli/dist/acp.d.ts +2 -0
  5. package/dist/cli/dist/acp.js +6 -0
  6. package/dist/cli/dist/acp.js.map +1 -0
  7. package/dist/cli/dist/approval-summary.d.ts +2 -0
  8. package/dist/cli/dist/approval-summary.js +32 -0
  9. package/dist/cli/dist/approval-summary.js.map +1 -0
  10. package/dist/cli/dist/board.d.ts +47 -0
  11. package/dist/cli/dist/board.js +255 -0
  12. package/dist/cli/dist/board.js.map +1 -0
  13. package/dist/cli/dist/contacts.d.ts +18 -0
  14. package/dist/cli/dist/contacts.js +154 -0
  15. package/dist/cli/dist/contacts.js.map +1 -0
  16. package/dist/cli/dist/doctor.d.ts +13 -0
  17. package/dist/cli/dist/doctor.js +30 -0
  18. package/dist/cli/dist/doctor.js.map +1 -0
  19. package/dist/cli/dist/error-messages.d.ts +2 -0
  20. package/dist/cli/dist/error-messages.js +24 -0
  21. package/dist/cli/dist/error-messages.js.map +1 -0
  22. package/dist/cli/dist/index.d.ts +2 -0
  23. package/dist/cli/dist/index.js +194 -0
  24. package/dist/cli/dist/index.js.map +1 -0
  25. package/dist/cli/dist/invite-share.d.ts +16 -0
  26. package/dist/cli/dist/invite-share.js +163 -0
  27. package/dist/cli/dist/invite-share.js.map +1 -0
  28. package/dist/cli/dist/mcp.d.ts +17 -0
  29. package/dist/cli/dist/mcp.js +105 -0
  30. package/dist/cli/dist/mcp.js.map +1 -0
  31. package/dist/cli/dist/network.d.ts +1 -0
  32. package/dist/cli/dist/network.js +396 -0
  33. package/dist/cli/dist/network.js.map +1 -0
  34. package/dist/cli/dist/public-settings.d.ts +27 -0
  35. package/dist/cli/dist/public-settings.js +33 -0
  36. package/dist/cli/dist/public-settings.js.map +1 -0
  37. package/dist/cli/dist/receipt.d.ts +39 -0
  38. package/dist/cli/dist/receipt.js +90 -0
  39. package/dist/cli/dist/receipt.js.map +1 -0
  40. package/dist/cli/dist/receive-hook.d.ts +2 -0
  41. package/dist/cli/dist/receive-hook.js +35 -0
  42. package/dist/cli/dist/receive-hook.js.map +1 -0
  43. package/dist/cli/dist/task-input.d.ts +2 -0
  44. package/dist/cli/dist/task-input.js +19 -0
  45. package/dist/cli/dist/task-input.js.map +1 -0
  46. package/dist/cli/dist/wake-hook.d.ts +3 -0
  47. package/dist/cli/dist/wake-hook.js +32 -0
  48. package/dist/cli/dist/wake-hook.js.map +1 -0
  49. package/dist/examples/two-owner-demo/alice-policy.json +4 -0
  50. package/dist/examples/two-owner-demo/bob-policy.json +4 -0
  51. package/dist/examples/two-owner-demo/dist/run-demo.d.ts +71 -0
  52. package/dist/examples/two-owner-demo/dist/run-demo.js +383 -0
  53. package/dist/examples/two-owner-demo/dist/run-demo.js.map +1 -0
  54. package/dist/package.json +1 -0
  55. package/dist/packages/adapters/dist/command.d.ts +122 -0
  56. package/dist/packages/adapters/dist/command.js +378 -0
  57. package/dist/packages/adapters/dist/command.js.map +1 -0
  58. package/dist/packages/adapters/dist/credential-proxy.d.ts +37 -0
  59. package/dist/packages/adapters/dist/credential-proxy.js +363 -0
  60. package/dist/packages/adapters/dist/credential-proxy.js.map +1 -0
  61. package/dist/packages/adapters/dist/index.d.ts +6 -0
  62. package/dist/packages/adapters/dist/index.js +6 -0
  63. package/dist/packages/adapters/dist/index.js.map +1 -0
  64. package/dist/packages/adapters/dist/mcp.d.ts +32 -0
  65. package/dist/packages/adapters/dist/mcp.js +87 -0
  66. package/dist/packages/adapters/dist/mcp.js.map +1 -0
  67. package/dist/packages/adapters/dist/openai.d.ts +23 -0
  68. package/dist/packages/adapters/dist/openai.js +81 -0
  69. package/dist/packages/adapters/dist/openai.js.map +1 -0
  70. package/dist/packages/adapters/dist/sandbox.d.ts +45 -0
  71. package/dist/packages/adapters/dist/sandbox.js +60 -0
  72. package/dist/packages/adapters/dist/sandbox.js.map +1 -0
  73. package/dist/packages/core/dist/capability.d.ts +36 -0
  74. package/dist/packages/core/dist/capability.js +203 -0
  75. package/dist/packages/core/dist/capability.js.map +1 -0
  76. package/dist/packages/core/dist/contract.d.ts +33 -0
  77. package/dist/packages/core/dist/contract.js +97 -0
  78. package/dist/packages/core/dist/contract.js.map +1 -0
  79. package/dist/packages/core/dist/crypto.d.ts +34 -0
  80. package/dist/packages/core/dist/crypto.js +329 -0
  81. package/dist/packages/core/dist/crypto.js.map +1 -0
  82. package/dist/packages/core/dist/discovery.d.ts +129 -0
  83. package/dist/packages/core/dist/discovery.js +527 -0
  84. package/dist/packages/core/dist/discovery.js.map +1 -0
  85. package/dist/packages/core/dist/edge.d.ts +4 -0
  86. package/dist/packages/core/dist/edge.js +6 -0
  87. package/dist/packages/core/dist/edge.js.map +1 -0
  88. package/dist/packages/core/dist/index.d.ts +9 -0
  89. package/dist/packages/core/dist/index.js +10 -0
  90. package/dist/packages/core/dist/index.js.map +1 -0
  91. package/dist/packages/core/dist/receipts.d.ts +9 -0
  92. package/dist/packages/core/dist/receipts.js +67 -0
  93. package/dist/packages/core/dist/receipts.js.map +1 -0
  94. package/dist/packages/core/dist/relay-auth.d.ts +40 -0
  95. package/dist/packages/core/dist/relay-auth.js +106 -0
  96. package/dist/packages/core/dist/relay-auth.js.map +1 -0
  97. package/dist/packages/core/dist/revocation.d.ts +58 -0
  98. package/dist/packages/core/dist/revocation.js +112 -0
  99. package/dist/packages/core/dist/revocation.js.map +1 -0
  100. package/dist/packages/core/dist/schema.d.ts +11 -0
  101. package/dist/packages/core/dist/schema.js +48 -0
  102. package/dist/packages/core/dist/schema.js.map +1 -0
  103. package/dist/packages/core/dist/types.d.ts +172 -0
  104. package/dist/packages/core/dist/types.js +2 -0
  105. package/dist/packages/core/dist/types.js.map +1 -0
  106. package/dist/packages/ledger/dist/index.d.ts +84 -0
  107. package/dist/packages/ledger/dist/index.js +269 -0
  108. package/dist/packages/ledger/dist/index.js.map +1 -0
  109. package/dist/packages/relay/dist/fetch.d.ts +250 -0
  110. package/dist/packages/relay/dist/fetch.js +1401 -0
  111. package/dist/packages/relay/dist/fetch.js.map +1 -0
  112. package/dist/packages/relay/dist/index.d.ts +103 -0
  113. package/dist/packages/relay/dist/index.js +855 -0
  114. package/dist/packages/relay/dist/index.js.map +1 -0
  115. package/dist/packages/relay/dist/journal.d.ts +77 -0
  116. package/dist/packages/relay/dist/journal.js +339 -0
  117. package/dist/packages/relay/dist/journal.js.map +1 -0
  118. package/dist/packages/relay/dist/notifications.d.ts +63 -0
  119. package/dist/packages/relay/dist/notifications.js +55 -0
  120. package/dist/packages/relay/dist/notifications.js.map +1 -0
  121. package/dist/packages/relay/dist/retry.d.ts +12 -0
  122. package/dist/packages/relay/dist/retry.js +59 -0
  123. package/dist/packages/relay/dist/retry.js.map +1 -0
  124. package/dist/packages/relay/dist/ssrf.d.ts +32 -0
  125. package/dist/packages/relay/dist/ssrf.js +213 -0
  126. package/dist/packages/relay/dist/ssrf.js.map +1 -0
  127. package/dist/packages/relay/dist/worker.d.ts +92 -0
  128. package/dist/packages/relay/dist/worker.js +460 -0
  129. package/dist/packages/relay/dist/worker.js.map +1 -0
  130. package/dist/packages/runtime/dist/a2a.d.ts +8 -0
  131. package/dist/packages/runtime/dist/a2a.js +69 -0
  132. package/dist/packages/runtime/dist/a2a.js.map +1 -0
  133. package/dist/packages/runtime/dist/adapter.d.ts +51 -0
  134. package/dist/packages/runtime/dist/adapter.js +2 -0
  135. package/dist/packages/runtime/dist/adapter.js.map +1 -0
  136. package/dist/packages/runtime/dist/approval-providers.d.ts +99 -0
  137. package/dist/packages/runtime/dist/approval-providers.js +269 -0
  138. package/dist/packages/runtime/dist/approval-providers.js.map +1 -0
  139. package/dist/packages/runtime/dist/daily-limit.d.ts +2 -0
  140. package/dist/packages/runtime/dist/daily-limit.js +54 -0
  141. package/dist/packages/runtime/dist/daily-limit.js.map +1 -0
  142. package/dist/packages/runtime/dist/gateway.d.ts +35 -0
  143. package/dist/packages/runtime/dist/gateway.js +92 -0
  144. package/dist/packages/runtime/dist/gateway.js.map +1 -0
  145. package/dist/packages/runtime/dist/inbox.d.ts +10 -0
  146. package/dist/packages/runtime/dist/inbox.js +24 -0
  147. package/dist/packages/runtime/dist/inbox.js.map +1 -0
  148. package/dist/packages/runtime/dist/index.d.ts +13 -0
  149. package/dist/packages/runtime/dist/index.js +14 -0
  150. package/dist/packages/runtime/dist/index.js.map +1 -0
  151. package/dist/packages/runtime/dist/mcp-server.d.ts +90 -0
  152. package/dist/packages/runtime/dist/mcp-server.js +647 -0
  153. package/dist/packages/runtime/dist/mcp-server.js.map +1 -0
  154. package/dist/packages/runtime/dist/mcp-tool-catalog.d.ts +3 -0
  155. package/dist/packages/runtime/dist/mcp-tool-catalog.js +507 -0
  156. package/dist/packages/runtime/dist/mcp-tool-catalog.js.map +1 -0
  157. package/dist/packages/runtime/dist/mcp.d.ts +73 -0
  158. package/dist/packages/runtime/dist/mcp.js +67 -0
  159. package/dist/packages/runtime/dist/mcp.js.map +1 -0
  160. package/dist/packages/runtime/dist/policy.d.ts +19 -0
  161. package/dist/packages/runtime/dist/policy.js +41 -0
  162. package/dist/packages/runtime/dist/policy.js.map +1 -0
  163. package/dist/packages/runtime/dist/quota.d.ts +39 -0
  164. package/dist/packages/runtime/dist/quota.js +147 -0
  165. package/dist/packages/runtime/dist/quota.js.map +1 -0
  166. package/dist/packages/runtime/dist/session-audit.d.ts +107 -0
  167. package/dist/packages/runtime/dist/session-audit.js +520 -0
  168. package/dist/packages/runtime/dist/session-audit.js.map +1 -0
  169. package/dist/packages/runtime/dist/session-timeouts.d.ts +33 -0
  170. package/dist/packages/runtime/dist/session-timeouts.js +62 -0
  171. package/dist/packages/runtime/dist/session-timeouts.js.map +1 -0
  172. package/dist/packages/runtime/dist/session.d.ts +323 -0
  173. package/dist/packages/runtime/dist/session.js +2252 -0
  174. package/dist/packages/runtime/dist/session.js.map +1 -0
  175. package/dist/packages/runtime/dist/task-journal.d.ts +36 -0
  176. package/dist/packages/runtime/dist/task-journal.js +104 -0
  177. package/dist/packages/runtime/dist/task-journal.js.map +1 -0
  178. package/dist/packages/runtime/dist/verifier.d.ts +64 -0
  179. package/dist/packages/runtime/dist/verifier.js +109 -0
  180. package/dist/packages/runtime/dist/verifier.js.map +1 -0
  181. package/dist/packages/sandbox/dist/container.d.ts +33 -0
  182. package/dist/packages/sandbox/dist/container.js +189 -0
  183. package/dist/packages/sandbox/dist/container.js.map +1 -0
  184. package/dist/packages/sandbox/dist/index.d.ts +34 -0
  185. package/dist/packages/sandbox/dist/index.js +179 -0
  186. package/dist/packages/sandbox/dist/index.js.map +1 -0
  187. package/dist/packages/sandbox/dist/permission.d.ts +58 -0
  188. package/dist/packages/sandbox/dist/permission.js +90 -0
  189. package/dist/packages/sandbox/dist/permission.js.map +1 -0
  190. package/dist/spec/README.ja.md +76 -0
  191. package/dist/spec/README.md +135 -0
  192. package/dist/spec/ack-receipt.schema.json +77 -0
  193. package/dist/spec/agent-card.schema.json +83 -0
  194. package/dist/spec/approval-receipt.schema.json +94 -0
  195. package/dist/spec/audit-entry.schema.json +101 -0
  196. package/dist/spec/capability-token.schema.json +133 -0
  197. package/dist/spec/envelope.schema.json +197 -0
  198. package/dist/spec/examples/sample-ack-receipt.json +16 -0
  199. package/dist/spec/examples/sample-agent-card.json +35 -0
  200. package/dist/spec/examples/sample-approval-receipt.json +18 -0
  201. package/dist/spec/examples/sample-audit-entry.json +37 -0
  202. package/dist/spec/examples/sample-capability-token.json +39 -0
  203. package/dist/spec/examples/sample-envelope.json +21 -0
  204. package/dist/spec/examples/sample-task-manifest.json +141 -0
  205. package/dist/spec/examples/sample-usage-receipt.json +19 -0
  206. package/dist/spec/task-manifest.schema.json +422 -0
  207. package/dist/spec/usage-receipt.schema.json +91 -0
  208. package/package.json +61 -5
@@ -0,0 +1,647 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { createSignedAgentCard, createInviteToken, createRevocationNotice, digest, parseAndVerifyInviteToken, resolveDid, validateRelayEndpointUrl, verifySignedAgentCard, } from '../../core/dist/index.js';
3
+ import { RevocationRegistry } from '../../core/dist/index.js';
4
+ import { verifyAudit } from '../../ledger/dist/index.js';
5
+ import { HttpRelayClient } from '../../relay/dist/index.js';
6
+ import { mcpTools } from './mcp-tool-catalog.js';
7
+ import { InteractiveCliApprovalProvider, PolicyAutoApprovalProvider, WebhookApprovalProvider, assertApprovalWebhookUrl, } from './approval-providers.js';
8
+ import { RequesterClient } from './session.js';
9
+ import { validateSessionTimeouts } from './session-timeouts.js';
10
+ class RpcError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ }
16
+ }
17
+ const PROTOCOL_VERSION = '2024-11-05';
18
+ const OUTBOUND_REASON = 'outbound delegation to a remote Sealbridge worker';
19
+ /**
20
+ * Guidance MCP clients place ahead of the tool catalog. Keep the workflow,
21
+ * the sibling-tool split, and the outbound security boundary in this text.
22
+ */
23
+ const SERVER_INSTRUCTIONS = "This server delegates JSON tasks to remote Sealbridge workers over an encrypted Blind Relay and checks the bilateral audit trail. Call acp_discover_workers or start from an acp-invite, mint one with acp_create_invite when you are the worker owner, then use acp_delegate_task for one worker or acp_delegate_multi for race or quorum. Use acp_verify_audit_bundle with an anchor or expected tail fields when truncation must be ruled out, and use acp_revoke_token with acp_inspect_revocations to record and read local invite or capability revocation. Delegation signs with this process's Ed25519 owner key, and task input is transmitted externally only after allowedWorkerDids, allowedRelayUrls, and the approval provider or webhook allow the dial. Verify and inspect stay on this machine, discover only reads a relay directory, and revoke does not push a notice to a worker. Do not put secrets in arguments: delegation is synchronous and returns the verified result in that same call.";
24
+ function rpcError(id, code, message) {
25
+ return { jsonrpc: '2.0', id: (id ?? null), error: { code, message } };
26
+ }
27
+ function toolResult(value, isError = false) {
28
+ const result = {
29
+ content: [{ type: 'text', text: JSON.stringify(value) }],
30
+ isError,
31
+ };
32
+ // Success payloads are also exposed as structuredContent for outputSchema.
33
+ // Failures stay in content text so they are not validated as the success schema.
34
+ if (!isError && value && typeof value === 'object' && !Array.isArray(value))
35
+ result.structuredContent = value;
36
+ return result;
37
+ }
38
+ function taskTimeouts(args) {
39
+ const options = {};
40
+ for (const key of ['executionTimeoutMs', 'taskTimeoutMs', 'waitTimeoutMs', 'connectTimeoutMs',
41
+ 'transportIdleTimeoutMs', 'heartbeatIntervalMs']) {
42
+ if (args[key] !== undefined) {
43
+ if (typeof args[key] !== 'number')
44
+ throw new RpcError(-32602, 'Invalid timeout: ' + key);
45
+ options[key] = args[key];
46
+ }
47
+ }
48
+ validateSessionTimeouts(options);
49
+ return options;
50
+ }
51
+ function asRecord(value) {
52
+ if (!value || typeof value !== 'object' || Array.isArray(value))
53
+ throw new RpcError(-32602, 'Invalid params');
54
+ return value;
55
+ }
56
+ /** Origin plus pathname, without a trailing slash. Query and fragment are dropped. */
57
+ export function normalizeMcpRelayUrl(value) {
58
+ let url;
59
+ try {
60
+ url = new URL(value);
61
+ }
62
+ catch {
63
+ throw new Error('MCP_RELAY_NOT_ALLOWED');
64
+ }
65
+ return url.origin + url.pathname.replace(/\/+$/, '');
66
+ }
67
+ function workerAllowlist(dids) {
68
+ if (dids === undefined)
69
+ return undefined;
70
+ if (!Array.isArray(dids))
71
+ throw new Error('INVALID_WORKER_DID');
72
+ const allow = new Set();
73
+ for (const did of dids) {
74
+ if (typeof did !== 'string')
75
+ throw new Error('INVALID_WORKER_DID');
76
+ const trimmed = did.trim();
77
+ try {
78
+ resolveDid(trimmed);
79
+ }
80
+ catch {
81
+ throw new Error('INVALID_WORKER_DID');
82
+ }
83
+ allow.add(trimmed);
84
+ }
85
+ return allow;
86
+ }
87
+ function relayAllowlist(urls, allowPrivateRelay) {
88
+ if (urls === undefined)
89
+ return undefined;
90
+ if (!Array.isArray(urls))
91
+ throw new Error('INVALID_RELAY_URL');
92
+ const allow = new Set();
93
+ for (const entry of urls) {
94
+ if (typeof entry !== 'string')
95
+ throw new Error('INVALID_RELAY_URL');
96
+ const trimmed = entry.trim();
97
+ validateRelayEndpointUrl(trimmed, { allowPrivateRelay });
98
+ allow.add(normalizeMcpRelayUrl(trimmed));
99
+ }
100
+ return allow;
101
+ }
102
+ function selectApproval(options) {
103
+ if (options.approvalProvider)
104
+ return options.approvalProvider;
105
+ if (options.approvalWebhookUrl) {
106
+ return new WebhookApprovalProvider(options.owner, {
107
+ url: options.approvalWebhookUrl,
108
+ fetch: options.approvalWebhookFetcher,
109
+ response: 'owner-decision',
110
+ });
111
+ }
112
+ if (options.requireInteractiveApproval) {
113
+ return new InteractiveCliApprovalProvider(options.owner, {
114
+ input: options.approvalInput,
115
+ output: options.approvalOutput,
116
+ });
117
+ }
118
+ return new PolicyAutoApprovalProvider(options.owner);
119
+ }
120
+ /** Dependency-free MCP server for ACP delegation, discovery, audit verification, and revocation. */
121
+ export class AcpMcpServer {
122
+ options;
123
+ revocation;
124
+ allowedWorkers;
125
+ allowedRelays;
126
+ approval;
127
+ framed;
128
+ constructor(options) {
129
+ this.options = options;
130
+ this.revocation = options.revocation ?? new RevocationRegistry();
131
+ this.allowedWorkers = workerAllowlist(options.allowedWorkerDids);
132
+ this.allowedRelays = relayAllowlist(options.allowedRelayUrls, options.allowPrivateRelay);
133
+ if (options.approvalWebhookUrl)
134
+ assertApprovalWebhookUrl(options.approvalWebhookUrl);
135
+ this.approval = selectApproval(options);
136
+ }
137
+ async handle(message) {
138
+ if (!message || typeof message !== 'object' || Array.isArray(message))
139
+ return rpcError(null, -32600, 'Invalid Request');
140
+ const request = message;
141
+ const hasId = Object.hasOwn(message, 'id');
142
+ const id = hasId ? request.id : null;
143
+ if (request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
144
+ if (!hasId)
145
+ return undefined;
146
+ return rpcError(id, -32600, 'Invalid Request');
147
+ }
148
+ try {
149
+ const result = await this.dispatch(request.method, request.params);
150
+ if (!hasId)
151
+ return undefined;
152
+ return { jsonrpc: '2.0', id: id, result };
153
+ }
154
+ catch (error) {
155
+ if (!hasId)
156
+ return undefined;
157
+ const code = error instanceof RpcError ? error.code : -32603;
158
+ const messageText = error instanceof Error ? error.message : String(error);
159
+ return rpcError(id, code, messageText);
160
+ }
161
+ }
162
+ /** Read newline-delimited JSON or Content-Length frames and answer in the same framing. */
163
+ async serve(input, output) {
164
+ let buffer = Buffer.alloc(0);
165
+ for await (const chunk of input) {
166
+ const next = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
167
+ buffer = Buffer.concat([buffer, next]);
168
+ buffer = await this.drain(buffer, output);
169
+ }
170
+ }
171
+ async drain(buffer, output) {
172
+ let rest = buffer;
173
+ while (rest.length > 0) {
174
+ if (this.framed === undefined) {
175
+ const preview = rest.toString('utf8');
176
+ if (preview.startsWith('Content-Length'))
177
+ this.framed = true;
178
+ else if (preview.trimStart().startsWith('{') || preview.includes('\n'))
179
+ this.framed = false;
180
+ else
181
+ break;
182
+ }
183
+ if (this.framed) {
184
+ const headerEnd = rest.indexOf('\r\n\r\n');
185
+ if (headerEnd < 0)
186
+ break;
187
+ const header = rest.subarray(0, headerEnd).toString('utf8');
188
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
189
+ if (!match)
190
+ throw new Error('INVALID_FRAMING');
191
+ const length = Number(match[1]);
192
+ const start = headerEnd + 4;
193
+ if (rest.length < start + length)
194
+ break;
195
+ const body = rest.subarray(start, start + length).toString('utf8');
196
+ rest = rest.subarray(start + length);
197
+ await this.reply(output, body);
198
+ }
199
+ else {
200
+ const newline = rest.indexOf(0x0a);
201
+ if (newline < 0)
202
+ break;
203
+ const line = rest.subarray(0, newline).toString('utf8').replace(/\r$/, '');
204
+ rest = rest.subarray(newline + 1);
205
+ if (line.trim())
206
+ await this.reply(output, line);
207
+ }
208
+ }
209
+ return rest;
210
+ }
211
+ async reply(output, body) {
212
+ let message;
213
+ try {
214
+ message = JSON.parse(body);
215
+ }
216
+ catch {
217
+ this.write(output, rpcError(null, -32700, 'Parse error'));
218
+ return;
219
+ }
220
+ const response = await this.handle(message);
221
+ if (response !== undefined)
222
+ this.write(output, response);
223
+ }
224
+ write(output, value) {
225
+ const json = JSON.stringify(value);
226
+ if (this.framed)
227
+ output.write('Content-Length: ' + Buffer.byteLength(json) + '\r\n\r\n' + json);
228
+ else
229
+ output.write(json + '\n');
230
+ }
231
+ async dispatch(method, params) {
232
+ if (method === 'initialize') {
233
+ return {
234
+ protocolVersion: PROTOCOL_VERSION,
235
+ capabilities: { tools: { listChanged: false } },
236
+ serverInfo: { name: 'sealbridge-mcp-server', version: '0.2.0' },
237
+ instructions: SERVER_INSTRUCTIONS,
238
+ };
239
+ }
240
+ if (method === 'notifications/initialized')
241
+ return {};
242
+ if (method === 'ping')
243
+ return {};
244
+ if (method === 'tools/list')
245
+ return { tools: mcpTools };
246
+ if (method === 'tools/call')
247
+ return this.toolsCall(params);
248
+ throw new RpcError(-32601, 'Method not found');
249
+ }
250
+ async toolsCall(params) {
251
+ const body = asRecord(params);
252
+ const name = body.name;
253
+ if (typeof name !== 'string')
254
+ throw new RpcError(-32602, 'Invalid params');
255
+ const args = body.arguments === undefined ? {} : asRecord(body.arguments);
256
+ try {
257
+ const value = await this.callTool(name, args);
258
+ return toolResult(value, false);
259
+ }
260
+ catch (error) {
261
+ if (error instanceof RpcError)
262
+ throw error;
263
+ return toolResult({ error: error instanceof Error ? error.message : String(error) }, true);
264
+ }
265
+ }
266
+ async callTool(name, args) {
267
+ if (name === 'acp_delegate_task')
268
+ return this.delegateTask(args);
269
+ if (name === 'acp_delegate_multi')
270
+ return this.delegateMulti(args);
271
+ if (name === 'acp_verify_audit_bundle')
272
+ return this.verifyBundle(args);
273
+ if (name === 'acp_create_invite')
274
+ return this.createInvite(args);
275
+ if (name === 'acp_revoke_token')
276
+ return this.revokeToken(args);
277
+ if (name === 'acp_discover_workers')
278
+ return this.discoverWorkers(args);
279
+ if (name === 'acp_inspect_revocations')
280
+ return this.inspectRevocations(args);
281
+ throw new RpcError(-32602, 'Unknown tool');
282
+ }
283
+ client(relay) {
284
+ const selected = relay ?? this.options.relay;
285
+ if (!selected)
286
+ throw new Error('RELAY_REQUIRED');
287
+ return new RequesterClient({
288
+ owner: this.options.owner,
289
+ relay: selected,
290
+ approvalProvider: this.approval,
291
+ allowPrivateRelay: this.options.allowPrivateRelay,
292
+ });
293
+ }
294
+ isInvite(value) {
295
+ return value.startsWith('acp-invite:') || value.startsWith('acp:');
296
+ }
297
+ parseInvite(token) {
298
+ return parseAndVerifyInviteToken(token, Date.now(), {
299
+ allowPrivateRelay: this.options.allowPrivateRelay,
300
+ });
301
+ }
302
+ /** Reject unlisted workers before any relay dial. */
303
+ assertWorker(invite, workerDid) {
304
+ if (!this.allowedWorkers)
305
+ return;
306
+ if (invite) {
307
+ if (this.allowedWorkers.has(invite.agentDid) || this.allowedWorkers.has(invite.ownerDid))
308
+ return;
309
+ throw new Error('MCP_WORKER_NOT_ALLOWED');
310
+ }
311
+ if (typeof workerDid === 'string' && this.allowedWorkers.has(workerDid))
312
+ return;
313
+ throw new Error('MCP_WORKER_NOT_ALLOWED');
314
+ }
315
+ /**
316
+ * Reject unlisted or switched relay URLs before any relay dial.
317
+ * Explicit tool URLs are dial targets. Invite URLs are outbound destinations
318
+ * even when the configured transport is what would be dialed.
319
+ */
320
+ assertRelays(explicit, inviteUrls) {
321
+ const configured = typeof this.options.relay === 'string' ? this.options.relay : undefined;
322
+ const outbound = [...explicit, ...inviteUrls];
323
+ if (configured)
324
+ outbound.push(configured);
325
+ if (this.allowedRelays) {
326
+ for (const url of outbound) {
327
+ if (!this.allowedRelays.has(normalizeMcpRelayUrl(url)))
328
+ throw new Error('MCP_RELAY_NOT_ALLOWED');
329
+ }
330
+ return;
331
+ }
332
+ if (this.options.allowAnyRelay)
333
+ return;
334
+ if (configured) {
335
+ const expected = normalizeMcpRelayUrl(configured);
336
+ for (const url of [...explicit, ...inviteUrls]) {
337
+ if (normalizeMcpRelayUrl(url) !== expected)
338
+ throw new Error('MCP_RELAY_NOT_ALLOWED');
339
+ }
340
+ return;
341
+ }
342
+ if (this.options.relay && explicit.length > 0)
343
+ throw new Error('MCP_RELAY_NOT_ALLOWED');
344
+ }
345
+ outboundApprovalRequired() {
346
+ if (this.options.approvalProvider)
347
+ return !(this.options.approvalProvider instanceof PolicyAutoApprovalProvider);
348
+ return Boolean(this.options.approvalWebhookUrl || this.options.requireInteractiveApproval);
349
+ }
350
+ /** Ask the configured provider before task input is written to the relay. */
351
+ async approveOutbound(summary) {
352
+ if (!this.outboundApprovalRequired())
353
+ return;
354
+ const taskId = 'task_mcp_' + digest(summary).slice(7, 23);
355
+ const receipt = await this.approval.approve({
356
+ taskId,
357
+ gate: 'external_side_effect',
358
+ target: summary,
359
+ details: { reason: OUTBOUND_REASON },
360
+ });
361
+ if (receipt.payload.decision !== 'APPROVED')
362
+ throw new Error('APPROVAL_REJECTED');
363
+ }
364
+ async delegateTask(args) {
365
+ const to = typeof args.inviteToken === 'string' ? args.inviteToken : args.workerDid;
366
+ if (typeof to !== 'string' || typeof args.title !== 'string')
367
+ throw new RpcError(-32602, 'Invalid params');
368
+ const invite = this.isInvite(to) ? this.parseInvite(to) : undefined;
369
+ const explicitRelay = typeof args.relayUrl === 'string' ? args.relayUrl : undefined;
370
+ this.assertWorker(invite, invite ? undefined : to);
371
+ this.assertRelays(explicitRelay ? [explicitRelay] : [], invite ? [invite.relayUrl] : []);
372
+ const relay = explicitRelay ?? this.options.relay;
373
+ await this.approveOutbound({
374
+ title: args.title,
375
+ description: typeof args.description === 'string' ? args.description : '',
376
+ input: (args.input ?? null),
377
+ workers: [
378
+ {
379
+ workerDid: invite?.agentDid ?? to,
380
+ agentDid: invite?.agentDid ?? null,
381
+ ownerDid: invite?.ownerDid ?? null,
382
+ },
383
+ ],
384
+ relayUrls: this.relayList(explicitRelay ? [explicitRelay] : [], invite ? [invite.relayUrl] : []),
385
+ });
386
+ const request = {
387
+ ...taskTimeouts(args),
388
+ to,
389
+ relay,
390
+ title: args.title,
391
+ description: typeof args.description === 'string' ? args.description : '',
392
+ input: (args.input ?? null),
393
+ outputSchema: args.outputSchema,
394
+ budget: args.budget,
395
+ minimumIsolationLevel: typeof args.minimumIsolationLevel === 'string' ? args.minimumIsolationLevel : undefined,
396
+ };
397
+ const result = await this.client(relay).delegate(request);
398
+ const report = verifyAudit(result.bundle);
399
+ return {
400
+ taskId: result.taskId,
401
+ status: result.state,
402
+ deliverable: result.deliverable,
403
+ auditBundle: {
404
+ valid: report.valid,
405
+ status: report.status,
406
+ count: result.bundle.entries.length,
407
+ head: result.bundle.checkpoint.actor.payload.head,
408
+ taskId: result.taskId,
409
+ },
410
+ };
411
+ }
412
+ async delegateMulti(args) {
413
+ if (!Array.isArray(args.workers) || (args.mode !== 'race' && args.mode !== 'quorum'))
414
+ throw new RpcError(-32602, 'Invalid params');
415
+ if (typeof args.title !== 'string')
416
+ throw new RpcError(-32602, 'Invalid params');
417
+ const relay = typeof args.relayUrl === 'string' ? args.relayUrl : undefined;
418
+ const explicitRelays = [];
419
+ if (relay)
420
+ explicitRelays.push(relay);
421
+ const inviteUrls = [];
422
+ const described = [];
423
+ const workers = args.workers.map((worker) => {
424
+ const item = asRecord(worker);
425
+ if (typeof item.workerDid !== 'string')
426
+ throw new RpcError(-32602, 'Invalid params');
427
+ const inviteToken = typeof item.inviteToken === 'string' ? item.inviteToken : undefined;
428
+ const invite = inviteToken ? this.parseInvite(inviteToken) : undefined;
429
+ this.assertWorker(invite, item.workerDid);
430
+ if (typeof item.relayUrl === 'string')
431
+ explicitRelays.push(item.relayUrl);
432
+ if (invite)
433
+ inviteUrls.push(invite.relayUrl);
434
+ described.push({
435
+ workerDid: invite?.agentDid ?? item.workerDid,
436
+ agentDid: invite?.agentDid ?? null,
437
+ ownerDid: invite?.ownerDid ?? null,
438
+ });
439
+ return {
440
+ workerDid: item.workerDid,
441
+ inviteToken,
442
+ relayUrl: typeof item.relayUrl === 'string' ? item.relayUrl : relay,
443
+ };
444
+ });
445
+ this.assertRelays(explicitRelays, inviteUrls);
446
+ await this.approveOutbound({
447
+ title: args.title,
448
+ description: typeof args.description === 'string' ? args.description : '',
449
+ input: (args.input ?? null),
450
+ mode: args.mode,
451
+ workers: described,
452
+ relayUrls: this.relayList(explicitRelays, inviteUrls),
453
+ });
454
+ const quorumSize = args.quorumSize === undefined
455
+ ? undefined
456
+ : typeof args.quorumSize === 'number'
457
+ ? args.quorumSize
458
+ : Number.NaN;
459
+ const request = {
460
+ ...taskTimeouts(args),
461
+ workers,
462
+ mode: args.mode,
463
+ quorumSize,
464
+ title: args.title,
465
+ description: typeof args.description === 'string' ? args.description : '',
466
+ input: (args.input ?? null),
467
+ outputSchema: args.outputSchema,
468
+ budget: args.budget,
469
+ minimumIsolationLevel: typeof args.minimumIsolationLevel === 'string' ? args.minimumIsolationLevel : undefined,
470
+ };
471
+ const result = await this.client(relay ?? this.options.relay).delegateMulti(request);
472
+ return {
473
+ mode: result.mode,
474
+ status: result.status,
475
+ deliverable: result.deliverable,
476
+ outputDigest: result.outputDigest,
477
+ taskIds: result.taskIds,
478
+ winners: result.winners,
479
+ cancelled: result.cancelled,
480
+ auditBundles: result.receipts.map((receipt) => ({
481
+ workerDid: receipt.workerDid,
482
+ taskId: receipt.taskId,
483
+ outputDigest: receipt.outputDigest,
484
+ valid: verifyAudit(receipt.bundle).valid,
485
+ count: receipt.bundle.entries.length,
486
+ head: receipt.bundle.checkpoint.actor.payload.head,
487
+ })),
488
+ };
489
+ }
490
+ relayList(explicit, inviteUrls) {
491
+ const urls = [...explicit, ...inviteUrls];
492
+ if (typeof this.options.relay === 'string')
493
+ urls.push(this.options.relay);
494
+ return urls;
495
+ }
496
+ verifyBundle(args) {
497
+ const bundle = args.bundle;
498
+ if (!bundle || typeof bundle !== 'object' || Array.isArray(bundle))
499
+ throw new RpcError(-32602, 'Invalid params');
500
+ const options = {};
501
+ let anchored = false;
502
+ if (args.anchor !== undefined) {
503
+ if (!args.anchor || typeof args.anchor !== 'object' || Array.isArray(args.anchor))
504
+ throw new RpcError(-32602, 'Invalid params');
505
+ const anchor = args.anchor;
506
+ // The legacy anchor object still pins taskId and actors, as before.
507
+ options.trustAnchor = anchor;
508
+ options.taskId = typeof anchor.taskId === 'string' ? anchor.taskId : '';
509
+ options.actors =
510
+ Array.isArray(anchor.actors) && anchor.actors.length === 2
511
+ ? anchor.actors
512
+ : ['', ''];
513
+ if (typeof anchor.head === 'string')
514
+ options.head = anchor.head;
515
+ anchored = true;
516
+ }
517
+ if (args.expectedFinalState !== undefined) {
518
+ if (typeof args.expectedFinalState !== 'string' || args.expectedFinalState.length === 0)
519
+ throw new RpcError(-32602, 'Invalid params');
520
+ options.expectedFinalState = args.expectedFinalState;
521
+ anchored = true;
522
+ }
523
+ if (args.expectedLastEntryHash !== undefined) {
524
+ if (typeof args.expectedLastEntryHash !== 'string' || args.expectedLastEntryHash.length === 0)
525
+ throw new RpcError(-32602, 'Invalid params');
526
+ options.expectedLastEntryHash = args.expectedLastEntryHash;
527
+ anchored = true;
528
+ }
529
+ if (args.expectedEntryCount !== undefined) {
530
+ if (typeof args.expectedEntryCount !== 'number' ||
531
+ !Number.isSafeInteger(args.expectedEntryCount) ||
532
+ args.expectedEntryCount < 0)
533
+ throw new RpcError(-32602, 'Invalid params');
534
+ options.expectedEntryCount = args.expectedEntryCount;
535
+ anchored = true;
536
+ }
537
+ const report = verifyAudit(bundle, anchored ? options : undefined);
538
+ return report;
539
+ }
540
+ createInvite(args) {
541
+ if (typeof args.relayUrl !== 'string')
542
+ throw new RpcError(-32602, 'Invalid params');
543
+ const agentDid = typeof args.agentDid === 'string' ? args.agentDid : this.options.owner.did;
544
+ const card = createSignedAgentCard({
545
+ name: typeof args.name === 'string' ? args.name : (this.options.name ?? 'Sealbridge Worker'),
546
+ agentDid,
547
+ relayEndpoint: args.relayUrl,
548
+ skills: [{ id: 'json-task', name: 'JSON task' }],
549
+ }, this.options.owner, agentDid === this.options.owner.did ? undefined : this.options.agentKey);
550
+ const inviteOptions = {
551
+ agentCard: card,
552
+ };
553
+ if (args.expiresInSeconds !== undefined) {
554
+ if (typeof args.expiresInSeconds !== 'number')
555
+ throw new Error('INVALID_INVITE_EXPIRY');
556
+ inviteOptions.expiresInSeconds = args.expiresInSeconds;
557
+ }
558
+ return {
559
+ inviteToken: createInviteToken(inviteOptions, this.options.owner),
560
+ };
561
+ }
562
+ revokeToken(args) {
563
+ if ((args.targetType !== 'invite' && args.targetType !== 'capability') ||
564
+ typeof args.targetId !== 'string' ||
565
+ typeof args.reason !== 'string')
566
+ throw new RpcError(-32602, 'Invalid params');
567
+ const notice = createRevocationNotice({ targetType: args.targetType, targetId: args.targetId, reason: args.reason }, this.options.owner);
568
+ this.revocation.revoke(notice);
569
+ return { notice: notice };
570
+ }
571
+ async discoverWorkers(args) {
572
+ if (args.relayUrl !== undefined && typeof args.relayUrl !== 'string')
573
+ throw new RpcError(-32602, 'Invalid params');
574
+ const explicitRelay = typeof args.relayUrl === 'string' ? args.relayUrl : undefined;
575
+ this.assertRelays(explicitRelay ? [explicitRelay] : [], []);
576
+ const selected = explicitRelay ?? this.options.relay;
577
+ if (!selected)
578
+ throw new Error('RELAY_REQUIRED');
579
+ if (typeof selected === 'string')
580
+ validateRelayEndpointUrl(selected, { allowPrivateRelay: this.options.allowPrivateRelay });
581
+ const transport = typeof selected === 'string'
582
+ ? new HttpRelayClient(selected, { allowPrivateRelay: this.options.allowPrivateRelay })
583
+ : selected;
584
+ if (!transport.discover)
585
+ throw new Error('DISCOVERY_UNAVAILABLE');
586
+ const rawCards = await transport.discover();
587
+ if (!Array.isArray(rawCards))
588
+ throw new Error('INVALID_AGENT_CARD');
589
+ const now = Date.now();
590
+ const cards = rawCards.filter((card) => verifySignedAgentCard(card, now));
591
+ return {
592
+ relayUrl: typeof selected === 'string' ? selected : null,
593
+ count: cards.length,
594
+ workers: cards.map((card) => {
595
+ const worker = {
596
+ name: card.payload.name,
597
+ description: card.payload.description,
598
+ ownerDid: card.payload.ownerDid,
599
+ agentDid: card.payload.agentDid,
600
+ relayEndpoint: card.payload.relayEndpoint,
601
+ expiresAt: card.payload.expiresAt,
602
+ version: card.payload.version,
603
+ costModes: card.payload.costModes,
604
+ skills: card.payload.skills.map((skill) => {
605
+ const item = { id: skill.id, name: skill.name };
606
+ if (typeof skill.description === 'string')
607
+ item.description = skill.description;
608
+ return item;
609
+ }),
610
+ signerDid: card.signature.signerDid,
611
+ signedAt: card.signature.signedAt,
612
+ signature: card.signature,
613
+ };
614
+ if (card.payload.agentDelegation)
615
+ worker.agentDelegation = card.payload.agentDelegation;
616
+ return worker;
617
+ }),
618
+ };
619
+ }
620
+ inspectRevocations(args) {
621
+ const hasType = args.targetType !== undefined;
622
+ const hasId = args.targetId !== undefined;
623
+ if (hasId && !hasType)
624
+ throw new RpcError(-32602, 'Invalid params');
625
+ if (hasType && args.targetType !== 'invite' && args.targetType !== 'capability')
626
+ throw new RpcError(-32602, 'Invalid params');
627
+ if (hasId && (typeof args.targetId !== 'string' || args.targetId.length === 0))
628
+ throw new RpcError(-32602, 'Invalid params');
629
+ const targetType = hasType ? args.targetType : undefined;
630
+ const targetId = hasId ? args.targetId : undefined;
631
+ const notices = this.revocation.exportNotices().filter((notice) => {
632
+ if (targetType && notice.payload.targetType !== targetType)
633
+ return false;
634
+ if (targetId !== undefined && notice.payload.targetId !== targetId)
635
+ return false;
636
+ return true;
637
+ });
638
+ const result = {
639
+ count: notices.length,
640
+ notices: notices,
641
+ };
642
+ if (targetType && targetId !== undefined)
643
+ result.revoked = this.revocation.isRevoked(targetType, targetId);
644
+ return result;
645
+ }
646
+ }
647
+ //# sourceMappingURL=mcp-server.js.map