@kya-os/mcp-i-cloudflare 1.3.5 → 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.
- package/dist/adapter.d.ts +3 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +170 -150
- package/dist/adapter.js.map +1 -1
- package/dist/cache/kv-tool-protection-cache.d.ts.map +1 -1
- package/dist/cache/kv-tool-protection-cache.js +5 -0
- package/dist/cache/kv-tool-protection-cache.js.map +1 -1
- package/dist/config.d.ts +275 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +108 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/proof-generator.d.ts +2 -0
- package/dist/proof-generator.d.ts.map +1 -1
- package/dist/proof-generator.js +1 -0
- package/dist/proof-generator.js.map +1 -1
- package/dist/providers/kv-identity.d.ts +42 -0
- package/dist/providers/kv-identity.d.ts.map +1 -0
- package/dist/providers/kv-identity.js +109 -0
- package/dist/providers/kv-identity.js.map +1 -0
- package/dist/runtime/oauth-handler.d.ts.map +1 -1
- package/dist/runtime/oauth-handler.js +27 -21
- package/dist/runtime/oauth-handler.js.map +1 -1
- package/dist/runtime.d.ts +4 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +7 -2
- package/dist/runtime.js.map +1 -1
- 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
|
|
12
|
-
import { WELL_KNOWN_CORS_HEADERS, MCP_CORS_HEADERS, PREFLIGHT_CORS_HEADERS } from
|
|
13
|
-
import { KVProofArchive } from
|
|
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 ===
|
|
35
|
+
if (method === "initialize") {
|
|
36
36
|
return {
|
|
37
|
-
jsonrpc:
|
|
37
|
+
jsonrpc: "2.0",
|
|
38
38
|
id,
|
|
39
39
|
result: {
|
|
40
|
-
protocolVersion:
|
|
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 ===
|
|
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:
|
|
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 ===
|
|
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 ||
|
|
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 +
|
|
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 &&
|
|
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(
|
|
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:
|
|
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 ===
|
|
143
|
+
if (method === "handshake") {
|
|
143
144
|
const handshakeResult = await this.runtime.handleHandshake(params || {});
|
|
144
145
|
return {
|
|
145
|
-
jsonrpc:
|
|
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
|
|
156
|
+
if (error &&
|
|
157
|
+
(error.name === "DelegationRequiredError" ||
|
|
158
|
+
error.constructor?.name === "DelegationRequiredError")) {
|
|
156
159
|
return {
|
|
157
|
-
jsonrpc:
|
|
160
|
+
jsonrpc: "2.0",
|
|
158
161
|
id,
|
|
159
162
|
error: {
|
|
160
163
|
code: -32001, // Custom error code for authorization required
|
|
161
|
-
message: error.message ||
|
|
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:
|
|
187
|
+
jsonrpc: "2.0",
|
|
175
188
|
id,
|
|
176
189
|
error: {
|
|
177
190
|
code: -32603,
|
|
178
|
-
message:
|
|
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:
|
|
197
|
-
version:
|
|
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
|
|
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 ===
|
|
229
|
+
if (url.pathname === "/health") {
|
|
215
230
|
return new Response(JSON.stringify({
|
|
216
|
-
status:
|
|
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:
|
|
223
|
-
sse:
|
|
224
|
-
streamableHttp:
|
|
237
|
+
http: "/mcp",
|
|
238
|
+
sse: "/sse",
|
|
239
|
+
streamableHttp: "/mcp",
|
|
225
240
|
},
|
|
226
|
-
features: [
|
|
241
|
+
features: ["identity", "proof", "delegation", "audit"],
|
|
227
242
|
}), {
|
|
228
|
-
headers: {
|
|
243
|
+
headers: { "Content-Type": "application/json" },
|
|
229
244
|
});
|
|
230
245
|
}
|
|
231
246
|
// Well-known endpoints for identity verification
|
|
232
|
-
if (url.pathname.startsWith(
|
|
247
|
+
if (url.pathname.startsWith("/.well-known/")) {
|
|
233
248
|
const handler = runtime.createWellKnownHandler({
|
|
234
249
|
serviceName: serverInfo.name,
|
|
235
|
-
serviceEndpoint: request.headers.get(
|
|
250
|
+
serviceEndpoint: request.headers.get("host") || "localhost",
|
|
236
251
|
});
|
|
237
252
|
const result = await handler(url.pathname);
|
|
238
253
|
if (result) {
|
|
239
|
-
if (
|
|
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
|
-
|
|
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 ===
|
|
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: {
|
|
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 ===
|
|
285
|
+
if (url.pathname === "/mcp") {
|
|
268
286
|
// Handle SSE streaming (for Claude Desktop via mcp-remote)
|
|
269
|
-
if (request.method ===
|
|
270
|
-
const acceptHeader = request.headers.get(
|
|
271
|
-
if (acceptHeader.includes(
|
|
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(
|
|
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
|
-
|
|
280
|
-
|
|
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 ===
|
|
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
|
-
|
|
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 ===
|
|
299
|
-
const acceptHeader = request.headers.get(
|
|
316
|
+
if (request.method === "POST") {
|
|
317
|
+
const acceptHeader = request.headers.get("Accept") || "";
|
|
300
318
|
// Check if client wants SSE response
|
|
301
|
-
if (acceptHeader.includes(
|
|
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
|
-
|
|
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:
|
|
338
|
+
jsonrpc: "2.0",
|
|
321
339
|
error: {
|
|
322
340
|
code: -32603,
|
|
323
|
-
message: error.message ||
|
|
324
|
-
}
|
|
341
|
+
message: error.message || "Internal error",
|
|
342
|
+
},
|
|
325
343
|
}), {
|
|
326
344
|
status: 500,
|
|
327
|
-
headers: {
|
|
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 ===
|
|
334
|
-
if (request.method ===
|
|
351
|
+
if (url.pathname === "/sse") {
|
|
352
|
+
if (request.method === "GET") {
|
|
335
353
|
return handleSSE(server, runtime);
|
|
336
354
|
}
|
|
337
|
-
if (request.method ===
|
|
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 ===
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
366
|
+
agent: isValid
|
|
367
|
+
? {
|
|
368
|
+
did: proof.did,
|
|
369
|
+
timestamp: proof.timestamp,
|
|
370
|
+
sessionId: proof.sessionId,
|
|
371
|
+
}
|
|
372
|
+
: null,
|
|
353
373
|
}), {
|
|
354
|
-
headers: {
|
|
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 ||
|
|
379
|
+
error: error.message || "Verification failed",
|
|
360
380
|
}), {
|
|
361
381
|
status: 400,
|
|
362
|
-
headers: {
|
|
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(
|
|
387
|
+
if (url.pathname.startsWith("/admin/")) {
|
|
368
388
|
// Only allow in development or if explicitly configured
|
|
369
|
-
if (config.environment ===
|
|
389
|
+
if (config.environment === "production") {
|
|
370
390
|
return new Response(JSON.stringify({
|
|
371
|
-
error:
|
|
391
|
+
error: "Admin endpoints disabled in production",
|
|
372
392
|
}), {
|
|
373
393
|
status: 403,
|
|
374
|
-
headers: {
|
|
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 ===
|
|
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:
|
|
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(
|
|
389
|
-
storedAt: key.metadata?.storedAt ||
|
|
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: {
|
|
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 ||
|
|
423
|
+
error: error.message || "Failed to list nonces",
|
|
404
424
|
}), {
|
|
405
425
|
status: 500,
|
|
406
|
-
headers: {
|
|
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:
|
|
436
|
+
error: "PROOF_ARCHIVE KV namespace not configured",
|
|
417
437
|
}), {
|
|
418
438
|
status: 503,
|
|
419
|
-
headers: {
|
|
439
|
+
headers: { "Content-Type": "application/json" },
|
|
420
440
|
});
|
|
421
441
|
}
|
|
422
442
|
// GET /admin/proofs - List proofs with optional filters
|
|
423
|
-
if (url.pathname ===
|
|
443
|
+
if (url.pathname === "/admin/proofs") {
|
|
424
444
|
try {
|
|
425
|
-
const sessionId = url.searchParams.get(
|
|
426
|
-
const did = url.searchParams.get(
|
|
427
|
-
const limit = parseInt(url.searchParams.get(
|
|
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: {
|
|
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 ||
|
|
467
|
+
error: error.message || "Failed to list proofs",
|
|
448
468
|
}), {
|
|
449
469
|
status: 500,
|
|
450
|
-
headers: {
|
|
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:
|
|
482
|
+
error: "Proof not found",
|
|
463
483
|
}), {
|
|
464
484
|
status: 404,
|
|
465
|
-
headers: {
|
|
485
|
+
headers: { "Content-Type": "application/json" },
|
|
466
486
|
});
|
|
467
487
|
}
|
|
468
488
|
return new Response(JSON.stringify(proof), {
|
|
469
|
-
headers: {
|
|
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 ||
|
|
494
|
+
error: error.message || "Failed to get proof",
|
|
475
495
|
}), {
|
|
476
496
|
status: 500,
|
|
477
|
-
headers: {
|
|
497
|
+
headers: { "Content-Type": "application/json" },
|
|
478
498
|
});
|
|
479
499
|
}
|
|
480
500
|
}
|
|
481
501
|
// GET /admin/stats - Combined statistics
|
|
482
|
-
if (url.pathname ===
|
|
502
|
+
if (url.pathname === "/admin/stats") {
|
|
483
503
|
try {
|
|
484
504
|
const [noncesList, proofStats] = await Promise.all([
|
|
485
|
-
env.NONCE_CACHE.list({ prefix:
|
|
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: {
|
|
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 ||
|
|
520
|
+
error: error.message || "Failed to get stats",
|
|
501
521
|
}), {
|
|
502
522
|
status: 500,
|
|
503
|
-
headers: {
|
|
523
|
+
headers: { "Content-Type": "application/json" },
|
|
504
524
|
});
|
|
505
525
|
}
|
|
506
526
|
}
|
|
507
527
|
}
|
|
508
528
|
// CORS preflight
|
|
509
|
-
if (request.method ===
|
|
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(
|
|
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 =
|
|
548
|
+
const sseData = ": ping\n\n";
|
|
529
549
|
return new Response(encoder.encode(sseData), {
|
|
530
550
|
headers: {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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:
|
|
585
|
+
jsonrpc: "2.0",
|
|
566
586
|
error: {
|
|
567
587
|
code: -32603,
|
|
568
|
-
message: error.message ||
|
|
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
|
-
|
|
577
|
-
|
|
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
|
}
|