03-comment-je-travaille/tutos/ia/brancher-mcp-claude-desktop-cursor.md

Tuto — Brancher un serveur MCP sur Claude Desktop / Cursor

Public visé : développeur ayant un serveur MCP (cf. serveur MCP en Symfony) à connecter à un client agentique. Objectif : déclarer le serveur côté client et vérifier que ses outils sont utilisables en démo.

Calé sur le réel : intégration telaria/mcp-bundle v0.1.3 jouée bout-en-bout le 2026-05-30 (WSL2 + MariaDB via Docker Desktop), cycle initialize → tools/list → tools/call complet, cœur RAG (embeddings → sqlite-vec) traversé. Protocole MCP révision 2025-11-25. Commande de démarrage du transport stdio confirmée Lead dev : bin/console mcp:serve (le nom mcp:stdio n'existe pas).


Principe

Un client MCP (Claude Desktop, Cursor, etc.) lance le serveur en sous-processus et dialogue avec lui en stdio (JSON-RPC sur stdin/stdout). On déclare donc, côté client, la commande qui démarre le serveur, et on lui transmet le token via la variable d'environnement MCP_TOKEN.

Token : obtention et transmission

  • Le token est Ă©mis par php bin/console app:mcp:seed (tenant/projet + root des docs). Il est affichĂ© une seule fois au seed ; en base, seul son hash SHA-256 est stockĂ©. Conservez-le immĂ©diatement.
  • En stdio, le token se transmet uniquement par la variable d'environnement MCP_TOKEN : jamais dans une URL, jamais en argument CLI (laisserait le token dans ps / l'historique shell).

1. Claude Desktop

La configuration se fait dans le fichier claude_desktop_config.json (accessible via les réglages « Développeur » de l'application).

{
  "mcpServers": {
    "tlr-mcp": {
      "command": "php",
      "args": ["/chemin/vers/codexia/bin/console", "mcp:serve"],
      "env": {
        "MCP_TOKEN": "le-token-affiché-une-fois-par-app:mcp:seed"
      }
    }
  }
}
  • command + args : la commande console qui dĂ©marre votre serveur MCP en stdio.
  • env.MCP_TOKEN : le token opaque obtenu au seed (cf. ci-dessus).
  • Après modification, redĂ©marrer Claude Desktop : le serveur tlr-mcp apparaĂ®t, ses outils deviennent disponibles.

2. Cursor

Cursor lit une configuration MCP équivalente (réglages MCP / fichier dédié au projet) :

{
  "mcpServers": {
    "tlr-mcp": {
      "command": "php",
      "args": ["/chemin/vers/codexia/bin/console", "mcp:serve"],
      "env": {
        "MCP_TOKEN": "le-token-affiché-une-fois-par-app:mcp:seed"
      }
    }
  }
}

Le principe est identique : une commande qui lance le serveur en stdio, token en MCP_TOKEN.


3. Vérifier

  1. Redémarrer le client après configuration.
  2. Vérifier que le serveur est listé et connecté (pas d'erreur de démarrage). Le handshake attendu est initialize → notifications/initialized → tools/* ; un appel d'outil avant initialize renvoie une erreur JSON-RPC -32600 (Invalid Request).
  3. Demander à l'assistant une question qui doit déclencher un outil, par ex. : « Cherche dans la documentation ce qui concerne le RGPD » → l'assistant doit appeler search_docs et répondre avec des passages sourcés.

Repère de démo (observé en réel le 2026-05-30) : tools/list renvoie 3 outils (list_docs, read_doc, search_docs) ; search_docs traverse le cœur RAG (embeddings → sqlite-vec) avec des hits pertinents (score ~0.88).

Astuce démo : préparer 2-3 questions qui exercent list_docs, read_doc et search_docs, et capturer l'écran (l'accès public n'est pas requis pour la vitrine).


Annexe — capture JSON-RPC brute (le protocole sous le capot)

Rejouée sur le vrai serveur (tlr-mcp v0.1.3, transport stdio, mcp:serve --silent), protocole 2025-11-25, token MCP_TOKEN read-only. Convention stdio : 1 message JSON par ligne ; la notification notifications/initialized n'attend aucune réponse (pas d'id). Sous Windows→WSL, retirer les CRLF avant le pipe (sinon le parse JSON-RPC casse).

1. initialize

RequĂŞte :

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"capture-tuto","version":"1.0"}}}

Réponse :

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"tlr-mcp","version":"1.0.0"}}}

2. notifications/initialized (notification — aucune réponse)

{"jsonrpc":"2.0","method":"notifications/initialized"}

3. tools/list

RequĂŞte :

{"jsonrpc":"2.0","id":2,"method":"tools/list"}

Réponse (3 outils ; schémas tronqués pour lisibilité) :

{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"list_docs","description":"Liste les documents Markdown indexés du projet…"},{"name":"read_doc","description":"Retourne le contenu Markdown brut d'un document autorisé…"},{"name":"search_docs","description":"Recherche sémantique dans la documentation via le cœur RAG…"}]}}

4. tools/call — list_docs (limit: 3)

RequĂŞte :

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_docs","arguments":{"limit":3}}}

Réponse (le résultat métier est du JSON encodé dans content[].text) :

{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\"status\":\"ok\",\"documents\":[{\"path\":\"DOCUMENTATION.md\",\"title\":\"Documentation Codexia — conventions\"},{\"path\":\"specs.md\",\"title\":\"Spécifications web pour une stack LAMP + Symfony\"},{\"path\":\"inputs/sources/appel-d-offre.md\",\"title\":\"Appel d'offre\"}],\"truncated\":true}"}]}}

5. tools/call — read_doc (path: DOCUMENTATION.md)

RequĂŞte :

{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"read_doc","arguments":{"path":"DOCUMENTATION.md"}}}

Réponse (content Markdown intégral abrégé ici) :

{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"{\"status\":\"ok\",\"path\":\"DOCUMENTATION.md\",\"title\":\"Documentation Codexia — conventions\",\"content\":\"# Documentation Codexia — conventions … [tronqué]\",\"metadata\":{\"mtime\":\"2026-05-29T22:16:07+00:00\",\"content_hash\":\"80968456…\",\"size_bytes\":2855}}"}]}}

6. tools/call — search_docs (le cœur RAG en action)

RequĂŞte :

{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"search_docs","arguments":{"query":"Qu'est-ce que la veille agentique ?","k":3}}}

Réponse (excerpts abrégés, scores réels ~0.85) :

{"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"{\"status\":\"ok\",\"hits\":[{\"path\":\"specs/ia-veille.md\",\"section\":\"8. Production documentaire d'accompagnement (doctrine)\",\"score\":0.8578,\"excerpt\":\"…\"},{\"path\":\"tutos/ia/veille-automatisee-symfony.md\",\"section\":\"Pour aller plus loin\",\"score\":0.8520,\"excerpt\":\"…\"},{\"path\":\"specs/ia-veille.md\",\"section\":\"Lot 3 — Veille agentique : spécification\",\"score\":0.8482,\"excerpt\":\"…\"}]}}

Dépannage courant

  • Le serveur n'apparaĂ®t pas : chemin de command/args erronĂ©, ou le serveur Ă©crit autre chose que du JSON-RPC sur stdout (rappel : logs sur stderr uniquement).
  • Erreur -32600 sur un appel d'outil : appel Ă©mis avant le handshake initialize → notifications/initialized. Laisser le client finir l'initialisation.
  • Auth refusĂ©e : MCP_TOKEN absent, expirĂ© ou rĂ©voquĂ© (le hash en base ne correspond plus). Re-seed et reporter le nouveau token.
  • « tool not found » : tools/list ne renvoie pas l'outil → vĂ©rifier l'enregistrement et le name, ainsi que les scopes (tool:<name>, project:<slug>).
  • RĂ©ponses sans sources : l'outil search_docs n'est pas rĂ©ellement branchĂ© sur le RetrievalService du cĹ“ur.

Pour aller plus loin

Assistant documentaire

Posez une question sur la documentation. Les réponses citent leurs sources — un clic ouvre le document à gauche.

Loading…
Loading the web debug toolbar…
Attempt #