@ansvar/eu-regulations-mcp 1.0.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.
- package/README.md +60 -22
- package/data/regulations.db +0 -0
- package/dist/database/sqlite-adapter.d.ts +2 -2
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.js.map +1 -1
- package/dist/http-server.js +27 -5
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/about.d.ts +40 -0
- package/dist/tools/about.d.ts.map +1 -0
- package/dist/tools/about.js +61 -0
- package/dist/tools/about.js.map +1 -0
- package/dist/tools/list.d.ts +7 -0
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +73 -8
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/registry.d.ts +11 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +56 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -5
- package/dist/worker.js.map +1 -1
- package/package.json +6 -5
- package/scripts/add-cross-references.sql +0 -200
- package/scripts/analyze-survey-responses.ts +0 -285
- package/scripts/build-db.ts +0 -421
- package/scripts/bulk-reingest-all.ts +0 -331
- package/scripts/check-updates.ts +0 -294
- package/scripts/extract-eprivacy-recitals.ts +0 -98
- package/scripts/ingest-eurlex-browser.ts +0 -113
- package/scripts/ingest-eurlex.ts +0 -349
- package/scripts/ingest-unece.ts +0 -382
- package/scripts/migrate-postgres.ts +0 -445
- package/scripts/migrate-to-postgres.ts +0 -353
- package/scripts/reingest-all-with-recitals.sh +0 -81
- package/scripts/sync-versions.ts +0 -206
- package/scripts/test-cross-refs.js +0 -26
- package/scripts/test-postgres-adapter.ts +0 -146
- package/scripts/update-dora-rts-metadata.ts +0 -112
- package/src/database/postgres-adapter.ts +0 -84
- package/src/database/sqlite-adapter.ts +0 -44
- package/src/database/types.ts +0 -10
- package/src/http-server.ts +0 -149
- package/src/index.ts +0 -61
- package/src/middleware/rate-limit.ts +0 -104
- package/src/tools/applicability.ts +0 -167
- package/src/tools/article.ts +0 -81
- package/src/tools/compare.ts +0 -217
- package/src/tools/definitions.ts +0 -49
- package/src/tools/evidence.ts +0 -84
- package/src/tools/list.ts +0 -124
- package/src/tools/map.ts +0 -86
- package/src/tools/recital.ts +0 -60
- package/src/tools/registry.ts +0 -311
- package/src/tools/search.ts +0 -297
- package/src/worker.ts +0 -708
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test PostgreSQL adapter with migrated data
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* DATABASE_URL=postgresql://... npx tsx scripts/test-postgres-adapter.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { PostgreSQLAdapter } from '../packages/core/src/adapters/postgresql-adapter.js';
|
|
11
|
-
|
|
12
|
-
async function main() {
|
|
13
|
-
console.log('๐งช Testing PostgreSQL Adapter\n');
|
|
14
|
-
|
|
15
|
-
const connectionString = process.env.DATABASE_URL;
|
|
16
|
-
if (!connectionString) {
|
|
17
|
-
console.error('โ DATABASE_URL environment variable is required');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
console.log(`๐ Connecting to: ${connectionString.replace(/:[^:@]+@/, ':***@')}\n`);
|
|
22
|
-
|
|
23
|
-
const adapter = new PostgreSQLAdapter(connectionString);
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
// Test 1: Connection
|
|
27
|
-
console.log('Test 1: Connection');
|
|
28
|
-
const connected = await adapter.testConnection();
|
|
29
|
-
console.log(connected ? 'โ
Connected\n' : 'โ Connection failed\n');
|
|
30
|
-
if (!connected) process.exit(1);
|
|
31
|
-
|
|
32
|
-
// Test 2: Get all regulations
|
|
33
|
-
console.log('Test 2: Get all regulations');
|
|
34
|
-
const regulations = await adapter.getAllRegulations();
|
|
35
|
-
console.log(`โ
Found ${regulations.length} regulations`);
|
|
36
|
-
console.log(` First 3: ${regulations.slice(0, 3).map(r => r.id).join(', ')}\n`);
|
|
37
|
-
|
|
38
|
-
// Test 3: Get specific regulation
|
|
39
|
-
console.log('Test 3: Get specific regulation (GDPR)');
|
|
40
|
-
const gdpr = await adapter.getRegulationById('GDPR');
|
|
41
|
-
if (gdpr) {
|
|
42
|
-
console.log(`โ
${gdpr.full_name}`);
|
|
43
|
-
console.log(` CELEX: ${gdpr.celex_id}\n`);
|
|
44
|
-
} else {
|
|
45
|
-
console.log('โ GDPR not found\n');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Test 4: Search articles
|
|
50
|
-
console.log('Test 4: Search articles for "incident reporting"');
|
|
51
|
-
const searchResults = await adapter.searchArticles('incident reporting', undefined, 5);
|
|
52
|
-
console.log(`โ
Found ${searchResults.length} results`);
|
|
53
|
-
searchResults.forEach((result, i) => {
|
|
54
|
-
console.log(` ${i + 1}. ${result.item.regulation} Article ${result.item.article_number}: ${result.item.title}`);
|
|
55
|
-
console.log(` Rank: ${result.rank.toFixed(4)}`);
|
|
56
|
-
});
|
|
57
|
-
console.log();
|
|
58
|
-
|
|
59
|
-
// Test 5: Get specific article
|
|
60
|
-
console.log('Test 5: Get GDPR Article 17');
|
|
61
|
-
const article = await adapter.getArticle('GDPR', '17');
|
|
62
|
-
if (article) {
|
|
63
|
-
console.log(`โ
${article.title}`);
|
|
64
|
-
console.log(` Text length: ${article.text.length} characters`);
|
|
65
|
-
if (article.recitals) {
|
|
66
|
-
console.log(` Related recitals: ${article.recitals}\n`);
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
console.log('โ Article not found\n');
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Test 6: Get recital
|
|
74
|
-
console.log('Test 6: Get GDPR Recital 1');
|
|
75
|
-
const recital = await adapter.getRecital('GDPR', 1);
|
|
76
|
-
if (recital) {
|
|
77
|
-
console.log(`โ
Text: ${recital.text.substring(0, 100)}...`);
|
|
78
|
-
console.log(` Full length: ${recital.text.length} characters\n`);
|
|
79
|
-
} else {
|
|
80
|
-
console.log('โ Recital not found\n');
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Test 7: Search recitals
|
|
85
|
-
console.log('Test 7: Search recitals for "data protection"');
|
|
86
|
-
const recitalResults = await adapter.searchRecitals('data protection', 3);
|
|
87
|
-
console.log(`โ
Found ${recitalResults.length} recital results`);
|
|
88
|
-
recitalResults.forEach((result, i) => {
|
|
89
|
-
console.log(` ${i + 1}. ${result.item.regulation} Recital ${result.item.recital_number}`);
|
|
90
|
-
console.log(` Rank: ${result.rank.toFixed(4)}`);
|
|
91
|
-
});
|
|
92
|
-
console.log();
|
|
93
|
-
|
|
94
|
-
// Test 8: Get definitions
|
|
95
|
-
console.log('Test 8: Search definitions for "personal data"');
|
|
96
|
-
const definitions = await adapter.getDefinitions('personal data');
|
|
97
|
-
console.log(`โ
Found ${definitions.length} definitions`);
|
|
98
|
-
definitions.slice(0, 3).forEach((def, i) => {
|
|
99
|
-
console.log(` ${i + 1}. ${def.regulation}: ${def.term}`);
|
|
100
|
-
console.log(` ${def.definition.substring(0, 80)}...`);
|
|
101
|
-
});
|
|
102
|
-
console.log();
|
|
103
|
-
|
|
104
|
-
// Test 9: Get control mappings
|
|
105
|
-
console.log('Test 9: Get ISO 27001 control mappings');
|
|
106
|
-
const mappings = await adapter.getControlMappings('ISO27001', undefined, undefined);
|
|
107
|
-
console.log(`โ
Found ${mappings.length} ISO 27001 mappings`);
|
|
108
|
-
const sample = mappings.slice(0, 2);
|
|
109
|
-
sample.forEach((map, i) => {
|
|
110
|
-
console.log(` ${i + 1}. ${map.control_id}: ${map.control_name}`);
|
|
111
|
-
console.log(` โ ${map.regulation} (${map.coverage})`);
|
|
112
|
-
});
|
|
113
|
-
console.log();
|
|
114
|
-
|
|
115
|
-
// Test 10: Get applicability rules
|
|
116
|
-
console.log('Test 10: Get applicability rules for financial sector');
|
|
117
|
-
const rules = await adapter.getApplicabilityRules('financial');
|
|
118
|
-
console.log(`โ
Found ${rules.length} applicability rules`);
|
|
119
|
-
rules.slice(0, 3).forEach((rule, i) => {
|
|
120
|
-
console.log(` ${i + 1}. ${rule.regulation}: ${rule.applies ? 'Applies' : 'Does not apply'}`);
|
|
121
|
-
console.log(` Confidence: ${rule.confidence}`);
|
|
122
|
-
});
|
|
123
|
-
console.log();
|
|
124
|
-
|
|
125
|
-
// Test 11: Statistics
|
|
126
|
-
console.log('Test 11: Get database statistics');
|
|
127
|
-
const stats = await adapter.getStatistics();
|
|
128
|
-
console.log('โ
Statistics:');
|
|
129
|
-
console.log(` Regulations: ${stats.regulations}`);
|
|
130
|
-
console.log(` Articles: ${stats.articles}`);
|
|
131
|
-
console.log(` Recitals: ${stats.recitals}`);
|
|
132
|
-
console.log(` Definitions: ${stats.definitions}`);
|
|
133
|
-
console.log(` Control Mappings: ${stats.control_mappings}\n`);
|
|
134
|
-
|
|
135
|
-
console.log('='.repeat(60));
|
|
136
|
-
console.log('โ
All tests passed!');
|
|
137
|
-
console.log('='.repeat(60));
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error('\nโ Test failed:', error);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
} finally {
|
|
142
|
-
await adapter.close();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
main();
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Update metadata for DORA RTS/ITS JSON files
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
|
|
8
|
-
interface RegulationMetadata {
|
|
9
|
-
id: string;
|
|
10
|
-
full_name: string;
|
|
11
|
-
effective_date: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const DORA_RTS_ITS_METADATA: Record<string, RegulationMetadata> = {
|
|
15
|
-
'32024R1774': {
|
|
16
|
-
id: 'DORA_RTS_ICT_RISK',
|
|
17
|
-
full_name: 'Commission Delegated Regulation (EU) 2024/1774 - ICT Risk Management Tools, Methods, Processes and Simplified Framework',
|
|
18
|
-
effective_date: '2025-01-17',
|
|
19
|
-
},
|
|
20
|
-
'32024R1772': {
|
|
21
|
-
id: 'DORA_RTS_INCIDENT_CLASS',
|
|
22
|
-
full_name: 'Commission Delegated Regulation (EU) 2024/1772 - Classification of ICT-Related Incidents and Cyber Threats',
|
|
23
|
-
effective_date: '2025-01-17',
|
|
24
|
-
},
|
|
25
|
-
'32024R1773': {
|
|
26
|
-
id: 'DORA_RTS_ICT_SERVICES',
|
|
27
|
-
full_name: 'Commission Delegated Regulation (EU) 2024/1773 - Policy on ICT Services Supporting Critical or Important Functions',
|
|
28
|
-
effective_date: '2025-01-17',
|
|
29
|
-
},
|
|
30
|
-
'32024R2956': {
|
|
31
|
-
id: 'DORA_ITS_REGISTER',
|
|
32
|
-
full_name: 'Commission Implementing Regulation (EU) 2024/2956 - Standard Templates for Register of Information',
|
|
33
|
-
effective_date: '2025-01-17',
|
|
34
|
-
},
|
|
35
|
-
'32024R1502': {
|
|
36
|
-
id: 'DORA_RTS_CRITICAL_PROVIDER',
|
|
37
|
-
full_name: 'Commission Delegated Regulation (EU) 2024/1502 - Criteria for Designation of Critical ICT Third-Party Service Providers',
|
|
38
|
-
effective_date: '2025-01-17',
|
|
39
|
-
},
|
|
40
|
-
'32024R1505': {
|
|
41
|
-
id: 'DORA_RTS_OVERSIGHT_FEES',
|
|
42
|
-
full_name: 'Commission Delegated Regulation (EU) 2024/1505 - Oversight Fees for Critical ICT Third-Party Service Providers',
|
|
43
|
-
effective_date: '2025-01-17',
|
|
44
|
-
},
|
|
45
|
-
'32025R0295': {
|
|
46
|
-
id: 'DORA_RTS_OVERSIGHT',
|
|
47
|
-
full_name: 'Commission Delegated Regulation (EU) 2025/295 - Harmonization of Oversight Activities Conditions',
|
|
48
|
-
effective_date: '2025-01-17',
|
|
49
|
-
},
|
|
50
|
-
'32025R0301': {
|
|
51
|
-
id: 'DORA_RTS_INCIDENT_REPORTING',
|
|
52
|
-
full_name: 'Commission Delegated Regulation (EU) 2025/301 - Content and Time Limits for Incident Reporting',
|
|
53
|
-
effective_date: '2025-01-17',
|
|
54
|
-
},
|
|
55
|
-
'32025R0302': {
|
|
56
|
-
id: 'DORA_ITS_INCIDENT_FORMS',
|
|
57
|
-
full_name: 'Commission Implementing Regulation (EU) 2025/302 - Standard Forms and Templates for Incident Reporting',
|
|
58
|
-
effective_date: '2025-01-17',
|
|
59
|
-
},
|
|
60
|
-
'32025R1190': {
|
|
61
|
-
id: 'DORA_RTS_TLPT',
|
|
62
|
-
full_name: 'Commission Delegated Regulation (EU) 2025/1190 - Threat-Led Penetration Testing (TLPT)',
|
|
63
|
-
effective_date: '2025-01-17',
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const FILE_MAPPING: Record<string, string> = {
|
|
68
|
-
'32024R1774': 'data/seed/dora-rts-ict-risk.json',
|
|
69
|
-
'32024R1772': 'data/seed/dora-rts-incident-classification.json',
|
|
70
|
-
'32024R1773': 'data/seed/dora-rts-ict-services-policy.json',
|
|
71
|
-
'32024R2956': 'data/seed/dora-its-register-templates.json',
|
|
72
|
-
'32024R1502': 'data/seed/dora-rts-critical-provider-designation.json',
|
|
73
|
-
'32024R1505': 'data/seed/dora-rts-oversight-fees.json',
|
|
74
|
-
'32025R0295': 'data/seed/dora-rts-oversight-harmonization.json',
|
|
75
|
-
'32025R0301': 'data/seed/dora-rts-incident-reporting.json',
|
|
76
|
-
'32025R0302': 'data/seed/dora-its-incident-forms.json',
|
|
77
|
-
'32025R1190': 'data/seed/dora-rts-tlpt.json',
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
function updateRegulationMetadata(celex: string, filePath: string): void {
|
|
81
|
-
const metadata = DORA_RTS_ITS_METADATA[celex];
|
|
82
|
-
if (!metadata) {
|
|
83
|
-
console.error(`No metadata found for CELEX ${celex}`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const fullPath = path.resolve(process.cwd(), filePath);
|
|
88
|
-
if (!fs.existsSync(fullPath)) {
|
|
89
|
-
console.error(`File not found: ${fullPath}`);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const data = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
94
|
-
|
|
95
|
-
// Update metadata
|
|
96
|
-
data.id = metadata.id;
|
|
97
|
-
data.full_name = metadata.full_name;
|
|
98
|
-
data.effective_date = metadata.effective_date;
|
|
99
|
-
|
|
100
|
-
// Write back
|
|
101
|
-
fs.writeFileSync(fullPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
102
|
-
console.log(`โ
Updated ${celex} โ ${metadata.id} (${filePath})`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Main
|
|
106
|
-
console.log('Updating DORA RTS/ITS metadata...\n');
|
|
107
|
-
|
|
108
|
-
for (const [celex, filePath] of Object.entries(FILE_MAPPING)) {
|
|
109
|
-
updateRegulationMetadata(celex, filePath);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log('\nโ
All metadata updated successfully!');
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import pg from 'pg';
|
|
2
|
-
import type { DatabaseAdapter, QueryResult } from './types.js';
|
|
3
|
-
|
|
4
|
-
export async function createPostgresAdapter(
|
|
5
|
-
connectionString: string
|
|
6
|
-
): Promise<DatabaseAdapter> {
|
|
7
|
-
const pool = new pg.Pool({
|
|
8
|
-
connectionString,
|
|
9
|
-
max: 10,
|
|
10
|
-
idleTimeoutMillis: 30000,
|
|
11
|
-
connectionTimeoutMillis: 10000,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Test connection
|
|
15
|
-
try {
|
|
16
|
-
await pool.query('SELECT 1');
|
|
17
|
-
} catch (error) {
|
|
18
|
-
throw new Error(`Failed to connect to PostgreSQL: ${error}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
type: 'postgres' as const,
|
|
23
|
-
|
|
24
|
-
async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
|
|
25
|
-
const QUERY_TIMEOUT_MS = 10000; // 10 seconds
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Create timeout promise
|
|
29
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
30
|
-
setTimeout(() => {
|
|
31
|
-
reject(new Error('Database query timeout: Query exceeded 10 seconds'));
|
|
32
|
-
}, QUERY_TIMEOUT_MS);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Race between query and timeout
|
|
36
|
-
const result = await Promise.race([
|
|
37
|
-
pool.query(sql, params),
|
|
38
|
-
timeoutPromise,
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
rows: result.rows as T[],
|
|
43
|
-
rowCount: result.rowCount || 0,
|
|
44
|
-
};
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const pgError = error as {
|
|
47
|
-
code?: string;
|
|
48
|
-
message: string;
|
|
49
|
-
detail?: string;
|
|
50
|
-
};
|
|
51
|
-
console.error('PostgreSQL query failed:', {
|
|
52
|
-
sql: sql.substring(0, 100), // Truncate for logging
|
|
53
|
-
params,
|
|
54
|
-
code: pgError.code,
|
|
55
|
-
message: pgError.message,
|
|
56
|
-
detail: pgError.detail,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Check for timeout error
|
|
60
|
-
if (pgError.message?.includes('timeout')) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
'Database query timeout: Query exceeded 10 seconds'
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Provide helpful error messages based on error code
|
|
67
|
-
if (pgError.code?.startsWith('08')) {
|
|
68
|
-
throw new Error(`Database connection error: ${pgError.message}`);
|
|
69
|
-
}
|
|
70
|
-
if (pgError.code === '42P01') {
|
|
71
|
-
throw new Error(`Table not found: ${pgError.message}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Query failed: ${pgError.message}${pgError.code ? ` (${pgError.code})` : ''}`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
async close(): Promise<void> {
|
|
81
|
-
await pool.end();
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { DatabaseAdapter, QueryResult } from './types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Adapter that wraps better-sqlite3 Database to match DatabaseAdapter interface.
|
|
6
|
-
* Allows existing SQLite-based entry points (stdio) to work with the new adapter interface.
|
|
7
|
-
*/
|
|
8
|
-
export function createSqliteAdapter(db: Database.Database): DatabaseAdapter {
|
|
9
|
-
return {
|
|
10
|
-
type: 'sqlite' as const,
|
|
11
|
-
|
|
12
|
-
async query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
|
|
13
|
-
try {
|
|
14
|
-
// Convert PostgreSQL syntax to SQLite syntax
|
|
15
|
-
let sqliteSql = sql
|
|
16
|
-
// Convert $1, $2 placeholders to ?
|
|
17
|
-
.replace(/\$\d+/g, '?')
|
|
18
|
-
// Convert ILIKE to LIKE (SQLite is case-insensitive by default with LIKE)
|
|
19
|
-
.replace(/\sILIKE\s/gi, ' LIKE ')
|
|
20
|
-
// Convert ::TEXT type casting to CAST(... AS TEXT)
|
|
21
|
-
.replace(/(\w+)::TEXT/g, 'CAST($1 AS TEXT)')
|
|
22
|
-
.replace(/(\w+)::INTEGER/g, 'CAST($1 AS INTEGER)')
|
|
23
|
-
// Convert DISTINCT ON (col) to GROUP BY col
|
|
24
|
-
// PostgreSQL: SELECT DISTINCT ON (col1) col1, col2 FROM ... ORDER BY col1, col2
|
|
25
|
-
// SQLite: SELECT col1, col2 FROM ... GROUP BY col1 HAVING MIN(col2) = col2
|
|
26
|
-
.replace(/SELECT\s+DISTINCT\s+ON\s*\([^)]+\)/gi, 'SELECT');
|
|
27
|
-
|
|
28
|
-
const stmt = db.prepare(sqliteSql);
|
|
29
|
-
const rows = params ? stmt.all(...params) : stmt.all();
|
|
30
|
-
return {
|
|
31
|
-
rows: rows as T[],
|
|
32
|
-
rowCount: rows.length,
|
|
33
|
-
};
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.error('SQLite query failed:', sql, params, error);
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
async close(): Promise<void> {
|
|
41
|
-
db.close();
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
package/src/database/types.ts
DELETED
package/src/http-server.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
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 { registerTools } from './tools/registry.js';
|
|
23
|
-
import { createSqliteAdapter } from './database/sqlite-adapter.js';
|
|
24
|
-
import type { DatabaseAdapter } from './database/types.js';
|
|
25
|
-
|
|
26
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
-
const __dirname = dirname(__filename);
|
|
28
|
-
|
|
29
|
-
// Database path - look for regulations.db in data folder
|
|
30
|
-
const DB_PATH = process.env.EU_COMPLIANCE_DB_PATH || join(__dirname, '..', 'data', 'regulations.db');
|
|
31
|
-
|
|
32
|
-
// HTTP server port
|
|
33
|
-
const PORT = parseInt(process.env.PORT || '3000', 10);
|
|
34
|
-
|
|
35
|
-
let db: DatabaseAdapter;
|
|
36
|
-
|
|
37
|
-
function getDatabase(): DatabaseAdapter {
|
|
38
|
-
if (!db) {
|
|
39
|
-
try {
|
|
40
|
-
const sqliteDb = new Database(DB_PATH, { readonly: true });
|
|
41
|
-
db = createSqliteAdapter(sqliteDb);
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return db;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Create MCP server instance
|
|
50
|
-
function createMcpServer(): Server {
|
|
51
|
-
const db = getDatabase();
|
|
52
|
-
const server = new Server(
|
|
53
|
-
{
|
|
54
|
-
name: 'eu-regulations-mcp',
|
|
55
|
-
version: '0.1.0',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
capabilities: {
|
|
59
|
-
tools: {},
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Register all tools using shared registry
|
|
65
|
-
registerTools(server, db);
|
|
66
|
-
|
|
67
|
-
return server;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Start HTTP server with Streamable HTTP transport
|
|
71
|
-
async function main() {
|
|
72
|
-
const mcpServer = createMcpServer();
|
|
73
|
-
|
|
74
|
-
// Map to store transports by session ID
|
|
75
|
-
const transports = new Map<string, StreamableHTTPServerTransport>();
|
|
76
|
-
|
|
77
|
-
const httpServer = createServer(async (req, res) => {
|
|
78
|
-
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
79
|
-
|
|
80
|
-
// Health check endpoint
|
|
81
|
-
if (url.pathname === '/health') {
|
|
82
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
83
|
-
res.end(JSON.stringify({ status: 'ok', server: 'eu-regulations-mcp' }));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// MCP endpoint
|
|
88
|
-
if (url.pathname === '/mcp') {
|
|
89
|
-
// Get or create session
|
|
90
|
-
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
91
|
-
|
|
92
|
-
let transport: StreamableHTTPServerTransport;
|
|
93
|
-
|
|
94
|
-
if (sessionId && transports.has(sessionId)) {
|
|
95
|
-
// Reuse existing transport for this session
|
|
96
|
-
transport = transports.get(sessionId)!;
|
|
97
|
-
} else {
|
|
98
|
-
// Create new transport with session ID generator
|
|
99
|
-
transport = new StreamableHTTPServerTransport({
|
|
100
|
-
sessionIdGenerator: () => randomUUID(),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Connect MCP server to transport
|
|
104
|
-
await mcpServer.connect(transport);
|
|
105
|
-
|
|
106
|
-
// Store transport by session ID once it's assigned
|
|
107
|
-
transport.onclose = () => {
|
|
108
|
-
if (transport.sessionId) {
|
|
109
|
-
transports.delete(transport.sessionId);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Handle the request
|
|
115
|
-
await transport.handleRequest(req, res);
|
|
116
|
-
|
|
117
|
-
// Store transport if new session was created
|
|
118
|
-
if (transport.sessionId && !transports.has(transport.sessionId)) {
|
|
119
|
-
transports.set(transport.sessionId, transport);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 404 for other paths
|
|
126
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
127
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
httpServer.listen(PORT, () => {
|
|
131
|
-
console.error(`EU Regulations MCP server (HTTP) listening on port ${PORT}`);
|
|
132
|
-
console.error(`MCP endpoint: http://localhost:${PORT}/mcp`);
|
|
133
|
-
console.error(`Health check: http://localhost:${PORT}/health`);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Graceful shutdown
|
|
137
|
-
process.on('SIGTERM', () => {
|
|
138
|
-
console.error('Received SIGTERM, shutting down...');
|
|
139
|
-
httpServer.close(() => {
|
|
140
|
-
if (db) db.close();
|
|
141
|
-
process.exit(0);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
main().catch((error) => {
|
|
147
|
-
console.error('Fatal error:', error);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import Database from 'better-sqlite3';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import { dirname, join } from 'path';
|
|
12
|
-
|
|
13
|
-
import { registerTools } from './tools/registry.js';
|
|
14
|
-
import { createSqliteAdapter } from './database/sqlite-adapter.js';
|
|
15
|
-
import type { DatabaseAdapter } from './database/types.js';
|
|
16
|
-
|
|
17
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
-
const __dirname = dirname(__filename);
|
|
19
|
-
|
|
20
|
-
// Database path - look for regulations.db in data folder
|
|
21
|
-
const DB_PATH = process.env.EU_COMPLIANCE_DB_PATH || join(__dirname, '..', 'data', 'regulations.db');
|
|
22
|
-
|
|
23
|
-
let db: DatabaseAdapter;
|
|
24
|
-
|
|
25
|
-
function getDatabase(): DatabaseAdapter {
|
|
26
|
-
if (!db) {
|
|
27
|
-
try {
|
|
28
|
-
const sqliteDb = new Database(DB_PATH, { readonly: true });
|
|
29
|
-
db = createSqliteAdapter(sqliteDb);
|
|
30
|
-
} catch (error) {
|
|
31
|
-
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return db;
|
|
35
|
-
}
|
|
36
|
-
const server = new Server(
|
|
37
|
-
{
|
|
38
|
-
name: 'eu-regulations-mcp',
|
|
39
|
-
version: '0.1.0',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
capabilities: {
|
|
43
|
-
tools: {},
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Register all tools using shared registry
|
|
49
|
-
registerTools(server, getDatabase());
|
|
50
|
-
|
|
51
|
-
// Start the server
|
|
52
|
-
async function main() {
|
|
53
|
-
const transport = new StdioServerTransport();
|
|
54
|
-
await server.connect(transport);
|
|
55
|
-
console.error('EU Regulations MCP server started');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
main().catch((error) => {
|
|
59
|
-
console.error('Fatal error:', error);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
});
|