@ansvar/eu-regulations-mcp 0.8.0 → 1.1.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.
Files changed (66) hide show
  1. package/README.md +76 -29
  2. package/data/regulations.db +0 -0
  3. package/data/seed/applicability/chips-act.json +67 -0
  4. package/data/seed/applicability/crma.json +85 -0
  5. package/data/seed/chips-act.json +714 -0
  6. package/data/seed/crma.json +877 -0
  7. package/data/seed/mappings/iso27001-chips-act.json +50 -0
  8. package/data/seed/mappings/iso27001-crma.json +50 -0
  9. package/data/seed/mappings/nist-csf-chips-act.json +56 -0
  10. package/data/seed/mappings/nist-csf-crma.json +56 -0
  11. package/dist/database/sqlite-adapter.d.ts +2 -2
  12. package/dist/database/sqlite-adapter.d.ts.map +1 -1
  13. package/dist/database/sqlite-adapter.js.map +1 -1
  14. package/dist/http-server.js +27 -5
  15. package/dist/http-server.js.map +1 -1
  16. package/dist/index.js +27 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/tools/about.d.ts +40 -0
  19. package/dist/tools/about.d.ts.map +1 -0
  20. package/dist/tools/about.js +61 -0
  21. package/dist/tools/about.js.map +1 -0
  22. package/dist/tools/list.d.ts +7 -0
  23. package/dist/tools/list.d.ts.map +1 -1
  24. package/dist/tools/list.js +73 -8
  25. package/dist/tools/list.js.map +1 -1
  26. package/dist/tools/registry.d.ts +11 -1
  27. package/dist/tools/registry.d.ts.map +1 -1
  28. package/dist/tools/registry.js +56 -4
  29. package/dist/tools/registry.js.map +1 -1
  30. package/dist/worker.d.ts.map +1 -1
  31. package/dist/worker.js +17 -5
  32. package/dist/worker.js.map +1 -1
  33. package/package.json +8 -7
  34. package/scripts/add-cross-references.sql +0 -200
  35. package/scripts/analyze-survey-responses.ts +0 -285
  36. package/scripts/build-db.ts +0 -421
  37. package/scripts/bulk-reingest-all.ts +0 -331
  38. package/scripts/check-updates.ts +0 -294
  39. package/scripts/extract-eprivacy-recitals.ts +0 -98
  40. package/scripts/ingest-eurlex-browser.ts +0 -113
  41. package/scripts/ingest-eurlex.ts +0 -346
  42. package/scripts/ingest-unece.ts +0 -382
  43. package/scripts/migrate-postgres.ts +0 -445
  44. package/scripts/migrate-to-postgres.ts +0 -353
  45. package/scripts/reingest-all-with-recitals.sh +0 -81
  46. package/scripts/sync-versions.ts +0 -206
  47. package/scripts/test-cross-refs.js +0 -26
  48. package/scripts/test-postgres-adapter.ts +0 -146
  49. package/scripts/update-dora-rts-metadata.ts +0 -112
  50. package/src/database/postgres-adapter.ts +0 -84
  51. package/src/database/sqlite-adapter.ts +0 -44
  52. package/src/database/types.ts +0 -10
  53. package/src/http-server.ts +0 -149
  54. package/src/index.ts +0 -61
  55. package/src/middleware/rate-limit.ts +0 -104
  56. package/src/tools/applicability.ts +0 -167
  57. package/src/tools/article.ts +0 -81
  58. package/src/tools/compare.ts +0 -217
  59. package/src/tools/definitions.ts +0 -49
  60. package/src/tools/evidence.ts +0 -84
  61. package/src/tools/list.ts +0 -124
  62. package/src/tools/map.ts +0 -86
  63. package/src/tools/recital.ts +0 -60
  64. package/src/tools/registry.ts +0 -311
  65. package/src/tools/search.ts +0 -297
  66. package/src/worker.ts +0 -708
package/src/worker.ts DELETED
@@ -1,708 +0,0 @@
1
- /**
2
- * Cloudflare Worker - EU Regulations HTTP API
3
- *
4
- * This is a simplified HTTP API wrapper for direct client access (ChatGPT, GitHub Copilot).
5
- * It does NOT implement the full MCP protocol - it provides a REST-style endpoint for tool execution.
6
- *
7
- * Architecture:
8
- * - POST /api/tool - Execute a tool by name with parameters (see API contract below)
9
- * - GET /tools - List available tools with their schemas (for discovery)
10
- * - GET /health - Health check endpoint
11
- * - GET / - API documentation and usage examples
12
- *
13
- * This design choice was made for simplicity over protocol compliance, targeting AI assistants
14
- * that need direct HTTP access rather than MCP protocol support.
15
- *
16
- * API Contract:
17
- * POST /api/tool
18
- * Request: { "tool": "tool_name", "params": { ...tool-specific params } }
19
- * Response: { "result": { ...tool result }, "timestamp": "ISO8601" }
20
- * Error: { "error": "error_type", "message": "details" }
21
- *
22
- * Features:
23
- * - PostgreSQL database adapter (Neon serverless)
24
- * - IP-based rate limiting (100 req/hour default)
25
- * - CORS support for ChatGPT/Copilot origins
26
- * - Rate limit headers (X-RateLimit-*)
27
- *
28
- * Environment variables:
29
- * - DATABASE_URL: PostgreSQL connection string (required)
30
- * - RATE_LIMIT_MAX_REQUESTS: Max requests per window (default: 100)
31
- * - RATE_LIMIT_WINDOW_MS: Rate limit window in ms (default: 3600000 = 1 hour)
32
- */
33
-
34
- import { createPostgresAdapter } from './database/postgres-adapter.js';
35
- import { RateLimiter } from './middleware/rate-limit.js';
36
- import type { DatabaseAdapter } from './database/types.js';
37
-
38
- // Import tool registry (single source of truth for tools)
39
- import { TOOLS } from './tools/registry.js';
40
-
41
- // Import tool handlers
42
- import { searchRegulations } from './tools/search.js';
43
- import { getArticle } from './tools/article.js';
44
- import { getRecital } from './tools/recital.js';
45
- import { listRegulations } from './tools/list.js';
46
- import { compareRequirements } from './tools/compare.js';
47
- import { mapControls } from './tools/map.js';
48
- import { checkApplicability } from './tools/applicability.js';
49
- import { getDefinitions } from './tools/definitions.js';
50
- import { getEvidenceRequirements } from './tools/evidence.js';
51
-
52
- interface Env {
53
- DATABASE_URL: string;
54
- RATE_LIMIT_MAX_REQUESTS?: string;
55
- RATE_LIMIT_WINDOW_MS?: string;
56
- NODE_ENV?: string;
57
- }
58
-
59
- // Global instances (initialized on first request)
60
- let db: DatabaseAdapter | null = null;
61
- let rateLimiter: RateLimiter | null = null;
62
-
63
- /**
64
- * Initialize database connection (lazy, cached)
65
- */
66
- async function getDatabase(env: Env): Promise<DatabaseAdapter> {
67
- if (!db) {
68
- if (!env.DATABASE_URL) {
69
- throw new Error('DATABASE_URL environment variable is required');
70
- }
71
- db = await createPostgresAdapter(env.DATABASE_URL);
72
- }
73
- return db;
74
- }
75
-
76
- /**
77
- * Initialize rate limiter (lazy, cached)
78
- */
79
- function getRateLimiter(env: Env): RateLimiter {
80
- if (!rateLimiter) {
81
- const maxRequests = parseInt(env.RATE_LIMIT_MAX_REQUESTS || '100');
82
- const windowMs = parseInt(env.RATE_LIMIT_WINDOW_MS || '3600000');
83
- rateLimiter = new RateLimiter(maxRequests, windowMs);
84
- }
85
- return rateLimiter;
86
- }
87
-
88
- /**
89
- * Extract client IP from Cloudflare headers.
90
- * Used for rate limiting by IP address.
91
- */
92
- function getClientIP(request: Request): string {
93
- return (
94
- request.headers.get('CF-Connecting-IP') ||
95
- request.headers.get('X-Forwarded-For')?.split(',')[0].trim() ||
96
- '0.0.0.0'
97
- );
98
- }
99
-
100
- /**
101
- * Generate CORS headers for ChatGPT/Copilot access.
102
- * Only allows requests from approved AI assistant origins.
103
- *
104
- * @param origin - The Origin header from the request
105
- * @returns CORS headers for the response, or null if origin is not allowed
106
- */
107
- function corsHeaders(origin?: string): HeadersInit | null {
108
- const allowedOrigins = [
109
- 'https://chat.openai.com',
110
- 'https://chatgpt.com',
111
- 'https://copilot.microsoft.com',
112
- 'https://github.com',
113
- ];
114
-
115
- // If no origin or origin not in allowed list, return null
116
- if (!origin || !allowedOrigins.includes(origin)) {
117
- return null;
118
- }
119
-
120
- return {
121
- 'Access-Control-Allow-Origin': origin,
122
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
123
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
124
- 'Access-Control-Max-Age': '86400',
125
- };
126
- }
127
-
128
- /**
129
- * Safely merge CORS headers, rejecting the request if origin is not allowed.
130
- * Used in responses that don't have access to request origin.
131
- *
132
- * @param baseHeaders - Base headers to merge with CORS headers
133
- * @param origin - The Origin header from the request
134
- * @returns Merged headers, or generates a 403 response if origin not allowed
135
- */
136
- function mergeCorsHeaders(
137
- baseHeaders: HeadersInit,
138
- origin?: string
139
- ): HeadersInit | Response {
140
- const cors = corsHeaders(origin);
141
-
142
- if (!cors) {
143
- return new Response(
144
- JSON.stringify({
145
- error: 'Forbidden',
146
- message: 'Origin not allowed',
147
- }),
148
- {
149
- status: 403,
150
- headers: {
151
- 'Content-Type': 'application/json',
152
- },
153
- }
154
- );
155
- }
156
-
157
- return { ...baseHeaders, ...cors };
158
- }
159
-
160
- /**
161
- * List available tools endpoint.
162
- * Returns all tools with their schemas for discovery.
163
- *
164
- * GET /tools
165
- * Response: { "tools": [{ "name": "...", "description": "...", "inputSchema": {...} }] }
166
- */
167
- function handleListTools(origin?: string): Response {
168
- const headers = mergeCorsHeaders(
169
- { 'Content-Type': 'application/json' },
170
- origin
171
- );
172
-
173
- if (headers instanceof Response) {
174
- return headers;
175
- }
176
-
177
- return new Response(
178
- JSON.stringify({
179
- tools: TOOLS.map(tool => ({
180
- name: tool.name,
181
- description: tool.description,
182
- inputSchema: tool.inputSchema,
183
- })),
184
- count: TOOLS.length,
185
- timestamp: new Date().toISOString(),
186
- }),
187
- {
188
- status: 200,
189
- headers,
190
- }
191
- );
192
- }
193
-
194
- /**
195
- * Health check endpoint.
196
- * Tests database connectivity and returns server status.
197
- *
198
- * GET /health
199
- * Response: { "status": "healthy|unhealthy", "database": "connected|error", ... }
200
- */
201
- async function handleHealthCheck(env: Env, origin?: string): Promise<Response> {
202
- const baseHeaders = { 'Content-Type': 'application/json' };
203
- const headers = mergeCorsHeaders(baseHeaders, origin);
204
-
205
- if (headers instanceof Response) {
206
- return headers;
207
- }
208
-
209
- try {
210
- const database = await getDatabase(env);
211
-
212
- // Test database connection
213
- await database.query('SELECT 1');
214
-
215
- return new Response(
216
- JSON.stringify({
217
- status: 'healthy',
218
- server: 'eu-regulations-mcp',
219
- version: '0.6.5',
220
- database: 'connected',
221
- timestamp: new Date().toISOString(),
222
- }),
223
- {
224
- status: 200,
225
- headers,
226
- }
227
- );
228
- } catch (error) {
229
- return new Response(
230
- JSON.stringify({
231
- status: 'unhealthy',
232
- error: error instanceof Error ? error.message : 'Unknown error',
233
- timestamp: new Date().toISOString(),
234
- }),
235
- {
236
- status: 503,
237
- headers,
238
- }
239
- );
240
- }
241
- }
242
-
243
- /**
244
- * Generate rate limit exceeded response.
245
- * Returns 429 status with retry information.
246
- *
247
- * @param resetAt - Timestamp when rate limit resets
248
- * @param limit - The actual rate limit
249
- * @param origin - The Origin header from the request
250
- * @returns 429 response with retry headers
251
- */
252
- function rateLimitResponse(
253
- resetAt: number,
254
- limit: number,
255
- origin?: string
256
- ): Response {
257
- const resetDate = new Date(resetAt);
258
- const baseHeaders = {
259
- 'Content-Type': 'application/json',
260
- 'Retry-After': Math.ceil((resetAt - Date.now()) / 1000).toString(),
261
- 'X-RateLimit-Limit': limit.toString(),
262
- 'X-RateLimit-Remaining': '0',
263
- 'X-RateLimit-Reset': resetDate.toISOString(),
264
- };
265
-
266
- const headers = mergeCorsHeaders(baseHeaders, origin);
267
-
268
- if (headers instanceof Response) {
269
- return headers;
270
- }
271
-
272
- return new Response(
273
- JSON.stringify({
274
- error: 'Rate limit exceeded',
275
- message: 'Too many requests. Please try again later.',
276
- retryAfter: Math.ceil((resetAt - Date.now()) / 1000),
277
- resetAt: resetDate.toISOString(),
278
- }),
279
- {
280
- status: 429,
281
- headers,
282
- }
283
- );
284
- }
285
-
286
- /**
287
- * Handle API tool execution endpoint.
288
- *
289
- * POST /api/tool
290
- * Request body: { "tool": "tool_name", "params": { ...tool-specific parameters } }
291
- * Success response: { "result": { ...tool output }, "timestamp": "ISO8601" }
292
- * Error response: { "error": "error_type", "message": "details" }
293
- *
294
- * @param request - The incoming HTTP request
295
- * @param env - Cloudflare Worker environment
296
- * @returns Response with tool result or error
297
- */
298
- async function handleToolCall(
299
- request: Request,
300
- env: Env
301
- ): Promise<Response> {
302
- const limiter = getRateLimiter(env);
303
- const clientIP = getClientIP(request);
304
- const origin = request.headers.get('Origin') || undefined;
305
-
306
- // Check rate limit
307
- const rateLimitInfo = limiter.getRateLimitInfo(clientIP);
308
- if (!rateLimitInfo.allowed) {
309
- return rateLimitResponse(
310
- rateLimitInfo.resetAt,
311
- limiter['maxRequests'],
312
- origin
313
- );
314
- }
315
-
316
- // Check request size (max 100KB)
317
- const contentLength = request.headers.get('Content-Length');
318
- const maxSize = 100 * 1024; // 100KB
319
- if (contentLength && parseInt(contentLength) > maxSize) {
320
- const baseHeaders = {
321
- 'Content-Type': 'application/json',
322
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
323
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
324
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
325
- };
326
- const headers = mergeCorsHeaders(baseHeaders, origin);
327
-
328
- if (headers instanceof Response) {
329
- return headers;
330
- }
331
-
332
- return new Response(
333
- JSON.stringify({
334
- error: 'Payload too large',
335
- message: 'Request body must not exceed 100KB',
336
- }),
337
- {
338
- status: 413,
339
- headers,
340
- }
341
- );
342
- }
343
-
344
- try {
345
- const database = await getDatabase(env);
346
- const body = (await request.json()) as any;
347
-
348
- // Input validation
349
- if (!body || typeof body !== 'object') {
350
- const baseHeaders = {
351
- 'Content-Type': 'application/json',
352
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
353
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
354
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
355
- };
356
- const headers = mergeCorsHeaders(baseHeaders, origin);
357
-
358
- if (headers instanceof Response) {
359
- return headers;
360
- }
361
-
362
- return new Response(
363
- JSON.stringify({
364
- error: 'Invalid request',
365
- message: 'Request body must be a JSON object',
366
- }),
367
- {
368
- status: 400,
369
- headers,
370
- }
371
- );
372
- }
373
-
374
- if (!body.tool || typeof body.tool !== 'string') {
375
- const baseHeaders = {
376
- 'Content-Type': 'application/json',
377
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
378
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
379
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
380
- };
381
- const headers = mergeCorsHeaders(baseHeaders, origin);
382
-
383
- if (headers instanceof Response) {
384
- return headers;
385
- }
386
-
387
- return new Response(
388
- JSON.stringify({
389
- error: 'Invalid request',
390
- message: 'Missing or invalid "tool" field (must be a string)',
391
- }),
392
- {
393
- status: 400,
394
- headers,
395
- }
396
- );
397
- }
398
-
399
- if (body.params !== undefined && typeof body.params !== 'object') {
400
- const baseHeaders = {
401
- 'Content-Type': 'application/json',
402
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
403
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
404
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
405
- };
406
- const headers = mergeCorsHeaders(baseHeaders, origin);
407
-
408
- if (headers instanceof Response) {
409
- return headers;
410
- }
411
-
412
- return new Response(
413
- JSON.stringify({
414
- error: 'Invalid request',
415
- message: 'Invalid "params" field (must be an object if provided)',
416
- }),
417
- {
418
- status: 400,
419
- headers,
420
- }
421
- );
422
- }
423
-
424
- let result: any;
425
-
426
- // Route to appropriate tool handler
427
- switch (body.tool) {
428
- case 'search_regulations':
429
- result = await searchRegulations(database, body.params);
430
- break;
431
- case 'get_article':
432
- result = await getArticle(database, body.params);
433
- break;
434
- case 'get_recital':
435
- result = await getRecital(database, body.params);
436
- break;
437
- case 'list_regulations':
438
- result = await listRegulations(database, body.params || {});
439
- break;
440
- case 'compare_requirements':
441
- result = await compareRequirements(database, body.params);
442
- break;
443
- case 'map_controls':
444
- result = await mapControls(database, body.params);
445
- break;
446
- case 'check_applicability':
447
- result = await checkApplicability(database, body.params);
448
- break;
449
- case 'get_definitions':
450
- result = await getDefinitions(database, body.params);
451
- break;
452
- case 'get_evidence_requirements':
453
- result = await getEvidenceRequirements(database, body.params);
454
- break;
455
- default:
456
- const baseHeaders = {
457
- 'Content-Type': 'application/json',
458
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
459
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
460
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
461
- };
462
- const headers = mergeCorsHeaders(baseHeaders, origin);
463
-
464
- if (headers instanceof Response) {
465
- return headers;
466
- }
467
-
468
- return new Response(
469
- JSON.stringify({
470
- error: 'Unknown tool',
471
- message: `Tool '${body.tool}' not found`,
472
- }),
473
- {
474
- status: 400,
475
- headers,
476
- }
477
- );
478
- }
479
-
480
- // Return successful response
481
- const successHeaders = {
482
- 'Content-Type': 'application/json',
483
- 'X-RateLimit-Limit': limiter['maxRequests'].toString(),
484
- 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(),
485
- 'X-RateLimit-Reset': new Date(rateLimitInfo.resetAt).toISOString(),
486
- };
487
- const headers = mergeCorsHeaders(successHeaders, origin);
488
-
489
- if (headers instanceof Response) {
490
- return headers;
491
- }
492
-
493
- return new Response(
494
- JSON.stringify({
495
- result,
496
- timestamp: new Date().toISOString(),
497
- }),
498
- {
499
- status: 200,
500
- headers,
501
- }
502
- );
503
- } catch (error) {
504
- // Log full error details for debugging
505
- console.error('Tool execution error:', {
506
- error: error instanceof Error ? error.message : 'Unknown error',
507
- stack: error instanceof Error ? error.stack : undefined,
508
- timestamp: new Date().toISOString(),
509
- });
510
-
511
- // Sanitize error message for client
512
- const isDevelopment = env.NODE_ENV === 'development';
513
- const errorMessage = isDevelopment
514
- ? error instanceof Error
515
- ? error.message
516
- : 'Unknown error'
517
- : 'An error occurred while processing your request';
518
-
519
- const errorHeaders = {
520
- 'Content-Type': 'application/json',
521
- };
522
- const headers = mergeCorsHeaders(errorHeaders, origin);
523
-
524
- if (headers instanceof Response) {
525
- return headers;
526
- }
527
-
528
- return new Response(
529
- JSON.stringify({
530
- error: 'Internal server error',
531
- message: errorMessage,
532
- }),
533
- {
534
- status: 500,
535
- headers,
536
- }
537
- );
538
- }
539
- }
540
-
541
- /**
542
- * Main fetch handler
543
- */
544
- export default {
545
- async fetch(request: Request, env: Env): Promise<Response> {
546
- const url = new URL(request.url);
547
- const origin = request.headers.get('Origin') || undefined;
548
-
549
- // Handle CORS preflight
550
- if (request.method === 'OPTIONS') {
551
- const headers = corsHeaders(origin);
552
- if (!headers) {
553
- return new Response(
554
- JSON.stringify({
555
- error: 'Forbidden',
556
- message: 'Origin not allowed',
557
- }),
558
- {
559
- status: 403,
560
- headers: {
561
- 'Content-Type': 'application/json',
562
- },
563
- }
564
- );
565
- }
566
-
567
- return new Response(null, {
568
- status: 204,
569
- headers,
570
- });
571
- }
572
-
573
- // Route handling
574
- switch (url.pathname) {
575
- case '/health':
576
- return handleHealthCheck(env, origin);
577
-
578
- case '/tools':
579
- if (request.method !== 'GET') {
580
- const baseHeaders = {
581
- 'Content-Type': 'application/json',
582
- Allow: 'GET',
583
- };
584
- const headers = mergeCorsHeaders(baseHeaders, origin);
585
-
586
- if (headers instanceof Response) {
587
- return headers;
588
- }
589
-
590
- return new Response(
591
- JSON.stringify({ error: 'Method not allowed' }),
592
- {
593
- status: 405,
594
- headers,
595
- }
596
- );
597
- }
598
- return handleListTools(origin);
599
-
600
- case '/api/tool':
601
- if (request.method !== 'POST') {
602
- const baseHeaders = {
603
- 'Content-Type': 'application/json',
604
- Allow: 'POST',
605
- };
606
- const headers = mergeCorsHeaders(baseHeaders, origin);
607
-
608
- if (headers instanceof Response) {
609
- return headers;
610
- }
611
-
612
- return new Response(
613
- JSON.stringify({ error: 'Method not allowed' }),
614
- {
615
- status: 405,
616
- headers,
617
- }
618
- );
619
- }
620
- return handleToolCall(request, env);
621
-
622
- case '/': {
623
- const baseHeaders = { 'Content-Type': 'application/json' };
624
- const headers = mergeCorsHeaders(baseHeaders, origin);
625
-
626
- if (headers instanceof Response) {
627
- return headers;
628
- }
629
-
630
- return new Response(
631
- JSON.stringify({
632
- server: 'eu-regulations-mcp',
633
- version: '0.6.5',
634
- description:
635
- 'HTTP API for EU cybersecurity regulations (ChatGPT/Copilot compatible)',
636
- endpoints: {
637
- '/': 'API documentation (this page)',
638
- '/health': 'Health check (GET)',
639
- '/tools': 'List available tools with schemas (GET)',
640
- '/api/tool': 'Execute a tool (POST)',
641
- },
642
- documentation:
643
- 'https://github.com/Ansvar-Systems/EU_compliance_MCP',
644
- usage: {
645
- discovery: {
646
- description: 'Get list of available tools',
647
- method: 'GET',
648
- url: '/tools',
649
- response: {
650
- tools: '[array of tools with name, description, inputSchema]',
651
- count: 9,
652
- },
653
- },
654
- execution: {
655
- description: 'Execute a tool',
656
- method: 'POST',
657
- url: '/api/tool',
658
- body: {
659
- tool: 'search_regulations',
660
- params: {
661
- query: 'incident reporting',
662
- limit: 10,
663
- },
664
- },
665
- response: {
666
- result: '[tool-specific output]',
667
- timestamp: 'ISO8601',
668
- },
669
- },
670
- },
671
- rateLimits: {
672
- requests: '100 per hour per IP',
673
- headers:
674
- 'X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset',
675
- },
676
- }),
677
- {
678
- status: 200,
679
- headers,
680
- }
681
- );
682
- }
683
-
684
- default: {
685
- const baseHeaders = { 'Content-Type': 'application/json' };
686
- const headers = mergeCorsHeaders(baseHeaders, origin);
687
-
688
- if (headers instanceof Response) {
689
- return headers;
690
- }
691
-
692
- return new Response(JSON.stringify({ error: 'Not found' }), {
693
- status: 404,
694
- headers,
695
- });
696
- }
697
- }
698
- },
699
-
700
- /**
701
- * Cleanup handler (called when worker is terminated)
702
- */
703
- async cleanup() {
704
- if (db) {
705
- await db.close();
706
- }
707
- },
708
- };