eul:get_document
Retrieve full text of EU legislation from EUR-Lex by CELEX number. Extract specific articles, sections, or line ranges. Output content as Markdown or save directly to a file.
Instructions
Retrieve EU legislation from EUR-Lex by CELEX number (e.g., "32016R0679" for GDPR, "32001L0029" for InfoSoc). Returns full text in Markdown. Use section for partial content: "Art. 5", "Artikel 5", or "lines:100-200". Use save_path to save to file instead of returning content.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| celex | Yes | CELEX number (e.g., "32016R0679", "32001L0029", "12016E267") | |
| language | Yes | Language code (default: DE) | DE |
| section | No | Extract section: "Art. 5", "Artikel 5-10", "Kapitel III", or "lines:100-200" | |
| save_path | No | Save full document to this file path instead of returning content |
Implementation Reference
- src/providers/eul/provider.ts:81-117 (handler)The handleGetDocument method handles the 'eul:get_document' tool. It fetches EU legislation from EUR-Lex by CELEX number via HTTP GET, converts HTML to Markdown using EulConverter, optionally extracts a section (by article range, line range, or heading), and optionally saves to a file. Returns the full document in Markdown format.
private async handleGetDocument(args: Record<string, unknown>): Promise<ToolResult> { const { celex, language = 'DE', section, save_path } = args as { celex: string; language?: string; section?: string; save_path?: string; }; logger.info('Fetching EUR-Lex document', { celex, language }); const response = await axios.get(`${CELLAR_BASE}/${celex}`, { headers: { 'Accept': 'text/html, application/xhtml+xml', 'Accept-Language': `${language.toLowerCase()}, en;q=0.8`, }, maxRedirects: 5, responseType: 'text', }); const markdown = this.converter.convert(response.data); validateConversion(markdown, 'EUR-Lex'); if (section) { const extracted = this.extractSection(markdown, section); if (!extracted) { return { content: [{ type: 'text', text: `Section "${section}" not found.` }], isError: true }; } return { content: [{ type: 'text', text: extracted }] }; } const fullDoc = `# ${celex}\n\n---\n\n${markdown}`; if (save_path) { const { writeFile } = await import('fs/promises'); await writeFile(save_path, fullDoc, 'utf-8'); return { content: [{ type: 'text', text: `Saved to ${save_path}\n\nCELEX: ${celex}\nLanguage: ${language}` }] }; } return { content: [{ type: 'text', text: fullDoc }] }; } - The tool definition/schema for 'eul:get_document', specifying its description and inputSchema with Zod validation for celex (string), language (optional, default DE), section (optional), and save_path (optional).
{ name: 'eul:get_document', description: 'Retrieve EU legislation from EUR-Lex by CELEX number (e.g., "32016R0679" for GDPR, "32001L0029" for InfoSoc). ' + 'Returns full text in Markdown. Use `section` for partial content: "Art. 5", "Artikel 5", or "lines:100-200". ' + 'Use `save_path` to save to file instead of returning content.', inputSchema: z.object({ celex: z.string().describe('CELEX number (e.g., "32016R0679", "32001L0029", "12016E267")'), language: z.string().optional().default('DE').describe('Language code (default: DE)'), section: z.string().optional().describe('Extract section: "Art. 5", "Artikel 5-10", "Kapitel III", or "lines:100-200"'), save_path: z.string().optional().describe('Save full document to this file path instead of returning content'), }), }, - src/providers/eul/tools/index.ts:4-31 (registration)The eulTools array is exported and contains the tool definition for 'eul:get_document'. This array is returned by the EulProvider.getTools() method which is called by the MCP server's getAllTools() to list available tools.
export const eulTools: ToolDefinition[] = [ { name: 'eul:search', description: 'Search EU legislation (directives, regulations, treaties) via EUR-Lex SPARQL endpoint. ' + 'Returns CELEX numbers, titles, and dates.', inputSchema: z.object({ query: z.string().describe('Search term in title (e.g., "Urheberrecht", "Datenschutz", "Verbraucherschutz")'), resource_type: z.enum(['any', 'directive', 'regulation', 'decision', 'treaty']).optional().default('any') .describe('Filter by type (default: any)'), language: z.string().optional().default('DE').describe('Language code (default: DE)'), limit: z.number().optional().default(10).describe('Maximum results (default: 10)'), }), }, { name: 'eul:get_document', description: 'Retrieve EU legislation from EUR-Lex by CELEX number (e.g., "32016R0679" for GDPR, "32001L0029" for InfoSoc). ' + 'Returns full text in Markdown. Use `section` for partial content: "Art. 5", "Artikel 5", or "lines:100-200". ' + 'Use `save_path` to save to file instead of returning content.', inputSchema: z.object({ celex: z.string().describe('CELEX number (e.g., "32016R0679", "32001L0029", "12016E267")'), language: z.string().optional().default('DE').describe('Language code (default: DE)'), section: z.string().optional().describe('Extract section: "Art. 5", "Artikel 5-10", "Kapitel III", or "lines:100-200"'), save_path: z.string().optional().describe('Save full document to this file path instead of returning content'), }), }, ]; - src/providers/eul/provider.ts:33-37 (registration)The handleToolCall method in EulProvider routes 'eul:get_document' to handleGetDocument. This is called from the main MCP server's CallToolRequestSchema handler via the centralized handleToolCall function.
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<ToolResult> { if (toolName === 'eul:search') return this.handleSearch(args); if (toolName === 'eul:get_document') return this.handleGetDocument(args); return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true }; } - src/providers/eul/converter.ts:1-44 (helper)EulConverter is a helper class used by handleGetDocument to convert fetched HTML from EUR-Lex into Markdown using TurndownService with custom rules for ELI article titles, subtitles, section headings, and footnote markers.
import TurndownService from 'turndown'; export class EulConverter { private turndown: TurndownService; constructor() { this.turndown = new TurndownService({ headingStyle: 'atx' }); // ELI article titles: <p class="oj-ti-art"> → ## Artikel N this.turndown.addRule('articleTitle', { filter: (node) => node.nodeName === 'P' && node.getAttribute('class') === 'oj-ti-art', replacement: (content) => `\n\n## ${content.trim()}\n\n`, }); // ELI article subtitles: <p class="oj-sti-art"> → ### Subtitle this.turndown.addRule('articleSubtitle', { filter: (node) => node.nodeName === 'P' && node.getAttribute('class') === 'oj-sti-art', replacement: (content) => `### ${content.trim()}\n\n`, }); // Section headings: oj-ti-section-1, oj-ti-section-2 this.turndown.addRule('sectionTitle', { filter: (node) => { const cls = node.getAttribute('class') || ''; return node.nodeName === 'P' && cls.startsWith('oj-ti-section-'); }, replacement: (content, node) => { const cls = (node as HTMLElement).getAttribute('class') || ''; const level = cls.includes('section-1') ? '#' : '##'; return `\n\n${level} ${content.trim()}\n\n`; }, }); // Footnote markers: <span class="oj-super oj-note-tag"> this.turndown.addRule('footnoteTag', { filter: (node) => (node.getAttribute('class') || '').includes('oj-note-tag'), replacement: (content) => `[^${content.trim()}]`, }); } convert(html: string): string { return this.turndown.turndown(html); } }