@cyanheads/eur-lex-mcp-server 0.1.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.
- package/AGENTS.md +383 -0
- package/CLAUDE.md +383 -0
- package/Dockerfile +99 -0
- package/LICENSE +201 -0
- package/README.md +343 -0
- package/changelog/0.1.x/0.1.1.md +31 -0
- package/changelog/template.md +127 -0
- package/dist/config/server-config.d.ts +16 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +43 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/eurlex-comparative-analysis.prompt.d.ts +10 -0
- package/dist/mcp-server/prompts/definitions/eurlex-comparative-analysis.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/eurlex-comparative-analysis.prompt.js +60 -0
- package/dist/mcp-server/prompts/definitions/eurlex-comparative-analysis.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts +9 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.js +7 -0
- package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/eurlex-document-relations.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/eurlex-document-relations.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/eurlex-document-relations.resource.js +91 -0
- package/dist/mcp-server/resources/definitions/eurlex-document-relations.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/eurlex-document.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/eurlex-document.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/eurlex-document.resource.js +61 -0
- package/dist/mcp-server/resources/definitions/eurlex-document.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +8 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +11 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-browse-subjects.tool.d.ts +25 -0
- package/dist/mcp-server/tools/definitions/eurlex-browse-subjects.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-browse-subjects.tool.js +121 -0
- package/dist/mcp-server/tools/definitions/eurlex-browse-subjects.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.d.ts +44 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.js +189 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.js +222 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-relations.tool.d.ts +38 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-relations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-relations.tool.js +203 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-relations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-lookup-celex.tool.d.ts +32 -0
- package/dist/mcp-server/tools/definitions/eurlex-lookup-celex.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-lookup-celex.tool.js +152 -0
- package/dist/mcp-server/tools/definitions/eurlex-lookup-celex.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-query-sparql.tool.d.ts +25 -0
- package/dist/mcp-server/tools/definitions/eurlex-query-sparql.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-query-sparql.tool.js +104 -0
- package/dist/mcp-server/tools/definitions/eurlex-query-sparql.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.d.ts +47 -0
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.js +203 -0
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +207 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +21 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/services/cellar-sparql/cellar-sparql-service.d.ts +27 -0
- package/dist/services/cellar-sparql/cellar-sparql-service.d.ts.map +1 -0
- package/dist/services/cellar-sparql/cellar-sparql-service.js +123 -0
- package/dist/services/cellar-sparql/cellar-sparql-service.js.map +1 -0
- package/dist/services/cellar-sparql/types.d.ts +62 -0
- package/dist/services/cellar-sparql/types.d.ts.map +1 -0
- package/dist/services/cellar-sparql/types.js +6 -0
- package/dist/services/cellar-sparql/types.js.map +1 -0
- package/dist/services/eurlex-content/eurlex-content-service.d.ts +45 -0
- package/dist/services/eurlex-content/eurlex-content-service.d.ts.map +1 -0
- package/dist/services/eurlex-content/eurlex-content-service.js +93 -0
- package/dist/services/eurlex-content/eurlex-content-service.js.map +1 -0
- package/package.json +105 -0
- package/server.json +161 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview eurlex_browse_subjects — Search the EuroVoc multilingual thesaurus.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurlex-browse-subjects
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { CellarSparqlService, getCellarSparqlService, } from '../../../services/cellar-sparql/cellar-sparql-service.js';
|
|
8
|
+
export const eurlex_browse_subjects = tool('eurlex_browse_subjects', {
|
|
9
|
+
title: 'Browse EuroVoc Subjects',
|
|
10
|
+
description: 'Search the EuroVoc multilingual thesaurus to resolve a human-readable term or keyword into EuroVoc concept IDs. ' +
|
|
11
|
+
'This tool is required before using the eurovoc_concept filter in eurlex_search_documents — ' +
|
|
12
|
+
'agents cannot guess numeric EuroVoc concept IDs. ' +
|
|
13
|
+
'Returns concept URI, preferred label in the requested language, concept code, and broader/narrower hierarchy hints. ' +
|
|
14
|
+
'EuroVoc covers all EU policy domains: agriculture, environment, finance, health, trade, transport, and more. ' +
|
|
15
|
+
'If no results are found in a non-English language, retry with language "en" and a broader English term.',
|
|
16
|
+
annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: true },
|
|
17
|
+
input: z.object({
|
|
18
|
+
keyword: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe('Search term to match against EuroVoc concept labels (e.g. "privacy", "agriculture", "trade").'),
|
|
22
|
+
language: z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^[a-z]{2,3}$/)
|
|
25
|
+
.default('en')
|
|
26
|
+
.describe('Language code for concept labels (e.g. "en", "fr", "de"). Defaults to English.'),
|
|
27
|
+
limit: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(50)
|
|
32
|
+
.default(20)
|
|
33
|
+
.describe('Maximum number of EuroVoc concepts to return (1–50). Defaults to 20.'),
|
|
34
|
+
}),
|
|
35
|
+
output: z.object({
|
|
36
|
+
concepts: z
|
|
37
|
+
.array(z
|
|
38
|
+
.object({
|
|
39
|
+
concept_uri: z
|
|
40
|
+
.string()
|
|
41
|
+
.describe('Full URI for the EuroVoc concept, usable in the eurovoc_concept filter.'),
|
|
42
|
+
pref_label: z
|
|
43
|
+
.string()
|
|
44
|
+
.describe('Preferred label for the concept in the requested language.'),
|
|
45
|
+
concept_code: z.string().optional().describe('Numeric EuroVoc concept code.'),
|
|
46
|
+
broader_label: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Preferred label of the broader (parent) concept, if available.'),
|
|
50
|
+
})
|
|
51
|
+
.describe('A single EuroVoc concept with its URI, label, code, and hierarchy context.'))
|
|
52
|
+
.describe('Matching EuroVoc concepts ordered by relevance of the label match.'),
|
|
53
|
+
total: z.number().describe('Number of concepts returned in this response.'),
|
|
54
|
+
}),
|
|
55
|
+
errors: [
|
|
56
|
+
{
|
|
57
|
+
reason: 'no_concepts',
|
|
58
|
+
code: JsonRpcErrorCode.NotFound,
|
|
59
|
+
when: 'No EuroVoc concepts matched the keyword in the requested language.',
|
|
60
|
+
recovery: 'Try a broader or simpler term, or retry with language "en" for wider coverage.',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
async handler(input, ctx) {
|
|
64
|
+
const svc = getCellarSparqlService();
|
|
65
|
+
const keyword = input.keyword.toLowerCase().trim();
|
|
66
|
+
const lang = input.language.toLowerCase().trim() || 'en';
|
|
67
|
+
const limit = input.limit;
|
|
68
|
+
const sparql = `
|
|
69
|
+
SELECT ?concept ?label ?code ?broaderLabel WHERE {
|
|
70
|
+
?concept a skos:Concept .
|
|
71
|
+
?concept skos:prefLabel ?label .
|
|
72
|
+
OPTIONAL { ?concept skos:notation ?code . }
|
|
73
|
+
OPTIONAL {
|
|
74
|
+
?concept skos:broader ?broader .
|
|
75
|
+
?broader skos:prefLabel ?broaderLabel .
|
|
76
|
+
FILTER(LANG(?broaderLabel) = "${lang}")
|
|
77
|
+
}
|
|
78
|
+
FILTER(LANG(?label) = "${lang}")
|
|
79
|
+
FILTER(CONTAINS(LCASE(STR(?label)), "${keyword.replace(/"/g, '\\"')}"))
|
|
80
|
+
} LIMIT ${limit}`;
|
|
81
|
+
const bindings = await svc.query(sparql, ctx);
|
|
82
|
+
ctx.log.info('EuroVoc subject browse', {
|
|
83
|
+
keyword,
|
|
84
|
+
language: lang,
|
|
85
|
+
resultCount: bindings.length,
|
|
86
|
+
});
|
|
87
|
+
if (bindings.length === 0) {
|
|
88
|
+
throw ctx.fail('no_concepts', `No EuroVoc concepts found for "${input.keyword}" in language "${lang}"`, {
|
|
89
|
+
...ctx.recoveryFor('no_concepts'),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const concepts = bindings.map((b) => {
|
|
93
|
+
const entry = {
|
|
94
|
+
concept_uri: CellarSparqlService.bindingValue(b, 'concept') ?? '',
|
|
95
|
+
pref_label: CellarSparqlService.bindingValue(b, 'label') ?? '',
|
|
96
|
+
};
|
|
97
|
+
const code = CellarSparqlService.bindingValue(b, 'code');
|
|
98
|
+
if (code)
|
|
99
|
+
entry.concept_code = code;
|
|
100
|
+
const broaderLabel = CellarSparqlService.bindingValue(b, 'broaderLabel');
|
|
101
|
+
if (broaderLabel)
|
|
102
|
+
entry.broader_label = broaderLabel;
|
|
103
|
+
return entry;
|
|
104
|
+
});
|
|
105
|
+
return { concepts, total: concepts.length };
|
|
106
|
+
},
|
|
107
|
+
format: (result) => {
|
|
108
|
+
const lines = [`## EuroVoc Concepts (${result.total} found)\n`];
|
|
109
|
+
for (const c of result.concepts) {
|
|
110
|
+
lines.push(`### ${c.pref_label}`);
|
|
111
|
+
lines.push(`**URI:** ${c.concept_uri}`);
|
|
112
|
+
if (c.concept_code)
|
|
113
|
+
lines.push(`**Code:** ${c.concept_code}`);
|
|
114
|
+
if (c.broader_label)
|
|
115
|
+
lines.push(`**Broader:** ${c.broader_label}`);
|
|
116
|
+
lines.push('');
|
|
117
|
+
}
|
|
118
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=eurlex-browse-subjects.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-browse-subjects.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurlex-browse-subjects.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview eurlex_get_cases — Search CJEU and General Court case law.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurlex-get-cases
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const eurlex_get_cases: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
case_number: z.ZodOptional<z.ZodString>;
|
|
9
|
+
keyword: z.ZodOptional<z.ZodString>;
|
|
10
|
+
court: z.ZodOptional<z.ZodEnum<{
|
|
11
|
+
CJEU: "CJEU";
|
|
12
|
+
GC: "GC";
|
|
13
|
+
}>>;
|
|
14
|
+
case_type: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
judgment: "judgment";
|
|
16
|
+
order: "order";
|
|
17
|
+
ag_opinion: "ag_opinion";
|
|
18
|
+
}>>;
|
|
19
|
+
date_from: z.ZodOptional<z.ZodString>;
|
|
20
|
+
date_to: z.ZodOptional<z.ZodString>;
|
|
21
|
+
offset: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
24
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
25
|
+
work_uri: z.ZodString;
|
|
26
|
+
celex_number: z.ZodString;
|
|
27
|
+
resource_type: z.ZodOptional<z.ZodString>;
|
|
28
|
+
date: z.ZodOptional<z.ZodString>;
|
|
29
|
+
title: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
total: z.ZodNumber;
|
|
32
|
+
offset: z.ZodNumber;
|
|
33
|
+
}, z.core.$strip>, readonly [{
|
|
34
|
+
readonly reason: "no_results";
|
|
35
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
36
|
+
readonly when: "The query returned zero bindings — no matching cases in CELLAR sector 6.";
|
|
37
|
+
readonly recovery: "Try a different keyword, broader date range, or remove the court/case_type filter.";
|
|
38
|
+
}, {
|
|
39
|
+
readonly reason: "sparql_error";
|
|
40
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
41
|
+
readonly when: "Virtuoso returned HTTP 200 with an error body — query malformed or timed out.";
|
|
42
|
+
readonly recovery: "Simplify the query or reduce the date range and retry.";
|
|
43
|
+
}], undefined>;
|
|
44
|
+
//# sourceMappingURL=eurlex-get-cases.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-get-cases.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurlex-get-cases.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAajE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgN3B,CAAC"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview eurlex_get_cases — Search CJEU and General Court case law.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurlex-get-cases
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { CellarSparqlService, getCellarSparqlService, } from '../../../services/cellar-sparql/cellar-sparql-service.js';
|
|
8
|
+
/** Case type to CELEX type substring mapping. */
|
|
9
|
+
const CASE_TYPE_PATTERN = {
|
|
10
|
+
judgment: 'CJ',
|
|
11
|
+
order: 'CO',
|
|
12
|
+
ag_opinion: 'CC',
|
|
13
|
+
};
|
|
14
|
+
export const eurlex_get_cases = tool('eurlex_get_cases', {
|
|
15
|
+
title: 'Search CJEU/GC Case Law',
|
|
16
|
+
description: 'Search CJEU (Court of Justice of the EU) and General Court case law — judgments, orders, and Advocate General opinions. ' +
|
|
17
|
+
'Distinct from eurlex_search_documents because case law uses CELEX sector 6 and practitioners search it differently: ' +
|
|
18
|
+
'by case number, court, party name, or AG opinion type. ' +
|
|
19
|
+
'Keyword search applies to case titles and CELEX strings only — full-text body search is not available. ' +
|
|
20
|
+
'Case numbers follow the pattern C-{num}/{year} for CJEU and T-{num}/{year} for General Court. ' +
|
|
21
|
+
'Returns case identifier, court, date, document type, and title (where available). ' +
|
|
22
|
+
'Use eurlex_get_document with the CELEX number to fetch the full judgment text.',
|
|
23
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
24
|
+
input: z.object({
|
|
25
|
+
case_number: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Case number in standard format: C-{num}/{year} for CJEU or T-{num}/{year} for General Court (e.g. C-131/12).'),
|
|
29
|
+
keyword: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Keyword to match against case titles and CELEX strings.'),
|
|
33
|
+
court: z
|
|
34
|
+
.enum(['CJEU', 'GC'])
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Court filter: CJEU = Court of Justice of the EU, GC = General Court.'),
|
|
37
|
+
case_type: z
|
|
38
|
+
.enum(['judgment', 'order', 'ag_opinion'])
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Case type filter: judgment, order (procedural decision), or ag_opinion (Advocate General opinion).'),
|
|
41
|
+
date_from: z
|
|
42
|
+
.string()
|
|
43
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Start of date range in ISO 8601 format (YYYY-MM-DD).'),
|
|
46
|
+
date_to: z
|
|
47
|
+
.string()
|
|
48
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('End of date range in ISO 8601 format (YYYY-MM-DD).'),
|
|
51
|
+
offset: z
|
|
52
|
+
.number()
|
|
53
|
+
.int()
|
|
54
|
+
.min(0)
|
|
55
|
+
.default(0)
|
|
56
|
+
.describe('Pagination offset — number of results to skip. Defaults to 0.'),
|
|
57
|
+
limit: z
|
|
58
|
+
.number()
|
|
59
|
+
.int()
|
|
60
|
+
.min(1)
|
|
61
|
+
.max(100)
|
|
62
|
+
.default(20)
|
|
63
|
+
.describe('Maximum number of results to return (1–100). Defaults to 20.'),
|
|
64
|
+
}),
|
|
65
|
+
output: z.object({
|
|
66
|
+
cases: z
|
|
67
|
+
.array(z
|
|
68
|
+
.object({
|
|
69
|
+
work_uri: z.string().describe('CELLAR work URI.'),
|
|
70
|
+
celex_number: z.string().describe('CELEX identifier for the case (e.g. 62024CJ0629).'),
|
|
71
|
+
resource_type: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('CDM resource type URI indicating the case type (e.g. .../resource-type/JUDG for Judgment). Absent for some older cases.'),
|
|
75
|
+
date: z.string().optional().describe('Judgment/opinion date in ISO 8601 format.'),
|
|
76
|
+
title: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Case title where available (e.g. "Google Spain SL v AEPD"). Absent for many older cases.'),
|
|
80
|
+
})
|
|
81
|
+
.describe('A single CJEU or General Court case law record.'))
|
|
82
|
+
.describe('Matching case law records ordered by date descending.'),
|
|
83
|
+
total: z.number().describe('Number of cases returned in this page (not a corpus-wide count).'),
|
|
84
|
+
offset: z.number().describe('Pagination offset used for this response.'),
|
|
85
|
+
}),
|
|
86
|
+
errors: [
|
|
87
|
+
{
|
|
88
|
+
reason: 'no_results',
|
|
89
|
+
code: JsonRpcErrorCode.NotFound,
|
|
90
|
+
when: 'The query returned zero bindings — no matching cases in CELLAR sector 6.',
|
|
91
|
+
recovery: 'Try a different keyword, broader date range, or remove the court/case_type filter.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
reason: 'sparql_error',
|
|
95
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
96
|
+
when: 'Virtuoso returned HTTP 200 with an error body — query malformed or timed out.',
|
|
97
|
+
recovery: 'Simplify the query or reduce the date range and retry.',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
async handler(input, ctx) {
|
|
101
|
+
const svc = getCellarSparqlService();
|
|
102
|
+
// All case law is in CELEX sector 6
|
|
103
|
+
const filters = [`FILTER(STRSTARTS(STR(?celexNumber), "6"))`];
|
|
104
|
+
if (input.case_number && input.case_number.trim()) {
|
|
105
|
+
// Convert standard case number format to CELEX substring
|
|
106
|
+
// e.g. C-131/12 → search for "131" and "12" in celex
|
|
107
|
+
const cn = input.case_number.trim().replace(/"/g, '\\"');
|
|
108
|
+
filters.push(`FILTER(CONTAINS(LCASE(STR(?title)), LCASE("${cn}")) || CONTAINS(LCASE(STR(?celexNumber)), LCASE("${cn}")))`);
|
|
109
|
+
}
|
|
110
|
+
if (input.keyword && input.keyword.trim()) {
|
|
111
|
+
const kw = input.keyword.trim().toLowerCase().replace(/"/g, '\\"');
|
|
112
|
+
filters.push(`FILTER(CONTAINS(LCASE(COALESCE(STR(?title), "")), "${kw}") || CONTAINS(LCASE(STR(?celexNumber)), "${kw}"))`);
|
|
113
|
+
}
|
|
114
|
+
if (input.court === 'CJEU') {
|
|
115
|
+
// CJEU cases have CELEX pattern 6{year}CJ or 6{year}CC (AG opinions)
|
|
116
|
+
filters.push(`FILTER(CONTAINS(STR(?celexNumber), "CJ") || CONTAINS(STR(?celexNumber), "CC") || CONTAINS(STR(?celexNumber), "CO"))`);
|
|
117
|
+
}
|
|
118
|
+
else if (input.court === 'GC') {
|
|
119
|
+
// General Court cases have pattern 6{year}TJ
|
|
120
|
+
filters.push(`FILTER(CONTAINS(STR(?celexNumber), "TJ") || CONTAINS(STR(?celexNumber), "TO"))`);
|
|
121
|
+
}
|
|
122
|
+
if (input.case_type) {
|
|
123
|
+
const pattern = CASE_TYPE_PATTERN[input.case_type];
|
|
124
|
+
if (pattern) {
|
|
125
|
+
filters.push(`FILTER(CONTAINS(STR(?celexNumber), "${pattern}"))`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (input.date_from && input.date_from.trim()) {
|
|
129
|
+
filters.push(`FILTER(?date >= "${input.date_from.trim()}"^^xsd:date)`);
|
|
130
|
+
}
|
|
131
|
+
if (input.date_to && input.date_to.trim()) {
|
|
132
|
+
filters.push(`FILTER(?date <= "${input.date_to.trim()}"^^xsd:date)`);
|
|
133
|
+
}
|
|
134
|
+
const sparql = `
|
|
135
|
+
SELECT DISTINCT ?work ?celexNumber ?type ?date ?title WHERE {
|
|
136
|
+
?work cdm:resource_legal_id_celex ?celexNumber .
|
|
137
|
+
OPTIONAL { ?work cdm:work_has_resource-type ?type . }
|
|
138
|
+
OPTIONAL { ?work cdm:work_date_document ?date . }
|
|
139
|
+
OPTIONAL { ?work cdm:work_title ?titleNode . ?titleNode cdm:expression_title ?title . FILTER(LANG(?title) = "en") }
|
|
140
|
+
${filters.join('\n ')}
|
|
141
|
+
} ORDER BY DESC(?date) LIMIT ${input.limit} OFFSET ${input.offset}`;
|
|
142
|
+
const bindings = await svc.query(sparql, ctx);
|
|
143
|
+
ctx.log.info('Case law search', {
|
|
144
|
+
caseNumber: input.case_number,
|
|
145
|
+
keyword: input.keyword,
|
|
146
|
+
court: input.court,
|
|
147
|
+
caseType: input.case_type,
|
|
148
|
+
resultCount: bindings.length,
|
|
149
|
+
});
|
|
150
|
+
if (bindings.length === 0) {
|
|
151
|
+
throw ctx.fail('no_results', 'No case law records matched the search criteria.', {
|
|
152
|
+
...ctx.recoveryFor('no_results'),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const cases = bindings.map((b) => {
|
|
156
|
+
const c = {
|
|
157
|
+
work_uri: CellarSparqlService.bindingValue(b, 'work') ?? '',
|
|
158
|
+
celex_number: CellarSparqlService.bindingValue(b, 'celexNumber') ?? '',
|
|
159
|
+
};
|
|
160
|
+
const type = CellarSparqlService.bindingValue(b, 'type');
|
|
161
|
+
if (type)
|
|
162
|
+
c.resource_type = type;
|
|
163
|
+
const date = CellarSparqlService.bindingValue(b, 'date');
|
|
164
|
+
if (date)
|
|
165
|
+
c.date = date;
|
|
166
|
+
const title = CellarSparqlService.bindingValue(b, 'title');
|
|
167
|
+
if (title)
|
|
168
|
+
c.title = title;
|
|
169
|
+
return c;
|
|
170
|
+
});
|
|
171
|
+
return { cases, total: cases.length, offset: input.offset };
|
|
172
|
+
},
|
|
173
|
+
format: (result) => {
|
|
174
|
+
const lines = [
|
|
175
|
+
`## CJEU/GC Case Law (${result.total} results, offset ${result.offset})\n`,
|
|
176
|
+
];
|
|
177
|
+
for (const c of result.cases) {
|
|
178
|
+
lines.push(`### ${c.celex_number}${c.title ? ` — ${c.title}` : ''}`);
|
|
179
|
+
if (c.date)
|
|
180
|
+
lines.push(`**Date:** ${c.date}`);
|
|
181
|
+
if (c.resource_type)
|
|
182
|
+
lines.push(`**Type:** ${c.resource_type}`);
|
|
183
|
+
lines.push(`**Work URI:** ${c.work_uri}`);
|
|
184
|
+
lines.push('');
|
|
185
|
+
}
|
|
186
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
//# sourceMappingURL=eurlex-get-cases.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-get-cases.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurlex-get-cases.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview eurlex_get_document — Fetch metadata and full text of an EU act by CELEX number.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurlex-get-document
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const eurlex_get_document: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
celex_number: z.ZodString;
|
|
9
|
+
eli_uri: z.ZodOptional<z.ZodString>;
|
|
10
|
+
language: z.ZodDefault<z.ZodString>;
|
|
11
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
12
|
+
html: "html";
|
|
13
|
+
xml: "xml";
|
|
14
|
+
}>>;
|
|
15
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
+
celex_number: z.ZodString;
|
|
17
|
+
work_uri: z.ZodOptional<z.ZodString>;
|
|
18
|
+
title: z.ZodOptional<z.ZodString>;
|
|
19
|
+
date: z.ZodOptional<z.ZodString>;
|
|
20
|
+
resource_type: z.ZodOptional<z.ZodString>;
|
|
21
|
+
author_institution: z.ZodOptional<z.ZodString>;
|
|
22
|
+
legal_basis: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
|
+
eurovoc_subjects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
|
+
in_force: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
content: z.ZodOptional<z.ZodString>;
|
|
26
|
+
content_available: z.ZodBoolean;
|
|
27
|
+
language: z.ZodString;
|
|
28
|
+
language_fallback: z.ZodOptional<z.ZodString>;
|
|
29
|
+
content_format: z.ZodString;
|
|
30
|
+
}, z.core.$strip>, readonly [{
|
|
31
|
+
readonly reason: "not_found";
|
|
32
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
33
|
+
readonly when: "CELEX number not found in CELLAR — the work does not exist in the corpus.";
|
|
34
|
+
readonly recovery: "Verify the CELEX number or use eurlex_lookup_celex to confirm it exists.";
|
|
35
|
+
}, {
|
|
36
|
+
readonly reason: "language_unavailable";
|
|
37
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
38
|
+
readonly when: "Requested language has no content in EUR-Lex after fallback to English also failed.";
|
|
39
|
+
readonly recovery: "Retry with language \"EN\" explicitly, or accept content_available: false and use metadata only.";
|
|
40
|
+
}, {
|
|
41
|
+
readonly reason: "content_fetch_failed";
|
|
42
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
43
|
+
readonly when: "EUR-Lex content API returned non-200 after language fallback attempts.";
|
|
44
|
+
readonly recovery: "The EUR-Lex content API may be temporarily unavailable. Retry after a short delay.";
|
|
45
|
+
}], undefined>;
|
|
46
|
+
//# sourceMappingURL=eurlex-get-document.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-get-document.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurlex-get-document.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAWjE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqP9B,CAAC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview eurlex_get_document — Fetch metadata and full text of an EU act by CELEX number.
|
|
3
|
+
* @module mcp-server/tools/definitions/eurlex-get-document
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { CellarSparqlService, getCellarSparqlService, } from '../../../services/cellar-sparql/cellar-sparql-service.js';
|
|
8
|
+
import { getEurLexContentService, } from '../../../services/eurlex-content/eurlex-content-service.js';
|
|
9
|
+
export const eurlex_get_document = tool('eurlex_get_document', {
|
|
10
|
+
title: 'Get EU Document',
|
|
11
|
+
description: 'Fetch the notice (metadata) and full text of an EU act by CELEX number or ELI URI. ' +
|
|
12
|
+
'Returns structured metadata — title, date, document type, author institution, legal basis, EuroVoc subjects — ' +
|
|
13
|
+
'plus the HTML or Formex4 XML content in the requested language. ' +
|
|
14
|
+
'Defaults to English (EN); not all works have content in all 24 official EU languages, ' +
|
|
15
|
+
'especially older acts pre-2004 EU enlargement. ' +
|
|
16
|
+
'If the requested language is unavailable, the server automatically falls back to English and notes the fallback. ' +
|
|
17
|
+
'CELEX format: {sector}{year}{type}{number} e.g. 32016R0679 for GDPR. ' +
|
|
18
|
+
'Use eurlex_lookup_celex to validate an identifier before calling this tool. ' +
|
|
19
|
+
'HTML format returns the full act text suitable for reading; XML returns Formex4 for structured processing.',
|
|
20
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
21
|
+
input: z.object({
|
|
22
|
+
celex_number: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.describe('CELEX number of the act to fetch (e.g. 32016R0679 for GDPR). Preferred over eli_uri.'),
|
|
26
|
+
eli_uri: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('ELI URI as alternative to celex_number (e.g. http://data.europa.eu/eli/reg/2016/679/oj). Used only if celex_number is absent or empty.'),
|
|
30
|
+
language: z
|
|
31
|
+
.string()
|
|
32
|
+
.regex(/^[A-Za-z]{2,3}$/)
|
|
33
|
+
.default('EN')
|
|
34
|
+
.describe('Language code for document content (ISO 639-1 uppercase, e.g. EN, FR, DE). ' +
|
|
35
|
+
'Defaults to EN. Falls back to EN if the requested language is unavailable.'),
|
|
36
|
+
format: z
|
|
37
|
+
.enum(['html', 'xml'])
|
|
38
|
+
.default('html')
|
|
39
|
+
.describe('Content format: "html" for readable HTML text (default), "xml" for Formex4 XML structured format.'),
|
|
40
|
+
}),
|
|
41
|
+
output: z.object({
|
|
42
|
+
celex_number: z.string().describe('Confirmed CELEX number for the retrieved work.'),
|
|
43
|
+
work_uri: z.string().optional().describe('CELLAR work URI.'),
|
|
44
|
+
title: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Document title in the requested language (absent for some older works and judgments).'),
|
|
48
|
+
date: z.string().optional().describe('Document date in ISO 8601 format (YYYY-MM-DD).'),
|
|
49
|
+
resource_type: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('CDM resource type URI indicating the document category (e.g. .../resource-type/REG for Regulation). Absent for some older works.'),
|
|
53
|
+
author_institution: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('URI of the originating EU institution in the CDM authority register. Resolve against eurlex_query_sparql for a human-readable label.'),
|
|
57
|
+
legal_basis: z
|
|
58
|
+
.array(z.string().describe('CELEX number or URI of a legal basis act.'))
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Legal basis acts for this work.'),
|
|
61
|
+
eurovoc_subjects: z
|
|
62
|
+
.array(z.string().describe('EuroVoc concept URI.'))
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('EuroVoc subject classifications.'),
|
|
65
|
+
in_force: z.boolean().optional().describe('Whether the act is currently in force.'),
|
|
66
|
+
content: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Full text content of the act in the requested format and language.'),
|
|
70
|
+
content_available: z.boolean().describe('Whether document content was successfully retrieved.'),
|
|
71
|
+
language: z.string().describe('Language code of the returned content.'),
|
|
72
|
+
language_fallback: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Human-readable note explaining the fallback that occurred (e.g. "Requested FR content unavailable; returned EN"). Present only when a fallback happened.'),
|
|
76
|
+
content_format: z.string().describe('Format of the returned content: "html" or "xml".'),
|
|
77
|
+
}),
|
|
78
|
+
errors: [
|
|
79
|
+
{
|
|
80
|
+
reason: 'not_found',
|
|
81
|
+
code: JsonRpcErrorCode.NotFound,
|
|
82
|
+
when: 'CELEX number not found in CELLAR — the work does not exist in the corpus.',
|
|
83
|
+
recovery: 'Verify the CELEX number or use eurlex_lookup_celex to confirm it exists.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
reason: 'language_unavailable',
|
|
87
|
+
code: JsonRpcErrorCode.NotFound,
|
|
88
|
+
when: 'Requested language has no content in EUR-Lex after fallback to English also failed.',
|
|
89
|
+
recovery: 'Retry with language "EN" explicitly, or accept content_available: false and use metadata only.',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
reason: 'content_fetch_failed',
|
|
93
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
94
|
+
when: 'EUR-Lex content API returned non-200 after language fallback attempts.',
|
|
95
|
+
recovery: 'The EUR-Lex content API may be temporarily unavailable. Retry after a short delay.',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
async handler(input, ctx) {
|
|
99
|
+
const sparqlSvc = getCellarSparqlService();
|
|
100
|
+
const contentSvc = getEurLexContentService();
|
|
101
|
+
const celexNumber = input.celex_number.trim();
|
|
102
|
+
const language = (input.language.trim().toUpperCase() || 'EN');
|
|
103
|
+
const format = input.format;
|
|
104
|
+
const safeCelexNumber = celexNumber.replace(/"/g, '\\"');
|
|
105
|
+
// Step 1: Fetch metadata via SPARQL
|
|
106
|
+
const metaSparql = `
|
|
107
|
+
SELECT ?work ?celexNumber ?type ?date ?title ?inForce ?author ?legalBasis ?eurovoc WHERE {
|
|
108
|
+
?work cdm:resource_legal_id_celex ?celexNumber .
|
|
109
|
+
FILTER(STR(?celexNumber) = "${safeCelexNumber}")
|
|
110
|
+
OPTIONAL { ?work cdm:work_has_resource-type ?type . }
|
|
111
|
+
OPTIONAL { ?work cdm:work_date_document ?date . }
|
|
112
|
+
OPTIONAL { ?work cdm:work_title ?titleNode . ?titleNode cdm:expression_title ?title . FILTER(LANG(?title) = "en") }
|
|
113
|
+
OPTIONAL { ?work cdm:resource_legal_in-force ?inForce . }
|
|
114
|
+
OPTIONAL { ?work cdm:work_created_by_agent ?author . }
|
|
115
|
+
OPTIONAL { ?work cdm:resource_legal_based_on_resource_legal ?legalBasis . }
|
|
116
|
+
OPTIONAL { ?work cdm:work_is_about_concept_eurovoc ?eurovoc . }
|
|
117
|
+
} LIMIT 20`;
|
|
118
|
+
const metaBindings = await sparqlSvc.query(metaSparql, ctx);
|
|
119
|
+
ctx.log.info('Document metadata fetch', { celexNumber, resultCount: metaBindings.length });
|
|
120
|
+
if (metaBindings.length === 0) {
|
|
121
|
+
throw ctx.fail('not_found', `No CELLAR work found for CELEX: ${celexNumber}`, {
|
|
122
|
+
...ctx.recoveryFor('not_found'),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Aggregate repeated fields from multi-row result
|
|
126
|
+
const first = metaBindings[0];
|
|
127
|
+
const legalBases = new Set();
|
|
128
|
+
const eurovocConcepts = new Set();
|
|
129
|
+
for (const b of metaBindings) {
|
|
130
|
+
const lb = CellarSparqlService.bindingValue(b, 'legalBasis');
|
|
131
|
+
if (lb)
|
|
132
|
+
legalBases.add(lb);
|
|
133
|
+
const ev = CellarSparqlService.bindingValue(b, 'eurovoc');
|
|
134
|
+
if (ev)
|
|
135
|
+
eurovocConcepts.add(ev);
|
|
136
|
+
}
|
|
137
|
+
const workUri = CellarSparqlService.bindingValue(first, 'work');
|
|
138
|
+
const confirmedCelex = CellarSparqlService.bindingValue(first, 'celexNumber') ?? celexNumber;
|
|
139
|
+
const resourceType = CellarSparqlService.bindingValue(first, 'type');
|
|
140
|
+
const date = CellarSparqlService.bindingValue(first, 'date');
|
|
141
|
+
const title = CellarSparqlService.bindingValue(first, 'title');
|
|
142
|
+
const inForceStr = CellarSparqlService.bindingValue(first, 'inForce');
|
|
143
|
+
const inForce = inForceStr !== undefined ? inForceStr === 'true' : undefined;
|
|
144
|
+
const authorUri = CellarSparqlService.bindingValue(first, 'author');
|
|
145
|
+
// Step 2: Fetch document content via EUR-Lex content API
|
|
146
|
+
const contentResult = await contentSvc.fetchContent(celexNumber, language, format, ctx);
|
|
147
|
+
const result = {
|
|
148
|
+
celex_number: confirmedCelex,
|
|
149
|
+
content_available: contentResult.contentAvailable,
|
|
150
|
+
language: contentResult.language,
|
|
151
|
+
content_format: format,
|
|
152
|
+
};
|
|
153
|
+
if (workUri)
|
|
154
|
+
result.work_uri = workUri;
|
|
155
|
+
if (title)
|
|
156
|
+
result.title = title;
|
|
157
|
+
if (date)
|
|
158
|
+
result.date = date;
|
|
159
|
+
if (resourceType)
|
|
160
|
+
result.resource_type = resourceType;
|
|
161
|
+
if (authorUri)
|
|
162
|
+
result.author_institution = authorUri;
|
|
163
|
+
if (legalBases.size > 0)
|
|
164
|
+
result.legal_basis = [...legalBases];
|
|
165
|
+
if (eurovocConcepts.size > 0)
|
|
166
|
+
result.eurovoc_subjects = [...eurovocConcepts];
|
|
167
|
+
if (typeof inForce === 'boolean')
|
|
168
|
+
result.in_force = inForce;
|
|
169
|
+
if (contentResult.contentAvailable && contentResult.content) {
|
|
170
|
+
result.content = contentResult.content;
|
|
171
|
+
}
|
|
172
|
+
if (contentResult.languageFallback) {
|
|
173
|
+
result.language_fallback = contentResult.languageFallback;
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
},
|
|
177
|
+
format: (result) => {
|
|
178
|
+
const lines = [
|
|
179
|
+
`## ${result.celex_number}${result.title ? ` — ${result.title}` : ''}\n`,
|
|
180
|
+
];
|
|
181
|
+
if (result.date)
|
|
182
|
+
lines.push(`**Date:** ${result.date}`);
|
|
183
|
+
if (result.resource_type)
|
|
184
|
+
lines.push(`**Type:** ${result.resource_type}`);
|
|
185
|
+
if (result.author_institution)
|
|
186
|
+
lines.push(`**Author:** ${result.author_institution}`);
|
|
187
|
+
if (typeof result.in_force === 'boolean')
|
|
188
|
+
lines.push(`**In Force:** ${result.in_force}`);
|
|
189
|
+
if (result.work_uri)
|
|
190
|
+
lines.push(`**Work URI:** ${result.work_uri}`);
|
|
191
|
+
if (result.legal_basis && result.legal_basis.length > 0) {
|
|
192
|
+
lines.push(`**Legal Basis:** ${result.legal_basis.join(', ')}`);
|
|
193
|
+
}
|
|
194
|
+
if (result.eurovoc_subjects && result.eurovoc_subjects.length > 0) {
|
|
195
|
+
lines.push(`**EuroVoc Subjects:** ${result.eurovoc_subjects.slice(0, 5).join(', ')}${result.eurovoc_subjects.length > 5 ? ` (+${result.eurovoc_subjects.length - 5} more)` : ''}`);
|
|
196
|
+
}
|
|
197
|
+
lines.push(`**Language:** ${result.language} | **Format:** ${result.content_format}`);
|
|
198
|
+
if (result.language_fallback)
|
|
199
|
+
lines.push(`*Note: ${result.language_fallback}*`);
|
|
200
|
+
lines.push(`**Content Available:** ${result.content_available}`);
|
|
201
|
+
if (result.content_available && result.content) {
|
|
202
|
+
lines.push('');
|
|
203
|
+
lines.push('---');
|
|
204
|
+
lines.push('');
|
|
205
|
+
// Truncate very large content for format() output
|
|
206
|
+
const maxLen = 8000;
|
|
207
|
+
if (result.content.length > maxLen) {
|
|
208
|
+
lines.push(result.content.slice(0, maxLen));
|
|
209
|
+
lines.push(`\n*[Content truncated — ${result.content.length} chars total. Use the CELEX number to fetch directly.]*`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push(result.content);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (!result.content_available) {
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push('*Document content is not available for this work in the requested language.*');
|
|
218
|
+
}
|
|
219
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=eurlex-get-document.tool.js.map
|