European Parliament MCP Server API - v1.3.9
    Preparing search index...

    Hack23 Logo

    πŸ“Š European Parliament MCP Server β€” Data Model

    Entity Relationships, Branded Types, and EP API Data Structures
    Comprehensive data model documentation for parliamentary intelligence data

    Owner Version Effective Date Review Cycle

    πŸ“‹ Document Owner: Hack23 | πŸ“„ Version: 1.2 | πŸ“… Last Updated: 2026-04-21 (UTC) πŸ”„ Review Cycle: Quarterly | ⏰ Next Review: 2026-07-21 🏷️ Classification: Public (Open Source MCP Server) βœ… ISMS Compliance: ISO 27001 (A.5.1, A.8.1, A.14.2), NIST CSF 2.0 (ID.AM, PR.DS), CIS Controls v8.1 (2.1, 16.1)


    1. Security Documentation Map
    2. Overview
    3. Core Entity Relationships
    4. MEP Entity Model
    5. Procedure Entity Model
    6. Meeting and Session Model
    7. Document Entity Model
    8. Vote Entity Model
    9. OSINT Output and Data Quality Model
    10. Branded Type Documentation
    11. Zod Schema Overview
    12. EP API JSON-LD Structure
    13. Cache Key Patterns

    Document Current Future Description
    Architecture ARCHITECTURE.md FUTURE_ARCHITECTURE.md C4 model, containers, components, ADRs
    Security Architecture SECURITY_ARCHITECTURE.md FUTURE_SECURITY_ARCHITECTURE.md Security controls, threat model
    Data Model DATA_MODEL.md FUTURE_DATA_MODEL.md Entity relationships, branded types
    Flowchart FLOWCHART.md FUTURE_FLOWCHART.md Business process flows
    State Diagram STATEDIAGRAM.md FUTURE_STATEDIAGRAM.md System state transitions
    Mind Map MINDMAP.md FUTURE_MINDMAP.md System concepts and relationships
    SWOT Analysis SWOT.md FUTURE_SWOT.md Strategic positioning
    Threat Model THREAT_MODEL.md FUTURE_THREAT_MODEL.md STRIDE, MITRE ATT&CK, attack trees
    CRA Assessment CRA-ASSESSMENT.md β€” EU Cyber Resilience Act conformity

    The EP MCP Server's data model reflects the structure of the European Parliament Open Data Portal API v2 (JSON-LD format). All entities are defined as TypeScript interfaces with corresponding Zod schemas for runtime validation. Branded types enforce semantic correctness for EP-specific identifiers.

    • Base URL: https://data.europarl.europa.eu/api/v2/
    • Format: JSON-LD (also supports RDF/XML)
    • Authentication: None required (public open data)
    • Rate Limits: 100 requests/minute (enforced server-side)

    For per-MEP roll-call vote (RCV) statistics, the EP Open Data API exposes only the placeholder MEPDetails.votingStatistics shape and frequently returns no real per-MEP vote counts. The MCP server therefore enriches voting-related metrics from the European Parliament DOCEO XML source (https://www.europarl.europa.eu/doceo/document/PV-{term}-{date}-RCV_EN.xml).

    DOCEO XML is the authoritative near-real-time source for the following per-MEP fields consumed by assess_mep_influence, detect_voting_anomalies, and comparative_intelligence:

    • totalVotes, votesFor, votesAgainst, abstentions
    • attendanceRate (fraction of inspected RCVs the MEP appeared on)
    • loyaltyScore (% of decisive votes where the MEP voted with their political-group majority)

    The shared aggregator lives in src/utils/doceoMepAggregator.ts and is bounded (≀ 2 s) and cached (5-minute TTL keyed by ${mepId}|${dateFrom}|${dateTo}). OSINT tool responses surface a dataSource: 'EP_API' | 'DOCEO' | 'EP_API+DOCEO' field so consumers can distinguish placeholder data from real RCV-backed metrics.

    detect_voting_anomalies builds its per-MEP defection / abstention / alignment baselines from the same DOCEO RCV records via src/utils/votingBaseline.ts. Each detected anomaly carries the following shape:

    interface VotingAnomaly {
    type: 'PARTY_DEFECTION'
    | 'ABSTENTION_SPIKE'
    | 'ALIGNMENT_SHIFT'
    | 'CROSS_PARTY_ALIGNMENT_SHIFT';
    severity: 'HIGH' | 'MEDIUM' | 'LOW';
    mepId: string;
    mepName: string;
    description: string;
    metrics: { expectedValue: number; actualValue: number; deviation: number };
    detectedDate: string; // YYYY-MM-DD
    evidenceVoteIds: string[]; // DOCEO LatestVoteRecord.id values
    }

    evidenceVoteIds are the unique vote identifiers (LatestVoteRecord.id, e.g. RCV-10-2026-01-15-001) returned by get_latest_votes β€” use them to drill down to the contributing roll-call records. Thresholds: defection-rate z β‰₯ 1.5, abstention-rate z β‰₯ 1.5, week-over-week defection delta β‰₯ 20pp, non-home-group alignment share β‰₯ 60% in a weekly sub-window (min 3 decisive RCVs). Confidence reflects RCV coverage: HIGH β‰₯50, MEDIUM 10–49, LOW <10 RCVs inspected.


    erDiagram
    MEP {
    MEP_ID id PK
    string identifier
    string label
    CountryCode country
    string politicalGroup
    DateString mandateStart
    DateString mandateEnd
    string email
    string status
    }

    POLITICAL_GROUP {
    string id PK
    string label
    string abbreviation
    int memberCount
    }

    COMMITTEE {
    string id PK
    string label
    string abbreviation
    string type
    }

    PROCEDURE {
    ProcedureID id PK
    string title
    string type
    string stage
    DateString startDate
    string leadCommittee
    }

    PLENARY_SESSION {
    string id PK
    DateString date
    string location
    string status
    int documentCount
    }

    VOTE {
    string id PK
    string procedureRef
    string result
    int votesFor
    int votesAgainst
    int abstentions
    DateString date
    }

    DOCUMENT {
    string id PK
    string title
    string type
    string procedureRef
    DateString adoptedDate
    string url
    }

    SPEECH {
    string id PK
    MEP_ID mepRef
    string sessionRef
    DateString date
    string language
    int durationSeconds
    }

    QUESTION {
    string id PK
    MEP_ID authorRef
    string type
    string subject
    DateString submittedDate
    string status
    }

    MEP }|--|| POLITICAL_GROUP : "belongs to"
    MEP }|--|{ COMMITTEE : "member of"
    MEP ||--|{ SPEECH : "delivered"
    MEP ||--|{ QUESTION : "submitted"
    PROCEDURE ||--|{ DOCUMENT : "produces"
    PROCEDURE ||--|{ VOTE : "subject of"
    PLENARY_SESSION ||--|{ VOTE : "held in"
    PLENARY_SESSION ||--|{ SPEECH : "delivered in"
    COMMITTEE ||--|{ PROCEDURE : "responsible for"

    erDiagram
    MEP {
    MEP_ID id PK
    string identifier "EP identifier e.g. ID12345"
    string label "Full name"
    CountryCode country "ISO 3166-1 alpha-2"
    string nationalParty "Home party name"
    string politicalGroup "EPP, S-D, Renew, etc."
    string politicalGroupAbbr "Short code"
    DateString mandateStart "Current mandate start"
    DateString mandateEnd "Mandate end (null if current)"
    string officialEmail "EP institutional email"
    string status "current, incoming, outgoing, homonym"
    string[] committeeRoles "Committee memberships"
    string photoUrl "Official EP photo URL"
    }

    MEP_DECLARATION {
    string id PK
    MEP_ID mepRef FK
    string type "financial, interest, activity"
    DateString submittedDate
    string documentUrl
    }

    MEP_ATTENDANCE {
    string id PK
    MEP_ID mepRef FK
    string sessionRef FK
    string status "present, absent, excused"
    DateString date
    }

    MEP ||--|{ MEP_DECLARATION : "has"
    MEP ||--|{ MEP_ATTENDANCE : "tracked in"

    erDiagram
    PROCEDURE {
    ProcedureID id PK "Format: YYYY-NNNN-TYPE"
    string title
    string type "COD, CNS, INI, etc."
    string stage "committee, plenary, trilogue, etc."
    string status "ongoing, completed, withdrawn"
    DateString startDate
    DateString adoptedDate
    string leadCommittee
    string rapporteur
    string[] coRapporteurs
    string legalBasis
    string[] relatedDocuments
    }

    PROCEDURE_EVENT {
    string id PK
    ProcedureID procedureRef FK
    string eventType "committee_vote, plenary_vote, etc."
    DateString date
    string description
    string documentRef
    }

    ADOPTED_TEXT {
    string id PK
    ProcedureID procedureRef FK
    string title
    DateString adoptedDate
    string url
    string documentType
    }

    PROCEDURE ||--|{ PROCEDURE_EVENT : "has events"
    PROCEDURE ||--|{ ADOPTED_TEXT : "produces"

    erDiagram
    PLENARY_SESSION {
    string id PK
    DateString date
    string location "Strasbourg or Brussels"
    string status "scheduled, completed, cancelled"
    string[] agendaItems
    int attendanceCount
    }

    COMMITTEE_MEETING {
    string id PK
    string committeeId FK
    DateString date
    string location
    string status
    string[] agendaItems
    }

    COMMITTEE_ACTIVITY {
    string committeeId PK
    DateString periodFrom
    DateString periodTo
    int activeLegislativeFiles "Filtered: /procedures by responsibleCommittee + status=ongoing + date window"
    int documentsProduced "Filtered: /committee-documents by committee + date window"
    int meetingsHeld "Filtered: /meetings by date window"
    int decisionsAdopted "Fan-out: /meetings/{id}/decisions across up to 8 sittings"
    int opinionsIssued "Not exposed by EP API β€” always 0"
    float decisionsPerMeeting
    float documentsPerMonth
    float activeFilesPerMember
    string documentsSourceStatus "OK | EMPTY | TIMEOUT | UNAVAILABLE"
    string proceduresSourceStatus "OK | EMPTY | TIMEOUT | UNAVAILABLE"
    string meetingsSourceStatus "OK | EMPTY | TIMEOUT | UNAVAILABLE"
    string decisionsSourceStatus "OK | EMPTY | TIMEOUT | UNAVAILABLE"
    string confidenceLevel "HIGH | MEDIUM | LOW"
    }

    MEETING_ACTIVITY {
    string id PK
    string meetingRef FK
    string activityType
    string description
    string documentRef
    string outcome
    }

    MEETING_DECISION {
    string id PK
    string meetingRef FK
    string decisionType
    string text
    DateString date
    string[] votes
    }

    PLENARY_SESSION ||--|{ MEETING_ACTIVITY : "contains"
    COMMITTEE_MEETING ||--|{ MEETING_ACTIVITY : "contains"
    PLENARY_SESSION ||--|{ MEETING_DECISION : "produces"
    COMMITTEE_MEETING ||--|{ MEETING_DECISION : "produces"

    erDiagram
    DOCUMENT {
    string id PK
    string title
    string type "A-item, B-item, report, amendment"
    string procedureRef
    DateString createdDate
    DateString adoptedDate
    string[] languages
    string url
    string status "draft, adopted, rejected"
    }

    EXTERNAL_DOCUMENT {
    string id PK
    string title
    string source "Commission, Council, etc."
    string documentNumber
    DateString date
    string url
    string[] relatedProcedures
    }

    PLENARY_DOCUMENT {
    string id PK
    string sessionRef FK
    string documentRef FK
    string agendaPosition
    string discussionType
    }

    COMMITTEE_DOCUMENT {
    string id PK
    string committeeRef FK
    string documentRef FK
    string role "report, opinion, position"
    }

    DOCUMENT ||--|{ PLENARY_DOCUMENT : "appears in"
    DOCUMENT ||--|{ COMMITTEE_DOCUMENT : "assigned to"

    erDiagram
    VOTE_RECORD {
    string id PK
    string procedureRef
    string sessionRef FK
    DateString date
    string voteType "rollcall, show of hands, electronic"
    string subject
    int votesFor
    int votesAgainst
    int abstentions
    string result "adopted, rejected"
    }

    MEP_VOTE {
    string id PK
    string voteRecordRef FK
    MEP_ID mepRef FK
    string position "for, against, abstention, absent"
    string politicalGroup
    }

    VOTE_RECORD ||--|{ MEP_VOTE : "composed of"

    All OSINT intelligence tools produce outputs conforming to the OsintStandardOutput interface, which includes explicit data quality metadata:

    erDiagram
    OSINT_OUTPUT {
    string confidenceLevel "HIGH, MEDIUM, LOW"
    string methodology "Analytical approach description"
    string dataFreshness "Data recency description"
    string sourceAttribution "EP Open Data Portal API v2"
    string_array dataQualityWarnings "Plain string warnings about data limitations"
    }

    METRIC_RESULT {
    number value "Computed value or null"
    string availability "AVAILABLE, PARTIAL, ESTIMATED, UNAVAILABLE"
    string confidence "HIGH, MEDIUM, LOW, NONE"
    string source "Data source description"
    string reason "Why unavailable or estimated"
    }

    OSINT_OUTPUT ||--o{ METRIC_RESULT : "wraps metrics with"

    Note: dataQualityWarnings is currently implemented as string[] (see src/tools/shared/types.ts). A structured warning type with message, affectedMetric, and severity fields is a future enhancement not yet implemented.

    monitor_legislative_pipeline extends the standard OSINT envelope with lifecycle-driven fields derived from the authoritative /procedures/{id}/events timeline:

    erDiagram
    PIPELINE_ITEM {
    string procedureId
    string currentStage "Normalized stage from latest lifecycle event when available; falls back to procedure.stage / procedure.status / 'Unknown' when no usable events exist"
    number daysInCurrentStage "Delta between latest event and now"
    boolean isStalled
    }

    COMPUTED_ATTRIBUTES {
    string bottleneckRisk "HIGH if dwell >= p95, MEDIUM if >= median, LOW otherwise"
    number estimatedCompletionDays "Historical median remaining-time, or heuristic fallback"
    }

    LIFECYCLE_EVENT {
    string date "ISO date of the event"
    string stage "Normalized stage key"
    string rawType "Original EP API event type (e.g. def/ep-activities/REFERRAL)"
    string title
    }

    LIFECYCLE_CORPUS {
    number corpusSize "Procedures contributing to the distribution"
    number totalObservations "Dwell samples across all (type, stage) cells"
    number computationTimeMs
    }

    PIPELINE_ENVELOPE {
    string forecastBasis "HISTORICAL_MEDIAN | INSUFFICIENT_DATA | NOT_APPLICABLE"
    }

    PIPELINE_ENVELOPE ||--o{ PIPELINE_ITEM : "pipeline[]"
    PIPELINE_ITEM ||--|| COMPUTED_ATTRIBUTES : "computedAttributes"
    PIPELINE_ITEM ||--o{ LIFECYCLE_EVENT : "lifecycleEvents[]"
    PIPELINE_ENVELOPE ||--|| LIFECYCLE_CORPUS : "lifecycleCorpus"
    • lifecycleEvents β€” per-procedure chronological echo of the underlying event sequence for traceability. Includes normalised stage and original rawType to support both display and downstream analytics.
    • forecastBasis β€” discriminated union at the envelope level. Set to HISTORICAL_MEDIAN when at least one procedure in scope used the historical median forecast; INSUFFICIENT_DATA when every forecast fell back to the heuristic; NOT_APPLICABLE when all procedures are already completed.
    • lifecycleCorpus β€” metadata about the corpus used to build the dwell distributions (latest 500 procedures, cached 30 min). No PII is retained β€” only event types, dates, and procedure types.
    Value Meaning Example
    AVAILABLE All required data retrieved from EP API MEP name, country, political group
    PARTIAL Some data retrieved; metric may be incomplete Committee membership without role details
    ESTIMATED Metric derived from proxy/indirect data sources Influence score based on committee count (not votes)
    UNAVAILABLE Required data not provided by EP API endpoint Per-MEP voting statistics (EP API returns 0)

    Branded types enforce semantic correctness for EP domain identifiers at both compile-time and runtime.

    import { z } from 'zod';

    // Procedure ID: YYYY/NNNN(TYPE) format
    // Example: 2024/0001(COD), 2023/0089(INI)
    const ProcedureIDSchema = z
    .string()
    .regex(/^\d{4}\/\d{4}\([A-Z]{2,4}\)$/)
    .brand<'ProcedureID'>();
    type ProcedureID = z.infer<typeof ProcedureIDSchema>;

    // ISO 3166-1 alpha-2 country code
    // Example: DE, FR, SE, PL
    const CountryCodeSchema = z
    .string()
    .length(2)
    .regex(/^[A-Z]{2}$/)
    .brand<'CountryCode'>();
    type CountryCode = z.infer<typeof CountryCodeSchema>;

    // ISO 8601 date string
    // Example: 2024-03-15
    const DateStringSchema = z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .brand<'DateString'>();
    type DateString = z.infer<typeof DateStringSchema>;

    // MEP identifier (positive integer)
    const MEP_IDSchema = z
    .number()
    .int()
    .positive()
    .brand<'MEP_ID'>();
    type MEP_ID = z.infer<typeof MEP_IDSchema>;

    // Political group abbreviation
    // Example: EPP, SD, Renew, Greens, ECR, ID, GUE
    const PoliticalGroupSchema = z
    .string()
    .min(1)
    .max(20)
    .brand<'PoliticalGroup'>();
    type PoliticalGroup = z.infer<typeof PoliticalGroupSchema>;
    Type Format Example Validation Rule
    ProcedureID YYYY/NNNN(TYPE) 2024/0001(COD) Regex: ^\d{4}\/\d{4}\([A-Z]{2,4}\)$
    CountryCode AA DE, FR 2 uppercase letters
    DateString YYYY-MM-DD 2024-03-15 ISO 8601 format
    MEP_ID Integer 12345 Positive integer
    PoliticalGroup String EPP, SD 1-20 chars

    Each of the 62 MCP tools has a corresponding Zod input schema. Representative examples:

    // get_meps tool schema
    const GetMEPsInputSchema = z.object({
    country: CountryCodeSchema.optional(),
    politicalGroup: PoliticalGroupSchema.optional(),
    term: z.number().int().positive().optional(),
    limit: z.number().int().min(1).max(100).default(50),
    offset: z.number().int().min(0).default(0),
    });

    // get_mep_details tool schema
    const GetMEPDetailsInputSchema = z.object({
    mepId: MEP_IDSchema,
    includeDeclarations: z.boolean().default(false),
    includeAttendance: z.boolean().default(false),
    });

    // get_procedures tool schema
    const GetProceduresInputSchema = z.object({
    procedureId: ProcedureIDSchema.optional(),
    type: z.enum(['COD', 'CNS', 'INI', 'RSP', 'BUD']).optional(),
    status: z.enum(['ongoing', 'completed', 'withdrawn']).optional(),
    fromDate: DateStringSchema.optional(),
    toDate: DateStringSchema.optional(),
    limit: z.number().int().min(1).max(100).default(20),
    });

    The EP API returns JSON-LD format. The server normalizes this to plain TypeScript objects:

    {
    "@context": "https://data.europarl.europa.eu/api/v2/",
    "@graph": [
    {
    "@type": "ep:Member",
    "@id": "https://data.europarl.europa.eu/api/v2/meps/12345",
    "identifier": "12345",
    "label": "Maria Example",
    "country": "DE",
    "politicalGroup": "EPP",
    "mandateStart": "2024-07-16",
    "mandateEnd": null
    }
    ],
    "meta": {
    "total": 720,
    "offset": 0,
    "limit": 50
    }
    }
    EP API JSON-LD β†’ JSON.parse() β†’ Extract @graph array β†’ Map to typed objects β†’ Zod validation β†’ Branded types applied
    

    The LRU cache uses deterministic key generation for all EP API calls. Parameter keys are sorted alphabetically before serialization to ensure identical queries always produce the same cache key regardless of property insertion order (ISMS A.8.11 β€” Data integrity).

    Pattern Example Key TTL
    mep:{id} mep:12345 15 min
    meps:list:{country}:{group}:{offset}:{limit} meps:list:DE:EPP:0:50 15 min
    procedure:{id} procedure:2024/0001(COD) 15 min
    procedures:list:{type}:{status}:{from}:{to}:{limit} procedures:list:COD:ongoing:::20 15 min
    plenary:{id} plenary:20240315 15 min
    plenary:list:{from}:{to}:{limit} plenary:list:2024-01-01:2024-03-31:20 15 min
    votes:{id} votes:V-2024-001 15 min
    committee:{id} committee:ENVI 15 min
    vocab:{type} vocab:countries 60 min

    Deterministic Cache Key Construction:

    // Cache keys are deterministic regardless of property insertion order.
    // Object.entries(params).sort() ensures { a: 1, b: 2 } and { b: 2, a: 1 }
    // produce identical keys, preventing cache misses and duplicate entries.
    private getCacheKey(endpoint: string, params?: Record<string, unknown>): string {
    const sortedParams = params !== undefined
    ? Object.fromEntries(Object.entries(params).sort(([a], [b]) => a.localeCompare(b)))
    : undefined;
    return JSON.stringify({ endpoint, params: sortedParams });
    }

    The analyze_legislative_effectiveness MCP tool emits a derived envelope assembled from four EP Open Data Portal sources. Schema additions:

    Field Type Description
    metrics.reportsAuthored number Procedures rapporteured by the subject (deduped by procedureId).
    metrics.opinionsDelivered number Procedures where the subject is a shadow / opinion rapporteur.
    metrics.amendmentsTabled number Plenary-session document items of type AMENDMENT authored by the subject.
    metrics.amendmentsAdopted number Subset of amendmentsTabled whose status includes ADOPTED.
    metrics.questionsAsked number Parliamentary questions filed by the subject within the window.
    metrics.legislativeSuccessRate number 100 Γ— procedures-with-adopted-text / attributed-procedures (0–100, 2 decimals).
    attributions.reportProcedureIds string[] Procedure IDs counted as reports authored, sorted ascending.
    attributions.opinionProcedureIds string[] Procedure IDs counted as opinions delivered, sorted ascending.
    attributions.amendmentDocumentIds string[] Document IDs counted as amendments tabled, sorted ascending.
    attributions.amendmentAdoptedDocumentIds string[] Document IDs counted as adopted amendments, sorted ascending.
    attributions.questionIds string[] Parliamentary question IDs, sorted ascending.
    dataSources.procedures 'OK' | 'EMPTY' | 'TIMEOUT' | 'UNAVAILABLE' Status of the /procedures fetch.
    dataSources.adoptedTexts 'OK' | 'EMPTY' | 'TIMEOUT' | 'UNAVAILABLE' Status of the /adopted-texts fetch.
    dataSources.plenaryDocumentItems 'OK' | 'EMPTY' | 'TIMEOUT' | 'UNAVAILABLE' Status of the /plenary-session-documents-items fetch.
    dataSources.questions 'OK' | 'EMPTY' | 'TIMEOUT' | 'UNAVAILABLE' Status of the /parliamentary-questions fetch.

    Each source runs under an independent 6 s AbortController budget; failures are tagged in dataSources rather than bubbled. Lists in attributions are guaranteed sorted ascending so repeated runs are byte-identical.


    See FUTURE_DATA_MODEL.md for planned enhancements including graph database support and temporal data models.