@ansvar/eu-regulations-mcp 0.1.0 → 0.2.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/LICENSE +190 -21
- package/README.md +125 -26
- package/data/seed/aifmd.json +432 -0
- package/data/seed/applicability/ai-act.json +87 -0
- package/data/seed/applicability/aifmd.json +74 -0
- package/data/seed/applicability/cbam.json +74 -0
- package/data/seed/applicability/cer.json +74 -0
- package/data/seed/applicability/cra.json +77 -0
- package/data/seed/applicability/csddd.json +74 -0
- package/data/seed/applicability/csrd.json +74 -0
- package/data/seed/applicability/cyber_solidarity.json +74 -0
- package/data/seed/applicability/cybersecurity-act.json +69 -0
- package/data/seed/applicability/data-act.json +71 -0
- package/data/seed/applicability/dga.json +74 -0
- package/data/seed/applicability/dma.json +77 -0
- package/data/seed/applicability/dsa.json +71 -0
- package/data/seed/applicability/eecc.json +74 -0
- package/data/seed/applicability/ehds.json +74 -0
- package/data/seed/applicability/eidas2.json +86 -0
- package/data/seed/applicability/eprivacy.json +74 -0
- package/data/seed/applicability/eu_taxonomy.json +74 -0
- package/data/seed/applicability/eucc.json +74 -0
- package/data/seed/applicability/eudr.json +74 -0
- package/data/seed/applicability/gpsr.json +74 -0
- package/data/seed/applicability/ivdr.json +74 -0
- package/data/seed/applicability/led.json +74 -0
- package/data/seed/applicability/machinery.json +74 -0
- package/data/seed/applicability/mdr.json +74 -0
- package/data/seed/applicability/mica.json +74 -0
- package/data/seed/applicability/mifid2.json +74 -0
- package/data/seed/applicability/mifir.json +74 -0
- package/data/seed/applicability/pld.json +74 -0
- package/data/seed/applicability/psd2.json +74 -0
- package/data/seed/applicability/red.json +74 -0
- package/data/seed/applicability/sfdr.json +74 -0
- package/data/seed/applicability/un-r155.json +68 -0
- package/data/seed/applicability/un-r156.json +68 -0
- package/data/seed/cbam.json +397 -0
- package/data/seed/cer.json +233 -0
- package/data/seed/csddd.json +205 -0
- package/data/seed/csrd.json +50 -0
- package/data/seed/cyber_solidarity.json +252 -0
- package/data/seed/data-act.json +517 -0
- package/data/seed/dga.json +342 -0
- package/data/seed/dma.json +499 -0
- package/data/seed/dsa.json +686 -0
- package/data/seed/eecc.json +981 -0
- package/data/seed/ehds.json +638 -0
- package/data/seed/eidas2.json +590 -0
- package/data/seed/eprivacy.json +115 -0
- package/data/seed/eu_taxonomy.json +285 -0
- package/data/seed/eucc.json +386 -0
- package/data/seed/eudr.json +401 -0
- package/data/seed/gpsr.json +462 -0
- package/data/seed/ivdr.json +1036 -0
- package/data/seed/led.json +480 -0
- package/data/seed/machinery.json +513 -0
- package/data/seed/mappings/iso27001-ai-act.json +114 -0
- package/data/seed/mappings/iso27001-aifmd.json +50 -0
- package/data/seed/mappings/iso27001-cbam.json +26 -0
- package/data/seed/mappings/iso27001-cer.json +74 -0
- package/data/seed/mappings/iso27001-cra.json +130 -0
- package/data/seed/mappings/iso27001-csddd.json +50 -0
- package/data/seed/mappings/iso27001-csrd.json +26 -0
- package/data/seed/mappings/iso27001-cyber_solidarity.json +82 -0
- package/data/seed/mappings/iso27001-cybersecurity-act.json +90 -0
- package/data/seed/mappings/iso27001-data-act.json +66 -0
- package/data/seed/mappings/iso27001-dga.json +50 -0
- package/data/seed/mappings/iso27001-dma.json +50 -0
- package/data/seed/mappings/iso27001-dsa.json +58 -0
- package/data/seed/mappings/iso27001-eecc.json +74 -0
- package/data/seed/mappings/iso27001-ehds.json +90 -0
- package/data/seed/mappings/iso27001-eidas2.json +106 -0
- package/data/seed/mappings/iso27001-eprivacy.json +66 -0
- package/data/seed/mappings/iso27001-eu_taxonomy.json +34 -0
- package/data/seed/mappings/iso27001-eucc.json +66 -0
- package/data/seed/mappings/iso27001-eudr.json +34 -0
- package/data/seed/mappings/iso27001-gpsr.json +42 -0
- package/data/seed/mappings/iso27001-ivdr.json +66 -0
- package/data/seed/mappings/iso27001-led.json +74 -0
- package/data/seed/mappings/iso27001-machinery.json +50 -0
- package/data/seed/mappings/iso27001-mdr.json +82 -0
- package/data/seed/mappings/iso27001-mica.json +66 -0
- package/data/seed/mappings/iso27001-mifid2.json +66 -0
- package/data/seed/mappings/iso27001-mifir.json +42 -0
- package/data/seed/mappings/iso27001-pld.json +26 -0
- package/data/seed/mappings/iso27001-psd2.json +82 -0
- package/data/seed/mappings/iso27001-red.json +42 -0
- package/data/seed/mappings/iso27001-sfdr.json +50 -0
- package/data/seed/mappings/iso27001-un-r155.json +130 -0
- package/data/seed/mappings/iso27001-un-r156.json +106 -0
- package/data/seed/mappings/nist-csf-ai-act.json +138 -0
- package/data/seed/mappings/nist-csf-aifmd.json +58 -0
- package/data/seed/mappings/nist-csf-cbam.json +42 -0
- package/data/seed/mappings/nist-csf-cer.json +90 -0
- package/data/seed/mappings/nist-csf-cra.json +130 -0
- package/data/seed/mappings/nist-csf-csddd.json +50 -0
- package/data/seed/mappings/nist-csf-csrd.json +34 -0
- package/data/seed/mappings/nist-csf-cyber_solidarity.json +90 -0
- package/data/seed/mappings/nist-csf-cybersecurity-act.json +90 -0
- package/data/seed/mappings/nist-csf-data-act.json +50 -0
- package/data/seed/mappings/nist-csf-dga.json +58 -0
- package/data/seed/mappings/nist-csf-dma.json +42 -0
- package/data/seed/mappings/nist-csf-dora.json +210 -0
- package/data/seed/mappings/nist-csf-dsa.json +82 -0
- package/data/seed/mappings/nist-csf-eecc.json +90 -0
- package/data/seed/mappings/nist-csf-ehds.json +98 -0
- package/data/seed/mappings/nist-csf-eidas2.json +114 -0
- package/data/seed/mappings/nist-csf-eprivacy.json +58 -0
- package/data/seed/mappings/nist-csf-eu_taxonomy.json +34 -0
- package/data/seed/mappings/nist-csf-eucc.json +66 -0
- package/data/seed/mappings/nist-csf-eudr.json +58 -0
- package/data/seed/mappings/nist-csf-gdpr.json +178 -0
- package/data/seed/mappings/nist-csf-gpsr.json +58 -0
- package/data/seed/mappings/nist-csf-ivdr.json +66 -0
- package/data/seed/mappings/nist-csf-led.json +74 -0
- package/data/seed/mappings/nist-csf-machinery.json +58 -0
- package/data/seed/mappings/nist-csf-mdr.json +66 -0
- package/data/seed/mappings/nist-csf-mica.json +98 -0
- package/data/seed/mappings/nist-csf-mifid2.json +74 -0
- package/data/seed/mappings/nist-csf-mifir.json +50 -0
- package/data/seed/mappings/nist-csf-nis2.json +194 -0
- package/data/seed/mappings/nist-csf-pld.json +34 -0
- package/data/seed/mappings/nist-csf-psd2.json +98 -0
- package/data/seed/mappings/nist-csf-red.json +58 -0
- package/data/seed/mappings/nist-csf-sfdr.json +42 -0
- package/data/seed/mappings/nist-csf-un-r155.json +130 -0
- package/data/seed/mappings/nist-csf-un-r156.json +98 -0
- package/data/seed/mdr.json +1066 -0
- package/data/seed/mica.json +1003 -0
- package/data/seed/mifid2.json +906 -0
- package/data/seed/mifir.json +512 -0
- package/data/seed/pld.json +244 -0
- package/data/seed/psd2.json +827 -0
- package/data/seed/red.json +452 -0
- package/data/seed/sfdr.json +228 -0
- package/data/seed/un-r155.json +166 -0
- package/data/seed/un-r156.json +150 -0
- package/dist/http-server.d.ts +9 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +342 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/map.d.ts +1 -1
- package/dist/tools/map.d.ts.map +1 -1
- package/dist/tools/map.js +3 -3
- package/dist/tools/map.js.map +1 -1
- package/package.json +6 -2
- package/scripts/build-db.ts +20 -8
- package/scripts/check-updates.ts +141 -39
- package/scripts/ingest-eurlex.ts +9 -1
- package/scripts/ingest-unece.ts +368 -0
- package/src/http-server.ts +380 -0
- package/src/index.ts +4 -4
- package/src/tools/map.ts +4 -4
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP Server Entry Point for Smithery Hosted Deployment
|
|
5
|
+
*
|
|
6
|
+
* This provides Streamable HTTP transport for remote MCP clients.
|
|
7
|
+
* Use src/index.ts for local stdio-based usage.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createServer } from 'node:http';
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
+
import Database from 'better-sqlite3';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { dirname, join } from 'path';
|
|
20
|
+
import { randomUUID } from 'crypto';
|
|
21
|
+
|
|
22
|
+
import { searchRegulations, type SearchInput } from './tools/search.js';
|
|
23
|
+
import { getArticle, type GetArticleInput } from './tools/article.js';
|
|
24
|
+
import { listRegulations, type ListInput } from './tools/list.js';
|
|
25
|
+
import { compareRequirements, type CompareInput } from './tools/compare.js';
|
|
26
|
+
import { mapControls, type MapControlsInput } from './tools/map.js';
|
|
27
|
+
import { checkApplicability, type ApplicabilityInput } from './tools/applicability.js';
|
|
28
|
+
import { getDefinitions, type DefinitionsInput } from './tools/definitions.js';
|
|
29
|
+
|
|
30
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
31
|
+
const __dirname = dirname(__filename);
|
|
32
|
+
|
|
33
|
+
// Database path - look for regulations.db in data folder
|
|
34
|
+
const DB_PATH = process.env.EU_COMPLIANCE_DB_PATH || join(__dirname, '..', 'data', 'regulations.db');
|
|
35
|
+
|
|
36
|
+
// HTTP server port
|
|
37
|
+
const PORT = parseInt(process.env.PORT || '3000', 10);
|
|
38
|
+
|
|
39
|
+
let db: Database.Database;
|
|
40
|
+
|
|
41
|
+
function getDatabase(): Database.Database {
|
|
42
|
+
if (!db) {
|
|
43
|
+
try {
|
|
44
|
+
db = new Database(DB_PATH, { readonly: true });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return db;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create MCP server instance
|
|
53
|
+
function createMcpServer(): Server {
|
|
54
|
+
const server = new Server(
|
|
55
|
+
{
|
|
56
|
+
name: 'eu-regulations-mcp',
|
|
57
|
+
version: '0.1.0',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
capabilities: {
|
|
61
|
+
tools: {},
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Define available tools
|
|
67
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
68
|
+
tools: [
|
|
69
|
+
{
|
|
70
|
+
name: 'search_regulations',
|
|
71
|
+
description: 'Search across all EU regulations for articles matching a query. Returns relevant articles with snippets highlighting matches.',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
query: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'Search query (e.g., "incident reporting", "personal data breach")',
|
|
78
|
+
},
|
|
79
|
+
regulations: {
|
|
80
|
+
type: 'array',
|
|
81
|
+
items: { type: 'string' },
|
|
82
|
+
description: 'Optional: filter to specific regulations (e.g., ["GDPR", "NIS2"])',
|
|
83
|
+
},
|
|
84
|
+
limit: {
|
|
85
|
+
type: 'number',
|
|
86
|
+
description: 'Maximum results to return (default: 10)',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
required: ['query'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'get_article',
|
|
94
|
+
description: 'Retrieve the full text of a specific article from a regulation.',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
regulation: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Regulation ID (e.g., "GDPR", "NIS2", "DORA")',
|
|
101
|
+
},
|
|
102
|
+
article: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Article number (e.g., "17", "23")',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ['regulation', 'article'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'list_regulations',
|
|
112
|
+
description: 'List available regulations and their structure. Without parameters, lists all regulations. With a regulation specified, shows chapters and articles.',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
regulation: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Optional: specific regulation to get detailed structure for',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'compare_requirements',
|
|
125
|
+
description: 'Compare requirements across multiple regulations on a specific topic. Useful for understanding differences in how regulations address similar concerns.',
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {
|
|
129
|
+
topic: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
description: 'Topic to compare (e.g., "incident reporting", "risk assessment")',
|
|
132
|
+
},
|
|
133
|
+
regulations: {
|
|
134
|
+
type: 'array',
|
|
135
|
+
items: { type: 'string' },
|
|
136
|
+
description: 'Regulations to compare (e.g., ["DORA", "NIS2"])',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
required: ['topic', 'regulations'],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'map_controls',
|
|
144
|
+
description: 'Map security framework controls to EU regulation requirements. Shows which articles satisfy specific security controls.',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
framework: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
enum: ['ISO27001', 'NIST_CSF'],
|
|
151
|
+
description: 'Control framework: ISO27001 (ISO 27001:2022) or NIST_CSF (NIST Cybersecurity Framework)',
|
|
152
|
+
},
|
|
153
|
+
control: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
description: 'Optional: specific control ID (e.g., "A.5.1" for ISO27001, "PR.AC-1" for NIST CSF)',
|
|
156
|
+
},
|
|
157
|
+
regulation: {
|
|
158
|
+
type: 'string',
|
|
159
|
+
description: 'Optional: filter mappings to specific regulation',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
required: ['framework'],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'check_applicability',
|
|
167
|
+
description: 'Determine which EU regulations apply to an organization based on sector and characteristics.',
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
properties: {
|
|
171
|
+
sector: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
enum: ['financial', 'healthcare', 'energy', 'transport', 'digital_infrastructure', 'public_administration', 'manufacturing', 'other'],
|
|
174
|
+
description: 'Organization sector',
|
|
175
|
+
},
|
|
176
|
+
subsector: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
description: 'Optional: more specific subsector (e.g., "bank", "insurance" for financial)',
|
|
179
|
+
},
|
|
180
|
+
member_state: {
|
|
181
|
+
type: 'string',
|
|
182
|
+
description: 'Optional: EU member state (ISO country code)',
|
|
183
|
+
},
|
|
184
|
+
size: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
enum: ['sme', 'large'],
|
|
187
|
+
description: 'Optional: organization size',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['sector'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'get_definitions',
|
|
195
|
+
description: 'Look up official definitions of terms from EU regulations. Terms are defined in each regulation\'s definitions article.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
term: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'Term to look up (e.g., "personal data", "incident", "processing")',
|
|
202
|
+
},
|
|
203
|
+
regulation: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: 'Optional: filter to specific regulation',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ['term'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
// Handle tool calls
|
|
215
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
216
|
+
const { name, arguments: args } = request.params;
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const database = getDatabase();
|
|
220
|
+
|
|
221
|
+
switch (name) {
|
|
222
|
+
case 'search_regulations': {
|
|
223
|
+
const input = args as unknown as SearchInput;
|
|
224
|
+
const results = await searchRegulations(database, input);
|
|
225
|
+
return {
|
|
226
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'get_article': {
|
|
231
|
+
const input = args as unknown as GetArticleInput;
|
|
232
|
+
const article = await getArticle(database, input);
|
|
233
|
+
if (!article) {
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: 'text', text: `Article ${input.article} not found in ${input.regulation}` }],
|
|
236
|
+
isError: true,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
content: [{ type: 'text', text: JSON.stringify(article, null, 2) }],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case 'list_regulations': {
|
|
245
|
+
const input = (args ?? {}) as unknown as ListInput;
|
|
246
|
+
const result = await listRegulations(database, input);
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'compare_requirements': {
|
|
253
|
+
const input = args as unknown as CompareInput;
|
|
254
|
+
const result = await compareRequirements(database, input);
|
|
255
|
+
return {
|
|
256
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case 'map_controls': {
|
|
261
|
+
const input = args as unknown as MapControlsInput;
|
|
262
|
+
const result = await mapControls(database, input);
|
|
263
|
+
return {
|
|
264
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'check_applicability': {
|
|
269
|
+
const input = args as unknown as ApplicabilityInput;
|
|
270
|
+
const result = await checkApplicability(database, input);
|
|
271
|
+
return {
|
|
272
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case 'get_definitions': {
|
|
277
|
+
const input = args as unknown as DefinitionsInput;
|
|
278
|
+
const result = await getDefinitions(database, input);
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
default:
|
|
285
|
+
return {
|
|
286
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
287
|
+
isError: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
293
|
+
isError: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return server;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Start HTTP server with Streamable HTTP transport
|
|
302
|
+
async function main() {
|
|
303
|
+
const mcpServer = createMcpServer();
|
|
304
|
+
|
|
305
|
+
// Map to store transports by session ID
|
|
306
|
+
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
307
|
+
|
|
308
|
+
const httpServer = createServer(async (req, res) => {
|
|
309
|
+
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
310
|
+
|
|
311
|
+
// Health check endpoint
|
|
312
|
+
if (url.pathname === '/health') {
|
|
313
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
314
|
+
res.end(JSON.stringify({ status: 'ok', server: 'eu-regulations-mcp' }));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// MCP endpoint
|
|
319
|
+
if (url.pathname === '/mcp') {
|
|
320
|
+
// Get or create session
|
|
321
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
322
|
+
|
|
323
|
+
let transport: StreamableHTTPServerTransport;
|
|
324
|
+
|
|
325
|
+
if (sessionId && transports.has(sessionId)) {
|
|
326
|
+
// Reuse existing transport for this session
|
|
327
|
+
transport = transports.get(sessionId)!;
|
|
328
|
+
} else {
|
|
329
|
+
// Create new transport with session ID generator
|
|
330
|
+
transport = new StreamableHTTPServerTransport({
|
|
331
|
+
sessionIdGenerator: () => randomUUID(),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Connect MCP server to transport
|
|
335
|
+
await mcpServer.connect(transport);
|
|
336
|
+
|
|
337
|
+
// Store transport by session ID once it's assigned
|
|
338
|
+
transport.onclose = () => {
|
|
339
|
+
if (transport.sessionId) {
|
|
340
|
+
transports.delete(transport.sessionId);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Handle the request
|
|
346
|
+
await transport.handleRequest(req, res);
|
|
347
|
+
|
|
348
|
+
// Store transport if new session was created
|
|
349
|
+
if (transport.sessionId && !transports.has(transport.sessionId)) {
|
|
350
|
+
transports.set(transport.sessionId, transport);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 404 for other paths
|
|
357
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
358
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
httpServer.listen(PORT, () => {
|
|
362
|
+
console.error(`EU Regulations MCP server (HTTP) listening on port ${PORT}`);
|
|
363
|
+
console.error(`MCP endpoint: http://localhost:${PORT}/mcp`);
|
|
364
|
+
console.error(`Health check: http://localhost:${PORT}/health`);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Graceful shutdown
|
|
368
|
+
process.on('SIGTERM', () => {
|
|
369
|
+
console.error('Received SIGTERM, shutting down...');
|
|
370
|
+
httpServer.close(() => {
|
|
371
|
+
if (db) db.close();
|
|
372
|
+
process.exit(0);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
main().catch((error) => {
|
|
378
|
+
console.error('Fatal error:', error);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -127,18 +127,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
name: 'map_controls',
|
|
130
|
-
description: 'Map
|
|
130
|
+
description: 'Map security framework controls to EU regulation requirements. Shows which articles satisfy specific security controls.',
|
|
131
131
|
inputSchema: {
|
|
132
132
|
type: 'object',
|
|
133
133
|
properties: {
|
|
134
134
|
framework: {
|
|
135
135
|
type: 'string',
|
|
136
|
-
enum: ['ISO27001'],
|
|
137
|
-
description: 'Control framework (
|
|
136
|
+
enum: ['ISO27001', 'NIST_CSF'],
|
|
137
|
+
description: 'Control framework: ISO27001 (ISO 27001:2022) or NIST_CSF (NIST Cybersecurity Framework)',
|
|
138
138
|
},
|
|
139
139
|
control: {
|
|
140
140
|
type: 'string',
|
|
141
|
-
description: 'Optional: specific control ID (e.g., "A.5.1", "
|
|
141
|
+
description: 'Optional: specific control ID (e.g., "A.5.1" for ISO27001, "PR.AC-1" for NIST CSF)',
|
|
142
142
|
},
|
|
143
143
|
regulation: {
|
|
144
144
|
type: 'string',
|
package/src/tools/map.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Database } from 'better-sqlite3';
|
|
2
2
|
|
|
3
3
|
export interface MapControlsInput {
|
|
4
|
-
framework: 'ISO27001';
|
|
4
|
+
framework: 'ISO27001' | 'NIST_CSF';
|
|
5
5
|
control?: string;
|
|
6
6
|
regulation?: string;
|
|
7
7
|
}
|
|
@@ -23,7 +23,7 @@ export async function mapControls(
|
|
|
23
23
|
db: Database,
|
|
24
24
|
input: MapControlsInput
|
|
25
25
|
): Promise<ControlMapping[]> {
|
|
26
|
-
const { control, regulation } = input;
|
|
26
|
+
const { framework, control, regulation } = input;
|
|
27
27
|
|
|
28
28
|
let sql = `
|
|
29
29
|
SELECT
|
|
@@ -34,10 +34,10 @@ export async function mapControls(
|
|
|
34
34
|
coverage,
|
|
35
35
|
notes
|
|
36
36
|
FROM control_mappings
|
|
37
|
-
WHERE
|
|
37
|
+
WHERE framework = ?
|
|
38
38
|
`;
|
|
39
39
|
|
|
40
|
-
const params: string[] = [];
|
|
40
|
+
const params: string[] = [framework];
|
|
41
41
|
|
|
42
42
|
if (control) {
|
|
43
43
|
sql += ` AND control_id = ?`;
|