@cyanheads/eur-lex-mcp-server 0.2.0 → 0.3.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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +3 -2
- package/changelog/0.2.x/0.2.1.md +16 -0
- package/changelog/0.3.x/0.3.0.md +26 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +4 -2
- package/dist/config/server-config.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.d.ts +6 -6
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.js +54 -20
- package/dist/mcp-server/tools/definitions/eurlex-get-cases.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.d.ts +12 -0
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.js +130 -23
- package/dist/mcp-server/tools/definitions/eurlex-get-document.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-lookup-celex.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.d.ts +5 -5
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.js +63 -24
- package/dist/mcp-server/tools/definitions/eurlex-search-documents.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +24 -12
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/services/cellar-sparql/cdm-labels.d.ts +9 -0
- package/dist/services/cellar-sparql/cdm-labels.d.ts.map +1 -1
- package/dist/services/cellar-sparql/cdm-labels.js +16 -0
- package/dist/services/cellar-sparql/cdm-labels.js.map +1 -1
- package/dist/services/eurlex-content/eurlex-content-service.d.ts +42 -9
- package/dist/services/eurlex-content/eurlex-content-service.d.ts.map +1 -1
- package/dist/services/eurlex-content/eurlex-content-service.js +135 -30
- package/dist/services/eurlex-content/eurlex-content-service.js.map +1 -1
- package/package.json +1 -1
- package/server.json +7 -7
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** eur-lex-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.3.0
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.9`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** eur-lex-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.3.0
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.9`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/eur-lex-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/eur-lex-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -62,6 +62,7 @@ Fetch the notice and full text of an EU legal act.
|
|
|
62
62
|
- Accepts CELEX numbers (e.g., `32016R0679`) or ELI URIs
|
|
63
63
|
- Returns structured metadata: title, date, document type, author institution, legal basis, EuroVoc subjects, in-force flag
|
|
64
64
|
- Full text in HTML (default) or Formex4 XML
|
|
65
|
+
- Content shaping for large acts: `content_mode` `"paged"` (default) returns a bounded character window (`offset` + `limit`) with `content_chars_total` and `has_more` so you can page to the end; `"full"` returns the whole body in one call; `"metadata_only"` skips the body
|
|
65
66
|
- Supports all 24 official EU languages; defaults to English with automatic fallback when a translation is unavailable
|
|
66
67
|
- Older acts and some CJEU judgments may lack English translations
|
|
67
68
|
|
|
@@ -275,7 +276,7 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
|
|
|
275
276
|
| Variable | Description | Default |
|
|
276
277
|
|:---------|:------------|:--------|
|
|
277
278
|
| `CELLAR_SPARQL_ENDPOINT` | CELLAR SPARQL endpoint URL override (e.g., for a local Virtuoso mirror). | `http://publications.europa.eu/webapi/rdf/sparql` |
|
|
278
|
-
| `EURLEX_CONTENT_BASE_URL` |
|
|
279
|
+
| `EURLEX_CONTENT_BASE_URL` | EU Publications Office CELLAR content resolver base URL override. | `http://publications.europa.eu` |
|
|
279
280
|
| `SPARQL_QUERY_TIMEOUT_MS` | Client-side timeout for SPARQL requests in milliseconds. | `55000` |
|
|
280
281
|
| `MAX_SPARQL_RESULTS` | Enforced ceiling on LIMIT in all generated SPARQL queries. | `100` |
|
|
281
282
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "eurlex_search_documents and eurlex_get_cases return one row per work, so multi-resource-type works (corrigenda) no longer duplicate or miscount LIMIT/total; constrained optional filters now accept empty string from form-based clients"
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.2.1 — 2026-06-30
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **`resource_type` may be comma-joined** ([#14](https://github.com/cyanheads/eur-lex-mcp-server/issues/14)) — on `eurlex_search_documents` and `eurlex_get_cases`, a work classified under several CDM resource-types (e.g. corrigenda) now reports all of them as one de-duplicated, sorted, comma-separated label string instead of a single arbitrary type. The field stays a `string`. New `resolveResourceTypeLabels` helper in `services/cellar-sparql/cdm-labels.ts`.
|
|
12
|
+
|
|
13
|
+
## Fixed
|
|
14
|
+
|
|
15
|
+
- **Multi-resource-type works returned duplicate rows** ([#14](https://github.com/cyanheads/eur-lex-mcp-server/issues/14)) — the shared search query used `SELECT DISTINCT`, so a work carrying several `cdm:work_has_resource-type` values (corrigenda hold two or three) cross-produced into one row per type, which `DISTINCT` could not merge. The query now `GROUP BY ?work` with `GROUP_CONCAT(DISTINCT …)` for the resource-type and `SAMPLE` for the single-valued fields, returning one row per work. `LIMIT`/`OFFSET` and `total` now bound distinct works rather than raw rows. Affects `eurlex_search_documents` and `eurlex_get_cases`.
|
|
16
|
+
- **Optional filters rejected empty strings from form-based clients** ([#15](https://github.com/cyanheads/eur-lex-mcp-server/issues/15)) — form clients that send `""` for a blank optional field hit a `-32602` validation error on the constrained filters. `document_type`, `date_from`, `date_to`, and `eurovoc_concept` (`eurlex_search_documents`) plus `court`, `case_type`, `date_from`, and `date_to` (`eurlex_get_cases`) now accept `""` as "filter absent" via a `z.union` with a literal-empty variant; a non-empty value still must satisfy its original constraint.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "eurlex_get_document re-sources act text from the EU Publications Office CELLAR resolver and refuses AWS WAF bot-challenge stubs (previously surfaced as content); adds content_mode/offset/limit body pagination with content_* navigation fields and removes the 8,000-char text cut"
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.3.0 — 2026-06-30
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`content_mode`, `offset`, `limit` inputs on `eurlex_get_document`** ([#12](https://github.com/cyanheads/eur-lex-mcp-server/issues/12)) — `content_mode` selects how much body to return: `"paged"` (default) yields a bounded `[offset, offset+limit)` window, `"full"` the entire body in one call, `"metadata_only"` skips the content fetch. `limit` defaults to 25,000 characters, capped at 100,000.
|
|
12
|
+
- **`content_*` navigation output fields** ([#12](https://github.com/cyanheads/eur-lex-mcp-server/issues/12)) — `content_mode`, `content_offset`, `content_chars_returned`, `content_chars_total`, and `has_more` let a client page contiguous windows to the end of an act and reconstruct the full body. Paging past the end returns an empty window with `has_more: false`, not an error.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **Act full text re-sourced from CELLAR content negotiation** ([#16](https://github.com/cyanheads/eur-lex-mcp-server/issues/16)) — `EurLexContentService` fetches `publications.europa.eu/resource/celex/{CELEX}` (`application/xhtml+xml` → `text/html` for HTML, Formex 4 for XML) instead of the WAF-fronted `eur-lex.europa.eu` legal-content endpoint. `Accept-Language` maps EUR-Lex two-letter codes to the ISO 639-2/T forms CELLAR requires; a multi-part Formex `300` response is treated as unavailable rather than reconstructed.
|
|
17
|
+
- **`EURLEX_CONTENT_BASE_URL` default is now `http://publications.europa.eu`** ([#16](https://github.com/cyanheads/eur-lex-mcp-server/issues/16)) — was `https://eur-lex.europa.eu`. The env var name is unchanged; `server.json`, `README.md`, and `.env.example` updated to match the CELLAR content-negotiation source.
|
|
18
|
+
- **`content_available` distinguishes "not requested" from "unavailable"** ([#12](https://github.com/cyanheads/eur-lex-mcp-server/issues/12)) — now `false` in `"metadata_only"` mode (no fetch attempted); pair it with `content_mode` to tell the two apart.
|
|
19
|
+
|
|
20
|
+
## Removed
|
|
21
|
+
|
|
22
|
+
- **The 8,000-character `format()` content cut** ([#12](https://github.com/cyanheads/eur-lex-mcp-server/issues/12)) — the text view and `structuredContent.content` honor the same `content_mode` window, so there is no separate downstream truncation.
|
|
23
|
+
|
|
24
|
+
## Fixed
|
|
25
|
+
|
|
26
|
+
- **AWS WAF bot-challenge stub surfaced as act content** ([#16](https://github.com/cyanheads/eur-lex-mcp-server/issues/16)) — `eurlex_get_document` returned the JavaScript bot-challenge interstitial (the `awswaf`/`gokuProps` stub) as `content` with `content_available: true` for every CELEX. A response carrying a WAF challenge signature is now detected and raised as a `content_challenge` / `ServiceUnavailable` error rather than reported as available content.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;iBA4BtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAQ9C"}
|
|
@@ -14,8 +14,10 @@ const ServerConfigSchema = z.object({
|
|
|
14
14
|
eurLexContentBaseUrl: z
|
|
15
15
|
.string()
|
|
16
16
|
.url()
|
|
17
|
-
.default('
|
|
18
|
-
.describe('
|
|
17
|
+
.default('http://publications.europa.eu')
|
|
18
|
+
.describe('Base URL of the EU Publications Office CELLAR content-negotiation resolver, which serves ' +
|
|
19
|
+
'act text via /resource/celex/{CELEX}. Replaces the WAF-protected eur-lex.europa.eu ' +
|
|
20
|
+
'legal-content endpoint, which now returns an AWS WAF bot-challenge stub (issue #16).'),
|
|
19
21
|
sparqlQueryTimeoutMs: z.coerce
|
|
20
22
|
.number()
|
|
21
23
|
.int()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,oBAAoB,EAAE,CAAC;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,iDAAiD,CAAC;SAC1D,QAAQ,CAAC,uCAAuC,CAAC;IACpD,oBAAoB,EAAE,CAAC;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,oBAAoB,EAAE,CAAC;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,iDAAiD,CAAC;SAC1D,QAAQ,CAAC,uCAAuC,CAAC;IACpD,oBAAoB,EAAE,CAAC;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,+BAA+B,CAAC;SACxC,QAAQ,CACP,2FAA2F;QACzF,qFAAqF;QACrF,sFAAsF,CACzF;IACH,oBAAoB,EAAE,CAAC,CAAC,MAAM;SAC3B,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,yDAAyD,CAAC;IACtE,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,GAAG,CAAC,GAAG,CAAC;SACR,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,2DAA2D,CAAC;CACzE,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,oBAAoB,EAAE,wBAAwB;QAC9C,oBAAoB,EAAE,yBAAyB;QAC/C,oBAAoB,EAAE,yBAAyB;QAC/C,gBAAgB,EAAE,oBAAoB;KACvC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -7,17 +7,17 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
|
7
7
|
export declare const eurlex_get_cases: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
8
|
case_number: z.ZodOptional<z.ZodString>;
|
|
9
9
|
keyword: z.ZodOptional<z.ZodString>;
|
|
10
|
-
court: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
court: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodEnum<{
|
|
11
11
|
CJEU: "CJEU";
|
|
12
12
|
GC: "GC";
|
|
13
|
-
}>>;
|
|
14
|
-
case_type: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
}>]>>;
|
|
14
|
+
case_type: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodEnum<{
|
|
15
15
|
judgment: "judgment";
|
|
16
16
|
order: "order";
|
|
17
17
|
ag_opinion: "ag_opinion";
|
|
18
|
-
}>>;
|
|
19
|
-
date_from: z.ZodOptional<z.ZodString
|
|
20
|
-
date_to: z.ZodOptional<z.ZodString
|
|
18
|
+
}>]>>;
|
|
19
|
+
date_from: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>;
|
|
20
|
+
date_to: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>;
|
|
21
21
|
offset: z.ZodDefault<z.ZodNumber>;
|
|
22
22
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
23
23
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -1 +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;
|
|
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;AAqCjE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyS3B,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
-
import { ENG_LANGUAGE_URI,
|
|
7
|
+
import { ENG_LANGUAGE_URI, resolveResourceTypeLabels, } from '../../../services/cellar-sparql/cdm-labels.js';
|
|
8
8
|
import { CellarSparqlService, getCellarSparqlService, } from '../../../services/cellar-sparql/cellar-sparql-service.js';
|
|
9
9
|
/** Case type to CELEX type substring mapping. */
|
|
10
10
|
const CASE_TYPE_PATTERN = {
|
|
@@ -52,23 +52,43 @@ export const eurlex_get_cases = tool('eurlex_get_cases', {
|
|
|
52
52
|
.optional()
|
|
53
53
|
.describe('Keyword to match against case titles and CELEX strings.'),
|
|
54
54
|
court: z
|
|
55
|
-
.
|
|
55
|
+
.union([
|
|
56
|
+
z.literal(''),
|
|
57
|
+
z.enum(['CJEU', 'GC']).describe('CJEU = Court of Justice of the EU, GC = General Court.'),
|
|
58
|
+
])
|
|
56
59
|
.optional()
|
|
57
|
-
.describe('Court filter: CJEU = Court of Justice of the EU, GC = General Court.'
|
|
60
|
+
.describe('Court filter: CJEU = Court of Justice of the EU, GC = General Court. ' +
|
|
61
|
+
'Leave blank or omit to search both courts.'),
|
|
58
62
|
case_type: z
|
|
59
|
-
.
|
|
63
|
+
.union([
|
|
64
|
+
z.literal(''),
|
|
65
|
+
z
|
|
66
|
+
.enum(['judgment', 'order', 'ag_opinion'])
|
|
67
|
+
.describe('judgment, order (procedural decision), or ag_opinion (Advocate General opinion).'),
|
|
68
|
+
])
|
|
60
69
|
.optional()
|
|
61
|
-
.describe('Case type filter: judgment, order (procedural decision), or ag_opinion (Advocate General opinion).'
|
|
70
|
+
.describe('Case type filter: judgment, order (procedural decision), or ag_opinion (Advocate General opinion). ' +
|
|
71
|
+
'Leave blank or omit to search all case types.'),
|
|
62
72
|
date_from: z
|
|
63
|
-
.
|
|
64
|
-
.
|
|
73
|
+
.union([
|
|
74
|
+
z.literal(''),
|
|
75
|
+
z
|
|
76
|
+
.string()
|
|
77
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
78
|
+
.describe('Start date in ISO 8601 format (YYYY-MM-DD).'),
|
|
79
|
+
])
|
|
65
80
|
.optional()
|
|
66
|
-
.describe('Start of date range in ISO 8601 format (YYYY-MM-DD).'),
|
|
81
|
+
.describe('Start of date range in ISO 8601 format (YYYY-MM-DD). Leave blank or omit for no lower bound.'),
|
|
67
82
|
date_to: z
|
|
68
|
-
.
|
|
69
|
-
.
|
|
83
|
+
.union([
|
|
84
|
+
z.literal(''),
|
|
85
|
+
z
|
|
86
|
+
.string()
|
|
87
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
88
|
+
.describe('End date in ISO 8601 format (YYYY-MM-DD).'),
|
|
89
|
+
])
|
|
70
90
|
.optional()
|
|
71
|
-
.describe('End of date range in ISO 8601 format (YYYY-MM-DD).'),
|
|
91
|
+
.describe('End of date range in ISO 8601 format (YYYY-MM-DD). Leave blank or omit for no upper bound.'),
|
|
72
92
|
offset: z
|
|
73
93
|
.number()
|
|
74
94
|
.int()
|
|
@@ -92,7 +112,9 @@ export const eurlex_get_cases = tool('eurlex_get_cases', {
|
|
|
92
112
|
resource_type: z
|
|
93
113
|
.string()
|
|
94
114
|
.optional()
|
|
95
|
-
.describe('Human-readable case type label (e.g. "Judgment", "Order", "AG Opinion").
|
|
115
|
+
.describe('Human-readable case type label (e.g. "Judgment", "Order", "AG Opinion"). ' +
|
|
116
|
+
'Cases classified under several resource-types (e.g. corrigenda) list all labels, comma-separated. ' +
|
|
117
|
+
'Absent for some older cases.'),
|
|
96
118
|
date: z.string().optional().describe('Judgment/opinion date in ISO 8601 format.'),
|
|
97
119
|
title: z
|
|
98
120
|
.string()
|
|
@@ -176,8 +198,20 @@ export const eurlex_get_cases = tool('eurlex_get_cases', {
|
|
|
176
198
|
// ?expr cdm:expression_belongs_to_work ?work (inverse of cdm:work_has_expression)
|
|
177
199
|
// ?expr cdm:expression_uses_language <.../ENG>
|
|
178
200
|
// ?expr cdm:expression_title ?title
|
|
201
|
+
//
|
|
202
|
+
// GROUP BY ?work collapses each work to one row. A work can carry several
|
|
203
|
+
// cdm:work_has_resource-type values (corrigenda hold 2–3); without grouping these
|
|
204
|
+
// cross-product into duplicate rows that SELECT DISTINCT cannot merge (?type
|
|
205
|
+
// differs per row), and LIMIT/OFFSET would then page over raw rows rather than
|
|
206
|
+
// works. GROUP_CONCAT gathers every resource-type URI per work; SAMPLE picks the
|
|
207
|
+
// single-valued display fields. Aggregate aliases are renamed (?celex/?docDate/
|
|
208
|
+
// ?docTitle) because a projected AS-variable cannot reuse a name already in scope.
|
|
179
209
|
const sparql = `
|
|
180
|
-
SELECT
|
|
210
|
+
SELECT ?work
|
|
211
|
+
(SAMPLE(?celexNumber) AS ?celex)
|
|
212
|
+
(GROUP_CONCAT(DISTINCT STR(?type); SEPARATOR=" ") AS ?types)
|
|
213
|
+
(SAMPLE(?date) AS ?docDate)
|
|
214
|
+
(SAMPLE(?title) AS ?docTitle) WHERE {
|
|
181
215
|
?work cdm:resource_legal_id_celex ?celexNumber .
|
|
182
216
|
OPTIONAL { ?work cdm:work_has_resource-type ?type . }
|
|
183
217
|
OPTIONAL { ?work cdm:work_date_document ?date . }
|
|
@@ -187,7 +221,7 @@ SELECT DISTINCT ?work ?celexNumber ?type ?date ?title WHERE {
|
|
|
187
221
|
?expr cdm:expression_title ?title .
|
|
188
222
|
}
|
|
189
223
|
${filters.join('\n ')}
|
|
190
|
-
} ORDER BY DESC(?
|
|
224
|
+
} GROUP BY ?work ORDER BY DESC(?docDate) LIMIT ${input.limit} OFFSET ${input.offset}`;
|
|
191
225
|
const queryEcho = {
|
|
192
226
|
...(input.case_number ? { case_number: input.case_number } : {}),
|
|
193
227
|
...(celexFragment ? { celex_fragment: celexFragment } : {}),
|
|
@@ -215,15 +249,15 @@ SELECT DISTINCT ?work ?celexNumber ?type ?date ?title WHERE {
|
|
|
215
249
|
const cases = bindings.map((b) => {
|
|
216
250
|
const c = {
|
|
217
251
|
work_uri: CellarSparqlService.bindingValue(b, 'work') ?? '',
|
|
218
|
-
celex_number: CellarSparqlService.bindingValue(b, '
|
|
252
|
+
celex_number: CellarSparqlService.bindingValue(b, 'celex') ?? '',
|
|
219
253
|
};
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
c.resource_type =
|
|
223
|
-
const date = CellarSparqlService.bindingValue(b, '
|
|
254
|
+
const resourceType = resolveResourceTypeLabels(CellarSparqlService.bindingValue(b, 'types'));
|
|
255
|
+
if (resourceType)
|
|
256
|
+
c.resource_type = resourceType;
|
|
257
|
+
const date = CellarSparqlService.bindingValue(b, 'docDate');
|
|
224
258
|
if (date)
|
|
225
259
|
c.date = date;
|
|
226
|
-
const title = CellarSparqlService.bindingValue(b, '
|
|
260
|
+
const title = CellarSparqlService.bindingValue(b, 'docTitle');
|
|
227
261
|
if (title)
|
|
228
262
|
c.title = title;
|
|
229
263
|
return c;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurlex-get-cases.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurlex-get-cases.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,
|
|
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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"}
|
|
@@ -12,6 +12,13 @@ export declare const eurlex_get_document: import("@cyanheads/mcp-ts-core").ToolD
|
|
|
12
12
|
html: "html";
|
|
13
13
|
xml: "xml";
|
|
14
14
|
}>>;
|
|
15
|
+
content_mode: z.ZodDefault<z.ZodEnum<{
|
|
16
|
+
full: "full";
|
|
17
|
+
metadata_only: "metadata_only";
|
|
18
|
+
paged: "paged";
|
|
19
|
+
}>>;
|
|
20
|
+
offset: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
15
22
|
}, z.core.$strip>, z.ZodObject<{
|
|
16
23
|
celex_number: z.ZodString;
|
|
17
24
|
work_uri: z.ZodOptional<z.ZodString>;
|
|
@@ -23,7 +30,12 @@ export declare const eurlex_get_document: import("@cyanheads/mcp-ts-core").ToolD
|
|
|
23
30
|
eurovoc_subjects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
31
|
in_force: z.ZodOptional<z.ZodBoolean>;
|
|
25
32
|
content: z.ZodOptional<z.ZodString>;
|
|
33
|
+
content_mode: z.ZodString;
|
|
26
34
|
content_available: z.ZodBoolean;
|
|
35
|
+
content_offset: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
content_chars_returned: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
content_chars_total: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
has_more: z.ZodBoolean;
|
|
27
39
|
language: z.ZodString;
|
|
28
40
|
language_fallback: z.ZodOptional<z.ZodString>;
|
|
29
41
|
content_format: z.ZodString;
|
|
@@ -1 +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;
|
|
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;AA2BjE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6Z9B,CAAC"}
|
|
@@ -8,6 +8,14 @@ import { ENG_LANGUAGE_URI, resolveCorporateBodyLabel, resolveResourceTypeLabel,
|
|
|
8
8
|
import { CellarSparqlService, getCellarSparqlService, } from '../../../services/cellar-sparql/cellar-sparql-service.js';
|
|
9
9
|
import { escapeSparqlLiteral, resolveEliToWork } from '../../../services/cellar-sparql/eli-resolution.js';
|
|
10
10
|
import { getEurLexContentService, } from '../../../services/eurlex-content/eurlex-content-service.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default character window returned for body content in "paged" mode — bounds a
|
|
13
|
+
* single call while keeping small acts whole. The tail of a larger act is never
|
|
14
|
+
* lost: page forward with `offset`, or request `content_mode: "full"`.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_CONTENT_LIMIT = 25_000;
|
|
17
|
+
/** Hard ceiling on one paged window. Use `content_mode: "full"` for the whole body in a single call. */
|
|
18
|
+
const MAX_CONTENT_LIMIT = 100_000;
|
|
11
19
|
export const eurlex_get_document = tool('eurlex_get_document', {
|
|
12
20
|
title: 'Get EU Document',
|
|
13
21
|
description: 'Fetch the notice (metadata) and full text of an EU act by CELEX number or ELI URI. ' +
|
|
@@ -18,7 +26,10 @@ export const eurlex_get_document = tool('eurlex_get_document', {
|
|
|
18
26
|
'If the requested language is unavailable, the server automatically falls back to English and notes the fallback. ' +
|
|
19
27
|
'CELEX format: {sector}{year}{type}{number} e.g. 32016R0679 for GDPR. ' +
|
|
20
28
|
'Use eurlex_lookup_celex to validate an identifier before calling this tool. ' +
|
|
21
|
-
'HTML format returns the full act text suitable for reading; XML returns Formex4 for structured processing.'
|
|
29
|
+
'HTML format returns the full act text suitable for reading; XML returns Formex4 for structured processing. ' +
|
|
30
|
+
'Large bodies are bounded per call but never lost: content_mode "paged" (default) returns a character window ' +
|
|
31
|
+
'(offset + limit) alongside content_chars_total and has_more, so you can page to the end and reconstruct the whole act; ' +
|
|
32
|
+
'content_mode "full" returns the entire body in one call; content_mode "metadata_only" returns metadata with no body and skips the content fetch.',
|
|
22
33
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
23
34
|
input: z.object({
|
|
24
35
|
celex_number: z
|
|
@@ -42,6 +53,27 @@ export const eurlex_get_document = tool('eurlex_get_document', {
|
|
|
42
53
|
.enum(['html', 'xml'])
|
|
43
54
|
.default('html')
|
|
44
55
|
.describe('Content format: "html" for readable HTML text (default), "xml" for Formex4 XML structured format.'),
|
|
56
|
+
content_mode: z
|
|
57
|
+
.enum(['metadata_only', 'paged', 'full'])
|
|
58
|
+
.default('paged')
|
|
59
|
+
.describe('How much of the document body to return. "paged" (default) returns a bounded character window — see offset/limit; ' +
|
|
60
|
+
'"full" returns the entire body in one call (large acts can be hundreds of KB); ' +
|
|
61
|
+
'"metadata_only" returns metadata with no body and skips the content fetch. offset and limit apply only to "paged".'),
|
|
62
|
+
offset: z
|
|
63
|
+
.number()
|
|
64
|
+
.int()
|
|
65
|
+
.min(0)
|
|
66
|
+
.default(0)
|
|
67
|
+
.describe('Character offset into the full document body where the returned window starts ("paged" mode only). ' +
|
|
68
|
+
'Page forward by setting offset = content_offset + content_chars_returned from the previous call.'),
|
|
69
|
+
limit: z
|
|
70
|
+
.number()
|
|
71
|
+
.int()
|
|
72
|
+
.min(1)
|
|
73
|
+
.max(MAX_CONTENT_LIMIT)
|
|
74
|
+
.default(DEFAULT_CONTENT_LIMIT)
|
|
75
|
+
.describe(`Maximum characters of body content to return in this window ("paged" mode only). Default ${DEFAULT_CONTENT_LIMIT}, max ${MAX_CONTENT_LIMIT}. ` +
|
|
76
|
+
'For the entire body in one response, use content_mode "full" instead of a large limit.'),
|
|
45
77
|
}),
|
|
46
78
|
output: z.object({
|
|
47
79
|
celex_number: z.string().describe('Confirmed CELEX number for the retrieved work.'),
|
|
@@ -71,8 +103,36 @@ export const eurlex_get_document = tool('eurlex_get_document', {
|
|
|
71
103
|
content: z
|
|
72
104
|
.string()
|
|
73
105
|
.optional()
|
|
74
|
-
.describe('
|
|
75
|
-
|
|
106
|
+
.describe('Body content of the act in the requested format and language. In "paged" mode this is a character window ' +
|
|
107
|
+
'(see content_offset / content_chars_returned / has_more); in "full" mode the entire body; ' +
|
|
108
|
+
'omitted in "metadata_only" mode, when the window is empty (offset past the end), or when content is unavailable.'),
|
|
109
|
+
content_mode: z
|
|
110
|
+
.string()
|
|
111
|
+
.describe('Content mode applied to this response: "metadata_only", "paged", or "full".'),
|
|
112
|
+
content_available: z
|
|
113
|
+
.boolean()
|
|
114
|
+
.describe('Whether body content was fetched from EUR-Lex. False in "metadata_only" mode (no fetch attempted) — ' +
|
|
115
|
+
'use content_mode to distinguish "not requested" from "unavailable upstream".'),
|
|
116
|
+
content_offset: z
|
|
117
|
+
.number()
|
|
118
|
+
.int()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Character offset where the returned content window begins. Present when a body was fetched and available.'),
|
|
121
|
+
content_chars_returned: z
|
|
122
|
+
.number()
|
|
123
|
+
.int()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe('Number of body characters returned in this response (equals content length). Present when a body was fetched and available.'),
|
|
126
|
+
content_chars_total: z
|
|
127
|
+
.number()
|
|
128
|
+
.int()
|
|
129
|
+
.optional()
|
|
130
|
+
.describe('Total character length of the full document body. Present when content was fetched and available; ' +
|
|
131
|
+
'use with content_offset to page through the entire act.'),
|
|
132
|
+
has_more: z
|
|
133
|
+
.boolean()
|
|
134
|
+
.describe('True when body content exists beyond the returned window. Page forward with offset = content_offset + content_chars_returned, ' +
|
|
135
|
+
'or request content_mode "full" for the entire act in one call. Always false in "metadata_only" mode.'),
|
|
76
136
|
language: z.string().describe('Language code of the returned content.'),
|
|
77
137
|
language_fallback: z
|
|
78
138
|
.string()
|
|
@@ -183,12 +243,18 @@ SELECT ?work ?celexNumber ?type ?date ?title ?inForce ?author ?legalBasis ?eurov
|
|
|
183
243
|
const inForceStr = CellarSparqlService.bindingValue(first, 'inForce');
|
|
184
244
|
const inForce = inForceStr !== undefined ? inForceStr === 'true' : undefined;
|
|
185
245
|
const authorUri = CellarSparqlService.bindingValue(first, 'author');
|
|
186
|
-
// Step 2:
|
|
187
|
-
|
|
246
|
+
// Step 2: assemble metadata, then shape the body per content_mode. The body
|
|
247
|
+
// is one navigable mechanism — "metadata_only" skips the fetch entirely,
|
|
248
|
+
// "full" returns the whole body, and "paged" returns a bounded
|
|
249
|
+
// [offset, offset+limit) window with content_chars_total + has_more so the
|
|
250
|
+
// tail is always reachable. The same shaped `content` feeds both
|
|
251
|
+
// structuredContent and format(); there is no separate truncation downstream.
|
|
188
252
|
const result = {
|
|
189
253
|
celex_number: confirmedCelex,
|
|
190
|
-
|
|
191
|
-
|
|
254
|
+
content_mode: input.content_mode,
|
|
255
|
+
content_available: false,
|
|
256
|
+
has_more: false,
|
|
257
|
+
language,
|
|
192
258
|
content_format: format,
|
|
193
259
|
};
|
|
194
260
|
if (workUri)
|
|
@@ -207,11 +273,36 @@ SELECT ?work ?celexNumber ?type ?date ?title ?inForce ?author ?legalBasis ?eurov
|
|
|
207
273
|
result.eurovoc_subjects = [...eurovocConcepts];
|
|
208
274
|
if (typeof inForce === 'boolean')
|
|
209
275
|
result.in_force = inForce;
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
276
|
+
if (input.content_mode !== 'metadata_only') {
|
|
277
|
+
const contentResult = await contentSvc.fetchContent(celexNumber, language, format, ctx);
|
|
278
|
+
result.content_available = contentResult.contentAvailable;
|
|
279
|
+
result.language = contentResult.language;
|
|
280
|
+
if (contentResult.languageFallback) {
|
|
281
|
+
result.language_fallback = contentResult.languageFallback;
|
|
282
|
+
}
|
|
283
|
+
if (contentResult.contentAvailable && contentResult.content) {
|
|
284
|
+
const full = contentResult.content;
|
|
285
|
+
const total = full.length;
|
|
286
|
+
result.content_chars_total = total;
|
|
287
|
+
if (input.content_mode === 'full') {
|
|
288
|
+
result.content = full;
|
|
289
|
+
result.content_offset = 0;
|
|
290
|
+
result.content_chars_returned = total;
|
|
291
|
+
result.has_more = false;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// Bounded [offset, offset+limit) window over the full body. offset is
|
|
295
|
+
// clamped to the body length so over-paging returns an empty window
|
|
296
|
+
// (has_more false) rather than erroring.
|
|
297
|
+
const offset = Math.min(input.offset, total);
|
|
298
|
+
const windowText = full.slice(offset, offset + input.limit);
|
|
299
|
+
result.content_offset = offset;
|
|
300
|
+
result.content_chars_returned = windowText.length;
|
|
301
|
+
result.has_more = offset + windowText.length < total;
|
|
302
|
+
if (windowText.length > 0)
|
|
303
|
+
result.content = windowText;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
215
306
|
}
|
|
216
307
|
return result;
|
|
217
308
|
},
|
|
@@ -238,22 +329,38 @@ SELECT ?work ?celexNumber ?type ?date ?title ?inForce ?author ?legalBasis ?eurov
|
|
|
238
329
|
lines.push(`**Language:** ${result.language} | **Format:** ${result.content_format}`);
|
|
239
330
|
if (result.language_fallback)
|
|
240
331
|
lines.push(`*Note: ${result.language_fallback}*`);
|
|
241
|
-
|
|
242
|
-
|
|
332
|
+
// Body rendering honors the same window as structuredContent.content — the
|
|
333
|
+
// shaped content is emitted verbatim with a navigation line; no second cut.
|
|
334
|
+
if (result.content_mode === 'metadata_only') {
|
|
243
335
|
lines.push('');
|
|
244
|
-
lines.push('
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
if (result.content
|
|
249
|
-
|
|
250
|
-
|
|
336
|
+
lines.push('*Body omitted (content_mode "metadata_only"). Request content_mode "paged" or "full" to retrieve the text.*');
|
|
337
|
+
}
|
|
338
|
+
else if (result.content_available) {
|
|
339
|
+
const total = result.content_chars_total ?? result.content?.length ?? 0;
|
|
340
|
+
if (result.content) {
|
|
341
|
+
const start = result.content_offset ?? 0;
|
|
342
|
+
const returned = result.content_chars_returned ?? result.content.length;
|
|
343
|
+
const end = start + returned;
|
|
344
|
+
if (result.content_mode === 'full') {
|
|
345
|
+
lines.push(`**Content** (full): full body — ${returned} of ${total} characters.`);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
lines.push(`**Content** (${result.content_mode}): characters ${start}–${end} of ${total} (${returned} returned).` +
|
|
349
|
+
(result.has_more
|
|
350
|
+
? ` More available — page forward with offset=${end}, or content_mode="full" for the entire act.`
|
|
351
|
+
: ' End of document.'));
|
|
352
|
+
}
|
|
353
|
+
lines.push('');
|
|
354
|
+
lines.push('---');
|
|
355
|
+
lines.push('');
|
|
356
|
+
lines.push(result.content);
|
|
251
357
|
}
|
|
252
358
|
else {
|
|
253
|
-
lines.push(
|
|
359
|
+
lines.push('');
|
|
360
|
+
lines.push(`*No content at offset ${result.content_offset ?? 0} — past the end of the ${total}-character body. Lower offset to read.*`);
|
|
254
361
|
}
|
|
255
362
|
}
|
|
256
|
-
else
|
|
363
|
+
else {
|
|
257
364
|
lines.push('');
|
|
258
365
|
lines.push('*Document content is not available for this work in the requested language.*');
|
|
259
366
|
}
|