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-bundlev0.1.3 jouée bout-en-bout le 2026-05-30 (WSL2 + MariaDB via Docker Desktop), cycleinitialize → tools/list → tools/callcomplet, cœur RAG (embeddings → sqlite-vec) traversé. Protocole MCP révision 2025-11-25. Commande de démarrage du transportstdioconfirmée Lead dev :bin/console mcp:serve(le nommcp:stdion'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'environnementMCP_TOKEN: jamais dans une URL, jamais en argument CLI (laisserait le token dansps/ 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 enstdio.env.MCP_TOKEN: le token opaque obtenu au seed (cf. ci-dessus).- Après modification, redémarrer Claude Desktop : le serveur
tlr-mcpapparaî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
- Redémarrer le client après configuration.
- 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 avantinitializerenvoie une erreur JSON-RPC-32600(Invalid Request). - 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_docset répondre avec des passages sourcés.
Repère de démo (observé en réel le 2026-05-30) :
tools/listrenvoie 3 outils (list_docs,read_doc,search_docs) ;search_docstraverse 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_docetsearch_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-mcpv0.1.3, transportstdio,mcp:serve --silent), protocole 2025-11-25, tokenMCP_TOKENread-only. Conventionstdio: 1 message JSON par ligne ; la notificationnotifications/initializedn'attend aucune réponse (pas d'id). Sous Windows→WSL, retirer lesCRLFavant 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/argserroné, ou le serveur écrit autre chose que du JSON-RPC surstdout(rappel : logs surstderruniquement). - Erreur
-32600sur un appel d'outil : appel émis avant le handshakeinitialize→notifications/initialized. Laisser le client finir l'initialisation. - Auth refusée :
MCP_TOKENabsent, expiré ou révoqué (le hash en base ne correspond plus). Re-seed et reporter le nouveau token. - « tool not found » :
tools/listne renvoie pas l'outil → vérifier l'enregistrement et lename, ainsi que les scopes (tool:<name>,project:<slug>). - Réponses sans sources : l'outil
search_docsn'est pas réellement branché sur leRetrievalServicedu cœur.
Pour aller plus loin
- Construire le serveur : serveur MCP en Symfony
- Branchement Claude Code (CLI) : brancher-mcp-claude-code.md
- Gouvernance tokens/scopes par instance : mcp-gouvernance-instances.md
- Spec MCP :
specs/ia-mcp.md - Concepts : fiche 4.6 — MCP