@ansvar/eu-regulations-mcp 0.8.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -29
- package/data/regulations.db +0 -0
- package/data/seed/applicability/chips-act.json +67 -0
- package/data/seed/applicability/crma.json +85 -0
- package/data/seed/chips-act.json +714 -0
- package/data/seed/crma.json +877 -0
- package/data/seed/mappings/iso27001-chips-act.json +50 -0
- package/data/seed/mappings/iso27001-crma.json +50 -0
- package/data/seed/mappings/nist-csf-chips-act.json +56 -0
- package/data/seed/mappings/nist-csf-crma.json +56 -0
- package/dist/database/sqlite-adapter.d.ts +2 -2
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.js.map +1 -1
- package/dist/http-server.js +27 -5
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/about.d.ts +40 -0
- package/dist/tools/about.d.ts.map +1 -0
- package/dist/tools/about.js +61 -0
- package/dist/tools/about.js.map +1 -0
- package/dist/tools/list.d.ts +7 -0
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +73 -8
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/registry.d.ts +11 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +56 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -5
- package/dist/worker.js.map +1 -1
- package/package.json +8 -7
- package/scripts/add-cross-references.sql +0 -200
- package/scripts/analyze-survey-responses.ts +0 -285
- package/scripts/build-db.ts +0 -421
- package/scripts/bulk-reingest-all.ts +0 -331
- package/scripts/check-updates.ts +0 -294
- package/scripts/extract-eprivacy-recitals.ts +0 -98
- package/scripts/ingest-eurlex-browser.ts +0 -113
- package/scripts/ingest-eurlex.ts +0 -346
- package/scripts/ingest-unece.ts +0 -382
- package/scripts/migrate-postgres.ts +0 -445
- package/scripts/migrate-to-postgres.ts +0 -353
- package/scripts/reingest-all-with-recitals.sh +0 -81
- package/scripts/sync-versions.ts +0 -206
- package/scripts/test-cross-refs.js +0 -26
- package/scripts/test-postgres-adapter.ts +0 -146
- package/scripts/update-dora-rts-metadata.ts +0 -112
- package/src/database/postgres-adapter.ts +0 -84
- package/src/database/sqlite-adapter.ts +0 -44
- package/src/database/types.ts +0 -10
- package/src/http-server.ts +0 -149
- package/src/index.ts +0 -61
- package/src/middleware/rate-limit.ts +0 -104
- package/src/tools/applicability.ts +0 -167
- package/src/tools/article.ts +0 -81
- package/src/tools/compare.ts +0 -217
- package/src/tools/definitions.ts +0 -49
- package/src/tools/evidence.ts +0 -84
- package/src/tools/list.ts +0 -124
- package/src/tools/map.ts +0 -86
- package/src/tools/recital.ts +0 -60
- package/src/tools/registry.ts +0 -311
- package/src/tools/search.ts +0 -297
- package/src/worker.ts +0 -708
package/src/tools/list.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../database/types.js';
|
|
2
|
-
|
|
3
|
-
export interface ListInput {
|
|
4
|
-
regulation?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface Chapter {
|
|
8
|
-
number: string;
|
|
9
|
-
title: string;
|
|
10
|
-
articles: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface RegulationInfo {
|
|
14
|
-
id: string;
|
|
15
|
-
full_name: string;
|
|
16
|
-
celex_id: string;
|
|
17
|
-
effective_date: string | null;
|
|
18
|
-
article_count: number;
|
|
19
|
-
chapters?: Chapter[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ListResult {
|
|
23
|
-
regulations: RegulationInfo[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function listRegulations(
|
|
27
|
-
db: DatabaseAdapter,
|
|
28
|
-
input: ListInput
|
|
29
|
-
): Promise<ListResult> {
|
|
30
|
-
const { regulation } = input;
|
|
31
|
-
|
|
32
|
-
if (regulation) {
|
|
33
|
-
// Get specific regulation with chapters
|
|
34
|
-
const regResult = await db.query(
|
|
35
|
-
`SELECT id, full_name, celex_id, effective_date
|
|
36
|
-
FROM regulations
|
|
37
|
-
WHERE id = $1`,
|
|
38
|
-
[regulation]
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
if (regResult.rows.length === 0) {
|
|
42
|
-
return { regulations: [] };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const regRow = regResult.rows[0] as {
|
|
46
|
-
id: string;
|
|
47
|
-
full_name: string;
|
|
48
|
-
celex_id: string;
|
|
49
|
-
effective_date: string | null;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Get articles grouped by chapter
|
|
53
|
-
const articlesResult = await db.query(
|
|
54
|
-
`SELECT article_number, title, chapter
|
|
55
|
-
FROM articles
|
|
56
|
-
WHERE regulation = $1
|
|
57
|
-
ORDER BY article_number::INTEGER`,
|
|
58
|
-
[regulation]
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const articles = articlesResult.rows as Array<{
|
|
62
|
-
article_number: string;
|
|
63
|
-
title: string | null;
|
|
64
|
-
chapter: string | null;
|
|
65
|
-
}>;
|
|
66
|
-
|
|
67
|
-
// Group by chapter
|
|
68
|
-
const chapterMap = new Map<string, Chapter>();
|
|
69
|
-
for (const article of articles) {
|
|
70
|
-
const chapterKey = article.chapter || 'General';
|
|
71
|
-
if (!chapterMap.has(chapterKey)) {
|
|
72
|
-
chapterMap.set(chapterKey, {
|
|
73
|
-
number: chapterKey,
|
|
74
|
-
title: `Chapter ${chapterKey}`,
|
|
75
|
-
articles: [],
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
chapterMap.get(chapterKey)!.articles.push(article.article_number);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
regulations: [{
|
|
83
|
-
id: regRow.id,
|
|
84
|
-
full_name: regRow.full_name,
|
|
85
|
-
celex_id: regRow.celex_id,
|
|
86
|
-
effective_date: regRow.effective_date,
|
|
87
|
-
article_count: articles.length,
|
|
88
|
-
chapters: Array.from(chapterMap.values()),
|
|
89
|
-
}],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// List all regulations with article counts
|
|
94
|
-
const result = await db.query(
|
|
95
|
-
`SELECT
|
|
96
|
-
r.id,
|
|
97
|
-
r.full_name,
|
|
98
|
-
r.celex_id,
|
|
99
|
-
r.effective_date,
|
|
100
|
-
COUNT(a.regulation) as article_count
|
|
101
|
-
FROM regulations r
|
|
102
|
-
LEFT JOIN articles a ON a.regulation = r.id
|
|
103
|
-
GROUP BY r.id, r.full_name, r.celex_id, r.effective_date
|
|
104
|
-
ORDER BY r.id`
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const rows = result.rows as Array<{
|
|
108
|
-
id: string;
|
|
109
|
-
full_name: string;
|
|
110
|
-
celex_id: string;
|
|
111
|
-
effective_date: string | null;
|
|
112
|
-
article_count: number;
|
|
113
|
-
}>;
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
regulations: rows.map(row => ({
|
|
117
|
-
id: row.id,
|
|
118
|
-
full_name: row.full_name,
|
|
119
|
-
celex_id: row.celex_id,
|
|
120
|
-
effective_date: row.effective_date,
|
|
121
|
-
article_count: Number(row.article_count),
|
|
122
|
-
})),
|
|
123
|
-
};
|
|
124
|
-
}
|
package/src/tools/map.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../database/types.js';
|
|
2
|
-
|
|
3
|
-
export interface MapControlsInput {
|
|
4
|
-
framework: 'ISO27001' | 'NIST_CSF';
|
|
5
|
-
control?: string;
|
|
6
|
-
regulation?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ControlMappingEntry {
|
|
10
|
-
regulation: string;
|
|
11
|
-
articles: string[];
|
|
12
|
-
coverage: 'full' | 'partial' | 'related';
|
|
13
|
-
notes: string | null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ControlMapping {
|
|
17
|
-
control_id: string;
|
|
18
|
-
control_name: string;
|
|
19
|
-
mappings: ControlMappingEntry[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function mapControls(
|
|
23
|
-
db: DatabaseAdapter,
|
|
24
|
-
input: MapControlsInput
|
|
25
|
-
): Promise<ControlMapping[]> {
|
|
26
|
-
const { framework, control, regulation } = input;
|
|
27
|
-
|
|
28
|
-
let sql = `
|
|
29
|
-
SELECT
|
|
30
|
-
control_id,
|
|
31
|
-
control_name,
|
|
32
|
-
regulation,
|
|
33
|
-
articles,
|
|
34
|
-
coverage,
|
|
35
|
-
notes
|
|
36
|
-
FROM control_mappings
|
|
37
|
-
WHERE framework = $1
|
|
38
|
-
`;
|
|
39
|
-
|
|
40
|
-
const params: string[] = [framework];
|
|
41
|
-
|
|
42
|
-
if (control) {
|
|
43
|
-
sql += ` AND control_id = $${params.length + 1}`;
|
|
44
|
-
params.push(control);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (regulation) {
|
|
48
|
-
sql += ` AND regulation = $${params.length + 1}`;
|
|
49
|
-
params.push(regulation);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
sql += ` ORDER BY control_id, regulation`;
|
|
53
|
-
|
|
54
|
-
const result = await db.query(sql, params);
|
|
55
|
-
|
|
56
|
-
const rows = result.rows as Array<{
|
|
57
|
-
control_id: string;
|
|
58
|
-
control_name: string;
|
|
59
|
-
regulation: string;
|
|
60
|
-
articles: string;
|
|
61
|
-
coverage: 'full' | 'partial' | 'related';
|
|
62
|
-
notes: string | null;
|
|
63
|
-
}>;
|
|
64
|
-
|
|
65
|
-
// Group by control_id
|
|
66
|
-
const controlMap = new Map<string, ControlMapping>();
|
|
67
|
-
|
|
68
|
-
for (const row of rows) {
|
|
69
|
-
if (!controlMap.has(row.control_id)) {
|
|
70
|
-
controlMap.set(row.control_id, {
|
|
71
|
-
control_id: row.control_id,
|
|
72
|
-
control_name: row.control_name,
|
|
73
|
-
mappings: [],
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
controlMap.get(row.control_id)!.mappings.push({
|
|
78
|
-
regulation: row.regulation,
|
|
79
|
-
articles: JSON.parse(row.articles),
|
|
80
|
-
coverage: row.coverage,
|
|
81
|
-
notes: row.notes,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return Array.from(controlMap.values());
|
|
86
|
-
}
|
package/src/tools/recital.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../database/types.js';
|
|
2
|
-
|
|
3
|
-
export interface GetRecitalInput {
|
|
4
|
-
regulation: string;
|
|
5
|
-
recital_number: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface Recital {
|
|
9
|
-
regulation: string;
|
|
10
|
-
recital_number: number;
|
|
11
|
-
text: string;
|
|
12
|
-
related_articles: string[] | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function getRecital(
|
|
16
|
-
db: DatabaseAdapter,
|
|
17
|
-
input: GetRecitalInput
|
|
18
|
-
): Promise<Recital | null> {
|
|
19
|
-
const { regulation, recital_number } = input;
|
|
20
|
-
|
|
21
|
-
// Validate recital_number is a safe integer
|
|
22
|
-
if (!Number.isInteger(recital_number) || !Number.isFinite(recital_number)) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Reject negative or unrealistic recital numbers
|
|
27
|
-
if (recital_number < 1 || recital_number > 10000) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const sql = `
|
|
32
|
-
SELECT
|
|
33
|
-
regulation,
|
|
34
|
-
recital_number,
|
|
35
|
-
text,
|
|
36
|
-
related_articles
|
|
37
|
-
FROM recitals
|
|
38
|
-
WHERE regulation = $1 AND recital_number = $2
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
const result = await db.query(sql, [regulation, recital_number]);
|
|
42
|
-
|
|
43
|
-
if (result.rows.length === 0) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const row = result.rows[0] as {
|
|
48
|
-
regulation: string;
|
|
49
|
-
recital_number: number;
|
|
50
|
-
text: string;
|
|
51
|
-
related_articles: string | null;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
regulation: row.regulation,
|
|
56
|
-
recital_number: row.recital_number,
|
|
57
|
-
text: row.text,
|
|
58
|
-
related_articles: row.related_articles ? JSON.parse(row.related_articles) : null,
|
|
59
|
-
};
|
|
60
|
-
}
|
package/src/tools/registry.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import {
|
|
3
|
-
CallToolRequestSchema,
|
|
4
|
-
ListToolsRequestSchema,
|
|
5
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
-
import type { DatabaseAdapter } from '../database/types.js';
|
|
7
|
-
|
|
8
|
-
import { searchRegulations, type SearchInput } from './search.js';
|
|
9
|
-
import { getArticle, type GetArticleInput } from './article.js';
|
|
10
|
-
import { getRecital, type GetRecitalInput } from './recital.js';
|
|
11
|
-
import { listRegulations, type ListInput } from './list.js';
|
|
12
|
-
import { compareRequirements, type CompareInput } from './compare.js';
|
|
13
|
-
import { mapControls, type MapControlsInput } from './map.js';
|
|
14
|
-
import { checkApplicability, type ApplicabilityInput } from './applicability.js';
|
|
15
|
-
import { getDefinitions, type DefinitionsInput } from './definitions.js';
|
|
16
|
-
import { getEvidenceRequirements, type EvidenceInput } from './evidence.js';
|
|
17
|
-
|
|
18
|
-
interface ToolDefinition {
|
|
19
|
-
name: string;
|
|
20
|
-
description: string;
|
|
21
|
-
inputSchema: any;
|
|
22
|
-
handler: (db: DatabaseAdapter, args: any) => Promise<any>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Centralized registry of all MCP tools.
|
|
27
|
-
* Single source of truth for both stdio and HTTP servers.
|
|
28
|
-
*/
|
|
29
|
-
export const TOOLS: ToolDefinition[] = [
|
|
30
|
-
{
|
|
31
|
-
name: 'search_regulations',
|
|
32
|
-
description: 'Search across all EU regulations for articles matching a query. Returns relevant articles with snippets highlighting matches. Token-efficient: returns 32-token snippets per match (safe for context).',
|
|
33
|
-
inputSchema: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
properties: {
|
|
36
|
-
query: {
|
|
37
|
-
type: 'string',
|
|
38
|
-
description: 'Search query (e.g., "incident reporting", "personal data breach")',
|
|
39
|
-
},
|
|
40
|
-
regulations: {
|
|
41
|
-
type: 'array',
|
|
42
|
-
items: { type: 'string' },
|
|
43
|
-
description: 'Optional: filter to specific regulations (e.g., ["GDPR", "NIS2"])',
|
|
44
|
-
},
|
|
45
|
-
limit: {
|
|
46
|
-
type: 'number',
|
|
47
|
-
description: 'Maximum results to return (default: 10)',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
required: ['query'],
|
|
51
|
-
},
|
|
52
|
-
handler: async (db, args) => {
|
|
53
|
-
const input = args as unknown as SearchInput;
|
|
54
|
-
return await searchRegulations(db, input);
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'get_article',
|
|
59
|
-
description: 'Retrieve the full text of a specific article from a regulation. WARNING: Token usage varies (500-70,000 tokens per article). Large articles are automatically truncated at 50,000 characters (~12,500 tokens) with a notice. Use search_regulations first to find relevant articles.',
|
|
60
|
-
inputSchema: {
|
|
61
|
-
type: 'object',
|
|
62
|
-
properties: {
|
|
63
|
-
regulation: {
|
|
64
|
-
type: 'string',
|
|
65
|
-
description: 'Regulation ID (e.g., "GDPR", "NIS2", "DORA")',
|
|
66
|
-
},
|
|
67
|
-
article: {
|
|
68
|
-
type: 'string',
|
|
69
|
-
description: 'Article number (e.g., "17", "23")',
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
required: ['regulation', 'article'],
|
|
73
|
-
},
|
|
74
|
-
handler: async (db, args) => {
|
|
75
|
-
const input = args as unknown as GetArticleInput;
|
|
76
|
-
const article = await getArticle(db, input);
|
|
77
|
-
if (!article) {
|
|
78
|
-
throw new Error(`Article ${input.article} not found in ${input.regulation}`);
|
|
79
|
-
}
|
|
80
|
-
return article;
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: 'get_recital',
|
|
85
|
-
description: 'Retrieve the full text of a specific recital from a regulation. Recitals provide context and interpretation guidance for articles.',
|
|
86
|
-
inputSchema: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
regulation: {
|
|
90
|
-
type: 'string',
|
|
91
|
-
description: 'Regulation ID (e.g., "GDPR", "NIS2", "DORA")',
|
|
92
|
-
},
|
|
93
|
-
recital_number: {
|
|
94
|
-
type: 'number',
|
|
95
|
-
description: 'Recital number (e.g., 1, 83)',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
required: ['regulation', 'recital_number'],
|
|
99
|
-
},
|
|
100
|
-
handler: async (db, args) => {
|
|
101
|
-
const input = args as unknown as GetRecitalInput;
|
|
102
|
-
const recital = await getRecital(db, input);
|
|
103
|
-
if (!recital) {
|
|
104
|
-
throw new Error(`Recital ${input.recital_number} not found in ${input.regulation}`);
|
|
105
|
-
}
|
|
106
|
-
return recital;
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'list_regulations',
|
|
111
|
-
description: 'List available regulations and their structure. Without parameters, lists all regulations. With a regulation specified, shows chapters and articles.',
|
|
112
|
-
inputSchema: {
|
|
113
|
-
type: 'object',
|
|
114
|
-
properties: {
|
|
115
|
-
regulation: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
description: 'Optional: specific regulation to get detailed structure for',
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
handler: async (db, args) => {
|
|
122
|
-
const input = (args ?? {}) as unknown as ListInput;
|
|
123
|
-
return await listRegulations(db, input);
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'compare_requirements',
|
|
128
|
-
description: 'Search and compare articles across multiple regulations on a specific topic. Returns matching articles from each regulation with text snippets showing how they address the topic. Uses full-text search with relevance ranking to find related requirements.',
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: 'object',
|
|
131
|
-
properties: {
|
|
132
|
-
topic: {
|
|
133
|
-
type: 'string',
|
|
134
|
-
description: 'Topic to compare (e.g., "incident reporting", "risk assessment")',
|
|
135
|
-
},
|
|
136
|
-
regulations: {
|
|
137
|
-
type: 'array',
|
|
138
|
-
items: { type: 'string' },
|
|
139
|
-
description: 'Regulations to compare (e.g., ["DORA", "NIS2"])',
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
required: ['topic', 'regulations'],
|
|
143
|
-
},
|
|
144
|
-
handler: async (db, args) => {
|
|
145
|
-
const input = args as unknown as CompareInput;
|
|
146
|
-
return await compareRequirements(db, input);
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: 'map_controls',
|
|
151
|
-
description: 'Map security framework controls to EU regulation requirements. Shows which articles satisfy specific security controls.',
|
|
152
|
-
inputSchema: {
|
|
153
|
-
type: 'object',
|
|
154
|
-
properties: {
|
|
155
|
-
framework: {
|
|
156
|
-
type: 'string',
|
|
157
|
-
enum: ['ISO27001', 'NIST_CSF'],
|
|
158
|
-
description: 'Control framework: ISO27001 (ISO 27001:2022) or NIST_CSF (NIST Cybersecurity Framework)',
|
|
159
|
-
},
|
|
160
|
-
control: {
|
|
161
|
-
type: 'string',
|
|
162
|
-
description: 'Optional: specific control ID (e.g., "A.5.1" for ISO27001, "PR.AC-1" for NIST CSF)',
|
|
163
|
-
},
|
|
164
|
-
regulation: {
|
|
165
|
-
type: 'string',
|
|
166
|
-
description: 'Optional: filter mappings to specific regulation',
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
required: ['framework'],
|
|
170
|
-
},
|
|
171
|
-
handler: async (db, args) => {
|
|
172
|
-
const input = args as unknown as MapControlsInput;
|
|
173
|
-
return await mapControls(db, input);
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: 'check_applicability',
|
|
178
|
-
description: 'Determine which EU regulations apply to an organization based on sector and characteristics. Supports tiered detail levels for optimal response length.',
|
|
179
|
-
inputSchema: {
|
|
180
|
-
type: 'object',
|
|
181
|
-
properties: {
|
|
182
|
-
sector: {
|
|
183
|
-
type: 'string',
|
|
184
|
-
enum: ['financial', 'healthcare', 'energy', 'transport', 'digital_infrastructure', 'public_administration', 'manufacturing', 'other'],
|
|
185
|
-
description: 'Organization sector',
|
|
186
|
-
},
|
|
187
|
-
subsector: {
|
|
188
|
-
type: 'string',
|
|
189
|
-
description: 'Optional: more specific subsector (e.g., "bank", "insurance" for financial)',
|
|
190
|
-
},
|
|
191
|
-
member_state: {
|
|
192
|
-
type: 'string',
|
|
193
|
-
description: 'Optional: EU member state (ISO country code)',
|
|
194
|
-
},
|
|
195
|
-
size: {
|
|
196
|
-
type: 'string',
|
|
197
|
-
enum: ['sme', 'large'],
|
|
198
|
-
description: 'Optional: organization size',
|
|
199
|
-
},
|
|
200
|
-
detail_level: {
|
|
201
|
-
type: 'string',
|
|
202
|
-
enum: ['summary', 'requirements', 'full'],
|
|
203
|
-
description: 'Optional: level of detail (summary=executive overview only, requirements=include key requirements, full=complete details with basis articles). Default: full',
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
required: ['sector'],
|
|
207
|
-
},
|
|
208
|
-
handler: async (db, args) => {
|
|
209
|
-
const input = args as unknown as ApplicabilityInput;
|
|
210
|
-
return await checkApplicability(db, input);
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
name: 'get_definitions',
|
|
215
|
-
description: 'Look up official definitions of terms from EU regulations. Terms are defined in each regulation\'s definitions article.',
|
|
216
|
-
inputSchema: {
|
|
217
|
-
type: 'object',
|
|
218
|
-
properties: {
|
|
219
|
-
term: {
|
|
220
|
-
type: 'string',
|
|
221
|
-
description: 'Term to look up (e.g., "personal data", "incident", "processing")',
|
|
222
|
-
},
|
|
223
|
-
regulation: {
|
|
224
|
-
type: 'string',
|
|
225
|
-
description: 'Optional: filter to specific regulation',
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
required: ['term'],
|
|
229
|
-
},
|
|
230
|
-
handler: async (db, args) => {
|
|
231
|
-
const input = args as unknown as DefinitionsInput;
|
|
232
|
-
return await getDefinitions(db, input);
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
name: 'get_evidence_requirements',
|
|
237
|
-
description: 'Get compliance evidence and audit artifacts required for specific regulation requirements. Shows what documents, logs, and test results auditors will ask for, including retention periods and maturity levels.',
|
|
238
|
-
inputSchema: {
|
|
239
|
-
type: 'object',
|
|
240
|
-
properties: {
|
|
241
|
-
regulation: {
|
|
242
|
-
type: 'string',
|
|
243
|
-
description: 'Optional: filter to specific regulation (e.g., "DORA", "GDPR")',
|
|
244
|
-
},
|
|
245
|
-
article: {
|
|
246
|
-
type: 'string',
|
|
247
|
-
description: 'Optional: filter to specific article (e.g., "6", "32")',
|
|
248
|
-
},
|
|
249
|
-
evidence_type: {
|
|
250
|
-
type: 'string',
|
|
251
|
-
enum: ['document', 'log', 'test_result', 'certification', 'policy', 'procedure'],
|
|
252
|
-
description: 'Optional: filter by evidence type',
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
handler: async (db, args) => {
|
|
257
|
-
const input = args as unknown as EvidenceInput;
|
|
258
|
-
return await getEvidenceRequirements(db, input);
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
];
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Register all tools with an MCP server instance.
|
|
265
|
-
* Use this for both stdio and HTTP servers to ensure parity.
|
|
266
|
-
*/
|
|
267
|
-
export function registerTools(server: Server, db: DatabaseAdapter): void {
|
|
268
|
-
// List available tools
|
|
269
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
270
|
-
tools: TOOLS.map(tool => ({
|
|
271
|
-
name: tool.name,
|
|
272
|
-
description: tool.description,
|
|
273
|
-
inputSchema: tool.inputSchema,
|
|
274
|
-
})),
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
// Handle tool calls
|
|
278
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
279
|
-
const { name, arguments: args } = request.params;
|
|
280
|
-
const tool = TOOLS.find(t => t.name === name);
|
|
281
|
-
|
|
282
|
-
if (!tool) {
|
|
283
|
-
return {
|
|
284
|
-
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
285
|
-
isError: true,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const result = await tool.handler(db, args || {});
|
|
291
|
-
return {
|
|
292
|
-
content: [
|
|
293
|
-
{
|
|
294
|
-
type: 'text',
|
|
295
|
-
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
};
|
|
299
|
-
} catch (error) {
|
|
300
|
-
return {
|
|
301
|
-
content: [
|
|
302
|
-
{
|
|
303
|
-
type: 'text',
|
|
304
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
305
|
-
},
|
|
306
|
-
],
|
|
307
|
-
isError: true,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
}
|