# OpenLegi - Documentation Complete > Serveur MCP pour l'acces aux sources juridiques francaises officielles ## Presentation OpenLegi est un serveur MCP (Model Context Protocol) qui permet aux LLMs d'acceder directement aux bases de donnees juridiques francaises : Legifrance, RNE (Registre National des Entreprises) et EUR-Lex. ## Configuration rapide ### Claude Desktop ```json { "mcpServers": { "Legifrance": { "type": "streamable-http", "url": "https://mcp.openlegi.fr/legifrance/mcp", "headers": { "Authorization": "Bearer VOTRE_TOKEN" } } } } ``` ## Liens - Documentation web : https://auth.openlegi.fr/documentation/ - Dashboard (obtenir token) : https://auth.openlegi.fr/users/dashboard/ - Contact : https://auth.openlegi.fr/contact/ - Endpoint MCP : https://mcp.openlegi.fr/legifrance/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) : ```json { "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) : ```json { "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 : ```python 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 : ```python 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 ```python { "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 ```python { "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 ```python { "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) ## rechercher_dans_texte_legal 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: ```python { "name": "rechercher_dans_texte_legal", "arguments": { "search": "7", "text_id": "78-17", "champ": "NUM_ARTICLE" } } ``` Recherche generale: ```python { "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 :** - **[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)** : SDK officiel avec support complet du protocole - **[Claude Desktop](https://claude.ai/download)** : Intégration native dans l'interface Claude **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 ```bash 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é) ```python session.headers.update({ "Authorization": "Bearer VOTRE_TOKEN_ICI" }) ``` ### 2. Query Parameter ```python url = f"https://mcp.openlegi.fr/legifrance/mcp?token=VOTRE_TOKEN" ``` ### 3. Path Token ```python url = "https://mcp.openlegi.fr/legifrance/mcp/token/VOTRE_TOKEN" ``` --- ## Client Python OpenLegiClient ### Classe complète ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ` - 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](https://github.com/modelcontextprotocol/python-sdk)** - SDK officiel avec : - Support complet du protocole MCP - Gestion automatique des sessions - Streaming SSE natif - Types Python stricts 2. **[Claude Desktop](https://claude.ai/download)** - Pour une utilisation interactive : - Configuration via `claude_desktop_config.json` - Interface graphique native - Pas de code nécessaire 3. **[Claude Code CLI](https://github.com/anthropics/claude-code)** - Pour l'automatisation : - Intégration Git - Exécution de tâches complexes - Support natif des serveurs MCP --- ## Support - 📧 Contact : [support@openlegi.fr](mailto:support@openlegi.fr) - 📖 Documentation complète : [https://auth.openlegi.fr/documentation](https://auth.openlegi.fr/documentation) - 🐛 Issues : [GitHub Issues](https://github.com/legalchain/legal-mcp/issues) --- ## Licence Ce code est fourni sous licence MIT à titre d'exemple éducatif. ### Integration Python - Utilisation ## Exemples d'utilisation ### Lister les outils disponibles ```python # 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 ```python 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 ```python 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 ```python 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"]) ``` ### Rechercher dans un texte légal (LODA) ```python 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 ```python with OpenLegiClient(token="VOTRE_TOKEN_ICI") as client: result = client.call_tool("lister_codes_juridiques", {}) print(result["content"][0]["text"]) ``` ## Gestion des erreurs ```python 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 ```python 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 ```python 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 ```python 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) ```python 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 ```python 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 ```python # ❌ 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 ```python # 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 ```http POST /api/search HTTP/1.1 Content-Type: application/json Accept: application/json {"query": "test"} ``` **Réponse REST classique :** ```http HTTP/1.1 200 OK Content-Type: application/json {"results": [...], "count": 42} ``` ### MCP avec SSE ```http 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 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) ```python # 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 ```python # 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 : ```python # 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é) ```python # 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) ```python # 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) ```python 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 ?** ```python >>> 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) ```python 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 ```python # ❌ ERREUR 406 Not Acceptable headers = {"Accept": "application/json"} # ✅ CORRECT headers = {"Accept": "application/json, text/event-stream"} ``` **Message d'erreur :** ```json { "error": { "code": -32600, "message": "Not Acceptable: Client must accept both application/json and text/event-stream" } } ``` ### Erreur 2 : Utiliser response.json() directement ```python # ❌ JSONDecodeError data = response.json() # ✅ Parser le SSE d'abord data = parse_sse(response.text) ``` ### Erreur 3 : Oublier l'initialisation de session ```python # ❌ 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)** ```python # Le serveur peut notifier le client event: notification data: {"method": "tools/list_changed"} ``` ### 2. **Reconnexion automatique** ```javascript // 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** ```python # 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 ```bash 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 ```python 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](https://piste.gouv.fr/) 2. Créez une application pour obtenir vos credentials : - Client ID - Client Secret 3. Ajoutez vos credentials dans votre profil OpenLegi 4. 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 - [Documentation MCP](https://modelcontextprotocol.io) - [API PISTE Légifrance](https://piste.gouv.fr/) - **Inscrivez-vous ici** - [Documentation OpenLegi](https://mcp.openlegi.fr/documentation) ## 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](https://auth.openlegi.fr/users/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.