Ask the match
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., "@Ask the matchVerify the claim that the home team had 14 high-xG chances"
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.
Mission Winning — explainable match intelligence (synthetic data)
The reason is computed, not written. Every number that reaches the screen is re-derived from the match state before it is shown, and when a claim fails, the rejection and the recovery are on screen rather than in a log.
Entry for the Microsoft × Premier League Inside the Game hackathon, built by Mission Winning. Every club, player and event in the football pipeline is synthetic — win chance here is match intelligence, not a price or a wager.
Live demo: https://snedz.github.io/match-intelligence/ — the static build, no server, no key, no account. It carries the 1:18 demo video and the 14-page deck with it, and it is the same build the video shows.
Run it (no keys, no cloud)
pip install -r requirements.txt
python -m itg.webRequires Python 3.9 or newer (verified on 3.9.6, 3.12.14 and 3.14.8). Open http://127.0.0.1:8000 . The page replays a seeded synthetic match on a pitch at 30× (play, pause, scrub). Overlay cards and the win-chance ribbon read the pipeline JSON. Fan/Analyst, EN/ES/FR, and the favourite-player list re-render the story with the offline templates.
Run the tests with the same install:
python -m pytest -q # green, offline, no keysTen of the tests are the Microsoft Agent Framework graph and are skipped unless that package is installed; the extra install below turns them on. The exact count is the badge on the repository — it moves every time a test is added, and a number in a document that nobody keeps in step is a number nobody should quote.
The same suite runs on every push, on Python 3.9, 3.12 and 3.14 —
.github/workflows/tests.yml. That matrix is exactly the set of interpreters
it has been verified on by hand (the same result on each), so a green
run is a fact rather than a hope; a version is added only after it has been
run locally. tests/test_requirements.py pins the install contract: every
third-party import in this repository has to be declared in requirements.txt,
so the two commands above cannot silently drift apart.
The Microsoft Agent Framework graph orchestrator is a separate install, because it pulls in ~70 packages and the default install should stay fast:
pip install -r requirements-agent-framework.txtEverything runs either way. Without the package, itg/workflow.py reports itself unavailable rather than pretending, the dependency-free pipeline is the orchestrator, and the trace records which one ran.
QA recovery demo: http://127.0.0.1:8000/?inject=1 or the "Inject a fabricated claim" switch. The same flag is GET /api/trace?seed=7&inject=1. QA rejects the claim, Story falls back to the offline template, and QA accepts that recap. The trace lists Data → Analysis → WinProb → Story → QA with timings.
python -m itg.pipeline --seed 7 --persona fan --language en
python -m itg.pipeline --seed 7 --persona analyst --language es --favorite Okafor
python -m itg.pipeline fan # same as persona fan, seed 7, English
python -m pytest -q--language accepts en, es, or fr (also English / Spanish / French). --persona is fan or analyst. --favorite highlights one player on the team sheet. With no model configured, Story uses offline templates. That path is what the tests run.
The JSON includes overlays. Cards with "type": "win_prob" are the swings, each with a short reason and the claims QA checked against match state.
Related MCP server: Football Atlas
Ask the match — the MCP server (itg/mcp.py)
The same match state, published to any MCP client. This is the integration surface: an agent can ask the match questions and check its own claims.
python -m itg.mcp # speak MCP on stdio (JSON-RPC 2.0)
python -m itg.mcp --demo # one scripted exchange, printed
python -m itg.mcp --list-toolsTool | What it answers |
| the shared match state: teams, score, events ingested, squad |
| the key moments, each with its reason and its recomputable claims |
| the win chance at a minute, and the factual cause of the swing |
| the guardrail, exposed — is this sentence about the match true? |
| recompute every number on every on-screen card from match state |
| which agent ran, in which order, who handed off, what was rejected |
| one player: team, position, distance, top speed, carries, xT |
verify_claim is the one to try first. Given a fabricated sentence it returns
accepted: false and the reasons:
{
"accepted": false,
"issues": [
"high-xG count 14 exceeds match state (4)",
"pressure count 223 exceeds match state (216)"
],
"how": "the QA agent recomputed the claim from match state, it did not read it and judge it"
}It is hand-rolled JSON-RPC rather than the mcp SDK on purpose: the install
path a judge takes is pip install -r requirements.txt, and a new dependency
there is a new way for the entry to fail before it is seen. It adds nothing to
the requirements, and tests/test_mcp.py asserts that it does not.
In a container
docker build -t mission-winning .
docker run --rm -p 8000:8000 mission-winning # the web app
docker run --rm -i mission-winning python -m itg.mcp # the MCP serverdocs/AZURE.md has the Azure path for the same image — Container Apps for the
app and the MCP server, Foundry with managed identity — with the exact az
commands. It is not deployed on Azure: there is no subscription behind this
entry, and that document says so in its first paragraph. The Dockerfile has
not been built in this environment either, because Docker is not installed
here; tests/test_deployment.py checks what it can (every path it copies
exists, it copies no .env, it runs as a non-root user, its entrypoint is a
real module).
Architecture
flowchart LR
G["Generator<br/>seeded possession-chain<br/>live event stream"] --> D["Data agent<br/>events → shared MatchState"]
D --> A["Analysis agent<br/>xG · pass difficulty · pressure surges<br/>momentum · synthetic xT"]
A --> W["WinProb agent<br/>Poisson win chance<br/>+ a factual 'why it moved'"]
W --> S["Story agent<br/>persona × language × favourite player"]
S --> Q["QA agent<br/>fact-check score, reasons,<br/>distances vs MatchState"]
Q -- "reject → grounded template" --> S
Q --> O["Overlay JSON (timestamped)<br/>+ web UI"]
D --> X["MCP server<br/>the same state,<br/>for any agent or client"]
Q --> X
M["Model chain<br/>Foundry (not wired — awaits Azure)<br/>→ OpenAI-compatible (Ollama)<br/>→ offline templates (default)"] -.-> SEvery agent reads and writes the same MatchState, so the QA agent checks a claim against the state the Story agent wrote from. One shared state, not five private copies.
The model chain always terminates at the offline templates, which is why the demo runs with no key, no account and no network. A model call that fails falls back to the grounded template; it never invents a value to cover the failure.
The pipeline
Stage | Agent | File | What it adds |
Ingest | Data agent |
| Streaming events into shared match state |
Interpret + Explain | Analysis agent |
| xG, pressure windows, momentum, pass difficulty |
Interpret | Win-chance agent |
| Live home/draw/away chance and why it moved |
Render + Personalize | Story agent |
| Fan vs analyst, EN/ES/FR, favourite player |
Guardrail | QA agent |
| Score, reasons, distances, and wagering language |
itg/pipeline.py is the orchestrator. If QA rejects a recap, Story is called again on the offline template only. python -m itg.web serves that JSON to the replay page (itg/web/).
itg/generator.py is a seeded possession-chain simulator. Shots get a toy xG from distance to goal. Carries, a synthetic expected-threat value, per-player speed and distance, and match metadata are added without changing that shot model, so the calibration below still applies. On-ball events also carry a SPADL-shaped descriptor; see NOTICE. Clubs play in the fictional Harbor Invitational.
Model backends (itg/llm.py)
Microsoft Foundry via Microsoft Agent Framework — set
FOUNDRY_PROJECT_ENDPOINT, runaz login. Not wired to a project yet; that waits on an Azure account.Any OpenAI-compatible endpoint (for example local Ollama) —
LLM_BASE_URL.Offline templates — the default. Always works with no keys. Tests use this mode.
If a model call fails, the agent falls back to the grounded template. Copy .env.example → .env. .env is git-ignored. Never commit keys.
License: MIT.
Win-chance agent (itg/winprob.py)
Live home/draw/away chances that explain their own swings ("Harbor City FC win chance 39%→8%, tilted to Northgate United by a goal by Vidal; 1 high-xG chance under sustained press (10 pressures)"). Poisson model on remaining minutes; rate = blend of pre-match strength and observed xG. Fitted on 300 synthetic matches, tested on 300 held-out synthetic matches — each scored at seven time points, so 2,100 scored predictions (python3 -m itg.winprob backtest):
Model | Brier (3-way) | Log-loss |
Win-chance agent | 0.350 | 0.599 |
Score-only baseline | 0.390 | 0.673 |
Reliability table in predictions/winprob-backtest.json (predicted vs actual within 7 points per bin; the largest gap is 6.8 points, in the 0.7–0.8 bin. The per-bin counts are scored predictions, not matches.) A 40-match smoke check in the tests must stay under a Brier of 0.40.
The full method, the reliability table bin by bin, and what this measurement
does and does not establish are in docs/CALIBRATION.md.
tests/test_deployment.py asserts that the numbers quoted there still match
predictions/winprob-backtest.json, so the document cannot drift away from
the artifact it was measured from.
What is where
| the orchestrator: ingest → interpret → explain → render → personalize, with the QA retry |
| the MCP server over the same match state |
| Data, Analysis, Story and QA agents |
| the win-chance model and its backtest |
| the replay page and the JSON API |
| how the win-chance number was measured |
| the Azure path, and what is not deployed |
| the pitch and the deck |
| the deck generator, so the PDF is reproducible rather than a hand-written file |
| how to work in this repository — for a human contributor, and for an agentic editor |
| the hash of the deck the PDF was built from; a test fails if the PDF goes stale |
| the 1:14 demo video (silent, 1600×900) |
This server cannot be deployed
Maintenance
Related MCP Connectors
API-Football MCP — comprehensive soccer/football data
Football fixtures, standings, and odds intelligence for AI agents.
Sportsbook-derived no-vig fair value with confidence, provenance, and history over REST/MCP.
Football-Data.org MCP — soccer competitions, matches, standings
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables MCP clients to access live football data from FotMob, including match stats, team form, injuries, and player workload, without making predictions.101MIT
- AlicenseNot gradedqualityBmaintenanceProvides access to a cited knowledge graph on football's Laws of the Game and major competitions, with tools for search, question answering, and entity exploration, returning claims with sources and confidence.CC BY-4.0
- FlicenseNot gradedqualityBmaintenanceProvides live football data from API-Football over HTTP, enabling queries for upcoming matches, recent results, current standings, injuries, live matches, and team search through MCP tools.-
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to fetch live football odds and team form, and flag matches where the market price diverges from a simple form-based estimate.Eclipse Public 2.0