nostr-bray 1.28.0 → 1.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +33 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -142,12 +142,10 @@ if (config.transport === 'stdio') {
142
142
  else {
143
143
  const { createServer } = await import('node:http');
144
144
  const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
145
- const { randomUUID } = await import('node:crypto');
145
+ const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
146
+ const { randomUUID, timingSafeEqual } = await import('node:crypto');
146
147
  const token = process.env.BRAY_HTTP_TOKEN ?? randomUUID();
147
148
  console.error(`nostr-bray HTTP auth token: ${token}`);
148
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
149
- await server.connect(transport);
150
- const { timingSafeEqual } = await import('node:crypto');
151
149
  const expectedAuth = Buffer.from(`Bearer ${token}`);
152
150
  // Sliding window rate limiter (per-IP)
153
151
  const rateLimits = new Map();
@@ -163,6 +161,25 @@ else {
163
161
  entry.count++;
164
162
  return entry.count <= RATE_LIMIT;
165
163
  }
164
+ // Session map — each MCP session gets its own transport.
165
+ // The underlying McpServer is reused across sessions: when a session ends its
166
+ // transport closes, which sets server._transport = undefined, allowing the next
167
+ // session to call server.connect(newTransport).
168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
+ const sessions = new Map();
170
+ async function createSession() {
171
+ // Close the current server connection so it can be reconnected below.
172
+ if (server.isConnected())
173
+ await server.close();
174
+ const t = new StreamableHTTPServerTransport({
175
+ sessionIdGenerator: () => randomUUID(),
176
+ onsessioninitialized: (id) => { sessions.set(id, t); },
177
+ });
178
+ t.onclose = () => { if (t.sessionId)
179
+ sessions.delete(t.sessionId); };
180
+ await server.connect(t);
181
+ return t;
182
+ }
166
183
  const httpServer = createServer(async (req, res) => {
167
184
  const clientIp = req.socket.remoteAddress ?? 'unknown';
168
185
  // Rate limiting
@@ -187,6 +204,7 @@ else {
187
204
  return;
188
205
  }
189
206
  // Parse body for POST requests (1MB limit)
207
+ let body;
190
208
  if (req.method === 'POST') {
191
209
  const MAX_BODY = 1_048_576;
192
210
  const chunks = [];
@@ -200,7 +218,6 @@ else {
200
218
  }
201
219
  chunks.push(chunk);
202
220
  }
203
- let body;
204
221
  try {
205
222
  body = JSON.parse(Buffer.concat(chunks).toString());
206
223
  }
@@ -209,11 +226,19 @@ else {
209
226
  res.end(JSON.stringify({ error: 'Invalid JSON' }));
210
227
  return;
211
228
  }
212
- await transport.handleRequest(req, res, body);
213
229
  }
214
- else {
215
- await transport.handleRequest(req, res);
230
+ // Route to existing session or create a new one for initialize requests
231
+ const sessionId = req.headers['mcp-session-id'];
232
+ let transport = sessionId ? sessions.get(sessionId) : undefined;
233
+ if (!transport) {
234
+ if (req.method !== 'POST' || !isInitializeRequest(body)) {
235
+ res.writeHead(400, { 'Content-Type': 'application/json' });
236
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: no active session' }, id: null }));
237
+ return;
238
+ }
239
+ transport = await createSession();
216
240
  }
241
+ await transport.handleRequest(req, res, body);
217
242
  });
218
243
  httpServer.listen(config.port, config.bindAddress, () => {
219
244
  console.error(`nostr-bray HTTP on ${config.bindAddress}:${config.port}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.28.0",
3
+ "version": "1.28.1",
4
4
  "mcpName": "dev.forgesworn/bray",
5
5
  "description": "Trust-aware Nostr for AI and humans. Three dimensions of trust -- verification, proximity, and access -- woven into every interaction.",
6
6
  "type": "module",