2026 FIFA World Cup prediction system with MCP + web UI
13 tools with mostly complete schemas and descriptions. Naming is verb-first and clear (get_*, record_*, upsert_*, list_*, mark_*, approve_*, reject_*). All tools have descriptions (10 - 500 chars, within baseline 10 - 1024). All parameters have type definitions and descriptions. However, output schemas are undocumented, no return type annotations visible in code. Error handling is present but minimal (ToolError raised for validation, no recovery guidance). Missing tool annotations (readOnlyHint, destructiveHint, idempotentHint). Some parameter descriptions are verbose but lack explicit constraints (e.g., 'credibility in [0,1]' stated in prose, not as JSON Schema bounds). Composition is strong, tools are single-responsibility and chain well (e.g., get_unprocessed_news → upsert_player_status → approve_intel). No security issues detected (no secrets in params, source_url traceability enforced).
Approve a pending intel item so it starts affecting predictions. ref is the 'ps:<id>'/'ts:<id>' value from list_pending_intel (a bare integer is treated as a player-status id).
Return current standings for a 2026 World Cup group (A-L).
Return raw news articles not yet processed, for off-pitch intel extraction.
Return the next N scheduled matches without a result yet.
List intel items awaiting human approval (low-confidence / single-source). Covers both player-availability statuses and team-level signals. Each item carries a ``kind`` ('player'|'team') and a ``ref`` ('ps:<id>'/'ts:<id>') to pass to approve_intel / reject_intel.
Mark news articles as processed so they are not re-extracted.
Output schemas undocumented. No return type annotations visible in code. LLMs cannot infer what fields to expect from responses (e.g., run_simulation returns dict[str, dict[str, float]], but structure of inner dict is opaque). Prevents downstream tool chaining and forces LLMs to guess field names.
Missing tool annotations. No readOnlyHint, destructiveHint, or idempotentHint metadata. LLMs cannot distinguish safe reads from destructive writes without reading descriptions. record_intel, record_match_result, upsert_player_status, upsert_team_signal, mark_news_processed, approve_intel, reject_intel should be annotated as destructive or write-heavy.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 71 | <=2025-11-25 | v2 |
Predict 1X2 + scoreline for a fixture (applies current intel) and persist it.
Record an off-pitch intelligence event that adjusts a team's expected goals. `direction` ("weaken" or "strengthen") determines the sign of the effect; `magnitude` is the size of the lambda multiplier delta (e.g. 0.20 for a key injury with direction="weaken"). credibility in [0,1] scales the effect. ALWAYS pass a real source_url so the adjustment is traceable.
Record an official match result and refresh standings/bracket.
Reject (delete) a pending or active intel item by its 'ps:<id>'/'ts:<id>' ref.
Run the Monte Carlo tournament simulation; return top title contenders.
Record/update a player's current status from news, adjusting expected goals. tier: 'key' | 'regular' | 'fringe' (you judge importance from the article). status: 'out' | 'doubtful' | 'suspended' | 'available' ('available' clears a prior status). affects: 'attack' (lowers this team's own scoring; forwards/wingers/attacking mids) | 'defense' (raises the OPPONENT's expected goals; goalkeepers and defenders) | 'both' (half each; defensive midfielders / box-to-box). Position heuristic: GK/CB -> defense, DM/CM -> both, FWD/AM/W -> attack. OMIT this argument when corroborating an existing row to preserve its channel; it defaults to 'attack' only when creating a brand-new row. confidence in [0,1]; ALWAYS pass a real source_url. official=True only for club/federation sources. High confidence AND (>=2 sources OR official) applies immediately; otherwise it is queued for review.
Record a team-level, between-the-lines signal that nudges a team's expected goals. Use this for qualitative signals that the player-availability tool cannot capture: tactics, morale, motivation, fatigue, or general form/eye-test. category: 'tactical' | 'morale' | 'motivation' | 'fatigue' | 'form'. direction: 'weaken' | 'strengthen'. magnitude_tier: 'major' | 'moderate' | 'minor' (these are deliberately soft; even 'major' is far smaller than a key-player injury). affects: 'attack' | 'defense' (raises the opponent's expected goals — defensive frailty) | 'both' (half each). OMIT this argument when corroborating an existing row to preserve its channel; it defaults to 'attack' only when creating a brand-new row. confidence in [0,1]; ALWAYS pass a real source_url. official=True only for club/federation sources. Record only FORWARD-LOOKING or beyond-the-result signals (e.g. "manager switching to a back three", "camp morale low after a bust-up", "clearly fatigued on a third match in eight days"). Do NOT log match recaps or scorelines here — results are ingested separately, and logging them as signals would double-count.
Numeric constraints stated in prose, not JSON Schema. 'credibility in [0,1]', 'limit max 20', 'iterations default 50000' are documented in descriptions but not enforced as minValue/maxValue/default in schema. LLMs may pass out-of-range values; validation happens at runtime, not schema-time.
Error messages are minimal. ToolError raised for validation (e.g., 'Invalid group', 'direction must be weaken or strengthen') but no recovery guidance. Per pattern:recovery-guide, errors should suggest next steps: 'Invalid group X. Valid groups: A-L.' or 'direction must be weaken or strengthen, try again with one of these values.'
No pagination support on list tools. get_unprocessed_news and list_pending_intel accept limit but no offset/cursor. If results exceed limit, LLM cannot fetch remaining items without re-calling with a different limit. Per pattern:paginated-result, list tools should return total_count or next_cursor.