ecole-directe-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ecole-directe-mcpQuels sont mes devoirs pour demain ?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ecoledirecte-mcp
Serveur MCP personnel pour École Directe : notes, devoirs, emploi du temps, vie scolaire, vie de classe, fil d'actualité, téléchargement de documents.
Installation locale (stdio)
npm install
npm run build
node dist/cli/index.js loginlogin demande l'identifiant et le mot de passe École Directe, puis, à la
première connexion depuis cette machine, une question de sécurité (QCM) —
c'est obligatoire côté École Directe, il n'y a pas de contournement.
La session est enregistrée dans ~/.config/ecoledirecte-mcp/session.json
(permissions 600) et rafraîchie automatiquement ensuite.
Related MCP server: iserv-mcp
Utilisation avec Claude Code
claude mcp add ecoledirecte -- node /chemin/absolu/vers/dist/cli/index.js serveLe serveur démarre même sans session valide : get_auth_status sert
justement à diagnostiquer ce cas, et les autres outils renvoient une erreur
explicite au lieu de faire tomber le serveur.
Outils exposés
Outil | Rôle |
| État de la session (présence, date du dernier rafraîchissement) |
| Notes de l'année scolaire |
| Devoirs entre deux dates |
| Marquer un devoir fait / non fait (écriture) |
| Emploi du temps entre deux dates |
| Vie scolaire (absences, retards, sanctions) |
| Vie de la classe et commentaires |
| Fil d'actualité personnel |
| Liste les messages d'un dossier (en-têtes seulement) |
| Contenu d'un message, HTML retiré, avec ses pièces jointes |
| Télécharge un document dans |
La messagerie est en deux outils parce que l'API l'impose : la liste
renvoie content: "" pour chaque message, les corps n'existent que sur
l'endpoint par message. Les pièces jointes se récupèrent avec
download_document en passant fileType = PIECE_JOINTE.
Variables d'environnement
SESSION_PATH— chemin du fichier de session (défaut~/.config/ecoledirecte-mcp/session.json)DEVICE_ID_PATH— chemin de l'identifiant d'appareil (défaut~/.config/ecoledirecte-mcp/device-id)DOWNLOAD_DIR— dossier de téléchargement (défaut~/.local/share/ecoledirecte-mcp/downloads)READ_ONLY—truepour désactivermark_homework_done(défautfalseen local)SESSION_MAX_AGE_MS— âge au-delà duquel la session est rafraîchie préventivement (défaut 15 min)
Transport HTTP uniquement :
MCP_AUTH_TOKEN— obligatoire, le serveur refuse de démarrer sans (openssl rand -hex 32)MCP_HTTP_HOST— adresse d'écoute (défaut127.0.0.1, jamais0.0.0.0)MCP_HTTP_PORT— port (défaut8787)MCP_ALLOWED_HOSTS— en-têtesHostacceptés, séparés par des virgules (défaut : l'adresse d'écoute)
Le deviceUUID généré au premier login est stocké séparément dans
~/.config/ecoledirecte-mcp/device-id. Ne pas le supprimer entre deux
logins, sous peine de redéclencher le QCM à chaque fois.
Authentification : ce qu'il faut savoir
École Directe délivre deux secrets distincts, tous deux dans
session.json :
token— jeton de session court, envoyé en headerX-Tokenà chaque appel de données ; il tourne à chaque login ou re-login ;accessToken— credential long, lié à l'appareil, seule chose capable de régénérer untokensans le mot de passe.
Les confondre fait échouer tous les appels avec 520 "Token invalide !".
C'est pour cette raison que l'authentification (login, QCM, re-login) est
implémentée directement dans src/client/edAuth.ts plutôt que déléguée à
@blockshub/blocksdirecte, dont le module d'auth :
ne lit le jeton que dans le corps de la réponse, alors qu'École Directe le renvoie aussi (parfois uniquement) dans le header
X-Token;ne reconnaît que les codes 250 et 505 au re-login — le
526« Votre session est invalide ou expirée » tombe dans son chemin de succès et produit une session vide qui ressemble à une réussite ;écrit sur stdout, ce qui corrompt le flux JSON-RPC du transport stdio.
Les modules de données de la librairie restent utilisés, avec un correctif
pour une récursion infinie dans leur vérification de module disponible
(patchBrokenModuleAvailabilityCheck). La messagerie, absente de la
librairie, est en HTTP direct (src/client/messaging.ts), ainsi que le
téléchargement (src/client/download.ts) : downloader.getStream() jette
les en-têtes de réponse, donc le vrai nom de fichier — porté par
Content-Disposition — était perdu et chaque document atterrissait sur le
disque nommé d'après son identifiant numérique, sans extension.
À noter : un téléchargement en échec répond quand même HTTP 200. École
Directe met son propre code dans l'en-tête X-Code (403 pour un
identifiant inconnu, avec une page d'erreur HTML en guise de contenu), ce
que le code vérifie avant d'écrire quoi que ce soit sur le disque.
Limitations connues (V1)
Expiration de session en cours d'utilisation. La session est rafraîchie
préventivement au-delà de SESSION_MAX_AGE_MS, et un appel de données qui
échoue de façon récupérable déclenche un rafraîchissement puis une seule
nouvelle tentative — jamais de boucle. Mais @blockshub/blocksdirecte ne
remonte pas le code d'erreur d'École Directe sur les appels de données :
seule une réponse vide là où la librairie garantit un objet permet de
déduire l'expiration (assertPresent). Pour une écriture comme
mark_homework_done, dont la réponse ne contient rien à inspecter, un outil
peut donc renvoyer une erreur d'authentification au lieu de se rattraper
tout seul — relancer login dans ce cas.
Durée de vie réelle du jeton inconnue. Les 15 minutes par défaut de
SESSION_MAX_AGE_MS sont une valeur prudente, pas une valeur observée. À
calibrer à l'usage (voir « Développement » ci-dessous).
@blockshub/blocksdirecte est épinglé à la version exacte 0.0.9-alpha
(pas de ^) : c'est une version alpha dont on corrige des bugs par
monkey-patch, une montée de version silencieuse casserait ces correctifs.
Hébergement sur le VPS, via Tailscale (V2)
Le transport HTTP est fait pour être joignable depuis le tailnet et nulle part ailleurs. Trois protections se cumulent :
Le port n'est publié que sur une adresse explicite.
docker-compose.ymlexigePUBLISH_ADDRESSet refuse de démarrer sans — écrire8787:8787aurait lié0.0.0.0sur l'hôte et exposé le serveur à l'internet ouvert. Pour un déploiement tailnet, mets-y l'IP Tailscale du VPS.Chaque requête
/mcpdoit porterAuthorization: Bearer $MCP_AUTH_TOKEN. La comparaison passe partimingSafeEqualsur des empreintes SHA-256 : à temps constant, et sans fuir la longueur du jeton attendu.Protection anti DNS rebinding : le
Hostde la requête doit figurer dansMCP_ALLOWED_HOSTS, sinon 403. Sans elle, une page ouverte dans ton navigateur pourrait faire pointer son propre domaine vers l'adresse tailnet et parler au serveur à ta place.
READ_ONLY vaut true par défaut sur ce transport (contre false en
stdio) : il est joignable depuis d'autres machines, pas seulement par toi à
ton clavier. mark_homework_done disparaît alors de la liste des outils.
Mise en route
cp .env.example .env # renseigner MCP_AUTH_TOKEN et PUBLISH_ADDRESS
docker compose build # build natif arm64 sur le VPS Ampere
docker compose run --rm -it ecoledirecte-mcp login
docker compose up -dLe login se fait bien avant le up, et via run --rm -it pour avoir un
terminal : il faut répondre au QCM. Session et identifiant d'appareil sont
écrits sur le volume session, donc conservés entre deux recréations du
conteneur — c'est pourquoi DEVICE_ID_PATH existe : sans lui l'identifiant
serait recréé à chaque fois et École Directe redemanderait le QCM.
Le conteneur écoute sur 0.0.0.0 à l'intérieur de son espace réseau, ce
qui est correct : l'isolation vient de la publication du port sur la seule IP
Tailscale.
/health répond sans jeton, et volontairement sans rien dire du compte :
{"status":"ok","sessionExists":true}. C'est ce que sonde le HEALTHCHECK.
Connecter un client
claude mcp add --transport http ecoledirecte http://<ip-tailscale>:8787/mcp \
--header "Authorization: Bearer $MCP_AUTH_TOKEN"Le transport est sans état (pas de mcp-session-id) : le serveur est
mono-utilisateur et ne pousse rien vers le client, donc il n'y a aucun cycle
de vie de session à gérer côté serveur.
Exposition publique, pour un connecteur Claude.ai
Un connecteur personnalisé Claude.ai est appelé par les serveurs d'Anthropic, pas par ton appareil : la doc exige un serveur « reachable over the public internet from Anthropic's IP ranges ». Tailscale ne peut donc pas servir ce cas — même installé sur tous tes appareils, l'endpoint resterait injoignable pour Anthropic. Une exposition publique est la seule voie.
Ce qui limite les dégâts
Anthropic publie sa plage de sortie, 160.79.104.0/21. En l'allowlistant dans
Nginx Proxy Manager, le domaine est public dans le DNS mais seule
l'infrastructure d'Anthropic peut lui parler : un scanner se fait refouler
avant même d'atteindre l'authentification. Onglet Advanced du Proxy Host :
allow 160.79.104.0/21;
deny all;
# Streamable HTTP peut ouvrir un flux SSE sur GET /mcp :
proxy_buffering off;
proxy_read_timeout 3600s;Plus un certificat Let's Encrypt avec Force SSL. Garde READ_ONLY=true.
Authentification : OAuth
Claude ne sait pas envoyer un en-tête fixe sur un connecteur personnalisé,
sauf via static_headers, en beta et réservé à un administrateur
d'organisation. Le serveur implémente donc un serveur d'autorisation OAuth
2.0 complet : métadonnées RFC 8414 et RFC 9728, enregistrement dynamique de
client (RFC 7591), PKCE S256, rotation des refresh tokens, 401 avec
WWW-Authenticate pointant vers les métadonnées de ressource.
Comme le serveur ne dessert qu'un seul compte, l'« utilisateur » OAuth est
toujours toi : le consentement est un écran qui demande
MCP_OAUTH_PASSPHRASE. Anthropic impose un humain dans la boucle — un flux
purement machine-à-machine n'est pas accepté. Cinq mauvaises réponses brûlent
la demande en cours.
Clients enregistrés et jetons sont persistés sur le volume, en empreintes
SHA-256 : le serveur n'a jamais besoin de relire un jeton, seulement de
vérifier une correspondance, donc une fuite du fichier ne donne aucun
identifiant utilisable. La persistance évite aussi que le connecteur casse à
chaque docker compose up.
Mise en route
Dans .env :
MCP_PUBLIC_URL=https://ed.ton-domaine.fr/mcp
MCP_OAUTH_PASSPHRASE=<une phrase longue>
MCP_ALLOWED_HOSTS=ed.ton-domaine.fr
READ_ONLY=trueSi NPM tourne en conteneur, aucun port n'est publié sur l'hôte : le proxy joint le service par son nom sur un réseau Docker partagé.
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -davec NPM_NETWORK réglé sur le réseau de NPM (docker network ls), et dans
NPM : Scheme http, Forward Hostname ecoledirecte-mcp, Forward Port
8787. Si NPM tourne sur l'hôte, garde docker-compose.yml seul avec
PUBLISH_ADDRESS=127.0.0.1.
Ensuite, dans Claude.ai : Paramètres → Connecteurs → connecteur personnalisé,
URL https://ed.ton-domaine.fr/mcp. Claude découvre le serveur
d'autorisation, s'enregistre, et t'affiche l'écran de consentement où tu
saisis la phrase secrète.
Statut
stdio (local), HTTP sur tailnet, et HTTP public avec OAuth pour un connecteur Claude.ai sont faits. Un outil d'envoi de messages reste volontairement non implémenté.
La messagerie est en lecture seule : lister et lire. Envoyer, répondre et transférer ne sont pas implémentés — ce sont des écritures visibles par des tiers (professeurs, administration), à n'ajouter que délibérément.
Développement
npm test # tests unitaires — aucun appel réseau réel
npm run typecheck # vérifie aussi test/ et scripts/
npm run build
npm run smoke-test # vérification manuelle contre le vrai comptesmoke-test utilise la session déjà enregistrée par login, n'a besoin
d'aucun identifiant, et n'est jamais lancé en CI. Il appelle chaque outil de
lecture à la suite et continue même si l'un échoue, pour montrer d'un coup
l'état réel de tous les endpoints.
This server cannot be deployed
Maintenance
Related MCP Connectors
Connect AI agents to 1000+ apps with managed authentication and tool-calling.
Connect your Moodle to AI assistants: courses, content, grading and reports from the chat.
Gmail, Outlook, Drive, OneDrive and calendars for AI agents. Many accounts, one endpoint, audit log.
Verified, pay-per-use API tools for AI agents through one authenticated connection.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Canvas LMS and Gradescope, allowing users to query courses, assignments, modules, calendar events, and find relevant resources using natural language.11 npmISC
- AlicenseNot gradedqualityAmaintenanceEnables accessing IServ school platform features such as timetable, exercises, messenger, and more via natural language, without exposing credentials to agents.1MIT
- AlicenseAqualityAmaintenanceEnables AI agents to query and manage EduPage school accounts, including timetables, grades, homework, meals, messages, multi-school discovery, role-aware student switching, and 2FA login.29278 PyPI1MIT
- AlicenseCqualityBmaintenanceEnables an agent to read and act on a Norwegian school parent portal through its unofficial API, covering message threads and attachments, timetables, absences, consent forms, news and scheduling events. Tokens are stored locally, and raw GET tools allow further endpoint exploration.12MIT