@ansvar/us-regulations-mcp 1.0.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 -0
- package/README.md +275 -0
- package/data/.gitkeep +0 -0
- package/data/regulations.db +0 -0
- package/data/seed/applicability/rules.json +74 -0
- package/data/seed/mappings/ccpa-nist-csf.json +144 -0
- package/data/seed/mappings/hipaa-nist-800-53.json +377 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/adapters/california-leginfo.d.ts +72 -0
- package/dist/ingest/adapters/california-leginfo.d.ts.map +1 -0
- package/dist/ingest/adapters/california-leginfo.js +270 -0
- package/dist/ingest/adapters/california-leginfo.js.map +1 -0
- package/dist/ingest/adapters/ecfr.d.ts +76 -0
- package/dist/ingest/adapters/ecfr.d.ts.map +1 -0
- package/dist/ingest/adapters/ecfr.js +355 -0
- package/dist/ingest/adapters/ecfr.js.map +1 -0
- package/dist/ingest/adapters/regulations-gov.d.ts +47 -0
- package/dist/ingest/adapters/regulations-gov.d.ts.map +1 -0
- package/dist/ingest/adapters/regulations-gov.js +91 -0
- package/dist/ingest/adapters/regulations-gov.js.map +1 -0
- package/dist/ingest/framework.d.ts +84 -0
- package/dist/ingest/framework.d.ts.map +1 -0
- package/dist/ingest/framework.js +8 -0
- package/dist/ingest/framework.js.map +1 -0
- package/dist/tools/action-items.d.ts +23 -0
- package/dist/tools/action-items.d.ts.map +1 -0
- package/dist/tools/action-items.js +118 -0
- package/dist/tools/action-items.js.map +1 -0
- package/dist/tools/applicability.d.ts +26 -0
- package/dist/tools/applicability.d.ts.map +1 -0
- package/dist/tools/applicability.js +49 -0
- package/dist/tools/applicability.js.map +1 -0
- package/dist/tools/compare.d.ts +20 -0
- package/dist/tools/compare.d.ts.map +1 -0
- package/dist/tools/compare.js +35 -0
- package/dist/tools/compare.js.map +1 -0
- package/dist/tools/definitions.d.ts +22 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +43 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/evidence.d.ts +23 -0
- package/dist/tools/evidence.d.ts.map +1 -0
- package/dist/tools/evidence.js +27 -0
- package/dist/tools/evidence.js.map +1 -0
- package/dist/tools/list.d.ts +25 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +66 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/map.d.ts +26 -0
- package/dist/tools/map.d.ts.map +1 -0
- package/dist/tools/map.js +58 -0
- package/dist/tools/map.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +260 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/search.d.ts +15 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +94 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/section.d.ts +19 -0
- package/dist/tools/section.d.ts.map +1 -0
- package/dist/tools/section.js +50 -0
- package/dist/tools/section.js.map +1 -0
- package/package.json +76 -0
- package/scripts/build-db.ts +268 -0
- package/scripts/ingest.ts +214 -0
- package/scripts/load-seed-data.ts +133 -0
- package/scripts/quality-test.ts +346 -0
- package/scripts/test-mcp-tools.ts +187 -0
- package/scripts/test-remaining-tools.ts +107 -0
- package/src/index.ts +55 -0
- package/src/ingest/adapters/california-leginfo.ts +322 -0
- package/src/ingest/adapters/ecfr.ts +403 -0
- package/src/ingest/adapters/regulations-gov.ts +112 -0
- package/src/ingest/framework.ts +92 -0
- package/src/tools/action-items.ts +164 -0
- package/src/tools/applicability.ts +91 -0
- package/src/tools/compare.ts +61 -0
- package/src/tools/definitions.ts +79 -0
- package/src/tools/evidence.ts +53 -0
- package/src/tools/list.ts +120 -0
- package/src/tools/map.ts +100 -0
- package/src/tools/registry.ts +275 -0
- package/src/tools/search.ts +132 -0
- package/src/tools/section.ts +85 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive Quality Test for US Compliance MCP
|
|
5
|
+
* Tests various compliance queries to assess quality and coverage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { searchRegulations } from '../src/tools/search.js';
|
|
12
|
+
import { getSection } from '../src/tools/section.js';
|
|
13
|
+
import { compareRequirements } from '../src/tools/compare.js';
|
|
14
|
+
import { mapControls } from '../src/tools/map.js';
|
|
15
|
+
import { checkApplicability } from '../src/tools/applicability.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
const DB_PATH = join(__dirname, '..', 'data', 'regulations.db');
|
|
20
|
+
|
|
21
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
22
|
+
|
|
23
|
+
interface TestCase {
|
|
24
|
+
name: string;
|
|
25
|
+
category: string;
|
|
26
|
+
test: () => Promise<any>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tests: TestCase[] = [];
|
|
30
|
+
let passed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
|
|
33
|
+
function addTest(category: string, name: string, test: () => Promise<any>) {
|
|
34
|
+
tests.push({ category, name, test });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ===== HIPAA QUERIES =====
|
|
38
|
+
addTest('HIPAA', 'Security rule access controls', async () => {
|
|
39
|
+
const result = await searchRegulations(db, {
|
|
40
|
+
query: 'access control security',
|
|
41
|
+
regulation: 'HIPAA',
|
|
42
|
+
limit: 5
|
|
43
|
+
});
|
|
44
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
45
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
46
|
+
console.log(` ๐ Top result: ${result[0].section} - ${result[0].title}`);
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
addTest('HIPAA', 'Audit logs and ePHI access', async () => {
|
|
51
|
+
const result = await searchRegulations(db, {
|
|
52
|
+
query: 'audit log electronic protected health',
|
|
53
|
+
regulation: 'HIPAA',
|
|
54
|
+
limit: 5
|
|
55
|
+
});
|
|
56
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
57
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
58
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
59
|
+
return result;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
addTest('HIPAA', 'Encryption requirements', async () => {
|
|
63
|
+
const result = await searchRegulations(db, {
|
|
64
|
+
query: 'encryption transmission storage',
|
|
65
|
+
regulation: 'HIPAA',
|
|
66
|
+
limit: 5
|
|
67
|
+
});
|
|
68
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
69
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
70
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
71
|
+
return result;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
addTest('HIPAA', 'Breach notification requirements', async () => {
|
|
75
|
+
const result = await searchRegulations(db, {
|
|
76
|
+
query: 'breach notification timeline',
|
|
77
|
+
regulation: 'HIPAA',
|
|
78
|
+
limit: 5
|
|
79
|
+
});
|
|
80
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
81
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
82
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
addTest('HIPAA', 'Risk assessment requirements', async () => {
|
|
87
|
+
const result = await searchRegulations(db, {
|
|
88
|
+
query: 'risk assessment analysis',
|
|
89
|
+
regulation: 'HIPAA',
|
|
90
|
+
limit: 5
|
|
91
|
+
});
|
|
92
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
93
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
94
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
95
|
+
return result;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ===== CCPA QUERIES =====
|
|
99
|
+
addTest('CCPA', 'Consumer rights to data deletion', async () => {
|
|
100
|
+
const result = await searchRegulations(db, {
|
|
101
|
+
query: 'consumer right delete personal information',
|
|
102
|
+
regulation: 'CCPA',
|
|
103
|
+
limit: 5
|
|
104
|
+
});
|
|
105
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
106
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
107
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
108
|
+
return result;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
addTest('CCPA', 'Data disclosure requirements', async () => {
|
|
112
|
+
const result = await searchRegulations(db, {
|
|
113
|
+
query: 'disclosure categories personal information',
|
|
114
|
+
regulation: 'CCPA',
|
|
115
|
+
limit: 5
|
|
116
|
+
});
|
|
117
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
118
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
119
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
120
|
+
return result;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
addTest('CCPA', 'Do Not Sell opt-out', async () => {
|
|
124
|
+
const result = await searchRegulations(db, {
|
|
125
|
+
query: 'do not sell opt-out',
|
|
126
|
+
regulation: 'CCPA',
|
|
127
|
+
limit: 5
|
|
128
|
+
});
|
|
129
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
130
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
131
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
132
|
+
return result;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ===== SOX QUERIES =====
|
|
136
|
+
addTest('SOX', 'Section 404 IT controls', async () => {
|
|
137
|
+
const result = await searchRegulations(db, {
|
|
138
|
+
query: 'internal control information technology',
|
|
139
|
+
regulation: 'SOX',
|
|
140
|
+
limit: 5
|
|
141
|
+
});
|
|
142
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
143
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
144
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
145
|
+
return result;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
addTest('SOX', 'Financial records retention', async () => {
|
|
149
|
+
const result = await searchRegulations(db, {
|
|
150
|
+
query: 'retention records audit',
|
|
151
|
+
regulation: 'SOX',
|
|
152
|
+
limit: 5
|
|
153
|
+
});
|
|
154
|
+
if (!result || result.length === 0) throw new Error('No results');
|
|
155
|
+
console.log(` ๐ Found ${result.length} sections`);
|
|
156
|
+
console.log(` ๐ Top result: ${result[0].section}`);
|
|
157
|
+
return result;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ===== CROSS-REGULATION QUERIES =====
|
|
161
|
+
addTest('Cross-Regulation', 'Compare breach notification timelines', async () => {
|
|
162
|
+
const result = await compareRequirements(db, {
|
|
163
|
+
topic: 'breach notification timeline',
|
|
164
|
+
regulations: ['HIPAA', 'CCPA']
|
|
165
|
+
});
|
|
166
|
+
if (!result || !result.comparisons) throw new Error('No comparison results');
|
|
167
|
+
console.log(` ๐ Compared ${result.comparisons.length} regulations`);
|
|
168
|
+
for (const comp of result.comparisons) {
|
|
169
|
+
console.log(` - ${comp.regulation}: ${comp.matches?.length || 0} matches`);
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
addTest('Cross-Regulation', 'Compare incident response requirements', async () => {
|
|
175
|
+
const result = await compareRequirements(db, {
|
|
176
|
+
topic: 'incident response',
|
|
177
|
+
regulations: ['HIPAA', 'CCPA', 'SOX']
|
|
178
|
+
});
|
|
179
|
+
if (!result || !result.comparisons) throw new Error('No comparison results');
|
|
180
|
+
console.log(` ๐ Compared ${result.comparisons.length} regulations`);
|
|
181
|
+
for (const comp of result.comparisons) {
|
|
182
|
+
console.log(` - ${comp.regulation}: ${comp.matches?.length || 0} matches`);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
addTest('Cross-Regulation', 'Data protection across all regulations', async () => {
|
|
188
|
+
const result = await compareRequirements(db, {
|
|
189
|
+
topic: 'data protection security',
|
|
190
|
+
regulations: ['HIPAA', 'CCPA', 'SOX']
|
|
191
|
+
});
|
|
192
|
+
if (!result || !result.comparisons) throw new Error('No comparison results');
|
|
193
|
+
console.log(` ๐ Compared ${result.comparisons.length} regulations`);
|
|
194
|
+
for (const comp of result.comparisons) {
|
|
195
|
+
console.log(` - ${comp.regulation}: ${comp.matches?.length || 0} matches`);
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ===== CONTROL MAPPINGS =====
|
|
201
|
+
addTest('Control Mappings', 'NIST 800-53 to HIPAA', async () => {
|
|
202
|
+
const result = await mapControls(db, {
|
|
203
|
+
framework: 'NIST_800_53_R5',
|
|
204
|
+
regulation: 'HIPAA'
|
|
205
|
+
});
|
|
206
|
+
if (!result.mappings || result.mappings.length === 0) throw new Error('No mappings');
|
|
207
|
+
console.log(` ๐ Found ${result.mappings.length} mappings`);
|
|
208
|
+
console.log(` ๐ Sample: ${result.mappings[0].control_id} โ ${result.mappings[0].regulation_sections?.slice(0, 3).join(', ')}`);
|
|
209
|
+
return result;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
addTest('Control Mappings', 'NIST CSF to CCPA', async () => {
|
|
213
|
+
const result = await mapControls(db, {
|
|
214
|
+
framework: 'NIST_CSF_2_0',
|
|
215
|
+
regulation: 'CCPA'
|
|
216
|
+
});
|
|
217
|
+
if (!result.mappings || result.mappings.length === 0) throw new Error('No mappings');
|
|
218
|
+
console.log(` ๐ Found ${result.mappings.length} mappings`);
|
|
219
|
+
console.log(` ๐ Sample: ${result.mappings[0].control_id}`);
|
|
220
|
+
return result;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ===== APPLICABILITY CHECKS =====
|
|
224
|
+
addTest('Applicability', 'Healthcare sector', async () => {
|
|
225
|
+
const result = await checkApplicability(db, { sector: 'healthcare' });
|
|
226
|
+
if (!result.applicable_regulations) throw new Error('No results');
|
|
227
|
+
console.log(` โ
${result.applicable_regulations.length} applicable regulations`);
|
|
228
|
+
for (const reg of result.applicable_regulations) {
|
|
229
|
+
console.log(` - ${reg.regulation}: ${reg.confidence}`);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
addTest('Applicability', 'Financial services', async () => {
|
|
235
|
+
const result = await checkApplicability(db, { sector: 'financial' });
|
|
236
|
+
if (!result.applicable_regulations) throw new Error('No results');
|
|
237
|
+
console.log(` โ
${result.applicable_regulations.length} applicable regulations`);
|
|
238
|
+
for (const reg of result.applicable_regulations) {
|
|
239
|
+
console.log(` - ${reg.regulation}: ${reg.confidence}`);
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
addTest('Applicability', 'E-commerce (California)', async () => {
|
|
245
|
+
const result = await checkApplicability(db, {
|
|
246
|
+
sector: 'technology',
|
|
247
|
+
details: 'e-commerce selling to California consumers'
|
|
248
|
+
});
|
|
249
|
+
if (!result.applicable_regulations) throw new Error('No results');
|
|
250
|
+
console.log(` โ
${result.applicable_regulations.length} applicable regulations`);
|
|
251
|
+
for (const reg of result.applicable_regulations) {
|
|
252
|
+
console.log(` - ${reg.regulation}: ${reg.confidence}`);
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ===== SECTION RETRIEVAL =====
|
|
258
|
+
addTest('Section Retrieval', 'HIPAA Security Rule 164.312', async () => {
|
|
259
|
+
const result = await getSection(db, { regulation: 'HIPAA', section: '164.312' });
|
|
260
|
+
if (!result || !result.text) throw new Error('Section not found');
|
|
261
|
+
console.log(` ๐ ${result.section_number}: ${result.title}`);
|
|
262
|
+
console.log(` ๐ Text length: ${result.text.length} chars`);
|
|
263
|
+
return result;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
addTest('Section Retrieval', 'CCPA 1798.100', async () => {
|
|
267
|
+
const result = await getSection(db, { regulation: 'CCPA', section: '1798.100' });
|
|
268
|
+
if (!result || !result.text) throw new Error('Section not found');
|
|
269
|
+
console.log(` ๐ ${result.section_number}: ${result.title}`);
|
|
270
|
+
console.log(` ๐ Text length: ${result.text.length} chars`);
|
|
271
|
+
return result;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ===== EDGE CASES =====
|
|
275
|
+
addTest('Edge Cases', 'Empty query handling', async () => {
|
|
276
|
+
try {
|
|
277
|
+
await searchRegulations(db, { query: '', limit: 5 });
|
|
278
|
+
throw new Error('Should have rejected empty query');
|
|
279
|
+
} catch (error: any) {
|
|
280
|
+
if (error.message.includes('Query cannot be empty')) {
|
|
281
|
+
console.log(' โ
Correctly rejected empty query');
|
|
282
|
+
return { success: true };
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
addTest('Edge Cases', 'Invalid regulation ID', async () => {
|
|
289
|
+
const result = await searchRegulations(db, {
|
|
290
|
+
query: 'test',
|
|
291
|
+
regulation: 'INVALID',
|
|
292
|
+
limit: 5
|
|
293
|
+
});
|
|
294
|
+
// Should return empty or handle gracefully
|
|
295
|
+
console.log(` โ
Handled invalid regulation: ${result.length} results`);
|
|
296
|
+
return result;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ===== RUN TESTS =====
|
|
300
|
+
async function runAllTests() {
|
|
301
|
+
console.log('๐ US Compliance MCP - Comprehensive Quality Assessment\n');
|
|
302
|
+
console.log('=' .repeat(70));
|
|
303
|
+
|
|
304
|
+
let currentCategory = '';
|
|
305
|
+
|
|
306
|
+
for (const testCase of tests) {
|
|
307
|
+
if (testCase.category !== currentCategory) {
|
|
308
|
+
currentCategory = testCase.category;
|
|
309
|
+
console.log(`\n๐ ${currentCategory.toUpperCase()}`);
|
|
310
|
+
console.log('-'.repeat(70));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
console.log(`\nโจ ${testCase.name}`);
|
|
315
|
+
await testCase.test();
|
|
316
|
+
console.log(' โ
PASSED');
|
|
317
|
+
passed++;
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
console.log(` โ FAILED: ${error.message}`);
|
|
320
|
+
failed++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Final Summary
|
|
325
|
+
console.log('\n' + '='.repeat(70));
|
|
326
|
+
console.log('๐ FINAL RESULTS');
|
|
327
|
+
console.log('='.repeat(70));
|
|
328
|
+
console.log(`Total Tests: ${tests.length}`);
|
|
329
|
+
console.log(`โ
Passed: ${passed} (${Math.round(passed/tests.length * 100)}%)`);
|
|
330
|
+
console.log(`โ Failed: ${failed} (${Math.round(failed/tests.length * 100)}%)`);
|
|
331
|
+
|
|
332
|
+
if (failed === 0) {
|
|
333
|
+
console.log('\n๐ All quality tests passed! MCP is production-ready.\n');
|
|
334
|
+
} else {
|
|
335
|
+
console.log(`\nโ ๏ธ ${failed} test(s) failed. Review errors above.\n`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
db.close();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
runAllTests().catch(error => {
|
|
343
|
+
console.error('Fatal error:', error);
|
|
344
|
+
db.close();
|
|
345
|
+
process.exit(1);
|
|
346
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test MCP Tools
|
|
5
|
+
* Verifies all MCP tools work correctly with populated database
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { searchRegulations } from '../src/tools/search.js';
|
|
12
|
+
import { getSection } from '../src/tools/section.js';
|
|
13
|
+
import { listRegulations } from '../src/tools/list.js';
|
|
14
|
+
import { mapControls } from '../src/tools/map.js';
|
|
15
|
+
import { checkApplicability } from '../src/tools/applicability.js';
|
|
16
|
+
import { getDefinitions } from '../src/tools/definitions.js';
|
|
17
|
+
import { compareRequirements } from '../src/tools/compare.js';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
const DB_PATH = join(__dirname, '..', 'data', 'regulations.db');
|
|
22
|
+
|
|
23
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
24
|
+
|
|
25
|
+
async function runTests() {
|
|
26
|
+
console.log('๐งช Testing US Compliance MCP Tools\n');
|
|
27
|
+
let passed = 0;
|
|
28
|
+
let failed = 0;
|
|
29
|
+
|
|
30
|
+
// Test 1: Search Regulations
|
|
31
|
+
console.log('1๏ธโฃ Testing search_regulations...');
|
|
32
|
+
try {
|
|
33
|
+
const result = await searchRegulations(db, { query: 'encryption', limit: 5 });
|
|
34
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
35
|
+
console.log(` โ
Found ${result.length} results for "encryption"`);
|
|
36
|
+
console.log(` Sample: ${result[0].section} - ${result[0].title?.substring(0, 50)}...`);
|
|
37
|
+
passed++;
|
|
38
|
+
} else {
|
|
39
|
+
console.log(` โ No results returned (got: ${JSON.stringify(result).substring(0, 100)})`);
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.log(` โ Error: ${error}`);
|
|
44
|
+
failed++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Test 2: Get Section
|
|
48
|
+
console.log('\n2๏ธโฃ Testing get_section...');
|
|
49
|
+
try {
|
|
50
|
+
const result = await getSection(db, { regulation: 'HIPAA', section: '164.308' });
|
|
51
|
+
if (result && result.section_number && result.text) {
|
|
52
|
+
console.log(` โ
Retrieved section ${result.section_number}`);
|
|
53
|
+
console.log(` Title: ${result.title}`);
|
|
54
|
+
console.log(` Text length: ${result.text.length} chars`);
|
|
55
|
+
passed++;
|
|
56
|
+
} else {
|
|
57
|
+
console.log(` โ Section not found or incomplete (got: ${JSON.stringify(result)?.substring(0, 100)})`);
|
|
58
|
+
failed++;
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(` โ Error: ${error}`);
|
|
62
|
+
failed++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 3: List Regulations
|
|
66
|
+
console.log('\n3๏ธโฃ Testing list_regulations...');
|
|
67
|
+
try {
|
|
68
|
+
const result = await listRegulations(db, {});
|
|
69
|
+
if (result.regulations && result.regulations.length > 0) {
|
|
70
|
+
console.log(` โ
Found ${result.regulations.length} regulations`);
|
|
71
|
+
console.log(` Regulations: ${result.regulations.map((r: any) => r.id).join(', ')}`);
|
|
72
|
+
passed++;
|
|
73
|
+
} else {
|
|
74
|
+
console.log(' โ No regulations returned');
|
|
75
|
+
failed++;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.log(` โ Error: ${error}`);
|
|
79
|
+
failed++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Test 4: Map Controls
|
|
83
|
+
console.log('\n4๏ธโฃ Testing map_controls...');
|
|
84
|
+
try {
|
|
85
|
+
const result = await mapControls(db, { framework: 'NIST_800_53_R5', regulation: 'HIPAA' });
|
|
86
|
+
if (result.mappings && result.mappings.length > 0) {
|
|
87
|
+
console.log(` โ
Found ${result.mappings.length} control mappings`);
|
|
88
|
+
console.log(` Sample: ${result.mappings[0].control_id} - ${result.mappings[0].control_name}`);
|
|
89
|
+
console.log(` Coverage: ${result.mappings[0].coverage}, Confidence: ${result.mappings[0].confidence}%`);
|
|
90
|
+
passed++;
|
|
91
|
+
} else {
|
|
92
|
+
console.log(' โ No mappings returned');
|
|
93
|
+
failed++;
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(` โ Error: ${error}`);
|
|
97
|
+
failed++;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Test 5: Check Applicability
|
|
101
|
+
console.log('\n5๏ธโฃ Testing check_applicability...');
|
|
102
|
+
try {
|
|
103
|
+
const result = await checkApplicability(db, { sector: 'healthcare' });
|
|
104
|
+
if (result.applicable_regulations && result.applicable_regulations.length > 0) {
|
|
105
|
+
console.log(` โ
Found ${result.applicable_regulations.length} applicable regulations for healthcare`);
|
|
106
|
+
console.log(` Sample: ${result.applicable_regulations[0].regulation} - ${result.applicable_regulations[0].confidence}`);
|
|
107
|
+
passed++;
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` โ No applicability rules returned (got: ${JSON.stringify(result).substring(0, 100)})`);
|
|
110
|
+
failed++;
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.log(` โ Error: ${error}`);
|
|
114
|
+
failed++;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Test 6: Get Definitions
|
|
118
|
+
console.log('\n6๏ธโฃ Testing get_definitions...');
|
|
119
|
+
try {
|
|
120
|
+
const result = await getDefinitions(db, { term: 'health' });
|
|
121
|
+
// Definitions might be empty if not yet extracted
|
|
122
|
+
console.log(` โน๏ธ Found ${result.definitions?.length || 0} definitions (expected 0 for MVP)`);
|
|
123
|
+
passed++;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.log(` โ Error: ${error}`);
|
|
126
|
+
failed++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Test 7: Compare Requirements
|
|
130
|
+
console.log('\n7๏ธโฃ Testing compare_requirements...');
|
|
131
|
+
try {
|
|
132
|
+
const result = await compareRequirements(db, {
|
|
133
|
+
topic: 'data protection',
|
|
134
|
+
regulations: ['HIPAA', 'CCPA']
|
|
135
|
+
});
|
|
136
|
+
if (result.comparisons && result.comparisons.length > 0) {
|
|
137
|
+
console.log(` โ
Compared ${result.comparisons.length} regulations`);
|
|
138
|
+
for (const comp of result.comparisons) {
|
|
139
|
+
console.log(` - ${comp.regulation}: ${comp.matches?.length || 0} matches`);
|
|
140
|
+
}
|
|
141
|
+
passed++;
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` โ No comparison results (got: ${JSON.stringify(result).substring(0, 100)})`);
|
|
144
|
+
failed++;
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.log(` โ Error: ${error}`);
|
|
148
|
+
failed++;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Test 8: List HIPAA Structure
|
|
152
|
+
console.log('\n8๏ธโฃ Testing list_regulations with HIPAA structure...');
|
|
153
|
+
try {
|
|
154
|
+
const result = await listRegulations(db, { regulation: 'HIPAA' });
|
|
155
|
+
if (result.structure && result.structure.chapters && result.structure.chapters.length > 0) {
|
|
156
|
+
const totalSections = result.structure.chapters.reduce((sum, ch) => sum + ch.sections.length, 0);
|
|
157
|
+
console.log(` โ
Found ${totalSections} HIPAA sections in ${result.structure.chapters.length} chapters`);
|
|
158
|
+
console.log(` Sample sections: ${result.structure.chapters[0].sections.slice(0, 3).join(', ')}`);
|
|
159
|
+
passed++;
|
|
160
|
+
} else {
|
|
161
|
+
console.log(` โ No structure returned (got: ${JSON.stringify(result).substring(0, 150)})`);
|
|
162
|
+
failed++;
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.log(` โ Error: ${error}`);
|
|
166
|
+
failed++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Summary
|
|
170
|
+
console.log('\n' + '='.repeat(50));
|
|
171
|
+
console.log(`๐ Test Results: ${passed} passed, ${failed} failed`);
|
|
172
|
+
console.log('='.repeat(50));
|
|
173
|
+
|
|
174
|
+
if (failed === 0) {
|
|
175
|
+
console.log('๐ All tests passed! MCP server is 100% ready.\n');
|
|
176
|
+
} else {
|
|
177
|
+
console.log('โ ๏ธ Some tests failed. Review errors above.\n');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
db.close();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
runTests().catch(error => {
|
|
185
|
+
console.error('Fatal error:', error);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test remaining MCP tools
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Database from 'better-sqlite3';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { getEvidenceRequirements } from '../src/tools/evidence.js';
|
|
11
|
+
import { getComplianceActionItems } from '../src/tools/action-items.js';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const DB_PATH = join(__dirname, '..', 'data', 'regulations.db');
|
|
16
|
+
|
|
17
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
18
|
+
|
|
19
|
+
async function testRemainingTools() {
|
|
20
|
+
console.log('๐งช Testing Remaining MCP Tools\n');
|
|
21
|
+
let passed = 0;
|
|
22
|
+
let failed = 0;
|
|
23
|
+
|
|
24
|
+
// Test 1: Get Evidence Requirements
|
|
25
|
+
console.log('1๏ธโฃ Testing get_evidence_requirements...');
|
|
26
|
+
try {
|
|
27
|
+
const result = await getEvidenceRequirements(db, {
|
|
28
|
+
regulation: 'HIPAA',
|
|
29
|
+
section: '164.308'
|
|
30
|
+
});
|
|
31
|
+
if (result && result.section) {
|
|
32
|
+
console.log(` โ
Retrieved evidence requirements for ${result.section}`);
|
|
33
|
+
console.log(` ๐ Requirements count: ${result.requirements?.length || 0}`);
|
|
34
|
+
if (result.requirements && result.requirements.length > 0) {
|
|
35
|
+
console.log(` ๐ Sample: ${result.requirements[0].category}`);
|
|
36
|
+
}
|
|
37
|
+
passed++;
|
|
38
|
+
} else {
|
|
39
|
+
console.log(` โ No evidence requirements returned`);
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
console.log(` โ Error: ${error.message}`);
|
|
44
|
+
failed++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Test 2: Get Compliance Action Items
|
|
48
|
+
console.log('\n2๏ธโฃ Testing get_compliance_action_items...');
|
|
49
|
+
try {
|
|
50
|
+
const result = await getComplianceActionItems(db, {
|
|
51
|
+
regulation: 'HIPAA',
|
|
52
|
+
sections: ['164.312', '164.308']
|
|
53
|
+
});
|
|
54
|
+
if (result && result.action_items) {
|
|
55
|
+
console.log(` โ
Retrieved ${result.action_items.length} action items`);
|
|
56
|
+
if (result.action_items.length > 0) {
|
|
57
|
+
console.log(` ๐ Sample: ${result.action_items[0].title}`);
|
|
58
|
+
console.log(` ๐ฏ Priority: ${result.action_items[0].priority}`);
|
|
59
|
+
}
|
|
60
|
+
passed++;
|
|
61
|
+
} else {
|
|
62
|
+
console.log(` โ No action items returned`);
|
|
63
|
+
failed++;
|
|
64
|
+
}
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
console.log(` โ Error: ${error.message}`);
|
|
67
|
+
failed++;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Test 3: Test with non-existent section
|
|
71
|
+
console.log('\n3๏ธโฃ Testing with non-existent section...');
|
|
72
|
+
try {
|
|
73
|
+
const result = await getEvidenceRequirements(db, {
|
|
74
|
+
regulation: 'HIPAA',
|
|
75
|
+
section: '999.999'
|
|
76
|
+
});
|
|
77
|
+
console.log(` โ
Handled non-existent section gracefully`);
|
|
78
|
+
passed++;
|
|
79
|
+
} catch (error: any) {
|
|
80
|
+
if (error.message.includes('not found')) {
|
|
81
|
+
console.log(` โ
Correctly returned error for non-existent section`);
|
|
82
|
+
passed++;
|
|
83
|
+
} else {
|
|
84
|
+
console.log(` โ Unexpected error: ${error.message}`);
|
|
85
|
+
failed++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Summary
|
|
90
|
+
console.log('\n' + '='.repeat(50));
|
|
91
|
+
console.log(`๐ Test Results: ${passed} passed, ${failed} failed`);
|
|
92
|
+
console.log('='.repeat(50));
|
|
93
|
+
|
|
94
|
+
if (failed === 0) {
|
|
95
|
+
console.log('๐ All remaining tools tested successfully!\n');
|
|
96
|
+
} else {
|
|
97
|
+
console.log('โ ๏ธ Some tests failed. Review errors above.\n');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
db.close();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
testRemainingTools().catch(error => {
|
|
105
|
+
console.error('Fatal error:', error);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Database path - look for regulations.db in data folder
|
|
19
|
+
const DB_PATH = process.env.US_COMPLIANCE_DB_PATH || join(__dirname, '..', 'data', 'regulations.db');
|
|
20
|
+
|
|
21
|
+
function getDatabase(): Database.Database {
|
|
22
|
+
try {
|
|
23
|
+
return new Database(DB_PATH, { readonly: true });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const db = getDatabase();
|
|
30
|
+
const server = new Server(
|
|
31
|
+
{
|
|
32
|
+
name: 'us-regulations-mcp',
|
|
33
|
+
version: '0.1.0',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
capabilities: {
|
|
37
|
+
tools: {},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Register all tools using shared registry
|
|
43
|
+
registerTools(server, db);
|
|
44
|
+
|
|
45
|
+
// Start the server
|
|
46
|
+
async function main() {
|
|
47
|
+
const transport = new StdioServerTransport();
|
|
48
|
+
await server.connect(transport);
|
|
49
|
+
console.error('US Regulations MCP server started');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
main().catch((error) => {
|
|
53
|
+
console.error('Fatal error:', error);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|