@hai.ai/jacs 0.6.0 → 0.8.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.
package/mcp.js CHANGED
@@ -1,141 +1,61 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.JACSTransportProxy = void 0;
7
4
  exports.createJACSTransportProxy = createJACSTransportProxy;
8
5
  exports.createJACSTransportProxyAsync = createJACSTransportProxyAsync;
9
- const index_js_1 = __importDefault(require("./index.js"));
10
- // Add near the top, after imports:
6
+ exports.getJacsMcpToolDefinitions = getJacsMcpToolDefinitions;
7
+ exports.handleJacsMcpToolCall = handleJacsMcpToolCall;
8
+ exports.registerJacsTools = registerJacsTools;
9
+ const index_js_1 = require("./index.js");
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
11
13
  const isStdioTransport = (transport) => {
12
14
  return transport.constructor.name === 'StdioServerTransport' ||
13
15
  transport.constructor.name === 'StdioClientTransport';
14
16
  };
15
- let enableDiagnosticLogging = false;
16
- function jacslog(...args) {
17
- console.error(...args);
17
+ function debugLog(proxyId, enabled, ...args) {
18
+ if (enabled)
19
+ console.error(`[${proxyId}]`, ...args);
18
20
  }
19
- // Load JACS config only once
20
- let jacsLoaded = false;
21
- let jacsLoadError = null;
22
- async function ensureJacsLoaded(configPath) {
23
- if (jacsLoaded)
24
- return;
25
- if (jacsLoadError)
26
- throw jacsLoadError;
27
- try {
28
- jacslog(`ensureJacsLoaded: Attempting to load JACS config from: ${configPath}`);
29
- jacsLoadError = null;
30
- await index_js_1.default.load(configPath);
31
- jacsLoaded = true;
32
- jacslog(`ensureJacsLoaded: JACS agent loaded successfully from ${configPath}.`);
21
+ /**
22
+ * Extract the native JacsAgent from either a JacsAgent or JacsClient instance.
23
+ * JacsClient stores its native agent in a private `agent` field.
24
+ */
25
+ function extractNativeAgent(clientOrAgent) {
26
+ if (clientOrAgent instanceof index_js_1.JacsAgent) {
27
+ return clientOrAgent;
33
28
  }
34
- catch (error) {
35
- jacsLoadError = error;
36
- console.error(`ensureJacsLoaded: CRITICAL: Failed to load JACS config from '${configPath}'. Error:`, jacsLoadError.message);
37
- throw jacsLoadError;
29
+ // JacsClient - access the private native agent at runtime
30
+ const native = clientOrAgent.agent;
31
+ if (!native) {
32
+ throw new Error('JacsClient has no loaded agent. Call quickstart(), ephemeral(), load(), or create() before wrapping with JACSTransportProxy.');
38
33
  }
34
+ return native;
39
35
  }
36
+ // ---------------------------------------------------------------------------
37
+ // JACSTransportProxy
38
+ // ---------------------------------------------------------------------------
40
39
  /**
41
- * JACS Transport Proxy - Wraps any transport with JACS encryption
40
+ * JACS Transport Proxy - Wraps any MCP transport with JACS signing/verification.
42
41
  *
43
- * This proxy sits between the MCP SDK and the actual transport,
44
- * intercepting serialized JSON strings (not JSON-RPC objects)
42
+ * Outgoing messages are signed with `signRequest()`.
43
+ * Incoming messages are verified with `verifyResponse()`, falling back to
44
+ * plain JSON if verification fails (the message was not JACS-signed).
45
45
  */
46
46
  class JACSTransportProxy {
47
- constructor(transport, role, jacsConfigPath) {
47
+ constructor(transport, clientOrAgent, role = "server") {
48
48
  this.transport = transport;
49
- this.jacsConfigPath = jacsConfigPath;
50
- this.jacsOperational = true;
49
+ this.nativeAgent = extractNativeAgent(clientOrAgent);
51
50
  this.proxyId = `JACS_${role.toUpperCase()}_PROXY`;
52
- // Disable JACS debugging for STDIO transports to prevent stdout contamination
53
51
  const suppressDebugForStdio = isStdioTransport(transport);
54
- const enableDiagnosticLogging = process.env.JACS_MCP_DEBUG === 'true' && !suppressDebugForStdio;
55
- if (suppressDebugForStdio) {
56
- console.error(`[${this.proxyId}] STDIO transport detected, suppressing debug output`);
57
- }
58
- jacslog(`[${this.proxyId}] CONSTRUCTOR: Wrapping transport with JACS. Config: ${jacsConfigPath}`);
59
- if (jacsConfigPath) {
60
- ensureJacsLoaded(jacsConfigPath)
61
- .then(() => {
62
- this.jacsOperational = true;
63
- jacslog(`[${this.proxyId}] JACS Loaded and operational.`);
64
- })
65
- .catch(err => {
66
- this.jacsOperational = false;
67
- console.error(`[${this.proxyId}] JACS Load FAILED:`, err.message);
68
- });
69
- }
70
- else {
71
- this.jacsOperational = false;
72
- console.warn(`[${this.proxyId}] No JACS config provided. Operating in passthrough mode.`);
73
- }
74
- // Intercept incoming messages from the transport
75
- this.transport.onmessage = async (incomingData) => {
76
- const logPrefix = `[${this.proxyId}] INCOMING`;
77
- try {
78
- let messageForSDK;
79
- if (typeof incomingData === 'string') {
80
- if (enableDiagnosticLogging)
81
- jacslog(`${logPrefix}: Received string from transport (len ${incomingData.length}): ${incomingData.substring(0, 100)}...`);
82
- if (this.jacsOperational) {
83
- // Try to decrypt/verify the string as a JACS artifact
84
- try {
85
- if (enableDiagnosticLogging)
86
- jacslog(`${logPrefix}: Attempting JACS verification of string...`);
87
- const verificationResult = await index_js_1.default.verifyResponse(incomingData);
88
- let decryptedMessage;
89
- if (verificationResult && typeof verificationResult === 'object' && 'payload' in verificationResult) {
90
- decryptedMessage = verificationResult.payload;
91
- }
92
- else {
93
- decryptedMessage = verificationResult;
94
- }
95
- if (enableDiagnosticLogging)
96
- jacslog(`${logPrefix}: JACS verification successful. Decrypted message: ${JSON.stringify(decryptedMessage).substring(0, 100)}...`);
97
- messageForSDK = decryptedMessage;
98
- }
99
- catch (jacsError) {
100
- // Not a JACS artifact, treat as plain JSON
101
- const errorMessage = jacsError instanceof Error ? jacsError.message : "Unknown JACS error";
102
- if (enableDiagnosticLogging)
103
- jacslog(`${logPrefix}: Not a JACS artifact, parsing as plain JSON. JACS error was: ${errorMessage}`);
104
- messageForSDK = JSON.parse(incomingData);
105
- }
106
- }
107
- else {
108
- // JACS not operational, parse as plain JSON
109
- if (enableDiagnosticLogging)
110
- jacslog(`${logPrefix}: JACS not operational, parsing as plain JSON.`);
111
- messageForSDK = JSON.parse(incomingData);
112
- }
113
- }
114
- else if (typeof incomingData === 'object' && incomingData !== null && 'jsonrpc' in incomingData) {
115
- if (enableDiagnosticLogging)
116
- jacslog(`${logPrefix}: Received object from transport, using as-is.`);
117
- messageForSDK = incomingData;
118
- }
119
- else {
120
- console.error(`${logPrefix}: Unexpected data type from transport:`, typeof incomingData);
121
- throw new Error("Invalid data type from transport");
122
- }
123
- if (enableDiagnosticLogging)
124
- jacslog(`${logPrefix}: Passing to MCP SDK: ${JSON.stringify(messageForSDK).substring(0, 100)}...`);
125
- // Pass the clean JSON-RPC message to the MCP SDK
126
- if (this.onmessage) {
127
- this.onmessage(messageForSDK);
128
- }
129
- }
130
- catch (error) {
131
- console.error(`${logPrefix}: Error processing incoming message:`, error);
132
- if (this.onerror)
133
- this.onerror(error);
134
- }
52
+ this.debug = process.env.JACS_MCP_DEBUG === 'true' && !suppressDebugForStdio;
53
+ // Intercept incoming messages from the wrapped transport
54
+ this.transport.onmessage = (incomingData) => {
55
+ this.handleIncoming(incomingData);
135
56
  };
136
- // Forward transport events
57
+ // Forward transport lifecycle events
137
58
  this.transport.onclose = () => {
138
- jacslog(`[${this.proxyId}] Transport closed.`);
139
59
  if (this.onclose)
140
60
  this.onclose();
141
61
  };
@@ -144,344 +64,86 @@ class JACSTransportProxy {
144
64
  if (this.onerror)
145
65
  this.onerror(error);
146
66
  };
147
- jacslog(`[${this.proxyId}] CONSTRUCTOR: Transport proxy initialized.`);
148
- if ('send' in this.transport && typeof this.transport.send === 'function') {
149
- const originalSend = this.transport.send.bind(this.transport);
150
- this.transport.send = async (data) => {
151
- if (typeof data === 'string') {
152
- // Check if this is a server-side SSE transport
153
- const sseTransport = this.transport;
154
- if (sseTransport._sseResponse) {
155
- // Server-side: write directly to SSE stream
156
- sseTransport._sseResponse.write(`event: message\ndata: ${data}\n\n`);
157
- return;
158
- }
159
- else if (sseTransport._endpoint) {
160
- // Client-side: use fetch (existing code)
161
- const headers = await (sseTransport._commonHeaders?.() || Promise.resolve({}));
162
- const response = await fetch(sseTransport._endpoint, {
163
- method: "POST",
164
- headers: {
165
- ...headers,
166
- "content-type": "application/json",
167
- },
168
- body: data, // Send raw string without JSON.stringify()
169
- });
170
- if (!response.ok) {
171
- const text = await response.text().catch(() => null);
172
- throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
173
- }
174
- return;
175
- }
176
- }
177
- return originalSend(data);
178
- };
179
- }
180
- // Replace the client monkey patch section in the constructor with this:
181
- if (role === "client") {
182
- jacslog(`[${this.proxyId}] Setting up EventSource interception for client...`);
183
- // Wait for the transport to be initialized, then intercept its EventSource
184
- setTimeout(() => {
185
- const sseTransport = this.transport;
186
- if (sseTransport._eventSource) {
187
- jacslog(`[${this.proxyId}] Found EventSource, intercepting onmessage...`);
188
- const originalOnMessage = sseTransport._eventSource.onmessage;
189
- sseTransport._eventSource.onmessage = async (event) => {
190
- jacslog(`[${this.proxyId}] EventSource received message:`, event.data?.substring(0, 100));
191
- try {
192
- // Try JACS verification first
193
- if (this.jacsOperational) {
194
- const verificationResult = await index_js_1.default.verifyResponse(event.data);
195
- let decryptedMessage;
196
- if (verificationResult && typeof verificationResult === 'object' && 'payload' in verificationResult) {
197
- decryptedMessage = verificationResult.payload;
198
- }
199
- else {
200
- decryptedMessage = verificationResult;
201
- }
202
- // Clean up JACS-added null values before passing to MCP SDK
203
- const cleanedMessage = this.removeNullValues(decryptedMessage);
204
- jacslog(`[${this.proxyId}] JACS verification successful, passing decrypted message to MCP SDK`);
205
- const newEvent = new MessageEvent('message', {
206
- data: JSON.stringify(cleanedMessage)
207
- });
208
- originalOnMessage.call(sseTransport._eventSource, newEvent);
209
- return;
210
- }
211
- }
212
- catch (jacsError) {
213
- jacslog(`[${this.proxyId}] Not a JACS artifact, passing original message to MCP SDK`);
214
- }
215
- // Not JACS or JACS failed, use original handler
216
- originalOnMessage.call(sseTransport._eventSource, event);
217
- };
218
- }
219
- else {
220
- jacslog(`[${this.proxyId}] EventSource not found, will retry...`);
221
- // Retry after transport is fully initialized
222
- setTimeout(() => {
223
- if (this.transport._eventSource) {
224
- jacslog(`[${this.proxyId}] Found EventSource on retry, intercepting...`);
225
- // Same logic as above
226
- }
227
- }, 100);
228
- }
229
- }, 50);
230
- }
231
67
  }
68
+ // -------------------------------------------------------------------------
69
+ // Transport interface
70
+ // -------------------------------------------------------------------------
232
71
  async start() {
233
- jacslog(`[${this.proxyId}] Starting underlying transport...`);
234
72
  return this.transport.start();
235
73
  }
236
74
  async close() {
237
- jacslog(`[${this.proxyId}] Closing underlying transport...`);
238
75
  return this.transport.close();
239
76
  }
240
- // Intercept outgoing messages to the transport
241
77
  async send(message) {
242
- const logPrefix = `[${this.proxyId}] OUTGOING`;
78
+ // Skip signing for error responses
79
+ if ('error' in message) {
80
+ debugLog(this.proxyId, this.debug, 'OUTGOING: error response, skipping signing');
81
+ await this.transport.send(message);
82
+ return;
83
+ }
243
84
  try {
244
- if (enableDiagnosticLogging)
245
- jacslog(`${logPrefix}: MCP SDK sending message: ${JSON.stringify(message).substring(0, 100)}...`);
246
- if (this.jacsOperational) {
247
- // Skip JACS for error responses
248
- if ('error' in message) {
249
- if (enableDiagnosticLogging)
250
- jacslog(`${logPrefix}: Error response, skipping JACS encryption.`);
251
- await this.transport.send(message);
252
- }
253
- else {
254
- try {
255
- if (enableDiagnosticLogging)
256
- jacslog(`${logPrefix}: Applying JACS encryption to message...`);
257
- // Clean up the message before JACS signing - remove null params
258
- const cleanMessage = { ...message };
259
- if ('params' in cleanMessage && cleanMessage.params === null) {
260
- delete cleanMessage.params;
261
- }
262
- const jacsArtifact = await index_js_1.default.signRequest(cleanMessage);
263
- await this.transport.send(jacsArtifact);
264
- }
265
- catch (jacsError) {
266
- console.error(`${logPrefix}: JACS encryption failed, sending plain message. Error:`, jacsError);
267
- await this.transport.send(message);
268
- }
269
- }
270
- }
271
- else {
272
- if (enableDiagnosticLogging)
273
- jacslog(`${logPrefix}: JACS not operational, sending plain message.`);
274
- await this.transport.send(message);
85
+ // Clean null params before signing (MCP SDK sometimes sends null params)
86
+ const cleanMessage = { ...message };
87
+ if ('params' in cleanMessage && cleanMessage.params === null) {
88
+ delete cleanMessage.params;
275
89
  }
276
- if (enableDiagnosticLogging)
277
- jacslog(`${logPrefix}: Successfully sent to transport.`);
90
+ debugLog(this.proxyId, this.debug, 'OUTGOING: signing message');
91
+ const signed = this.nativeAgent.signRequest(cleanMessage);
92
+ await this.transport.send(signed);
278
93
  }
279
- catch (error) {
280
- console.error(`${logPrefix}: Error sending message:`, error);
281
- throw error;
94
+ catch (signError) {
95
+ console.error(`[${this.proxyId}] Signing failed, sending plain message:`, signError);
96
+ await this.transport.send(message);
282
97
  }
283
98
  }
284
- // Forward transport properties
285
99
  get sessionId() {
286
100
  return this.transport.sessionId;
287
101
  }
288
- // Handle HTTP POST for SSE transports (if applicable)
289
- /**
290
- * REQUIRED for SSE (Server-Sent Events) transport pattern in MCP.
291
- *
292
- * WHY THIS EXISTS:
293
- * SSE is inherently unidirectional (server→client), but MCP requires bidirectional communication.
294
- * The MCP SSE implementation solves this with a hybrid approach:
295
- * - Server→Client: Uses SSE stream for real-time messages
296
- * - Client→Server: Uses HTTP POST to a specific endpoint
297
- *
298
- * This function intercepts those client POST requests, decrypts JACS payloads,
299
- * and forwards the decrypted messages to the underlying SSE transport handler.
300
- *
301
- * Without this, JACS-encrypted client messages would never reach the MCP server.
302
- */
303
- async handlePostMessage(req, res, rawBodyString) {
304
- const logPrefix = `[${this.proxyId}] HTTP_POST`;
305
- // Verify the underlying transport actually supports POST handling
306
- // (not all MCP transports do - only SSE transports need this)
307
- if (!('handlePostMessage' in this.transport) || typeof this.transport.handlePostMessage !== 'function') {
308
- console.error(`${logPrefix}: Underlying transport does not support handlePostMessage`);
309
- if (!res.writableEnded)
310
- res.writeHead(500).end("Transport does not support POST handling");
311
- return;
312
- }
313
- // Extract the request body (which contains the JACS-encrypted payload)
314
- let bodyToProcess;
315
- if (rawBodyString !== undefined) {
316
- // Body already provided (likely from Express middleware)
317
- bodyToProcess = rawBodyString;
318
- }
319
- else {
320
- // Manually read the request body from the HTTP stream
321
- const bodyBuffer = [];
322
- for await (const chunk of req) {
323
- bodyBuffer.push(chunk);
324
- }
325
- bodyToProcess = Buffer.concat(bodyBuffer).toString();
326
- if (!bodyToProcess) {
327
- if (!res.writableEnded)
328
- res.writeHead(400).end("Empty body");
329
- return;
330
- }
331
- }
332
- if (enableDiagnosticLogging)
333
- jacslog(`${logPrefix}: Raw body (len ${bodyToProcess.length}): ${bodyToProcess.substring(0, 100)}...`);
334
- // Add this debug line before calling jacs.verifyResponse:
335
- jacslog(`${logPrefix}: JACS Debug - Body type: ${typeof bodyToProcess}`);
336
- jacslog(`${logPrefix}: JACS Debug - First 200 chars:`, JSON.stringify(bodyToProcess.substring(0, 200)));
337
- jacslog(`${logPrefix}: JACS Debug - Is valid JSON?`, (() => {
338
- try {
339
- JSON.parse(bodyToProcess);
340
- return true;
341
- }
342
- catch {
343
- return false;
344
- }
345
- })());
346
- try {
347
- let processedBody = bodyToProcess;
348
- if (this.jacsOperational) {
349
- // Try normalizing the JSON string before JACS verification:
350
- try {
351
- // First, try to parse and re-stringify to normalize
352
- const parsedJson = JSON.parse(bodyToProcess);
353
- const normalizedJsonString = JSON.stringify(parsedJson);
354
- if (enableDiagnosticLogging)
355
- jacslog(`${logPrefix}: Attempting JACS verification with normalized JSON...`);
356
- const verificationResult = await index_js_1.default.verifyResponse(normalizedJsonString);
357
- let decryptedMessage;
358
- if (verificationResult && typeof verificationResult === 'object' && 'payload' in verificationResult) {
359
- decryptedMessage = verificationResult.payload;
360
- }
361
- else {
362
- decryptedMessage = verificationResult;
363
- }
364
- // Clean up JACS-added null params before passing to MCP SDK
365
- if ('params' in decryptedMessage && decryptedMessage.params === null) {
366
- const cleanMessage = { ...decryptedMessage };
367
- delete cleanMessage.params;
368
- processedBody = JSON.stringify(cleanMessage);
369
- }
370
- else {
371
- processedBody = JSON.stringify(decryptedMessage);
372
- }
373
- if (enableDiagnosticLogging)
374
- jacslog(`${logPrefix}: JACS verification successful. Decrypted to: ${processedBody.substring(0, 100)}...`);
375
- }
376
- catch (parseError) {
377
- // If it's not valid JSON, try with original string
378
- if (enableDiagnosticLogging)
379
- jacslog(`${logPrefix}: JSON normalization failed, trying original string...`);
380
- const verificationResult = await index_js_1.default.verifyResponse(bodyToProcess);
381
- let decryptedMessage;
382
- if (verificationResult && typeof verificationResult === 'object' && 'payload' in verificationResult) {
383
- decryptedMessage = verificationResult.payload;
384
- }
385
- else {
386
- decryptedMessage = verificationResult;
387
- }
388
- // Clean up JACS-added null params before passing to MCP SDK
389
- if ('params' in decryptedMessage && decryptedMessage.params === null) {
390
- const cleanMessage = { ...decryptedMessage };
391
- delete cleanMessage.params;
392
- processedBody = JSON.stringify(cleanMessage);
393
- }
394
- else {
395
- processedBody = JSON.stringify(decryptedMessage);
396
- }
397
- if (enableDiagnosticLogging)
398
- jacslog(`${logPrefix}: JACS verification successful. Decrypted to: ${processedBody.substring(0, 100)}...`);
399
- }
400
- }
401
- // Forward to underlying transport's POST handler
402
- await this.transport.handlePostMessage(req, res, processedBody);
403
- }
404
- catch (error) {
405
- console.error(`${logPrefix}: Error processing POST:`, error);
406
- if (!res.writableEnded) {
407
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
408
- res.writeHead(500).end(`Error: ${errorMessage}`);
409
- }
410
- }
411
- }
412
- async handleIncomingMessage(incomingData) {
413
- const logPrefix = `[${this.proxyId}] INCOMING`;
102
+ // -------------------------------------------------------------------------
103
+ // Internal
104
+ // -------------------------------------------------------------------------
105
+ handleIncoming(incomingData) {
414
106
  try {
415
107
  let messageForSDK;
416
108
  if (typeof incomingData === 'string') {
417
- if (enableDiagnosticLogging)
418
- jacslog(`${logPrefix}: Received string from transport (len ${incomingData.length}): ${incomingData.substring(0, 100)}...`);
419
- if (this.jacsOperational) {
420
- try {
421
- if (enableDiagnosticLogging)
422
- jacslog(`${logPrefix}: Attempting JACS verification of string...`);
423
- const verificationResult = await index_js_1.default.verifyResponse(incomingData);
424
- let decryptedMessage;
425
- if (verificationResult && typeof verificationResult === 'object' && 'payload' in verificationResult) {
426
- decryptedMessage = verificationResult.payload;
427
- }
428
- else {
429
- decryptedMessage = verificationResult;
430
- }
431
- if (enableDiagnosticLogging)
432
- jacslog(`${logPrefix}: JACS verification successful. Decrypted message: ${JSON.stringify(decryptedMessage).substring(0, 100)}...`);
433
- messageForSDK = decryptedMessage;
434
- }
435
- catch (jacsError) {
436
- const errorMessage = jacsError instanceof Error ? jacsError.message : "Unknown JACS error";
437
- if (enableDiagnosticLogging)
438
- jacslog(`${logPrefix}: Not a JACS artifact, parsing as plain JSON. JACS error was: ${errorMessage}`);
439
- messageForSDK = JSON.parse(incomingData);
440
- }
109
+ // Try JACS verification first
110
+ try {
111
+ debugLog(this.proxyId, this.debug, 'INCOMING: attempting JACS verification');
112
+ const result = this.nativeAgent.verifyResponse(incomingData);
113
+ messageForSDK = (result && typeof result === 'object' && 'payload' in result)
114
+ ? result.payload
115
+ : result;
441
116
  }
442
- else {
443
- if (enableDiagnosticLogging)
444
- jacslog(`${logPrefix}: JACS not operational, parsing as plain JSON.`);
117
+ catch {
118
+ // Not a JACS artifact, parse as plain JSON
119
+ debugLog(this.proxyId, this.debug, 'INCOMING: not a JACS artifact, parsing as plain JSON');
445
120
  messageForSDK = JSON.parse(incomingData);
446
121
  }
447
122
  }
448
123
  else if (typeof incomingData === 'object' && incomingData !== null && 'jsonrpc' in incomingData) {
449
- if (enableDiagnosticLogging)
450
- jacslog(`${logPrefix}: Received object from transport, using as-is.`);
451
124
  messageForSDK = incomingData;
452
125
  }
453
126
  else {
454
- console.error(`${logPrefix}: Unexpected data type from transport:`, typeof incomingData);
455
- throw new Error("Invalid data type from transport");
127
+ throw new Error(`Unexpected incoming data type: ${typeof incomingData}`);
456
128
  }
457
- if (enableDiagnosticLogging)
458
- jacslog(`${logPrefix}: Passing to MCP SDK: ${JSON.stringify(messageForSDK).substring(0, 100)}...`);
459
129
  if (this.onmessage) {
460
130
  this.onmessage(messageForSDK);
461
131
  }
462
132
  }
463
133
  catch (error) {
464
- console.error(`${logPrefix}: Error processing incoming message:`, error);
134
+ console.error(`[${this.proxyId}] Error processing incoming message:`, error);
465
135
  if (this.onerror)
466
136
  this.onerror(error);
467
137
  }
468
138
  }
469
139
  /**
470
- * Removes null and undefined values from JSON objects to prevent MCP schema validation failures.
471
- *
472
- * WORKAROUND for MCP JSON Schema validation issues:
473
- * - Addresses strict validators (like Anthropic's API) that reject schemas with null values
474
- * - Handles edge cases where tools have null inputSchema causing client validation errors
475
- * - Prevents "invalid_type: expected object, received undefined" errors in TypeScript SDK v1.9.0
476
- * - Cleans up malformed schemas before transmission to avoid -32602 JSON-RPC errors
477
- *
478
- * Related issues:
479
- * - https://github.com/modelcontextprotocol/typescript-sdk/issues/400 (null schema tools)
480
- * - https://github.com/anthropics/claude-code/issues/586 (Anthropic strict Draft 2020-12)
481
- * - https://github.com/agno-agi/agno/issues/2791 (missing type field)
140
+ * Removes null and undefined values from JSON objects to prevent MCP schema
141
+ * validation failures with strict validators.
482
142
  *
483
- * @param obj - The object to clean (typically MCP tool/resource schemas)
484
- * @returns A new object with all null/undefined values recursively removed
143
+ * Workaround for:
144
+ * - https://github.com/modelcontextprotocol/typescript-sdk/issues/400
145
+ * - https://github.com/anthropics/claude-code/issues/586
146
+ * - https://github.com/agno-agi/agno/issues/2791
485
147
  */
486
148
  removeNullValues(obj) {
487
149
  if (obj === null || obj === undefined)
@@ -501,14 +163,379 @@ class JACSTransportProxy {
501
163
  }
502
164
  }
503
165
  exports.JACSTransportProxy = JACSTransportProxy;
166
+ // ---------------------------------------------------------------------------
504
167
  // Factory functions
505
- function createJACSTransportProxy(transport, configPath, role) {
506
- jacslog(`Creating JACS Transport Proxy for role: ${role}`);
507
- return new JACSTransportProxy(transport, role, configPath);
168
+ // ---------------------------------------------------------------------------
169
+ /**
170
+ * Create a transport proxy from a pre-loaded JacsClient or JacsAgent.
171
+ */
172
+ function createJACSTransportProxy(transport, clientOrAgent, role = "server") {
173
+ return new JACSTransportProxy(transport, clientOrAgent, role);
174
+ }
175
+ /**
176
+ * Create a transport proxy by loading a JACS agent from a config file.
177
+ * Awaits agent loading before returning, so the proxy is immediately usable.
178
+ */
179
+ async function createJACSTransportProxyAsync(transport, configPath, role = "server") {
180
+ const agent = new index_js_1.JacsAgent();
181
+ await agent.load(configPath);
182
+ return new JACSTransportProxy(transport, agent, role);
183
+ }
184
+ /**
185
+ * Returns the full list of JACS MCP tool definitions.
186
+ *
187
+ * Use this with `server.setRequestHandler(ListToolsRequestSchema, ...)` to
188
+ * advertise JACS tools from a Node.js MCP server.
189
+ */
190
+ function getJacsMcpToolDefinitions() {
191
+ return [
192
+ {
193
+ name: 'jacs_sign_document',
194
+ description: 'Sign arbitrary JSON data with JACS cryptographic provenance.',
195
+ inputSchema: {
196
+ type: 'object',
197
+ properties: {
198
+ data: { type: 'string', description: 'JSON string of data to sign' },
199
+ },
200
+ required: ['data'],
201
+ },
202
+ },
203
+ {
204
+ name: 'jacs_verify_document',
205
+ description: 'Verify a JACS-signed document. Returns validity, signer, and errors.',
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ document: { type: 'string', description: 'The signed JSON document to verify' },
210
+ },
211
+ required: ['document'],
212
+ },
213
+ },
214
+ {
215
+ name: 'jacs_verify_by_id',
216
+ description: 'Verify a document by its storage ID (uuid:version format).',
217
+ inputSchema: {
218
+ type: 'object',
219
+ properties: {
220
+ document_id: { type: 'string', description: 'Document ID in uuid:version format' },
221
+ },
222
+ required: ['document_id'],
223
+ },
224
+ },
225
+ {
226
+ name: 'jacs_create_agreement',
227
+ description: 'Create a multi-party agreement requiring signatures from specified agents.',
228
+ inputSchema: {
229
+ type: 'object',
230
+ properties: {
231
+ document: { type: 'string', description: 'JSON string of document to agree on' },
232
+ agent_ids: { type: 'array', items: { type: 'string' }, description: 'Agent IDs who must sign' },
233
+ question: { type: 'string', description: 'Question or prompt for signers' },
234
+ timeout: { type: 'string', description: 'ISO 8601 deadline' },
235
+ quorum: { type: 'number', description: 'Minimum signatures required (M-of-N)' },
236
+ },
237
+ required: ['document', 'agent_ids'],
238
+ },
239
+ },
240
+ {
241
+ name: 'jacs_sign_agreement',
242
+ description: 'Sign an existing multi-party agreement.',
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: {
246
+ document: { type: 'string', description: 'The agreement document to sign' },
247
+ },
248
+ required: ['document'],
249
+ },
250
+ },
251
+ {
252
+ name: 'jacs_check_agreement',
253
+ description: 'Check the status of a multi-party agreement.',
254
+ inputSchema: {
255
+ type: 'object',
256
+ properties: {
257
+ document: { type: 'string', description: 'The agreement document to check' },
258
+ },
259
+ required: ['document'],
260
+ },
261
+ },
262
+ {
263
+ name: 'jacs_audit',
264
+ description: 'Run a JACS security audit on documents and keys.',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {
268
+ recent_n: { type: 'number', description: 'Number of recent documents to audit' },
269
+ },
270
+ },
271
+ },
272
+ {
273
+ name: 'jacs_sign_file',
274
+ description: 'Sign a file with JACS. Optionally embed the file content.',
275
+ inputSchema: {
276
+ type: 'object',
277
+ properties: {
278
+ file_path: { type: 'string', description: 'Path to the file to sign' },
279
+ embed: { type: 'boolean', description: 'Embed file content in the document (default false)' },
280
+ },
281
+ required: ['file_path'],
282
+ },
283
+ },
284
+ {
285
+ name: 'jacs_verify_self',
286
+ description: "Verify this agent's own cryptographic integrity.",
287
+ inputSchema: { type: 'object', properties: {} },
288
+ },
289
+ {
290
+ name: 'jacs_agent_info',
291
+ description: 'Get the current agent ID, name, and diagnostics.',
292
+ inputSchema: { type: 'object', properties: {} },
293
+ },
294
+ {
295
+ name: 'fetch_agent_key',
296
+ description: "Fetch an agent's public key from HAI's key distribution service.",
297
+ inputSchema: {
298
+ type: 'object',
299
+ properties: {
300
+ agent_id: { type: 'string', description: 'Agent ID (UUID format)' },
301
+ version: { type: 'string', description: "Key version or 'latest'" },
302
+ },
303
+ required: ['agent_id'],
304
+ },
305
+ },
306
+ {
307
+ name: 'jacs_register',
308
+ description: 'Register this agent with HAI.ai for cross-organization key discovery.',
309
+ inputSchema: {
310
+ type: 'object',
311
+ properties: {
312
+ api_key: { type: 'string', description: 'HAI API key (optional, uses env if not set)' },
313
+ preview: { type: 'boolean', description: 'Preview mode (default true)' },
314
+ },
315
+ },
316
+ },
317
+ {
318
+ name: 'jacs_setup_instructions',
319
+ description: 'Get DNS and well-known setup instructions for a domain.',
320
+ inputSchema: {
321
+ type: 'object',
322
+ properties: {
323
+ domain: { type: 'string', description: 'Domain name for setup' },
324
+ },
325
+ required: ['domain'],
326
+ },
327
+ },
328
+ {
329
+ name: 'jacs_trust_agent',
330
+ description: 'Add an agent to the local trust store.',
331
+ inputSchema: {
332
+ type: 'object',
333
+ properties: {
334
+ agent_json: { type: 'string', description: 'Agent JSON document to trust' },
335
+ },
336
+ required: ['agent_json'],
337
+ },
338
+ },
339
+ {
340
+ name: 'jacs_list_trusted',
341
+ description: 'List all agent IDs in the local trust store.',
342
+ inputSchema: { type: 'object', properties: {} },
343
+ },
344
+ {
345
+ name: 'jacs_is_trusted',
346
+ description: 'Check if a specific agent is in the local trust store.',
347
+ inputSchema: {
348
+ type: 'object',
349
+ properties: {
350
+ agent_id: { type: 'string', description: 'Agent ID to check' },
351
+ },
352
+ required: ['agent_id'],
353
+ },
354
+ },
355
+ {
356
+ name: 'jacs_reencrypt_key',
357
+ description: 'Re-encrypt the private key with a new password.',
358
+ inputSchema: {
359
+ type: 'object',
360
+ properties: {
361
+ old_password: { type: 'string', description: 'Current password' },
362
+ new_password: { type: 'string', description: 'New password' },
363
+ },
364
+ required: ['old_password', 'new_password'],
365
+ },
366
+ },
367
+ ];
508
368
  }
509
- async function createJACSTransportProxyAsync(transport, configPath, role) {
510
- jacslog(`Creating JACS Transport Proxy (async) for role: ${role}`);
511
- await ensureJacsLoaded(configPath);
512
- return new JACSTransportProxy(transport, role, configPath);
369
+ /**
370
+ * Handle a JACS MCP tool call. Returns a JSON string result.
371
+ *
372
+ * Use this with `server.setRequestHandler(CallToolRequestSchema, ...)`.
373
+ */
374
+ async function handleJacsMcpToolCall(client, toolName, args) {
375
+ const text = (s) => ({ content: [{ type: 'text', text: s }] });
376
+ try {
377
+ switch (toolName) {
378
+ case 'jacs_sign_document': {
379
+ const data = JSON.parse(args.data);
380
+ const signed = await client.signMessage(data);
381
+ return text(JSON.stringify({
382
+ success: true, documentId: signed.documentId,
383
+ agentId: signed.agentId, timestamp: signed.timestamp,
384
+ raw: signed.raw,
385
+ }));
386
+ }
387
+ case 'jacs_verify_document': {
388
+ const result = await client.verify(args.document);
389
+ return text(JSON.stringify({
390
+ success: result.valid, valid: result.valid,
391
+ signerId: result.signerId, timestamp: result.timestamp,
392
+ data: result.data, errors: result.errors,
393
+ }));
394
+ }
395
+ case 'jacs_verify_by_id': {
396
+ const result = await client.verifyById(args.document_id);
397
+ return text(JSON.stringify({
398
+ success: result.valid, valid: result.valid,
399
+ errors: result.errors,
400
+ }));
401
+ }
402
+ case 'jacs_create_agreement': {
403
+ const doc = JSON.parse(args.document);
404
+ const opts = {};
405
+ if (args.question)
406
+ opts.question = args.question;
407
+ if (args.timeout)
408
+ opts.timeout = args.timeout;
409
+ if (args.quorum !== undefined)
410
+ opts.quorum = args.quorum;
411
+ const signed = await client.createAgreement(doc, args.agent_ids, opts);
412
+ return text(JSON.stringify({
413
+ success: true, documentId: signed.documentId,
414
+ agentId: signed.agentId, raw: signed.raw,
415
+ }));
416
+ }
417
+ case 'jacs_sign_agreement': {
418
+ const signed = await client.signAgreement(args.document);
419
+ return text(JSON.stringify({
420
+ success: true, documentId: signed.documentId,
421
+ agentId: signed.agentId, raw: signed.raw,
422
+ }));
423
+ }
424
+ case 'jacs_check_agreement': {
425
+ const status = await client.checkAgreement(args.document);
426
+ return text(JSON.stringify({ success: true, ...status }));
427
+ }
428
+ case 'jacs_audit': {
429
+ const result = await client.audit(args.recent_n !== undefined ? { recentN: args.recent_n } : undefined);
430
+ return text(JSON.stringify({ success: true, ...result }));
431
+ }
432
+ case 'jacs_sign_file': {
433
+ const signed = await client.signFile(args.file_path, args.embed || false);
434
+ return text(JSON.stringify({
435
+ success: true, documentId: signed.documentId,
436
+ agentId: signed.agentId, raw: signed.raw,
437
+ }));
438
+ }
439
+ case 'jacs_verify_self': {
440
+ const result = await client.verifySelf();
441
+ return text(JSON.stringify({
442
+ success: result.valid, valid: result.valid,
443
+ signerId: result.signerId, errors: result.errors,
444
+ }));
445
+ }
446
+ case 'jacs_agent_info': {
447
+ const nativeAgent = extractNativeAgent(client);
448
+ let diagnostics = {};
449
+ try {
450
+ diagnostics = JSON.parse(nativeAgent.diagnostics());
451
+ }
452
+ catch { /* ok */ }
453
+ return text(JSON.stringify({
454
+ agentId: client.agentId, name: client.name,
455
+ strict: client.strict, diagnostics,
456
+ }));
457
+ }
458
+ case 'fetch_agent_key': {
459
+ const keyInfo = (0, index_js_1.fetchRemoteKey)(args.agent_id, args.version || null);
460
+ return text(JSON.stringify({
461
+ success: true, agentId: keyInfo.agentId,
462
+ version: keyInfo.version, algorithm: keyInfo.algorithm,
463
+ publicKeyHash: keyInfo.publicKeyHash,
464
+ }));
465
+ }
466
+ case 'jacs_register': {
467
+ const nativeAgent = extractNativeAgent(client);
468
+ const result = await nativeAgent.registerWithHai(args.api_key || null, null, args.preview !== false);
469
+ return text(result);
470
+ }
471
+ case 'jacs_setup_instructions': {
472
+ const nativeAgent = extractNativeAgent(client);
473
+ const result = await nativeAgent.getSetupInstructions(args.domain, null);
474
+ return text(result);
475
+ }
476
+ case 'jacs_trust_agent': {
477
+ const result = client.trustAgent(args.agent_json);
478
+ return text(JSON.stringify({ success: true, result }));
479
+ }
480
+ case 'jacs_list_trusted': {
481
+ const agents = client.listTrustedAgents();
482
+ return text(JSON.stringify({ success: true, trustedAgents: agents }));
483
+ }
484
+ case 'jacs_is_trusted': {
485
+ const trusted = client.isTrusted(args.agent_id);
486
+ return text(JSON.stringify({ agentId: args.agent_id, trusted }));
487
+ }
488
+ case 'jacs_reencrypt_key': {
489
+ const nativeAgent = extractNativeAgent(client);
490
+ await nativeAgent.reencryptKey(args.old_password, args.new_password);
491
+ return text(JSON.stringify({ success: true, message: 'Key re-encrypted' }));
492
+ }
493
+ default:
494
+ return text(JSON.stringify({ error: `Unknown tool: ${toolName}` }));
495
+ }
496
+ }
497
+ catch (err) {
498
+ return text(JSON.stringify({ success: false, error: String(err) }));
499
+ }
500
+ }
501
+ /**
502
+ * Register all JACS tools on an MCP Server instance.
503
+ *
504
+ * Call this once during server setup to add JACS signing, verification,
505
+ * agreements, trust, audit, and HAI integration tools.
506
+ *
507
+ * @example
508
+ * ```typescript
509
+ * import { Server } from '@modelcontextprotocol/sdk/server/index.js';
510
+ * import { JacsClient } from '@hai.ai/jacs/client';
511
+ * import { registerJacsTools } from '@hai.ai/jacs/mcp';
512
+ *
513
+ * const server = new Server(
514
+ * { name: 'my-server', version: '1.0.0' },
515
+ * { capabilities: { tools: {} } },
516
+ * );
517
+ * const client = await JacsClient.quickstart();
518
+ * registerJacsTools(server, client);
519
+ * ```
520
+ */
521
+ function registerJacsTools(server, client) {
522
+ // Lazy import MCP SDK schemas — only needed if registering tools
523
+ let ListToolsRequestSchema;
524
+ let CallToolRequestSchema;
525
+ try {
526
+ const types = require('@modelcontextprotocol/sdk/types.js');
527
+ ListToolsRequestSchema = types.ListToolsRequestSchema;
528
+ CallToolRequestSchema = types.CallToolRequestSchema;
529
+ }
530
+ catch {
531
+ throw new Error('@modelcontextprotocol/sdk is required for registerJacsTools. ' +
532
+ 'Install it with: npm install @modelcontextprotocol/sdk');
533
+ }
534
+ const tools = getJacsMcpToolDefinitions();
535
+ server.setRequestHandler(ListToolsRequestSchema, () => ({ tools }));
536
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
537
+ const { name, arguments: args } = request.params;
538
+ return handleJacsMcpToolCall(client, name, args || {});
539
+ });
513
540
  }
514
541
  //# sourceMappingURL=mcp.js.map