MCP : erreurs structurées, pannes de source visibles, secrets hors conversation, pagination annoncée#4
Open
hermias1 wants to merge 2 commits into
Open
Conversation
Chaque tool porte désormais les annotations de sécurité MCP (ToolAnnotations) : un title lisible et readOnlyHint=True — le serveur est en consultation pure. Les clients MCP (Claude.ai, Claude Desktop...) peuvent ainsi exécuter les tools sans confirmation par appel, et c'est le critère n°1 de la review du Connectors Directory d'Anthropic. tests/test_annotations.py verrouille la présence des annotations sur tout nouveau tool (branché dans run_all.sh).
…s, secrets hors conversation, pagination annoncée Aligne le serveur sur les critères du Connectors Directory d'Anthropic et l'article 'Writing effective tools for agents' : - Contrat d'erreur uniforme _tool_error (error + error_category validation/not_found/upstream/auth + retryable) sur les 30 tools ; plus aucun tool ne retourne None ni ne laisse remonter d'exception brute (raise_for_status PISTE catchés, erreurs 401/429 actionnables). - search_all / search_decisions_citing : une source en panne n'apparaît plus comme '0 résultat' — champ source_errors + warning explicites. - Sécurité : suppression des paramètres client_id/client_secret des tools PISTE (les secrets OAuth ne transitent plus par la conversation ; session_token ou env vars serveur uniquement). - FTS5 : sanitization partagée dila/european (tokens à tiret quotés — '79-105' plantait —, guillemets/parenthèses déséquilibrés, opérateurs orphelins) + filet OperationalError → note actionnable au lieu d'une exception opaque. Clamp limit [1,50] et offset >= 0 côté sources. - Pagination annoncée : truncated + next_offset sur les tools de recherche ; offset ajouté à search_cedh/search_cjue/ search_judiciaire_libre ; plafond par défaut sur _all_ta (60). - Token efficiency : get_decision_text ne renvoie plus le texte en double (text_segments supprimé, full_text seul). - Event loop : appels SQLite synchrones enveloppés dans asyncio.to_thread (le fan-out de search_all est réellement parallèle). - Cohérence des chiffres entre instructions / about_justicelibre / docstrings / README (30 tools, ~550k JADE, ~1,17M judiciaire, 26 codes supportés, ~8k CNIL). tests/test_error_contract.py verrouille le contrat (offline, y compris le sanitizer contre un vrai index FTS5 en mémoire).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Audit du serveur contre les critères de review du Connectors Directory et l'article Writing effective tools for agents. Plusieurs anti-patterns identifiés, dont deux sérieux :
search_all, une exception sur une source produisait un hit-fantôme sauté à la fusion — le modèle concluait « aucune jurisprudence n'existe » quand le warehouse avait un blip. Pour un outil juridique c'est le pire cas de figure.search_judiciaire/get_decision_judiciaireacceptaientclient_id/client_secreten paramètres — le Client Secret PISTE transitait par le contexte LLM (historique, logs clients). Le mécanismesession_tokenexistait précisément pour éviter ça.Changements
_tool_error→{error, error_category: validation|not_found|upstream|auth, retryable}partout. Plus aucun tool ne retourneNone(chaque introuvable suggère le tool alternatif), plus d'exception brute (lesraise_for_status()PISTE sont catchés : 401 → régénérer le token, 429 → retryable).search_alletsearch_decisions_citingexposentsource_errors+warning;per_source_countsne contient que les sources ayant répondu.client_id/client_secretdes tools PISTE. Restent :session_token(site) ou variables d'envPISTE_CLIENT_ID/PISTE_CLIENT_SECRET(auto-hébergement).dila/european(les tokens à tiret —79-105,garde-à-vue— plantaient enOperationalErrorbrut ; guillemets/parenthèses déséquilibrés ; opérateurs orphelins) + filetOperationalError→ note actionnable.limitclampé [1,50] etoffset ≥ 0côté sources.truncated+next_offsetsur les tools de recherche (pattern recommandé par l'article) ;offsetajouté àsearch_cedh,search_cjue,search_judiciaire_libre;search_admin_recent_all_taplafonné à 60 par défaut (0 = illimité reste possible).get_decision_textrenvoyaittext_segmentsetfull_text(le même arrêt deux fois) —full_textseul désormais.asyncio.to_thread— le fan-out desearch_allest réellement parallèle, et un client lent ne bloque plus les autres en Streamable HTTP.about_justicelibre/docstrings disaient tour à tour 24 tools, ~4M JADE, ~620k judiciaire, 22/25/72 codes. Alignés sur les valeurs réelles (30 tools, ~550k, ~1,17M, 26 codes — compte exact deSUPPORTED_CODES—, ~8k CNIL).Vérification
tests/test_error_contract.py(nouveau, offline) : contrat d'erreur, validation sans DB, absence de params credentials dans les schémas, sanitizer contre un vrai index FTS5 en mémoire (le cas79-105est verrouillé), pagination exposée.test_annotations(3),test_error_contract(6),test_source_url(2),test_citations(2).import server+list_tools()→ 30 tools.search_api.py/token_server.py/ssr.pyinchangés dans leur comportement (le champerrorreste une string, aucune clé renommée,dila.searchlève toujoursValueErrorpour ses appelants internes).Hors périmètre (volontairement)
http://sur IP publique par défaut) et la protection DNS-rebinding est désactivée en prod : décisions d'infra qui vous appartiennent → issue séparée à venir.token_server.py(API REST publique) acceptelimitjusqu'à 100 alors que les sources locales clampent désormais à 50 — sans gravité (résultats simplement plafonnés), à harmoniser si vous le souhaitez.