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

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": {
    "OpenLegi": {
      "type": "streamable-http",
      "url": "https://mcp.openlegi.fr/legifrance/mcp",
      "headers": {
        "Authorization": "Bearer VOTRE_TOKEN_ICI"
      }
    }
  }
}

Pour accéder aux 3 services (Légifrance, RNE, EUR-Lex) :

{
  "mcpServers": {
    "Légifrance": {
      "type": "streamable-http",
      "url": "https://mcp.openlegi.fr/legifrance/mcp",
      "headers": {
        "Authorization": "Bearer VOTRE_TOKEN_ICI"
      }
    },
    "RNE": {
      "type": "streamable-http",
      "url": "https://mcp.openlegi.fr/rne/mcp",
      "headers": {
        "Authorization": "Bearer VOTRE_TOKEN_ICI"
      }
    },
    "EUR-Lex": {
      "type": "streamable-http",
      "url": "https://mcp.openlegi.fr/eurlex/mcp",
      "headers": {
        "Authorization": "Bearer VOTRE_TOKEN_ICI"
      }
    }
  }
}

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.

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.

Clés API

Option 1 : Token de test (limité)

Inscrivez-vous gratuitement pour obtenir un token de test. Ce token utilise des credentials PISTE partagés et est soumis à des limitations strictes de rate limiting.

Option 2 : Credentials personnels PISTE (recommandé)

Pour un accès illimité, inscrivez-vous sur PISTE :

  1. Créez un compte sur PISTE
  2. Créez une application pour obtenir vos credentials :
  3. Client ID
  4. Client Secret
  5. Ajoutez vos credentials dans votre profil OpenLegi
  6. Profitez d'un accès illimité aux API Légifrance

Avantages des credentials personnels : - ✅ Aucune limitation de nombre de requêtes - ✅ Aucune limitation de durée - ✅ Performances optimales - ✅ Priorité dans les files d'attente

Pourquoi s'inscrire sur PISTE ?

Les tokens partagés utilisent des credentials PISTE communs à tous les utilisateurs, ce qui entraîne : - Des limites strictes de nombre de requêtes par fenêtre de temps - Des limites de durée d'utilisation cumulée - Des ralentissements en cas de forte affluence

Avec vos propres credentials PISTE (gratuits), vous bénéficiez d'un accès direct et illimité aux API officielles Légifrance.

Ressources

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.