Inspiration
A few years ago, broadband was limited. On big match days, football fans packed into viewing centres, pubs and the local spot to watch together. That was where you defended your club, argued about the referee and proved your loyalty in front of people who knew you.
That shared experience has mostly gone. Fans now watch alone and scroll through feeds that were never built for football.
Built Fanmoot (fanmoot.com) to bring it back. Fans join their club, build a visible loyalty record, and talk in real time before, during and after every match. It is a live product with real fans.
The Alexa+ track asked a natural question: what if you didn't need a screen at all? Fans are often busy with something else during a match, like cooking, driving or putting the kids to bed. Voice is the right way to ask "how are we getting on?" and "what are people saying?", and to answer back.
What it does
Fanmoot on Alexa+ lets a fan follow and join the football conversation by voice:
- "Alexa, what's happening with Arsenal today?" Alexa gives the score the way a fan would say it ("Arsenal are two-one up at the Emirates, sixty-seventh minute"), and a live scoreboard card appears.
- "What are fans saying about it?" Alexa reads the match thread, sums up the mood ("most think the second goal was offside; one is blaming the manager"), quotes a fan by name and shows a mood card.
- "Tell them the second goal was offside." Alexa asks the fan to sign in to Fanmoot through a real OAuth 2.1 grant, reads the take back, waits for a "yes", then posts it under the fan's own handle on fanmoot.com.
That is a multi-turn agentic workflow with a write action: read, summarise, compose, authenticate, confirm and post. Fans can also reply, react, predict scores, follow clubs, and check fixtures, live scores and trending debates.
There are two parts:
fanmoot-mcp, the part the track asks for. A self-hosted MCP server atmcp.fanmoot.com. It implements MCP spec 2025-11-25 over Streamable HTTP and has its own OAuth 2.1 authorization server. It exposes 19 tools (12 need no sign-in, 7 act as the fan), 8 MCP Appsui://card views, resources and prompts. It is built to Alexa+'s documented account-linking contract. It already works today in Claude, ChatGPT, Cursor, VS Code and MCP Inspector.fanmoot-alexa-plus-demo, the face. The Alexa+ Add-on toolkit is in preview with select partners, and the organisers confirmed it is not available to participants. So I built an Alexa+-style device atalexaplus.fanmoot.com. Only the device is simulated. A real agent answers with real model output, over a real MCP session against the live server. Fans sign in through a real OAuth grant and post to real Fanmoot threads. A live MCP trace inspector beside the device shows every JSON-RPC frame, OAuth request and model call as it happens. Any of them can be copied as a cURL script and replayed from a terminal.
How we built it
The MCP server (fanmoot-mcp), Python 3.12, FastAPI and the official mcp SDK
- Spec compliance, with tests to prove it. The server negotiates protocol
2025-11-25exactly (a test fails the build otherwise). It serves one Streamable HTTP endpoint with sessions andLast-Event-IDresumability, and validatesOriginagainst DNS rebinding. The README maps every requirement to the code that meets it and the test that proves it. - Its own OAuth 2.1 authorization server. It supports PKCE S256 only, RFC 9728 protected-resource metadata, RFC 8414 server metadata, resource indicators, rotating refresh tokens with reuse detection, and 401/403 challenges that start account linking. There are two tiers of client registration in one table. Alexa+ gets a pre-seeded static client because it has no dynamic registration. Every other client registers itself through RFC 7591 or a SEP-991 client metadata document. New fans can sign up mid-flow, and a consent screen lets them choose from four scopes.
- A client holds an opaque handle, never a Fanmoot token. The server swaps the handle for a 15-minute Fanmoot token on each call. The fan's Fanmoot session is encrypted at rest with rotatable keys. A leaked client token is revocable and useless against fanmoot.com.
- A genuine third-party integration. The server is a client of Fanmoot's public API, not its database. Everything it can do, a fan could do with
curl. Its own PostgreSQL holds OAuth state and nothing else. - Tools written for a voice, not a REST client. Tools take what fans actually say ("the gunners", "spurs", "arsenal vs spurs"). An ambiguous name comes back as a question ("Arsenal or Aston Villa?") instead of a guess. Every result has a sentence a model can speak unedited plus
structuredContentfor machines. Lists are bounded, and the tool says what it left out. A score nobody has confirmed is never stated as fact. - Writes that are safe to retry. Every post carries an idempotency key, so a retried request posts once. A take held by Fanmoot's football-topicality gate is never reported as live, and a take that is too long is never silently trimmed.
- Prompt-injection defence. Fan posts reach a model only as attributed quotes (
Jo says: “…”). Control characters and bidi overrides are stripped first. - Latency. Alexa+ requires answers in under 500 ms. A read-through cache with ETag revalidation and a warm-up loop keep tools inside a 400 ms p95 budget, which CI enforces. Measured warm p95 was under 70 ms for every tool.
- Bedrock inside the server.
match_moodlabels a match thread's public posts with Amazon Nova Lite on Bedrock and caches the result per thread. If no model is available it falls back to an offline lexicon and says so.
The Alexa+ demo (fanmoot-alexa-plus-demo), FastAPI, React, Vite and Framer Motion
- Agent: Google ADK runs the agent loop. Each turn it gets exactly the tools
tools/listreturned, so the demo cannot drift from the server. - A model chain on Amazon Bedrock: Amazon Nova Pro first, Claude Sonnet 4.6 on Bedrock second, and Gemini as a fallback from another provider. Failover happens per model call, not per turn, so a fallback can never repeat a tool call and post a take twice. An operator API can reorder the chain live, without a redeploy.
- Alexa's voice is Amazon Polly (generative voice, sentence by sentence while the answer streams). Her ears are Amazon Transcribe streaming. The browser sends 16 kHz audio to our server, which relays it to Transcribe over a WebSocket it signs with SigV4. No AWS key ever reaches the browser.
- The cards are the server's own. The demo reads each
ui://resource over MCP and runs it as an MCP Apps host would. The card runs in a sandboxed iframe with its own origin and CSP, and its buttons (react, reply, follow, predict) call tools over the visitor's own MCP session. - Cost and abuse controls: daily spend ceilings per provider and overall, turn limits per session and per address, with spend measured per turn. A turn costs about $0.011 on Nova Pro. A load check held 20 concurrent visitors, each with its own MCP session.
- Accessible: captions on by default, full keyboard navigation, screen reader announcements, reduced motion respected, and a typed input that is always available.
Challenges we ran into
- The toolkit I was building for is unavailable to participants. The Alexa+ MCP Toolkit docs read as if
alexa-ai deployis available, but the CLI package returns 404 and the answer only appears in forum threads. I rebuilt the plan around it. I wrote the server to Alexa's documented contract anyway, included an add-on manifest clearly marked as never deployed or validated, and put our effort into making the simulated surface provably real. - Alexa+ has no dynamic client registration, but other MCP clients expect it. One authorization server had to serve both, so I built two registration tiers that share one grant model.
- An MCP client SDK bug. In the Python SDK version I used, a 401 or 403 on a POST ended the client's whole session instead of failing one request. That broke the "sign in when needed" flow. I worked around it with a small HTTP transport underneath the SDK.
- 500 ms is tight for a service that calls another service. Caching had to be designed in as a requirement, not added later as an optimisation.
- Making a demo verifiably real. The organisers said scripted responses were acceptable, so a judge has no reason to assume ours are real. The trace inspector and "Copy as cURL" exist so anyone can check.
Accomplishments that we're proud of
- A standards-compliant MCP server that already works in real clients, not only in our demo. One URL, and the fan can sign in or sign up from any client.
- A complete write path under the fan's own account, with consent, scopes, idempotency and honest reporting when a post is held for moderation.
- Every claim is checkable. A compliance table links each requirement to code and a test. The trace shows the protocol version, the PKCE method, the Bedrock model that answered and per-call timings.
- It sounds like a football fan, not a database: "two-one", "nil", "half seven tonight".
What we learned
- A voice surface changes tool design. Inputs have to be the nouns people say, outputs have to be sentences, and ambiguity should come back as a question.
- OAuth is most of the work in a write-capable MCP server, and the MCP
2025-11-25authorization spec, RFC 9728 and resource indicators fit together well once all the pieces are in place. - Per-call model failover across providers is straightforward with ADK and LiteLLM, but retry policy must have a single owner, or a fan hears an answer twice.
What's next for Fanmmot
- Publish to Alexa+ as soon as the Add-on toolkit opens to developers. The server, static client support and manifest are ready.
- Push notifications when your club scores, or when someone replies to your take.
- Club partnerships, so official crests and club accounts come through licensed agreements.
- Extract the OAuth 2.1 authorization server into a standalone open-source library for other MCP server authors.
Built With
- alembic
- amazon-bedrock
- amazon-nova
- amazon-polly
- amazon-transcribe
- amazon-web-services
- claude
- docker
- dokploy
- fastapi
- framer-motion
- gemini
- google-adk
- litellm
- mcp-apps
- model-context-protocol
- oauth2
- postgresql
- python
- react
- sqlalchemy
- tailwindcss
- typescript
- vite

Log in or sign up for Devpost to join the conversation.