
Plugs Claude directly into the European Union's CELLAR database and EUR-Lex content APIs. Seven tools cover the full research workflow: keyword search across 2.7 million legislative acts with EuroVoc subject filtering, full-text retrieval in 24 languages, CELEX and ELI citation resolution, CJEU case law search, and graph traversal for amendments, consolidations, and legal basis chains. Raw SPARQL access included when you need custom CDM ontology queries. Built on the public CELLAR endpoint, so no API key required. Reach for this when you're doing EU regulatory analysis, tracking transposition measures, or need structured access to treaties and directives instead of scraping HTML.
Search EU legislation, CJEU case law, and treaties; traverse the CELLAR relationship graph; resolve EuroVoc concepts via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://eur-lex.caseyjhand.com/mcp
EU legislation, CJEU case law, and treaties over the EU Publications Office's CELLAR semantic repository and the EUR-Lex content API. Search documents and case law, fetch full text, resolve citations, traverse the amendment and citation graph, and browse the EuroVoc thesaurus from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
eurlex_search_documents | Search EU legislation, treaties, and preparatory acts by type, date, EuroVoc subject, author institution, and in-force status |
eurlex_get_document | Fetch metadata and full text (HTML, Markdown, or Formex4 XML) for an act by CELEX, ELI, or work URI |
eurlex_lookup_celex | Resolve a CELEX number or ELI URI to its canonical CELLAR work |
eurlex_get_cases | Search CJEU and General Court case law by case number, court, case type, and date range |
eurlex_get_relations | Traverse the CELLAR relationship graph — amendments, repeals, consolidations, legal basis, citations, transpositions |
eurlex_browse_subjects | Search the EuroVoc thesaurus to resolve terms to concept URIs |
eurlex_query_sparql | Run a raw, read-only SPARQL SELECT against the CELLAR endpoint |
| Resource | Description |
|---|---|
eurlex://document/{celexNumber} | Metadata snapshot for a CELLAR work |
eurlex://document/{celexNumber}/relations | One-hop relationship summary for a CELLAR work |
All resource data is also reachable via tools.
| Prompt | Description |
|---|---|
eurlex_comparative_analysis | Frame a comparative EU/US legal analysis for a policy domain |
eurlex_search_documents tooldocument_type (REG, DIR, DEC, TREATY, JUDG, OPIN_AG, PROP, REC) expands to its full CELLAR authority family; include_consolidated folds in consolidated texts of that categorydate_from/date_to), EuroVoc concept URI (from eurlex_browse_subjects), author institution, and in-force-only filtersoffset and limit (max 100); each result flags is_consolidatedno_filters, invalid_date_range, no_resultseurlex_get_document toolcelex_number, eli_uri, or work_urihtml (default), markdown (server-side converted), or xml (Formex4); all 24 EUR-Lex language codes, case-insensitive, defaulting to and falling back to Englishcontent_mode "paged" (default, offset/limit window), "full" (first window from zero), or "metadata_only"; paged and full windows both cap at 100,000 characters, with content_chars_total/has_more to page the restoutline: true returns chapter/article/annex/recital headings with offsets; select (e.g. { articles: "1,5,17" }) returns just those sectionsresolve: "current_consolidated" serves the newest consolidated version instead of the requested base act; is_superseded/current_consolidated_celex/consolidated_as_of flag a stale base act either waycontent_challenge error when EUR-Lex returns a WAF bot-challenge instead of texteurlex_lookup_celex toolidentifier_type auto-detects the format or can be set explicitlyfound: false for a well-formed identifier that matches no workambiguous_identifier error when auto-detection can't classify the inputeurlex_get_cases toolcase_number (C-131/12 / T-131/12), court (CJEU or GC), case_type (judgment, order, ag_opinion), keyword, and date rangeinclude_derivative includes themdisplay_title, parties, subject_matter, case_referenceoffset and limit (max 100)eurlex_get_relations toolcelex_number or work_urirelation_types filters to a subset of cites, amends, amended_by, repeals, repealed_by, implicitly_repeals, implicitly_repealed_by, legal_basis, consolidated_version, national_transposition; omit for alloffset/limit (max 100, default 100)relation_type, direction (outgoing/incoming), related_work_uri, and related_celex_number when knownempty_relation_types distinguishes "no edges of this type" from "edges paged out of this window"; no_relations fires only when the first page is emptyeurlex_browse_subjects toollanguage; defaults to Englishoffset and limit (max 50)eurlex_query_sparql toolcdm:, skos:, and xsd: prefixes are auto-injected; results capped at 100 rowstimeout_hint (1000–55000 ms); the Virtuoso endpoint enforces a 60-second hard limitnot_read_only, sparql_error, sparql_timeouteurlex://document/{celexNumber} resourceapplication/json — resource type, author institution(s), date, title, in-force flag, legal basis, EuroVoc subjectscelexNumber comes from eurlex_search_documents, eurlex_get_cases, or eurlex_lookup_celexeurlex://document/{celexNumber}/relations resourcetruncated plus a continuation pointer to eurlex_get_relations when more relations existeurlex_comparative_analysis promptdomain required; focus optional, folded into its matching analysis axis or added as its own sectioneurlex_browse_subjects → eurlex_search_documents → eurlex_get_document → eurlex_get_relations for the EU side and courtlistener_search_opinions for the US side, plus a six-axis analysis frameworkBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
EUR-Lex-specific:
/resource/celex/{CELEX}); HTML and Formex4 XML pass through, Markdown is converted server-sideVirtuoso 37000 Error body) are classified and re-raised as ServiceUnavailable or InvalidParamsAgent-friendly output:
eurlex_browse_subjects before concept-filtered searcheseurlex_lookup_celex confirms CELEX/ELI existence upfront, preventing downstream errors in document or relation fetchescontent_status, content_unavailability_reason, and requested/effective language fields distinguish skipped, available, absent, upstream-failed, and incomplete content without string parsingreason codes on every tool's error contract let agents branch on outcomes programmaticallyA public instance is available at https://eur-lex.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"eur-lex-mcp-server": {
"type": "streamable-http",
"url": "https://eur-lex.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file. No API key is required.
{
"mcpServers": {
"eur-lex-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/eur-lex-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"eur-lex-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/eur-lex-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"eur-lex-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/eur-lex-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/eur-lex-mcp-server.git
cd eur-lex-mcp-server
bun install
cp .env.example .env
# All server-specific vars have sensible defaults — no required vars
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
| Variable | Description | Default |
|---|---|---|
CELLAR_SPARQL_ENDPOINT | CELLAR SPARQL endpoint URL override (e.g., for a local Virtuoso mirror). | http://publications.europa.eu/webapi/rdf/sparql |
EURLEX_CONTENT_BASE_URL | EU Publications Office CELLAR content resolver base URL override. | http://publications.europa.eu |
SPARQL_QUERY_TIMEOUT_MS | Client-side timeout for SPARQL requests in milliseconds. | 55000 |
MAX_SPARQL_RESULTS | Enforced ceiling on LIMIT in all generated SPARQL queries. | 100 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_SESSION_MODE | Session handling: stateful, stateless, or auto (auto resolves to stateful). | stateless |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t eur-lex-mcp-server .
docker run --rm -p 3010:3010 eur-lex-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/eur-lex-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, resources, and prompts; initializes services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/services/cellar-sparql | CELLAR SPARQL service — POST client, binding mapper, LIMIT enforcement, CDM PREFIX declarations. |
src/services/eurlex-content | CELLAR content service — content-negotiation GET client for /resource/celex/{CELEX} (Accept / Accept-Language) with English language fallback. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Seven tools across document search, retrieval, resolution, case law, relations, EuroVoc, and raw SPARQL. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Metadata and relations resources. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Comparative analysis prompt. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
CELLAR_SPARQL_ENDPOINTdefault: http://publications.europa.eu/webapi/rdf/sparqlCELLAR SPARQL endpoint URL override (e.g., for a local Virtuoso mirror).
EURLEX_CONTENT_BASE_URLdefault: https://eur-lex.europa.euEUR-Lex content API base URL override.
SPARQL_QUERY_TIMEOUT_MSdefault: 55000Client-side timeout for SPARQL requests in milliseconds.
MAX_SPARQL_RESULTSdefault: 100Enforced ceiling on LIMIT in all generated SPARQL queries.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_PUBLIC_URLPublic origin for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.