# Serveur MCP Deux façons de brancher un agent compatible MCP sur le téléphone : - **En ligne, sans rien installer** (recommandé) : `https://mcp.dialyne.com/mcp` (Streamable HTTP). Deux façons de s'authentifier : - **OAuth 2.1 (Claude, ChatGPT, Cursor…)** : colle seulement l'URL `…/mcp` ; le client découvre l'autorisation tout seul, ouvre une page où tu te connectes (e-mail + mot de passe du compte, ou une clé API) et clique « Autoriser ». - **Clé API en en-tête** `Authorization: Bearer tel_…` (agents sans navigateur : Hermes, OpenClaw, n8n, scripts). La découverte (`initialize`, `tools/list`) répond sans authentification ; l'appel d'un outil sans jeton renvoie `401` + `WWW-Authenticate: Bearer resource_metadata="…"`, ce qui déclenche la connexion OAuth. Ne mets jamais la clé dans l'URL : l'ancienne forme `…/mcp/tel_…` est dépréciée et sera coupée. - **En local** (`code/mcp_server/server.py`, transport stdio), mêmes outils. ## En ligne Guides pas à pas : [Claude](guide-claude.md), [ChatGPT](guide-chatgpt.md), [Cursor](guide-cursor.md), [n8n](guide-n8n.md), [Hermes](guide-hermes.md). - **Claude (claude.ai / Desktop) › Réglages › Connecteurs › Ajouter un connecteur personnalisé** : URL `…/mcp`, puis « Se connecter » et « Autoriser ». - **ChatGPT › Réglages › Connecteurs (mode développeur) › Créer** : URL `…/mcp`, authentification « OAuth » (enregistrement dynamique, rien à saisir). - **Claude Code** : `claude mcp add --transport http dialyne https://mcp.dialyne.com/mcp` (OAuth au premier appel via `/mcp`), ou avec `--header "Authorization: Bearer tel_…"`. - **Cursor** (`.cursor/mcp.json`) : `{"mcpServers": {"dialyne": {"url": "https://mcp.dialyne.com/mcp", "headers": {"Authorization": "Bearer tel_…"}}}}`. - **API OpenAI (Responses)** : outil `{"type": "mcp", "server_label": "dialyne", "server_url": "https://mcp.dialyne.com/mcp", "headers": {"Authorization": "Bearer tel_…"}, "require_approval": "never"}`. Le serveur s'appelle `dialyne`. Les outils sont en anglais (les modèles les choisissent mieux) ; ils renvoient un résultat écrit, jamais d'audio, et des erreurs explicites (`isError: true`, code stable dans `structuredContent.error` ; erreurs de limites et d'offre : [Offres et limites](offres.md#codes-d-erreur)). | Outil | Rôle | |---|---| | `place_phone_call(phone_number, task, language?, scheduled_at?, voice?, idempotency_key?, ai_disclosure?, callee_informed_confirmation?, outside_hours?)` | Lance (ou planifie) un appel en France ; renvoie `call_id` tout de suite (l'appel n'est pas fini). `ai_disclosure="callee_already_informed"` (+ confirmation de l'utilisateur) retire la mention IA de l'accroche pour une personne déjà prévenue ; `outside_hours` est obsolète (accepté, ignoré) : aucune plage horaire imposée | | `get_call(call_id, wait_seconds=25, after_event_id=0, response_format="concise")` | Statut et résultat ; attend jusqu'à 50 s un événement important (décroché, attente, question, transfert, fin) | | `answer_call_question(call_id, answer, question_id?)` | Répond à la question de l'agent pendant l'appel (45 s ; échéance dans `pending_question.expires_at` et `seconds_left`) | | `hold_call_question(call_id, question_id?)` | Demande 60 s de plus, une seule fois par question : l'agent vocal demande à l'interlocuteur s'il veut bien patienter, et le délai n'est prolongé que s'il accepte (`can_hold` dans `pending_question`). Voir [Questions en direct](questions.md) | | `transfer_call_to_user(call_id, user_phone_number)` | Passe l'appel en cours à l'utilisateur sans coupure | | `schedule_callback(call_id, callback_at)` | Programme un rappel d'un appel terminé (14 jours max, 3 rappels) | | `cancel_call(call_id)` | Annule un appel en cours ou planifié | | `list_calls(query="", outcome?, limit=10)` | Appels passés et planifiés (recherche dans la mission, le résumé, la transcription, le numéro) | | `get_account_balance()` | Minutes de conversation restantes, offre (`plan`), prix à la minute (`price_per_minute_eur`) et limites (`limits`) | | `place_batch_calls(phone_numbers, task, language?, voice?, scheduled_at?, name?)` | Pro et Entreprise : une même mission vers une liste de numéros (100 au plus). Voir [Appels en lot](lots.md) | | `get_batch_calls(batch_id)` | Avancement et résultats d'un lot | | `configure_agent_identity(first_name?, tone?, opening_line?, who_is_calling_answer?, introduction?, ai_mention_in_opening?, reset_to_recommended?, accept_responsibility?, warning_version?)` | Lit ou modifie l'identité de l'agent (prénom, ton, ouverture, « de la part de qui », présentation, mention IA). Un écart aux réglages recommandés renvoie d'abord l'avertissement (`responsibility_acceptance_required`) : l'agent client le montre à l'utilisateur et ne l'accepte qu'avec son accord explicite (journalisé). L'agent vocal ne nie jamais être une IA | Statuts : `scheduled`, `in_progress`, `retry_scheduled`, `completed`, `cancelled`, `failed`. Résultats : `success`, `failed`, `voicemail`, `no_answer`, `busy`, `callback_requested`. ## OAuth (détails techniques) Conforme à la spécification MCP « Authorization » (2025-06-18) : - `401` sur `tools/call` sans jeton → `WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp"`. - Ressource protégée (RFC 9728) : `GET /.well-known/oauth-protected-resource/mcp` ; serveur d'autorisation (RFC 8414) : `GET /.well-known/oauth-authorization-server`. - Enregistrement dynamique (RFC 7591) : `POST /oauth/register` (`redirect_uris` en https, `http://localhost`/`127.0.0.1` ou schéma d'application ; client public `none` par défaut, ou `client_secret_post` / `client_secret_basic`). - `GET /oauth/authorize` (code + **PKCE S256 obligatoire**, `state`, `resource` = `…/mcp`), `POST /oauth/token` (`authorization_code`, `refresh_token`), `POST /oauth/revoke`. - Jetons liés au compte : accès `oat_…` valable 1 h, rafraîchissement `ort_…` 30 jours avec rotation (un ancien jeton réutilisé révoque toute l'autorisation). Le jeton d'accès vaut une clé API pour l'API REST du même compte. - Applications connectées : `GET /v1/applications`, révocation `DELETE /v1/applications/{client_id}`. ## Installation (stdio) ```bash cd code/mcp_server python -m venv .venv && . .venv/bin/activate pip install -r requirements.txt ``` ## Claude Desktop / Claude Code `claude_desktop_config.json` (ou `.mcp.json`) : ```json { "mcpServers": { "dialyne": { "command": "/chemin/vers/code/mcp_server/.venv/bin/python", "args": ["/chemin/vers/code/mcp_server/server.py"], "env": {"DIALYNE_API_KEY": "tel_xxx", "DIALYNE_API_URL": "https://api.dialyne.com"} } } } ``` (`TELEPHONE_API_KEY` / `TELEPHONE_API_URL` restent lues si les nouvelles variables sont absentes.) ## Cursor `~/.cursor/mcp.json` : même bloc `mcpServers` que ci-dessus. ## Exemple de dialogue > « Appelle le 01 42 00 00 00 et réserve une table pour 4 vendredi 20 h au nom de Moha. » 1. L'agent appelle `place_phone_call("+33142000000", "Réserve une table pour 4 vendredi 20 h au nom de Moha")`. 2. Il suit avec `get_call(call_id, wait_seconds=25)` ; si `pending_question` est rempli (« Terrasse ou salle ? »), il répond avec `answer_call_question`. S'il doit te demander, il appelle d'abord `hold_call_question` (l'interlocuteur patiente une minute s'il l'accepte), puis te pose la question et répond. 3. À `done = true`, il te donne `outcome` et `summary`. ## Tester sans client MCP ```bash npx @modelcontextprotocol/inspector python server.py ```