@ansvar/eu-regulations-mcp 0.6.5 → 0.7.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 +18 -27
- package/data/regulations.db +0 -0
- package/dist/database/postgres-adapter.d.ts +3 -0
- package/dist/database/postgres-adapter.d.ts.map +1 -0
- package/dist/database/postgres-adapter.js +65 -0
- package/dist/database/postgres-adapter.js.map +1 -0
- package/dist/database/sqlite-adapter.d.ts +8 -0
- package/dist/database/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/sqlite-adapter.js +40 -0
- package/dist/database/sqlite-adapter.js.map +1 -0
- package/dist/database/types.d.ts +10 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +2 -0
- package/dist/database/types.js.map +1 -0
- package/dist/http-server.js +3 -1
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts +47 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +85 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/tools/applicability.d.ts +2 -2
- package/dist/tools/applicability.d.ts.map +1 -1
- package/dist/tools/applicability.js +18 -18
- package/dist/tools/applicability.js.map +1 -1
- package/dist/tools/article.d.ts +2 -2
- package/dist/tools/article.d.ts.map +1 -1
- package/dist/tools/article.js +4 -3
- package/dist/tools/article.js.map +1 -1
- package/dist/tools/compare.d.ts +2 -2
- package/dist/tools/compare.d.ts.map +1 -1
- package/dist/tools/compare.js +3 -6
- package/dist/tools/compare.js.map +1 -1
- package/dist/tools/definitions.d.ts +2 -2
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +4 -4
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/evidence.d.ts +2 -2
- package/dist/tools/evidence.d.ts.map +1 -1
- package/dist/tools/evidence.js +6 -6
- package/dist/tools/evidence.js.map +1 -1
- package/dist/tools/list.d.ts +2 -2
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +16 -19
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/map.d.ts +2 -2
- package/dist/tools/map.d.ts.map +1 -1
- package/dist/tools/map.js +5 -4
- package/dist/tools/map.js.map +1 -1
- package/dist/tools/recital.d.ts +2 -2
- package/dist/tools/recital.d.ts.map +1 -1
- package/dist/tools/recital.js +4 -3
- package/dist/tools/recital.js.map +1 -1
- package/dist/tools/registry.d.ts +3 -3
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/search.d.ts +2 -2
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +150 -60
- package/dist/tools/search.js.map +1 -1
- package/dist/worker.d.ts +50 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +565 -0
- package/dist/worker.js.map +1 -0
- package/package.json +9 -5
- package/src/database/postgres-adapter.ts +84 -0
- package/src/database/sqlite-adapter.ts +44 -0
- package/src/database/types.ts +10 -0
- package/src/http-server.ts +6 -3
- package/src/index.ts +14 -8
- package/src/middleware/rate-limit.ts +104 -0
- package/src/tools/applicability.ts +25 -20
- package/src/tools/article.ts +11 -9
- package/src/tools/compare.ts +8 -8
- package/src/tools/definitions.ts +6 -11
- package/src/tools/evidence.ts +8 -20
- package/src/tools/list.ts +33 -25
- package/src/tools/map.ts +8 -6
- package/src/tools/recital.ts +11 -9
- package/src/tools/registry.ts +3 -3
- package/src/tools/search.ts +205 -82
- package/src/worker.ts +708 -0
package/README.md
CHANGED
|
@@ -6,20 +6,14 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/Ansvar-Systems/EU_compliance_MCP)
|
|
8
8
|
[](https://github.com/Ansvar-Systems/EU_compliance_MCP/actions/workflows/check-updates.yml)
|
|
9
|
-
[](https://github.com/Ansvar-Systems/EU_compliance_MCP/actions/workflows/deploy-azure.yml)
|
|
10
9
|
[](docs/COVERAGE_GAPS.md)
|
|
11
10
|
[](docs/COVERAGE_GAPS.md)
|
|
12
|
-
[](https://eu-regulations-mcp.jollysea-916ea475.westeurope.azurecontainerapps.io/health)
|
|
13
|
-
|
|
14
|
-
<a href="https://glama.ai/mcp/servers/@Mortalus/eu-regulations">
|
|
15
|
-
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Mortalus/eu-regulations/badge" />
|
|
16
|
-
</a>
|
|
17
11
|
|
|
18
12
|
Query **37 EU regulations** — from GDPR and AI Act to DORA, MiFID II, eIDAS, Medical Device Regulation, and more — directly from Claude, Cursor, or any MCP-compatible client.
|
|
19
13
|
|
|
20
14
|
If you're building digital products, financial services, healthcare tech, or connected devices for the European market, this is your compliance reference.
|
|
21
15
|
|
|
22
|
-
Built by [Ansvar Systems](https://ansvar.
|
|
16
|
+
Built by [Ansvar Systems](https://ansvar.eu) — Stockholm, Sweden
|
|
23
17
|
|
|
24
18
|
---
|
|
25
19
|
|
|
@@ -78,22 +72,6 @@ Restart Claude Desktop. Done.
|
|
|
78
72
|
}
|
|
79
73
|
```
|
|
80
74
|
|
|
81
|
-
### Hosted Service (Zero Setup)
|
|
82
|
-
|
|
83
|
-
Already running on Azure - just add to your config:
|
|
84
|
-
|
|
85
|
-
```json
|
|
86
|
-
{
|
|
87
|
-
"mcpServers": {
|
|
88
|
-
"eu-regulations": {
|
|
89
|
-
"url": "https://eu-regulations-mcp.jollysea-916ea475.westeurope.azurecontainerapps.io/mcp"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
See [HANDOVER.md](HANDOVER.md) and [SETUP-CICD.md](SETUP-CICD.md) for deployment details.
|
|
96
|
-
|
|
97
75
|
---
|
|
98
76
|
|
|
99
77
|
## Example Queries
|
|
@@ -133,10 +111,23 @@ Once connected, just ask naturally:
|
|
|
133
111
|
|
|
134
112
|
### Why This Works
|
|
135
113
|
|
|
114
|
+
**Verbatim Source Text (No LLM Processing):**
|
|
115
|
+
- All article text is ingested from EUR-Lex/UNECE official sources
|
|
116
|
+
- Snippets are returned **unchanged** from SQLite FTS5 database rows
|
|
117
|
+
- Zero LLM summarization or paraphrasing — the database contains regulation text, not AI interpretations
|
|
118
|
+
- **Note:** HTML-to-text conversion normalizes whitespace/formatting, but preserves content
|
|
119
|
+
|
|
136
120
|
**Smart Context Management:**
|
|
137
|
-
- Search returns **
|
|
138
|
-
- Article retrieval
|
|
139
|
-
- Cross-references help navigate without loading everything
|
|
121
|
+
- Search returns **32-token snippets** with highlighted matches (safe for context)
|
|
122
|
+
- Article retrieval warns about token usage (some articles = 70k tokens)
|
|
123
|
+
- Cross-references help navigate without loading everything at once
|
|
124
|
+
|
|
125
|
+
**Technical Architecture:**
|
|
126
|
+
```
|
|
127
|
+
EUR-Lex HTML → Parse → SQLite → FTS5 snippet() → MCP response
|
|
128
|
+
↑ ↑
|
|
129
|
+
Formatting only Verbatim database query
|
|
130
|
+
```
|
|
140
131
|
|
|
141
132
|
### Example: EUR-Lex vs. This MCP
|
|
142
133
|
|
|
@@ -192,7 +183,7 @@ We build AI-accelerated threat modeling and compliance tools for automotive, fin
|
|
|
192
183
|
|
|
193
184
|
So we're open-sourcing it. Navigating 37 regulations shouldn't require a legal team.
|
|
194
185
|
|
|
195
|
-
**[ansvar.
|
|
186
|
+
**[ansvar.eu](https://ansvar.eu)** — Stockholm, Sweden
|
|
196
187
|
|
|
197
188
|
---
|
|
198
189
|
|
package/data/regulations.db
CHANGED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-adapter.d.ts","sourceRoot":"","sources":["../../src/database/postgres-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAE/D,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CA8E1B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
export async function createPostgresAdapter(connectionString) {
|
|
3
|
+
const pool = new pg.Pool({
|
|
4
|
+
connectionString,
|
|
5
|
+
max: 10,
|
|
6
|
+
idleTimeoutMillis: 30000,
|
|
7
|
+
connectionTimeoutMillis: 10000,
|
|
8
|
+
});
|
|
9
|
+
// Test connection
|
|
10
|
+
try {
|
|
11
|
+
await pool.query('SELECT 1');
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error(`Failed to connect to PostgreSQL: ${error}`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: 'postgres',
|
|
18
|
+
async query(sql, params) {
|
|
19
|
+
const QUERY_TIMEOUT_MS = 10000; // 10 seconds
|
|
20
|
+
try {
|
|
21
|
+
// Create timeout promise
|
|
22
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
reject(new Error('Database query timeout: Query exceeded 10 seconds'));
|
|
25
|
+
}, QUERY_TIMEOUT_MS);
|
|
26
|
+
});
|
|
27
|
+
// Race between query and timeout
|
|
28
|
+
const result = await Promise.race([
|
|
29
|
+
pool.query(sql, params),
|
|
30
|
+
timeoutPromise,
|
|
31
|
+
]);
|
|
32
|
+
return {
|
|
33
|
+
rows: result.rows,
|
|
34
|
+
rowCount: result.rowCount || 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const pgError = error;
|
|
39
|
+
console.error('PostgreSQL query failed:', {
|
|
40
|
+
sql: sql.substring(0, 100), // Truncate for logging
|
|
41
|
+
params,
|
|
42
|
+
code: pgError.code,
|
|
43
|
+
message: pgError.message,
|
|
44
|
+
detail: pgError.detail,
|
|
45
|
+
});
|
|
46
|
+
// Check for timeout error
|
|
47
|
+
if (pgError.message?.includes('timeout')) {
|
|
48
|
+
throw new Error('Database query timeout: Query exceeded 10 seconds');
|
|
49
|
+
}
|
|
50
|
+
// Provide helpful error messages based on error code
|
|
51
|
+
if (pgError.code?.startsWith('08')) {
|
|
52
|
+
throw new Error(`Database connection error: ${pgError.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (pgError.code === '42P01') {
|
|
55
|
+
throw new Error(`Table not found: ${pgError.message}`);
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Query failed: ${pgError.message}${pgError.code ? ` (${pgError.code})` : ''}`);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
async close() {
|
|
61
|
+
await pool.end();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=postgres-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-adapter.js","sourceRoot":"","sources":["../../src/database/postgres-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,gBAAwB;IAExB,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;QACvB,gBAAgB;QAChB,GAAG,EAAE,EAAE;QACP,iBAAiB,EAAE,KAAK;QACxB,uBAAuB,EAAE,KAAK;KAC/B,CAAC,CAAC;IAEH,kBAAkB;IAClB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,oCAAoC,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAmB;QAEzB,KAAK,CAAC,KAAK,CAAU,GAAW,EAAE,MAAc;YAC9C,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,aAAa;YAE7C,IAAI,CAAC;gBACH,yBAAyB;gBACzB,MAAM,cAAc,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;oBACtD,UAAU,CAAC,GAAG,EAAE;wBACd,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,CAAC;oBACzE,CAAC,EAAE,gBAAgB,CAAC,CAAC;gBACvB,CAAC,CAAC,CAAC;gBAEH,iCAAiC;gBACjC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;oBAChC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC;oBACvB,cAAc;iBACf,CAAC,CAAC;gBAEH,OAAO;oBACL,IAAI,EAAE,MAAM,CAAC,IAAW;oBACxB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,CAAC;iBAC/B,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,OAAO,GAAG,KAIf,CAAC;gBACF,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE;oBACxC,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,uBAAuB;oBACnD,MAAM;oBACN,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,MAAM,EAAE,OAAO,CAAC,MAAM;iBACvB,CAAC,CAAC;gBAEH,0BAA0B;gBAC1B,IAAI,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBACzC,MAAM,IAAI,KAAK,CACb,mDAAmD,CACpD,CAAC;gBACJ,CAAC;gBAED,qDAAqD;gBACrD,IAAI,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnC,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBACnE,CAAC;gBACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC7B,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBACzD,CAAC;gBAED,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9E,CAAC;YACJ,CAAC;QACH,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { DatabaseAdapter } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Adapter that wraps better-sqlite3 Database to match DatabaseAdapter interface.
|
|
5
|
+
* Allows existing SQLite-based entry points (stdio) to work with the new adapter interface.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSqliteAdapter(db: Database.Database): DatabaseAdapter;
|
|
8
|
+
//# sourceMappingURL=sqlite-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-adapter.d.ts","sourceRoot":"","sources":["../../src/database/sqlite-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,eAAe,CAoC1E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter that wraps better-sqlite3 Database to match DatabaseAdapter interface.
|
|
3
|
+
* Allows existing SQLite-based entry points (stdio) to work with the new adapter interface.
|
|
4
|
+
*/
|
|
5
|
+
export function createSqliteAdapter(db) {
|
|
6
|
+
return {
|
|
7
|
+
type: 'sqlite',
|
|
8
|
+
async query(sql, params) {
|
|
9
|
+
try {
|
|
10
|
+
// Convert PostgreSQL syntax to SQLite syntax
|
|
11
|
+
let sqliteSql = sql
|
|
12
|
+
// Convert $1, $2 placeholders to ?
|
|
13
|
+
.replace(/\$\d+/g, '?')
|
|
14
|
+
// Convert ILIKE to LIKE (SQLite is case-insensitive by default with LIKE)
|
|
15
|
+
.replace(/\sILIKE\s/gi, ' LIKE ')
|
|
16
|
+
// Convert ::TEXT type casting to CAST(... AS TEXT)
|
|
17
|
+
.replace(/(\w+)::TEXT/g, 'CAST($1 AS TEXT)')
|
|
18
|
+
.replace(/(\w+)::INTEGER/g, 'CAST($1 AS INTEGER)')
|
|
19
|
+
// Convert DISTINCT ON (col) to GROUP BY col
|
|
20
|
+
// PostgreSQL: SELECT DISTINCT ON (col1) col1, col2 FROM ... ORDER BY col1, col2
|
|
21
|
+
// SQLite: SELECT col1, col2 FROM ... GROUP BY col1 HAVING MIN(col2) = col2
|
|
22
|
+
.replace(/SELECT\s+DISTINCT\s+ON\s*\([^)]+\)/gi, 'SELECT');
|
|
23
|
+
const stmt = db.prepare(sqliteSql);
|
|
24
|
+
const rows = params ? stmt.all(...params) : stmt.all();
|
|
25
|
+
return {
|
|
26
|
+
rows: rows,
|
|
27
|
+
rowCount: rows.length,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error('SQLite query failed:', sql, params, error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
async close() {
|
|
36
|
+
db.close();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=sqlite-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-adapter.js","sourceRoot":"","sources":["../../src/database/sqlite-adapter.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAqB;IACvD,OAAO;QACL,IAAI,EAAE,QAAiB;QAEvB,KAAK,CAAC,KAAK,CAAU,GAAW,EAAE,MAAc;YAC9C,IAAI,CAAC;gBACH,6CAA6C;gBAC7C,IAAI,SAAS,GAAG,GAAG;oBACjB,mCAAmC;qBAClC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;oBACvB,0EAA0E;qBACzE,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC;oBACjC,mDAAmD;qBAClD,OAAO,CAAC,cAAc,EAAE,kBAAkB,CAAC;qBAC3C,OAAO,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;oBAClD,4CAA4C;oBAC5C,gFAAgF;oBAChF,2EAA2E;qBAC1E,OAAO,CAAC,sCAAsC,EAAE,QAAQ,CAAC,CAAC;gBAE7D,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACnC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvD,OAAO;oBACL,IAAI,EAAE,IAAW;oBACjB,QAAQ,EAAE,IAAI,CAAC,MAAM;iBACtB,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBAC1D,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,KAAK,CAAC,KAAK;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface QueryResult<T = any> {
|
|
2
|
+
rows: T[];
|
|
3
|
+
rowCount: number;
|
|
4
|
+
}
|
|
5
|
+
export interface DatabaseAdapter {
|
|
6
|
+
query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>>;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
type: 'sqlite' | 'postgres';
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/database/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG;IAClC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/database/types.ts"],"names":[],"mappings":""}
|
package/dist/http-server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'url';
|
|
|
13
13
|
import { dirname, join } from 'path';
|
|
14
14
|
import { randomUUID } from 'crypto';
|
|
15
15
|
import { registerTools } from './tools/registry.js';
|
|
16
|
+
import { createSqliteAdapter } from './database/sqlite-adapter.js';
|
|
16
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
18
|
const __dirname = dirname(__filename);
|
|
18
19
|
// Database path - look for regulations.db in data folder
|
|
@@ -23,7 +24,8 @@ let db;
|
|
|
23
24
|
function getDatabase() {
|
|
24
25
|
if (!db) {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
const sqliteDb = new Database(DB_PATH, { readonly: true });
|
|
28
|
+
db = createSqliteAdapter(sqliteDb);
|
|
27
29
|
}
|
|
28
30
|
catch (error) {
|
|
29
31
|
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
package/dist/http-server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAKnG,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,19 +5,24 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { dirname, join } from 'path';
|
|
7
7
|
import { registerTools } from './tools/registry.js';
|
|
8
|
+
import { createSqliteAdapter } from './database/sqlite-adapter.js';
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = dirname(__filename);
|
|
10
11
|
// Database path - look for regulations.db in data folder
|
|
11
12
|
const DB_PATH = process.env.EU_COMPLIANCE_DB_PATH || join(__dirname, '..', 'data', 'regulations.db');
|
|
13
|
+
let db;
|
|
12
14
|
function getDatabase() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
if (!db) {
|
|
16
|
+
try {
|
|
17
|
+
const sqliteDb = new Database(DB_PATH, { readonly: true });
|
|
18
|
+
db = createSqliteAdapter(sqliteDb);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Failed to open database at ${DB_PATH}: ${error}`);
|
|
22
|
+
}
|
|
18
23
|
}
|
|
24
|
+
return db;
|
|
19
25
|
}
|
|
20
|
-
const db = getDatabase();
|
|
21
26
|
const server = new Server({
|
|
22
27
|
name: 'eu-regulations-mcp',
|
|
23
28
|
version: '0.1.0',
|
|
@@ -27,7 +32,7 @@ const server = new Server({
|
|
|
27
32
|
},
|
|
28
33
|
});
|
|
29
34
|
// Register all tools using shared registry
|
|
30
|
-
registerTools(server,
|
|
35
|
+
registerTools(server, getDatabase());
|
|
31
36
|
// Start the server
|
|
32
37
|
async function main() {
|
|
33
38
|
const transport = new StdioServerTransport();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAKjF,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAKjF,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGnE,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,yDAAyD;AACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAErG,IAAI,EAAmB,CAAC;AAExB,SAAS,WAAW;IAClB,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,EAAE,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AACD,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;CACF,CACF,CAAC;AAEF,2CAA2C;AAC3C,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AAErC,mBAAmB;AACnB,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface RateLimitInfo {
|
|
2
|
+
allowed: boolean;
|
|
3
|
+
remaining: number;
|
|
4
|
+
resetAt: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Fixed-window rate limiter for IP-based request throttling.
|
|
8
|
+
*
|
|
9
|
+
* Uses fixed time windows that reset at specific intervals. This is simpler
|
|
10
|
+
* than true sliding windows but allows burst traffic at window boundaries
|
|
11
|
+
* (e.g., 100 requests at 09:59:59 + 100 at 10:00:01 = 200 in 2 seconds).
|
|
12
|
+
*
|
|
13
|
+
* Trade-off accepted: Simplicity and performance over burst protection.
|
|
14
|
+
* Suitable for basic rate limiting where occasional bursts are acceptable.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const limiter = new RateLimiter(100, 3600000); // 100 requests per hour
|
|
18
|
+
* if (limiter.checkLimit(clientIP)) {
|
|
19
|
+
* // Allow request
|
|
20
|
+
* } else {
|
|
21
|
+
* // Return 429 Too Many Requests
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
export declare class RateLimiter {
|
|
25
|
+
private records;
|
|
26
|
+
private readonly maxRequests;
|
|
27
|
+
private readonly windowMs;
|
|
28
|
+
constructor(maxRequests: number, windowMs: number);
|
|
29
|
+
/**
|
|
30
|
+
* Check if a request from the given IP should be allowed
|
|
31
|
+
* @param ip Client IP address
|
|
32
|
+
* @returns true if allowed, false if rate limited
|
|
33
|
+
*/
|
|
34
|
+
checkLimit(ip: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get detailed rate limit information for an IP
|
|
37
|
+
* @param ip Client IP address
|
|
38
|
+
* @returns Rate limit status with remaining requests and reset time
|
|
39
|
+
*/
|
|
40
|
+
getRateLimitInfo(ip: string): RateLimitInfo;
|
|
41
|
+
/**
|
|
42
|
+
* Clean up old records to prevent memory leak
|
|
43
|
+
* Should be called periodically (e.g., every hour)
|
|
44
|
+
*/
|
|
45
|
+
cleanup(): void;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAKjD;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa;IAsC3C;;;OAGG;IACH,OAAO,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-window rate limiter for IP-based request throttling.
|
|
3
|
+
*
|
|
4
|
+
* Uses fixed time windows that reset at specific intervals. This is simpler
|
|
5
|
+
* than true sliding windows but allows burst traffic at window boundaries
|
|
6
|
+
* (e.g., 100 requests at 09:59:59 + 100 at 10:00:01 = 200 in 2 seconds).
|
|
7
|
+
*
|
|
8
|
+
* Trade-off accepted: Simplicity and performance over burst protection.
|
|
9
|
+
* Suitable for basic rate limiting where occasional bursts are acceptable.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const limiter = new RateLimiter(100, 3600000); // 100 requests per hour
|
|
13
|
+
* if (limiter.checkLimit(clientIP)) {
|
|
14
|
+
* // Allow request
|
|
15
|
+
* } else {
|
|
16
|
+
* // Return 429 Too Many Requests
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
export class RateLimiter {
|
|
20
|
+
records = new Map();
|
|
21
|
+
maxRequests;
|
|
22
|
+
windowMs;
|
|
23
|
+
constructor(maxRequests, windowMs) {
|
|
24
|
+
this.maxRequests = maxRequests;
|
|
25
|
+
this.windowMs = windowMs;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a request from the given IP should be allowed
|
|
29
|
+
* @param ip Client IP address
|
|
30
|
+
* @returns true if allowed, false if rate limited
|
|
31
|
+
*/
|
|
32
|
+
checkLimit(ip) {
|
|
33
|
+
return this.getRateLimitInfo(ip).allowed;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get detailed rate limit information for an IP
|
|
37
|
+
* @param ip Client IP address
|
|
38
|
+
* @returns Rate limit status with remaining requests and reset time
|
|
39
|
+
*/
|
|
40
|
+
getRateLimitInfo(ip) {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
let record = this.records.get(ip);
|
|
43
|
+
// Clean up if window expired
|
|
44
|
+
if (record && now > record.resetAt) {
|
|
45
|
+
this.records.delete(ip);
|
|
46
|
+
record = undefined;
|
|
47
|
+
}
|
|
48
|
+
// Initialize new record if needed
|
|
49
|
+
if (!record) {
|
|
50
|
+
record = {
|
|
51
|
+
count: 0,
|
|
52
|
+
resetAt: now + this.windowMs,
|
|
53
|
+
};
|
|
54
|
+
this.records.set(ip, record);
|
|
55
|
+
}
|
|
56
|
+
// Check if over limit
|
|
57
|
+
if (record.count >= this.maxRequests) {
|
|
58
|
+
return {
|
|
59
|
+
allowed: false,
|
|
60
|
+
remaining: 0,
|
|
61
|
+
resetAt: record.resetAt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Increment and allow
|
|
65
|
+
record.count++;
|
|
66
|
+
return {
|
|
67
|
+
allowed: true,
|
|
68
|
+
remaining: this.maxRequests - record.count,
|
|
69
|
+
resetAt: record.resetAt,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clean up old records to prevent memory leak
|
|
74
|
+
* Should be called periodically (e.g., every hour)
|
|
75
|
+
*/
|
|
76
|
+
cleanup() {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
for (const [ip, record] of this.records.entries()) {
|
|
79
|
+
if (now > record.resetAt) {
|
|
80
|
+
this.records.delete(ip);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,WAAW;IACd,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpC,WAAW,CAAS;IACpB,QAAQ,CAAS;IAElC,YAAY,WAAmB,EAAE,QAAgB;QAC/C,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,EAAU;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAElC,6BAA6B;QAC7B,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACxB,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;QAED,kCAAkC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG;gBACP,KAAK,EAAE,CAAC;gBACR,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ;aAC7B,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC;QAED,sBAAsB;QACtB,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,CAAC;gBACZ,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,KAAK;YAC1C,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YAClD,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;gBACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DatabaseAdapter } from '../database/types.js';
|
|
2
2
|
export type Sector = 'financial' | 'healthcare' | 'energy' | 'transport' | 'digital_infrastructure' | 'public_administration' | 'manufacturing' | 'other';
|
|
3
3
|
export interface ApplicabilityInput {
|
|
4
4
|
sector: Sector;
|
|
@@ -36,5 +36,5 @@ export interface ApplicabilityResult {
|
|
|
36
36
|
next_steps?: string;
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
export declare function checkApplicability(db:
|
|
39
|
+
export declare function checkApplicability(db: DatabaseAdapter, input: ApplicabilityInput): Promise<ApplicabilityResult>;
|
|
40
40
|
//# sourceMappingURL=applicability.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applicability.d.ts","sourceRoot":"","sources":["../../src/tools/applicability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"applicability.d.ts","sourceRoot":"","sources":["../../src/tools/applicability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,MAAM,GACd,WAAW,GACX,YAAY,GACZ,QAAQ,GACR,WAAW,GACX,wBAAwB,GACxB,uBAAuB,GACvB,eAAe,GACf,OAAO,CAAC;AAEZ,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,GAAG,cAAc,GAAG,MAAM,CAAC;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC/C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC/C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,sBAAsB,EAAE,oBAAoB,EAAE,CAAC;IAC/C,OAAO,CAAC,EAAE;QACR,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;YACf,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CA+G9B"}
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
export async function checkApplicability(db, input) {
|
|
2
2
|
const { sector, subsector, detail_level = 'full' } = input;
|
|
3
3
|
// Query for matching rules - check both sector match and subsector match
|
|
4
|
+
// Note: We handle deduplication in JavaScript, so no need for DISTINCT ON
|
|
4
5
|
let sql = `
|
|
5
|
-
SELECT
|
|
6
|
+
SELECT
|
|
6
7
|
regulation,
|
|
7
8
|
confidence,
|
|
8
9
|
basis_article as basis,
|
|
9
|
-
notes
|
|
10
|
-
FROM applicability_rules
|
|
11
|
-
WHERE applies = 1
|
|
12
|
-
AND (
|
|
13
|
-
(sector = ? AND (subsector IS NULL OR subsector = ?))
|
|
14
|
-
OR (sector = ? AND subsector IS NULL)
|
|
15
|
-
)
|
|
16
|
-
ORDER BY
|
|
10
|
+
notes,
|
|
17
11
|
CASE confidence
|
|
18
12
|
WHEN 'definite' THEN 1
|
|
19
13
|
WHEN 'likely' THEN 2
|
|
20
14
|
WHEN 'possible' THEN 3
|
|
21
|
-
END
|
|
22
|
-
|
|
15
|
+
END as conf_order
|
|
16
|
+
FROM applicability_rules
|
|
17
|
+
WHERE applies = true
|
|
18
|
+
AND (
|
|
19
|
+
(sector = $1 AND (subsector IS NULL OR subsector = $2))
|
|
20
|
+
OR (sector = $3 AND subsector IS NULL)
|
|
21
|
+
)
|
|
22
|
+
ORDER BY regulation, conf_order
|
|
23
23
|
`;
|
|
24
|
-
const
|
|
24
|
+
const result = await db.query(sql, [sector, subsector || '', sector]);
|
|
25
|
+
const rows = result.rows;
|
|
25
26
|
// Deduplicate by regulation, keeping highest confidence
|
|
26
27
|
const regulationMap = new Map();
|
|
27
28
|
for (const row of rows) {
|
|
@@ -40,12 +41,11 @@ export async function checkApplicability(db, input) {
|
|
|
40
41
|
if (detail_level === 'summary') {
|
|
41
42
|
// Get regulation metadata for summary
|
|
42
43
|
const regIds = applicable_regulations.map(r => r.regulation);
|
|
43
|
-
const placeholders = regIds.map(() =>
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
`).all(...regIds);
|
|
44
|
+
const placeholders = regIds.map((_, i) => `$${i + 1}`).join(', ');
|
|
45
|
+
const regDataResult = await db.query(`SELECT id, full_name, effective_date
|
|
46
|
+
FROM regulations
|
|
47
|
+
WHERE id IN (${placeholders})`, regIds);
|
|
48
|
+
const regData = regDataResult.rows;
|
|
49
49
|
const regMetadata = new Map(regData.map(r => [r.id, r]));
|
|
50
50
|
// Priority deadlines for key regulations
|
|
51
51
|
const priorityDeadlines = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applicability.js","sourceRoot":"","sources":["../../src/tools/applicability.ts"],"names":[],"mappings":"AAoDA,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,
|
|
1
|
+
{"version":3,"file":"applicability.js","sourceRoot":"","sources":["../../src/tools/applicability.ts"],"names":[],"mappings":"AAoDA,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,EAAmB,EACnB,KAAyB;IAEzB,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC;IAE3D,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,GAAG,GAAG;;;;;;;;;;;;;;;;;;GAkBT,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtE,MAAM,IAAI,GAAG,MAAM,CAAC,IAKlB,CAAC;IAEH,wDAAwD;IACxD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAgC,CAAC;IAC9D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE;gBAChC,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,KAAK,EAAE,GAAG,CAAC,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;IAElE,yDAAyD;IACzD,IAAI,OAAO,CAAC;IACZ,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,sCAAsC;QACtC,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC7D,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,KAAK,CAClC;;sBAEgB,YAAY,GAAG,EAC/B,MAAM,CACP,CAAC;QAEF,MAAM,OAAO,GAAG,aAAa,CAAC,IAI5B,CAAC;QAEH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEzD,yCAAyC;QACzC,MAAM,iBAAiB,GAA2B;YAChD,MAAM,EAAE,uBAAuB;YAC/B,MAAM,EAAE,uCAAuC;YAC/C,QAAQ,EAAE,iCAAiC;YAC3C,QAAQ,EAAE,+BAA+B;YACzC,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,+BAA+B;SACzC,CAAC;QAEF,MAAM,mBAAmB,GAAwB,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAChF,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACjD,OAAO;gBACL,EAAE,EAAE,GAAG,CAAC,UAAU;gBAClB,SAAS,EAAE,QAAQ,EAAE,SAAS,IAAI,GAAG,CAAC,UAAU;gBAChD,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,iBAAiB,EAAE,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC;aACrD,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG;YACpB,QAAQ,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,MAAM;YAChF,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,MAAM;YAC5E,QAAQ,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,MAAM;SACjF,CAAC;QAEF,OAAO,GAAG;YACR,WAAW,EAAE,sBAAsB,CAAC,MAAM;YAC1C,aAAa;YACb,mBAAmB;YACnB,UAAU,EAAE,qHAAqH;SAClI,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,KAAK;QACb,sBAAsB;QACtB,OAAO;KACR,CAAC;AACJ,CAAC"}
|
package/dist/tools/article.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DatabaseAdapter } from '../database/types.js';
|
|
2
2
|
export interface GetArticleInput {
|
|
3
3
|
regulation: string;
|
|
4
4
|
article: string;
|
|
@@ -16,5 +16,5 @@ export interface Article {
|
|
|
16
16
|
original_length?: number;
|
|
17
17
|
token_estimate?: number;
|
|
18
18
|
}
|
|
19
|
-
export declare function getArticle(db:
|
|
19
|
+
export declare function getArticle(db: DatabaseAdapter, input: GetArticleInput): Promise<Article | null>;
|
|
20
20
|
//# sourceMappingURL=article.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/tools/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/tools/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAwDzB"}
|
package/dist/tools/article.js
CHANGED
|
@@ -10,12 +10,13 @@ export async function getArticle(db, input) {
|
|
|
10
10
|
recitals,
|
|
11
11
|
cross_references
|
|
12
12
|
FROM articles
|
|
13
|
-
WHERE regulation =
|
|
13
|
+
WHERE regulation = $1 AND article_number = $2
|
|
14
14
|
`;
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
15
|
+
const result = await db.query(sql, [regulation, article]);
|
|
16
|
+
if (result.rows.length === 0) {
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
+
const row = result.rows[0];
|
|
19
20
|
// Token management: Truncate very large articles to prevent context overflow
|
|
20
21
|
const MAX_CHARS = 50000; // ~12,500 tokens (safe for 200k context window)
|
|
21
22
|
const originalLength = row.text.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/tools/article.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,
|
|
1
|
+
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/tools/article.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAmB,EACnB,KAAsB;IAEtB,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAEtC,MAAM,GAAG,GAAG;;;;;;;;;;;GAWX,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1D,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAQxB,CAAC;IAEF,6EAA6E;IAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,gDAAgD;IACzE,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;IACvC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,qBAAqB;IAC1E,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACpB,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,cAAc,GAAG,SAAS,EAAE,CAAC;QAC/B,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,sDAAsD,GAAG,cAAc,GAAG,WAAW,GAAG,aAAa,GAAG,8DAA8D,CAAC;QACjN,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,OAAO;QACL,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI;QACJ,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;QACxD,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI;QAChF,SAAS;QACT,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QACvD,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;KACtD,CAAC;AACJ,CAAC"}
|