Documentation OpenLegi

Accedez aux sources juridiques officielles francaises directement depuis Claude Desktop, ChatGPT ou tout autre LLM compatible MCP.

Demarrage rapide

Configurer OpenLegi MCP en quelques minutes

Clés API personnelles

Par défaut, OpenLegi utilise des clés API partagées avec des limites de débit. Pour un usage intensif ou professionnel, vous pouvez configurer vos propres credentials PISTE.

Vous pouvez vous aider d'un tutoriel complet à copier/coller directement dans Claude.

Avantages des clés personnelles

  • Aucune limite de débit — Pas de quota partagé avec d'autres utilisateurs
  • Mode illimité — Vos requêtes ne sont plus comptabilisées
  • Stockage sécurisé — Vos credentials sont chiffrés

Obtenir vos credentials PISTE

  1. Rendez-vous sur https://piste.gouv.fr (portail PISTE)
  2. Créez un compte ou connectez-vous
  3. Créez une application et souscrivez à l'API Legifrance
  4. Attendez l'approbation de votre application (peut prendre quelques jours)
  5. Une fois approuvée, accédez à votre application en environnement Production
  6. Récupérez votre identifiant OAuth (pas API Keys) (= Client ID) et votre Consumer Secret (= Client Secret)

Configurer dans OpenLegi

  1. Connectez-vous sur auth.openlegi.fr
  2. Accédez à votre Dashboard
  3. Onglet Clés OAuth → cliquez sur Ajouter une clé
  4. Renseignez un nom, votre Client ID et Client Secret
  5. La validation automatique teste vos credentials (jusqu'à 30 secondes)
  6. Si la validation réussit, votre clé passe en statut Active

Statuts possibles

Statut Signification Action
Active Credentials valides, prêts à l'emploi Rien à faire
En attente La validation n'a pas pu aboutir (timeout) Cliquez sur Tester pour relancer
Invalide Credentials rejetés par l'API PISTE Vérifiez vos credentials (voir erreurs courantes)

Erreurs courantes

"Client ID ou Client Secret invalide" (erreur invalid_client)

  • Vérifiez que vous utilisez les credentials de l'environnement Production (pas Sandbox)
  • Vérifiez que votre application PISTE est bien approuvée (statut "Approved")
  • Attention aux espaces invisibles lors du copier-coller
  • Le Consumer Key et le Consumer Secret sont différents du login/mot de passe PISTE

"Timeout de connexion"

  • Le serveur de validation n'a pas répondu à temps
  • Cela peut indiquer des credentials invalides (le serveur retente plusieurs fois avant d'échouer)
  • Vérifiez d'abord vos credentials, puis cliquez sur Tester

"Accès interdit" (erreur 403)

  • Votre application PISTE n'a probablement pas souscrit à l'API Legifrance
  • Connectez-vous sur le portail PISTE et vérifiez vos souscriptions

Gestion des clés

  • Une seule clé active par provider — Supprimez l'ancienne avant d'en créer une nouvelle
  • Bouton Tester — Relance la validation à tout moment
  • Bouton Supprimer — Supprime la clé et repasse automatiquement en clés partagées
  • Toggle "Clés personnelles" — Active/désactive l'utilisation de vos clés (nécessite une clé active)

Limitations connues

⚠️ Avertissement important — nature des résultats

OpenLegi est une interface d'accès à la base Légifrance, pas un substitut à Légifrance.

Les résultats transitent par une API qui opère un filtre, une mise en forme et une mise en cache sur les données brutes de Légifrance. Ce que vous lisez dans une réponse d'outil — ou dans la réponse d'un assistant IA — est une représentation intermédiaire du texte officiel, pas le texte officiel lui-même.

Règle absolue : pour tout usage professionnel, juridique ou décisionnel, toujours vérifier le texte sur Légifrance via le lien article_lc fourni dans les métadonnées. Ce lien pointe vers la source officielle faisant foi.

Ne jamais citer, commenter ou appliquer un texte de loi sur la seule foi d'une réponse d'un chatbot ou d'un outil IA, même lorsque celui-ci affiche un lien ou un identifiant Légifrance.


⚠️ Limite de cette documentation. Les observations ci-dessous sont issues d'un échantillon limité de 12 cas tests conduits sur quelques codes et lois. Elles documentent des comportements observés, non des comportements générals de fonctionnement. Tout comportement non testé doit être considéré comme inconnu jusqu'à vérification. Cette documentation s'intéresse principalement au fonds LEGI et ne documente pas les limitations connues sur les autres. Par exemple, JUDILIBRE présente des doublons (régression de pseudonymisation) sur les décisions des tribunaux de commerce. Ces limitations feront l'objet d'une documentation séparée lors de la publication de ce fonds.


Périmètre de couverture

Source juridique Statut observé Exemple
Codes français consolidés (L., R., D.) Accessible Art. L1132-1 C. trav.Art. R625-1 C. com.
Lois autonomes hors code (LODA) Accessible Art. 6 Loi n° 78-17
Versions historiques / droit transitoire ❌ Non accessible Art. L3121-1 C. trav. — versions antérieures à 2016 inaccessibles
Règlements européens directement applicables ❌ Hors périmètre Art. 83 RGPD (Rgt UE 2016/679) → SEARCH_NO_RESULTS

Risques identifiés

Les risques ci-dessous combinent les limites propres à OpenLegi et celles introduites par l'utilisation dans un workflow IA (LLM → restitution). Ils ont été observés sur l'échantillon de tests et d'autres risques non encore documentés peuvent exister.

1 — Numéro réaffecté après renumérotation législative

Certaines réformes réaffectent d'anciens numéros à de nouveaux articles. OpenLegi retourne l'article en vigueur portant ce numéro — qui peut être radicalement différent du texte historiquement attendu.

Exemple documenté. Demander Art. 1382 C. civ. retourne un article sur les présomptions judiciaires (lien), et non l'ancien article sur la responsabilité délictuelle, devenu Art. 1240 depuis l'ordonnance n° 2016-131.

Mitigation. Toujours croiser le numéro retourné avec la section parente et les premiers mots du texte. Codes particulièrement concernés : Code civil (2016), Code du travail (2008), Code de commerce (ordonnances successives).


2 — Références internes orphelines

Un article VIGUEUR peut contenir des renvois vers des articles abrogés. OpenLegi retourne le texte brut sans valider la cohérence des renvois internes.

Exemple documenté. Art. R625-1 C. com. (v1.0, inchangé depuis 2007) cite L. 143-11-4 C. trav., abrogé en 2008 et remplacé par Art. L3253-15.

Mitigation. Signal d'alerte : article avec numéro de version faible (v1.0 ou v2.0) et date de vigueur ancienne (avant 2010), surtout en partie réglementaire (R. ou D.). Vérifier les renvois directement sur Légifrance.


3 — Troncature des textes longs

L'API peut retourner des textes tronqués, signalés par .... Un LLM peut restituer ce texte incomplet sans signaler la coupure, voire compléter la fin de mémoire.

Signaux d'alerte.

Signal Exemple
Texte se terminant par ou ... Art. L1132-1 C. trav. — 25 critères de discrimination
Alinéas numérotés (I., II., 1°, 2°…) dont certains semblent manquants Art. 222-33 C. pén. — 3 parties + peines aggravées
Article version ≥ 5.0 ou loi de transposition Art. L1132-1 v11.0 — Art. 6 Loi 78-17

Mitigation. Ouvrir le lien Légifrance pour vérifier le texte intégral. Ne jamais citer un article dont la complétude n'a pas été vérifiée à la source.


4 — Hallucination et paraphrase par le LLM

Un LLM dispose de millions de textes juridiques en mémoire. Il peut compléter silencieusement un texte tronqué, substituer une version ancienne à la version en vigueur, ou reformuler une disposition en en modifiant la portée — sans le signaler.

Exemple. Art. 226-1 C. pén. : "à titre privé ou confidentiel" reformulé en "en privé" efface une condition alternative et modifie le champ de l'infraction.

Cas à risque élevé : articles renumérotés, réformes récentes (loi SREN 2024, ord. 2023-1142…), textes tronqués.

Mitigation. Exiger du LLM la reproduction exacte du texte retourné par l'outil, sans reformulation. Instruction système recommandée : "Ne jamais paraphraser ni compléter un texte juridique. Reproduire uniquement le contenu exact retourné par l'outil, en signalant toute troncature (...) sans la corriger."


5 — Faux positif du statut VIGUEUR

Le statut VIGUEUR confirme uniquement que l'article existe dans la base consolidée. Il ne garantit pas que le texte est complet, que ses renvois internes sont valides, ni que le numéro correspond au texte recherché (voir risques 1 et 2).

Mitigation. VIGUEUR est une condition nécessaire, non suffisante. Croiser systématiquement avec la section parente, le numéro de version, la date de vigueur, et l'absence de troncature.


6 — Décalage de cache

Les données peuvent être servies depuis un cache intermédiaire dont la durée de rafraîchissement n'est pas fiable. En cas de modification législative très récente, le texte retourné peut ne pas encore refléter la dernière version en vigueur.

Mitigation. Pour tout article susceptible d'avoir été modifié récemment, consulter directement Légifrance.


Checklist de validation avant citation

  • [ ] Le texte ne se termine pas par ... et tous les alinéas attendus sont présents
  • [ ] La section parente correspond à la matière recherchée
  • [ ] Le numéro de version et la date sont cohérents avec la réforme attendue
  • [ ] Les renvois internes ont été vérifiés (si article ancien, v1.0)
  • [ ] Le lien Légifrance a été ouvert et le texte officiel lu
  • [ ] Le texte cité est la reproduction exacte du texte officiel, sans paraphrase

Hors périmètre — alternatives recommandées

Besoin Alternative
Règlements et directives européens (RGPD, DMA, DSA, AI Act…) EUR-Lex
Versions historiques / droit transitoire Légifrance — onglet "Versions" sur la fiche article
Jurisprudence judiciaire et administrative Outils rechercher_jurisprudence_judiciaire / rechercher_jurisprudence_administrative

Documentation établie sur un échantillon de 12 cas tests — Code civil, Code du travail, Code pénal, Code de commerce, Loi n° 78-17. Les comportements observés ne valent pas garantie générale. Dernière mise à jour : 30 mars 2026.

Liste des services compatibles

Compatibilité avec les Assistants IA

Plateforme Support MCP Méthode d'intégration Notes
Claude Desktop ✅ Support complet Configuration native dans claude_desktop_config.json Recommandé
Claude (Web) ✅ Support complet Support MCP natif sur claude.ai via interface web Interface web
Perplexity ⚠️ Support partiel Client lourd uniquement (application desktop) Desktop uniquement
Mistral Le Chat ✅ Support complet Via "Connecteurs" dans la rubrique "Intelligence" Interface web
Microsoft Copilot ✅ Support complet Via Copilot Studio (configuration serveur MCP) Entreprise
ChatGPT ❌ Non compatible Pas de support MCP natif -
Google Gemini ❌ Non compatible Pas de support MCP natif -
VSCode / Cursor ✅ Support complet Extensions MCP disponibles (Continue, Cline, etc.) IDE
JetBrains IDEs ⚠️ Support partiel Support partiel via plugins tiers En développement

Légende

  • Support complet : Intégration MCP native et fonctionnelle
  • ⚠️ Support partiel : Intégration limitée ou en cours de développement
  • Non compatible : Pas de support MCP disponible

Configuration Claude Desktop

Pour utiliser OpenLegi MCP avec Claude Desktop, ajoutez cette configuration dans votre fichier claude_desktop_config.json :

Claude Desktop

Ajoutez cette configuration dans ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\Claude\claude_desktop_config.json (Windows) :

{
  "mcpServers": {
    "MCP Legifrance": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://mcp.openlegi.fr/legifrance/mcp?token=YOUR_TOKEN_HERE"
      ]
    }
  }
}

Plugins et Skills

Construire un Skill Claude qui utilise openlegi

Ce guide explique comment créer un Skill au format Agent Skills pour exploiter openlegi. Un Skill est l'unité élémentaire d'extension de Claude : un dossier contenant un fichier SKILL.md et, en option, des scripts, des références et des assets. Là où un plugin regroupe plusieurs capacités cohérentes pour une équipe, un Skill encapsule une compétence atomique distribuable indépendamment.

Skill ou plugin : quand choisir quoi

Un plugin Claude bundle plusieurs skills, des agents planifiés, des hooks et une configuration MCP partagée via un fichier CLAUDE.md. Un Skill au format Agent Skills est plus léger : un dossier autonome qui se charge dynamiquement quand sa description correspond au contexte. (Anthropic Engineering)

Trois critères de choix.

Granularité : un Skill couvre une compétence (vérifier une citation d'article, formater une note de jurisprudence). Un plugin couvre une pratique (droit du travail, des affaires, contentieux spécifiques, etc).

Portabilité : depuis octobre 2025, Agent Skills est un standard ouvert publié sur agentskills.io. Il a été adopté par Claude Code, OpenAI Codex CLI, Gemini CLI, Cursor et GitHub Copilot. (agentskills.io) Un Skill bien construit fonctionne sur plusieurs assistants. Un plugin reste spécifique à l'écosystème Claude (Cowork et Claude Code).

Surface de distribution : un Skill peut être uploadé sur claude.ai dans les settings, via l'API Claude, ou ajouté dans Claude Code via le système de fichiers. (Claude Platform Docs) Les trois surfaces ne synchronisent pas entre elles.

À noter. Pour publier sur claude.ai uniquement, un Skill suffit. Pour packager une suite complète (veille hebdo + recherche + rédaction de notes) avec un profil de pratique partagé, un plugin est plus adapté.

Le mécanisme de progressive disclosure

L'architecture Skills repose sur un chargement en trois niveaux qui contrôle l'usage de contexte. (Anthropic Engineering)

Niveau Contenu Quand chargé
1. Metadata name + description de la frontmatter YAML Toujours, dans le system prompt
2. Corps du SKILL.md Procédure en markdown Quand le skill se déclenche
3. Ressources bundlées scripts/, references/, assets/ À la demande

Au démarrage, Claude pré-charge uniquement les métadonnées de tous les skills installés. Il décide ensuite de consulter ou non un skill en se basant sur sa description. Si le skill est déclenché, son corps est injecté dans le contexte. Les fichiers bundlés ne sont lus que si la procédure les référence explicitement. (Anthropic Engineering)

Cette mécanique a une conséquence pratique : la qualité de la description détermine la qualité du déclenchement. Anthropic recommande de garder ce champ informatif et explicite, en mentionnant à la fois ce que fait le skill et dans quels contextes il doit s'activer. (Anthropic Skills repo)

Raccourci recommandé : faire générer le skill par le skill-creator

Avant de rédiger un skill manuellement, la voie la plus efficace est de déléguer le travail au skill-creator, un méta-skill publié par Anthropic dont la fonction est précisément de créer d'autres skills. (anthropics/skills, skill-creator)

L'astuce qui rend cette approche particulièrement adaptée à openlegi : quand le skill-creator s'exécute dans une session Claude où le connecteur MCP openlegi est branché, il découvre automatiquement la liste des outils disponibles (rechercher_code, rechercher_jurisprudence_judiciaire, recherche_journal_officiel, etc.) ainsi que leurs descriptions détaillées et leurs paramètres. Il s'appuie sur ces métadonnées pour construire la procédure du skill sans qu'on ait à les lui réécrire. Les descriptions d'outils MCP openlegi sont riches et autoportantes, ce qui rend la génération du skill quasi-immédiate.

Installation du skill-creator

/plugin marketplace add anthropic-agent-skills@https://github.com/anthropics/skills
/plugin install example-skills@anthropic-agent-skills

Le skill-creator est inclus dans le bundle example-skills. (anthropics/skills)

Prompt type à copier-coller

Lancez une conversation Claude (Cowork, Code ou claude.ai si le skill y est installé) et envoyez :

Crée un skill openlegi pour [décris ta tâche en une phrase].

Contexte technique :
- Les outils MCP openlegi sont déjà disponibles dans cette session.
  Inspecte-les via tool_search ou liste-les pour t'imprégner de leurs
  descriptions et de leurs paramètres avant de drafter le skill.
- Documentation openlegi optimisée pour LLM :
  https://www.openlegi.fr/llms-full.txt
- Garde-fous obligatoires : citation systématique du lien article_lc,
  pas de paraphrase de texte légal, signalement explicite des troncatures.

Procède selon ta méthode habituelle : cold-start interview, draft du
SKILL.md, propositions de prompts de test, puis itération.

Trois éléments font la différence dans ce prompt.

La référence à tool_search force le skill-creator à interroger l'environnement avant d'écrire le skill, plutôt que d'inventer des appels d'outils qui n'existent pas. Les descriptions retournées contiennent les paramètres exacts (code_name, champ, panorama, etc.) qu'il reproduira fidèlement.

Le lien vers llms-full.txt donne au skill-creator une vue exhaustive de la doc openlegi formatée pour LLM. (openlegi, accueil) Il y trouve les conventions de nommage, les limitations connues et les exemples d'usage.

La mention explicite des garde-fous juridiques garantit que le skill généré intègre les règles openlegi sur la vérification, la non-paraphrase et la troncature, plutôt qu'un boilerplate générique.

Ce qui sort de la procédure

Le skill-creator suit une boucle structurée : capture d'intention via questions à l'utilisateur, rédaction du SKILL.md, génération de prompts de test, exécution des tests (en parallèle via subagents si disponibles, en série sinon), évaluation qualitative, itération. (anthropics/skills, skill-creator)

Le livrable final est un dossier complet (SKILL.md + éventuels scripts/, references/, assets/) que le skill-creator peut packager en fichier .skill prêt à uploader. (anthropics/skills)

Limites à connaître

Cette approche fonctionne mieux pour les skills à output objectivement vérifiable (recherche, extraction, vérification de citation). Pour les skills à output subjectif (style rédactionnel, formatage de note), le skill-creator recommande lui-même de privilégier l'évaluation qualitative manuelle. (anthropics/skills, skill-creator)

Une revue humaine du SKILL.md généré reste indispensable avant publication. Le skill-creator peut sous-estimer certaines spécificités du droit français (renumérotations, droits transitoires, droit européen directement applicable) si l'utilisateur ne les a pas mentionnées pendant l'interview. Les sections suivantes de ce guide servent alors de checklist de revue.

Astuce avancée. Le skill-creator inclut une boucle d'optimisation automatique de la description (voir scripts/run_loop.py dans le repo). Elle exécute les prompts de test plusieurs fois sur des variantes de description pour mesurer le taux de déclenchement et sélectionner la meilleure. C'est l'étape qui transforme un skill fonctionnel en skill robuste. (anthropics/skills, skill-creator)

La suite de ce guide documente la création manuelle, soit comme alternative pour les utilisateurs qui préfèrent garder le contrôle, soit comme référence pour réviser un skill produit par le skill-creator.

Prérequis

Élément Comment l'obtenir
Compte openlegi avec token actif auth.openlegi.fr
Surface Claude (claude.ai, API ou Claude Code) Selon le mode de distribution visé
Connecteur MCP openlegi déjà configuré sur la surface Voir doc openlegi de configuration

Un Skill ne déclare pas lui-même de connecteurs MCP. Il suppose que le serveur openlegi est déjà branché sur l'environnement Claude où il s'exécute.

Anatomie d'un Skill

Structure minimale :

mon-skill/
├── SKILL.md           # obligatoire
├── scripts/           # optionnel
├── references/        # optionnel
└── assets/            # optionnel

Le seul fichier requis est SKILL.md. Tout le reste est optionnel et n'est chargé qu'à la demande. (anthropics/skills)

Étape 1 : la frontmatter YAML

Tout skill commence par un bloc YAML entre deux lignes ---. Deux champs sont requis : name et description. (Claude Platform Docs)

---
name: verification-article-fr
description: Vérifie qu'une citation d'article de code juridique français est exacte en comparant le texte cité au texte officiel récupéré via openlegi. Utiliser ce skill chaque fois que l'utilisateur cite un article du Code civil, Code du travail, Code de commerce, Code pénal ou tout autre code français, ou quand l'utilisateur demande de vérifier une référence légale, contrôler un numéro d'article, valider une citation juridique ou détecter des hallucinations sur du droit français.
---

Quelques règles tirées de la pratique d'Anthropic.

Le nom du skill doit correspondre au nom du dossier qui le contient. (anthropics/skills)

La description doit dire à la fois ce que fait le skill ET dans quels contextes le déclencher. Anthropic note que Claude a tendance à sous-déclencher les skills, ce qui justifie de rendre la description légèrement "pushy" en multipliant les formulations de déclenchement possibles. (anthropics/skills, skill-creator)

La description doit rester sous 1024 caractères, car elle est chargée en permanence dans le contexte de Claude. (Anthropic, claude-for-legal)

Étape 2 : le corps du skill

Après la frontmatter, le markdown libre contient la procédure que Claude suivra quand le skill se déclenchera. La forme impérative est recommandée. (anthropics/skills, skill-creator)

Exemple complet pour verification-article-fr :

---
name: verification-article-fr
description: [...comme ci-dessus...]
---

# Vérification de citation d'article (droit français)

Quand un utilisateur cite un article d'un code juridique français, ce skill
récupère le texte officiel via openlegi et compare avec ce qui a été cité,
en signalant explicitement toute divergence.

## Procédure

1. **Identifier la référence à vérifier** :
   - Code source (Code civil, Code du travail, etc.)
   - Numéro d'article (L1234-5, R625-1, etc.)
   - Texte cité (verbatim ou paraphrase)

2. **Appeler openlegi** via l'outil `rechercher_code` :
   ```json
   {
     "search": "<numéro d'article>",
     "code_name": "<nom du code>",
     "champ": "NUM_ARTICLE",
     "max_results": 1
   }
   ```

3. **Comparer la version officielle avec la citation** :
   - Reproduire le texte officiel retourné, sans paraphrase
   - Diffuser le texte cité par l'utilisateur
   - Identifier les divergences mot à mot

4. **Produire une note de vérification** :

   Si la citation est exacte :
   > ✅ Citation conforme au texte officiel.
   > Source : [lien article_lc Légifrance]

   Si la citation diverge :
   > ⚠️ Divergences détectées entre la citation et le texte officiel.
   >
   > **Texte cité** : "[citation utilisateur]"
   > **Texte officiel** : "[texte openlegi]"
   > **Divergences** : [liste précise]
   >
   > Source : [lien article_lc Légifrance]

## Cas particuliers

### Article potentiellement renuméroté

Si la citation porte sur le Code civil pré-2016, le Code du travail pré-2008
ou le Code de commerce, vérifier que le numéro retourné par openlegi
correspond bien au texte attendu, et non à un nouvel article ayant pris
le même numéro après réforme. Voir `references/renumerotations.md`
pour la liste des codes concernés.

### Texte tronqué

Si la réponse openlegi se termine par `...` ou `…`, signaler la troncature
sans tenter de compléter. Inviter l'utilisateur à ouvrir le lien Légifrance
pour le texte intégral.

### Article non trouvé

Si openlegi retourne `SEARCH_NO_RESULTS`, ne pas inventer le texte. Indiquer
que l'article n'a pas été trouvé dans la base consolidée et proposer des
alternatives (article voisin, ancienne numérotation).

## Garde-fous absolus

- Ne jamais paraphraser le texte officiel : reproduire à l'identique.
- Toujours afficher le lien `article_lc` Légifrance retourné par openlegi.
- Si l'utilisateur conteste la version openlegi, l'inviter à vérifier
  directement sur legifrance.gouv.fr. openlegi est une interface, pas
  la source officielle.

Anthropic recommande de garder le corps du SKILL.md sous 500 lignes. Au-delà, il est préférable de découper en fichiers de référence dans references/ que Claude lira à la demande. (anthropics/skills, skill-creator)

Étape 3 : ajouter des scripts déterministes

Pour les opérations répétitives qui ne nécessitent pas de raisonnement (parsing, diff, génération de fichier), un script vaut mieux qu'une instruction en langage naturel. Le code est plus rapide et plus reproductible. (Anthropic Engineering)

Exemple : scripts/diff_citation.py

#!/usr/bin/env python3
"""
Compare un texte cité avec le texte officiel openlegi.
Retourne une liste de divergences mot à mot.

Usage:
    python diff_citation.py --cite "texte cité" --official "texte officiel"
"""
import argparse
import difflib
import json
import sys


def diff_texts(cite: str, official: str) -> dict:
    """Produit un diff structuré entre deux textes."""
    cite_words = cite.split()
    official_words = official.split()

    matcher = difflib.SequenceMatcher(None, cite_words, official_words)
    divergences = []

    for tag, i1, i2, j1, j2 in matcher.get_opcodes():
        if tag == "equal":
            continue
        divergences.append({
            "type": tag,
            "cited": " ".join(cite_words[i1:i2]) or None,
            "official": " ".join(official_words[j1:j2]) or None,
        })

    return {
        "match_ratio": matcher.ratio(),
        "is_exact_match": matcher.ratio() == 1.0,
        "divergences": divergences,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--cite", required=True)
    parser.add_argument("--official", required=True)
    args = parser.parse_args()

    result = diff_texts(args.cite, args.official)
    json.dump(result, sys.stdout, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    main()

Dans le corps du skill, on référence le script :

4. Si le texte cité dépasse 50 mots, utiliser le script
   `scripts/diff_citation.py` pour produire un diff structuré
   plutôt que de comparer mot à mot manuellement.

Le script est exécuté par Claude via le tool bash (ou équivalent), sans être chargé en contexte. C'est ce qu'Anthropic appelle l'usage du code comme outil exécutable plutôt que comme documentation. (Anthropic Engineering)

Étape 4 : ajouter des références

Le dossier references/ contient des fichiers markdown qui ne sont pas chargés par défaut mais référencés par le corps du SKILL.md. C'est utile pour les contenus volumineux (listes, taxonomies, jurisprudence de référence). (anthropics/skills)

Exemple : references/renumerotations.md

# Codes français renumérotés

Liste des réformes connues qui ont réaffecté d'anciens numéros d'articles
à de nouveaux articles. À consulter quand une citation porte sur un de
ces codes et précède la date de réforme.

## Code civil

**Ordonnance n° 2016-131 du 10 février 2016** : refonte du droit
des contrats et des obligations.

Exemples de réaffectations :
- Ancien Art. 1382 (responsabilité délictuelle) devenu Art. 1240.
- Le numéro 1382 est aujourd'hui occupé par un article sur
  les présomptions judiciaires.

## Code du travail

**Recodification 2008** : changement complet de la numérotation.
- Articles L1132-1 et suivants, R625-1 et suivants : nouveaux numéros
  depuis 2008. Les anciens numéros (L122-45 etc.) ne sont plus en vigueur.

## Code de commerce

Plusieurs ordonnances successives ont modifié la numérotation.
Vérifier systématiquement la date de la citation et la date de version
retournée par openlegi.

Pour les références volumineuses (>300 lignes), Anthropic recommande d'inclure une table des matières au début du fichier pour permettre à Claude de naviguer efficacement. (anthropics/skills, skill-creator)

Étape 5 : ajouter des assets

Le dossier assets/ contient les fichiers utilisés dans la sortie produite par le skill : templates, polices, icônes, modèles de documents. Contrairement aux références, ils ne sont pas lus pour informer Claude mais utilisés directement dans le résultat final. (anthropics/skills)

Pour le skill verification-article-fr, on pourrait fournir un template de note de vérification :

assets/
└── template-note-verification.md
# Note de vérification

**Référence vérifiée** : {{reference}}
**Date de vérification** : {{date}}
**Source consultée** : openlegi (interface Légifrance)
**Lien officiel** : {{article_lc}}

## Résultat

{{resultat_verification}}

## Texte officiel

{{texte_officiel}}

## Texte cité

{{texte_cite}}

## Divergences détectées

{{divergences}}

---

*Cette note est un brouillon à valider. Pour tout usage professionnel,
ouvrir le lien officiel et vérifier le texte directement sur Légifrance.*

Étape 6 : optimiser la description

La description est le seul élément du skill qui détermine son déclenchement. Anthropic fournit dans son skill-creator un outil d'optimisation automatique de la description, qui exécute une boucle d'évaluation sur des prompts de test pour mesurer le taux de déclenchement et proposer des variantes. (anthropics/skills, skill-creator)

À défaut d'outillage, quelques règles empiriques.

Inclure les termes que l'utilisateur emploie réellement. Pour un skill juridique français, "article", "code", "vérifier", "citation", "référence", "hallucination" sont des signaux forts.

Énumérer plusieurs formulations équivalentes. "Vérifier une référence", "contrôler un numéro d'article", "valider une citation" sont trois formulations qui doivent toutes déclencher le skill.

Mentionner les types de documents source si pertinent. "Code civil, Code du travail, Code de commerce" donne plus de signaux qu'une formule générique comme "code juridique".

Éviter les descriptions vagues du type "ce skill aide à vérifier des trucs". Le signal de déclenchement doit être concret.

Étape 7 : tester le skill

Anthropic recommande de définir 2 à 3 prompts de test représentatifs avant publication. (anthropics/skills, skill-creator)

Pour verification-article-fr :

Test 1 : "L'article L1132-1 du Code du travail dispose que toute discrimination
est interdite. Peux-tu vérifier cette citation ?"
Attendu : déclenchement du skill, appel openlegi, restitution du texte officiel
avec liste des 25 critères de discrimination effectivement listés dans
l'article (non tronqués).

Test 2 : "Selon l'article 1382 du Code civil, tout fait quelconque de l'homme
qui cause à autrui un dommage oblige celui par la faute duquel il est arrivé
à le réparer."
Attendu : déclenchement, détection de la renumérotation (devenu Art. 1240),
signalement explicite.

Test 3 : "Quelle est la différence entre droit civil et droit pénal ?"
Attendu : pas de déclenchement (la question ne cite aucun article).

L'objectif des tests négatifs (test 3) est de vérifier que le skill ne sur-déclenche pas sur des questions tangentielles.

Étape 8 : packager le skill

Le format de distribution est un fichier .skill qui est essentiellement le dossier compressé. Le repo anthropics/skills fournit un script package_skill.py pour cela. (anthropics/skills)

Manuellement, l'opération équivaut à :

cd parent-folder/
zip -r verification-article-fr.skill verification-article-fr/

Le fichier .skill peut alors être uploadé sur claude.ai (Settings > Capabilities > Skills), poussé via l'API Claude, ou placé dans le répertoire .claude/skills/ d'un projet Claude Code.

Étape 9 : distribuer sur les trois surfaces

Les Skills ne synchronisent pas automatiquement entre claude.ai, l'API et Claude Code. Il faut publier sur chaque surface séparément. (Claude Platform Docs)

claude.ai : upload du fichier .skill via les settings. Disponible immédiatement dans toutes les conversations de l'utilisateur.

API Claude : upload via l'endpoint Skills API. Le skill devient utilisable dans toute requête API qui le référence. Pour les déploiements multi-utilisateurs (chatbot, intégration interne), c'est le mode privilégié.

Claude Code : déposer le dossier dans .claude/skills/ du projet. Le skill devient disponible dans la session Claude Code de ce projet.

Conséquence pratique. Un Skill destiné à être utilisé largement (par exemple, le skill de vérification d'article décrit ici) doit être publié au moins sur claude.ai et l'API pour couvrir la majorité des cas d'usage en cabinet.

Adoption hors Claude

Le standard Agent Skills est ouvert. Au moment de la publication de ce guide, il est implémenté par Claude Code, OpenAI Codex CLI, Gemini CLI, GitHub Copilot et Cursor. (agentskills.io)

Un skill openlegi rédigé selon le format de base (frontmatter standard, markdown, scripts optionnels) fonctionne sans modification sur la plupart de ces outils. Les fonctionnalités avancées propres à Claude (context forking, par exemple) ne sont pas portables. (Agensi)

Cette portabilité a une implication stratégique pour openlegi : un skill bien conçu autour des outils MCP openlegi devient utilisable depuis n'importe quel assistant compatible MCP plus Agent Skills, ce qui élargit l'usage au-delà de l'écosystème Claude.

Garde-fous spécifiques aux skills juridiques

Anthropic insiste sur le fait que les skills sont des capacités exécutables qui peuvent invoquer des outils et exécuter du code. Un skill malveillant peut détourner ces capacités. (Claude Platform Docs)

Pour les skills juridiques, deux points de vigilance.

Auditer les scripts bundlés. Avant d'installer un skill provenant d'un tiers, lire le contenu des fichiers dans scripts/. Un skill qui télécharge des données externes sans rapport avec sa fonction déclarée doit être considéré comme suspect. (Claude Platform Docs)

Préserver le caractère d'aide à la décision. Tout skill qui produit du contenu juridique doit explicitement positionner sa sortie comme un brouillon à valider, jamais comme un avis. Cette règle reprend la position d'Anthropic sur claude-for-legal : chaque output est un brouillon pour revue d'avocat, pas un avis juridique, pas une conclusion légale. (Anthropic, claude-for-legal)

Avertissement openlegi. openlegi est une interface d'accès à Légifrance, pas un substitut. Tout skill construit sur openlegi doit prévoir un mécanisme de renvoi vers le texte officiel Légifrance via le lien article_lc. Cette règle est documentée dans les limitations connues d'openlegi. (openlegi, Limitations connues)

Exemple complet : repo de référence

Un squelette minimal d'un skill openlegi a la structure suivante :

verification-article-fr/
├── SKILL.md
├── scripts/
│   └── diff_citation.py
├── references/
│   └── renumerotations.md
└── assets/
    └── template-note-verification.md

Pour des exemples grandeur réelle, le repo anthropics/skills contient les skills de production qui alimentent Claude (docx, pdf, pptx, xlsx) ainsi qu'un skill-creator complet, sous licence Apache 2.0 pour les exemples communautaires et source-available pour les skills documents. (anthropics/skills)

Limites et points de vigilance

Le contenu des Skills n'est pas couvert par les arrangements Zero Data Retention. Les définitions de skills et les données d'exécution sont conservées selon la politique de rétention standard d'Anthropic. (Claude Platform Docs) Pour les cabinets soumis à des contraintes strictes de confidentialité, ce point doit être vérifié avant déploiement.

Les Skills ne se synchronisent pas entre claude.ai, l'API et Claude Code. Il faut maintenir la publication sur chaque surface. (Claude Platform Docs)

Claude tend à sous-déclencher les skills sur les tâches simples qu'il peut accomplir directement avec ses outils de base. (anthropics/skills, skill-creator) Un skill construit pour une opération triviale (lire un fichier, par exemple) ne se déclenchera pas, même si sa description matche. Les skills sont conçus pour des tâches multi-étapes ou spécialisées.

Aller plus loin

Sujet Ressource
Skill-creator (raccourci recommandé) anthropics/skills/skill-creator
Spécification Agent Skills agentskills.io
Repo officiel Anthropic anthropics/skills
Documentation API Skills Claude Platform Docs
Article fondateur d'Anthropic Equipping agents for the real world
Doc openlegi optimisée pour LLM llms-full.txt
Outils openlegi exhaustifs openlegi, Liste des outils
Construire un plugin (variante) Guide précédent dans cette documentation

Construire un plugin Claude qui utilise openlegi

Ce guide explique comment packager les outils MCP openlegi dans un plugin Claude réutilisable. Un plugin permet de distribuer à plusieurs utilisateurs (équipe, cabinet, direction juridique) un ensemble cohérent de skills, d'agents planifiés et de connecteurs MCP, configurés une seule fois et personnalisés via un profil de pratique.

Pourquoi un plugin plutôt qu'un simple .mcp.json

Un fichier .mcp.json configure un serveur MCP pour une seule instance Claude. Un plugin Claude apporte trois éléments supplémentaires : un profil de pratique (CLAUDE.md) que tous les skills lisent en amont, des skills explicitement déclenchables via des slash commands (/<plugin>:<skill>), et des agents planifiés qui s'exécutent en arrière-plan selon une cadence cron. (Anthropic, Customize Claude Code with plugins)

Le format de référence est celui utilisé par Anthropic pour le repo claude-for-legal, publié sous licence Apache 2.0. (Anthropic, claude-for-legal) Aucune compilation n'est requise : tout est en markdown et JSON.

À noter. Les plugins fonctionnent sur Claude Cowork, Claude Code et Claude Desktop. Pour Claude Web (claude.ai) seuls les connecteurs MCP individuels sont disponibles, sans la couche plugin.

Prérequis

Élément Comment l'obtenir
Compte openlegi avec token actif auth.openlegi.fr
Claude Code ou Claude Cowork installé claude.com/download
Git (pour publication éventuelle) Selon votre OS

Vérifiez la disponibilité du service openlegi avant de commencer :

curl https://mcp.openlegi.fr/health

La réponse doit indiquer {"status": "ok", ...}. (openlegi, Health Check)

Architecture d'un plugin

Un plugin Claude est un répertoire avec une structure normalisée :

mon-plugin-openlegi/
  .claude-plugin/
    plugin.json           # manifeste du plugin
  CLAUDE.md               # profil de pratique (template)
  README.md
  skills/                 # skills déclenchables
    veille-jorf/
      SKILL.md
    recherche-code/
      SKILL.md
  agents/                 # agents planifiés (optionnel)
    digest-hebdo.md
  hooks/                  # hooks pre/post tool (optionnel)
  .mcp.json               # déclaration des connecteurs MCP

Chaque fichier SKILL.md est un skill autonome. Chaque agent est un fichier markdown avec une frontmatter de planification.

Étape 1 : le manifeste plugin.json

Créez .claude-plugin/plugin.json :

{
  "name": "veille-juridique-fr",
  "version": "0.1.0",
  "description": "Veille et recherche en droit français via openlegi",
  "author": {
    "name": "Cabinet Exemple",
    "url": "https://exemple.fr"
  },
  "license": "MIT",
  "keywords": ["droit-francais", "veille", "openlegi", "legifrance"]
}

Le champ name est utilisé comme préfixe pour toutes les slash commands. Un skill nommé veille-jorf deviendra /veille-juridique-fr:veille-jorf.

Étape 2 : la configuration MCP

Créez .mcp.json à la racine du plugin pour déclarer la dépendance à openlegi :

{
  "mcpServers": {
    "openlegi-legifrance": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://mcp.openlegi.fr/legifrance/mcp?token=${OPENLEGI_TOKEN}"
      ]
    },
    "openlegi-rne": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://mcp.openlegi.fr/inpi/mcp?token=${OPENLEGI_TOKEN}"
      ]
    }
  }
}

Les variables d'environnement comme ${OPENLEGI_TOKEN} sont résolues au moment de l'installation. (openlegi, Configuration Claude Desktop)

Sécurité. Ne jamais coder le token en dur dans .mcp.json. Le plugin sera potentiellement partagé via Git ou un registry, et le token est personnel.

Étape 3 : le profil de pratique CLAUDE.md

Le fichier CLAUDE.md à la racine est un template. Il sera lu automatiquement par tous les skills du plugin avant chaque exécution. Voici un exemple minimal :

# Profil de pratique — Veille juridique FR

## Domaines de pratique
À compléter via /veille-juridique-fr:cold-start-interview

## Codes prioritaires
- Code du travail
- Code de commerce
- Code civil

## Juridictions suivies
- Cour de cassation (chambre sociale)
- Conseil d'État

## Style de restitution
- Note de synthèse en 5 points maximum
- Citation systématique de l'article source avec lien Légifrance
- Aucune paraphrase d'un texte légal sans contrôle préalable

## Garde-fous
- Toujours afficher le lien `article_lc` Légifrance pour vérification
- Signaler toute troncature (`...`) sans la corriger
- Refuser de donner un avis juridique : restitution factuelle uniquement

Le profil est rempli soit manuellement, soit via une cold-start interview (voir étape 5). Il survit aux mises à jour du plugin. (Anthropic, claude-for-legal)

Étape 4 : créer un skill

Un skill est un fichier SKILL.md placé dans skills/<nom-du-skill>/. La structure est imposée par Anthropic.

Exemple complet : skills/veille-jorf/SKILL.md

---
name: veille-jorf
description: Surveille les publications récentes au Journal Officiel selon les filtres définis dans le profil de pratique. Utiliser quand l'utilisateur demande une veille JORF, une revue des derniers décrets, ou un suivi des publications récentes du gouvernement français.
argument-hint: "[nombre_jours]"
---

# Veille JORF

Récupère les publications récentes du Journal Officiel français via openlegi
et produit une note de synthèse selon le style maison défini dans CLAUDE.md.

## Procédure

1. Lire le profil de pratique dans `~/.claude/plugins/config/veille-juridique-fr/CLAUDE.md`
   pour identifier les ministères et types de textes prioritaires.

2. Appeler l'outil openlegi `recherche_journal_officiel` :
   - text_types : selon le profil (par défaut DECRET et ARRETE)
   - ministeres : selon le profil
   - date_publication : [today - nombre_jours, today]
   - sort : PUBLI_DATE_DESC
   - max_results : 20

3. Pour chaque résultat, extraire :
   - Titre
   - Date de signature
   - Ministère émetteur
   - Lien Légifrance (champ `article_lc` des métadonnées)

4. Produire une note structurée :
   - Titre : "Veille JORF — [date début] au [date fin]"
   - 1 ligne par texte avec lien cliquable
   - Aucune paraphrase du contenu : titre officiel uniquement

## Garde-fous

- Si un texte semble particulièrement pertinent, proposer à l'utilisateur
  d'appeler `rechercher_dans_texte_legal` pour récupérer le texte intégral,
  mais ne jamais reproduire le texte sans le lien de vérification.
- Si openlegi retourne une erreur de quota, suggérer de configurer
  des clés API PISTE personnelles via le dashboard openlegi.

Points clés du format SKILL.md :

La frontmatter YAML contient name, description et argument-hint. Le champ description est le signal de déclenchement automatique du skill : Claude le lit pour décider si le skill s'applique au message en cours. Anthropic recommande de garder ce champ sous 1024 caractères. (Anthropic, claude-for-legal)

Le corps du skill est une procédure en langage naturel. Pas de DSL, pas de pseudo-code obligatoire. Claude exécute les étapes en appelant les outils MCP déclarés dans .mcp.json.

Étape 5 : le cold-start interview

C'est la convention Anthropic pour personnaliser le plugin lors du premier usage. Créez skills/cold-start-interview/SKILL.md :

---
name: cold-start-interview
description: Initialise le profil de pratique du plugin veille-juridique-fr. À exécuter une fois après l'installation. Pose des questions sur les domaines, juridictions et style attendu, puis écrit le profil dans CLAUDE.md.
---

# Cold-start interview — Veille juridique FR

## Objectif

Remplir le fichier `~/.claude/plugins/config/veille-juridique-fr/CLAUDE.md`
avec les paramètres spécifiques à l'utilisateur ou au cabinet.

## Procédure

1. Présenter brièvement le plugin et expliquer que l'interview dure 5 à 10 minutes.

2. Poser les questions suivantes, une par une :

   a) Quels sont les 3 à 5 domaines de droit que vous suivez en priorité ?
   b) Quels codes juridiques consultez-vous le plus souvent ?
   c) Quelles juridictions surveillez-vous (Cour de cassation, Conseil d'État, etc.) ?
   d) Quels ministères publient les textes qui vous concernent ?
   e) Quel format de restitution préférez-vous (note courte, tableau, bullet points) ?
   f) Avez-vous des garde-fous spécifiques (privilege, anonymisation, etc.) ?

3. Demander à l'utilisateur de fournir 2 à 3 exemples de notes de veille
   passées (URL ou copier-coller) pour calibrer le style.

4. Écrire le fichier CLAUDE.md selon les réponses.

5. Tester immédiatement avec `/veille-juridique-fr:veille-jorf 7` pour valider.

Cette convention permet à un utilisateur non technique de configurer le plugin sans toucher au fichier CLAUDE.md directement.

Étape 6 : ajouter un agent planifié

Les agents planifiés s'exécutent en arrière-plan selon une cadence cron. Créez agents/digest-hebdo.md :

---
name: digest-hebdo
schedule: "0 8 * * 1"
description: Digest hebdomadaire de veille juridique, envoyé chaque lundi à 8h.
---

# Digest hebdomadaire

Chaque lundi matin, produire une note couvrant :

1. Les publications JORF de la semaine écoulée (appeler veille-jorf 7).
2. Les nouvelles décisions de la Cour de cassation dans les domaines suivis
   (appeler rechercher_jurisprudence_judiciaire avec juridiction_judiciaire=["Cour de cassation"],
   date_decision sur les 7 derniers jours, panorama=true).
3. Les éventuelles décisions du Conseil d'État en lien avec les domaines suivis.

Sortie attendue :
- Un fichier markdown nommé `digest-AAAA-MM-DD.md`
- Posté dans le canal Slack #veille-juridique si le connecteur Slack est configuré
- Sinon stocké dans `~/Documents/veille-juridique/`

La frontmatter schedule utilise une syntaxe cron standard. (Anthropic, claude-for-legal)

Pour un déploiement headless (sans interface), cet agent peut être converti en Managed Agent via l'API Claude. Cela suppose la création d'un cookbook séparé dans managed-agent-cookbooks/digest-hebdo/. La documentation Anthropic décrit cette opération via le script deploy-managed-agent.sh. (Anthropic, claude-for-legal)

Étape 7 : tester le plugin localement

Depuis Claude Code, ajoutez le marketplace local et installez le plugin :

# Pointer Claude Code vers le répertoire local du plugin
/plugin marketplace add /chemin/absolu/vers/mon-plugin-openlegi

# Installer
/plugin install veille-juridique-fr@local

# Redémarrer Claude Code, puis lancer la cold-start interview
/veille-juridique-fr:cold-start-interview

Sur Claude Cowork, l'installation se fait via l'onglet Customize > Browse plugins > Upload custom plugin file en uploadant le répertoire zippé.

Étape 8 : structurer les garde-fous spécifiques au droit français

openlegi documente plusieurs risques structurels lors de l'usage de Légifrance via un LLM, dont la troncature des textes longs, les références internes orphelines et les numéros d'articles réaffectés après renumérotation législative. (openlegi, Limitations connues)

Pour les couvrir au niveau plugin, ajoutez un hook pre-tool dans hooks/openlegi-guardrails.md :

---
name: openlegi-guardrails
type: pre-tool
applies-to: ["rechercher_code", "rechercher_dans_texte_legal"]
---

# Garde-fous openlegi

Avant tout appel à openlegi sur un code ou texte légal :

1. Si la requête concerne un article potentiellement renuméroté
   (Code civil avant 2016, Code du travail avant 2008, Code de commerce),
   demander à l'utilisateur de confirmer la version recherchée.

2. Après l'appel, si la réponse contient `...` ou se termine par des points
   de suspension, signaler explicitement la troncature dans la restitution.

3. Toujours afficher le lien `article_lc` retourné dans les métadonnées,
   sans exception.

4. Ne jamais paraphraser le texte d'un article. Reproduire le texte exact
   retourné par l'outil. Toute reformulation est interdite.

Ces garde-fous reprennent textuellement les recommandations openlegi sur la prévention des hallucinations en droit français. (openlegi, Limitations connues)

Étape 9 : documenter le plugin

Le README.md à la racine du plugin doit minimalement contenir :

  • Liste des skills exposés avec leur commande
  • Liste des connecteurs MCP requis
  • Exemples d'usage
  • Mention du token openlegi nécessaire

Anthropic recommande également de garder la description de chaque skill sous 1024 caractères, car c'est le signal de déclenchement automatique. (Anthropic, claude-for-legal)

Étape 10 : publier le plugin

Trois options de distribution.

Privé (cabinet ou direction juridique) : héberger le repo sur un GitHub privé, ajouter le marketplace via /plugin marketplace add https://github.com/cabinet/mon-plugin-openlegi.

Public via le Legal Builder Hub : Anthropic propose un système de découverte et d'installation de skills communautaires avec contrôles de sécurité. (Anthropic, claude-for-legal) Le passage par ce hub implique de soumettre le plugin à un QA framework qui scanne le contenu, vérifie les licences et journalise les installations.

Public en dehors du hub : publication sur GitHub sous licence libre. C'est l'approche utilisée pour anthropics/claude-for-legal lui-même, sous Apache 2.0. (Anthropic, claude-for-legal)

Exemple complet : repo de référence

Un squelette minimal de plugin openlegi est disponible sous la forme suivante :

veille-juridique-fr/
  .claude-plugin/
    plugin.json
  skills/
    cold-start-interview/SKILL.md
    veille-jorf/SKILL.md
    recherche-code/SKILL.md
    recherche-jurisprudence/SKILL.md
  agents/
    digest-hebdo.md
  hooks/
    openlegi-guardrails.md
  .mcp.json
  CLAUDE.md
  README.md
  LICENSE

Pour un exemple grandeur réelle de plugins exploitant des connecteurs MCP juridiques, consulter le repo anthropics/claude-for-legal qui contient 12 plugins de référence sous licence Apache 2.0 (5,2k étoiles, 726 forks au 16 mai 2026). (Anthropic, claude-for-legal)

Limites et points de vigilance

Les hooks pre-tool et la cadence des agents planifiés sont des fonctionnalités actives sur Claude Code et Cowork. Leur disponibilité sur d'autres clients MCP (Mistral Le Chat, Microsoft Copilot Studio) peut être partielle ou inexistante. (openlegi, Liste des services compatibles)

La délégation à des sous-agents (callable_agents) est en Research Preview au moment de la publication de ce guide et supporte un seul niveau de délégation. (Anthropic, claude-for-legal)

Tout output produit par un plugin construit sur openlegi reste un brouillon à valider. La règle openlegi est absolue : pour tout usage professionnel, vérifier le texte sur Légifrance via le lien article_lc fourni dans les métadonnées. (openlegi, Limitations connues)

Aller plus loin

Sujet Ressource
Outils openlegi exhaustifs openlegi, Liste des outils
Référence Claude plugins anthropics/claude-for-legal
API Claude Managed Agents docs.claude.com/managed-agents
Spécification MCP modelcontextprotocol.io
Clés API PISTE personnelles openlegi, Clés API personnelles

Outils disponibles

Reference des outils MCP pour acceder aux donnees juridiques

Liste des outils

Service Légifrance (Droit français)

Outil Description
rechercher_code Recherche dans les codes juridiques français
rechercher_jurisprudence_judiciaire Recherche de jurisprudence judiciaire (JURI)
rechercher_jurisprudence_administrative Recherche de jurisprudence administrative (CETAT)
rechercher_decisions_cnil Recherche dans les décisions CNIL
rechercher_decisions_constitutionnelles Recherche dans les décisions du Conseil constitutionnel
rechercher_conventions_collectives Recherche dans les conventions collectives (KALI)
rechercher_dans_texte_legal Recherche dans les textes légaux consolidés (LODA)
recherche_journal_officiel Recherche dans le Journal Officiel (JORF)
dernier_journal_officiel Récupère les derniers JO publiés
lister_codes_juridiques Liste tous les codes juridiques disponibles
lister_emetteurs_jorf Liste les émetteurs du Journal Officiel
lister_natures_textes_jorf Liste les types de textes publiés au JORF

Service RNE (Registre National des Entreprises)

Outil Description
rne_search_companies Recherche d'entreprises françaises
rne_get_company_data Données complètes d'une entreprise
rne_get_bodacc Annonces légales BODACC
rne_download_acte Télécharge un acte PDF (URL SAS temporaire)
rne_download_bilan Télécharge un bilan PDF
rne_download_extrait_rne Télécharge l'extrait RNE (KBIS)
rne_list_temp_files Liste les fichiers temporaires
rne_delete_temp_file Supprime un fichier temporaire
rne_cleanup_temp_files Nettoie les fichiers temporaires

Service EUR-Lex (Droit européen)

Outil Description
eurlex_search_by_title Recherche par titre de document
eurlex_search_by_number Recherche par numéro CELEX
eurlex_fetch_article Récupère un article spécifique
eurlex_fetch_recital Récupère un considérant
eurlex_list_articles Liste tous les articles d'un document
eurlex_list_recitals Liste tous les considérants
eurlex_search_in_document Recherche dans le texte d'un document
eurlex_get_metadata Récupère les métadonnées d'un document
eurlex_fetch_text Récupère le texte intégral d'un document

Mode d'affichage - pagination et panorama

Pagination

La plupart des outils supportent la pagination :

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    # Page 1
    result_page1 = client.call_tool(
        "rechercher_code",
        {
            "search": "responsabilité",
            "code_name": "Code civil",
            "page_number": 1,
            "page_size": 20
        }
    )

    # Page 2
    result_page2 = client.call_tool(
        "rechercher_code",
        {
            "search": "responsabilité",
            "code_name": "Code civil",
            "page_number": 2,
            "page_size": 20
        }
    )

Mode Panorama

Pour éviter de récupérer des textes intégraux volumineux, utilisez le mode panorama=True qui retourne uniquement les métadonnées et résumés :

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool(
        "rechercher_jurisprudence_judiciaire",
        {
            "search": "contrat de travail",
            "panorama": True,  # Vue d'ensemble uniquement
            "page_size": 50
        }
    )

Codes juridiques

rechercher_code

Recherche dans les codes juridiques francais (Code civil, Code penal, Code du travail, etc.)

Parametres

Parametre Type Description
search string Termes de recherche (obligatoire)
code_name string Nom du code juridique (obligatoire)
max_results integer Nombre de resultats (defaut: 10, max: 100)
champ string Champ de recherche: ALL, TITLE, NUM_ARTICLE, ARTICLE
sort string Tri: PERTINENCE, DATE_ASC, DATE_DESC

Exemple

{
    "name": "rechercher_code",
    "arguments": {
        "search": "contrat de travail",
        "code_name": "Code du travail",
        "max_results": 5
    }
}

Codes disponibles

Utilisez l'outil lister_codes_juridiques pour obtenir la liste complete des codes disponibles.

Exemples courants: - Code civil - Code penal - Code du travail - Code de commerce - Code de procedure civile - Code de procedure penale - Code general des impots

Jurisprudence judiciaire

rechercher_jurisprudence_judiciaire

Recherche dans la jurisprudence judiciaire (Cour de cassation, cours d'appel, tribunaux)

Parametres

Parametre Type Description
search string Termes ou numeros d'affaires (obligatoire)
max_results integer Nombre de resultats (defaut: 10, max: 100)
juridiction_judiciaire array Juridictions a inclure
publication_bulletin array ['T'] pour publiees au bulletin
sort string Tri: PERTINENCE, DATE_ASC, DATE_DESC
champ string Champ: ALL, TITLE, ABSTRACTS, TEXTE, NUM_AFFAIRE

Exemple

{
    "name": "rechercher_jurisprudence_judiciaire",
    "arguments": {
        "search": "responsabilite civile",
        "juridiction_judiciaire": ["Cour de cassation"],
        "publication_bulletin": ["T"],
        "max_results": 10
    }
}

Juridictions disponibles

  • Cour de cassation
  • Juridictions d'appel
  • Juridictions du premier degre

Journal Officiel (JORF)

recherche_journal_officiel

Recherche dans le Journal Officiel francais (decrets, arretes, nominations, etc.)

Parametres

Parametre Type Description
search string Mots-cles a rechercher (obligatoire)
max_results integer Nombre de resultats (defaut: 5, max: 100)
ministeres array Filtrer par ministeres
text_types array Types de textes (LOI, DECRET, ARRETE, etc.)
sort string Tri: PERTINENCE, PUBLI_DATE_ASC, PUBLI_DATE_DESC
date_publication array Plage de dates [debut, fin] au format YYYY-MM-DD

Exemple

{
    "name": "recherche_journal_officiel",
    "arguments": {
        "search": "nomination ambassadeur",
        "text_types": ["DECRET"],
        "max_results": 10
    }
}

Types de textes disponibles

  • LOI - Lois
  • DECRET - Decrets
  • ARRETE - Arretes
  • ORDONNANCE - Ordonnances
  • DECISION - Decisions
  • AVIS - Avis

Textes consolides (LODA)

Recherche dans les textes legaux historiques avec validation MCP (lois, ordonnances, decrets, arretes)

Parametres

Parametre Type Description
search string Mots-cles ou numero d'article (obligatoire)
text_id string Numero du texte (format AAAA-NUMERO, optionnel)
champ string Champ: ALL, TITLE, TABLE, NUM_ARTICLE, ARTICLE
max_results integer Nombre de resultats (defaut: 10, max: 100)
sort string Tri: PERTINENCE, PUBLICATION_DATE_ASC, PUBLICATION_DATE_DESC
type_recherche string TOUS_LES_MOTS_DANS_UN_CHAMP, EXACTE, UN_DES_MOT

Exemples

Recherche d'un article specifique dans une loi:

{
    "name": "rechercher_dans_texte_legal",
    "arguments": {
        "search": "7",
        "text_id": "78-17",
        "champ": "NUM_ARTICLE"
    }
}

Recherche generale:

{
    "name": "rechercher_dans_texte_legal",
    "arguments": {
        "search": "signature electronique validite conditions",
        "max_results": 10
    }
}

Modes d'integration

Differentes facons d'utiliser OpenLegi MCP

Integration Python - Installation

Intégration Python - OpenLegi MCP

🎯 Recommandation importante

Pour une intégration simple et robuste, nous recommandons d'utiliser les librairies officielles MCP :

La classe OpenLegiClient ci-dessous est fournie à titre éducatif et pour les cas d'usage avancés nécessitant un contrôle fin du protocole.


Installation

pip install requests

Configuration

URLs de base

Environnement URL
Production https://mcp.openlegi.fr
Local http://localhost:3000

Services disponibles

Service Endpoint Description
Légifrance /legifrance/mcp Codes, jurisprudences, JORF, textes légaux
INPI /inpi/mcp Registre du commerce et des sociétés
EUR-Lex /eurlex/mcp Droit européen

Authentification

Trois méthodes supportées :

1. Header Authorization (recommandé)

session.headers.update({
    "Authorization": "Bearer VOTRE_TOKEN_ICI"
})

2. Query Parameter

url = f"https://mcp.openlegi.fr/legifrance/mcp?token=VOTRE_TOKEN"

3. Path Token

url = "https://mcp.openlegi.fr/legifrance/mcp/token/VOTRE_TOKEN"

Client Python OpenLegiClient

Classe complète

import requests
import json


class OpenLegiClient:
    """Client Python pour l'API OpenLegi MCP.

    Gère le protocole MCP avec support du format SSE (Server-Sent Events).
    """

    def __init__(
        self,
        token: str,
        service: str = "legifrance",
        base_url: str = "https://mcp.openlegi.fr"
    ):
        """Initialise le client MCP.

        Args:
            token: Token d'authentification OpenLegi
            service: Service à utiliser ('legifrance', 'inpi', 'eurlex')
            base_url: URL de base du serveur MCP
        """
        self.base_url = base_url
        self.service = service
        self.endpoint = f"{base_url}/{service}/mcp"
        self.session = requests.Session()

        # Headers requis par le protocole MCP
        self.session.headers.update({
            "Content-Type": "application/json",
            "Accept": "application/json, text/event-stream",  # Les deux types sont requis
            "Authorization": f"Bearer {token}"
        })
        # Initialiser la session MCP
        self._initialize()

    def _initialize(self):
        """Initialise la session MCP et récupère le session ID."""
        payload = {
            "jsonrpc": "2.0",
            "id": 0,
            "method": "initialize",
            "params": {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {
                    "name": "OpenLegiClient",
                    "version": "1.0.0"
                }
            }
        }

        response = self.session.post(self.endpoint, json=payload)
        response.raise_for_status()

        # Récupérer le session ID du header
        session_id = response.headers.get('mcp-session-id')
        if session_id:
            self.session.headers.update({"mcp-session-id": session_id})


    @staticmethod
    def _parse_sse_response(text: str) -> dict:
        """Parse une réponse SSE et extrait le JSON de la ligne data:.

        Le serveur MCP renvoie les réponses au format SSE :
        event: message
        data: {"jsonrpc":"2.0","id":1,"result":{...}}

        Args:
            text: Texte brut de la réponse HTTP

        Returns:
            Dictionnaire JSON extrait de la ligne 'data:'

        Raises:
            ValueError: Si aucune ligne 'data:' n'est trouvée
        """
        lines = text.strip().split('\n')
        for line in lines:
            if line.startswith('data: '):
                return json.loads(line[6:])  # Retire 'data: ' et parse le JSON
        raise ValueError("Aucune ligne 'data:' trouvée dans la réponse SSE")

    def call_tool(self, tool_name: str, arguments: dict) -> dict:
        """Appelle un tool MCP avec les arguments fournis.

        Args:
            tool_name: Nom du tool à appeler (ex: 'rechercher_code')
            arguments: Dictionnaire des arguments du tool

        Returns:
            Résultat du tool (contenu du champ 'result')

        Raises:
            requests.HTTPError: Si la requête HTTP échoue
            Exception: Si le serveur renvoie une erreur JSON-RPC
        """
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/call",
            "params": {
                "name": tool_name,
                "arguments": arguments
            }
        }

        response = self.session.post(self.endpoint, json=payload)
        response.raise_for_status()

        # Parser la réponse SSE
        result = self._parse_sse_response(response.text)

        if "error" in result:
            raise Exception(f"Erreur MCP: {result['error']}")

        return result["result"]

    def list_tools(self) -> list[dict]:
        """Liste tous les tools disponibles sur le serveur.

        Returns:
            Liste des tools avec leurs métadonnées (name, description, inputSchema)

        Raises:
            requests.HTTPError: Si la requête HTTP échoue
        """
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/list",
            "params": {}
        }

        response = self.session.post(self.endpoint, json=payload)
        response.raise_for_status()

        # Parser la réponse SSE
        result = self._parse_sse_response(response.text)
        return result["result"]["tools"]

    def close(self):
        """Ferme la session HTTP."""
        self.session.close()

    def __enter__(self):
        """Support du context manager (with statement)."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Fermeture automatique de la session."""
        self.close()

Exemples d'utilisation

1. Lister les tools disponibles

from openlegi_client import OpenLegiClient

# Créer le client avec votre token
token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Récupérer la liste des tools
    tools = client.list_tools()

    print(f"📋 {len(tools)} tools disponibles:\n")

    for tool in tools:
        print(f"🔧 {tool['name']}")
        print(f"   {tool['description'][:100]}...")
        print()

Sortie attendue :

📋 12 tools disponibles:

🔧 rechercher_dans_texte_legal
   Recherche dans les textes légaux consolidés depuis légifrance...

🔧 rechercher_code
   Recherche des articles juridiques dans les codes juridiques français...

🔧 rechercher_jurisprudence_judiciaire
   Recherche des jurisprudences judiciaires avec validation MCP...

🔧 rechercher_jurisprudence_administrative
   Recherche dans la jurisprudence administrative (Conseil d'État...

🔧 rechercher_decisions_cnil
   Recherche dans les délibérations et décisions de la CNIL...

🔧 rechercher_conventions_collectives
   Recherche dans les conventions collectives (KALI)...

🔧 rechercher_decisions_constitutionnelles
   Recherche dans les décisions du Conseil constitutionnel...

🔧 recherche_journal_officiel
   Recherche dans le Journal Officiel français...

🔧 dernier_journal_officiel
   Récupère le(s) dernier(s) Journal(aux) Officiel(s) publiés...

🔧 lister_codes_juridiques
   Liste tous les codes juridiques disponibles sur Legifrance...

🔧 lister_emetteurs_jorf
   Liste tous les émetteurs/autorités disponibles pour les recherches JORF...

🔧 lister_natures_textes_jorf
   Liste toutes les natures de textes disponibles pour les recherches JORF...

2. Rechercher un article dans un code

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Rechercher l'article 9 du Code civil
    result = client.call_tool(
        tool_name="rechercher_code",
        arguments={
            "search": "9",
            "code_name": "Code civil",
            "champ": "NUM_ARTICLE"
        }
    )

    print("📖 Résultat de la recherche:\n")
    print(result)

3. Rechercher dans la jurisprudence

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Rechercher des décisions sur la responsabilité civile
    result = client.call_tool(
        tool_name="rechercher_jurisprudence_judiciaire",
        arguments={
            "search": "responsabilité civile",
            "juridiction_judiciaire": ["Cour de cassation"],
            "publication_bulletin": ["T"],
            "sort": "DATE_DESC",
            "page_size": 5
        }
    )

    print("⚖️ Jurisprudences trouvées:\n")
    print(result)

4. Consulter le dernier Journal Officiel

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Récupérer les 3 derniers JO
    result = client.call_tool(
        tool_name="dernier_journal_officiel",
        arguments={
            "nb_jo": 3
        }
    )

    print("📰 Derniers Journaux Officiels:\n")
    print(result)

5. Rechercher dans les conventions collectives

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Rechercher des conventions sur le télétravail
    result = client.call_tool(
        tool_name="rechercher_conventions_collectives",
        arguments={
            "search": "télétravail",
            "panorama": True,  # Vue d'ensemble sans texte intégral
            "page_size": 10
        }
    )

    print("📄 Conventions collectives:\n")
    print(result)

6. Lister les codes juridiques disponibles

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

with OpenLegiClient(token=token) as client:
    # Lister tous les codes disponibles
    result = client.call_tool(
        tool_name="lister_codes_juridiques",
        arguments={}
    )

    print("📚 Codes juridiques disponibles:\n")
    for code in result:
        print(f"  • {code}")

Gestion des erreurs

Erreurs HTTP

from openlegi_client import OpenLegiClient
import requests

token = "votre_token_ici"

try:
    with OpenLegiClient(token=token) as client:
        result = client.call_tool("rechercher_code", {"search": "test"})
except requests.HTTPError as e:
    print(f"❌ Erreur HTTP: {e}")
    print(f"Status: {e.response.status_code}")
    print(f"Détails: {e.response.text}")

Erreurs JSON-RPC

from openlegi_client import OpenLegiClient

token = "votre_token_ici"

try:
    with OpenLegiClient(token=token) as client:
        # Tool inexistant
        result = client.call_tool("tool_qui_nexiste_pas", {})
except Exception as e:
    print(f"❌ Erreur MCP: {e}")

Notes importantes

Format SSE (Server-Sent Events)

Le serveur MCP renvoie les réponses au format SSE, même pour les requêtes JSON-RPC simples :

event: message
data: {"jsonrpc":"2.0","id":1,"result":{...}}

C'est pourquoi : 1. Le header Accept doit inclure text/event-stream 2. La réponse doit être parsée ligne par ligne 3. On ne peut pas utiliser directement response.json()

Performance

  • Toujours utiliser requests.Session() pour réutiliser la connexion HTTP
  • Le context manager (with statement) garantit la fermeture de la session
  • Les credentials PISTE personnels éliminent les limitations de rate limiting

Authentification

  • Le token est obligatoire pour tous les endpoints
  • Format recommandé : Header Authorization: Bearer <token>
  • Testez votre token avec list_tools() avant d'appeler des tools complexes

Alternatives recommandées

Pour une intégration production, considérez :

  1. MCP Python SDK - SDK officiel avec :
  2. Support complet du protocole MCP
  3. Gestion automatique des sessions
  4. Streaming SSE natif
  5. Types Python stricts

  6. Claude Desktop - Pour une utilisation interactive :

  7. Configuration via claude_desktop_config.json
  8. Interface graphique native
  9. Pas de code nécessaire

  10. Claude Code CLI - Pour l'automatisation :

  11. Intégration Git
  12. Exécution de tâches complexes
  13. Support natif des serveurs MCP

Support


Licence

Ce code est fourni sous licence MIT à titre d'exemple éducatif.

Health Check - Vérifier la disponibilité des services

Endpoint public

GET https://mcp.openlegi.fr/health

Aucune authentification requise. Ne consomme pas votre quota de rate limit.

Réponse

{
  "status": "ok",
  "services": {
    "legifrance": true,
    "rne": true,
    "eurlex": true,
    "bofip": true,
    "bodacc": true
  },
  "auth_enabled": true
}
Champ Description
status "ok" si la gateway est opérationnelle
services Statut de chaque service (true = activé, false = désactivé)
auth_enabled Indique si l'authentification est active

Exemple

curl https://mcp.openlegi.fr/health

Usage recommandé

Utilisez cet endpoint pour vérifier la disponibilité du service avant ou pendant vos intégrations.

Ne pas utiliser les outils MCP (comme lister_codes_juridiques) comme health check : - Cela consomme votre quota de rate limit (40 requêtes / 5 min en credentials partagés) - C'est plus lent (~2-3s vs instantané pour /health) - Ce n'est pas conçu pour cela

Monitoring

Pour un monitoring automatisé, configurez un check régulier sur cet endpoint :

# Vérification simple
STATUS=$(curl -s https://mcp.openlegi.fr/health | jq -r '.status')
if [ "$STATUS" != "ok" ]; then
    echo "Service indisponible"
fi

Integration Python - Utilisation

Exemples d'utilisation

Lister les outils disponibles

# Service Légifrance
with OpenLegiClient(token="VOTRE_TOKEN", service="legifrance") as client:
    tools = client.list_tools()
    print(f"🔧 {len(tools)} outils Légifrance disponibles")
    for tool in tools:
        print(f"- {tool['name']}: {tool['description']}")

# Service RNE (Entreprises)
with OpenLegiClient(token="VOTRE_TOKEN", service="rne") as client:
    tools = client.list_tools()
    print(f"\n🏢 {len(tools)} outils RNE disponibles")

# Service EUR-Lex (Droit européen)
with OpenLegiClient(token="VOTRE_TOKEN", service="eurlex") as client:
    tools = client.list_tools()
    print(f"\n🇪🇺 {len(tools)} outils EUR-Lex disponibles")

Rechercher dans le Journal Officiel

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool(
        "recherche_journal_officiel",
        {
            "search": "dechet industriel recyclage",
            "max_results": 5,
            "sort": "SIGNATURE_DATE_DESC"
        }
    )

    # Le résultat est dans result["content"][0]["text"]
    print(result["content"][0]["text"])

Rechercher dans un code juridique

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool(
        "rechercher_code",
        {
            "search": "mariage",
            "code_name": "Code civil",
            "page_size": 10
        }
    )

    print(result["content"][0]["text"])

Rechercher de la jurisprudence

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool(
        "rechercher_jurisprudence_judiciaire",
        {
            "search": "licenciement abusif",
            "sort": "DATE_DESC",
            "panorama": True,  # Vue d'ensemble (métadonnées uniquement)
            "page_size": 10
        }
    )

    print(result["content"][0]["text"])
with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool(
        "rechercher_dans_texte_legal",
        {
            "search": "signature électronique",
            "champ": "ALL",
            "page_size": 5
        }
    )

    print(result["content"][0]["text"])

Lister les codes juridiques disponibles

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    result = client.call_tool("lister_codes_juridiques", {})
    print(result["content"][0]["text"])

Gestion des erreurs

import requests
from requests.exceptions import RequestException, HTTPError

with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client:
    try:
        result = client.call_tool(
            "recherche_journal_officiel",
            {"search": "urbanisme"}
        )
        print(result["content"][0]["text"])

    except HTTPError as e:
        if e.response.status_code == 401:
            print("❌ Erreur d'authentification: token invalide ou expiré")
        elif e.response.status_code == 403:
            print("❌ Accès interdit: vérifiez vos permissions")
        elif e.response.status_code == 404:
            print("❌ Endpoint non trouvé")
        else:
            print(f"❌ Erreur HTTP {e.response.status_code}: {e}")

    except RequestException as e:
        print(f"❌ Erreur de connexion: {e}")

    except Exception as e:
        print(f"❌ Erreur: {e}")

Rechercher des décisions CNIL

with OpenLegiClient(token="VOTRE_TOKEN", service="legifrance") as client:
    result = client.call_tool(
        "rechercher_decisions_cnil",
        {
            "search": "RGPD",
            "nature_delib": ["Sanction"],
            "sort": "DATE_DECISION_DESC",
            "page_size": 10
        }
    )
    print(result["content"][0]["text"])

Rechercher des décisions du Conseil constitutionnel

with OpenLegiClient(token="VOTRE_TOKEN", service="legifrance") as client:
    result = client.call_tool(
        "rechercher_decisions_constitutionnelles",
        {
            "search": "liberté expression",
            "panorama": True,
            "page_size": 10
        }
    )
    print(result["content"][0]["text"])

Rechercher des conventions collectives

with OpenLegiClient(token="VOTRE_TOKEN", service="legifrance") as client:
    result = client.call_tool(
        "rechercher_conventions_collectives",
        {
            "search": "métallurgie",
            "page_size": 10
        }
    )
    print(result["content"][0]["text"])

Rechercher une entreprise (RNE)

with OpenLegiClient(token="VOTRE_TOKEN", service="rne") as client:
    result = client.call_tool(
        "rne_search_companies",
        {
            "query": "Google France",
            "page": 1,
            "per_page": 10
        }
    )
    print(result["content"][0]["text"])

Rechercher dans EUR-Lex

with OpenLegiClient(token="VOTRE_TOKEN", service="eurlex") as client:
    result = client.call_tool(
        "eurlex_search_by_title",
        {
            "title": "RGPD",
            "max_results": 5
        }
    )
    print(result["content"][0]["text"])

Optimisation des performances

Optimisation des performances

Réutilisation de session

# ❌ Mauvaise pratique : nouvelle connexion à chaque appel
import requests

def mauvais_appel(search: str):
    response = requests.post(
        "https://mcp.openlegi.fr/legifrance/mcp",
        json={"jsonrpc": "2.0", "method": "tools/call", ...},
        headers={"Authorization": "Bearer TOKEN"}
    )
    return response.json()

# ✅ Bonne pratique : réutilisation de session
with OpenLegiClient(token="TOKEN") as client:
    for search_term in ["mariage", "divorce", "succession"]:
        result = client.call_tool("rechercher_code", {"search": search_term})
        # Traitement...

Connexion persistante

# Pour un script long avec de nombreux appels
client = OpenLegiClient(token="VOTRE_TOKEN_ICI")

try:
    for i in range(100):
        result = client.call_tool("recherche_journal_officiel", {"search": f"terme{i}"})
        # Traitement...
finally:
    client.close()  # Fermeture propre

Depannage - codes erreur

Erreur 400 Bad Request

  • Vérifiez qu'il n'y a pas de slash final avant le ? dans l'URL
  • Vérifiez le format JSON de votre payload

Erreur 401 Unauthorized

  • Vérifiez que votre token est valide
  • Vérifiez que le token est bien préfixé par Bearer dans le header

Erreur 403 Forbidden

  • Vérifiez que votre token a les permissions nécessaires (scopes)
  • Contactez l'administrateur pour vérifier vos droits d'accès

Erreur de connexion

  • Vérifiez que le serveur est accessible
  • En local : vérifiez que le serveur est démarré (./scripts/run_dev.sh)
  • Vérifiez votre connexion réseau

Protocole MCP et API Rest

SSE vs REST : Comprendre le protocole MCP

🤔 Le malentendu fréquent

Ce que les gens pensent :

"C'est une API REST classique, je fais une requête POST et je reçois du JSON"

La réalité :

"C'est un serveur MCP qui utilise SSE (Server-Sent Events) même pour les requêtes simples"


📊 Comparaison visuelle

API REST classique

POST /api/search HTTP/1.1
Content-Type: application/json
Accept: application/json

{"query": "test"}

Réponse REST classique :

HTTP/1.1 200 OK
Content-Type: application/json

{"results": [...], "count": 42}

MCP avec SSE

POST /legifrance/mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc": "2.0", "method": "tools/list", "params": {}}

Réponse MCP (format SSE) :

HTTP/1.1 200 OK
Content-Type: text/event-stream

event: message
data: {"jsonrpc":"2.0","id":1,"result":{...}}

🔍 Différence clé

Aspect REST classique MCP avec SSE
Content-Type application/json text/event-stream
Format réponse JSON brut event: ...\ndata: {...}
Parsing response.json() Parser ligne par ligne
Header Accept application/json application/json, text/event-stream
Streaming Non (buffer complet) Oui (événements progressifs)

🧩 Pourquoi MCP utilise SSE ?

1. Protocole unifié pour tous les cas d'usage

MCP supporte deux modes de communication :

Mode 1 : Requête/Réponse simple (votre cas)

# Vous envoyez une requête
{"jsonrpc": "2.0", "method": "tools/list", "params": {}}

# Vous recevez UNE réponse
event: message
data: {"jsonrpc":"2.0","result":{...}}

Mode 2 : Notifications asynchrones

# Le serveur peut envoyer des notifications à tout moment
event: notification
data: {"method": "tools/list_changed"}

event: notification
data: {"method": "resources/updated", "params": {...}}

Le SSE permet au serveur d'envoyer des événements au client SANS que le client n'ait fait de requête.

2. Support du streaming de contenu

Pour les outils qui génèrent beaucoup de données :

# Réponse progressive (streaming)
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Début..."}]}}

event: progress
data: {"progress": 50, "total": 100}

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"...Suite..."}]}}

3. Maintien de la connexion

Avec SSE, la connexion HTTP reste ouverte : - Le serveur peut pousser des mises à jour - Pas besoin de polling (requêtes répétées) - Plus efficace pour les sessions longues


🎯 "Streamable" vs "Streaming"

Streamable (capacité)

# Le header indique QUE LE CLIENT SUPPORTE le streaming
headers = {
    "Accept": "application/json, text/event-stream"
}

Signification : "Je suis capable de recevoir à la fois du JSON simple ET du streaming SSE"

Streaming (action)

# Le serveur CHOISIT de streamer la réponse
for chunk in long_computation():
    send_sse_event("progress", {"chunk": chunk})

En pratique pour MCP : - Toutes les réponses sont au format SSE (même si elles ne "streament" pas) - C'est le format d'enveloppe du protocole - Même une réponse instantanée utilise event: message\ndata: {...}


💡 Analogie simple

REST classique = Lettre postale

  • Vous envoyez une lettre
  • Vous recevez UNE réponse complète
  • La boîte aux lettres se ferme après

MCP avec SSE = Canal téléphonique

  • Vous appelez et la ligne reste ouverte
  • Vous posez une question (requête)
  • Vous recevez la réponse (event: message)
  • MAIS la ligne reste ouverte
  • Le serveur peut vous rappeler plus tard (notifications)
  • Vous pouvez poser d'autres questions sur la même ligne

🛠️ Implémentation pratique

Code REST classique (NE MARCHE PAS avec MCP)

import requests

# ❌ Ceci échoue avec MCP
response = requests.post(
    "https://mcp.openlegi.fr/legifrance/mcp",
    json={"jsonrpc": "2.0", "method": "tools/list"},
    headers={"Authorization": "Bearer TOKEN"}
)

# ❌ JSONDecodeError: Expecting value
data = response.json()

Pourquoi ça échoue ?

>>> response.text
'event: message\ndata: {"jsonrpc":"2.0",...}\n\n'

>>> response.json()
# Erreur : "event: message" n'est pas du JSON valide !

Code MCP correct (avec parser SSE)

import requests
import json

def parse_sse(text):
    """Extrait le JSON de la ligne 'data:' """
    for line in text.split('\n'):
        if line.startswith('data: '):
            return json.loads(line[6:])  # Retire 'data: '
    raise ValueError("Pas de ligne data: trouvée")

# ✅ Ceci fonctionne
response = requests.post(
    "https://mcp.openlegi.fr/legifrance/mcp",
    json={"jsonrpc": "2.0", "method": "tools/list"},
    headers={
        "Authorization": "Bearer TOKEN",
        "Accept": "application/json, text/event-stream"  # Important !
    }
)

# ✅ Parser le SSE
data = parse_sse(response.text)
print(data)  # {"jsonrpc":"2.0","result":{...}}

📋 Format SSE en détail

Structure complète

event: message
id: 123
data: {"jsonrpc":"2.0","id":1,"result":{...}}
Champ Description Exemple
event: Type d'événement message, notification, progress
id: Identifiant unique (optionnel) 123
data: Payload JSON {"jsonrpc":"2.0",...}
Ligne vide Fin de l'événement \n\n

Événements multiples

event: message
data: {"result": "partie 1"}

event: progress
data: {"percent": 50}

event: message
data: {"result": "partie 2"}

Chaque bloc est séparé par une ligne vide.


⚠️ Erreurs courantes

Erreur 1 : Header Accept manquant

# ❌ ERREUR 406 Not Acceptable
headers = {"Accept": "application/json"}

# ✅ CORRECT
headers = {"Accept": "application/json, text/event-stream"}

Message d'erreur :

{
  "error": {
    "code": -32600,
    "message": "Not Acceptable: Client must accept both application/json and text/event-stream"
  }
}

Erreur 2 : Utiliser response.json() directement

# ❌ JSONDecodeError
data = response.json()

# ✅ Parser le SSE d'abord
data = parse_sse(response.text)

Erreur 3 : Oublier l'initialisation de session

# ❌ 400 Bad Request (pas de session ID)
response = session.post(url, json={"method": "tools/list"})

# ✅ Initialiser d'abord
init_response = session.post(url, json={"method": "initialize"})
session_id = init_response.headers.get('mcp-session-id')
session.headers.update({"mcp-session-id": session_id})

🚀 Avantages du SSE pour MCP

1. Bidirectionnel (serveur → client)

# Le serveur peut notifier le client
event: notification
data: {"method": "tools/list_changed"}

2. Reconnexion automatique

// Côté JavaScript
const eventSource = new EventSource('/mcp');
eventSource.addEventListener('message', (e) => {
  console.log(JSON.parse(e.data));
});
// Se reconnecte automatiquement si déconnecté

3. HTTP standard

  • Pas besoin de WebSocket
  • Passe les proxies/firewalls
  • Compatible avec HTTP/1.1 et HTTP/2

4. Streaming progressif

# Pour les longues réponses
for i in range(100):
    send_event("progress", {"step": i, "total": 100})

send_event("message", {"result": final_data})

📚 Spécification SSE

Format officiel W3C

stream        = [ bom ] *event
event         = *( comment / field ) end-of-line
comment       = colon *any-char end-of-line
field         = 1*name-char [ colon [ space ] *any-char ] end-of-line
end-of-line   = ( cr lf / cr / lf )

Champs standards

Champ Signification
event: Type d'événement (défaut: "message")
data: Données de l'événement (peut être multi-lignes)
id: Identifiant de l'événement (pour reconnexion)
retry: Délai de reconnexion en ms

Exemple multi-lignes

event: message
data: {
data:   "long": "json",
data:   "with": "multiple",
data:   "lines": true
data: }

Le client doit joindre toutes les lignes data:


🔧 Debugging

Voir le format brut avec curl

curl -N \
  -H "Authorization: Bearer TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{}}' \
  https://mcp.openlegi.fr/legifrance/mcp

Sortie :

event: message
data: {"jsonrpc":"2.0","id":null,"result":{"tools":[...]}}

Voir avec Python requests

import requests

response = requests.post(
    "https://mcp.openlegi.fr/legifrance/mcp",
    json={"jsonrpc": "2.0", "method": "tools/list"},
    headers={
        "Authorization": "Bearer TOKEN",
        "Accept": "application/json, text/event-stream"
    }
)

print("=== RAW RESPONSE ===")
print(response.text)
print("=== CONTENT-TYPE ===")
print(response.headers.get('Content-Type'))

📖 Ressources

Spécifications

  • SSE (W3C) : https://html.spec.whatwg.org/multipage/server-sent-events.html
  • MCP Protocol : https://spec.modelcontextprotocol.io/
  • JSON-RPC 2.0 : https://www.jsonrpc.org/specification

Librairies Python

  • requests : HTTP classique (ce que vous utilisez)
  • httpx : HTTP avec support async et streaming
  • sseclient-py : Parser SSE dédié
  • mcp SDK : SDK officiel MCP

🎓 Résumé pour expliquer à quelqu'un

"Le serveur MCP n'est PAS une API REST classique. Il utilise le protocole MCP qui encapsule toutes les réponses au format SSE (Server-Sent Events), même pour les requêtes simples.

C'est pourquoi vous devez : 1. Ajouter text/event-stream dans le header Accept 2. Parser la réponse ligne par ligne pour extraire le JSON de la ligne data: 3. Ne pas utiliser response.json() directement

Le SSE permet au serveur d'envoyer des notifications asynchrones et de streamer les réponses progressivement, même si pour l'instant vous n'utilisez que le mode requête/réponse simple.

C'est comme si vous demandiez une lettre postale mais que vous receviez un télégramme : le contenu est le même, mais le format d'enveloppe est différent."


✅ Checklist de migration REST → MCP

  • [ ] Ajouter text/event-stream au header Accept
  • [ ] Implémenter un parser SSE (parse_sse_response())
  • [ ] Remplacer response.json() par parse_sse(response.text)
  • [ ] Ajouter l'initialisation de session (initialize avec mcp-session-id)
  • [ ] Gérer les notifications asynchrones (optionnel)
  • [ ] Tester avec différents tools
  • [ ] Documenter le changement pour l'équipe

📞 Questions fréquentes

Q: Pourquoi pas du JSON simple ?

R: MCP a besoin de supporter les notifications serveur et le streaming. SSE est le standard HTTP pour ça.

Q: Est-ce que je peux forcer du JSON pur ?

R: Non, c'est le protocole MCP. Utilisez les SDK officiels si vous voulez abstraire ça.

Q: Ça consomme plus de ressources ?

R: Négligeable. Le surcoût est juste quelques lignes de parsing.

Q: Et WebSocket alors ?

R: SSE est plus simple (HTTP standard) et suffisant pour MCP. WebSocket serait overkill.

Q: Ça marche avec tous les clients HTTP ?

R: Oui, mais vous devez parser manuellement. Les SDK MCP le font pour vous.


🎯 Conclusion

MCP avec SSE ≠ API REST classique

Même si vous faites des requêtes POST et recevez des données, le format est différent. C'est une architecture événementielle (event-driven) qui se trouve être synchrone pour vos cas d'usage actuels.

Solution recommandée : Utilisez un SDK MCP officiel ou la classe OpenLegiClient fournie qui gère tout ça pour vous.

Architecture

Vue d'ensemble

Architecture OpenLegi MCP

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   LLM/Client    │ ←→  │  Serveur MCP    │ ←→  │ API Legifrance  │
│                 │     │         │     │                 │
│ Claude Desktop  │     │ Protocole MCP   │     │ Donnees         │
│ Applications    │     │ JSON-RPC        │     │ Juridiques      │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Composants

  1. Client LLM - Claude Desktop, Cursor, ou votre application
  2. Serveur MCP - Gere l'authentification et les requetes
  3. API Legifrance - Source officielle des donnees juridiques

Flux de donnees

  1. Le client envoie une requete JSON-RPC au serveur MCP
  2. Le serveur valide le token et verifie les quotas
  3. La requete est traduite en appel API Legifrance
  4. Les resultats sont formates et renvoyes au client

Sécurité

Securite

Authentification

  • Token Bearer - Authentification par token unique
  • OAuth2 Microsoft - Connexion via compte Microsoft
  • Chiffrement - Toutes les communications en HTTPS

Protection des donnees

  • Rate limiting - Limitation du nombre de requetes
  • Logs d'acces - Tracabilite des appels
  • Cles API chiffrees - Stockage securise des credentials

Bonnes pratiques

  1. Ne partagez jamais votre token - Il est personnel
  2. Regenerez-le regulierement - Via votre Dashboard
  3. Utilisez des variables d'environnement - Ne codez pas le token en dur

En cas de compromission

Si vous pensez que votre token a ete compromis:

  1. Connectez-vous a votre Dashboard
  2. Cliquez sur "Regenerer le token"
  3. Mettez a jour vos configurations

Performance

Performance

Optimisations

  • Formatage optimise - Reponses adaptees aux LLM
  • Transport HTTP streamable - Pour les grandes reponses
  • Pagination intelligente - Gestion efficace des volumes

Conseils d'utilisation

  1. Limitez max_results - Ne demandez que ce dont vous avez besoin
  2. Utilisez les filtres - Affinez vos recherches (ministeres, dates, etc.)
  3. Preferez les recherches specifiques - Plutot que des termes trop generaux

Temps de reponse typiques

Operation Temps moyen
Liste des outils < 100ms
Recherche simple 200-500ms
Recherche complexe 500ms-2s

Gestion des erreurs

En cas de timeout ou d'erreur, le serveur renvoie un message explicite. Attendez quelques secondes avant de reessayer.