ecoledirecte-mcp and ffe-mcp: two homemade MCP servers, no official API

I spent a recent evening figuring out why my own MCP server had stopped
logging into EcoleDirecte with perfectly valid credentials. Not an exotic
bug, just an undocumented login protocol that had to be reproduced down to
the exact header. That session is a good excuse to write up the two MCP
servers I built for my own use: ecoledirecte-mcp for my kids' grades and
homework, ffe-mcp for chess tournaments sanctioned by the French Chess
Federation (FFE).
The problem it solves#
Neither site exposes a public API. EcoleDirecte has an app and a website, full stop: to know whether my kids have homework due tomorrow, I have to open the site, log in, navigate to the assignment book. Nothing that fits into a conversation with Claude where I'm already looking at my week's schedule.
For chess, I already had a skill that searches tournaments via WebFetch
directly against the FFE site. It worked, with a limitation documented right
in the skill itself: the site's classic ASP.NET pagination and JavaScript
postback filters aren't something a plain fetch can drive, so only the
first batch of results per department ever came back. A dedicated server,
with its own identified endpoints and its own parsers, closes that gap
properly instead of half-working around it on every call.
Why an MCP server rather than a skill or a script#
A skill encodes a behavior, an MCP server encodes access to an external system: I go into that distinction in the article on MCP. What this post covers is what it actually takes to build that second kind of building block yourself, when the well-documented public API you'd rather call simply doesn't exist.
What each server exposes#

A real capture of the interface, with the student's, school's, parent's
and teacher's names swapped for fictitious ones before the screenshot was
taken — this is the page consulter_devoirs reads on my behalf.
ecoledirecte-mcp — requires the account's credentials (read from a
local .env, never sent anywhere else):
| Tool | Description |
|---|---|
lister_eleves | Lists the students linked to the account |
consulter_notes | A student's grades by subject and period |
consulter_devoirs | A student's assignment book |
consulter_absences | A student's absences, lateness, sanctions |
consulter_messages | Messages received in the account's inbox |
ffe-mcp — public data, no credentials required:
| Tool | Description |
|---|---|
rechercher_joueur | Searches a player by name, returns FFE number and rating |
lister_tournois | Sanctioned tournaments for a given department |
details_tournoi | Time control, number of rounds, announcement for a tournament |
classement_tournoi | Standings after the last round published by the organizer |
resultats_joueur_tournoi | A player's round-by-round results in a tournament |

Public data this time, no anonymization needed: the page lister_tournois
parses for department 92.
How they're built#
Both projects share the same three-layer architecture:
src/domain/: business types and interfaces (ports), zero dependency on the source API or sitesrc/infrastructure/: the adapter that speaks the source's raw format (HTTP requests for one, HTML parsing for the other), backed by a file cache that acts as a safety netsrc/mcp/: the tools exposed to Claude, which orchestrate the live fetch and the fallback to cache
That isolation has one purpose: the day EcoleDirecte changes its login protocol, or the FFE tweaks a results page's layout, exactly one file needs to change. The tools exposed to Claude don't move.
Both run locally only, over @modelcontextprotocol/sdk in stdio transport:
no HTTP server exposed, no deployment, no network port to secure. Any
family who wants to use one clones the repo and runs it at home, with their
own credentials for ecoledirecte-mcp, no credentials at all for
ffe-mcp.
The real technical obstacle: reverse-engineering an undocumented protocol#
The ecoledirecte-mcp case is the more instructive one. login.awp kept
rejecting my credentials with a generic "invalid username or password"
message, even though the same credentials worked fine on the website.
First lead, plausible but wrong: anti-bot scripts
(transparentedge.io, a TEDGEPT cookie) spotted while comparing a
network capture of a real browser login with what my Node client was
sending. It looked like blocked fingerprinting.
The actual cause, found by cross-checking the community-maintained
documentation of the EcoleDirecte protocol (unofficial, but reverse
documented by several open source projects), was simpler and strictly
protocol-level: login.awp expects a preliminary GET (?gtk=1) that
sets two cookies, one of them named GTK, to be sent back on the login
POST both as an X-Gtk header and as a plain Cookie header. My
client was sending neither. Once both were added, login succeeded on the
first try.
The lesson that matters isn't the missing header, it's more general: an error message produced by an undocumented API is not proof of its cause. I burned time on a plausible anti-bot theory before checking the actual expected protocol against a network capture, point by point. Same principle I describe in the article on debugging with an agent: cite raw evidence before concluding, never a plausible-sounding story.
On the ffe-mcp side, the fragility is different: no protocol to
reverse-engineer, but public HTML pages whose structure can change without
notice. The defense isn't the same either: the vitest suite runs against
HTML fixtures actually captured from echecs.asso.fr, and the README
explicitly documents which fields were verified by real capture and which
are still unconfirmed. If the layout changes, the test fails loudly and
fast instead of silently returning a wrong standing.
How to use them#
For ecoledirecte-mcp, a CLI wizard writes the config file outside the
project directory, so it stays stable even when installed via npx:
npx ecoledirecte-mcp-init
claude mcp add ecoledirecte -- npx -y ecoledirecte-mcpFor ffe-mcp, no credentials to provide since the data is public:
git clone https://github.com/riadh-mnasri/ffe-mcp.git
cd ffe-mcp && npm install && npm run build
claude mcp add ffe -- node /path/to/ffe-mcp/dist/index.jsBoth also work with Claude Desktop, by adding the equivalent entry to
claude_desktop_config.json.
Sharing without becoming a service#
Both projects are MIT licensed, with no account to create and no central
database: it's not a service you connect to, it's a program each family
clones and runs at home. For ecoledirecte-mcp specifically, that choice
isn't just an architectural preference, it's what removes the need to carry
the responsibility of storing someone else's school data: there is no
"someone else", each family hosts their own.
What this generalizes to#
Three principles that hold beyond these two projects, for any tool built against a source with no official API: start from an actually captured request rather than a guess at the protocol, isolate the volatile adapter from the rest of the code so only one layer breaks the day the source changes, and prefer falling back to an explicitly dated cache over a hard failure when the source is outside your control. Both servers' code is public: ecoledirecte-mcp and ffe-mcp.


