@contextvm/sdk 0.1.30-rc.0 → 0.1.30-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,13 +13,17 @@ const logger = createLogger('nostr-server-transport');
13
13
  */
14
14
  export class NostrServerTransport extends BaseNostrTransport {
15
15
  constructor(options) {
16
+ var _a, _b;
16
17
  super(options);
17
18
  this.clientSessions = new Map();
19
+ this.eventIdToClient = new Map(); // eventId -> clientPubkey
18
20
  this.isInitialized = false;
19
21
  this.serverInfo = options.serverInfo;
20
22
  this.isPublicServer = options.isPublicServer;
21
23
  this.allowedPublicKeys = options.allowedPublicKeys;
22
24
  this.excludedCapabilities = options.excludedCapabilities;
25
+ this.cleanupIntervalMs = (_a = options.cleanupIntervalMs) !== null && _a !== void 0 ? _a : 60000;
26
+ this.sessionTimeoutMs = (_b = options.sessionTimeoutMs) !== null && _b !== void 0 ? _b : 300000;
23
27
  }
24
28
  /**
25
29
  * Generates common tags from server information for use in Nostr events.
@@ -27,6 +31,9 @@ export class NostrServerTransport extends BaseNostrTransport {
27
31
  */
28
32
  generateCommonTags() {
29
33
  var _a, _b, _c, _d;
34
+ if (this.cachedCommonTags) {
35
+ return this.cachedCommonTags;
36
+ }
30
37
  const commonTags = [];
31
38
  if ((_a = this.serverInfo) === null || _a === void 0 ? void 0 : _a.name) {
32
39
  commonTags.push([NOSTR_TAGS.NAME, this.serverInfo.name]);
@@ -43,6 +50,7 @@ export class NostrServerTransport extends BaseNostrTransport {
43
50
  if (this.encryptionMode !== EncryptionMode.DISABLED) {
44
51
  commonTags.push([NOSTR_TAGS.SUPPORT_ENCRYPTION]);
45
52
  }
53
+ this.cachedCommonTags = commonTags;
46
54
  return commonTags;
47
55
  }
48
56
  /**
@@ -50,24 +58,70 @@ export class NostrServerTransport extends BaseNostrTransport {
50
58
  * to receive incoming MCP requests.
51
59
  */
52
60
  async start() {
53
- await this.connect();
54
- const pubkey = await this.getPublicKey();
55
- logger.info('Server pubkey:', pubkey);
56
- // Subscribe to events targeting this server's public key
57
- const filters = this.createSubscriptionFilters(pubkey);
58
- await this.subscribe(filters, this.processIncomingEvent.bind(this));
59
- if (this.isPublicServer) {
60
- await this.getAnnouncementData();
61
+ var _a;
62
+ try {
63
+ await this.connect();
64
+ const pubkey = await this.getPublicKey();
65
+ logger.info('Server pubkey:', pubkey);
66
+ // Subscribe to events targeting this server's public key
67
+ const filters = this.createSubscriptionFilters(pubkey);
68
+ await this.subscribe(filters, async (event) => {
69
+ var _a;
70
+ try {
71
+ await this.processIncomingEvent(event);
72
+ }
73
+ catch (error) {
74
+ logger.error('Error processing incoming event', {
75
+ error: error instanceof Error ? error.message : String(error),
76
+ stack: error instanceof Error ? error.stack : undefined,
77
+ eventId: event.id,
78
+ });
79
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
80
+ }
81
+ });
82
+ if (this.isPublicServer) {
83
+ await this.getAnnouncementData();
84
+ }
85
+ // Start periodic cleanup of inactive sessions
86
+ this.cleanupInterval = setInterval(() => {
87
+ const cleaned = this.cleanupInactiveSessions();
88
+ if (cleaned > 0) {
89
+ logger.info(`Cleaned up ${cleaned} inactive sessions`);
90
+ }
91
+ }, this.cleanupIntervalMs);
92
+ }
93
+ catch (error) {
94
+ logger.error('Error starting NostrServerTransport', {
95
+ error: error instanceof Error ? error.message : String(error),
96
+ stack: error instanceof Error ? error.stack : undefined,
97
+ });
98
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
99
+ throw error;
61
100
  }
62
101
  }
63
102
  /**
64
103
  * Closes the transport, disconnecting from the relay.
65
104
  */
66
105
  async close() {
67
- var _a;
68
- await this.disconnect();
69
- this.clientSessions.clear();
70
- (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
106
+ var _a, _b;
107
+ try {
108
+ // Clear the cleanup interval
109
+ if (this.cleanupInterval) {
110
+ clearInterval(this.cleanupInterval);
111
+ this.cleanupInterval = undefined;
112
+ }
113
+ await this.disconnect();
114
+ this.clientSessions.clear();
115
+ (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
116
+ }
117
+ catch (error) {
118
+ logger.error('Error closing NostrServerTransport', {
119
+ error: error instanceof Error ? error.message : String(error),
120
+ stack: error instanceof Error ? error.stack : undefined,
121
+ });
122
+ (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error instanceof Error ? error : new Error(String(error)));
123
+ throw error;
124
+ }
71
125
  }
72
126
  /**
73
127
  * Sends JSON-RPC messages over the Nostr transport.
@@ -80,7 +134,6 @@ export class NostrServerTransport extends BaseNostrTransport {
80
134
  await this.handleResponse(message);
81
135
  }
82
136
  else if (isJSONRPCNotification(message)) {
83
- this.cleanupInactiveSessions();
84
137
  await this.handleNotification(message);
85
138
  }
86
139
  else {
@@ -111,7 +164,17 @@ export class NostrServerTransport extends BaseNostrTransport {
111
164
  };
112
165
  // Collect events using the subscribe method with onEvent hook
113
166
  await this.relayHandler.subscribe([filter], (event) => {
114
- events.push(event);
167
+ var _a;
168
+ try {
169
+ events.push(event);
170
+ }
171
+ catch (error) {
172
+ logger.error('Error in relay subscription event collection', {
173
+ error: error instanceof Error ? error.message : String(error),
174
+ eventId: event.id,
175
+ });
176
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
177
+ }
115
178
  });
116
179
  if (!events.length) {
117
180
  logger.info(`No events found for kind ${kind} to delete`);
@@ -136,43 +199,52 @@ export class NostrServerTransport extends BaseNostrTransport {
136
199
  * the initialize request, waiting for the response, and then proceeding with other announcements.
137
200
  */
138
201
  async getAnnouncementData() {
139
- var _a, _b;
140
- const initializeParams = {
141
- protocolVersion: LATEST_PROTOCOL_VERSION,
142
- capabilities: {},
143
- clientInfo: {
144
- name: 'DummyClient',
145
- version: '1.0.0',
146
- },
147
- };
148
- // Send the initialize request if not already initialized
149
- if (!this.isInitialized) {
150
- const initializeMessage = {
151
- jsonrpc: '2.0',
152
- id: 'announcement',
153
- method: 'initialize',
154
- params: initializeParams,
155
- };
156
- logger.info('Sending initialize request for announcement');
157
- (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, initializeMessage);
158
- }
202
+ var _a, _b, _c;
159
203
  try {
160
- // Wait for initialization to complete
161
- await this.waitForInitialization();
162
- // Send all announcements now that we're initialized
163
- for (const [key, methodValue] of Object.entries(announcementMethods)) {
164
- logger.info('Sending announcement', { key, methodValue });
165
- const message = {
204
+ const initializeParams = {
205
+ protocolVersion: LATEST_PROTOCOL_VERSION,
206
+ capabilities: {},
207
+ clientInfo: {
208
+ name: 'DummyClient',
209
+ version: '1.0.0',
210
+ },
211
+ };
212
+ // Send the initialize request if not already initialized
213
+ if (!this.isInitialized) {
214
+ const initializeMessage = {
166
215
  jsonrpc: '2.0',
167
216
  id: 'announcement',
168
- method: methodValue,
169
- params: key === 'server' ? initializeParams : {},
217
+ method: 'initialize',
218
+ params: initializeParams,
170
219
  };
171
- (_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, message);
220
+ logger.info('Sending initialize request for announcement');
221
+ (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, initializeMessage);
222
+ }
223
+ try {
224
+ // Wait for initialization to complete
225
+ await this.waitForInitialization();
226
+ // Send all announcements now that we're initialized
227
+ for (const [key, methodValue] of Object.entries(announcementMethods)) {
228
+ logger.info('Sending announcement', { key, methodValue });
229
+ const message = {
230
+ jsonrpc: '2.0',
231
+ id: 'announcement',
232
+ method: methodValue,
233
+ params: key === 'server' ? initializeParams : {},
234
+ };
235
+ (_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, message);
236
+ }
237
+ }
238
+ catch (error) {
239
+ logger.warn('Server not initialized after waiting, skipping announcements', { error: error instanceof Error ? error.message : error });
172
240
  }
173
241
  }
174
242
  catch (error) {
175
- logger.warn('Server not initialized after waiting, skipping announcements', { error: error instanceof Error ? error.message : error });
243
+ logger.error('Error in getAnnouncementData', {
244
+ error: error instanceof Error ? error.message : String(error),
245
+ stack: error instanceof Error ? error.stack : undefined,
246
+ });
247
+ (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error instanceof Error ? error : new Error(String(error)));
176
248
  }
177
249
  }
178
250
  /**
@@ -181,13 +253,18 @@ export class NostrServerTransport extends BaseNostrTransport {
181
253
  * The method will always resolve, allowing announcements to proceed.
182
254
  */
183
255
  async waitForInitialization() {
184
- const startTime = Date.now();
185
- const timeoutMs = 10000; // 10 seconds timeout
186
- while (!this.isInitialized && Date.now() - startTime < timeoutMs) {
187
- await new Promise((resolve) => setTimeout(resolve, 100));
256
+ if (this.isInitialized)
257
+ return;
258
+ if (!this.initializationPromise) {
259
+ this.initializationPromise = new Promise((resolve) => {
260
+ this.initializationResolver = resolve;
261
+ });
262
+ }
263
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Initialization timeout')), 10000));
264
+ try {
265
+ await Promise.race([this.initializationPromise, timeout]);
188
266
  }
189
- // Log warning if not initialized but don't throw error
190
- if (!this.isInitialized) {
267
+ catch (_a) {
191
268
  logger.warn('Server initialization not completed within timeout, proceeding with announcements');
192
269
  }
193
270
  }
@@ -197,33 +274,43 @@ export class NostrServerTransport extends BaseNostrTransport {
197
274
  * @param message The JSON-RPC response containing the announcement data.
198
275
  */
199
276
  async announcer(message) {
200
- const recipientPubkey = await this.getPublicKey();
201
- const commonTags = this.generateCommonTags();
202
- const announcementMapping = [
203
- {
204
- schema: InitializeResultSchema,
205
- kind: SERVER_ANNOUNCEMENT_KIND,
206
- tags: commonTags,
207
- },
208
- { schema: ListToolsResultSchema, kind: TOOLS_LIST_KIND, tags: [] },
209
- {
210
- schema: ListResourcesResultSchema,
211
- kind: RESOURCES_LIST_KIND,
212
- tags: [],
213
- },
214
- {
215
- schema: ListResourceTemplatesResultSchema,
216
- kind: RESOURCETEMPLATES_LIST_KIND,
217
- tags: [],
218
- },
219
- { schema: ListPromptsResultSchema, kind: PROMPTS_LIST_KIND, tags: [] },
220
- ];
221
- for (const mapping of announcementMapping) {
222
- if (mapping.schema.safeParse(message.result).success) {
223
- await this.sendMcpMessage(message.result, recipientPubkey, mapping.kind, mapping.tags);
224
- break;
277
+ var _a;
278
+ try {
279
+ const recipientPubkey = await this.getPublicKey();
280
+ const commonTags = this.generateCommonTags();
281
+ const announcementMapping = [
282
+ {
283
+ schema: InitializeResultSchema,
284
+ kind: SERVER_ANNOUNCEMENT_KIND,
285
+ tags: commonTags,
286
+ },
287
+ { schema: ListToolsResultSchema, kind: TOOLS_LIST_KIND, tags: [] },
288
+ {
289
+ schema: ListResourcesResultSchema,
290
+ kind: RESOURCES_LIST_KIND,
291
+ tags: [],
292
+ },
293
+ {
294
+ schema: ListResourceTemplatesResultSchema,
295
+ kind: RESOURCETEMPLATES_LIST_KIND,
296
+ tags: [],
297
+ },
298
+ { schema: ListPromptsResultSchema, kind: PROMPTS_LIST_KIND, tags: [] },
299
+ ];
300
+ for (const mapping of announcementMapping) {
301
+ if (mapping.schema.safeParse(message.result).success) {
302
+ await this.sendMcpMessage(message.result, recipientPubkey, mapping.kind, mapping.tags);
303
+ break;
304
+ }
225
305
  }
226
306
  }
307
+ catch (error) {
308
+ logger.error('Error in announcer', {
309
+ error: error instanceof Error ? error.message : String(error),
310
+ stack: error instanceof Error ? error.stack : undefined,
311
+ });
312
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
313
+ }
227
314
  }
228
315
  /**
229
316
  * Gets or creates a client session with proper initialization.
@@ -240,6 +327,7 @@ export class NostrServerTransport extends BaseNostrTransport {
240
327
  isEncrypted,
241
328
  lastActivity: now,
242
329
  pendingRequests: new Map(),
330
+ eventToProgressToken: new Map(),
243
331
  };
244
332
  this.clientSessions.set(clientPubkey, newSession);
245
333
  return newSession;
@@ -253,7 +341,7 @@ export class NostrServerTransport extends BaseNostrTransport {
253
341
  * @param eventId The Nostr event ID.
254
342
  * @param request The request message.
255
343
  */
256
- handleIncomingRequest(session, eventId, request) {
344
+ handleIncomingRequest(session, eventId, request, clientPubkey) {
257
345
  var _a, _b;
258
346
  // Store the original request ID for later restoration
259
347
  const originalRequestId = request.id;
@@ -261,10 +349,13 @@ export class NostrServerTransport extends BaseNostrTransport {
261
349
  request.id = eventId;
262
350
  // Store in client session
263
351
  session.pendingRequests.set(eventId, originalRequestId);
352
+ this.eventIdToClient.set(eventId, clientPubkey);
264
353
  // Track progress tokens if provided
265
354
  const progressToken = (_b = (_a = request.params) === null || _a === void 0 ? void 0 : _a._meta) === null || _b === void 0 ? void 0 : _b.progressToken;
266
355
  if (progressToken) {
267
- session.pendingRequests.set(String(progressToken), eventId);
356
+ const tokenStr = String(progressToken);
357
+ session.pendingRequests.set(tokenStr, eventId);
358
+ session.eventToProgressToken.set(eventId, tokenStr);
268
359
  }
269
360
  }
270
361
  /**
@@ -283,48 +374,45 @@ export class NostrServerTransport extends BaseNostrTransport {
283
374
  * @param response The JSON-RPC response or error to send.
284
375
  */
285
376
  async handleResponse(response) {
286
- var _a, _b, _c;
377
+ var _a, _b, _c, _d, _e;
287
378
  // Handle special announcement responses
288
379
  if (response.id === 'announcement') {
289
380
  if (isJSONRPCResponse(response)) {
290
381
  if (InitializeResultSchema.safeParse(response.result).success) {
291
382
  this.isInitialized = true;
383
+ (_a = this.initializationResolver) === null || _a === void 0 ? void 0 : _a.call(this); // Resolve waiting promise
292
384
  // Send the initialized notification
293
385
  const initializedNotification = {
294
386
  jsonrpc: '2.0',
295
387
  method: 'notifications/initialized',
296
388
  };
297
- (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, initializedNotification);
389
+ (_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, initializedNotification);
298
390
  logger.info('Initialized');
299
391
  }
300
392
  await this.announcer(response);
301
393
  }
302
394
  return;
303
395
  }
304
- // Find the client session with this pending request
396
+ // Find the client session with this pending request using O(1) lookup
305
397
  const nostrEventId = response.id;
306
- let targetClientPubkey;
307
- let originalRequestId;
308
- for (const [clientPubkey, session] of this.clientSessions.entries()) {
309
- const originalId = session.pendingRequests.get(nostrEventId);
310
- if (originalId !== undefined) {
311
- targetClientPubkey = clientPubkey;
312
- originalRequestId = originalId;
313
- break;
314
- }
315
- }
316
- if (!targetClientPubkey || originalRequestId === undefined) {
317
- (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, new Error(`No pending request found for response ID: ${response.id}`));
398
+ const targetClientPubkey = this.eventIdToClient.get(nostrEventId);
399
+ if (!targetClientPubkey) {
400
+ (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`No pending request found for response ID: ${response.id}`));
318
401
  return;
319
402
  }
320
- // Restore the original request ID in the response
321
- response.id = originalRequestId;
322
- // Send the response back to the original requester
323
403
  const session = this.clientSessions.get(targetClientPubkey);
324
404
  if (!session) {
325
- (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`No session found for client: ${targetClientPubkey}`));
405
+ (_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`No session found for client: ${targetClientPubkey}`));
326
406
  return;
327
407
  }
408
+ const originalRequestId = session.pendingRequests.get(nostrEventId);
409
+ if (originalRequestId === undefined) {
410
+ (_e = this.onerror) === null || _e === void 0 ? void 0 : _e.call(this, new Error(`No original request ID found for response ID: ${response.id}`));
411
+ return;
412
+ }
413
+ // Restore the original request ID in the response
414
+ response.id = originalRequestId;
415
+ // Send the response back to the original requester
328
416
  const tags = this.createResponseTags(targetClientPubkey, nostrEventId);
329
417
  if (isJSONRPCResponse(response) &&
330
418
  InitializeResultSchema.safeParse(response.result).success &&
@@ -336,19 +424,13 @@ export class NostrServerTransport extends BaseNostrTransport {
336
424
  }
337
425
  await this.sendMcpMessage(response, targetClientPubkey, CTXVM_MESSAGES_KIND, tags, session.isEncrypted);
338
426
  // Clean up the pending request and any associated progress token
339
- if (session) {
340
- session.pendingRequests.delete(nostrEventId);
341
- // Find and delete the corresponding progress token if it exists
342
- let progressTokenToDelete;
343
- for (const [key, value] of session.pendingRequests.entries()) {
344
- if (value === nostrEventId) {
345
- progressTokenToDelete = key;
346
- break;
347
- }
348
- }
349
- if (progressTokenToDelete !== undefined) {
350
- session.pendingRequests.delete(String(progressTokenToDelete));
351
- }
427
+ session.pendingRequests.delete(nostrEventId);
428
+ this.eventIdToClient.delete(nostrEventId);
429
+ // Clean up progress token if it exists
430
+ const progressToken = session.eventToProgressToken.get(nostrEventId);
431
+ if (progressToken) {
432
+ session.pendingRequests.delete(progressToken);
433
+ session.eventToProgressToken.delete(nostrEventId);
352
434
  }
353
435
  }
354
436
  /**
@@ -356,30 +438,60 @@ export class NostrServerTransport extends BaseNostrTransport {
356
438
  * @param notification The JSON-RPC notification to send.
357
439
  */
358
440
  async handleNotification(notification) {
359
- var _a, _b, _c;
360
- // Special handling for progress notifications
361
- // TODO: Add handling for `notifications/resources/updated`, as they need to be associated with an id
362
- if (isJSONRPCNotification(notification) &&
363
- notification.method === 'notifications/progress' &&
364
- ((_b = (_a = notification.params) === null || _a === void 0 ? void 0 : _a._meta) === null || _b === void 0 ? void 0 : _b.progressToken)) {
365
- const token = String(notification.params._meta.progressToken);
366
- for (const [clientPubkey, session] of this.clientSessions.entries()) {
367
- if (session.pendingRequests.has(token)) {
368
- const nostrEventId = session.pendingRequests.get(token);
369
- await this.sendNotification(clientPubkey, notification, nostrEventId);
441
+ var _a, _b, _c, _d, _e;
442
+ try {
443
+ // Special handling for progress notifications
444
+ // TODO: Add handling for `notifications/resources/updated`, as they need to be associated with an id
445
+ if (isJSONRPCNotification(notification) &&
446
+ notification.method === 'notifications/progress' &&
447
+ ((_b = (_a = notification.params) === null || _a === void 0 ? void 0 : _a._meta) === null || _b === void 0 ? void 0 : _b.progressToken)) {
448
+ const token = String(notification.params._meta.progressToken);
449
+ // Use reverse lookup map for O(1) progress token routing
450
+ // First find the session that has this progress token
451
+ let targetClientPubkey;
452
+ let nostrEventId;
453
+ for (const [clientPubkey, session] of this.clientSessions.entries()) {
454
+ if (session.pendingRequests.has(token)) {
455
+ nostrEventId = session.pendingRequests.get(token);
456
+ targetClientPubkey = clientPubkey;
457
+ break;
458
+ }
459
+ }
460
+ if (targetClientPubkey && nostrEventId) {
461
+ await this.sendNotification(targetClientPubkey, notification, nostrEventId);
370
462
  return;
371
463
  }
464
+ const error = new Error(`No client found for progress token: ${token}`);
465
+ logger.error('Progress token not found', { token });
466
+ (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
467
+ return;
372
468
  }
373
- (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`No client found for progress token: ${token}`));
374
- return;
375
- }
376
- const promises = [];
377
- for (const [clientPubkey, session] of this.clientSessions.entries()) {
378
- if (session.isInitialized) {
379
- promises.push(this.sendNotification(clientPubkey, notification));
469
+ const promises = [];
470
+ for (const [clientPubkey, session] of this.clientSessions.entries()) {
471
+ if (session.isInitialized) {
472
+ promises.push(this.sendNotification(clientPubkey, notification));
473
+ }
380
474
  }
475
+ try {
476
+ await Promise.all(promises);
477
+ }
478
+ catch (error) {
479
+ logger.error('Error broadcasting notification', {
480
+ error: error instanceof Error ? error.message : String(error),
481
+ method: isJSONRPCNotification(notification)
482
+ ? notification.method
483
+ : 'unknown',
484
+ });
485
+ (_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, error instanceof Error ? error : new Error(String(error)));
486
+ }
487
+ }
488
+ catch (error) {
489
+ logger.error('Error in handleNotification', {
490
+ error: error instanceof Error ? error.message : String(error),
491
+ stack: error instanceof Error ? error.stack : undefined,
492
+ });
493
+ (_e = this.onerror) === null || _e === void 0 ? void 0 : _e.call(this, error instanceof Error ? error : new Error(String(error)));
381
494
  }
382
- await Promise.all(promises);
383
495
  }
384
496
  /**
385
497
  * Sends a notification to a specific client by their public key.
@@ -406,11 +518,23 @@ export class NostrServerTransport extends BaseNostrTransport {
406
518
  * @param event The incoming Nostr event.
407
519
  */
408
520
  async processIncomingEvent(event) {
409
- if (event.kind === GIFT_WRAP_KIND) {
410
- await this.handleEncryptedEvent(event);
521
+ var _a;
522
+ try {
523
+ if (event.kind === GIFT_WRAP_KIND) {
524
+ await this.handleEncryptedEvent(event);
525
+ }
526
+ else {
527
+ this.handleUnencryptedEvent(event);
528
+ }
411
529
  }
412
- else {
413
- this.handleUnencryptedEvent(event);
530
+ catch (error) {
531
+ logger.error('Error in processIncomingEvent', {
532
+ error: error instanceof Error ? error.message : String(error),
533
+ stack: error instanceof Error ? error.stack : undefined,
534
+ eventId: event.id,
535
+ eventKind: event.kind,
536
+ });
537
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
414
538
  }
415
539
  }
416
540
  /**
@@ -429,6 +553,12 @@ export class NostrServerTransport extends BaseNostrTransport {
429
553
  this.authorizeAndProcessEvent(currentEvent, true);
430
554
  }
431
555
  catch (error) {
556
+ logger.error('Failed to handle encrypted Nostr event', {
557
+ error: error instanceof Error ? error.message : String(error),
558
+ stack: error instanceof Error ? error.stack : undefined,
559
+ eventId: event.id,
560
+ pubkey: event.pubkey,
561
+ });
432
562
  (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error
433
563
  ? error
434
564
  : new Error('Failed to handle encrypted Nostr event'));
@@ -479,66 +609,93 @@ export class NostrServerTransport extends BaseNostrTransport {
479
609
  * @param isEncrypted Whether the original event was encrypted.
480
610
  */
481
611
  authorizeAndProcessEvent(event, isEncrypted) {
482
- var _a, _b, _c, _d;
483
- const mcpMessage = this.convertNostrEventToMcpMessage(event);
484
- if (!mcpMessage) {
485
- logger.error('Skipping invalid Nostr event with malformed JSON content');
486
- return;
487
- }
488
- if ((_a = this.allowedPublicKeys) === null || _a === void 0 ? void 0 : _a.length) {
489
- // Check if the message should bypass whitelisting due to excluded capabilities
490
- const shouldBypassWhitelisting = ((_b = this.excludedCapabilities) === null || _b === void 0 ? void 0 : _b.length) &&
491
- (isJSONRPCRequest(mcpMessage) || isJSONRPCNotification(mcpMessage)) &&
492
- this.isCapabilityExcluded(mcpMessage.method, (_c = mcpMessage.params) === null || _c === void 0 ? void 0 : _c.name);
493
- if (!this.allowedPublicKeys.includes(event.pubkey) &&
494
- !shouldBypassWhitelisting) {
495
- logger.error(`Unauthorized message from ${event.pubkey}, message: ${JSON.stringify(mcpMessage)}. Ignoring.`);
496
- if (this.isPublicServer && isJSONRPCRequest(mcpMessage)) {
497
- const errorResponse = {
498
- jsonrpc: '2.0',
499
- id: mcpMessage.id,
500
- error: {
501
- code: -32000,
502
- message: 'Unauthorized',
503
- },
504
- };
505
- const tags = this.createResponseTags(event.pubkey, event.id);
506
- this.sendMcpMessage(errorResponse, event.pubkey, CTXVM_MESSAGES_KIND, tags, isEncrypted).catch((err) => {
507
- var _a;
508
- (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to send unauthorized response: ${err}`));
509
- });
510
- }
612
+ var _a, _b, _c, _d, _e;
613
+ try {
614
+ const mcpMessage = this.convertNostrEventToMcpMessage(event);
615
+ if (!mcpMessage) {
616
+ logger.error('Skipping invalid Nostr event with malformed JSON content', {
617
+ eventId: event.id,
618
+ pubkey: event.pubkey,
619
+ content: event.content,
620
+ });
511
621
  return;
512
622
  }
623
+ if ((_a = this.allowedPublicKeys) === null || _a === void 0 ? void 0 : _a.length) {
624
+ // Check if the message should bypass whitelisting due to excluded capabilities
625
+ const shouldBypassWhitelisting = ((_b = this.excludedCapabilities) === null || _b === void 0 ? void 0 : _b.length) &&
626
+ (isJSONRPCRequest(mcpMessage) || isJSONRPCNotification(mcpMessage)) &&
627
+ this.isCapabilityExcluded(mcpMessage.method, (_c = mcpMessage.params) === null || _c === void 0 ? void 0 : _c.name);
628
+ if (!this.allowedPublicKeys.includes(event.pubkey) &&
629
+ !shouldBypassWhitelisting) {
630
+ logger.error(`Unauthorized message from ${event.pubkey}, message: ${JSON.stringify(mcpMessage)}. Ignoring.`);
631
+ if (this.isPublicServer && isJSONRPCRequest(mcpMessage)) {
632
+ const errorResponse = {
633
+ jsonrpc: '2.0',
634
+ id: mcpMessage.id,
635
+ error: {
636
+ code: -32000,
637
+ message: 'Unauthorized',
638
+ },
639
+ };
640
+ const tags = this.createResponseTags(event.pubkey, event.id);
641
+ this.sendMcpMessage(errorResponse, event.pubkey, CTXVM_MESSAGES_KIND, tags, isEncrypted).catch((err) => {
642
+ var _a;
643
+ logger.error('Failed to send unauthorized response', {
644
+ error: err instanceof Error ? err.message : String(err),
645
+ pubkey: event.pubkey,
646
+ eventId: event.id,
647
+ });
648
+ (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to send unauthorized response: ${err}`));
649
+ });
650
+ }
651
+ return;
652
+ }
653
+ }
654
+ const now = Date.now();
655
+ const session = this.getOrCreateClientSession(event.pubkey, now, isEncrypted);
656
+ session.lastActivity = now;
657
+ if (isJSONRPCRequest(mcpMessage)) {
658
+ this.handleIncomingRequest(session, event.id, mcpMessage, event.pubkey);
659
+ }
660
+ else if (isJSONRPCNotification(mcpMessage)) {
661
+ this.handleIncomingNotification(session, mcpMessage);
662
+ }
663
+ (_d = this.onmessage) === null || _d === void 0 ? void 0 : _d.call(this, mcpMessage);
513
664
  }
514
- const now = Date.now();
515
- const session = this.getOrCreateClientSession(event.pubkey, now, isEncrypted);
516
- session.lastActivity = now;
517
- if (isJSONRPCRequest(mcpMessage)) {
518
- this.handleIncomingRequest(session, event.id, mcpMessage);
519
- }
520
- else if (isJSONRPCNotification(mcpMessage)) {
521
- this.handleIncomingNotification(session, mcpMessage);
665
+ catch (error) {
666
+ logger.error('Error in authorizeAndProcessEvent', {
667
+ error: error instanceof Error ? error.message : String(error),
668
+ stack: error instanceof Error ? error.stack : undefined,
669
+ eventId: event.id,
670
+ pubkey: event.pubkey,
671
+ });
672
+ (_e = this.onerror) === null || _e === void 0 ? void 0 : _e.call(this, error instanceof Error ? error : new Error(String(error)));
522
673
  }
523
- (_d = this.onmessage) === null || _d === void 0 ? void 0 : _d.call(this, mcpMessage);
524
674
  }
525
675
  /**
526
676
  * Cleans up inactive client sessions based on a timeout.
527
677
  * @param timeoutMs Timeout in milliseconds for considering a session inactive (default: 5 minutes).
528
678
  * @returns The number of sessions that were cleaned up.
529
679
  */
530
- cleanupInactiveSessions(timeoutMs = 300000) {
680
+ cleanupInactiveSessions(timeoutMs) {
531
681
  const now = Date.now();
532
- const keysToDelete = [];
682
+ const timeout = timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : this.sessionTimeoutMs;
683
+ let cleaned = 0;
533
684
  for (const [clientPubkey, session] of this.clientSessions.entries()) {
534
- if (now - session.lastActivity > timeoutMs) {
535
- keysToDelete.push(clientPubkey);
685
+ if (now - session.lastActivity > timeout) {
686
+ // Clean up reverse lookup mappings for this session
687
+ for (const eventId of session.pendingRequests.keys()) {
688
+ this.eventIdToClient.delete(eventId);
689
+ }
690
+ // Clean up progress token mappings
691
+ for (const eventId of session.eventToProgressToken.keys()) {
692
+ this.eventIdToClient.delete(eventId);
693
+ }
694
+ this.clientSessions.delete(clientPubkey);
695
+ cleaned++;
536
696
  }
537
697
  }
538
- for (const key of keysToDelete) {
539
- this.clientSessions.delete(key);
540
- }
541
- return keysToDelete.length;
698
+ return cleaned;
542
699
  }
543
700
  }
544
701
  //# sourceMappingURL=nostr-server-transport.js.map