french-politics-mcp
Provides access to official 2022 French presidential election programs for five parties, retrieving exact passages with full references and without synthesis.
README
Civis — french-politics-mcp
Serveur MCP qui donne accès aux programmes officiels de l'élection présidentielle française de 2022, pour cinq partis, avec une contrainte : ne jamais répondre sans source primaire.
Le serveur ne raisonne pas et ne rédige aucune synthèse. Il retrouve des passages, renvoie leur texte exact et leur référence complète, et dit explicitement quand le corpus ne documente pas un sujet. La synthèse revient au modèle client, à partir de citations vérifiables.
Se connecter
Le serveur est déployé, public et en lecture seule :
https://french-politics-mcp.mael-app.workers.dev/mcp
Avec Claude Code :
claude mcp add --transport http civis https://french-politics-mcp.mael-app.workers.dev/mcp
Avec l'inspecteur MCP, en transport Streamable HTTP :
npx @modelcontextprotocol/inspector --cli \
https://french-politics-mcp.mael-app.workers.dev/mcp \
--transport http --method tools/list
Pour un client qui ne parle que stdio, passer par le proxy mcp-remote. Aucune authentification n'est requise, le corpus est public.
Corpus
158 documents, 4 704 passages citables, 3,4 millions de caractères, pour ce seul scrutin. Dix thèmes comparables, tous couverts par les cinq partis.
| Parti | Candidat | Documents | Passages |
|---|---|---|---|
| LFI | Jean-Luc Mélenchon | Programme abrégé + 41 livrets thématiques + 13 plans | 2 358 |
| RN | Marine Le Pen | Manifeste + 17 livrets « projet » | 791 |
| Renaissance | Emmanuel Macron | Projet 24 p. + 41 fiches thématiques | 739 |
| LR | Valérie Pécresse | Le courage de faire + 33 fiches de projet | 551 |
| PS | Anne Hidalgo | Programme officiel 44 p. | 266 |
S'y ajoutent deux séries homogènes : la déclaration officielle de chaque candidat à la Commission nationale de contrôle de la campagne — le seul document que les cinq ont produit dans le même format — et les réponses écrites au questionnaire TDIE sur les transports, pour les quatre candidats qui y ont répondu (Emmanuel Macron ne l'a pas fait).
Ces séries sont retenues selon une règle uniforme : tous ceux qui ont produit le document, sans sélection destinée à combler tel ou tel parti.
Le choix de la présidentielle 2022 plutôt que des législatives 2024 est délibéré : en 2024, LFI et le PS partageaient le programme du Nouveau Front populaire, ce qui rendait la comparaison parti par parti sans objet.
Un corpus inégal, et pourquoi il le reste
Le PS est documenté 9 fois moins que LFI. Ce n'est pas un défaut de collecte : Anne Hidalgo n'a produit aucun livret thématique, là où LFI en a publié 54, et la candidature d'Emmanuel Macron, déposée cinq semaines avant le scrutin, n'a matériellement pas donné lieu à un appareil programmatique volumineux. Le déséquilibre reflète ce que les partis ont réellement publié.
Plutôt que de tronquer les uns pour égaliser, le serveur rend l'écart visible et l'empêche de fausser les résultats :
compare_partiesannonce l'écart et joint à chaque parti son volume de corpus. Unnot_founddoit se lire « le corpus documente peu ce parti », jamais « ce parti n'avait pas de position ». Le classement n'y est pas affecté : chaque parti est interrogé par une requête séparée.search_documentsplafonne la part d'un même parti à un tiers des résultats, faute de quoi le parti le plus volumineux monopoliserait les recherches non filtrées.
Sources et robots.txt
Deux hôtes interdisent le parcours automatisé, et cela a été respecté :
mlafrance.fr(Disallow: /) sert les livrets du RN. On passe parrassemblementnational.fr, qui les sert à l'identique enAllow: /. Ce qui reste exclusif à mlafrance.fr — dossiers de presse, discours, communiqués — n'est de toute façon pas programmatique.laec.frpublie le texte intégral de L'Avenir en commun, mais sonrobots.txtest une liste blanche de moteurs de recherche close parDisallow: /. Le texte du livre n'est donc pas dans le corpus. Ses livrets et plans, republiés par LFI surmelenchon2027.fr(hôte permissif), le sont — et représentent un volume bien supérieur.
Les sites de campagne d'Emmanuel Macron et d'Anne Hidalgo ayant disparu, leurs documents sont cités via l'archive Wayback, à un horodatage figé pour rester reproductibles ; l'URL d'origine est conservée dans les métadonnées. Chaque source porte son empreinte SHA-256.
Tools MCP
| Tool | Rôle |
|---|---|
search_documents |
Recherche des passages, filtrables par parti et par thème. Une liste vide est une réponse : le corpus ne documente pas le sujet. |
get_passage |
Texte exact d'un passage et de ses voisins, pour vérifier qu'une citation n'est pas sortie de son contexte. |
compare_parties |
Sur un thème, les meilleurs passages de chaque parti avec un niveau de preuve. Aucune synthèse côté serveur. |
list_parties |
Ce que le corpus contient par parti : volume, thèmes couverts. |
list_sources |
Documents primaires, URL, type, empreinte SHA-256. |
Resources : party://{partyId}, election://presidentielle-2022, topics://catalog.
Prompts : compare-topic, summarize-party-position, find-direct-quote.
Niveaux de preuve
compare_parties qualifie chaque parti par un evidenceLevel, déduit du nombre de mots-clés du thème réellement présents dans le passage — un signal mesurable, jamais un jugement sur le fond.
direct_quote— le passage traite explicitement le thèmeclear_paraphrase— il l'aborde sans lui être consacréweak_inference— le lien n'est que lexical, à interpréter avec prudencenot_found— rien dans le corpus : ne prêter aucune position au parti
Architecture
TypeScript de bout en bout, sur Cloudflare Workers, avec le corpus dans D1 (SQLite managé).
src/
index.ts Worker : health check + createMcpHandler sur /mcp
server/ McpServer — tools, resources, prompts
domain/ types, partis, taxonomie des thèmes
search/ pipeline linguistique français + constructeur de requête FTS5
storage/ accès D1 : recherche, passages, agrégats
ingest/ pipeline local : fetch → extract → normalize → build-sql
data/corpus/ corpus.json — artefact normalisé, source du SQL
data/sql/ schema.sql (écrit à la main) + seed.sql (généré)
data/text/ texte extrait, versionné (preuve consultable)
data/raw/ PDF sources (non versionnés, checksums.json l'est)
Le Worker est sans état et n'embarque aucune donnée : pas de Durable Object, transport Streamable HTTP uniquement (SSE est déprécié). Une instance McpServer neuve est créée à chaque requête — le SDK l'exige depuis la version 1.26, un serveur partagé pouvant faire fuiter la réponse d'un client vers un autre.
Pourquoi D1 plutôt qu'un index embarqué
La première version chargeait un index MiniSearch depuis le bundle. Ce n'était pas la taille qui posait problème (609 Ko gzippés sur les 3 Mo autorisés) mais le CPU au démarrage à froid, mesuré à ~8,8 ms sur un isolate neuf — dont 6,2 ms pour la seule réhydratation de l'index — face à la limite de 10 ms de CPU par requête du plan gratuit. La première requête servie par chaque isolate passait tout près du plafond.
D1 déplace ce travail vers de l'entrée-sortie, qui ne compte pas dans le budget CPU. Le bundle tombe à 386 Ko gzippés, et le coût par requête devient l'attente réseau plus quelques microsecondes de sérialisation. Le corpus peut par ailleurs grossir sans que la taille du Worker bouge.
Recherche
Recherche plein texte FTS5 avec classement bm25, le titre de section comptant double.
FTS5 n'embarque aucun analyseur français — son tokenizer se contente de retirer les diacritiques. Le contenu indexé est donc pré-stemmé à l'ingestion par src/search/french.ts, et la requête passe par exactement le même code côté Worker. Sans cela, « retraites » ne retrouverait pas « retraite ». Toute modification du stemmer impose de régénérer le seed.
Ce pipeline est écrit à la main : tokenisation sans accents, mots vides, et un stemmer léger dans l'esprit du French light stemmer de Savoy. Snowball aurait pesé 867 Ko pour un gain marginal. npm run stem:check vérifie la convergence des variantes sur le vocabulaire du corpus (21 groupes sur 22 ; le préfixe FTS5 rattrape le dernier).
Trois règles décident de ce qui est renvoyé et dans quel ordre :
- Pénalité de navigation — les sommaires sont denses en mots-clés mais ne contiennent aucune position citable. Ils sont pénalisés au classement, jamais exclus : l'heuristique n'est pas assez sûre pour rendre du texte inatteignable.
- Plancher relatif — les résultats sous 25 % du meilleur score sont coupés. « cryptomonnaies blockchain NFT » ne remonte rien plutôt que cinq passages sans rapport.
- Plafond par parti — sur une recherche non filtrée, aucun parti ne peut occuper plus d'un tiers des résultats. Les partis n'ayant pas publié le même volume, LFI raflait sept des douze premiers résultats sur « augmenter le SMIC » : c'était la taille de son corpus qui parlait, pas la pertinence. Le classement par score est conservé, seuls les résultats excédentaires d'un parti déjà bien représenté sont écartés. Un filtre explicite sur un parti désactive la règle.
- Couverture thématique — sur une comparaison, le score bm25 est majoré de 15 % par mot-clé distinct du thème présent dans le passage. bm25 seul récompense la rareté d'un terme, ce qui n'est pas la même chose que traiter le sujet ; la couverture seule échoue symétriquement, le vocabulaire d'un thème étant ambigu. Multiplier les deux laisse la pertinence lexicale départager à couverture comparable.
Ingestion
npm run ingest # fetch → extract → normalize → build-sql
npm run corpus:check # vérifie les invariants du corpus
Prérequis : poppler (brew install poppler, ou apt install poppler-utils), pour pdftotext. Requis uniquement pour l'ingestion, exécutée en local ; le texte extrait est versionné et le Worker n'en dépend jamais.
Le manifeste (ingest/manifest.ts) accepte deux formats. Les PDF passent par poppler, avec un mode par source : le mode par défaut convient aux programmes mis en page, raw est réservé aux déclarations CNCCEP dont les colonnes s'entrelaceraient autrement. Les pages HTML passent par ingest/html.ts, qui isole le contenu éditorial du gabarit — sans quoi chaque page injecterait son menu et son pied de page dans le corpus, le pendant HTML des titres courants des PDF.
L'extraction passe par poppler plutôt que par une bibliothèque JS parce que ces programmes mélangent, parfois sur une même page, des paragraphes pleine largeur et des zones à deux colonnes. Les extracteurs qui suivent l'ordre du flux PDF recollent alors la ligne de gauche et celle de droite en une seule phrase — pour un serveur dont la promesse est la citation exacte, c'est disqualifiant : cela fabrique des phrases que le candidat n'a jamais écrites.
Le nettoyage traite ensuite les défauts propres à ces documents : titres courants soudés au texte (détectés par n-grammes répétés entre pages), césures, caractères de contrôle laissés à la place de puces, et mots disloqués par l'interlettrage des titres (« UNE RE TRAITE DIGNE » → « UNE RETRAITE DIGNE »).
corpus:check revérifie l'invariant central : chunk.text est exactement texteSource.slice(charStart, charEnd). C'est ce qui rend une citation vérifiable. Il contrôle aussi que 95 % au moins de chaque document se retrouve dans un passage citable — la couverture est actuellement de 99,7 % à 99,9 %.
Développement
npm install
npm run db:reset:local # applique schema.sql + seed.sql au SQLite local
npm run dev # wrangler dev sur http://localhost:8787
npm run typecheck
Le développement local n'a besoin d'aucun compte Cloudflare : --local travaille sur un SQLite dans .wrangler/state, et le database_id de wrangler.jsonc peut rester un placeholder.
Tester le serveur avec l'inspecteur MCP, en transport Streamable HTTP sur http://localhost:8787/mcp :
npx @modelcontextprotocol/inspector
Déploiement
wrangler d1 create civis-corpus # coller le database_id dans wrangler.jsonc
npm run ingest:sql # regénère data/sql/seed.sql
npm run db:reset:remote # applique le schéma et le corpus
npm run deploy
claude mcp add --transport http civis https://french-politics-mcp.mael-app.workers.dev/mcp
db:reset:remote recrée les tables : le corpus est un artefact généré, jamais modifié en place.
Limites de la V1
Le corpus se limite à un scrutin : une position citée engage la campagne présidentielle de 2022, pas la ligne actuelle du parti. Il ne contient ni déclarations médiatiques, ni programmes législatifs. Pour LFI, seule la version abrégée de L'Avenir en commun est librement accessible ; l'édition intégrale est un ouvrage payant.
La recherche est purement lexicale, avec deux conséquences. Une question formulée sans les mots du programme peut ne rien remonter. Et le vocabulaire d'un thème est parfois ambigu : sur l'immigration, « étranger » désigne aussi bien les étrangers en France que les Français de l'étranger, ce qu'aucun réglage lexical ne distingue — c'est précisément ce que la recherche sémantique résoudrait.
FTS5 n'offre pas non plus de correspondance approximative : une coquille dans la requête ne remonte rien. L'extraction par poppler étant propre, le corpus lui-même n'en souffre pas.
Le texte intégral de L'Avenir en commun manque, son seul hébergement libre interdisant le parcours automatisé (voir plus haut). Ses 54 livrets et plans, eux, sont présents.
La recherche sémantique (Vectorize + embeddings multilingues via Workers AI, qui tiennent dans le palier gratuit à cette échelle), les autres scrutins et la détection de positions contradictoires relèvent d'une version ultérieure.
Contribuer
Les règles de contribution, la convention de commit et la marche à suivre pour ajouter une source au corpus sont dans CONTRIBUTING.md. Pour signaler une faille, voir SECURITY.md.
La CI, la configuration Dependabot et les réglages de protection de branche dont elle dépend sont décrits dans .github/AUTOMATION.md.
Licence
Le code est sous Apache-2.0 : src/, ingest/, le schéma de base de données, le manifeste des sources, la taxonomie des thèmes et les métadonnées des partis.
Le texte des programmes dans data/text/ n'est pas couvert par cette licence. Ce sont des œuvres protégées, dont les droits appartiennent aux partis, aux candidats et à leurs éditeurs. Le projet n'en revendique aucun droit et n'en concède aucun. Ce texte est reproduit à des fins documentaires et de citation : le serveur répond en citant mot pour mot, ce qui exige une copie fidèle et vérifiable de la source. Trois des sites de campagne de 2022 ayant déjà disparu, cette copie fait aussi office d'archive.
Chaque document porte son URL de provenance, son empreinte SHA-256 et son parti. La liste complète est dans ingest/manifest.ts, et npm run ingest reproduit data/text/ depuis ces URLs.
Le détail des attributions et la procédure de retrait sont dans NOTICE. Toute demande de retrait d'un ayant droit est honorée sans discussion : le corpus se reconstruit depuis ses sources, retirer un document ne coûte qu'une entrée de manifeste.
Le projet ne prend aucune position politique. Les partis sont traités selon une règle uniforme et le serveur est conçu pour citer, pas pour résumer.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.