Repository navigation
feat(app): WebMCP tools for leaderboard and XP (Agent-Ready quest, part A) - #610
Open
mschilling wants to merge 2 commits into
Open
mschilling wants to merge 2 commits into
mschilling wants to merge 2 commits into
Conversation
…ebMCP Register read-only WebMCP tools tied to component lifecycle: show_leaderboard (switches the visible period), explain_xp (summarises HQ XP cards) and compare_with (highlights another hero's row and compares XP). Tools unregister through an AbortController on DestroyRef and are a silent no-op without document.modelContext.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Period races and ignored loading timeouts can produce inaccurate or unsuccessful tool responses.
Review effort: Balanced
Findings: 2
Open (4)
What changed in this PR
Adds read-only WebMCP tools to the Angular player app for leaderboard navigation, hero comparison, and XP summaries.
Changes:
- Introduces WebMCP registration, lifecycle handling, and local debugging.
- Adds formatting and tool factories with unit tests.
- Wires tools and visual highlighting into leaderboard and HQ screens.
| File | Description |
|---|---|
leaderboard.component.ts |
Registers the overview leaderboard tool. |
full-leaderboard.component.ts |
Registers leaderboard/comparison tools and row highlighting. |
hq.component.ts |
Registers XP explanation and card highlighting. |
model-context.ts |
Implements WebMCP types, registration, cleanup, and utilities. |
model-context.spec.ts |
Tests registration lifecycle and tool execution. |
code-heroes-tools.ts |
Defines the three WebMCP tools. |
webmcp-format.ts |
Formats leaderboard, XP, and comparison responses. |
webmcp-format.spec.ts |
Tests formatting, matching, and output limits. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- show_leaderboard errors if the visible period changed while loading - explain_xp errors when HQ data does not finish loading in time - daily goal reached exactly no longer reports '0 XP over' - singular nouns for activity counts of one
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
This is the first step of the "Agent-Ready" quest. The player app (
apps/frontend/app) now exposes read-only WebMCP tools, so an in-browser AI agent can drive the screens players already use. Each tool changes what the user sees and returns a short text summary (≤1,500 chars).show_leaderboard/leaderboard,/leaderboard/{heroes,bots,projects}{ period: 'day' | 'week' }explain_xp/hq{ period, compare?: boolean }compareadds the daily goal (day) or your weekly Heroes rank (week).compare_with/leaderboard/heroes{ user: string }isError.readOnlyHint: true. The leaderboard tools also setuntrustedContentHint: true, because display names are user-controlled. Names are quoted, collapsed and capped in the output.core/webmcp/model-context.tsfollow the WebMCP draft.Plan
core/webmcp/model-context.tsdocument.modelContext, falling back tonavigator.modelContext. Without either it is a silent no-op.registerWebMcpTools(tools, destroyRef)registers each tool with{ signal }from anAbortControllerand aborts it onDestroyRef.onDestroy. It also callsunregisterTool(name)on early Chromium builds that ignore the signal./leaderboard→/leaderboard/heroesboth registershow_leaderboard. Destroying the old component then doesn't remove the new tool.waitUntil()polls a signal-backed condition.executeruns outside an injection context, sotoObservablecan't be used there.window.__codeheroesWebMcp, gated onenvironment.name === 'local'. Headless Chrome has no WebMCP, so screenshots and manual checks callexecutethrough it.core/webmcp/webmcp-format.ts: pure formatters (leaderboard summary, XP breakdown, comparison, name matching) and budget clamping.core/webmcp/code-heroes-tools.ts: tool factories that take accessor callbacks over the components' existing signals. The components stay the source of truth, and the tools can be tested without TestBed.LeaderboardComponentregistersshow_leaderboard.FullLeaderboardComponentregistersshow_leaderboard, pluscompare_withon heroes only. It adds ahighlightedIdsignal and an.is-highlightedrow style.HqComponentregistersexplain_xp. It adds afocusedCardsignal and an outline that clears after 4s.The plan was rubber-ducked before implementation. That review led to the duplicate-name registry, no
toObservableinexecute, partial-board wording on the overview, ambiguous matches returning candidates, and quoted names.Part B: "Agent-Ready" achievement (design only, follow-up)
Goal: award a special badge when a merged PR introduces WebMCP (
modelContext.registerTool(or atoolname=form attribute).Why it is not in this PR: the
pull_request(closed + merged) andpushwebhook payloads carry no diffs. Detection needs the GitHub REST API:github-receiverkeeps doing what it does. The merged PR event already produces apull_request_mergegame action.game-engine(or a small dedicated Firestore-triggered function, sogame-enginestays I/O-free), onpull_request_merge:GitHubAppService(libs/server/integrations), usinginstallation.idfrom the stored event;GET /repos/{owner}/{repo}/pulls/{number}/files?per_page=100(max 3,000 files;patchis omitted for very large diffs);+lines inpatch) for/\bmodelContext\.registerTool\s*\(/or/\btoolname\s*=/. Skipnode_modules, lockfiles anddist/.agent_readyentry inSPECIAL_BADGES(metadata: { trigger: 'webmcp_introduced' }) throughSpecialBadgeService. The badge is idempotent per user, like the other special badges.GITHUB_APP_ID/GITHUB_APP_PRIVATE_KEYsecrets to another function;Each of these warrants its own PR and review, so it is a follow-up issue.
How to test
npx nx test appruns the new specs incore/webmcp/*.spec.ts: 24 tests covering formatting, budgets, matching, registration/abort lifecycle and the newest-owner-wins rule.nx serve app, emulators), open/leaderboard/heroesand in DevTools run:/hq:await __codeheroesWebMcp.explain_xp({ period: 'week', compare: true }).Test plan
nx test app: 24/24)nx build app(production) succeeds; no new budget warnings (the two existing component-style warnings are in settings/badges-modal)eslinton touched files: no new findings (the 4 errors inleaderboard-section.component.tsand the unusedcomputedwarning were already onmain)main, so they were not reformatted, to keep the diff small)show_leaderboardswitched Week → Today;compare_withhighlighted the matched rowdocument.modelContext(tool list changes on route navigation)Notes
nx lint appalready fails onmain(a11y click/key andprefer-injecterrors in other files).npm run setup, Jest also picks up the generatedsrc/environments/environment.test.ts, because it matches*.test.ts, and reports it as an empty failing suite. Without that generated file all suites pass. CI doesn't run setup, so this doesn't affect CI.compare_withworks on the current period of the heroes leaderboard only. It doesn't add a new compare view./users/:idprofile-level compare once a compare view exists.