Skip to content
Riadh Mnasri
← Back to blog
7 min read

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

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#

EcoleDirecte assignment book with student, school, and teacher names anonymized

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):

ToolDescription
lister_elevesLists the students linked to the account
consulter_notesA student's grades by subject and period
consulter_devoirsA student's assignment book
consulter_absencesA student's absences, lateness, sanctions
consulter_messagesMessages received in the account's inbox

ffe-mcp — public data, no credentials required:

ToolDescription
rechercher_joueurSearches a player by name, returns FFE number and rating
lister_tournoisSanctioned tournaments for a given department
details_tournoiTime control, number of rounds, announcement for a tournament
classement_tournoiStandings after the last round published by the organizer
resultats_joueur_tournoiA player's round-by-round results in a tournament

List of FFE-sanctioned chess tournaments in the Hauts-de-Seine department, on echecs.asso.fr

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 site
  • src/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 net
  • src/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.

Note

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.

Warning

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:

bash
npx ecoledirecte-mcp-init
claude mcp add ecoledirecte -- npx -y ecoledirecte-mcp

For ffe-mcp, no credentials to provide since the data is public:

bash
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.js

Both 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.

ClaudestdioLocal MCP serverdomain / infrastructure / mcpSource (API / HTML)Local cachefallback on failure, "stale" flag

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.