Skip to main content
Glama
mael-app

french-politics-mcp

by mael-app

Civis — french-politics-mcp

CI Licence Apache 2.0 MCP Streamable HTTP État du Worker Corpus

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.

Related MCP server: alexandria-mcp

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_parties annonce l'écart et joint à chaque parti son volume de corpus. Un not_found doit 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_documents plafonne 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 par rassemblementnational.fr, qui les sert à l'identique en Allow: /. Ce qui reste exclusif à mlafrance.fr — dossiers de presse, discours, communiqués — n'est de toute façon pas programmatique.

  • laec.fr publie le texte intégral de L'Avenir en commun, mais son robots.txt est une liste blanche de moteurs de recherche close par Disallow: /. Le texte du livre n'est donc pas dans le corpus. Ses livrets et plans, republiés par LFI sur melenchon2027.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ème

  • clear_paraphrase — il l'aborde sans lui être consacré

  • weak_inference — le lien n'est que lexical, à interpréter avec prudence

  • not_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.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mael-app/french-politics-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server