Inspiration
I play Fantasy Premier League against my friends and my family. Every week before the deadline I end up sitting there on my own, going back and forth on the same three decisions. Swap someone in, undo it, try a different shape, run out of money, start again. Sometimes you just want someone to bounce it off, and there's nobody around at eleven at night.
The obvious answer is to ask an AI. But it can't actually see what I'm doing. It can see my saved team if I hand it the API, and that's the version from last week. It cannot see the half finished thing on my screen, the transfer I'm still thinking about, the player I dragged out two minutes ago and haven't committed to.
That gap is the whole project. The planning happens in the browser tab and nowhere else, so nothing has ever been able to help with the part that's actually hard.
What it does
Gaffer is a planning board for your FPL squad that an agent can read and act on while you're using it.
It exposes eight WebMCP tools. The agent can read the squad exactly as it sits on screen including changes you just made by hand, search the player pool with filters, propose transfers, set the captain, and highlight players on the pitch so you can see what it's talking about.
You stay in charge. It proposes, you override, it reads the board again and works from your version. When you're done there's a copy button that gives you the plan as plain text to take back to the real FPL site.
How I built it
React and Vite, no backend at all. The whole squad lives in one reducer at the app root, which turned out to be the important decision because that object is what the read tool reports on. Every mutation runs through a validator that checks the FPL rules, budget, three per club, valid formation, captain in the starting eleven, and returns violations as text the agent can actually use rather than just failing.
Tools are registered with the imperative API using document.modelContext.registerTool with an AbortController for cleanup, plus one declarative tool defined with HTML attributes on a form.
The part I'm most pleased with is the dynamic registration. FPL is a constraint system, so the set of legal moves changes constantly. I register only the moves that are legal right now. If you have a free transfer, make_free_transfer exists. Once you've spent it, that tool is gone and take_points_hit takes its place, and that one makes you acknowledge the four point cost. Use your wildcard and play_wildcard disappears for good.
So an illegal move isn't rejected. It cannot be called, because the function isn't there. The registered tool set is the legal move set.
Player data is a snapshot taken on 3 September 2026, because the FPL API doesn't send CORS headers and can't be called from a browser. A serverless proxy and real team import are the obvious next step.
Challenges
The API had moved. Chrome deprecated navigator.modelContext in favour of document.modelContext in version 150, and I was building on 148, so my tools were registering into a namespace my own browser didn't have. Everything looked broken when it wasn't. I ended up supporting both locations, which was the right call anyway since judges will open this on whatever Chrome they happen to be running.
I also couldn't get into an agent client to demo with, so I drove the tools through executeTool from the console instead. That turned out better than what I'd planned. You can see the actual calls and the actual return values with nothing in between, and when the tool list changes in front of you there's no chat transcript in the way to make you wonder if it's real.
The CORS wall on the FPL API cost me time before I gave up and bundled a snapshot. Should have made that call an hour earlier than I did.
What I learned
Descriptions are the interface. An agent picks tools off the description text alone, so writing them is closer to writing docs for a stranger than writing code comments.
Failures should teach. Returning "four players from Brighton, the limit is three" instead of throwing means the agent can correct itself on the next call, and watching that happen is more convincing than any successful call.
And the real unlock of WebMCP isn't that agents can click buttons faster. It's that the browser holds state no server will ever see. Unsaved work, current selection, the thing you're halfway through. That's the interesting surface.
What's next
Real team import through a proxy, multi gameweek chip planning, mini leagues so the agent knows what your rivals are running.
But the bigger thing is that FPL is the case study, not the product. The pattern is that the agent reads the draft state you made by hand and gets handed exactly the moves that are legal right now. That fits any planning surface where people make provisional decisions under rules. Portfolios, sprint boards, shift rotas, product configurators. This is the reference implementation.
Log in or sign up for Devpost to join the conversation.