@kya-os/mcp-i-cloudflare 1.3.6 → 1.3.7

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 (43) hide show
  1. package/dist/adapter.d.ts +3 -3
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adapter.js +170 -150
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/cache/kv-tool-protection-cache.d.ts.map +1 -1
  6. package/dist/cache/kv-tool-protection-cache.js +5 -0
  7. package/dist/cache/kv-tool-protection-cache.js.map +1 -1
  8. package/dist/config.d.ts +275 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +108 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +20 -7
  15. package/dist/index.js.map +1 -1
  16. package/dist/proof-generator.d.ts +2 -0
  17. package/dist/proof-generator.d.ts.map +1 -1
  18. package/dist/proof-generator.js +1 -0
  19. package/dist/proof-generator.js.map +1 -1
  20. package/dist/providers/identity.d.ts +33 -0
  21. package/dist/providers/identity.d.ts.map +1 -0
  22. package/dist/providers/identity.js +62 -0
  23. package/dist/providers/identity.js.map +1 -0
  24. package/dist/providers/kv-identity.d.ts +42 -0
  25. package/dist/providers/kv-identity.d.ts.map +1 -0
  26. package/dist/providers/kv-identity.js +109 -0
  27. package/dist/providers/kv-identity.js.map +1 -0
  28. package/dist/providers/nonce-cache.d.ts +17 -0
  29. package/dist/providers/nonce-cache.d.ts.map +1 -0
  30. package/dist/providers/nonce-cache.js +58 -0
  31. package/dist/providers/nonce-cache.js.map +1 -0
  32. package/dist/runtime/index.d.ts +31 -0
  33. package/dist/runtime/index.d.ts.map +1 -0
  34. package/dist/runtime/index.js +72 -0
  35. package/dist/runtime/index.js.map +1 -0
  36. package/dist/runtime/oauth-handler.d.ts.map +1 -1
  37. package/dist/runtime/oauth-handler.js +27 -21
  38. package/dist/runtime/oauth-handler.js.map +1 -1
  39. package/dist/runtime.d.ts +4 -2
  40. package/dist/runtime.d.ts.map +1 -1
  41. package/dist/runtime.js +7 -2
  42. package/dist/runtime.js.map +1 -1
  43. package/package.json +9 -4
package/dist/adapter.js CHANGED
@@ -8,9 +8,9 @@
8
8
  * This implementation avoids Node.js dependencies by implementing
9
9
  * the MCP protocol directly.
10
10
  */
11
- import { createCloudflareRuntime } from './index';
12
- import { WELL_KNOWN_CORS_HEADERS, MCP_CORS_HEADERS, PREFLIGHT_CORS_HEADERS } from '@kya-os/mcp-i-core';
13
- import { KVProofArchive } from './storage/kv-proof-archive';
11
+ import { createCloudflareRuntime } from "./index";
12
+ import { WELL_KNOWN_CORS_HEADERS, MCP_CORS_HEADERS, PREFLIGHT_CORS_HEADERS, } from "@kya-os/mcp-i-core";
13
+ import { KVProofArchive } from "./storage/kv-proof-archive";
14
14
  /**
15
15
  * Lightweight MCP protocol implementation for Cloudflare Workers
16
16
  */
@@ -22,7 +22,7 @@ class CloudflareMCPServer {
22
22
  constructor(runtime, serverInfo, tools = [], proofArchive) {
23
23
  this.runtime = runtime;
24
24
  this.serverInfo = serverInfo;
25
- this.tools = new Map(tools.map(t => [t.name, t]));
25
+ this.tools = new Map(tools.map((t) => [t.name, t]));
26
26
  this.proofArchive = proofArchive;
27
27
  }
28
28
  /**
@@ -32,34 +32,34 @@ class CloudflareMCPServer {
32
32
  const { jsonrpc, id, method, params } = request;
33
33
  try {
34
34
  // Initialize/describe request
35
- if (method === 'initialize') {
35
+ if (method === "initialize") {
36
36
  return {
37
- jsonrpc: '2.0',
37
+ jsonrpc: "2.0",
38
38
  id,
39
39
  result: {
40
- protocolVersion: '2025-06-18',
40
+ protocolVersion: "2025-06-18",
41
41
  capabilities: {
42
- tools: {}
42
+ tools: {},
43
43
  },
44
- serverInfo: this.serverInfo
45
- }
44
+ serverInfo: this.serverInfo,
45
+ },
46
46
  };
47
47
  }
48
48
  // List tools
49
- if (method === 'tools/list') {
50
- const tools = Array.from(this.tools.values()).map(t => ({
49
+ if (method === "tools/list") {
50
+ const tools = Array.from(this.tools.values()).map((t) => ({
51
51
  name: t.name,
52
52
  description: t.description,
53
- inputSchema: t.inputSchema
53
+ inputSchema: t.inputSchema,
54
54
  }));
55
55
  return {
56
- jsonrpc: '2.0',
56
+ jsonrpc: "2.0",
57
57
  id,
58
- result: { tools }
58
+ result: { tools },
59
59
  };
60
60
  }
61
61
  // Call tool with identity/proof wrapping
62
- if (method === 'tools/call') {
62
+ if (method === "tools/call") {
63
63
  const toolName = params.name;
64
64
  const tool = this.tools.get(toolName);
65
65
  if (!tool) {
@@ -73,11 +73,11 @@ class CloudflareMCPServer {
73
73
  const randomSuffix = Math.random().toString(36).substring(2, 10);
74
74
  session = {
75
75
  id: `ephemeral-${timestamp}-${randomSuffix}`,
76
- audience: params.audience || 'mcp-client',
76
+ audience: params.audience || "mcp-client",
77
77
  clientDid: params.clientDid,
78
78
  agentDid: (await this.runtime.getIdentity()).did,
79
79
  createdAt: timestamp,
80
- expiresAt: timestamp + (30 * 60 * 1000), // 30 minutes
80
+ expiresAt: timestamp + 30 * 60 * 1000, // 30 minutes
81
81
  // Extract delegation token from MCP request if provided
82
82
  delegationToken: params.delegationToken,
83
83
  consentProof: params.consentProof,
@@ -99,7 +99,8 @@ class CloudflareMCPServer {
99
99
  // Check if runtime has tool protection configured with requiredScopes
100
100
  if (runtimeConfig?.delegation?.toolProtections?.[toolName]) {
101
101
  const toolProtection = runtimeConfig.delegation.toolProtections[toolName];
102
- if (toolProtection.requiredScopes && toolProtection.requiredScopes.length > 0) {
102
+ if (toolProtection.requiredScopes &&
103
+ toolProtection.requiredScopes.length > 0) {
103
104
  // Use the first required scope as the scopeId (e.g., "files:read")
104
105
  scopeId = toolProtection.requiredScopes[0];
105
106
  }
@@ -125,26 +126,26 @@ class CloudflareMCPServer {
125
126
  }
126
127
  catch (error) {
127
128
  // Log error but don't fail the request
128
- console.error('Failed to store proof:', error);
129
+ console.error("Failed to store proof:", error);
129
130
  }
130
131
  }
131
132
  // Return result with detached proof (matching MCP-I pattern)
132
133
  return {
133
- jsonrpc: '2.0',
134
+ jsonrpc: "2.0",
134
135
  id,
135
136
  result: {
136
137
  result, // Tool execution result
137
- proof // Detached proof with sessionId, audience, timestamp, etc.
138
- }
138
+ proof, // Detached proof with sessionId, audience, timestamp, etc.
139
+ },
139
140
  };
140
141
  }
141
142
  // Handle MCP-I specific handshake
142
- if (method === 'handshake') {
143
+ if (method === "handshake") {
143
144
  const handshakeResult = await this.runtime.handleHandshake(params || {});
144
145
  return {
145
- jsonrpc: '2.0',
146
+ jsonrpc: "2.0",
146
147
  id,
147
- result: handshakeResult
148
+ result: handshakeResult,
148
149
  };
149
150
  }
150
151
  // Unknown method
@@ -152,31 +153,43 @@ class CloudflareMCPServer {
152
153
  }
153
154
  catch (error) {
154
155
  // Check if this is a DelegationRequiredError - include consent URL in response
155
- if (error.name === 'DelegationRequiredError' || error.constructor?.name === 'DelegationRequiredError') {
156
+ if (error &&
157
+ (error.name === "DelegationRequiredError" ||
158
+ error.constructor?.name === "DelegationRequiredError")) {
156
159
  return {
157
- jsonrpc: '2.0',
160
+ jsonrpc: "2.0",
158
161
  id,
159
162
  error: {
160
163
  code: -32001, // Custom error code for authorization required
161
- message: error.message || `Delegation required for tool "${error.toolName || 'unknown'}"`,
164
+ message: error.message ||
165
+ `Delegation required for tool "${error.toolName || "unknown"}"`,
162
166
  data: {
163
167
  toolName: error.toolName,
164
168
  requiredScopes: error.requiredScopes || [],
165
169
  consentUrl: error.consentUrl,
170
+ resumeToken: error.resumeToken, // Include resume token for resumption
166
171
  // MCP-I specific: provide authorization URL for client to display
167
172
  authorizationUrl: error.consentUrl,
168
- }
169
- }
173
+ },
174
+ },
170
175
  };
171
176
  }
172
177
  // Generic error handler
178
+ let errorMessage = "Internal error";
179
+ if (error) {
180
+ // Only use error message if it's an Error object with a message property
181
+ if (typeof error === 'object' && error instanceof Error && error.message) {
182
+ errorMessage = error.message;
183
+ }
184
+ // For non-Error objects (strings, null, etc.), use default "Internal error"
185
+ }
173
186
  return {
174
- jsonrpc: '2.0',
187
+ jsonrpc: "2.0",
175
188
  id,
176
189
  error: {
177
190
  code: -32603,
178
- message: error.message || 'Internal error'
179
- }
191
+ message: errorMessage,
192
+ },
180
193
  };
181
194
  }
182
195
  }
@@ -193,12 +206,14 @@ export function createMCPICloudflareAdapter(config) {
193
206
  const runtime = createCloudflareRuntime(config);
194
207
  // Server info
195
208
  const serverInfo = config.serverInfo || {
196
- name: 'MCP-I Cloudflare Server',
197
- version: '1.0.0'
209
+ name: "MCP-I Cloudflare Server",
210
+ version: "1.0.0",
198
211
  };
199
212
  // Initialize proof archive if PROOF_ARCHIVE KV is available
200
213
  const env = config.env;
201
- const proofArchive = env.PROOF_ARCHIVE ? new KVProofArchive(env.PROOF_ARCHIVE) : undefined;
214
+ const proofArchive = env.PROOF_ARCHIVE
215
+ ? new KVProofArchive(env.PROOF_ARCHIVE)
216
+ : undefined;
202
217
  // Create lightweight MCP server
203
218
  const server = new CloudflareMCPServer(runtime, serverInfo, config.tools || [], proofArchive);
204
219
  // Return fetch handler
@@ -211,94 +226,97 @@ export function createMCPICloudflareAdapter(config) {
211
226
  const url = new URL(request.url);
212
227
  const identity = await runtime.getIdentity();
213
228
  // Health check endpoint
214
- if (url.pathname === '/health') {
229
+ if (url.pathname === "/health") {
215
230
  return new Response(JSON.stringify({
216
- status: 'healthy',
231
+ status: "healthy",
217
232
  timestamp: new Date().toISOString(),
218
233
  identity: {
219
- did: identity.did
234
+ did: identity.did,
220
235
  },
221
236
  transport: {
222
- http: '/mcp',
223
- sse: '/sse',
224
- streamableHttp: '/mcp'
237
+ http: "/mcp",
238
+ sse: "/sse",
239
+ streamableHttp: "/mcp",
225
240
  },
226
- features: ['identity', 'proof', 'delegation', 'audit']
241
+ features: ["identity", "proof", "delegation", "audit"],
227
242
  }), {
228
- headers: { 'Content-Type': 'application/json' }
243
+ headers: { "Content-Type": "application/json" },
229
244
  });
230
245
  }
231
246
  // Well-known endpoints for identity verification
232
- if (url.pathname.startsWith('/.well-known/')) {
247
+ if (url.pathname.startsWith("/.well-known/")) {
233
248
  const handler = runtime.createWellKnownHandler({
234
249
  serviceName: serverInfo.name,
235
- serviceEndpoint: request.headers.get('host') || 'localhost'
250
+ serviceEndpoint: request.headers.get("host") || "localhost",
236
251
  });
237
252
  const result = await handler(url.pathname);
238
253
  if (result) {
239
- if (result.status && result.headers && result.body) {
254
+ // Check if it's a WellKnownResponse (has status, headers, body)
255
+ if ("status" in result && "headers" in result && "body" in result) {
240
256
  return new Response(result.body, {
241
257
  status: result.status,
242
258
  headers: {
243
259
  ...result.headers,
244
- ...WELL_KNOWN_CORS_HEADERS
245
- }
260
+ ...WELL_KNOWN_CORS_HEADERS,
261
+ },
246
262
  });
247
263
  }
264
+ // Otherwise it's an MCPIdentity object, return as JSON
248
265
  return new Response(JSON.stringify(result), {
266
+ status: 200,
249
267
  headers: {
250
- 'Content-Type': 'application/json',
251
- ...WELL_KNOWN_CORS_HEADERS
252
- }
268
+ "Content-Type": "application/json",
269
+ ...WELL_KNOWN_CORS_HEADERS,
270
+ },
253
271
  });
254
272
  }
255
273
  }
256
274
  // Debug endpoint (development only)
257
- if (url.pathname === '/debug' && config.environment !== 'production') {
275
+ if (url.pathname === "/debug" && config.environment !== "production") {
258
276
  const debugHandler = runtime.createDebugEndpoint();
259
277
  if (debugHandler) {
260
278
  const result = await debugHandler();
261
279
  return new Response(JSON.stringify(result), {
262
- headers: { 'Content-Type': 'application/json' }
280
+ headers: { "Content-Type": "application/json" },
263
281
  });
264
282
  }
265
283
  }
266
284
  // Main MCP protocol endpoint - supports both POST and SSE
267
- if (url.pathname === '/mcp') {
285
+ if (url.pathname === "/mcp") {
268
286
  // Handle SSE streaming (for Claude Desktop via mcp-remote)
269
- if (request.method === 'GET') {
270
- const acceptHeader = request.headers.get('Accept') || '';
271
- if (acceptHeader.includes('text/event-stream')) {
287
+ if (request.method === "GET") {
288
+ const acceptHeader = request.headers.get("Accept") || "";
289
+ if (acceptHeader.includes("text/event-stream")) {
272
290
  return handleSSE(server, runtime);
273
291
  }
274
292
  // GET requests without SSE Accept header should return Method Not Allowed
275
293
  // MCP protocol primarily uses POST for JSON-RPC
276
- return new Response('Method Not Allowed: Use POST for JSON-RPC or GET with Accept: text/event-stream for SSE', {
294
+ return new Response("Method Not Allowed: Use POST for JSON-RPC or GET with Accept: text/event-stream for SSE", {
277
295
  status: 405,
278
296
  headers: {
279
- 'Content-Type': 'text/plain',
280
- 'Allow': 'POST, GET',
281
- ...MCP_CORS_HEADERS
282
- }
297
+ "Content-Type": "text/plain",
298
+ Allow: "POST, GET",
299
+ ...MCP_CORS_HEADERS,
300
+ },
283
301
  });
284
302
  }
285
303
  // Handle DELETE requests (session termination)
286
- if (request.method === 'DELETE') {
304
+ if (request.method === "DELETE") {
287
305
  // MCP Inspector and clients may send DELETE to terminate sessions
288
306
  // Return 200 OK with empty body (some environments don't support 204 well)
289
- return new Response('', {
307
+ return new Response("", {
290
308
  status: 200,
291
309
  headers: {
292
- 'Content-Length': '0',
293
- ...MCP_CORS_HEADERS
294
- }
310
+ "Content-Length": "0",
311
+ ...MCP_CORS_HEADERS,
312
+ },
295
313
  });
296
314
  }
297
315
  // Handle standard POST (existing functionality)
298
- if (request.method === 'POST') {
299
- const acceptHeader = request.headers.get('Accept') || '';
316
+ if (request.method === "POST") {
317
+ const acceptHeader = request.headers.get("Accept") || "";
300
318
  // Check if client wants SSE response
301
- if (acceptHeader.includes('text/event-stream')) {
319
+ if (acceptHeader.includes("text/event-stream")) {
302
320
  // Return SSE stream for POST (Streamable HTTP mode)
303
321
  return handleSSEPost(server, runtime, request);
304
322
  }
@@ -310,100 +328,102 @@ export function createMCPICloudflareAdapter(config) {
310
328
  const response = await server.handleRequest(jsonRpcRequest);
311
329
  return new Response(JSON.stringify(response), {
312
330
  headers: {
313
- 'Content-Type': 'application/json',
314
- ...MCP_CORS_HEADERS
315
- }
331
+ "Content-Type": "application/json",
332
+ ...MCP_CORS_HEADERS,
333
+ },
316
334
  });
317
335
  }
318
336
  catch (error) {
319
337
  return new Response(JSON.stringify({
320
- jsonrpc: '2.0',
338
+ jsonrpc: "2.0",
321
339
  error: {
322
340
  code: -32603,
323
- message: error.message || 'Internal error'
324
- }
341
+ message: error.message || "Internal error",
342
+ },
325
343
  }), {
326
344
  status: 500,
327
- headers: { 'Content-Type': 'application/json' }
345
+ headers: { "Content-Type": "application/json" },
328
346
  });
329
347
  }
330
348
  }
331
349
  }
332
350
  // SSE endpoint for backward compatibility and Claude Desktop
333
- if (url.pathname === '/sse') {
334
- if (request.method === 'GET') {
351
+ if (url.pathname === "/sse") {
352
+ if (request.method === "GET") {
335
353
  return handleSSE(server, runtime);
336
354
  }
337
- if (request.method === 'POST') {
355
+ if (request.method === "POST") {
338
356
  return handleSSEPost(server, runtime, request);
339
357
  }
340
358
  }
341
359
  // Standalone verify endpoint for external verification
342
- if (url.pathname === '/verify' && request.method === 'POST') {
360
+ if (url.pathname === "/verify" && request.method === "POST") {
343
361
  try {
344
- const { data, proof } = await request.json();
362
+ const { data, proof } = (await request.json());
345
363
  const isValid = await runtime.verifyProof(data, proof);
346
364
  return new Response(JSON.stringify({
347
365
  verified: isValid,
348
- agent: isValid ? {
349
- did: proof.did,
350
- timestamp: proof.timestamp,
351
- sessionId: proof.sessionId
352
- } : null
366
+ agent: isValid
367
+ ? {
368
+ did: proof.did,
369
+ timestamp: proof.timestamp,
370
+ sessionId: proof.sessionId,
371
+ }
372
+ : null,
353
373
  }), {
354
- headers: { 'Content-Type': 'application/json' }
374
+ headers: { "Content-Type": "application/json" },
355
375
  });
356
376
  }
357
377
  catch (error) {
358
378
  return new Response(JSON.stringify({
359
- error: error.message || 'Verification failed'
379
+ error: error.message || "Verification failed",
360
380
  }), {
361
381
  status: 400,
362
- headers: { 'Content-Type': 'application/json' }
382
+ headers: { "Content-Type": "application/json" },
363
383
  });
364
384
  }
365
385
  }
366
386
  // Admin endpoints for viewing nonces and proofs
367
- if (url.pathname.startsWith('/admin/')) {
387
+ if (url.pathname.startsWith("/admin/")) {
368
388
  // Only allow in development or if explicitly configured
369
- if (config.environment === 'production') {
389
+ if (config.environment === "production") {
370
390
  return new Response(JSON.stringify({
371
- error: 'Admin endpoints disabled in production'
391
+ error: "Admin endpoints disabled in production",
372
392
  }), {
373
393
  status: 403,
374
- headers: { 'Content-Type': 'application/json' }
394
+ headers: { "Content-Type": "application/json" },
375
395
  });
376
396
  }
377
397
  const env = config.env;
378
398
  // GET /admin/nonces - List active nonces
379
- if (url.pathname === '/admin/nonces') {
399
+ if (url.pathname === "/admin/nonces") {
380
400
  try {
381
401
  // Use KV list to get nonce keys
382
- const noncesList = await env.NONCE_CACHE.list({ prefix: 'nonce:' });
402
+ const noncesList = await env.NONCE_CACHE.list({ prefix: "nonce:" });
383
403
  const nonces = [];
384
404
  for (const key of noncesList.keys) {
385
405
  const value = await env.NONCE_CACHE.get(key.name);
386
406
  if (value) {
387
407
  nonces.push({
388
- nonce: key.name.replace('nonce:', ''),
389
- storedAt: key.metadata?.storedAt || 'unknown',
390
- expiresAt: key.expiration
408
+ nonce: key.name.replace("nonce:", ""),
409
+ storedAt: key.metadata?.storedAt || "unknown",
410
+ expiresAt: key.expiration,
391
411
  });
392
412
  }
393
413
  }
394
414
  return new Response(JSON.stringify({
395
415
  total: nonces.length,
396
- nonces
416
+ nonces,
397
417
  }), {
398
- headers: { 'Content-Type': 'application/json' }
418
+ headers: { "Content-Type": "application/json" },
399
419
  });
400
420
  }
401
421
  catch (error) {
402
422
  return new Response(JSON.stringify({
403
- error: error.message || 'Failed to list nonces'
423
+ error: error.message || "Failed to list nonces",
404
424
  }), {
405
425
  status: 500,
406
- headers: { 'Content-Type': 'application/json' }
426
+ headers: { "Content-Type": "application/json" },
407
427
  });
408
428
  }
409
429
  }
@@ -413,18 +433,18 @@ export function createMCPICloudflareAdapter(config) {
413
433
  : null;
414
434
  if (!proofArchive) {
415
435
  return new Response(JSON.stringify({
416
- error: 'PROOF_ARCHIVE KV namespace not configured'
436
+ error: "PROOF_ARCHIVE KV namespace not configured",
417
437
  }), {
418
438
  status: 503,
419
- headers: { 'Content-Type': 'application/json' }
439
+ headers: { "Content-Type": "application/json" },
420
440
  });
421
441
  }
422
442
  // GET /admin/proofs - List proofs with optional filters
423
- if (url.pathname === '/admin/proofs') {
443
+ if (url.pathname === "/admin/proofs") {
424
444
  try {
425
- const sessionId = url.searchParams.get('sessionId');
426
- const did = url.searchParams.get('did');
427
- const limit = parseInt(url.searchParams.get('limit') || '50');
445
+ const sessionId = url.searchParams.get("sessionId");
446
+ const did = url.searchParams.get("did");
447
+ const limit = parseInt(url.searchParams.get("limit") || "50");
428
448
  let proofs;
429
449
  if (sessionId) {
430
450
  proofs = await proofArchive.getBySession(sessionId, limit);
@@ -437,17 +457,17 @@ export function createMCPICloudflareAdapter(config) {
437
457
  }
438
458
  return new Response(JSON.stringify({
439
459
  total: proofs.length,
440
- proofs
460
+ proofs,
441
461
  }), {
442
- headers: { 'Content-Type': 'application/json' }
462
+ headers: { "Content-Type": "application/json" },
443
463
  });
444
464
  }
445
465
  catch (error) {
446
466
  return new Response(JSON.stringify({
447
- error: error.message || 'Failed to list proofs'
467
+ error: error.message || "Failed to list proofs",
448
468
  }), {
449
469
  status: 500,
450
- headers: { 'Content-Type': 'application/json' }
470
+ headers: { "Content-Type": "application/json" },
451
471
  });
452
472
  }
453
473
  }
@@ -459,60 +479,60 @@ export function createMCPICloudflareAdapter(config) {
459
479
  const proof = await proofArchive.get(proofId);
460
480
  if (!proof) {
461
481
  return new Response(JSON.stringify({
462
- error: 'Proof not found'
482
+ error: "Proof not found",
463
483
  }), {
464
484
  status: 404,
465
- headers: { 'Content-Type': 'application/json' }
485
+ headers: { "Content-Type": "application/json" },
466
486
  });
467
487
  }
468
488
  return new Response(JSON.stringify(proof), {
469
- headers: { 'Content-Type': 'application/json' }
489
+ headers: { "Content-Type": "application/json" },
470
490
  });
471
491
  }
472
492
  catch (error) {
473
493
  return new Response(JSON.stringify({
474
- error: error.message || 'Failed to get proof'
494
+ error: error.message || "Failed to get proof",
475
495
  }), {
476
496
  status: 500,
477
- headers: { 'Content-Type': 'application/json' }
497
+ headers: { "Content-Type": "application/json" },
478
498
  });
479
499
  }
480
500
  }
481
501
  // GET /admin/stats - Combined statistics
482
- if (url.pathname === '/admin/stats') {
502
+ if (url.pathname === "/admin/stats") {
483
503
  try {
484
504
  const [noncesList, proofStats] = await Promise.all([
485
- env.NONCE_CACHE.list({ prefix: 'nonce:' }),
486
- proofArchive.getStats()
505
+ env.NONCE_CACHE.list({ prefix: "nonce:" }),
506
+ proofArchive.getStats(),
487
507
  ]);
488
508
  return new Response(JSON.stringify({
489
509
  nonces: {
490
- active: noncesList.keys.length
510
+ active: noncesList.keys.length,
491
511
  },
492
512
  proofs: proofStats,
493
- timestamp: new Date().toISOString()
513
+ timestamp: new Date().toISOString(),
494
514
  }), {
495
- headers: { 'Content-Type': 'application/json' }
515
+ headers: { "Content-Type": "application/json" },
496
516
  });
497
517
  }
498
518
  catch (error) {
499
519
  return new Response(JSON.stringify({
500
- error: error.message || 'Failed to get stats'
520
+ error: error.message || "Failed to get stats",
501
521
  }), {
502
522
  status: 500,
503
- headers: { 'Content-Type': 'application/json' }
523
+ headers: { "Content-Type": "application/json" },
504
524
  });
505
525
  }
506
526
  }
507
527
  }
508
528
  // CORS preflight
509
- if (request.method === 'OPTIONS') {
529
+ if (request.method === "OPTIONS") {
510
530
  return new Response(null, {
511
- headers: PREFLIGHT_CORS_HEADERS
531
+ headers: PREFLIGHT_CORS_HEADERS,
512
532
  });
513
533
  }
514
- return new Response('Not found', { status: 404 });
515
- }
534
+ return new Response("Not found", { status: 404 });
535
+ },
516
536
  };
517
537
  }
518
538
  /**
@@ -525,14 +545,14 @@ async function handleSSE(server, runtime) {
525
545
  const encoder = new TextEncoder();
526
546
  // Return a simple SSE ping response
527
547
  // This establishes the SSE connection but doesn't try to keep it alive
528
- const sseData = ': ping\n\n';
548
+ const sseData = ": ping\n\n";
529
549
  return new Response(encoder.encode(sseData), {
530
550
  headers: {
531
- 'Content-Type': 'text/event-stream',
532
- 'Cache-Control': 'no-cache',
533
- 'X-Content-Type-Options': 'nosniff',
534
- ...MCP_CORS_HEADERS
535
- }
551
+ "Content-Type": "text/event-stream",
552
+ "Cache-Control": "no-cache",
553
+ "X-Content-Type-Options": "nosniff",
554
+ ...MCP_CORS_HEADERS,
555
+ },
536
556
  });
537
557
  }
538
558
  /**
@@ -553,30 +573,30 @@ async function handleSSEPost(server, runtime, request) {
553
573
  // Return SSE response directly without TransformStream
554
574
  return new Response(encoder.encode(eventData), {
555
575
  headers: {
556
- 'Content-Type': 'text/event-stream',
557
- 'Cache-Control': 'no-cache',
558
- 'X-Content-Type-Options': 'nosniff',
559
- ...MCP_CORS_HEADERS
560
- }
576
+ "Content-Type": "text/event-stream",
577
+ "Cache-Control": "no-cache",
578
+ "X-Content-Type-Options": "nosniff",
579
+ ...MCP_CORS_HEADERS,
580
+ },
561
581
  });
562
582
  }
563
583
  catch (error) {
564
584
  const errorResponse = {
565
- jsonrpc: '2.0',
585
+ jsonrpc: "2.0",
566
586
  error: {
567
587
  code: -32603,
568
- message: error.message || 'Internal error'
569
- }
588
+ message: error.message || "Internal error",
589
+ },
570
590
  };
571
591
  const eventData = `data: ${JSON.stringify(errorResponse)}\n\n`;
572
592
  // Return error as SSE event (status 200 per SSE spec)
573
593
  return new Response(encoder.encode(eventData), {
574
594
  status: 200, // SSE always returns 200, even for errors
575
595
  headers: {
576
- 'Content-Type': 'text/event-stream',
577
- 'Cache-Control': 'no-cache',
578
- ...MCP_CORS_HEADERS
579
- }
596
+ "Content-Type": "text/event-stream",
597
+ "Cache-Control": "no-cache",
598
+ ...MCP_CORS_HEADERS,
599
+ },
580
600
  });
581
601
  }
582
602
  }