Skip to content

MCP : erreurs structurées, pannes de source visibles, secrets hors conversation, pagination annoncée#4

Open
hermias1 wants to merge 2 commits into
Dahliyaal:mainfrom
hermias1:fix/mcp-antipatterns
Open

MCP : erreurs structurées, pannes de source visibles, secrets hors conversation, pagination annoncée#4
hermias1 wants to merge 2 commits into
Dahliyaal:mainfrom
hermias1:fix/mcp-antipatterns

Conversation

@hermias1

Copy link
Copy Markdown

⚠️ Basée sur #2 (annotations) — merger #2 d'abord, cette PR se rebasera proprement. Le diff affiché inclut #2 tant qu'elle n'est pas mergée.

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 :

  1. Une source en panne = "0 résultat" : dans 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.
  2. Secrets OAuth dans la conversation : search_judiciaire/get_decision_judiciaire acceptaient client_id/client_secret en paramètres — le Client Secret PISTE transitait par le contexte LLM (historique, logs clients). Le mécanisme session_token existait précisément pour éviter ça.

Changements

  • Contrat d'erreur uniforme : helper _tool_error{error, error_category: validation|not_found|upstream|auth, retryable} partout. Plus aucun tool ne retourne None (chaque introuvable suggère le tool alternatif), plus d'exception brute (les raise_for_status() PISTE sont catchés : 401 → régénérer le token, 429 → retryable).
  • Pannes visibles : search_all et search_decisions_citing exposent source_errors + warning ; per_source_counts ne contient que les sources ayant répondu.
  • Suppression de client_id/client_secret des tools PISTE. Restent : session_token (site) ou variables d'env PISTE_CLIENT_ID/PISTE_CLIENT_SECRET (auto-hébergement). ⚠️ breaking pour un client qui passait les credentials en clair — c'est voulu.
  • FTS5 durci : sanitization partagée dila/european (les tokens à tiret — 79-105, garde-à-vue — plantaient en OperationalError brut ; guillemets/parenthèses déséquilibrés ; opérateurs orphelins) + filet OperationalError → note actionnable. limit clampé [1,50] et offset ≥ 0 côté sources.
  • Troncature annoncée : truncated + next_offset sur les tools de recherche (pattern recommandé par l'article) ; offset ajouté à search_cedh, search_cjue, search_judiciaire_libre ; search_admin_recent_all_ta plafonné à 60 par défaut (0 = illimité reste possible).
  • Tokens ÷2 sur le tool le plus lourd : get_decision_text renvoyait text_segments et full_text (le même arrêt deux fois) — full_text seul désormais.
  • Event loop : les appels SQLite synchrones (dila/european) passent par asyncio.to_thread — le fan-out de search_all est réellement parallèle, et un client lent ne bloque plus les autres en Streamable HTTP.
  • Cohérence des chiffres : instructions/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 de SUPPORTED_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 cas 79-105 est verrouillé), pagination exposée.
  • Suite complète au vert : test_annotations (3), test_error_contract (6), test_source_url (2), test_citations (2). import server + list_tools() → 30 tools.
  • Rétrocompatibilité vérifiée : search_api.py/token_server.py/ssr.py inchangés dans leur comportement (le champ error reste une string, aucune clé renommée, dila.search lève toujours ValueError pour ses appelants internes).

Hors périmètre (volontairement)

  • La clé warehouse transite en clair (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) accepte limit jusqu'à 100 alors que les sources locales clampent désormais à 50 — sans gravité (résultats simplement plafonnés), à harmoniser si vous le souhaitez.

hermias1 added 2 commits July 10, 2026 03:05
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant