@ansvar/us-regulations-mcp 1.0.0 → 1.2.1

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 (57) hide show
  1. package/README.md +422 -79
  2. package/data/regulations.db +0 -0
  3. package/data/seed/colorado-cpa.json +97 -0
  4. package/data/seed/ffiec.json +103 -0
  5. package/data/seed/mappings/ccpa-nist-csf.json +11 -1
  6. package/data/seed/mappings/hipaa-nist-800-53.json +10 -1
  7. package/data/seed/nydfs.json +122 -0
  8. package/data/seed/sox.json +109 -0
  9. package/dist/index.js +1 -1
  10. package/dist/ingest/adapters/colorado-public.d.ts +25 -0
  11. package/dist/ingest/adapters/colorado-public.d.ts.map +1 -0
  12. package/dist/ingest/adapters/colorado-public.js +76 -0
  13. package/dist/ingest/adapters/colorado-public.js.map +1 -0
  14. package/dist/ingest/adapters/connecticut-cga.d.ts +22 -0
  15. package/dist/ingest/adapters/connecticut-cga.d.ts.map +1 -0
  16. package/dist/ingest/adapters/connecticut-cga.js +116 -0
  17. package/dist/ingest/adapters/connecticut-cga.js.map +1 -0
  18. package/dist/ingest/adapters/ecfr.d.ts +46 -4
  19. package/dist/ingest/adapters/ecfr.d.ts.map +1 -1
  20. package/dist/ingest/adapters/ecfr.js +131 -16
  21. package/dist/ingest/adapters/ecfr.js.map +1 -1
  22. package/dist/ingest/adapters/ffiec.d.ts +42 -0
  23. package/dist/ingest/adapters/ffiec.d.ts.map +1 -0
  24. package/dist/ingest/adapters/ffiec.js +68 -0
  25. package/dist/ingest/adapters/ffiec.js.map +1 -0
  26. package/dist/ingest/adapters/nydfs.d.ts +42 -0
  27. package/dist/ingest/adapters/nydfs.d.ts.map +1 -0
  28. package/dist/ingest/adapters/nydfs.js +68 -0
  29. package/dist/ingest/adapters/nydfs.js.map +1 -0
  30. package/dist/ingest/adapters/regulations-gov.d.ts +11 -12
  31. package/dist/ingest/adapters/regulations-gov.d.ts.map +1 -1
  32. package/dist/ingest/adapters/regulations-gov.js +46 -43
  33. package/dist/ingest/adapters/regulations-gov.js.map +1 -1
  34. package/dist/ingest/adapters/utah-xcode.d.ts +19 -0
  35. package/dist/ingest/adapters/utah-xcode.d.ts.map +1 -0
  36. package/dist/ingest/adapters/utah-xcode.js +112 -0
  37. package/dist/ingest/adapters/utah-xcode.js.map +1 -0
  38. package/dist/ingest/adapters/virginia-law.d.ts +21 -0
  39. package/dist/ingest/adapters/virginia-law.d.ts.map +1 -0
  40. package/dist/ingest/adapters/virginia-law.js +111 -0
  41. package/dist/ingest/adapters/virginia-law.js.map +1 -0
  42. package/package.json +26 -4
  43. package/scripts/build-db.ts +50 -32
  44. package/scripts/check-updates.ts +184 -0
  45. package/scripts/ingest.ts +72 -25
  46. package/src/index.ts +1 -1
  47. package/src/ingest/adapters/colorado-public.ts +96 -0
  48. package/src/ingest/adapters/connecticut-cga.ts +150 -0
  49. package/src/ingest/adapters/ecfr.ts +158 -17
  50. package/src/ingest/adapters/ffiec.ts +77 -0
  51. package/src/ingest/adapters/nydfs.ts +77 -0
  52. package/src/ingest/adapters/regulations-gov.ts +48 -47
  53. package/src/ingest/adapters/utah-xcode.ts +143 -0
  54. package/src/ingest/adapters/virginia-law.ts +140 -0
  55. package/scripts/quality-test.ts +0 -346
  56. package/scripts/test-mcp-tools.ts +0 -187
  57. package/scripts/test-remaining-tools.ts +0 -107
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ansvar/us-regulations-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.1",
4
4
  "mcpName": "us.ansvar/us-regulations-mcp",
5
- "description": "MCP server for US cybersecurity and privacy regulations. Query HIPAA, CCPA, SOX, and more directly from Claude.",
5
+ "description": "MCP server for US cybersecurity and privacy regulations. Query HIPAA, CCPA, SOX, GLBA, FERPA, COPPA, FDA 21 CFR Part 11, EPA RMP, FFIEC, NYDFS 500, and 4 state privacy laws (VA, CO, CT, UT) directly from Claude.",
6
6
  "main": "dist/index.js",
7
7
  "type": "module",
8
8
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "clean": "rm -rf dist && find . -name '*.tsbuildinfo' -not -path './node_modules/*' -delete",
14
- "test": "vitest run",
14
+ "test": "tsx scripts/quality-test.ts && tsx scripts/test-remaining-tools.ts && tsx scripts/test-mcp-tools.ts && tsx scripts/test-ecfr-adapter.ts && tsx scripts/test-seed-adapters.ts && tsx scripts/test-v1.2-adapters.ts && tsx scripts/test-pr-2-enhancements.ts",
15
15
  "test:watch": "vitest",
16
16
  "dev": "tsx src/index.ts",
17
17
  "start": "node dist/index.js",
@@ -19,6 +19,7 @@
19
19
  "ingest": "tsx scripts/ingest.ts",
20
20
  "load-seed": "tsx scripts/load-seed-data.ts",
21
21
  "test:mcp": "tsx scripts/test-mcp-tools.ts",
22
+ "check-updates": "tsx scripts/check-updates.ts",
22
23
  "lint": "eslint src --ext .ts",
23
24
  "prepare": "test -f dist/index.js || npm run build",
24
25
  "prepublishOnly": "npm run build"
@@ -30,13 +31,34 @@
30
31
  "hipaa",
31
32
  "ccpa",
32
33
  "sox",
34
+ "glba",
35
+ "ferpa",
36
+ "coppa",
37
+ "fda",
38
+ "epa",
39
+ "ffiec",
40
+ "nydfs",
41
+ "virginia-cdpa",
42
+ "colorado-cpa",
43
+ "connecticut-ctdpa",
44
+ "utah-ucpa",
45
+ "state-privacy",
33
46
  "us-regulations",
34
47
  "cybersecurity",
35
48
  "privacy",
36
49
  "healthcare",
37
- "financial",
50
+ "financial-services",
51
+ "education",
52
+ "children-privacy",
53
+ "pharmaceutical",
54
+ "environmental",
55
+ "banking",
38
56
  "sarbanes-oxley",
39
57
  "california-consumer-privacy-act",
58
+ "gramm-leach-bliley",
59
+ "pci-dss",
60
+ "payment-card-security",
61
+ "23-nycrr-500",
40
62
  "claude",
41
63
  "llm"
42
64
  ],
@@ -129,16 +129,25 @@ CREATE TABLE IF NOT EXISTS source_registry (
129
129
  `;
130
130
 
131
131
  interface RegulationSeed {
132
- id: string;
133
- full_name: string;
134
- short_name?: string;
135
- citation: string;
136
- effective_date?: string;
137
- source_url?: string;
138
- jurisdiction?: string;
139
- regulation_type?: string;
132
+ regulation: {
133
+ id: string;
134
+ full_name: string;
135
+ short_name?: string;
136
+ citation: string;
137
+ effective_date?: string;
138
+ source_url?: string;
139
+ jurisdiction?: string;
140
+ regulation_type?: string;
141
+ };
142
+ source?: {
143
+ official_url?: string;
144
+ publisher?: string;
145
+ last_verified?: string;
146
+ verification_method?: string;
147
+ disclaimer?: string;
148
+ };
140
149
  sections: Array<{
141
- number: string;
150
+ sectionNumber: string;
142
151
  title?: string;
143
152
  text: string;
144
153
  part?: string;
@@ -184,22 +193,30 @@ function buildDatabase() {
184
193
  if (file.startsWith('mappings')) continue;
185
194
 
186
195
  console.log(`Loading ${file}...`);
187
- const content = readFileSync(join(SEED_DIR, file), 'utf-8');
188
- const regulation: RegulationSeed = JSON.parse(content);
196
+ let seedData: RegulationSeed;
197
+ try {
198
+ const content = readFileSync(join(SEED_DIR, file), 'utf-8');
199
+ seedData = JSON.parse(content);
200
+ } catch (error) {
201
+ const message = error instanceof Error ? error.message : String(error);
202
+ console.error(`Failed to parse ${file}: ${message}`);
203
+ throw error;
204
+ }
205
+ const reg = seedData.regulation;
189
206
 
190
207
  // Insert regulation
191
208
  db.prepare(`
192
209
  INSERT INTO regulations (id, full_name, short_name, citation, effective_date, source_url, jurisdiction, regulation_type)
193
210
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
194
211
  `).run(
195
- regulation.id,
196
- regulation.full_name,
197
- regulation.short_name || null,
198
- regulation.citation,
199
- regulation.effective_date || null,
200
- regulation.source_url || null,
201
- regulation.jurisdiction || null,
202
- regulation.regulation_type || null
212
+ reg.id,
213
+ reg.full_name,
214
+ reg.short_name || null,
215
+ reg.citation,
216
+ reg.effective_date || null,
217
+ seedData.source?.official_url || reg.source_url || null,
218
+ reg.jurisdiction || null,
219
+ reg.regulation_type || null
203
220
  );
204
221
 
205
222
  // Insert sections
@@ -208,10 +225,10 @@ function buildDatabase() {
208
225
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
209
226
  `);
210
227
 
211
- for (const section of regulation.sections) {
228
+ for (const section of seedData.sections) {
212
229
  insertSection.run(
213
- regulation.id,
214
- section.number,
230
+ reg.id,
231
+ section.sectionNumber,
215
232
  section.title || null,
216
233
  section.text,
217
234
  section.part || null,
@@ -223,34 +240,35 @@ function buildDatabase() {
223
240
  }
224
241
 
225
242
  // Insert definitions
226
- if (regulation.definitions) {
243
+ if (seedData.definitions) {
227
244
  const insertDefinition = db.prepare(`
228
245
  INSERT OR IGNORE INTO definitions (regulation, term, definition, section)
229
246
  VALUES (?, ?, ?, ?)
230
247
  `);
231
248
 
232
- for (const def of regulation.definitions) {
233
- insertDefinition.run(regulation.id, def.term, def.definition, def.section);
249
+ for (const def of seedData.definitions) {
250
+ insertDefinition.run(reg.id, def.term, def.definition, def.section);
234
251
  }
235
252
  }
236
253
 
237
254
  // Update source registry with timestamps
238
255
  const now = new Date().toISOString();
239
- const sourceType = regulation.source_url?.includes('api') ? 'api' : regulation.source_url?.includes('.pdf') ? 'pdf' : 'html';
256
+ const sourceUrl = seedData.source?.official_url || reg.source_url || '';
257
+ const sourceType = sourceUrl.includes('api') ? 'api' : sourceUrl.includes('.pdf') ? 'pdf' : 'seed';
240
258
  db.prepare(`
241
259
  INSERT INTO source_registry (regulation, source_type, source_url, api_endpoint, last_fetched, sections_expected, sections_parsed, quality_status)
242
260
  VALUES (?, ?, ?, ?, ?, ?, ?, 'complete')
243
261
  `).run(
244
- regulation.id,
262
+ reg.id,
245
263
  sourceType,
246
- regulation.source_url || '',
247
- regulation.source_url?.includes('api') ? regulation.source_url : null,
264
+ sourceUrl,
265
+ sourceUrl.includes('api') ? sourceUrl : null,
248
266
  now,
249
- regulation.sections.length,
250
- regulation.sections.length
267
+ seedData.sections.length,
268
+ seedData.sections.length
251
269
  );
252
270
 
253
- console.log(` Loaded ${regulation.sections.length} sections, ${regulation.definitions?.length || 0} definitions`);
271
+ console.log(` Loaded ${seedData.sections.length} sections, ${seedData.definitions?.length || 0} definitions`);
254
272
  }
255
273
 
256
274
  // Note: Control mappings and applicability rules are loaded separately
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Regulation Update Checker
4
+ *
5
+ * Checks official sources for regulation updates and reports changes.
6
+ * Exit codes:
7
+ * 0 = No updates found
8
+ * 1 = Updates found (triggers GitHub issue creation)
9
+ * 2 = Error occurred
10
+ */
11
+
12
+ import Database from 'better-sqlite3';
13
+ import { join, dirname } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const DB_PATH = join(__dirname, '..', 'data', 'regulations.db');
19
+
20
+ interface SourceCheck {
21
+ regulation: string;
22
+ source_type: 'ecfr' | 'leginfo' | 'state' | 'seed';
23
+ last_checked?: string;
24
+ last_modified?: string;
25
+ status: 'current' | 'update_available' | 'check_failed';
26
+ message?: string;
27
+ }
28
+
29
+ async function checkEcfrUpdates(regulation: string, title: number, part: number): Promise<SourceCheck> {
30
+ const url = `https://www.ecfr.gov/api/versioner/v1/full/${new Date().toISOString().split('T')[0]}/title-${title}.xml`;
31
+
32
+ try {
33
+ const response = await fetch(url, { method: 'HEAD' });
34
+ const lastModified = response.headers.get('last-modified');
35
+
36
+ return {
37
+ regulation,
38
+ source_type: 'ecfr',
39
+ last_checked: new Date().toISOString(),
40
+ last_modified: lastModified || undefined,
41
+ status: 'current',
42
+ message: `eCFR Title ${title} Part ${part} checked - ${lastModified || 'no date'}`
43
+ };
44
+ } catch (error) {
45
+ return {
46
+ regulation,
47
+ source_type: 'ecfr',
48
+ last_checked: new Date().toISOString(),
49
+ status: 'check_failed',
50
+ message: `Failed to check eCFR: ${error}`
51
+ };
52
+ }
53
+ }
54
+
55
+ async function checkCaliforniaLegInfo(): Promise<SourceCheck> {
56
+ // California LegInfo doesn't provide easy update detection
57
+ // Would need to scrape the bill page or use their search API
58
+ return {
59
+ regulation: 'CCPA',
60
+ source_type: 'leginfo',
61
+ last_checked: new Date().toISOString(),
62
+ status: 'current',
63
+ message: 'California LegInfo - manual check required (no API for amendments)'
64
+ };
65
+ }
66
+
67
+ async function checkStateLegislature(regulation: string, state: string): Promise<SourceCheck> {
68
+ // State legislatures require manual checking or web scraping
69
+ return {
70
+ regulation,
71
+ source_type: 'state',
72
+ last_checked: new Date().toISOString(),
73
+ status: 'current',
74
+ message: `${state} legislature - manual check required (no standardized API)`
75
+ };
76
+ }
77
+
78
+ async function runUpdateChecks(): Promise<void> {
79
+ console.log('═══════════════════════════════════════════════════════════');
80
+ console.log(' US REGULATIONS UPDATE CHECKER');
81
+ console.log(' ' + new Date().toISOString());
82
+ console.log('═══════════════════════════════════════════════════════════\n');
83
+
84
+ const checks: SourceCheck[] = [];
85
+
86
+ // Check eCFR sources
87
+ console.log('📡 Checking eCFR.gov sources...\n');
88
+
89
+ const ecfrSources = [
90
+ { regulation: 'HIPAA', title: 45, part: 164 },
91
+ { regulation: 'GLBA', title: 16, part: 314 },
92
+ { regulation: 'FERPA', title: 34, part: 99 },
93
+ { regulation: 'COPPA', title: 16, part: 312 },
94
+ { regulation: 'FDA_CFR_11', title: 21, part: 11 },
95
+ { regulation: 'EPA_RMP', title: 40, part: 68 },
96
+ ];
97
+
98
+ for (const source of ecfrSources) {
99
+ const check = await checkEcfrUpdates(source.regulation, source.title, source.part);
100
+ checks.push(check);
101
+ console.log(` ${check.regulation}: ${check.message}`);
102
+ }
103
+
104
+ // Check California LegInfo
105
+ console.log('\n📡 Checking California LegInfo...\n');
106
+ const ccpaCheck = await checkCaliforniaLegInfo();
107
+ checks.push(ccpaCheck);
108
+ console.log(` ${ccpaCheck.regulation}: ${ccpaCheck.message}`);
109
+
110
+ // Check state legislatures
111
+ console.log('\n📡 Checking State Legislatures...\n');
112
+ const stateChecks = [
113
+ { regulation: 'VIRGINIA_CDPA', state: 'Virginia' },
114
+ { regulation: 'COLORADO_CPA', state: 'Colorado' },
115
+ { regulation: 'CONNECTICUT_CTDPA', state: 'Connecticut' },
116
+ { regulation: 'UTAH_UCPA', state: 'Utah' },
117
+ ];
118
+
119
+ for (const state of stateChecks) {
120
+ const check = await checkStateLegislature(state.regulation, state.state);
121
+ checks.push(check);
122
+ console.log(` ${check.regulation}: ${check.message}`);
123
+ }
124
+
125
+ // Seed data sources (FFIEC, NYDFS, SOX)
126
+ console.log('\n📡 Checking Seed Data Sources...\n');
127
+ const seedSources = ['FFIEC', 'NYDFS_500', 'SOX'];
128
+ for (const reg of seedSources) {
129
+ checks.push({
130
+ regulation: reg,
131
+ source_type: 'seed',
132
+ last_checked: new Date().toISOString(),
133
+ status: 'current',
134
+ message: `${reg} - seed data (manual updates only)`
135
+ });
136
+ console.log(` ${reg}: seed data (manual updates only)`);
137
+ }
138
+
139
+ // Summary
140
+ console.log('\n═══════════════════════════════════════════════════════════');
141
+ console.log(' SUMMARY');
142
+ console.log('═══════════════════════════════════════════════════════════\n');
143
+
144
+ const updatesCounts = {
145
+ current: checks.filter(c => c.status === 'current').length,
146
+ updates: checks.filter(c => c.status === 'update_available').length,
147
+ failed: checks.filter(c => c.status === 'check_failed').length,
148
+ };
149
+
150
+ console.log(`✅ Current: ${updatesCounts.current}`);
151
+ console.log(`🔔 Updates Available: ${updatesCounts.updates}`);
152
+ console.log(`❌ Check Failed: ${updatesCounts.failed}`);
153
+
154
+ if (updatesCounts.updates > 0) {
155
+ console.log('\n⚠️ UPDATES DETECTED - GitHub issue will be created');
156
+ console.log('\nRegulations with updates:');
157
+ checks.filter(c => c.status === 'update_available').forEach(c => {
158
+ console.log(` - ${c.regulation}: ${c.message}`);
159
+ });
160
+ process.exit(1); // Exit with code 1 to trigger issue creation
161
+ }
162
+
163
+ if (updatesCounts.failed > 0) {
164
+ console.log('\n⚠️ Some checks failed:');
165
+ checks.filter(c => c.status === 'check_failed').forEach(c => {
166
+ console.log(` - ${c.regulation}: ${c.message}`);
167
+ });
168
+ // Don't exit with error for failed checks, just log them
169
+ }
170
+
171
+ console.log('\n✅ All regulations are current');
172
+ process.exit(0);
173
+ }
174
+
175
+ // Handle errors
176
+ process.on('unhandledRejection', (error) => {
177
+ console.error('❌ Unhandled error:', error);
178
+ process.exit(2);
179
+ });
180
+
181
+ runUpdateChecks().catch((error) => {
182
+ console.error('❌ Update check failed:', error);
183
+ process.exit(2);
184
+ });
package/scripts/ingest.ts CHANGED
@@ -10,9 +10,22 @@
10
10
  import Database from 'better-sqlite3';
11
11
  import { join, dirname } from 'path';
12
12
  import { fileURLToPath } from 'url';
13
- import { createHipaaAdapter } from '../src/ingest/adapters/ecfr.js';
13
+ import {
14
+ createHipaaAdapter,
15
+ createGlbaAdapter,
16
+ createFerpaAdapter,
17
+ createCoppaAdapter,
18
+ createFdaAdapter,
19
+ createEpaRmpAdapter
20
+ } from '../src/ingest/adapters/ecfr.js';
14
21
  import { createCcpaAdapter } from '../src/ingest/adapters/california-leginfo.js';
15
22
  import { createSoxAdapter } from '../src/ingest/adapters/regulations-gov.js';
23
+ import { createFfiecAdapter } from '../src/ingest/adapters/ffiec.js';
24
+ import { createNydfsAdapter } from '../src/ingest/adapters/nydfs.js';
25
+ import { createVirginiaAdapter } from '../src/ingest/adapters/virginia-law.js';
26
+ import { createColoradoAdapter } from '../src/ingest/adapters/colorado-public.js';
27
+ import { createConnecticutAdapter } from '../src/ingest/adapters/connecticut-cga.js';
28
+ import { createUtahAdapter } from '../src/ingest/adapters/utah-xcode.js';
16
29
  import type { SourceAdapter } from '../src/ingest/framework.js';
17
30
 
18
31
  const __filename = fileURLToPath(import.meta.url);
@@ -159,45 +172,79 @@ async function ingestRegulation(
159
172
  }
160
173
  }
161
174
 
175
+ /**
176
+ * All regulations to ingest
177
+ * Adding new regulations: add import + entry to this array
178
+ */
179
+ const REGULATIONS = [
180
+ { id: 'HIPAA', adapter: createHipaaAdapter() },
181
+ { id: 'CCPA', adapter: createCcpaAdapter() },
182
+ { id: 'SOX', adapter: createSoxAdapter() },
183
+ { id: 'GLBA', adapter: createGlbaAdapter() },
184
+ { id: 'FERPA', adapter: createFerpaAdapter() },
185
+ { id: 'COPPA', adapter: createCoppaAdapter() },
186
+ { id: 'FDA_CFR_11', adapter: createFdaAdapter() },
187
+ { id: 'EPA_RMP', adapter: createEpaRmpAdapter() },
188
+ { id: 'FFIEC', adapter: createFfiecAdapter() },
189
+ { id: 'NYDFS_500', adapter: createNydfsAdapter() },
190
+ { id: 'VIRGINIA_CDPA', adapter: createVirginiaAdapter() },
191
+ { id: 'COLORADO_CPA', adapter: createColoradoAdapter() },
192
+ { id: 'CONNECTICUT_CTDPA', adapter: createConnecticutAdapter() },
193
+ { id: 'UTAH_UCPA', adapter: createUtahAdapter() },
194
+ ];
195
+
162
196
  /**
163
197
  * Main ingestion function
164
198
  */
165
199
  async function ingestAll(): Promise<IngestResult[]> {
166
- console.log('🚀 Starting US Compliance MCP Ingestion...\n');
167
- console.log(`Database: ${DB_PATH}`);
168
-
169
200
  const db = Database(DB_PATH);
170
201
  const results: IngestResult[] = [];
171
202
 
172
- const adapters: Array<{ id: string; adapter: SourceAdapter }> = [
173
- { id: 'HIPAA', adapter: createHipaaAdapter() },
174
- { id: 'CCPA', adapter: createCcpaAdapter() },
175
- { id: 'SOX', adapter: createSoxAdapter() },
176
- ];
203
+ console.log(`🚀 Ingesting ${REGULATIONS.length} regulations...\n`);
177
204
 
178
- for (const { id, adapter } of adapters) {
205
+ // Sequential ingestion for now
206
+ for (const { id, adapter } of REGULATIONS) {
179
207
  const result = await ingestRegulation(db, id, adapter);
180
208
  results.push(result);
181
209
  }
182
210
 
183
211
  db.close();
184
212
 
213
+ // Summary report
185
214
  console.log('\n📊 Ingestion Summary:');
186
- console.table(
187
- results.map(r => ({
188
- Regulation: r.regulation,
189
- Status: r.success ? '✅ Success' : '❌ Failed',
190
- Sections: r.sections_added,
191
- Definitions: r.definitions_added,
192
- 'Duration (s)': (r.duration_ms / 1000).toFixed(2),
193
- Error: r.error || '-',
194
- }))
195
- );
196
-
197
- const totalSections = results.reduce((sum, r) => sum + r.sections_added, 0);
198
- const successCount = results.filter(r => r.success).length;
199
-
200
- console.log(`\n✨ Total: ${totalSections} sections from ${successCount}/${adapters.length} regulations`);
215
+ console.log('━'.repeat(60));
216
+
217
+ let totalSections = 0;
218
+ let totalDefinitions = 0;
219
+ let successCount = 0;
220
+
221
+ results.forEach(r => {
222
+ const status = r.success ? '' : '❌';
223
+ const secStr = `${r.sections_added} sections`.padEnd(15);
224
+ const defStr = `${r.definitions_added} defs`.padEnd(10);
225
+ const timeStr = `${r.duration_ms}ms`;
226
+
227
+ console.log(`${status} ${r.regulation.padEnd(12)} ${secStr} ${defStr} ${timeStr}`);
228
+
229
+ if (r.success) {
230
+ successCount++;
231
+ totalSections += r.sections_added;
232
+ totalDefinitions += r.definitions_added;
233
+ } else {
234
+ console.error(` Error: ${r.error}`);
235
+ }
236
+ });
237
+
238
+ console.log('━'.repeat(60));
239
+ console.log(`Total: ${successCount}/${results.length} regulations, ${totalSections} sections, ${totalDefinitions} definitions`);
240
+
241
+ // Exit with error if any failed
242
+ if (successCount < results.length) {
243
+ console.error('\n❌ Some regulations failed to ingest');
244
+ process.exit(1);
245
+ }
246
+
247
+ console.log('\n✅ All regulations ingested successfully!');
201
248
 
202
249
  return results;
203
250
  }
package/src/index.ts CHANGED
@@ -30,7 +30,7 @@ const db = getDatabase();
30
30
  const server = new Server(
31
31
  {
32
32
  name: 'us-regulations-mcp',
33
- version: '0.1.0',
33
+ version: '1.1.0',
34
34
  },
35
35
  {
36
36
  capabilities: {
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Colorado Privacy Act Adapter
3
+ *
4
+ * Fetches Colorado CPA from official Colorado General Assembly sources.
5
+ * Source: C.R.S. § 6-1-1301 to 6-1-1313
6
+ *
7
+ * Official Source: https://leg.colorado.gov/colorado-revised-statutes
8
+ *
9
+ * NOTE: Uses seed data extracted from official Colorado Revised Statutes.
10
+ * The Colorado General Assembly publishes statutes in PDF format at leg.colorado.gov.
11
+ * This adapter uses pre-verified seed data to ensure accuracy and avoid
12
+ * reliance on third-party aggregators.
13
+ */
14
+
15
+ import {
16
+ SourceAdapter,
17
+ RegulationMetadata,
18
+ Section,
19
+ Definition,
20
+ UpdateStatus,
21
+ } from '../framework.js';
22
+ import * as fs from 'fs';
23
+ import * as path from 'path';
24
+ import { fileURLToPath } from 'url';
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+
29
+ export class ColoradoLegAdapter implements SourceAdapter {
30
+ private readonly regulationId = 'COLORADO_CPA';
31
+ private readonly seedPath: string;
32
+
33
+ constructor() {
34
+ this.seedPath = path.join(__dirname, '../../../data/seed/colorado-cpa.json');
35
+ }
36
+
37
+ async fetchMetadata(): Promise<RegulationMetadata> {
38
+ return {
39
+ id: 'COLORADO_CPA',
40
+ full_name: 'Colorado Privacy Act',
41
+ citation: 'C.R.S. §6-1-1301 to 6-1-1313',
42
+ effective_date: '2023-07-01',
43
+ last_amended: '2024-01-01',
44
+ source_url: 'https://leg.colorado.gov/colorado-revised-statutes',
45
+ jurisdiction: 'colorado',
46
+ regulation_type: 'statute',
47
+ };
48
+ }
49
+
50
+ async *fetchSections(): AsyncGenerator<Section[]> {
51
+ console.log('Loading Colorado CPA from official seed data...');
52
+
53
+ try {
54
+ const seedData = JSON.parse(fs.readFileSync(this.seedPath, 'utf-8'));
55
+
56
+ if (!seedData.sections || !Array.isArray(seedData.sections)) {
57
+ throw new Error('Invalid seed data format');
58
+ }
59
+
60
+ const sections: Section[] = seedData.sections.map((s: any) => ({
61
+ sectionNumber: s.sectionNumber,
62
+ title: s.title,
63
+ text: s.text,
64
+ chapter: s.chapter || 'Colorado Privacy Act',
65
+ }));
66
+
67
+ console.log(` Loaded ${sections.length} sections from seed data`);
68
+
69
+ // Validate minimum section count
70
+ if (sections.length < 10) {
71
+ throw new Error(`Expected at least 10 sections, got ${sections.length}`);
72
+ }
73
+
74
+ yield sections;
75
+ } catch (error) {
76
+ console.error('Failed to load Colorado CPA seed data:', error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ async extractDefinitions(): Promise<Definition[]> {
82
+ return [];
83
+ }
84
+
85
+ async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
86
+ // Seed data is static - updates require manual verification
87
+ return {
88
+ hasChanges: false,
89
+ changes: ['Colorado CPA uses verified seed data. Check leg.colorado.gov for updates.']
90
+ };
91
+ }
92
+ }
93
+
94
+ export function createColoradoAdapter(): SourceAdapter {
95
+ return new ColoradoLegAdapter();
96
+ }