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

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 +171 -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,44 @@ 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.consentUrl
165
+ ? `${error.message || `Delegation required for tool "${error.toolName || "unknown"}"`}. <authorization_url>${error.consentUrl}</authorization_url>`
166
+ : error.message || `Delegation required for tool "${error.toolName || "unknown"}"`,
162
167
  data: {
163
168
  toolName: error.toolName,
164
169
  requiredScopes: error.requiredScopes || [],
165
170
  consentUrl: error.consentUrl,
171
+ resumeToken: error.resumeToken, // Include resume token for resumption
166
172
  // MCP-I specific: provide authorization URL for client to display
167
173
  authorizationUrl: error.consentUrl,
168
- }
169
- }
174
+ },
175
+ },
170
176
  };
171
177
  }
172
178
  // Generic error handler
179
+ let errorMessage = "Internal error";
180
+ if (error) {
181
+ // Only use error message if it's an Error object with a message property
182
+ if (typeof error === 'object' && error instanceof Error && error.message) {
183
+ errorMessage = error.message;
184
+ }
185
+ // For non-Error objects (strings, null, etc.), use default "Internal error"
186
+ }
173
187
  return {
174
- jsonrpc: '2.0',
188
+ jsonrpc: "2.0",
175
189
  id,
176
190
  error: {
177
191
  code: -32603,
178
- message: error.message || 'Internal error'
179
- }
192
+ message: errorMessage,
193
+ },
180
194
  };
181
195
  }
182
196
  }
@@ -193,12 +207,14 @@ export function createMCPICloudflareAdapter(config) {
193
207
  const runtime = createCloudflareRuntime(config);
194
208
  // Server info
195
209
  const serverInfo = config.serverInfo || {
196
- name: 'MCP-I Cloudflare Server',
197
- version: '1.0.0'
210
+ name: "MCP-I Cloudflare Server",
211
+ version: "1.0.0",
198
212
  };
199
213
  // Initialize proof archive if PROOF_ARCHIVE KV is available
200
214
  const env = config.env;
201
- const proofArchive = env.PROOF_ARCHIVE ? new KVProofArchive(env.PROOF_ARCHIVE) : undefined;
215
+ const proofArchive = env.PROOF_ARCHIVE
216
+ ? new KVProofArchive(env.PROOF_ARCHIVE)
217
+ : undefined;
202
218
  // Create lightweight MCP server
203
219
  const server = new CloudflareMCPServer(runtime, serverInfo, config.tools || [], proofArchive);
204
220
  // Return fetch handler
@@ -211,94 +227,97 @@ export function createMCPICloudflareAdapter(config) {
211
227
  const url = new URL(request.url);
212
228
  const identity = await runtime.getIdentity();
213
229
  // Health check endpoint
214
- if (url.pathname === '/health') {
230
+ if (url.pathname === "/health") {
215
231
  return new Response(JSON.stringify({
216
- status: 'healthy',
232
+ status: "healthy",
217
233
  timestamp: new Date().toISOString(),
218
234
  identity: {
219
- did: identity.did
235
+ did: identity.did,
220
236
  },
221
237
  transport: {
222
- http: '/mcp',
223
- sse: '/sse',
224
- streamableHttp: '/mcp'
238
+ http: "/mcp",
239
+ sse: "/sse",
240
+ streamableHttp: "/mcp",
225
241
  },
226
- features: ['identity', 'proof', 'delegation', 'audit']
242
+ features: ["identity", "proof", "delegation", "audit"],
227
243
  }), {
228
- headers: { 'Content-Type': 'application/json' }
244
+ headers: { "Content-Type": "application/json" },
229
245
  });
230
246
  }
231
247
  // Well-known endpoints for identity verification
232
- if (url.pathname.startsWith('/.well-known/')) {
248
+ if (url.pathname.startsWith("/.well-known/")) {
233
249
  const handler = runtime.createWellKnownHandler({
234
250
  serviceName: serverInfo.name,
235
- serviceEndpoint: request.headers.get('host') || 'localhost'
251
+ serviceEndpoint: request.headers.get("host") || "localhost",
236
252
  });
237
253
  const result = await handler(url.pathname);
238
254
  if (result) {
239
- if (result.status && result.headers && result.body) {
255
+ // Check if it's a WellKnownResponse (has status, headers, body)
256
+ if ("status" in result && "headers" in result && "body" in result) {
240
257
  return new Response(result.body, {
241
258
  status: result.status,
242
259
  headers: {
243
260
  ...result.headers,
244
- ...WELL_KNOWN_CORS_HEADERS
245
- }
261
+ ...WELL_KNOWN_CORS_HEADERS,
262
+ },
246
263
  });
247
264
  }
265
+ // Otherwise it's an MCPIdentity object, return as JSON
248
266
  return new Response(JSON.stringify(result), {
267
+ status: 200,
249
268
  headers: {
250
- 'Content-Type': 'application/json',
251
- ...WELL_KNOWN_CORS_HEADERS
252
- }
269
+ "Content-Type": "application/json",
270
+ ...WELL_KNOWN_CORS_HEADERS,
271
+ },
253
272
  });
254
273
  }
255
274
  }
256
275
  // Debug endpoint (development only)
257
- if (url.pathname === '/debug' && config.environment !== 'production') {
276
+ if (url.pathname === "/debug" && config.environment !== "production") {
258
277
  const debugHandler = runtime.createDebugEndpoint();
259
278
  if (debugHandler) {
260
279
  const result = await debugHandler();
261
280
  return new Response(JSON.stringify(result), {
262
- headers: { 'Content-Type': 'application/json' }
281
+ headers: { "Content-Type": "application/json" },
263
282
  });
264
283
  }
265
284
  }
266
285
  // Main MCP protocol endpoint - supports both POST and SSE
267
- if (url.pathname === '/mcp') {
286
+ if (url.pathname === "/mcp") {
268
287
  // 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')) {
288
+ if (request.method === "GET") {
289
+ const acceptHeader = request.headers.get("Accept") || "";
290
+ if (acceptHeader.includes("text/event-stream")) {
272
291
  return handleSSE(server, runtime);
273
292
  }
274
293
  // GET requests without SSE Accept header should return Method Not Allowed
275
294
  // 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', {
295
+ return new Response("Method Not Allowed: Use POST for JSON-RPC or GET with Accept: text/event-stream for SSE", {
277
296
  status: 405,
278
297
  headers: {
279
- 'Content-Type': 'text/plain',
280
- 'Allow': 'POST, GET',
281
- ...MCP_CORS_HEADERS
282
- }
298
+ "Content-Type": "text/plain",
299
+ Allow: "POST, GET",
300
+ ...MCP_CORS_HEADERS,
301
+ },
283
302
  });
284
303
  }
285
304
  // Handle DELETE requests (session termination)
286
- if (request.method === 'DELETE') {
305
+ if (request.method === "DELETE") {
287
306
  // MCP Inspector and clients may send DELETE to terminate sessions
288
307
  // Return 200 OK with empty body (some environments don't support 204 well)
289
- return new Response('', {
308
+ return new Response("", {
290
309
  status: 200,
291
310
  headers: {
292
- 'Content-Length': '0',
293
- ...MCP_CORS_HEADERS
294
- }
311
+ "Content-Length": "0",
312
+ ...MCP_CORS_HEADERS,
313
+ },
295
314
  });
296
315
  }
297
316
  // Handle standard POST (existing functionality)
298
- if (request.method === 'POST') {
299
- const acceptHeader = request.headers.get('Accept') || '';
317
+ if (request.method === "POST") {
318
+ const acceptHeader = request.headers.get("Accept") || "";
300
319
  // Check if client wants SSE response
301
- if (acceptHeader.includes('text/event-stream')) {
320
+ if (acceptHeader.includes("text/event-stream")) {
302
321
  // Return SSE stream for POST (Streamable HTTP mode)
303
322
  return handleSSEPost(server, runtime, request);
304
323
  }
@@ -310,100 +329,102 @@ export function createMCPICloudflareAdapter(config) {
310
329
  const response = await server.handleRequest(jsonRpcRequest);
311
330
  return new Response(JSON.stringify(response), {
312
331
  headers: {
313
- 'Content-Type': 'application/json',
314
- ...MCP_CORS_HEADERS
315
- }
332
+ "Content-Type": "application/json",
333
+ ...MCP_CORS_HEADERS,
334
+ },
316
335
  });
317
336
  }
318
337
  catch (error) {
319
338
  return new Response(JSON.stringify({
320
- jsonrpc: '2.0',
339
+ jsonrpc: "2.0",
321
340
  error: {
322
341
  code: -32603,
323
- message: error.message || 'Internal error'
324
- }
342
+ message: error.message || "Internal error",
343
+ },
325
344
  }), {
326
345
  status: 500,
327
- headers: { 'Content-Type': 'application/json' }
346
+ headers: { "Content-Type": "application/json" },
328
347
  });
329
348
  }
330
349
  }
331
350
  }
332
351
  // SSE endpoint for backward compatibility and Claude Desktop
333
- if (url.pathname === '/sse') {
334
- if (request.method === 'GET') {
352
+ if (url.pathname === "/sse") {
353
+ if (request.method === "GET") {
335
354
  return handleSSE(server, runtime);
336
355
  }
337
- if (request.method === 'POST') {
356
+ if (request.method === "POST") {
338
357
  return handleSSEPost(server, runtime, request);
339
358
  }
340
359
  }
341
360
  // Standalone verify endpoint for external verification
342
- if (url.pathname === '/verify' && request.method === 'POST') {
361
+ if (url.pathname === "/verify" && request.method === "POST") {
343
362
  try {
344
- const { data, proof } = await request.json();
363
+ const { data, proof } = (await request.json());
345
364
  const isValid = await runtime.verifyProof(data, proof);
346
365
  return new Response(JSON.stringify({
347
366
  verified: isValid,
348
- agent: isValid ? {
349
- did: proof.did,
350
- timestamp: proof.timestamp,
351
- sessionId: proof.sessionId
352
- } : null
367
+ agent: isValid
368
+ ? {
369
+ did: proof.did,
370
+ timestamp: proof.timestamp,
371
+ sessionId: proof.sessionId,
372
+ }
373
+ : null,
353
374
  }), {
354
- headers: { 'Content-Type': 'application/json' }
375
+ headers: { "Content-Type": "application/json" },
355
376
  });
356
377
  }
357
378
  catch (error) {
358
379
  return new Response(JSON.stringify({
359
- error: error.message || 'Verification failed'
380
+ error: error.message || "Verification failed",
360
381
  }), {
361
382
  status: 400,
362
- headers: { 'Content-Type': 'application/json' }
383
+ headers: { "Content-Type": "application/json" },
363
384
  });
364
385
  }
365
386
  }
366
387
  // Admin endpoints for viewing nonces and proofs
367
- if (url.pathname.startsWith('/admin/')) {
388
+ if (url.pathname.startsWith("/admin/")) {
368
389
  // Only allow in development or if explicitly configured
369
- if (config.environment === 'production') {
390
+ if (config.environment === "production") {
370
391
  return new Response(JSON.stringify({
371
- error: 'Admin endpoints disabled in production'
392
+ error: "Admin endpoints disabled in production",
372
393
  }), {
373
394
  status: 403,
374
- headers: { 'Content-Type': 'application/json' }
395
+ headers: { "Content-Type": "application/json" },
375
396
  });
376
397
  }
377
398
  const env = config.env;
378
399
  // GET /admin/nonces - List active nonces
379
- if (url.pathname === '/admin/nonces') {
400
+ if (url.pathname === "/admin/nonces") {
380
401
  try {
381
402
  // Use KV list to get nonce keys
382
- const noncesList = await env.NONCE_CACHE.list({ prefix: 'nonce:' });
403
+ const noncesList = await env.NONCE_CACHE.list({ prefix: "nonce:" });
383
404
  const nonces = [];
384
405
  for (const key of noncesList.keys) {
385
406
  const value = await env.NONCE_CACHE.get(key.name);
386
407
  if (value) {
387
408
  nonces.push({
388
- nonce: key.name.replace('nonce:', ''),
389
- storedAt: key.metadata?.storedAt || 'unknown',
390
- expiresAt: key.expiration
409
+ nonce: key.name.replace("nonce:", ""),
410
+ storedAt: key.metadata?.storedAt || "unknown",
411
+ expiresAt: key.expiration,
391
412
  });
392
413
  }
393
414
  }
394
415
  return new Response(JSON.stringify({
395
416
  total: nonces.length,
396
- nonces
417
+ nonces,
397
418
  }), {
398
- headers: { 'Content-Type': 'application/json' }
419
+ headers: { "Content-Type": "application/json" },
399
420
  });
400
421
  }
401
422
  catch (error) {
402
423
  return new Response(JSON.stringify({
403
- error: error.message || 'Failed to list nonces'
424
+ error: error.message || "Failed to list nonces",
404
425
  }), {
405
426
  status: 500,
406
- headers: { 'Content-Type': 'application/json' }
427
+ headers: { "Content-Type": "application/json" },
407
428
  });
408
429
  }
409
430
  }
@@ -413,18 +434,18 @@ export function createMCPICloudflareAdapter(config) {
413
434
  : null;
414
435
  if (!proofArchive) {
415
436
  return new Response(JSON.stringify({
416
- error: 'PROOF_ARCHIVE KV namespace not configured'
437
+ error: "PROOF_ARCHIVE KV namespace not configured",
417
438
  }), {
418
439
  status: 503,
419
- headers: { 'Content-Type': 'application/json' }
440
+ headers: { "Content-Type": "application/json" },
420
441
  });
421
442
  }
422
443
  // GET /admin/proofs - List proofs with optional filters
423
- if (url.pathname === '/admin/proofs') {
444
+ if (url.pathname === "/admin/proofs") {
424
445
  try {
425
- const sessionId = url.searchParams.get('sessionId');
426
- const did = url.searchParams.get('did');
427
- const limit = parseInt(url.searchParams.get('limit') || '50');
446
+ const sessionId = url.searchParams.get("sessionId");
447
+ const did = url.searchParams.get("did");
448
+ const limit = parseInt(url.searchParams.get("limit") || "50");
428
449
  let proofs;
429
450
  if (sessionId) {
430
451
  proofs = await proofArchive.getBySession(sessionId, limit);
@@ -437,17 +458,17 @@ export function createMCPICloudflareAdapter(config) {
437
458
  }
438
459
  return new Response(JSON.stringify({
439
460
  total: proofs.length,
440
- proofs
461
+ proofs,
441
462
  }), {
442
- headers: { 'Content-Type': 'application/json' }
463
+ headers: { "Content-Type": "application/json" },
443
464
  });
444
465
  }
445
466
  catch (error) {
446
467
  return new Response(JSON.stringify({
447
- error: error.message || 'Failed to list proofs'
468
+ error: error.message || "Failed to list proofs",
448
469
  }), {
449
470
  status: 500,
450
- headers: { 'Content-Type': 'application/json' }
471
+ headers: { "Content-Type": "application/json" },
451
472
  });
452
473
  }
453
474
  }
@@ -459,60 +480,60 @@ export function createMCPICloudflareAdapter(config) {
459
480
  const proof = await proofArchive.get(proofId);
460
481
  if (!proof) {
461
482
  return new Response(JSON.stringify({
462
- error: 'Proof not found'
483
+ error: "Proof not found",
463
484
  }), {
464
485
  status: 404,
465
- headers: { 'Content-Type': 'application/json' }
486
+ headers: { "Content-Type": "application/json" },
466
487
  });
467
488
  }
468
489
  return new Response(JSON.stringify(proof), {
469
- headers: { 'Content-Type': 'application/json' }
490
+ headers: { "Content-Type": "application/json" },
470
491
  });
471
492
  }
472
493
  catch (error) {
473
494
  return new Response(JSON.stringify({
474
- error: error.message || 'Failed to get proof'
495
+ error: error.message || "Failed to get proof",
475
496
  }), {
476
497
  status: 500,
477
- headers: { 'Content-Type': 'application/json' }
498
+ headers: { "Content-Type": "application/json" },
478
499
  });
479
500
  }
480
501
  }
481
502
  // GET /admin/stats - Combined statistics
482
- if (url.pathname === '/admin/stats') {
503
+ if (url.pathname === "/admin/stats") {
483
504
  try {
484
505
  const [noncesList, proofStats] = await Promise.all([
485
- env.NONCE_CACHE.list({ prefix: 'nonce:' }),
486
- proofArchive.getStats()
506
+ env.NONCE_CACHE.list({ prefix: "nonce:" }),
507
+ proofArchive.getStats(),
487
508
  ]);
488
509
  return new Response(JSON.stringify({
489
510
  nonces: {
490
- active: noncesList.keys.length
511
+ active: noncesList.keys.length,
491
512
  },
492
513
  proofs: proofStats,
493
- timestamp: new Date().toISOString()
514
+ timestamp: new Date().toISOString(),
494
515
  }), {
495
- headers: { 'Content-Type': 'application/json' }
516
+ headers: { "Content-Type": "application/json" },
496
517
  });
497
518
  }
498
519
  catch (error) {
499
520
  return new Response(JSON.stringify({
500
- error: error.message || 'Failed to get stats'
521
+ error: error.message || "Failed to get stats",
501
522
  }), {
502
523
  status: 500,
503
- headers: { 'Content-Type': 'application/json' }
524
+ headers: { "Content-Type": "application/json" },
504
525
  });
505
526
  }
506
527
  }
507
528
  }
508
529
  // CORS preflight
509
- if (request.method === 'OPTIONS') {
530
+ if (request.method === "OPTIONS") {
510
531
  return new Response(null, {
511
- headers: PREFLIGHT_CORS_HEADERS
532
+ headers: PREFLIGHT_CORS_HEADERS,
512
533
  });
513
534
  }
514
- return new Response('Not found', { status: 404 });
515
- }
535
+ return new Response("Not found", { status: 404 });
536
+ },
516
537
  };
517
538
  }
518
539
  /**
@@ -525,14 +546,14 @@ async function handleSSE(server, runtime) {
525
546
  const encoder = new TextEncoder();
526
547
  // Return a simple SSE ping response
527
548
  // This establishes the SSE connection but doesn't try to keep it alive
528
- const sseData = ': ping\n\n';
549
+ const sseData = ": ping\n\n";
529
550
  return new Response(encoder.encode(sseData), {
530
551
  headers: {
531
- 'Content-Type': 'text/event-stream',
532
- 'Cache-Control': 'no-cache',
533
- 'X-Content-Type-Options': 'nosniff',
534
- ...MCP_CORS_HEADERS
535
- }
552
+ "Content-Type": "text/event-stream",
553
+ "Cache-Control": "no-cache",
554
+ "X-Content-Type-Options": "nosniff",
555
+ ...MCP_CORS_HEADERS,
556
+ },
536
557
  });
537
558
  }
538
559
  /**
@@ -553,30 +574,30 @@ async function handleSSEPost(server, runtime, request) {
553
574
  // Return SSE response directly without TransformStream
554
575
  return new Response(encoder.encode(eventData), {
555
576
  headers: {
556
- 'Content-Type': 'text/event-stream',
557
- 'Cache-Control': 'no-cache',
558
- 'X-Content-Type-Options': 'nosniff',
559
- ...MCP_CORS_HEADERS
560
- }
577
+ "Content-Type": "text/event-stream",
578
+ "Cache-Control": "no-cache",
579
+ "X-Content-Type-Options": "nosniff",
580
+ ...MCP_CORS_HEADERS,
581
+ },
561
582
  });
562
583
  }
563
584
  catch (error) {
564
585
  const errorResponse = {
565
- jsonrpc: '2.0',
586
+ jsonrpc: "2.0",
566
587
  error: {
567
588
  code: -32603,
568
- message: error.message || 'Internal error'
569
- }
589
+ message: error.message || "Internal error",
590
+ },
570
591
  };
571
592
  const eventData = `data: ${JSON.stringify(errorResponse)}\n\n`;
572
593
  // Return error as SSE event (status 200 per SSE spec)
573
594
  return new Response(encoder.encode(eventData), {
574
595
  status: 200, // SSE always returns 200, even for errors
575
596
  headers: {
576
- 'Content-Type': 'text/event-stream',
577
- 'Cache-Control': 'no-cache',
578
- ...MCP_CORS_HEADERS
579
- }
597
+ "Content-Type": "text/event-stream",
598
+ "Cache-Control": "no-cache",
599
+ ...MCP_CORS_HEADERS,
600
+ },
580
601
  });
581
602
  }
582
603
  }