> Pour un agent : la carte de la documentation d'Escouade Mail est sur https://escouade.io/mail/docs/llms.txt ; chaque page existe en Markdown, à la même adresse suffixée `.md`.

# Serveur MCP

> Le MCP est la seule interface d'Escouade Mail. L'adresse des agents est `https://api.subagent.fr/mcp`, en Streamable HTTP. Sans clé, le serveur n'y sert que l'inscription. Avec une clé, il sert les outils que ses permissions ouvrent. Les assistants en ligne se branchent au même serveur par une seconde adresse, `https://mail.escouade.io/mcp`.

## En bref

| Propriété | Valeur |
|---|---|
| Adresse des agents | `https://api.subagent.fr/mcp` |
| Adresse des assistants en ligne | `https://mail.escouade.io/mcp` : connexion OAuth, sans clé à copier. Voir [Connecteur](https://escouade.io/mail/docs/connecteur.md). |
| Transport | Streamable HTTP : des requêtes JSON-RPC 2.0 en `POST`, des réponses en `application/json` |
| Authentification | En-tête `Authorization: Bearer sak_…`. Sans en-tête, à l'adresse des agents : l'inscription seulement. |
| Outils | Ceux de la [référence](https://escouade.io/mail/docs/reference/index.md), filtrés par les permissions de la clé |
| Vues | Un établi par conversation, ouvert par `open_workspace` puis piloté par `navigate_workspace` : voir [Vues](https://escouade.io/mail/docs/vues.md) |
| Règles données à la connexion | Le champ `instructions` de la réponse à `initialize` : moins de 2 000 caractères |

## Brancher un client

Le serveur se déclare par son adresse, et la clé par un en-tête.

Claude Code :

```sh
claude mcp add \
  --transport http subagent \
  https://api.subagent.fr/mcp \
  --header "Authorization: Bearer $SUBAGENT_KEY"
```

Cursor (`.cursor/mcp.json`) :

```json
{
  "mcpServers": {
    "subagent": {
      "url": "https://api.subagent.fr/mcp",
      "headers": {
        "Authorization": "Bearer ${env:SUBAGENT_KEY}"
      }
    }
  }
}
```

Autre client (`.mcp.json`) :

```json
{
  "mcpServers": {
    "subagent": {
      "type": "http",
      "url": "https://api.subagent.fr/mcp",
      "headers": {
        "Authorization": "Bearer ${SUBAGENT_KEY}"
      }
    }
  }
}
```

Pas encore de clé : déclarez l'adresse sans en-tête, laissez l'agent appeler `sign_up`, puis ajoutez l'en-tête. Voir [Démarrer](https://escouade.io/mail/docs/demarrer.md).

Un assistant en ligne (Claude, ChatGPT) ne déclare pas d'en-tête : il se branche à `https://mail.escouade.io/mcp`, où l'humain se connecte avec son compte Escouade. Voir [Connecteur](https://escouade.io/mail/docs/connecteur.md). Client par client : [Brancher mon agent](https://escouade.io/mail/docs/brancher.md).

## Sans clé

À l'adresse `https://api.subagent.fr/mcp`, sans en-tête d'autorisation, le serveur répond quand même, pour l'inscription. À l'adresse `https://mail.escouade.io/mcp`, il répond 401 : l'assistant ouvre alors la page de connexion.

- Il sert `initialize`, `ping`, `tools/list` et `tools/call`.
- `tools/list` rend un seul outil : `sign_up`.
- Il n'ouvre pas de session et ne sert pas les vues.
- L'appel d'un autre outil est refusé avec le code `unauthorized`. Toute autre méthode reçoit le statut HTTP 401.

Le serveur lit l'en-tête à chaque requête. Après `sign_up`, la connexion ouverte sans clé ne montre toujours que `sign_up` : il faut présenter la clé. Voir [Clés et permissions](https://escouade.io/mail/docs/cles.md#apres-sign-up).

## Avec une clé

- `tools/list` rend les outils que les permissions de la clé ouvrent. `sign_up` n'y figure plus.
- Une fois l'espace vérifié, `verify` et `attach_human` quittent la liste.
- Une clé présentée mais inconnue ou révoquée reçoit le statut HTTP 401.

## Un seul établi

`open_workspace` est l'entrée visuelle. Après son ouverture, le modèle reprend l'`instance_id` du contexte de cette conversation et demande une page avec `navigate_workspace` : audiences, contacts, campagnes ou performances. Les outils métier continuent à rendre leurs données sans demander une nouvelle carte.

La vue reçoit les demandes avec `workspace_events`, réservé à l'application par `_meta.ui.visibility: ["app"]`. Elle attend au plus 18 secondes par appel et garde une seule attente en cours. Le modèle ne doit pas appeler cet outil pour entretenir la liaison. `navigate_workspace` reste visible au modèle et à l'application.

Un reçu `pending` signifie que la navigation est demandée. Une saisie en cours peut retarder ou empêcher son affichage : la vue laisse l'humain choisir. Voir [Navigation de l'établi](https://escouade.io/mail/docs/vues.md#navigation).

## Le résultat d'un outil

Un appel réussi rend trois choses.

Résultat de tools/call :

```json
{
  "content": [{ "type": "text", "text": "Message msg_… en file dans le fil thr_… : …" }],
  "structuredContent": { "message": {}, "thread": {}, "inbox": {}, "replayed": false, "estimated_send_at": "2026-10-13T14:31:00+02:00" },
  "isError": false
}
```

- **`content`** : un texte lisible en français. Il dit qui, quoi, quand, et le quota restant.
- **`structuredContent`** : l'objet de l'outil, dont le schéma est publié dans `https://api.subagent.fr/openapi.json`. C'est lui qu'un programme lit.
- **`isError`** : `false`.

Le résultat porte aussi `_meta["subagent/requestId"]`, un identifiant de diagnostic. Les métadonnées de contexte de l'espace peuvent accompagner l'objet métier ; elles servent au cloisonnement et à l'actualisation des données de la vue.

Le texte lisible coupe le corps d'un message à 2 000 caractères. Le corps entier est dans `structuredContent`.

## Un refus

Un refus du service n'est pas une erreur de protocole. C'est un résultat d'outil marqué `isError`, avec un texte : le message, le code entre parenthèses, puis la piste après une flèche.

```text
approve_message demande la permission approve, que cette clé n’a pas. (code forbidden)
→ La validation est un geste humain : votre humain l’accomplit depuis son assistant (Claude, ChatGPT…), branché à l’adresse https://mail.escouade.io/mcp où il se connecte avec son e-mail.
```

Par MCP, le même refus figure aussi dans `structuredContent.error` : `code`, `message` et, lorsqu'elle existe, la piste `hint`. Un client doit lire ces champs ; le texte reste disponible pour l'humain. En HTTP, l'objet est `{"error": {"code", "message", "hint"}}`. Voir [Erreurs](https://escouade.io/mail/docs/erreurs.md).

## Ce que chaque outil annonce

Dans `tools/list`, chaque outil porte :

- `name`, `title` et `description`, en français ;
- `inputSchema` : ses paramètres. Un paramètre inconnu est refusé. `tools/list` ne porte pas de schéma de sortie (`outputSchema`) ;
- `annotations` : `readOnlyHint` (l'outil ne fait que lire), `destructiveHint` (son effet ne se défait pas), `idempotentHint` ;
- `_meta.ui.resourceUri` et `_meta["openai/outputTemplate"]` pour l'entrée visuelle `open_workspace` ;
- `_meta.ui.visibility` : `model` et `app` en général, `app` uniquement pour `workspace_events` ;
- `_meta["openai/widgetAccessible"]: true` pour les appels de la vue, y compris les outils sans nouvelle carte.

Le schéma de l'objet structuré de chaque outil se lit dans `https://api.subagent.fr/openapi.json`.

L'établi utilise la ressource `ui://subagent/workspace-v12.html`. Les adresses des anciennes vues restent lisibles pour les conversations déjà ouvertes. Après une mise à jour du connecteur, l'hôte doit relire les descripteurs d'outils et leur adresse de ressource ; un ancien descripteur en cache peut encore demander les cartes précédentes.

## Protocole

| Sujet | Ce que fait le serveur |
|---|---|
| Versions | `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26`. Une autre version dans l'en-tête `Mcp-Protocol-Version` est refusée. |
| Sessions | `initialize` ouvre une session et rend l'en-tête `Mcp-Session-Id`. Elle vaut 24 heures après la dernière requête. La session est facultative : une requête sans cet en-tête est servie sans état. Une session expirée est effacée après 30 jours. |
| Fin de session | `DELETE` sur `https://api.subagent.fr/mcp`, avec l'en-tête `Mcp-Session-Id`. |
| `GET` | Refusé avec le statut 405 : le serveur n'ouvre pas de flux. |
| Lots | Refusés : un message JSON-RPC par requête. |
| Taille | Une requête fait 1 Mio au plus. |
| Origine | Une requête venue de la page web d'un autre site (en-tête `Origin`) est refusée. |
| Débit | 60 requêtes par minute sans clé, 600 avec une clé, par adresse du client. Voir [Limites et quotas](https://escouade.io/mail/docs/limites.md#debit). |

## Sans MCP

Chaque outil existe aussi en `POST https://api.subagent.fr/v1/{opération}`, avec les mêmes arguments et le même objet en réponse. Voir [API HTTP](https://escouade.io/mail/docs/api-http.md).
