@contextvm/sdk 0.11.8 → 0.11.9
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/dist/esm/core/utils/base64.d.ts +3 -0
- package/dist/esm/core/utils/base64.d.ts.map +1 -0
- package/dist/esm/core/utils/base64.js +24 -0
- package/dist/esm/core/utils/base64.js.map +1 -0
- package/dist/esm/payments/handlers/ln-bolt11-lnbits-payment-handler.d.ts.map +1 -1
- package/dist/esm/payments/handlers/ln-bolt11-lnbits-payment-handler.js +2 -1
- package/dist/esm/payments/handlers/ln-bolt11-lnbits-payment-handler.js.map +1 -1
- package/dist/esm/payments/processors/ln-bolt11-lnbits-payment-processor.d.ts.map +1 -1
- package/dist/esm/payments/processors/ln-bolt11-lnbits-payment-processor.js +2 -1
- package/dist/esm/payments/processors/ln-bolt11-lnbits-payment-processor.js.map +1 -1
- package/dist/esm/transport/capability-negotiator.d.ts +141 -0
- package/dist/esm/transport/capability-negotiator.d.ts.map +1 -0
- package/dist/esm/transport/capability-negotiator.js +198 -0
- package/dist/esm/transport/capability-negotiator.js.map +1 -0
- package/dist/esm/transport/middleware.d.ts +9 -0
- package/dist/esm/transport/middleware.d.ts.map +1 -0
- package/dist/esm/transport/middleware.js +2 -0
- package/dist/esm/transport/middleware.js.map +1 -0
- package/dist/esm/transport/nostr-client/event-pipeline.d.ts +29 -0
- package/dist/esm/transport/nostr-client/event-pipeline.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/event-pipeline.js +109 -0
- package/dist/esm/transport/nostr-client/event-pipeline.js.map +1 -0
- package/dist/esm/transport/nostr-client/inbound-coordinator.d.ts +37 -0
- package/dist/esm/transport/nostr-client/inbound-coordinator.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/inbound-coordinator.js +159 -0
- package/dist/esm/transport/nostr-client/inbound-coordinator.js.map +1 -0
- package/dist/esm/transport/nostr-client/inbound-notification-dispatcher.d.ts +28 -0
- package/dist/esm/transport/nostr-client/inbound-notification-dispatcher.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/inbound-notification-dispatcher.js +65 -0
- package/dist/esm/transport/nostr-client/inbound-notification-dispatcher.js.map +1 -0
- package/dist/esm/transport/nostr-client/open-stream-factory.d.ts +36 -0
- package/dist/esm/transport/nostr-client/open-stream-factory.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/open-stream-factory.js +126 -0
- package/dist/esm/transport/nostr-client/open-stream-factory.js.map +1 -0
- package/dist/esm/transport/nostr-client/outbound-sender.d.ts +45 -0
- package/dist/esm/transport/nostr-client/outbound-sender.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/outbound-sender.js +105 -0
- package/dist/esm/transport/nostr-client/outbound-sender.js.map +1 -0
- package/dist/esm/transport/nostr-client/server-metadata-store.d.ts +35 -0
- package/dist/esm/transport/nostr-client/server-metadata-store.d.ts.map +1 -0
- package/dist/esm/transport/nostr-client/server-metadata-store.js +107 -0
- package/dist/esm/transport/nostr-client/server-metadata-store.js.map +1 -0
- package/dist/esm/transport/nostr-client-transport.d.ts +18 -41
- package/dist/esm/transport/nostr-client-transport.d.ts.map +1 -1
- package/dist/esm/transport/nostr-client-transport.js +117 -553
- package/dist/esm/transport/nostr-client-transport.js.map +1 -1
- package/dist/esm/transport/nostr-server/correlation-store.d.ts +2 -2
- package/dist/esm/transport/nostr-server/correlation-store.d.ts.map +1 -1
- package/dist/esm/transport/nostr-server/correlation-store.js +12 -3
- package/dist/esm/transport/nostr-server/correlation-store.js.map +1 -1
- package/dist/esm/transport/nostr-server/event-pipeline.d.ts +32 -0
- package/dist/esm/transport/nostr-server/event-pipeline.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/event-pipeline.js +118 -0
- package/dist/esm/transport/nostr-server/event-pipeline.js.map +1 -0
- package/dist/esm/transport/nostr-server/inbound-coordinator.d.ts +56 -0
- package/dist/esm/transport/nostr-server/inbound-coordinator.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/inbound-coordinator.js +168 -0
- package/dist/esm/transport/nostr-server/inbound-coordinator.js.map +1 -0
- package/dist/esm/transport/nostr-server/inbound-notification-dispatcher.d.ts +44 -0
- package/dist/esm/transport/nostr-server/inbound-notification-dispatcher.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/inbound-notification-dispatcher.js +153 -0
- package/dist/esm/transport/nostr-server/inbound-notification-dispatcher.js.map +1 -0
- package/dist/esm/transport/nostr-server/open-stream-factory.d.ts +81 -0
- package/dist/esm/transport/nostr-server/open-stream-factory.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/open-stream-factory.js +224 -0
- package/dist/esm/transport/nostr-server/open-stream-factory.js.map +1 -0
- package/dist/esm/transport/nostr-server/outbound-notification-broadcaster.d.ts +24 -0
- package/dist/esm/transport/nostr-server/outbound-notification-broadcaster.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/outbound-notification-broadcaster.js +64 -0
- package/dist/esm/transport/nostr-server/outbound-notification-broadcaster.js.map +1 -0
- package/dist/esm/transport/nostr-server/outbound-response-router.d.ts +62 -0
- package/dist/esm/transport/nostr-server/outbound-response-router.d.ts.map +1 -0
- package/dist/esm/transport/nostr-server/outbound-response-router.js +125 -0
- package/dist/esm/transport/nostr-server/outbound-response-router.js.map +1 -0
- package/dist/esm/transport/nostr-server-transport.d.ts +15 -62
- package/dist/esm/transport/nostr-server-transport.d.ts.map +1 -1
- package/dist/esm/transport/nostr-server-transport.js +121 -741
- package/dist/esm/transport/nostr-server-transport.js.map +1 -1
- package/dist/esm/transport/open-stream/registry.d.ts.map +1 -1
- package/dist/esm/transport/open-stream/registry.js +7 -1
- package/dist/esm/transport/open-stream/registry.js.map +1 -1
- package/dist/esm/transport/open-stream/session.d.ts +1 -0
- package/dist/esm/transport/open-stream/session.d.ts.map +1 -1
- package/dist/esm/transport/open-stream/session.js +14 -4
- package/dist/esm/transport/open-stream/session.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isJSONRPCNotification, isJSONRPCResultResponse, isJSONRPCErrorResponse, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { BaseNostrTransport, } from './base-nostr-transport.js';
|
|
3
|
-
import { CTXVM_MESSAGES_KIND, DEFAULT_TIMEOUT_MS,
|
|
4
|
-
import {
|
|
5
|
-
import { verifyEvent } from 'nostr-tools/pure';
|
|
6
|
-
import { injectClientPubkey, injectRequestEventId, withTimeout, } from '../core/utils/utils.js';
|
|
3
|
+
import { CTXVM_MESSAGES_KIND, DEFAULT_TIMEOUT_MS, NOSTR_TAGS, DEFAULT_LRU_SIZE, } from '../core/index.js';
|
|
4
|
+
import { withTimeout } from '../core/utils/utils.js';
|
|
7
5
|
import { CorrelationStore } from './nostr-server/correlation-store.js';
|
|
8
6
|
import { SessionStore } from './nostr-server/session-store.js';
|
|
9
7
|
import { LruCache } from '../core/utils/lru-cache.js';
|
|
@@ -11,10 +9,14 @@ import { ApplesauceRelayPool } from '../relay/applesauce-relay-pool.js';
|
|
|
11
9
|
import { AuthorizationPolicy, } from './nostr-server/authorization-policy.js';
|
|
12
10
|
import { AnnouncementManager, } from './nostr-server/announcement-manager.js';
|
|
13
11
|
import { OversizedTransferReceiver, } from './oversized-transfer/index.js';
|
|
14
|
-
import { OpenStreamReceiver, OpenStreamWriter, buildOpenStreamAcceptFrame, buildOpenStreamAbortFrame, buildOpenStreamPingFrame, buildOpenStreamPongFrame, } from './open-stream/index.js';
|
|
15
12
|
import { DEFAULT_CHUNK_SIZE, DEFAULT_OVERSIZED_THRESHOLD, } from './oversized-transfer/constants.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
13
|
+
import { ServerCapabilityNegotiator } from './capability-negotiator.js';
|
|
14
|
+
import { InboundNotificationDispatcher } from './nostr-server/inbound-notification-dispatcher.js';
|
|
15
|
+
import { OutboundResponseRouter } from './nostr-server/outbound-response-router.js';
|
|
16
|
+
import { OutboundNotificationBroadcaster } from './nostr-server/outbound-notification-broadcaster.js';
|
|
17
|
+
import { ServerOpenStreamFactory } from './nostr-server/open-stream-factory.js';
|
|
18
|
+
import { ServerEventPipeline } from './nostr-server/event-pipeline.js';
|
|
19
|
+
import { ServerInboundCoordinator } from './nostr-server/inbound-coordinator.js';
|
|
18
20
|
/**
|
|
19
21
|
* A server-side transport layer for CTXVM that uses Nostr events for communication.
|
|
20
22
|
* This transport listens for incoming MCP requests via Nostr events and can send
|
|
@@ -23,7 +25,7 @@ import { sendAcceptFrame, sendOversizedServerResponse, } from './nostr-server/ov
|
|
|
23
25
|
*/
|
|
24
26
|
export class NostrServerTransport extends BaseNostrTransport {
|
|
25
27
|
constructor(options) {
|
|
26
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l
|
|
28
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
27
29
|
super('nostr-server-transport', options);
|
|
28
30
|
this.inboundMiddlewares = [];
|
|
29
31
|
this.listToolsResultTransformers = [];
|
|
@@ -34,14 +36,6 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
34
36
|
* Used for gift-wrap envelopes (outer event ids) and decrypted inner events.
|
|
35
37
|
*/
|
|
36
38
|
this.seenEventIds = new LruCache(DEFAULT_LRU_SIZE);
|
|
37
|
-
/** Pending final responses held until their CEP-41 stream terminates. */
|
|
38
|
-
this.pendingOpenStreamResponses = new Map();
|
|
39
|
-
/** Sessions logically evicted after CEP-41 probe timeout, retained only to flush deferred finals. */
|
|
40
|
-
this.pendingOpenStreamSessionEvictions = new Map();
|
|
41
|
-
/** Client pubkeys that should no longer receive new notifications. */
|
|
42
|
-
this.evictedOpenStreamClientPubkeys = new Set();
|
|
43
|
-
/** Active server-side CEP-41 writers keyed by inbound request event id. */
|
|
44
|
-
this.openStreamWriters = new Map();
|
|
45
39
|
this.injectClientPubkey = (_a = options.injectClientPubkey) !== null && _a !== void 0 ? _a : false;
|
|
46
40
|
this.shouldInjectRequestEventId = (_b = options.injectRequestEventId) !== null && _b !== void 0 ? _b : false;
|
|
47
41
|
this.onClientSessionEvicted = options.onClientSessionEvicted;
|
|
@@ -120,56 +114,6 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
120
114
|
this.oversizedChunkSize = (_g = ot === null || ot === void 0 ? void 0 : ot.chunkSizeBytes) !== null && _g !== void 0 ? _g : DEFAULT_CHUNK_SIZE;
|
|
121
115
|
this.oversizedReceiver = new OversizedTransferReceiver((_h = ot === null || ot === void 0 ? void 0 : ot.policy) !== null && _h !== void 0 ? _h : {}, this.logger);
|
|
122
116
|
this.openStreamEnabled = (_k = (_j = options.openStream) === null || _j === void 0 ? void 0 : _j.enabled) !== null && _k !== void 0 ? _k : false;
|
|
123
|
-
this.openStreamReceiver = new OpenStreamReceiver({
|
|
124
|
-
maxConcurrentStreams: (_m = (_l = options.openStream) === null || _l === void 0 ? void 0 : _l.policy) === null || _m === void 0 ? void 0 : _m.maxConcurrentStreams,
|
|
125
|
-
maxBufferedChunksPerStream: (_p = (_o = options.openStream) === null || _o === void 0 ? void 0 : _o.policy) === null || _p === void 0 ? void 0 : _p.maxBufferedChunksPerStream,
|
|
126
|
-
maxBufferedBytesPerStream: (_r = (_q = options.openStream) === null || _q === void 0 ? void 0 : _q.policy) === null || _r === void 0 ? void 0 : _r.maxBufferedBytesPerStream,
|
|
127
|
-
idleTimeoutMs: (_t = (_s = options.openStream) === null || _s === void 0 ? void 0 : _s.policy) === null || _t === void 0 ? void 0 : _t.idleTimeoutMs,
|
|
128
|
-
probeTimeoutMs: (_v = (_u = options.openStream) === null || _u === void 0 ? void 0 : _u.policy) === null || _v === void 0 ? void 0 : _v.probeTimeoutMs,
|
|
129
|
-
closeGracePeriodMs: (_x = (_w = options.openStream) === null || _w === void 0 ? void 0 : _w.policy) === null || _x === void 0 ? void 0 : _x.closeGracePeriodMs,
|
|
130
|
-
getSessionOptions: (progressToken) => {
|
|
131
|
-
let progress = 0;
|
|
132
|
-
return {
|
|
133
|
-
sendPing: async (nonce) => {
|
|
134
|
-
progress += 1;
|
|
135
|
-
await this.sendNotification(progressToken, {
|
|
136
|
-
jsonrpc: '2.0',
|
|
137
|
-
method: 'notifications/progress',
|
|
138
|
-
params: buildOpenStreamPingFrame({
|
|
139
|
-
progressToken,
|
|
140
|
-
progress,
|
|
141
|
-
nonce,
|
|
142
|
-
}),
|
|
143
|
-
});
|
|
144
|
-
},
|
|
145
|
-
sendPong: async (nonce) => {
|
|
146
|
-
progress += 1;
|
|
147
|
-
await this.sendNotification(progressToken, {
|
|
148
|
-
jsonrpc: '2.0',
|
|
149
|
-
method: 'notifications/progress',
|
|
150
|
-
params: buildOpenStreamPongFrame({
|
|
151
|
-
progressToken,
|
|
152
|
-
progress,
|
|
153
|
-
nonce,
|
|
154
|
-
}),
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
sendAbort: async (reason) => {
|
|
158
|
-
progress += 1;
|
|
159
|
-
await this.sendNotification(progressToken, {
|
|
160
|
-
jsonrpc: '2.0',
|
|
161
|
-
method: 'notifications/progress',
|
|
162
|
-
params: buildOpenStreamAbortFrame({
|
|
163
|
-
progressToken,
|
|
164
|
-
progress,
|
|
165
|
-
reason,
|
|
166
|
-
}),
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
},
|
|
171
|
-
logger: this.logger,
|
|
172
|
-
});
|
|
173
117
|
// Advertise CEP-22 support so clients can skip the accept handshake.
|
|
174
118
|
const internalCommonTags = [];
|
|
175
119
|
if (this.oversizedEnabled) {
|
|
@@ -179,6 +123,97 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
179
123
|
internalCommonTags.push([NOSTR_TAGS.SUPPORT_OPEN_STREAM]);
|
|
180
124
|
}
|
|
181
125
|
this.announcementManager.setInternalCommonTags(internalCommonTags);
|
|
126
|
+
this.capabilityNegotiator = new ServerCapabilityNegotiator({
|
|
127
|
+
getCommonTags: this.announcementManager.getCommonTags.bind(this.announcementManager),
|
|
128
|
+
composeOutboundTags: this.composeOutboundTags.bind(this),
|
|
129
|
+
giftWrapMode: this.giftWrapMode,
|
|
130
|
+
});
|
|
131
|
+
this.openStreamFactory = new ServerOpenStreamFactory({
|
|
132
|
+
openStreamEnabled: this.openStreamEnabled,
|
|
133
|
+
sendNotification: this.sendNotification.bind(this),
|
|
134
|
+
handleResponse: async (response) => {
|
|
135
|
+
await this.outboundResponseRouter.route(response);
|
|
136
|
+
},
|
|
137
|
+
sessionStore: this.sessionStore,
|
|
138
|
+
onClientSessionEvicted: this.onClientSessionEvicted,
|
|
139
|
+
correlationStore: this.correlationStore,
|
|
140
|
+
policy: (_l = options.openStream) === null || _l === void 0 ? void 0 : _l.policy,
|
|
141
|
+
logger: this.logger,
|
|
142
|
+
});
|
|
143
|
+
this.inboundCoordinator = new ServerInboundCoordinator({
|
|
144
|
+
sessionStore: this.sessionStore,
|
|
145
|
+
correlationStore: this.correlationStore,
|
|
146
|
+
authorizationPolicy: this.authorizationPolicy,
|
|
147
|
+
openStreamFactory: this.openStreamFactory,
|
|
148
|
+
inboundMiddlewares: this.inboundMiddlewares,
|
|
149
|
+
injectClientPubkey: this.injectClientPubkey,
|
|
150
|
+
shouldInjectRequestEventId: this.shouldInjectRequestEventId,
|
|
151
|
+
oversizedEnabled: this.oversizedEnabled,
|
|
152
|
+
openStreamEnabled: this.openStreamEnabled,
|
|
153
|
+
giftWrapMode: this.giftWrapMode,
|
|
154
|
+
sendMcpMessage: this.sendMcpMessage.bind(this),
|
|
155
|
+
createResponseTags: (clientPubkey, requestId) => this.createResponseTags(clientPubkey, String(requestId)),
|
|
156
|
+
getOrCreateClientSession: this.getOrCreateClientSession.bind(this),
|
|
157
|
+
forwardMessage: async (msg, clientPubkey) => {
|
|
158
|
+
var _a, _b;
|
|
159
|
+
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
160
|
+
(_b = this.onmessageWithContext) === null || _b === void 0 ? void 0 : _b.call(this, msg, { clientPubkey });
|
|
161
|
+
return true;
|
|
162
|
+
},
|
|
163
|
+
logger: this.logger,
|
|
164
|
+
onerror: (error) => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); },
|
|
165
|
+
});
|
|
166
|
+
this.inboundNotificationDispatcher = new InboundNotificationDispatcher({
|
|
167
|
+
openStreamReceiver: this.openStreamFactory.getReceiver(),
|
|
168
|
+
oversizedReceiver: this.oversizedReceiver,
|
|
169
|
+
openStreamFactory: this.openStreamFactory,
|
|
170
|
+
correlationStore: this.correlationStore,
|
|
171
|
+
sendNotification: this.sendNotification.bind(this),
|
|
172
|
+
handleIncomingRequest: this.inboundCoordinator.handleIncomingRequest.bind(this.inboundCoordinator),
|
|
173
|
+
handleIncomingNotification: this.inboundCoordinator.handleIncomingNotification.bind(this.inboundCoordinator),
|
|
174
|
+
cleanupDroppedRequest: this.inboundCoordinator.cleanupDroppedRequest.bind(this.inboundCoordinator),
|
|
175
|
+
shouldInjectRequestEventId: this.shouldInjectRequestEventId,
|
|
176
|
+
injectClientPubkey: this.injectClientPubkey,
|
|
177
|
+
logger: this.logger,
|
|
178
|
+
onerror: (error) => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); },
|
|
179
|
+
});
|
|
180
|
+
this.inboundCoordinator.setNotificationDispatcher(this.inboundNotificationDispatcher);
|
|
181
|
+
this.outboundResponseRouter = new OutboundResponseRouter({
|
|
182
|
+
correlationStore: this.correlationStore,
|
|
183
|
+
sessionStore: this.sessionStore,
|
|
184
|
+
announcementManager: this.announcementManager,
|
|
185
|
+
openStreamFactory: this.openStreamFactory,
|
|
186
|
+
oversizedConfig: {
|
|
187
|
+
enabled: this.oversizedEnabled,
|
|
188
|
+
threshold: this.oversizedThreshold,
|
|
189
|
+
chunkSize: this.oversizedChunkSize,
|
|
190
|
+
},
|
|
191
|
+
applyListToolsResultTransformers: this.applyListToolsResultTransformers.bind(this),
|
|
192
|
+
buildOutboundTags: this.capabilityNegotiator.buildOutboundTags.bind(this.capabilityNegotiator),
|
|
193
|
+
createResponseTags: this.createResponseTags.bind(this),
|
|
194
|
+
chooseGiftWrapKind: this.capabilityNegotiator.chooseOutboundGiftWrapKind.bind(this.capabilityNegotiator),
|
|
195
|
+
sendMcpMessage: this.sendMcpMessage.bind(this),
|
|
196
|
+
measurePublishedMcpMessageSize: this.measurePublishedMcpMessageSize.bind(this),
|
|
197
|
+
resolveSafeOversizedChunkSize: this.resolveSafeOversizedChunkSize.bind(this),
|
|
198
|
+
logger: this.logger,
|
|
199
|
+
onerror: (error) => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); },
|
|
200
|
+
});
|
|
201
|
+
this.outboundNotificationBroadcaster = new OutboundNotificationBroadcaster({
|
|
202
|
+
correlationStore: this.correlationStore,
|
|
203
|
+
sessionStore: this.sessionStore,
|
|
204
|
+
sendNotification: this.sendNotification.bind(this),
|
|
205
|
+
enqueueTask: this.taskQueue.add.bind(this.taskQueue),
|
|
206
|
+
logger: this.logger,
|
|
207
|
+
onerror: (error) => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); },
|
|
208
|
+
});
|
|
209
|
+
this.eventPipeline = new ServerEventPipeline({
|
|
210
|
+
signer: this.signer,
|
|
211
|
+
seenEventIds: this.seenEventIds,
|
|
212
|
+
encryptionMode: this.encryptionMode,
|
|
213
|
+
giftWrapMode: this.giftWrapMode,
|
|
214
|
+
logger: this.logger,
|
|
215
|
+
onerror: (error) => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); },
|
|
216
|
+
});
|
|
182
217
|
}
|
|
183
218
|
/**
|
|
184
219
|
* Sets extra tags to include in server announcement + initialize response events.
|
|
@@ -273,9 +308,8 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
273
308
|
this.correlationStore.clear();
|
|
274
309
|
this.seenEventIds.clear();
|
|
275
310
|
this.oversizedReceiver.clear();
|
|
276
|
-
this.
|
|
277
|
-
this.
|
|
278
|
-
this.openStreamWriters.clear();
|
|
311
|
+
this.openStreamFactory.getReceiver().clear();
|
|
312
|
+
this.openStreamFactory.clear();
|
|
279
313
|
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
280
314
|
}
|
|
281
315
|
catch (error) {
|
|
@@ -332,39 +366,6 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
332
366
|
}
|
|
333
367
|
return session;
|
|
334
368
|
}
|
|
335
|
-
takePendingServerDiscoveryTags(session) {
|
|
336
|
-
if (session.hasSentCommonTags) {
|
|
337
|
-
return [];
|
|
338
|
-
}
|
|
339
|
-
session.hasSentCommonTags = true;
|
|
340
|
-
return this.announcementManager.getCommonTags();
|
|
341
|
-
}
|
|
342
|
-
buildServerOutboundTags(params) {
|
|
343
|
-
const { baseTags, session, includeDiscovery = true, negotiationTags = [], } = params;
|
|
344
|
-
return this.composeOutboundTags({
|
|
345
|
-
baseTags,
|
|
346
|
-
discoveryTags: includeDiscovery
|
|
347
|
-
? this.takePendingServerDiscoveryTags(session)
|
|
348
|
-
: [],
|
|
349
|
-
negotiationTags,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
chooseServerOutboundGiftWrapKind(params) {
|
|
353
|
-
const { session, fallbackWrapKind } = params;
|
|
354
|
-
if (!session.isEncrypted) {
|
|
355
|
-
return undefined;
|
|
356
|
-
}
|
|
357
|
-
if (this.giftWrapMode === GiftWrapMode.EPHEMERAL) {
|
|
358
|
-
return EPHEMERAL_GIFT_WRAP_KIND;
|
|
359
|
-
}
|
|
360
|
-
if (this.giftWrapMode === GiftWrapMode.PERSISTENT) {
|
|
361
|
-
return GIFT_WRAP_KIND;
|
|
362
|
-
}
|
|
363
|
-
if (session.supportsEphemeralEncryption) {
|
|
364
|
-
return EPHEMERAL_GIFT_WRAP_KIND;
|
|
365
|
-
}
|
|
366
|
-
return fallbackWrapKind;
|
|
367
|
-
}
|
|
368
369
|
getRelayUrls(relayHandler) {
|
|
369
370
|
return relayHandler.getRelayUrls();
|
|
370
371
|
}
|
|
@@ -391,108 +392,6 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
391
392
|
});
|
|
392
393
|
}
|
|
393
394
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Handles incoming requests with correlation tracking.
|
|
396
|
-
* @param eventId The Nostr event ID.
|
|
397
|
-
* @param request The request message.
|
|
398
|
-
* @param clientPubkey The client's public key.
|
|
399
|
-
*/
|
|
400
|
-
handleIncomingRequest(event, eventId, request, clientPubkey, wrapKind) {
|
|
401
|
-
var _a, _b;
|
|
402
|
-
// Store the original request ID for later restoration
|
|
403
|
-
const originalRequestId = request.id;
|
|
404
|
-
// Use the unique Nostr event ID as the MCP request ID to avoid collisions
|
|
405
|
-
request.id = eventId;
|
|
406
|
-
// Register the event route in the correlation store
|
|
407
|
-
const progressToken = (_b = (_a = request.params) === null || _a === void 0 ? void 0 : _a._meta) === null || _b === void 0 ? void 0 : _b.progressToken;
|
|
408
|
-
this.correlationStore.registerEventRoute(eventId, clientPubkey, originalRequestId, progressToken ? String(progressToken) : undefined, wrapKind, this.shouldInjectRequestEventId ? event : undefined);
|
|
409
|
-
if (this.openStreamEnabled && progressToken) {
|
|
410
|
-
const writer = new OpenStreamWriter({
|
|
411
|
-
progressToken: String(progressToken),
|
|
412
|
-
publishFrame: async (frame) => {
|
|
413
|
-
await this.sendNotification(clientPubkey, {
|
|
414
|
-
jsonrpc: '2.0',
|
|
415
|
-
method: 'notifications/progress',
|
|
416
|
-
params: frame,
|
|
417
|
-
});
|
|
418
|
-
return undefined;
|
|
419
|
-
},
|
|
420
|
-
onClose: async () => {
|
|
421
|
-
await this.flushPendingOpenStreamResponse(eventId);
|
|
422
|
-
},
|
|
423
|
-
onAbort: async (reason) => {
|
|
424
|
-
await this.handleOpenStreamWriterAbort(clientPubkey, eventId, reason);
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
this.openStreamWriters.set(eventId, writer);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
async flushPendingOpenStreamResponse(eventId) {
|
|
431
|
-
const pendingResponse = this.pendingOpenStreamResponses.get(eventId);
|
|
432
|
-
this.pendingOpenStreamResponses.delete(eventId);
|
|
433
|
-
this.openStreamWriters.delete(eventId);
|
|
434
|
-
if (!pendingResponse) {
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
await this.handleResponse(pendingResponse);
|
|
438
|
-
}
|
|
439
|
-
async handleOpenStreamWriterAbort(clientPubkey, eventId, reason) {
|
|
440
|
-
if (reason === 'Probe timeout') {
|
|
441
|
-
const session = this.sessionStore.getSession(clientPubkey);
|
|
442
|
-
if (session) {
|
|
443
|
-
this.pendingOpenStreamSessionEvictions.set(eventId, {
|
|
444
|
-
clientPubkey,
|
|
445
|
-
session: { ...session },
|
|
446
|
-
});
|
|
447
|
-
this.evictedOpenStreamClientPubkeys.add(clientPubkey);
|
|
448
|
-
}
|
|
449
|
-
const removed = this.sessionStore.removeSession(clientPubkey);
|
|
450
|
-
if (removed || session) {
|
|
451
|
-
this.logger.info('Removed session after open-stream probe timeout', {
|
|
452
|
-
clientPubkey,
|
|
453
|
-
eventId,
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
await this.flushPendingOpenStreamResponse(eventId);
|
|
458
|
-
}
|
|
459
|
-
finalizePendingOpenStreamSessionEviction(eventId) {
|
|
460
|
-
const pendingEviction = this.pendingOpenStreamSessionEvictions.get(eventId);
|
|
461
|
-
if (!pendingEviction) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
this.pendingOpenStreamSessionEvictions.delete(eventId);
|
|
465
|
-
this.evictedOpenStreamClientPubkeys.delete(pendingEviction.clientPubkey);
|
|
466
|
-
}
|
|
467
|
-
findEventIdByProgressToken(progressToken) {
|
|
468
|
-
for (const [clientPubkey] of this.sessionStore.getAllSessions()) {
|
|
469
|
-
const eventId = this.correlationStore.getEventIdByProgressToken(clientPubkey, progressToken);
|
|
470
|
-
if (eventId) {
|
|
471
|
-
return eventId;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return undefined;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Cleans up request correlation for a request that was dropped by middleware.
|
|
478
|
-
*/
|
|
479
|
-
cleanupDroppedRequest(message) {
|
|
480
|
-
if (!isJSONRPCRequest(message)) {
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
this.correlationStore.popEventRoute(String(message.id));
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Handles incoming notifications.
|
|
487
|
-
* @param clientPubkey The client's public key.
|
|
488
|
-
* @param notification The notification message.
|
|
489
|
-
*/
|
|
490
|
-
handleIncomingNotification(clientPubkey, notification) {
|
|
491
|
-
if (isJSONRPCNotification(notification) &&
|
|
492
|
-
notification.method === NOTIFICATIONS_INITIALIZED_METHOD) {
|
|
493
|
-
this.sessionStore.markInitialized(clientPubkey);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
395
|
/**
|
|
497
396
|
* Handles response messages by finding the original request and routing back to client.
|
|
498
397
|
* @param response The JSON-RPC response or error to send.
|
|
@@ -504,183 +403,14 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
504
403
|
return this.listToolsAnnouncementTagsProducers.flatMap((producer) => producer(result));
|
|
505
404
|
}
|
|
506
405
|
async handleResponse(response) {
|
|
507
|
-
|
|
508
|
-
// Handle special announcement responses
|
|
509
|
-
if (response.id === 'announcement') {
|
|
510
|
-
const wasHandled = await this.announcementManager.handleAnnouncementResponse(response);
|
|
511
|
-
if (wasHandled && isJSONRPCResultResponse(response)) {
|
|
512
|
-
if (InitializeResultSchema.safeParse(response.result).success) {
|
|
513
|
-
this.logger.info('Initialized');
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
// Find the event route using O(1) lookup
|
|
519
|
-
const nostrEventId = response.id;
|
|
520
|
-
const existingOpenStreamWriter = this.openStreamWriters.get(nostrEventId);
|
|
521
|
-
if (existingOpenStreamWriter && existingOpenStreamWriter.isActive) {
|
|
522
|
-
this.pendingOpenStreamResponses.set(nostrEventId, response);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
const route = this.correlationStore.popEventRoute(nostrEventId);
|
|
526
|
-
if (!route) {
|
|
527
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`No pending request found for response ID: ${response.id}`));
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
const pendingEviction = this.pendingOpenStreamSessionEvictions.get(nostrEventId);
|
|
531
|
-
const session = (_b = this.sessionStore.getSession(route.clientPubkey)) !== null && _b !== void 0 ? _b : pendingEviction === null || pendingEviction === void 0 ? void 0 : pendingEviction.session;
|
|
532
|
-
if (!session) {
|
|
533
|
-
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`No session found for client: ${route.clientPubkey}`));
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
const parsedListToolsResult = isJSONRPCResultResponse(response)
|
|
537
|
-
? ListToolsResultSchema.safeParse(response.result)
|
|
538
|
-
: null;
|
|
539
|
-
const responseToSend = (parsedListToolsResult === null || parsedListToolsResult === void 0 ? void 0 : parsedListToolsResult.success)
|
|
540
|
-
? {
|
|
541
|
-
...response,
|
|
542
|
-
result: this.applyListToolsResultTransformers(parsedListToolsResult.data),
|
|
543
|
-
}
|
|
544
|
-
: response;
|
|
545
|
-
// Restore the original request ID in the response
|
|
546
|
-
responseToSend.id = route.originalRequestId;
|
|
547
|
-
// CEP-22 Oversized Transfer (proactive path for server responses)
|
|
548
|
-
if (this.oversizedEnabled &&
|
|
549
|
-
route.progressToken &&
|
|
550
|
-
session.supportsOversizedTransfer) {
|
|
551
|
-
// Serialize after restoring id so the client receives the original JSON-RPC id.
|
|
552
|
-
const serialized = JSON.stringify(responseToSend);
|
|
553
|
-
const continuationFrameTags = this.createResponseTags(route.clientPubkey, nostrEventId);
|
|
554
|
-
const startFrameTags = this.buildServerOutboundTags({
|
|
555
|
-
baseTags: continuationFrameTags,
|
|
556
|
-
session,
|
|
557
|
-
});
|
|
558
|
-
const giftWrapKind = this.chooseServerOutboundGiftWrapKind({
|
|
559
|
-
session,
|
|
560
|
-
fallbackWrapKind: route.wrapKind,
|
|
561
|
-
});
|
|
562
|
-
const publishedEventSize = await this.measurePublishedMcpMessageSize(responseToSend, route.clientPubkey, CTXVM_MESSAGES_KIND, startFrameTags, session.isEncrypted, giftWrapKind);
|
|
563
|
-
if (publishedEventSize > this.oversizedThreshold) {
|
|
564
|
-
const chunkSizeBytes = await this.resolveSafeOversizedChunkSize({
|
|
565
|
-
desiredChunkSizeBytes: this.oversizedChunkSize,
|
|
566
|
-
maxPublishedEventBytes: this.oversizedThreshold,
|
|
567
|
-
recipientPublicKey: route.clientPubkey,
|
|
568
|
-
kind: CTXVM_MESSAGES_KIND,
|
|
569
|
-
progressToken: route.progressToken,
|
|
570
|
-
progress: 2,
|
|
571
|
-
tags: continuationFrameTags,
|
|
572
|
-
isEncrypted: session.isEncrypted,
|
|
573
|
-
giftWrapKind,
|
|
574
|
-
});
|
|
575
|
-
try {
|
|
576
|
-
await sendOversizedServerResponse({
|
|
577
|
-
serialized,
|
|
578
|
-
clientPubkey: route.clientPubkey,
|
|
579
|
-
progressToken: route.progressToken,
|
|
580
|
-
startFrameTags,
|
|
581
|
-
continuationFrameTags,
|
|
582
|
-
isEncrypted: session.isEncrypted,
|
|
583
|
-
giftWrapKind,
|
|
584
|
-
}, {
|
|
585
|
-
chunkSizeBytes,
|
|
586
|
-
}, {
|
|
587
|
-
sendMcpMessage: this.sendMcpMessage.bind(this),
|
|
588
|
-
logger: this.logger,
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
catch (error) {
|
|
592
|
-
this.correlationStore.registerEventRoute(nostrEventId, route.clientPubkey, route.originalRequestId, route.progressToken, route.wrapKind);
|
|
593
|
-
throw error;
|
|
594
|
-
}
|
|
595
|
-
finally {
|
|
596
|
-
this.finalizePendingOpenStreamSessionEviction(nostrEventId);
|
|
597
|
-
}
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
// Send the response back to the original requester
|
|
602
|
-
const tags = this.buildServerOutboundTags({
|
|
603
|
-
baseTags: this.createResponseTags(route.clientPubkey, nostrEventId),
|
|
604
|
-
session,
|
|
605
|
-
});
|
|
606
|
-
const giftWrapKind = this.chooseServerOutboundGiftWrapKind({
|
|
607
|
-
session,
|
|
608
|
-
fallbackWrapKind: route.wrapKind,
|
|
609
|
-
});
|
|
610
|
-
// Attach pricing tags to capability list responses so clients can access CEP-8 pricing
|
|
611
|
-
if (isJSONRPCResultResponse(responseToSend)) {
|
|
612
|
-
const result = responseToSend.result;
|
|
613
|
-
if (ListToolsResultSchema.safeParse(result).success ||
|
|
614
|
-
ListResourcesResultSchema.safeParse(result).success ||
|
|
615
|
-
ListResourceTemplatesResultSchema.safeParse(result).success ||
|
|
616
|
-
ListPromptsResultSchema.safeParse(result).success) {
|
|
617
|
-
tags.push(...this.announcementManager.getPricingTags());
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
await this.sendMcpMessage(responseToSend, route.clientPubkey, CTXVM_MESSAGES_KIND, tags, session.isEncrypted, undefined, giftWrapKind);
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
this.correlationStore.registerEventRoute(nostrEventId, route.clientPubkey, route.originalRequestId, route.progressToken, route.wrapKind);
|
|
625
|
-
throw error;
|
|
626
|
-
}
|
|
627
|
-
finally {
|
|
628
|
-
this.finalizePendingOpenStreamSessionEviction(nostrEventId);
|
|
629
|
-
}
|
|
406
|
+
await this.outboundResponseRouter.route(response);
|
|
630
407
|
}
|
|
631
408
|
/**
|
|
632
409
|
* Handles notification messages with routing.
|
|
633
410
|
* @param notification The JSON-RPC notification to send.
|
|
634
411
|
*/
|
|
635
412
|
async handleNotification(notification) {
|
|
636
|
-
|
|
637
|
-
try {
|
|
638
|
-
// Special handling for progress notifications
|
|
639
|
-
// TODO: Add handling for `notifications/resources/updated`, as they need to be associated with an id
|
|
640
|
-
if (isJSONRPCNotification(notification) &&
|
|
641
|
-
notification.method === 'notifications/progress' &&
|
|
642
|
-
((_a = notification.params) === null || _a === void 0 ? void 0 : _a.progressToken)) {
|
|
643
|
-
const token = String(notification.params.progressToken);
|
|
644
|
-
const nostrEventId = this.findEventIdByProgressToken(token);
|
|
645
|
-
if (nostrEventId) {
|
|
646
|
-
const route = this.correlationStore.getEventRoute(nostrEventId);
|
|
647
|
-
if (route) {
|
|
648
|
-
await this.sendNotification(route.clientPubkey, notification, nostrEventId);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
const error = new Error(`No client found for progress token: ${token}`);
|
|
653
|
-
this.logger.error('Progress token not found', { token });
|
|
654
|
-
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
// Use TaskQueue for outbound notification broadcasting to prevent event loop blocking
|
|
658
|
-
for (const [clientPubkey, session,] of this.sessionStore.getAllSessions()) {
|
|
659
|
-
if (session.isInitialized) {
|
|
660
|
-
this.taskQueue.add(async () => {
|
|
661
|
-
try {
|
|
662
|
-
await this.sendNotification(clientPubkey, notification);
|
|
663
|
-
}
|
|
664
|
-
catch (error) {
|
|
665
|
-
this.logger.error('Error sending notification', {
|
|
666
|
-
error: error instanceof Error ? error.message : String(error),
|
|
667
|
-
clientPubkey,
|
|
668
|
-
method: isJSONRPCNotification(notification)
|
|
669
|
-
? notification.method
|
|
670
|
-
: 'unknown',
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
catch (error) {
|
|
678
|
-
this.logger.error('Error in handleNotification', {
|
|
679
|
-
error: error instanceof Error ? error.message : String(error),
|
|
680
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
681
|
-
});
|
|
682
|
-
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error instanceof Error ? error : new Error(String(error)));
|
|
683
|
-
}
|
|
413
|
+
await this.outboundNotificationBroadcaster.broadcast(notification);
|
|
684
414
|
}
|
|
685
415
|
/**
|
|
686
416
|
* Sends a notification to a specific client by their public key.
|
|
@@ -689,7 +419,7 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
689
419
|
* @returns Promise that resolves when the notification is sent.
|
|
690
420
|
*/
|
|
691
421
|
async sendNotification(clientPubkey, notification, correlatedEventId) {
|
|
692
|
-
if (this.
|
|
422
|
+
if (this.openStreamFactory.isClientEvicted(clientPubkey)) {
|
|
693
423
|
throw new Error(`No active session found for client: ${clientPubkey}`);
|
|
694
424
|
}
|
|
695
425
|
const session = this.sessionStore.getSession(clientPubkey);
|
|
@@ -700,11 +430,11 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
700
430
|
if (correlatedEventId) {
|
|
701
431
|
baseTags.push([NOSTR_TAGS.EVENT_ID, correlatedEventId]);
|
|
702
432
|
}
|
|
703
|
-
const tags = this.
|
|
433
|
+
const tags = this.capabilityNegotiator.buildOutboundTags({
|
|
704
434
|
baseTags,
|
|
705
435
|
session,
|
|
706
436
|
});
|
|
707
|
-
const giftWrapKind = this.
|
|
437
|
+
const giftWrapKind = this.capabilityNegotiator.chooseOutboundGiftWrapKind({
|
|
708
438
|
session,
|
|
709
439
|
});
|
|
710
440
|
await this.sendMcpMessage(notification, clientPubkey, CTXVM_MESSAGES_KIND, tags, session.isEncrypted, undefined, giftWrapKind);
|
|
@@ -716,369 +446,18 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
716
446
|
* @param event The incoming Nostr event.
|
|
717
447
|
*/
|
|
718
448
|
async processIncomingEvent(event) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
event.kind === EPHEMERAL_GIFT_WRAP_KIND) {
|
|
723
|
-
if (!this.isGiftWrapKindAllowed(event.kind)) {
|
|
724
|
-
this.logger.debug('Skipping gift wrap due to GiftWrapMode policy', {
|
|
725
|
-
eventId: event.id,
|
|
726
|
-
kind: event.kind,
|
|
727
|
-
});
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
// Deduplicate gift-wrap envelopes before any expensive decryption.
|
|
731
|
-
if (this.seenEventIds.has(event.id)) {
|
|
732
|
-
this.logger.debug('Skipping duplicate gift-wrapped event', {
|
|
733
|
-
eventId: event.id,
|
|
734
|
-
});
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
this.seenEventIds.set(event.id, true);
|
|
738
|
-
await this.handleEncryptedEvent(event);
|
|
739
|
-
}
|
|
740
|
-
else {
|
|
741
|
-
await this.handleUnencryptedEvent(event);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
catch (error) {
|
|
745
|
-
this.logger.error('Error in processIncomingEvent', {
|
|
746
|
-
error: error instanceof Error ? error.message : String(error),
|
|
747
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
748
|
-
eventId: event.id,
|
|
749
|
-
eventKind: event.kind,
|
|
750
|
-
});
|
|
751
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error ? error : new Error(String(error)));
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Handles encrypted (gift-wrapped) events.
|
|
756
|
-
* @param event The incoming gift-wrapped Nostr event.
|
|
757
|
-
*/
|
|
758
|
-
async handleEncryptedEvent(event) {
|
|
759
|
-
var _a;
|
|
760
|
-
if (this.encryptionMode === EncryptionMode.DISABLED) {
|
|
761
|
-
this.logger.error(`Received encrypted message from ${event.pubkey} but encryption is disabled. Ignoring.`);
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
try {
|
|
765
|
-
const decryptedJson = await withTimeout(decryptMessage(event, this.signer), DEFAULT_TIMEOUT_MS, 'Decrypt message timed out');
|
|
766
|
-
const currentEvent = JSON.parse(decryptedJson);
|
|
767
|
-
// Verify the inner event's cryptographic signature to prevent identity
|
|
768
|
-
// forgery. Without this check an attacker can place any pubkey inside
|
|
769
|
-
// the plaintext and bypass allowlists. (Fixes #64)
|
|
770
|
-
if (!verifyEvent(currentEvent)) {
|
|
771
|
-
this.logger.error('Rejecting decrypted inner event with invalid signature', {
|
|
772
|
-
innerEventId: currentEvent.id,
|
|
773
|
-
innerPubkey: currentEvent.pubkey,
|
|
774
|
-
outerEventId: event.id,
|
|
775
|
-
});
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
// Deduplicate decrypted inner events before authorization and dispatch.
|
|
779
|
-
if (this.seenEventIds.has(currentEvent.id)) {
|
|
780
|
-
this.logger.debug('Skipping duplicate decrypted inner event', {
|
|
781
|
-
outerEventId: event.id,
|
|
782
|
-
innerEventId: currentEvent.id,
|
|
783
|
-
});
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
this.seenEventIds.set(currentEvent.id, true);
|
|
787
|
-
await this.authorizeAndProcessEvent(currentEvent, true, event.kind);
|
|
788
|
-
}
|
|
789
|
-
catch (error) {
|
|
790
|
-
this.logger.error('Failed to handle encrypted Nostr event', {
|
|
791
|
-
error: error instanceof Error ? error.message : String(error),
|
|
792
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
793
|
-
eventId: event.id,
|
|
794
|
-
pubkey: event.pubkey,
|
|
795
|
-
});
|
|
796
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error instanceof Error
|
|
797
|
-
? error
|
|
798
|
-
: new Error('Failed to handle encrypted Nostr event'));
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Handles unencrypted events.
|
|
803
|
-
* @param event The incoming Nostr event.
|
|
804
|
-
*/
|
|
805
|
-
async handleUnencryptedEvent(event) {
|
|
806
|
-
if (this.encryptionMode === EncryptionMode.REQUIRED) {
|
|
807
|
-
this.logger.error(`Received unencrypted message from ${event.pubkey} but encryption is required. Ignoring.`);
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
if (!verifyEvent(event)) {
|
|
811
|
-
this.logger.error('Rejecting unencrypted event with invalid signature', {
|
|
812
|
-
eventId: event.id,
|
|
813
|
-
pubkey: event.pubkey,
|
|
814
|
-
});
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
await this.authorizeAndProcessEvent(event, false);
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Authorizes and processes an incoming Nostr event, handling message validation,
|
|
821
|
-
* client authorization, session management, and optional client public key injection.
|
|
822
|
-
* @param event The Nostr event to process.
|
|
823
|
-
* @param isEncrypted Whether the original event was encrypted.
|
|
824
|
-
*/
|
|
825
|
-
async authorizeAndProcessEvent(event, isEncrypted, wrapKind) {
|
|
826
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
827
|
-
try {
|
|
828
|
-
const mcpMessage = this.convertNostrEventToMcpMessage(event);
|
|
449
|
+
const unwrapped = await this.eventPipeline.unwrap(event);
|
|
450
|
+
if (unwrapped) {
|
|
451
|
+
const mcpMessage = this.convertNostrEventToMcpMessage(unwrapped.event);
|
|
829
452
|
if (!mcpMessage) {
|
|
830
453
|
this.logger.error('Skipping invalid Nostr event with malformed JSON content', {
|
|
831
|
-
eventId: event.id,
|
|
832
|
-
pubkey: event.pubkey,
|
|
833
|
-
content: event.content,
|
|
454
|
+
eventId: unwrapped.event.id,
|
|
455
|
+
pubkey: unwrapped.event.pubkey,
|
|
456
|
+
content: unwrapped.event.content,
|
|
834
457
|
});
|
|
835
458
|
return;
|
|
836
459
|
}
|
|
837
|
-
|
|
838
|
-
// Check authorization using the authorization policy
|
|
839
|
-
const authDecision = await this.authorizationPolicy.authorize(event.pubkey, mcpMessage);
|
|
840
|
-
if (!authDecision.allowed) {
|
|
841
|
-
this.logger.error(`Unauthorized message from ${event.pubkey}, message: ${JSON.stringify(mcpMessage)}. Ignoring.`);
|
|
842
|
-
if ('shouldReplyUnauthorized' in authDecision &&
|
|
843
|
-
authDecision.shouldReplyUnauthorized &&
|
|
844
|
-
isJSONRPCRequest(mcpMessage)) {
|
|
845
|
-
const errorResponse = {
|
|
846
|
-
jsonrpc: '2.0',
|
|
847
|
-
id: mcpMessage.id,
|
|
848
|
-
error: {
|
|
849
|
-
code: -32000,
|
|
850
|
-
message: 'Unauthorized',
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
const tags = this.createResponseTags(event.pubkey, event.id);
|
|
854
|
-
this.sendMcpMessage(errorResponse, event.pubkey, CTXVM_MESSAGES_KIND, tags, isEncrypted, undefined, isEncrypted
|
|
855
|
-
? this.giftWrapMode === GiftWrapMode.EPHEMERAL
|
|
856
|
-
? EPHEMERAL_GIFT_WRAP_KIND
|
|
857
|
-
: this.giftWrapMode === GiftWrapMode.PERSISTENT
|
|
858
|
-
? GIFT_WRAP_KIND
|
|
859
|
-
: wrapKind
|
|
860
|
-
: undefined).catch((err) => {
|
|
861
|
-
var _a;
|
|
862
|
-
this.logger.error('Failed to send unauthorized response', {
|
|
863
|
-
error: err instanceof Error ? err.message : String(err),
|
|
864
|
-
pubkey: event.pubkey,
|
|
865
|
-
eventId: event.id,
|
|
866
|
-
});
|
|
867
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to send unauthorized response: ${err}`));
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
const session = this.getOrCreateClientSession(event.pubkey, isEncrypted);
|
|
873
|
-
const hadLearnedOversizedSupport = session.supportsOversizedTransfer;
|
|
874
|
-
const discoveredCapabilities = learnPeerCapabilities(event.tags);
|
|
875
|
-
session.supportsEncryption || (session.supportsEncryption = discoveredCapabilities.supportsEncryption);
|
|
876
|
-
session.supportsEphemeralEncryption || (session.supportsEphemeralEncryption = discoveredCapabilities.supportsEphemeralEncryption);
|
|
877
|
-
session.supportsOversizedTransfer || (session.supportsOversizedTransfer = this.oversizedEnabled &&
|
|
878
|
-
discoveredCapabilities.supportsOversizedTransfer);
|
|
879
|
-
session.supportsOpenStream || (session.supportsOpenStream = this.openStreamEnabled && discoveredCapabilities.supportsOpenStream);
|
|
880
|
-
const shouldSendAccept = !hadLearnedOversizedSupport;
|
|
881
|
-
const forward = async (msg) => {
|
|
882
|
-
var _a, _b;
|
|
883
|
-
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
884
|
-
(_b = this.onmessageWithContext) === null || _b === void 0 ? void 0 : _b.call(this, msg, {
|
|
885
|
-
clientPubkey: event.pubkey,
|
|
886
|
-
});
|
|
887
|
-
return true;
|
|
888
|
-
};
|
|
889
|
-
const clientPmis = event.tags
|
|
890
|
-
.filter((tag) => tag[0] === 'pmi' && typeof tag[1] === 'string')
|
|
891
|
-
.map((tag) => tag[1]);
|
|
892
|
-
const ctx = {
|
|
893
|
-
clientPubkey: event.pubkey,
|
|
894
|
-
clientPmis: clientPmis.length > 0 ? clientPmis : undefined,
|
|
895
|
-
};
|
|
896
|
-
const middlewares = this.inboundMiddlewares;
|
|
897
|
-
const dispatch = async (index, msg) => {
|
|
898
|
-
const mw = middlewares[index];
|
|
899
|
-
if (!mw) {
|
|
900
|
-
return await forward(msg);
|
|
901
|
-
}
|
|
902
|
-
let forwarded = false;
|
|
903
|
-
await mw(msg, ctx, async (nextMsg) => {
|
|
904
|
-
forwarded = await dispatch(index + 1, nextMsg);
|
|
905
|
-
});
|
|
906
|
-
return forwarded;
|
|
907
|
-
};
|
|
908
|
-
if (isJSONRPCRequest(inboundMessage)) {
|
|
909
|
-
this.handleIncomingRequest(event, event.id, inboundMessage, event.pubkey, wrapKind);
|
|
910
|
-
if (this.shouldInjectRequestEventId) {
|
|
911
|
-
injectRequestEventId(inboundMessage, event.id);
|
|
912
|
-
}
|
|
913
|
-
if (this.injectClientPubkey) {
|
|
914
|
-
injectClientPubkey(inboundMessage, event.pubkey);
|
|
915
|
-
}
|
|
916
|
-
const openStreamWriter = this.openStreamWriters.get(event.id);
|
|
917
|
-
if (openStreamWriter) {
|
|
918
|
-
const params = (_a = inboundMessage.params) !== null && _a !== void 0 ? _a : {};
|
|
919
|
-
inboundMessage.params = params;
|
|
920
|
-
const meta = (_b = params._meta) !== null && _b !== void 0 ? _b : {};
|
|
921
|
-
params._meta = meta;
|
|
922
|
-
meta.stream = openStreamWriter;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
else if (isJSONRPCNotification(inboundMessage)) {
|
|
926
|
-
this.handleIncomingNotification(event.pubkey, inboundMessage);
|
|
927
|
-
if (inboundMessage.method === 'notifications/progress' &&
|
|
928
|
-
OpenStreamReceiver.isOpenStreamFrame(inboundMessage)) {
|
|
929
|
-
const frame = (_c = inboundMessage.params) === null || _c === void 0 ? void 0 : _c.cvm;
|
|
930
|
-
if ((frame === null || frame === void 0 ? void 0 : frame.frameType) === 'abort') {
|
|
931
|
-
const progressToken = String((_e = (_d = inboundMessage.params) === null || _d === void 0 ? void 0 : _d.progressToken) !== null && _e !== void 0 ? _e : '');
|
|
932
|
-
const eventId = this.correlationStore.getEventIdByProgressToken(event.pubkey, progressToken);
|
|
933
|
-
const writer = eventId
|
|
934
|
-
? this.openStreamWriters.get(eventId)
|
|
935
|
-
: undefined;
|
|
936
|
-
if (writer) {
|
|
937
|
-
void writer.abort(frame.reason).catch((err) => {
|
|
938
|
-
var _a;
|
|
939
|
-
this.logger.error('Open stream abort propagation failed (server)', {
|
|
940
|
-
error: err instanceof Error ? err.message : String(err),
|
|
941
|
-
pubkey: event.pubkey,
|
|
942
|
-
progressToken,
|
|
943
|
-
});
|
|
944
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error ? err : new Error(String(err)));
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
if ((frame === null || frame === void 0 ? void 0 : frame.frameType) === 'ping') {
|
|
950
|
-
const progressToken = String((_g = (_f = inboundMessage.params) === null || _f === void 0 ? void 0 : _f.progressToken) !== null && _g !== void 0 ? _g : '');
|
|
951
|
-
const nonce = 'nonce' in frame && typeof frame.nonce === 'string'
|
|
952
|
-
? frame.nonce
|
|
953
|
-
: '';
|
|
954
|
-
const eventId = this.correlationStore.getEventIdByProgressToken(event.pubkey, progressToken);
|
|
955
|
-
const writer = eventId
|
|
956
|
-
? this.openStreamWriters.get(eventId)
|
|
957
|
-
: undefined;
|
|
958
|
-
if (writer) {
|
|
959
|
-
void writer.pong(nonce).catch((err) => {
|
|
960
|
-
var _a;
|
|
961
|
-
this.logger.error('Open stream ping handling failed (server)', {
|
|
962
|
-
error: err instanceof Error ? err.message : String(err),
|
|
963
|
-
pubkey: event.pubkey,
|
|
964
|
-
progressToken,
|
|
965
|
-
});
|
|
966
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error ? err : new Error(String(err)));
|
|
967
|
-
});
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
this.openStreamReceiver
|
|
972
|
-
.processFrame(inboundMessage)
|
|
973
|
-
.then(async () => {
|
|
974
|
-
var _a, _b, _c, _d;
|
|
975
|
-
const frameType = frame === null || frame === void 0 ? void 0 : frame.frameType;
|
|
976
|
-
if (frameType === 'start' && session.supportsOpenStream) {
|
|
977
|
-
await this.sendNotification(event.pubkey, {
|
|
978
|
-
jsonrpc: '2.0',
|
|
979
|
-
method: 'notifications/progress',
|
|
980
|
-
params: buildOpenStreamAcceptFrame({
|
|
981
|
-
progressToken: String((_b = (_a = inboundMessage.params) === null || _a === void 0 ? void 0 : _a.progressToken) !== null && _b !== void 0 ? _b : ''),
|
|
982
|
-
progress: Number((_d = (_c = inboundMessage.params) === null || _c === void 0 ? void 0 : _c.progress) !== null && _d !== void 0 ? _d : 0) + 1,
|
|
983
|
-
}),
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
})
|
|
987
|
-
.catch((err) => {
|
|
988
|
-
var _a;
|
|
989
|
-
this.logger.error('Open stream error (server)', {
|
|
990
|
-
error: err instanceof Error ? err.message : String(err),
|
|
991
|
-
pubkey: event.pubkey,
|
|
992
|
-
});
|
|
993
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error ? err : new Error(String(err)));
|
|
994
|
-
});
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
if (inboundMessage.method === 'notifications/progress' &&
|
|
998
|
-
OversizedTransferReceiver.isOversizedFrame(inboundMessage)) {
|
|
999
|
-
this.oversizedReceiver
|
|
1000
|
-
.processFrame(inboundMessage)
|
|
1001
|
-
.then(async (synthetic) => {
|
|
1002
|
-
var _a, _b, _c, _d;
|
|
1003
|
-
if (synthetic === null) {
|
|
1004
|
-
if (((_b = (_a = inboundMessage.params) === null || _a === void 0 ? void 0 : _a.cvm) === null || _b === void 0 ? void 0 : _b.frameType) === 'start' &&
|
|
1005
|
-
shouldSendAccept) {
|
|
1006
|
-
await sendAcceptFrame({
|
|
1007
|
-
clientPubkey: event.pubkey,
|
|
1008
|
-
progressToken: String((_d = (_c = inboundMessage.params) === null || _c === void 0 ? void 0 : _c.progressToken) !== null && _d !== void 0 ? _d : ''),
|
|
1009
|
-
}, {
|
|
1010
|
-
sendNotification: this.sendNotification.bind(this),
|
|
1011
|
-
}).catch((err) => {
|
|
1012
|
-
this.logger.error('Failed to send oversized accept', {
|
|
1013
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1014
|
-
});
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
if (isJSONRPCRequest(synthetic)) {
|
|
1020
|
-
this.handleIncomingRequest(event, event.id, synthetic, event.pubkey, wrapKind);
|
|
1021
|
-
if (this.shouldInjectRequestEventId) {
|
|
1022
|
-
injectRequestEventId(synthetic, event.id);
|
|
1023
|
-
}
|
|
1024
|
-
if (this.injectClientPubkey) {
|
|
1025
|
-
injectClientPubkey(synthetic, event.pubkey);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
else if (isJSONRPCNotification(synthetic)) {
|
|
1029
|
-
this.handleIncomingNotification(event.pubkey, synthetic);
|
|
1030
|
-
}
|
|
1031
|
-
void dispatch(0, synthetic)
|
|
1032
|
-
.then((forwarded) => {
|
|
1033
|
-
if (!forwarded) {
|
|
1034
|
-
this.cleanupDroppedRequest(synthetic);
|
|
1035
|
-
}
|
|
1036
|
-
})
|
|
1037
|
-
.catch((err) => {
|
|
1038
|
-
var _a;
|
|
1039
|
-
this.logger.error('Error dispatching reassembled oversized message', {
|
|
1040
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1041
|
-
pubkey: event.pubkey,
|
|
1042
|
-
});
|
|
1043
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error
|
|
1044
|
-
? err
|
|
1045
|
-
: new Error('oversized dispatch failed'));
|
|
1046
|
-
});
|
|
1047
|
-
})
|
|
1048
|
-
.catch((err) => {
|
|
1049
|
-
var _a;
|
|
1050
|
-
this.logger.error('Oversized transfer error (server)', {
|
|
1051
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1052
|
-
});
|
|
1053
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error ? err : new Error(String(err)));
|
|
1054
|
-
});
|
|
1055
|
-
return;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
void dispatch(0, inboundMessage)
|
|
1059
|
-
.then((forwarded) => {
|
|
1060
|
-
if (!forwarded) {
|
|
1061
|
-
this.cleanupDroppedRequest(inboundMessage);
|
|
1062
|
-
}
|
|
1063
|
-
})
|
|
1064
|
-
.catch((err) => {
|
|
1065
|
-
var _a;
|
|
1066
|
-
this.logger.error('Error in inboundMiddleware chain', {
|
|
1067
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1068
|
-
eventId: event.id,
|
|
1069
|
-
pubkey: event.pubkey,
|
|
1070
|
-
});
|
|
1071
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err instanceof Error ? err : new Error('inboundMiddleware failed'));
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
this.logger.error('Error in authorizeAndProcessEvent', {
|
|
1076
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1077
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
1078
|
-
eventId: event.id,
|
|
1079
|
-
pubkey: event.pubkey,
|
|
1080
|
-
});
|
|
1081
|
-
(_h = this.onerror) === null || _h === void 0 ? void 0 : _h.call(this, error instanceof Error ? error : new Error(String(error)));
|
|
460
|
+
await this.inboundCoordinator.authorizeAndProcessEvent(unwrapped.event, unwrapped.isEncrypted, mcpMessage, unwrapped.wrapKind);
|
|
1082
461
|
}
|
|
1083
462
|
}
|
|
1084
463
|
/**
|
|
@@ -1090,10 +469,11 @@ export class NostrServerTransport extends BaseNostrTransport {
|
|
|
1090
469
|
sessionStore: this.sessionStore,
|
|
1091
470
|
correlationStore: this.correlationStore,
|
|
1092
471
|
oversizedReceiver: this.oversizedReceiver,
|
|
1093
|
-
openStreamReceiver: this.
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
472
|
+
openStreamReceiver: this.openStreamFactory.getReceiver(),
|
|
473
|
+
openStreamWriters: this.openStreamFactory.getWritersMap(),
|
|
474
|
+
pendingOpenStreamResponses: this.openStreamFactory.getPendingResponsesMap(),
|
|
475
|
+
openStreamFactory: this.openStreamFactory,
|
|
476
|
+
inboundCoordinator: this.inboundCoordinator,
|
|
1097
477
|
};
|
|
1098
478
|
}
|
|
1099
479
|
}
|