@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,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CellarSparqlService — HTTP client for the EU Publications Office CELLAR
|
|
3
|
+
* Virtuoso SPARQL endpoint. Handles POST queries, LIMIT enforcement, and Virtuoso-specific
|
|
4
|
+
* error detection (HTTP 200 with error body).
|
|
5
|
+
* @module services/cellar-sparql/cellar-sparql-service
|
|
6
|
+
*/
|
|
7
|
+
import { invalidParams, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
9
|
+
/** Required PREFIX declarations for CELLAR CDM ontology queries. */
|
|
10
|
+
const SPARQL_PREFIXES = `PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
|
|
11
|
+
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
|
|
12
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
13
|
+
`;
|
|
14
|
+
/** Pattern matching Virtuoso-specific error responses (HTTP 200 with error body). */
|
|
15
|
+
const VIRTUOSO_ERROR_RE = /Virtuoso\s+\d+\s+Error/i;
|
|
16
|
+
/** Pattern for Virtuoso timeout messages (SP031). */
|
|
17
|
+
const VIRTUOSO_TIMEOUT_RE = /SP031|query execution timed out/i;
|
|
18
|
+
/**
|
|
19
|
+
* Inject or cap the LIMIT clause in a SPARQL query to `max`.
|
|
20
|
+
* If the query has no LIMIT, appends one. If it has a LIMIT above `max`, rewrites it.
|
|
21
|
+
*/
|
|
22
|
+
function enforceLimitInQuery(query, max) {
|
|
23
|
+
const limitRe = /\bLIMIT\s+(\d+)/i;
|
|
24
|
+
const match = limitRe.exec(query);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return `${query.trimEnd()}\nLIMIT ${max}`;
|
|
27
|
+
}
|
|
28
|
+
const existing = parseInt(match[1], 10);
|
|
29
|
+
if (existing > max) {
|
|
30
|
+
return query.replace(limitRe, `LIMIT ${max}`);
|
|
31
|
+
}
|
|
32
|
+
return query;
|
|
33
|
+
}
|
|
34
|
+
export class CellarSparqlService {
|
|
35
|
+
endpoint;
|
|
36
|
+
timeoutMs;
|
|
37
|
+
maxResults;
|
|
38
|
+
constructor(_config, _storage, serverConfig) {
|
|
39
|
+
this.endpoint = serverConfig.cellarSparqlEndpoint;
|
|
40
|
+
this.timeoutMs = serverConfig.sparqlQueryTimeoutMs;
|
|
41
|
+
this.maxResults = serverConfig.maxSparqlResults;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Execute a raw SPARQL SELECT query. Prefixes are prepended automatically
|
|
45
|
+
* if not already present. LIMIT is injected/capped at `maxResults`.
|
|
46
|
+
*/
|
|
47
|
+
async query(rawQuery, ctx) {
|
|
48
|
+
const withPrefixes = rawQuery.includes('PREFIX cdm:') ? rawQuery : SPARQL_PREFIXES + rawQuery;
|
|
49
|
+
const cappedQuery = enforceLimitInQuery(withPrefixes, this.maxResults);
|
|
50
|
+
return withRetry(async () => {
|
|
51
|
+
const body = new URLSearchParams({
|
|
52
|
+
query: cappedQuery,
|
|
53
|
+
format: 'application/sparql-results+json',
|
|
54
|
+
});
|
|
55
|
+
const response = await fetch(this.endpoint, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
59
|
+
Accept: 'application/sparql-results+json',
|
|
60
|
+
},
|
|
61
|
+
body: body.toString(),
|
|
62
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
63
|
+
});
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
if (response.status === 400) {
|
|
67
|
+
// HTTP 400 = client error (malformed query) — not retryable
|
|
68
|
+
throw invalidParams(`CELLAR SPARQL error: ${text.slice(0, 300)}`, {
|
|
69
|
+
reason: 'sparql_error',
|
|
70
|
+
retryable: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
throw serviceUnavailable(`CELLAR SPARQL HTTP ${response.status}`, {
|
|
74
|
+
status: response.status,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Virtuoso returns HTTP 200 even for errors — inspect the body. */
|
|
78
|
+
if (VIRTUOSO_ERROR_RE.test(text)) {
|
|
79
|
+
if (VIRTUOSO_TIMEOUT_RE.test(text)) {
|
|
80
|
+
throw serviceUnavailable('CELLAR SPARQL query timed out on Virtuoso', {
|
|
81
|
+
reason: 'sparql_timeout',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Syntax / semantic error — not transient, fail immediately
|
|
85
|
+
throw invalidParams(`CELLAR SPARQL error: ${text.slice(0, 300)}`, {
|
|
86
|
+
reason: 'sparql_error',
|
|
87
|
+
retryable: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
96
|
+
throw serviceUnavailable('CELLAR returned HTML instead of SPARQL results — possibly rate-limited.');
|
|
97
|
+
}
|
|
98
|
+
throw serviceUnavailable('Failed to parse CELLAR SPARQL response as JSON');
|
|
99
|
+
}
|
|
100
|
+
return parsed.results.bindings;
|
|
101
|
+
}, {
|
|
102
|
+
operation: 'CellarSparqlService.query',
|
|
103
|
+
baseDelayMs: 1500,
|
|
104
|
+
signal: ctx.signal,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Extract a string value from a binding field, returning undefined if absent. */
|
|
108
|
+
static bindingValue(binding, field) {
|
|
109
|
+
return binding?.[field]?.value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// --- Init/accessor pattern ---
|
|
113
|
+
let _service;
|
|
114
|
+
export function initCellarSparqlService(config, storage, serverConfig) {
|
|
115
|
+
_service = new CellarSparqlService(config, storage, serverConfig);
|
|
116
|
+
}
|
|
117
|
+
export function getCellarSparqlService() {
|
|
118
|
+
if (!_service) {
|
|
119
|
+
throw new Error('CellarSparqlService not initialized — call initCellarSparqlService() in setup()');
|
|
120
|
+
}
|
|
121
|
+
return _service;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=cellar-sparql-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cellar-sparql-service.js","sourceRoot":"","sources":["../../../src/services/cellar-sparql/cellar-sparql-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared domain types for the CELLAR SPARQL service and EUR-Lex content service.
|
|
3
|
+
* @module services/cellar-sparql/types
|
|
4
|
+
*/
|
|
5
|
+
/** A single binding row from a SPARQL SELECT result. */
|
|
6
|
+
export type SparqlBinding = Record<string, {
|
|
7
|
+
type: string;
|
|
8
|
+
value: string;
|
|
9
|
+
datatype?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** The full SPARQL results JSON envelope from Virtuoso. */
|
|
12
|
+
export interface SparqlResultsJson {
|
|
13
|
+
head: {
|
|
14
|
+
vars: string[];
|
|
15
|
+
};
|
|
16
|
+
results: {
|
|
17
|
+
bindings: SparqlBinding[];
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** A resolved CELLAR work record. */
|
|
21
|
+
export interface CellarWork {
|
|
22
|
+
authorInstitution?: string;
|
|
23
|
+
celexNumber: string;
|
|
24
|
+
date?: string;
|
|
25
|
+
eurovocConcepts?: string[];
|
|
26
|
+
inForce?: boolean;
|
|
27
|
+
resourceType?: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
workUri: string;
|
|
30
|
+
}
|
|
31
|
+
/** A search result entry from CELLAR. */
|
|
32
|
+
export interface WorkSearchResult {
|
|
33
|
+
celexNumber: string;
|
|
34
|
+
date?: string;
|
|
35
|
+
resourceType?: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
workUri: string;
|
|
38
|
+
}
|
|
39
|
+
/** A CJEU/GC case law result entry. */
|
|
40
|
+
export interface CaseResult {
|
|
41
|
+
celexNumber: string;
|
|
42
|
+
court?: string;
|
|
43
|
+
date?: string;
|
|
44
|
+
resourceType?: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
workUri: string;
|
|
47
|
+
}
|
|
48
|
+
/** A single CDM relation between works. */
|
|
49
|
+
export interface WorkRelation {
|
|
50
|
+
direction: 'outgoing' | 'incoming';
|
|
51
|
+
relatedCelexNumber?: string;
|
|
52
|
+
relatedWorkUri: string;
|
|
53
|
+
relationType: string;
|
|
54
|
+
}
|
|
55
|
+
/** An EuroVoc concept from the thesaurus. */
|
|
56
|
+
export interface EuroVocConcept {
|
|
57
|
+
broaderLabel?: string;
|
|
58
|
+
conceptCode?: string;
|
|
59
|
+
conceptUri: string;
|
|
60
|
+
prefLabel: string;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/cellar-sparql/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/F,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACzB,OAAO,EAAE;QAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;CACxC;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/cellar-sparql/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview EurLexContentService — HTTP client for the EUR-Lex REST content API.
|
|
3
|
+
* Fetches full HTML or XML text of EU legal acts via the legal-content URL pattern.
|
|
4
|
+
* Document content is NOT available via CELLAR work URI content negotiation (returns 400).
|
|
5
|
+
* @module services/eurlex-content/eurlex-content-service
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
8
|
+
import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
|
|
9
|
+
import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
|
|
10
|
+
import type { ServerConfig } from '../../config/server-config.js';
|
|
11
|
+
export type ContentFormat = 'html' | 'xml';
|
|
12
|
+
/** Language codes supported by EUR-Lex (24 official EU languages). */
|
|
13
|
+
export type EurLexLanguage = 'EN' | 'FR' | 'DE' | 'ES' | 'IT' | 'PL' | 'PT' | 'NL' | 'CS' | 'DA' | 'EL' | 'ET' | 'FI' | 'HU' | 'LT' | 'LV' | 'MT' | 'RO' | 'SK' | 'SL' | 'SV' | 'BG' | 'HR' | 'GA';
|
|
14
|
+
export interface FetchContentResult {
|
|
15
|
+
content: string;
|
|
16
|
+
contentAvailable: boolean;
|
|
17
|
+
format: ContentFormat;
|
|
18
|
+
language: EurLexLanguage;
|
|
19
|
+
/** Set when a language fallback occurred. */
|
|
20
|
+
languageFallback?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare class EurLexContentService {
|
|
23
|
+
private readonly baseUrl;
|
|
24
|
+
private readonly timeoutMs;
|
|
25
|
+
constructor(_config: AppConfig, _storage: StorageService, serverConfig: ServerConfig);
|
|
26
|
+
/**
|
|
27
|
+
* Build the EUR-Lex legal-content URL for a CELEX number.
|
|
28
|
+
* Pattern: /legal-content/{LANG}/TXT/{FORMAT}/?uri=CELEX:{celex}
|
|
29
|
+
*/
|
|
30
|
+
buildContentUrl(celexNumber: string, language: EurLexLanguage, format: ContentFormat): string;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the full text content of an EU act by CELEX number.
|
|
33
|
+
* If the requested language is unavailable, falls back to English.
|
|
34
|
+
* Returns `contentAvailable: false` with an empty string if both attempts fail.
|
|
35
|
+
*/
|
|
36
|
+
fetchContent(celexNumber: string, language: EurLexLanguage, format: ContentFormat, ctx: Context): Promise<FetchContentResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Attempt to fetch content for a specific language. Returns null on non-200 or
|
|
39
|
+
* empty/redirect response rather than throwing — callers handle fallback logic.
|
|
40
|
+
*/
|
|
41
|
+
private tryFetch;
|
|
42
|
+
}
|
|
43
|
+
export declare function initEurLexContentService(config: AppConfig, storage: StorageService, serverConfig: ServerConfig): void;
|
|
44
|
+
export declare function getEurLexContentService(): EurLexContentService;
|
|
45
|
+
//# sourceMappingURL=eurlex-content-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-content-service.d.ts","sourceRoot":"","sources":["../../../src/services/eurlex-content/eurlex-content-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,sEAAsE;AACtE,MAAM,MAAM,cAAc,GACtB,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,CAAC;AAET,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY;IAKpF;;;OAGG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM;IAK7F;;;;OAIG;IACG,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,kBAAkB,CAAC;IAuB9B;;;OAGG;YACW,QAAQ;CA0CvB;AAMD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,YAAY,GACzB,IAAI,CAEN;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAO9D"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview EurLexContentService — HTTP client for the EUR-Lex REST content API.
|
|
3
|
+
* Fetches full HTML or XML text of EU legal acts via the legal-content URL pattern.
|
|
4
|
+
* Document content is NOT available via CELLAR work URI content negotiation (returns 400).
|
|
5
|
+
* @module services/eurlex-content/eurlex-content-service
|
|
6
|
+
*/
|
|
7
|
+
import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
9
|
+
export class EurLexContentService {
|
|
10
|
+
baseUrl;
|
|
11
|
+
timeoutMs;
|
|
12
|
+
constructor(_config, _storage, serverConfig) {
|
|
13
|
+
this.baseUrl = serverConfig.eurLexContentBaseUrl.replace(/\/$/, '');
|
|
14
|
+
this.timeoutMs = serverConfig.sparqlQueryTimeoutMs;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build the EUR-Lex legal-content URL for a CELEX number.
|
|
18
|
+
* Pattern: /legal-content/{LANG}/TXT/{FORMAT}/?uri=CELEX:{celex}
|
|
19
|
+
*/
|
|
20
|
+
buildContentUrl(celexNumber, language, format) {
|
|
21
|
+
const fmt = format === 'xml' ? 'XML' : 'HTML';
|
|
22
|
+
return `${this.baseUrl}/legal-content/${language}/TXT/${fmt}/?uri=CELEX:${celexNumber}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetch the full text content of an EU act by CELEX number.
|
|
26
|
+
* If the requested language is unavailable, falls back to English.
|
|
27
|
+
* Returns `contentAvailable: false` with an empty string if both attempts fail.
|
|
28
|
+
*/
|
|
29
|
+
async fetchContent(celexNumber, language, format, ctx) {
|
|
30
|
+
const primary = await this.tryFetch(celexNumber, language, format, ctx);
|
|
31
|
+
if (primary !== null) {
|
|
32
|
+
return { content: primary, language, format, contentAvailable: true };
|
|
33
|
+
}
|
|
34
|
+
// Language fallback: try English if primary language failed
|
|
35
|
+
if (language !== 'EN') {
|
|
36
|
+
const fallback = await this.tryFetch(celexNumber, 'EN', format, ctx);
|
|
37
|
+
if (fallback !== null) {
|
|
38
|
+
return {
|
|
39
|
+
content: fallback,
|
|
40
|
+
language: 'EN',
|
|
41
|
+
format,
|
|
42
|
+
contentAvailable: true,
|
|
43
|
+
languageFallback: `Requested language ${language} unavailable; returned English content.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { content: '', language, format, contentAvailable: false };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Attempt to fetch content for a specific language. Returns null on non-200 or
|
|
51
|
+
* empty/redirect response rather than throwing — callers handle fallback logic.
|
|
52
|
+
*/
|
|
53
|
+
async tryFetch(celexNumber, language, format, ctx) {
|
|
54
|
+
const url = this.buildContentUrl(celexNumber, language, format);
|
|
55
|
+
return withRetry(async () => {
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
headers: { Accept: format === 'xml' ? 'application/xml' : 'text/html' },
|
|
58
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
59
|
+
redirect: 'follow',
|
|
60
|
+
});
|
|
61
|
+
if (response.status === 404 || response.status === 302 || !response.ok) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
// Detect HTML error pages masquerading as success (e.g. rate-limit pages)
|
|
66
|
+
if (format === 'xml' && /^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
67
|
+
throw serviceUnavailable('EUR-Lex returned HTML instead of XML content.');
|
|
68
|
+
}
|
|
69
|
+
// Empty content means unavailable
|
|
70
|
+
if (text.trim().length < 100) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return text;
|
|
74
|
+
}, {
|
|
75
|
+
operation: 'EurLexContentService.tryFetch',
|
|
76
|
+
baseDelayMs: 1000,
|
|
77
|
+
maxRetries: 2,
|
|
78
|
+
signal: ctx.signal,
|
|
79
|
+
}).catch(() => null); // Treat fetch failures as unavailable for language fallback
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// --- Init/accessor pattern ---
|
|
83
|
+
let _service;
|
|
84
|
+
export function initEurLexContentService(config, storage, serverConfig) {
|
|
85
|
+
_service = new EurLexContentService(config, storage, serverConfig);
|
|
86
|
+
}
|
|
87
|
+
export function getEurLexContentService() {
|
|
88
|
+
if (!_service) {
|
|
89
|
+
throw new Error('EurLexContentService not initialized — call initEurLexContentService() in setup()');
|
|
90
|
+
}
|
|
91
|
+
return _service;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=eurlex-content-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eurlex-content-service.js","sourceRoot":"","sources":["../../../src/services/eurlex-content/eurlex-content-service.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cyanheads/eur-lex-mcp-server",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"mcpName": "io.github.cyanheads/eur-lex-mcp-server",
|
|
5
|
+
"description": "Search EU legislation, CJEU case law, and treaties; traverse the CELLAR relationship graph; resolve EuroVoc concepts via MCP. STDIO or Streamable HTTP.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"eur-lex-mcp-server": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"changelog/",
|
|
14
|
+
"dist/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CLAUDE.md",
|
|
18
|
+
"AGENTS.md",
|
|
19
|
+
"Dockerfile",
|
|
20
|
+
"server.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "bun run scripts/build.ts",
|
|
24
|
+
"rebuild": "bun run scripts/clean.ts && bun run scripts/build.ts",
|
|
25
|
+
"clean": "bun run scripts/clean.ts",
|
|
26
|
+
"devcheck": "bun run scripts/devcheck.ts",
|
|
27
|
+
"audit:refresh": "rm -f bun.lock && bun install && bun audit",
|
|
28
|
+
"tree": "bun run scripts/tree.ts",
|
|
29
|
+
"list-skills": "bun run scripts/list-skills.ts",
|
|
30
|
+
"format": "biome check --write .",
|
|
31
|
+
"format:unsafe": "biome check --write --unsafe .",
|
|
32
|
+
"lint:mcp": "bun run scripts/lint-mcp.ts",
|
|
33
|
+
"lint:packaging": "bun run scripts/lint-packaging.ts",
|
|
34
|
+
"bundle": "npm run build && npx -y @anthropic-ai/mcpb pack . dist/eur-lex-mcp-server.mcpb",
|
|
35
|
+
"changelog:build": "bun run scripts/build-changelog.ts",
|
|
36
|
+
"changelog:check": "bun run scripts/build-changelog.ts --check",
|
|
37
|
+
"release:github": "bun run scripts/release-github.ts",
|
|
38
|
+
"publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
|
|
42
|
+
"start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"eur-lex",
|
|
46
|
+
"eu-law",
|
|
47
|
+
"european-union",
|
|
48
|
+
"cellar",
|
|
49
|
+
"sparql",
|
|
50
|
+
"cjeu",
|
|
51
|
+
"eurovoc",
|
|
52
|
+
"legislation",
|
|
53
|
+
"legal",
|
|
54
|
+
"mcp",
|
|
55
|
+
"mcp-server",
|
|
56
|
+
"model-context-protocol",
|
|
57
|
+
"typescript",
|
|
58
|
+
"bun",
|
|
59
|
+
"stdio",
|
|
60
|
+
"streamable-http",
|
|
61
|
+
"ai-agent"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/cyanheads/eur-lex-mcp-server.git"
|
|
66
|
+
},
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/cyanheads/eur-lex-mcp-server/issues"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/cyanheads/eur-lex-mcp-server#readme",
|
|
71
|
+
"author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/eur-lex-mcp-server#readme)",
|
|
72
|
+
"funding": [
|
|
73
|
+
{
|
|
74
|
+
"type": "github",
|
|
75
|
+
"url": "https://github.com/sponsors/cyanheads"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "buy_me_a_coffee",
|
|
79
|
+
"url": "https://www.buymeacoffee.com/cyanheads"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"license": "Apache-2.0",
|
|
83
|
+
"packageManager": "bun@1.3.11",
|
|
84
|
+
"engines": {
|
|
85
|
+
"bun": ">=1.3.0",
|
|
86
|
+
"node": ">=24.0.0"
|
|
87
|
+
},
|
|
88
|
+
"publishConfig": {
|
|
89
|
+
"access": "public"
|
|
90
|
+
},
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"@cyanheads/mcp-ts-core": "0.9.21",
|
|
93
|
+
"pino-pretty": "^13.1.3",
|
|
94
|
+
"zod": "^4.4.3"
|
|
95
|
+
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"@biomejs/biome": "^2.4.16",
|
|
98
|
+
"@types/node": "^25.9.1",
|
|
99
|
+
"depcheck": "^1.4.7",
|
|
100
|
+
"ignore": "^7.0.5",
|
|
101
|
+
"tsc-alias": "^1.8.17",
|
|
102
|
+
"typescript": "^6.0.3",
|
|
103
|
+
"vitest": "^4.1.8"
|
|
104
|
+
}
|
|
105
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.cyanheads/eur-lex-mcp-server",
|
|
4
|
+
"description": "Search EU legislation, CJEU case law, and treaties; traverse CELLAR graph; browse EuroVoc concepts.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/cyanheads/eur-lex-mcp-server",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.1",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
14
|
+
"identifier": "@cyanheads/eur-lex-mcp-server",
|
|
15
|
+
"runtimeHint": "bun",
|
|
16
|
+
"version": "0.1.1",
|
|
17
|
+
"packageArguments": [
|
|
18
|
+
{
|
|
19
|
+
"type": "positional",
|
|
20
|
+
"value": "run"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "positional",
|
|
24
|
+
"value": "start:stdio"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"environmentVariables": [
|
|
28
|
+
{
|
|
29
|
+
"name": "CELLAR_SPARQL_ENDPOINT",
|
|
30
|
+
"description": "CELLAR SPARQL endpoint URL override (e.g., for a local Virtuoso mirror).",
|
|
31
|
+
"format": "string",
|
|
32
|
+
"isRequired": false,
|
|
33
|
+
"default": "http://publications.europa.eu/webapi/rdf/sparql"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "EURLEX_CONTENT_BASE_URL",
|
|
37
|
+
"description": "EUR-Lex content API base URL override.",
|
|
38
|
+
"format": "string",
|
|
39
|
+
"isRequired": false,
|
|
40
|
+
"default": "https://eur-lex.europa.eu"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "SPARQL_QUERY_TIMEOUT_MS",
|
|
44
|
+
"description": "Client-side timeout for SPARQL requests in milliseconds.",
|
|
45
|
+
"format": "string",
|
|
46
|
+
"isRequired": false,
|
|
47
|
+
"default": "55000"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "MAX_SPARQL_RESULTS",
|
|
51
|
+
"description": "Enforced ceiling on LIMIT in all generated SPARQL queries.",
|
|
52
|
+
"format": "string",
|
|
53
|
+
"isRequired": false,
|
|
54
|
+
"default": "100"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "MCP_LOG_LEVEL",
|
|
58
|
+
"description": "Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').",
|
|
59
|
+
"format": "string",
|
|
60
|
+
"isRequired": false,
|
|
61
|
+
"default": "info"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"transport": {
|
|
65
|
+
"type": "stdio"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"registryType": "npm",
|
|
70
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
71
|
+
"identifier": "@cyanheads/eur-lex-mcp-server",
|
|
72
|
+
"runtimeHint": "bun",
|
|
73
|
+
"version": "0.1.1",
|
|
74
|
+
"packageArguments": [
|
|
75
|
+
{
|
|
76
|
+
"type": "positional",
|
|
77
|
+
"value": "run"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"type": "positional",
|
|
81
|
+
"value": "start:http"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"environmentVariables": [
|
|
85
|
+
{
|
|
86
|
+
"name": "MCP_HTTP_HOST",
|
|
87
|
+
"description": "The hostname for the HTTP server.",
|
|
88
|
+
"format": "string",
|
|
89
|
+
"isRequired": false,
|
|
90
|
+
"default": "127.0.0.1"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "MCP_HTTP_PORT",
|
|
94
|
+
"description": "The port to run the HTTP server on.",
|
|
95
|
+
"format": "string",
|
|
96
|
+
"isRequired": false,
|
|
97
|
+
"default": "3010"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "MCP_HTTP_ENDPOINT_PATH",
|
|
101
|
+
"description": "The endpoint path for the MCP server.",
|
|
102
|
+
"format": "string",
|
|
103
|
+
"isRequired": false,
|
|
104
|
+
"default": "/mcp"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "MCP_PUBLIC_URL",
|
|
108
|
+
"description": "Public origin for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).",
|
|
109
|
+
"format": "string",
|
|
110
|
+
"isRequired": false
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "MCP_AUTH_MODE",
|
|
114
|
+
"description": "Authentication mode to use: 'none', 'jwt', or 'oauth'.",
|
|
115
|
+
"format": "string",
|
|
116
|
+
"isRequired": false,
|
|
117
|
+
"default": "none"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "MCP_LOG_LEVEL",
|
|
121
|
+
"description": "Sets the minimum log level for output (e.g., 'debug', 'info', 'warn').",
|
|
122
|
+
"format": "string",
|
|
123
|
+
"isRequired": false,
|
|
124
|
+
"default": "info"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "CELLAR_SPARQL_ENDPOINT",
|
|
128
|
+
"description": "CELLAR SPARQL endpoint URL override (e.g., for a local Virtuoso mirror).",
|
|
129
|
+
"format": "string",
|
|
130
|
+
"isRequired": false,
|
|
131
|
+
"default": "http://publications.europa.eu/webapi/rdf/sparql"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "EURLEX_CONTENT_BASE_URL",
|
|
135
|
+
"description": "EUR-Lex content API base URL override.",
|
|
136
|
+
"format": "string",
|
|
137
|
+
"isRequired": false,
|
|
138
|
+
"default": "https://eur-lex.europa.eu"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "SPARQL_QUERY_TIMEOUT_MS",
|
|
142
|
+
"description": "Client-side timeout for SPARQL requests in milliseconds.",
|
|
143
|
+
"format": "string",
|
|
144
|
+
"isRequired": false,
|
|
145
|
+
"default": "55000"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "MAX_SPARQL_RESULTS",
|
|
149
|
+
"description": "Enforced ceiling on LIMIT in all generated SPARQL queries.",
|
|
150
|
+
"format": "string",
|
|
151
|
+
"isRequired": false,
|
|
152
|
+
"default": "100"
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
"transport": {
|
|
156
|
+
"type": "streamable-http",
|
|
157
|
+
"url": "http://localhost:3010/mcp"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|