@aborruso/ckan-mcp-server 0.3.2 → 0.4.0
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +81 -24
- package/LOG.md +72 -10
- package/README.md +87 -8
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-final.json +7 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/worker.js +387 -0
- package/openspec/changes/add-cloudflare-workers/design.md +734 -0
- package/openspec/changes/add-cloudflare-workers/proposal.md +183 -0
- package/openspec/changes/add-cloudflare-workers/specs/cloudflare-deployment/spec.md +389 -0
- package/openspec/changes/add-cloudflare-workers/tasks.md +519 -0
- package/package.json +6 -2
- package/wrangler.toml +6 -0
- package/test-urls.js +0 -18
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# Design: Cloudflare Workers Deployment
|
|
2
|
+
|
|
3
|
+
## Architecture Overview
|
|
4
|
+
|
|
5
|
+
### Current Architecture (Node.js)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────┐
|
|
9
|
+
│ CKAN MCP Server (Node.js) │
|
|
10
|
+
├─────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
13
|
+
│ │ stdio.ts │ │ http.ts │ │
|
|
14
|
+
│ │ Transport │ │ Transport │ │
|
|
15
|
+
│ └──────┬───────┘ └──────┬───────┘ │
|
|
16
|
+
│ │ │ │
|
|
17
|
+
│ └──────┬────────────┘ │
|
|
18
|
+
│ │ │
|
|
19
|
+
│ ┌──────▼───────┐ │
|
|
20
|
+
│ │ MCP Server │ │
|
|
21
|
+
│ │ (SDK) │ │
|
|
22
|
+
│ └──────┬───────┘ │
|
|
23
|
+
│ │ │
|
|
24
|
+
│ ┌───────────┴───────────┐ │
|
|
25
|
+
│ │ Tool Handlers │ │
|
|
26
|
+
│ │ (7 CKAN tools) │ │
|
|
27
|
+
│ └───────────┬───────────┘ │
|
|
28
|
+
│ │ │
|
|
29
|
+
│ ┌──────▼───────┐ │
|
|
30
|
+
│ │ HTTP Client │ │
|
|
31
|
+
│ │ (axios) │ │
|
|
32
|
+
│ └──────┬───────┘ │
|
|
33
|
+
│ │ │
|
|
34
|
+
└────────────────┼───────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
┌──────▼───────┐
|
|
37
|
+
│ CKAN API │
|
|
38
|
+
│ (external) │
|
|
39
|
+
└──────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Proposed Architecture (Cloudflare Workers)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌─────────────────────────────────────────┐
|
|
46
|
+
│ CKAN MCP Server (Workers Runtime) │
|
|
47
|
+
├─────────────────────────────────────────┤
|
|
48
|
+
│ │
|
|
49
|
+
│ ┌──────────────┐ │
|
|
50
|
+
│ │ worker.ts │ │
|
|
51
|
+
│ │ (fetch API) │ │
|
|
52
|
+
│ └──────┬───────┘ │
|
|
53
|
+
│ │ │
|
|
54
|
+
│ ┌──────▼───────┐ │
|
|
55
|
+
│ │ MCP Server │ │
|
|
56
|
+
│ │ (SDK) │ │
|
|
57
|
+
│ └──────┬───────┘ │
|
|
58
|
+
│ │ │
|
|
59
|
+
│ ┌───────────┴───────────┐ │
|
|
60
|
+
│ │ Tool Handlers │ │
|
|
61
|
+
│ │ (7 CKAN tools) │ │
|
|
62
|
+
│ │ [REUSED CODE] │ │
|
|
63
|
+
│ └───────────┬───────────┘ │
|
|
64
|
+
│ │ │
|
|
65
|
+
│ ┌──────▼───────┐ │
|
|
66
|
+
│ │ HTTP Client │ │
|
|
67
|
+
│ │ (fetch API) │ │
|
|
68
|
+
│ └──────┬───────┘ │
|
|
69
|
+
│ │ │
|
|
70
|
+
└────────────────┼───────────────────────┘
|
|
71
|
+
│
|
|
72
|
+
┌──────▼───────┐
|
|
73
|
+
│ CKAN API │
|
|
74
|
+
│ (external) │
|
|
75
|
+
└──────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key differences**:
|
|
79
|
+
- **Entry point**: `worker.ts` replaces `stdio.ts` and `http.ts`
|
|
80
|
+
- **HTTP client**: Native `fetch()` replaces `axios`
|
|
81
|
+
- **Runtime**: Cloudflare Workers runtime (V8) instead of Node.js
|
|
82
|
+
- **Tool handlers**: Unchanged (100% code reuse)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Component Design
|
|
87
|
+
|
|
88
|
+
### 1. Worker Entry Point (`src/worker.ts`)
|
|
89
|
+
|
|
90
|
+
**Purpose**: Handle incoming HTTP requests and route to MCP server
|
|
91
|
+
|
|
92
|
+
**Interface**:
|
|
93
|
+
```typescript
|
|
94
|
+
export default {
|
|
95
|
+
async fetch(request: Request, env: Env): Promise<Response>
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Responsibilities**:
|
|
100
|
+
1. Health check endpoint (`GET /health`)
|
|
101
|
+
2. MCP protocol endpoint (`POST /mcp`)
|
|
102
|
+
3. Error handling and logging
|
|
103
|
+
4. Request/response transformation
|
|
104
|
+
|
|
105
|
+
**Code structure**:
|
|
106
|
+
```typescript
|
|
107
|
+
// src/worker.ts
|
|
108
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
109
|
+
import { registerPackageTools } from './tools/package.js';
|
|
110
|
+
import { registerOrganizationTools } from './tools/organization.js';
|
|
111
|
+
import { registerDatastoreTools } from './tools/datastore.js';
|
|
112
|
+
import { registerStatusTools } from './tools/status.js';
|
|
113
|
+
import { registerResources } from './resources/index.js';
|
|
114
|
+
|
|
115
|
+
export default {
|
|
116
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
117
|
+
const url = new URL(request.url);
|
|
118
|
+
|
|
119
|
+
// Health check
|
|
120
|
+
if (request.method === 'GET' && url.pathname === '/health') {
|
|
121
|
+
return new Response(JSON.stringify({
|
|
122
|
+
status: 'ok',
|
|
123
|
+
version: '0.4.0',
|
|
124
|
+
tools: 7,
|
|
125
|
+
resources: 3
|
|
126
|
+
}), {
|
|
127
|
+
headers: { 'Content-Type': 'application/json' }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MCP endpoint
|
|
132
|
+
if (request.method === 'POST' && url.pathname === '/mcp') {
|
|
133
|
+
return handleMcpRequest(request);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 404 for all other routes
|
|
137
|
+
return new Response('Not Found', { status: 404 });
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
async function handleMcpRequest(request: Request): Promise<Response> {
|
|
142
|
+
try {
|
|
143
|
+
// Parse JSON-RPC request
|
|
144
|
+
const body = await request.json();
|
|
145
|
+
|
|
146
|
+
// Create MCP server instance
|
|
147
|
+
const server = new Server({
|
|
148
|
+
name: 'ckan-mcp-server',
|
|
149
|
+
version: '0.4.0'
|
|
150
|
+
}, {
|
|
151
|
+
capabilities: {
|
|
152
|
+
tools: {},
|
|
153
|
+
resources: {}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Register all tools
|
|
158
|
+
registerPackageTools(server);
|
|
159
|
+
registerOrganizationTools(server);
|
|
160
|
+
registerDatastoreTools(server);
|
|
161
|
+
registerStatusTools(server);
|
|
162
|
+
registerResources(server);
|
|
163
|
+
|
|
164
|
+
// Process MCP request
|
|
165
|
+
const response = await server.handleRequest(body);
|
|
166
|
+
|
|
167
|
+
// Return JSON-RPC response
|
|
168
|
+
return new Response(JSON.stringify(response), {
|
|
169
|
+
headers: { 'Content-Type': 'application/json' }
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return new Response(JSON.stringify({
|
|
173
|
+
jsonrpc: '2.0',
|
|
174
|
+
error: {
|
|
175
|
+
code: -32603,
|
|
176
|
+
message: 'Internal error',
|
|
177
|
+
data: error.message
|
|
178
|
+
},
|
|
179
|
+
id: null
|
|
180
|
+
}), {
|
|
181
|
+
status: 500,
|
|
182
|
+
headers: { 'Content-Type': 'application/json' }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
### 2. HTTP Client Adaptation
|
|
191
|
+
|
|
192
|
+
**Current**: `src/utils/http.ts` uses `axios`
|
|
193
|
+
**Problem**: `axios` uses Node.js `http` module, not available in Workers
|
|
194
|
+
**Solution**: Replace with native `fetch()`
|
|
195
|
+
|
|
196
|
+
**Before** (axios):
|
|
197
|
+
```typescript
|
|
198
|
+
import axios from 'axios';
|
|
199
|
+
|
|
200
|
+
export async function makeCkanRequest<T>(
|
|
201
|
+
serverUrl: string,
|
|
202
|
+
endpoint: string,
|
|
203
|
+
params?: Record<string, any>
|
|
204
|
+
): Promise<T> {
|
|
205
|
+
const response = await axios.get(`${serverUrl}/api/3/action/${endpoint}`, {
|
|
206
|
+
params,
|
|
207
|
+
timeout: 30000,
|
|
208
|
+
headers: { 'User-Agent': 'CKAN-MCP-Server/1.0' }
|
|
209
|
+
});
|
|
210
|
+
return response.data.result;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**After** (fetch):
|
|
215
|
+
```typescript
|
|
216
|
+
export async function makeCkanRequest<T>(
|
|
217
|
+
serverUrl: string,
|
|
218
|
+
endpoint: string,
|
|
219
|
+
params?: Record<string, any>
|
|
220
|
+
): Promise<T> {
|
|
221
|
+
const url = new URL(`${serverUrl}/api/3/action/${endpoint}`);
|
|
222
|
+
if (params) {
|
|
223
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
224
|
+
url.searchParams.append(key, String(value));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const controller = new AbortController();
|
|
229
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(url.toString(), {
|
|
233
|
+
method: 'GET',
|
|
234
|
+
headers: { 'User-Agent': 'CKAN-MCP-Server/1.0' },
|
|
235
|
+
signal: controller.signal
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const data = await response.json();
|
|
243
|
+
if (!data.success) {
|
|
244
|
+
throw new Error(data.error?.message || 'CKAN API error');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return data.result;
|
|
248
|
+
} finally {
|
|
249
|
+
clearTimeout(timeoutId);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Changes**:
|
|
255
|
+
- Replace `axios.get()` with `fetch()`
|
|
256
|
+
- Build URL with `URLSearchParams`
|
|
257
|
+
- Implement timeout with `AbortController`
|
|
258
|
+
- Manual error handling (no axios interceptors)
|
|
259
|
+
|
|
260
|
+
**Impact**:
|
|
261
|
+
- Remove `axios` dependency from `package.json`
|
|
262
|
+
- Workers bundle size decreases (~20KB smaller)
|
|
263
|
+
- No changes to tool handlers (same API)
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### 3. Build System
|
|
268
|
+
|
|
269
|
+
**Current**: Single build target (Node.js)
|
|
270
|
+
```
|
|
271
|
+
esbuild.config.js → dist/index.js (CommonJS, Node.js platform)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Proposed**: Dual build targets
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
esbuild.config.js → dist/index.js (CommonJS, Node.js platform)
|
|
278
|
+
esbuild.worker.js → dist/worker.js (ESM, browser platform)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**New file**: `esbuild.worker.js`
|
|
282
|
+
```javascript
|
|
283
|
+
import esbuild from 'esbuild';
|
|
284
|
+
|
|
285
|
+
await esbuild.build({
|
|
286
|
+
entryPoints: ['src/worker.ts'],
|
|
287
|
+
bundle: true,
|
|
288
|
+
outfile: 'dist/worker.js',
|
|
289
|
+
format: 'esm', // Workers require ESM
|
|
290
|
+
platform: 'browser', // Workers use Web APIs
|
|
291
|
+
target: 'es2022',
|
|
292
|
+
external: [], // Bundle everything (no node_modules in Workers)
|
|
293
|
+
minify: true, // Reduce bundle size
|
|
294
|
+
sourcemap: false,
|
|
295
|
+
treeShaking: true,
|
|
296
|
+
mainFields: ['browser', 'module', 'main'],
|
|
297
|
+
conditions: ['worker', 'browser'],
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Key differences**:
|
|
302
|
+
- **format**: ESM (not CommonJS)
|
|
303
|
+
- **platform**: browser (not node)
|
|
304
|
+
- **external**: empty array (bundle all dependencies)
|
|
305
|
+
- **minify**: true (reduce Workers script size)
|
|
306
|
+
|
|
307
|
+
**Bundle analysis**:
|
|
308
|
+
- Current Node.js bundle: ~50KB
|
|
309
|
+
- Estimated Workers bundle: ~30-40KB (no axios, smaller MCP SDK)
|
|
310
|
+
- Workers limit: 1MB (10x safety margin)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### 4. Configuration
|
|
315
|
+
|
|
316
|
+
**File**: `wrangler.toml`
|
|
317
|
+
```toml
|
|
318
|
+
name = "ckan-mcp-server"
|
|
319
|
+
main = "dist/worker.js"
|
|
320
|
+
compatibility_date = "2024-01-01"
|
|
321
|
+
|
|
322
|
+
# Build configuration
|
|
323
|
+
[build]
|
|
324
|
+
command = "npm run build:worker"
|
|
325
|
+
watch_dir = "src"
|
|
326
|
+
|
|
327
|
+
# Environment variables (optional)
|
|
328
|
+
[vars]
|
|
329
|
+
DEFAULT_CKAN_SERVER = "https://demo.ckan.org"
|
|
330
|
+
|
|
331
|
+
# Production environment
|
|
332
|
+
[env.production]
|
|
333
|
+
name = "ckan-mcp-server"
|
|
334
|
+
routes = [
|
|
335
|
+
{ pattern = "ckan-mcp.example.com", custom_domain = true }
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
# Development environment
|
|
339
|
+
[env.development]
|
|
340
|
+
name = "ckan-mcp-server-dev"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Configuration options**:
|
|
344
|
+
- `name`: Workers script name (shows in Cloudflare dashboard)
|
|
345
|
+
- `main`: Entry point (must be ESM)
|
|
346
|
+
- `compatibility_date`: Workers runtime version
|
|
347
|
+
- `build.command`: Runs before deployment
|
|
348
|
+
- `vars`: Environment variables accessible in worker
|
|
349
|
+
- `env.*`: Multiple environments (dev/staging/prod)
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Design Decisions
|
|
354
|
+
|
|
355
|
+
### Decision 1: Keep Node.js Modes Unchanged
|
|
356
|
+
|
|
357
|
+
**Options**:
|
|
358
|
+
1. Replace Node.js runtime entirely with Workers (breaking change)
|
|
359
|
+
2. Support both Node.js and Workers (dual build)
|
|
360
|
+
|
|
361
|
+
**Choice**: Option 2 (dual build)
|
|
362
|
+
|
|
363
|
+
**Rationale**:
|
|
364
|
+
- **No breaking changes**: Existing users unaffected
|
|
365
|
+
- **Flexibility**: Users can choose deployment model
|
|
366
|
+
- **Development**: Local testing still uses Node.js (`npm start`)
|
|
367
|
+
- **Cost**: Minimal (2 build configs, ~100 lines of adapter code)
|
|
368
|
+
|
|
369
|
+
**Trade-offs**:
|
|
370
|
+
- **Pro**: Zero migration pain
|
|
371
|
+
- **Pro**: Supports offline/airgapped usage (stdio mode)
|
|
372
|
+
- **Con**: Maintain 2 entry points (worker.ts + index.ts)
|
|
373
|
+
- **Con**: Test 2 runtimes (Node.js + Workers)
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### Decision 2: Replace axios with fetch()
|
|
378
|
+
|
|
379
|
+
**Options**:
|
|
380
|
+
1. Bundle axios for Workers (use polyfill)
|
|
381
|
+
2. Replace with native fetch()
|
|
382
|
+
|
|
383
|
+
**Choice**: Option 2 (native fetch)
|
|
384
|
+
|
|
385
|
+
**Rationale**:
|
|
386
|
+
- **Bundle size**: axios is ~15KB minified, fetch is native (0KB)
|
|
387
|
+
- **Compatibility**: fetch is standard across Node.js 18+ and Workers
|
|
388
|
+
- **Simplicity**: No polyfills or adapters needed
|
|
389
|
+
- **Performance**: fetch is optimized in Workers runtime
|
|
390
|
+
|
|
391
|
+
**Trade-offs**:
|
|
392
|
+
- **Pro**: Smaller bundle, faster cold starts
|
|
393
|
+
- **Pro**: Future-proof (fetch is Web standard)
|
|
394
|
+
- **Con**: Rewrite HTTP client (~50 lines)
|
|
395
|
+
- **Con**: Different error handling (manual checks vs axios interceptors)
|
|
396
|
+
|
|
397
|
+
**Impact**:
|
|
398
|
+
- Modify `src/utils/http.ts` (~30 lines changed)
|
|
399
|
+
- No changes to tool handlers (API unchanged)
|
|
400
|
+
- Node.js modes continue using axios (or migrate to fetch for consistency)
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### Decision 3: No Server-Side Caching
|
|
405
|
+
|
|
406
|
+
**Options**:
|
|
407
|
+
1. Add Workers KV cache for CKAN responses
|
|
408
|
+
2. No caching (same as Node.js version)
|
|
409
|
+
|
|
410
|
+
**Choice**: Option 2 (no caching)
|
|
411
|
+
|
|
412
|
+
**Rationale**:
|
|
413
|
+
- **Consistency**: Same behavior as Node.js version
|
|
414
|
+
- **Freshness**: Always return latest CKAN data
|
|
415
|
+
- **Simplicity**: No cache invalidation logic
|
|
416
|
+
- **Free tier**: KV has storage limits (1GB)
|
|
417
|
+
|
|
418
|
+
**Trade-offs**:
|
|
419
|
+
- **Pro**: Simple, consistent, fresh data
|
|
420
|
+
- **Con**: Higher latency for repeated queries
|
|
421
|
+
- **Con**: More CKAN API requests
|
|
422
|
+
|
|
423
|
+
**Future enhancement**: Add optional caching with TTL (documented in future-ideas.md)
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### Decision 4: Single Global Deployment
|
|
428
|
+
|
|
429
|
+
**Options**:
|
|
430
|
+
1. Deploy to single Workers instance (user's account)
|
|
431
|
+
2. Provide official public endpoint (Anthropic-hosted)
|
|
432
|
+
3. Both
|
|
433
|
+
|
|
434
|
+
**Choice**: Option 1 (user deploys own instance)
|
|
435
|
+
|
|
436
|
+
**Rationale**:
|
|
437
|
+
- **No hosting cost**: Users deploy to their own free tier
|
|
438
|
+
- **No rate limit sharing**: Each user gets 100k req/day
|
|
439
|
+
- **No liability**: Anthropic not responsible for uptime/abuse
|
|
440
|
+
- **Easy forking**: Users can customize and deploy
|
|
441
|
+
|
|
442
|
+
**Trade-offs**:
|
|
443
|
+
- **Pro**: Zero hosting cost, infinite scalability
|
|
444
|
+
- **Pro**: Users control their deployment
|
|
445
|
+
- **Con**: Users must create Cloudflare account (5 min)
|
|
446
|
+
- **Con**: No "official" endpoint to share
|
|
447
|
+
|
|
448
|
+
**Future option**: Provide public endpoint in addition to user deployments
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Runtime Compatibility
|
|
453
|
+
|
|
454
|
+
### Node.js APIs → Workers APIs
|
|
455
|
+
|
|
456
|
+
| Node.js API | Workers API | Impact |
|
|
457
|
+
|----------------------|-----------------------|-----------------------|
|
|
458
|
+
| `http.createServer` | `fetch(request)` | Different entry point |
|
|
459
|
+
| `axios` | `fetch()` | Rewrite HTTP client |
|
|
460
|
+
| `process.env` | `env` parameter | Minimal (no env vars) |
|
|
461
|
+
| `console.log` | `console.log` | ✅ Compatible |
|
|
462
|
+
| `JSON.*` | `JSON.*` | ✅ Compatible |
|
|
463
|
+
| `URL` | `URL` | ✅ Compatible |
|
|
464
|
+
| `AbortController` | `AbortController` | ✅ Compatible |
|
|
465
|
+
|
|
466
|
+
**Compatibility score**: 95%
|
|
467
|
+
- Most code works unchanged
|
|
468
|
+
- Only HTTP client and entry point need adaptation
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Error Handling
|
|
473
|
+
|
|
474
|
+
### Workers-Specific Errors
|
|
475
|
+
|
|
476
|
+
1. **Script exceeded CPU time limit**
|
|
477
|
+
- **Cause**: Worker runs > 10ms CPU time (free tier)
|
|
478
|
+
- **Mitigation**: CKAN API calls are I/O (don't count toward CPU)
|
|
479
|
+
- **Likelihood**: Very low (current tools are I/O-bound)
|
|
480
|
+
|
|
481
|
+
2. **Script exceeded memory limit**
|
|
482
|
+
- **Cause**: Worker uses > 128MB memory (free tier)
|
|
483
|
+
- **Mitigation**: No large in-memory datasets
|
|
484
|
+
- **Likelihood**: Very low (responses truncated to 50KB)
|
|
485
|
+
|
|
486
|
+
3. **Subrequest limit exceeded**
|
|
487
|
+
- **Cause**: > 50 subrequests per invocation (free tier)
|
|
488
|
+
- **Mitigation**: Tools make 1-2 CKAN API calls max
|
|
489
|
+
- **Likelihood**: Zero (tools designed for single queries)
|
|
490
|
+
|
|
491
|
+
4. **Network timeout**
|
|
492
|
+
- **Cause**: CKAN API takes > 30s
|
|
493
|
+
- **Mitigation**: Same as Node.js (30s timeout with AbortController)
|
|
494
|
+
- **Likelihood**: Low (most CKAN queries < 5s)
|
|
495
|
+
|
|
496
|
+
**Error response format** (JSON-RPC):
|
|
497
|
+
```json
|
|
498
|
+
{
|
|
499
|
+
"jsonrpc": "2.0",
|
|
500
|
+
"error": {
|
|
501
|
+
"code": -32603,
|
|
502
|
+
"message": "Internal error",
|
|
503
|
+
"data": "Error details here"
|
|
504
|
+
},
|
|
505
|
+
"id": null
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Performance Characteristics
|
|
512
|
+
|
|
513
|
+
### Latency Breakdown
|
|
514
|
+
|
|
515
|
+
**Total latency** = Cold start + Worker execution + CKAN API + Response
|
|
516
|
+
|
|
517
|
+
1. **Cold start**: 0-50ms (first request after idle)
|
|
518
|
+
- Workers spin down after ~15 seconds of inactivity
|
|
519
|
+
- Subsequent requests: 0ms (hot start)
|
|
520
|
+
|
|
521
|
+
2. **Worker execution**: 1-5ms (CPU time)
|
|
522
|
+
- Parse JSON-RPC request
|
|
523
|
+
- Validate parameters
|
|
524
|
+
- Call CKAN API (I/O - doesn't count)
|
|
525
|
+
- Format response
|
|
526
|
+
|
|
527
|
+
3. **CKAN API**: 500-5000ms (network + server)
|
|
528
|
+
- Depends on CKAN portal load
|
|
529
|
+
- Italy (dati.gov.it): ~3s average
|
|
530
|
+
- US (data.gov): ~2s average
|
|
531
|
+
|
|
532
|
+
4. **Response serialization**: 1-5ms
|
|
533
|
+
- JSON.stringify()
|
|
534
|
+
- Truncate to 50KB if needed
|
|
535
|
+
|
|
536
|
+
**Expected total latency**:
|
|
537
|
+
- **First request** (cold start): 0.5-5.1s
|
|
538
|
+
- **Hot requests**: 0.5-5s
|
|
539
|
+
- **Target**: < 5s for 95th percentile
|
|
540
|
+
|
|
541
|
+
**Comparison to Node.js**:
|
|
542
|
+
- Similar (CKAN API dominates latency)
|
|
543
|
+
- Slight advantage: Workers edge routing (closer to CKAN servers)
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Security Considerations
|
|
548
|
+
|
|
549
|
+
### 1. Public Access
|
|
550
|
+
- **Risk**: Anyone can call Workers endpoint
|
|
551
|
+
- **Mitigation**: Read-only operations, no sensitive data
|
|
552
|
+
- **Decision**: Acceptable (CKAN portals are public)
|
|
553
|
+
|
|
554
|
+
### 2. Rate Limiting
|
|
555
|
+
- **Risk**: Abuse could exhaust free tier (100k req/day)
|
|
556
|
+
- **Mitigation**: Cloudflare automatic rate limiting, user can upgrade
|
|
557
|
+
- **Decision**: Monitor usage, add custom limits if needed
|
|
558
|
+
|
|
559
|
+
### 3. CKAN API Keys
|
|
560
|
+
- **Risk**: Some CKAN APIs require authentication
|
|
561
|
+
- **Mitigation**: Store API keys in Workers secrets (not implemented yet)
|
|
562
|
+
- **Decision**: Document in future-ideas.md (v0.5.0)
|
|
563
|
+
|
|
564
|
+
### 4. Input Validation
|
|
565
|
+
- **Risk**: Malformed requests crash worker
|
|
566
|
+
- **Mitigation**: Existing Zod schemas validate all inputs
|
|
567
|
+
- **Decision**: No changes needed (already secure)
|
|
568
|
+
|
|
569
|
+
### 5. CORS
|
|
570
|
+
- **Risk**: Browser clients blocked by CORS
|
|
571
|
+
- **Mitigation**: Add CORS headers to all responses
|
|
572
|
+
- **Decision**: Add `Access-Control-Allow-Origin: *` (public API)
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Testing Strategy
|
|
577
|
+
|
|
578
|
+
### 1. Local Testing (wrangler dev)
|
|
579
|
+
```bash
|
|
580
|
+
npm run dev:worker
|
|
581
|
+
curl http://localhost:8787/health
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Validates**:
|
|
585
|
+
- Build succeeds
|
|
586
|
+
- Worker starts without errors
|
|
587
|
+
- Basic routing works
|
|
588
|
+
|
|
589
|
+
### 2. Integration Testing (all tools)
|
|
590
|
+
```bash
|
|
591
|
+
# Test each tool locally
|
|
592
|
+
curl -X POST http://localhost:8787/mcp \
|
|
593
|
+
-H "Content-Type: application/json" \
|
|
594
|
+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"ckan_status_show","arguments":{"server_url":"https://demo.ckan.org"}},"id":1}'
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Validates**:
|
|
598
|
+
- All 7 tools work in Workers runtime
|
|
599
|
+
- CKAN API integration works
|
|
600
|
+
- Response formatting correct
|
|
601
|
+
|
|
602
|
+
### 3. Production Testing (after deployment)
|
|
603
|
+
```bash
|
|
604
|
+
# Test on live Workers endpoint
|
|
605
|
+
curl -X POST https://ckan-mcp-server.aborruso.workers.dev/mcp \
|
|
606
|
+
-H "Content-Type: application/json" \
|
|
607
|
+
-d '...'
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Validates**:
|
|
611
|
+
- Deployment successful
|
|
612
|
+
- HTTPS works
|
|
613
|
+
- Global routing works
|
|
614
|
+
|
|
615
|
+
### 4. Claude Desktop Integration
|
|
616
|
+
- Configure Claude Desktop with Workers URL
|
|
617
|
+
- Test all tools through Claude UI
|
|
618
|
+
- Verify markdown formatting
|
|
619
|
+
|
|
620
|
+
**Validates**:
|
|
621
|
+
- End-to-end MCP protocol
|
|
622
|
+
- User experience matches local mode
|
|
623
|
+
|
|
624
|
+
### 5. Load Testing (optional)
|
|
625
|
+
```bash
|
|
626
|
+
# Use wrk or ab to simulate load
|
|
627
|
+
wrk -t4 -c100 -d30s https://ckan-mcp-server.aborruso.workers.dev/health
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**Validates**:
|
|
631
|
+
- Handles concurrent requests
|
|
632
|
+
- No rate limit issues
|
|
633
|
+
- Stable under load
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Monitoring and Debugging
|
|
638
|
+
|
|
639
|
+
### 1. Cloudflare Dashboard
|
|
640
|
+
- **Metrics**: Requests/day, errors, CPU time
|
|
641
|
+
- **Access**: https://dash.cloudflare.com → Workers & Pages → ckan-mcp-server
|
|
642
|
+
|
|
643
|
+
### 2. Real-Time Logs
|
|
644
|
+
```bash
|
|
645
|
+
wrangler tail
|
|
646
|
+
```
|
|
647
|
+
Shows live logs from Workers (console.log, errors)
|
|
648
|
+
|
|
649
|
+
### 3. Error Tracking
|
|
650
|
+
- Workers dashboard shows error rate
|
|
651
|
+
- Stack traces in tail output
|
|
652
|
+
- Sentry integration (future)
|
|
653
|
+
|
|
654
|
+
### 4. Analytics
|
|
655
|
+
- Free tier includes basic analytics
|
|
656
|
+
- Paid tier: detailed metrics, alerts
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## Rollout Plan
|
|
661
|
+
|
|
662
|
+
### Phase 1: Development (tasks 1-3)
|
|
663
|
+
- Set up local environment
|
|
664
|
+
- Implement worker.ts
|
|
665
|
+
- Test locally with wrangler dev
|
|
666
|
+
|
|
667
|
+
### Phase 2: Deployment (task 4)
|
|
668
|
+
- Deploy to workers.dev
|
|
669
|
+
- Test in production
|
|
670
|
+
- Verify all tools work
|
|
671
|
+
|
|
672
|
+
### Phase 3: Documentation (tasks 4.3-4.5)
|
|
673
|
+
- Write DEPLOYMENT.md
|
|
674
|
+
- Update README.md
|
|
675
|
+
- Update LOG.md
|
|
676
|
+
|
|
677
|
+
### Phase 4: Validation (task 4.6)
|
|
678
|
+
- Claude Desktop integration test
|
|
679
|
+
- User acceptance
|
|
680
|
+
|
|
681
|
+
### Rollback Triggers
|
|
682
|
+
- Any tool fails in production
|
|
683
|
+
- Workers limits exceeded
|
|
684
|
+
- Unexpected errors > 1%
|
|
685
|
+
|
|
686
|
+
**Rollback action**: Keep using Node.js modes, document Workers issues
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## Future Enhancements
|
|
691
|
+
|
|
692
|
+
### 1. Response Caching (v0.5.0)
|
|
693
|
+
```typescript
|
|
694
|
+
// Use Workers KV for caching
|
|
695
|
+
const cache = await env.CACHE.get(`ckan:${serverUrl}:${endpoint}:${hash}`);
|
|
696
|
+
if (cache) return JSON.parse(cache);
|
|
697
|
+
|
|
698
|
+
const result = await makeCkanRequest(...);
|
|
699
|
+
await env.CACHE.put(`ckan:${serverUrl}:${endpoint}:${hash}`, JSON.stringify(result), {
|
|
700
|
+
expirationTtl: 3600 // 1 hour TTL
|
|
701
|
+
});
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### 2. Custom Domain (v0.5.0)
|
|
705
|
+
```toml
|
|
706
|
+
# wrangler.toml
|
|
707
|
+
routes = [
|
|
708
|
+
{ pattern = "ckan-mcp.example.com", custom_domain = true }
|
|
709
|
+
]
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### 3. Analytics Dashboard (v0.6.0)
|
|
713
|
+
- Track most used tools
|
|
714
|
+
- Monitor CKAN portal health
|
|
715
|
+
- Usage statistics
|
|
716
|
+
|
|
717
|
+
### 4. Multi-Region Deployment (v0.6.0)
|
|
718
|
+
- Deploy to specific Cloudflare regions
|
|
719
|
+
- Route to nearest CKAN portal
|
|
720
|
+
- Reduce latency
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Conclusion
|
|
725
|
+
|
|
726
|
+
This design enables Cloudflare Workers deployment with:
|
|
727
|
+
- **Minimal code changes** (~150 lines new code)
|
|
728
|
+
- **No breaking changes** (Node.js modes unchanged)
|
|
729
|
+
- **95% code reuse** (only entry point + HTTP client adapted)
|
|
730
|
+
- **Production-ready** (all 7 tools fully functional)
|
|
731
|
+
- **Future-proof** (uses Web standards: fetch, ESM)
|
|
732
|
+
|
|
733
|
+
**Risk**: Low (well-understood technologies, small scope)
|
|
734
|
+
**Reward**: High (global deployment, zero hosting cost, great UX)
|