feat: add WebMCP challenge web app - #40
Conversation
Thin FastAPI adapter (webapp/) over the unchanged repopulse core: GET /api/health, POST /api/scan, POST /api/compare with a stable error contract, public-repos-only enforcement before tree reads, and a vanilla-JS one-page dashboard. Four read-only WebMCP tools (scan_repository, get_attention_items, get_check_details, compare_refs) share the exact same state and code paths as the UI buttons. Core engine, CLI, schemas, scoring, and action behavior untouched. New optional 'web' extra (fastapi, uvicorn); base install unchanged.
Do not overwrite the selected repository until a scan succeeds, so compare and the visible report stay on the same repo. Use a human-facing compare error, strip/reject blank compare refs, and record the web layer in the changelog and translations.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f42c1fad8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
WebMCP follow-up — apply on the same branchPlease address these recommendations on the existing Required before merge
Recommended if it stays surgical
Verification required after the fixesRun and report actual results for: ruff check .
mypy repopulse
mypy webapp
pytest
python -m build
repopulse scan . --format summary --quiet
repopulse scan https://github.com/3ssiri/RepoPulse --format summary --quietThen run the web app locally and manually verify:
WebMCP verification — do not mark complete without actual tool executionExecute all four tools in a supported WebMCP environment:
Confirm that Finally deploy the same FastAPI app over HTTPS, add the live URL to the PR, and re-run the four WebMCP tools against the deployed app if the challenge environment requires it. Keep the current architecture: same repository, same branch, top-level |
Show Compare-before-Scan errors in the UI, discard stale scan/compare results, enforce the 256-char ref limit on URL-derived refs, abort partial WebMCP registration, and reuse privacy-check repo metadata so scan/compare do not call get_repo twice.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Pushed review fixes to this same branch ( Addressed
Verification (actual)Live local API (
Not done yet (out of this code round)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0186d72c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Current readiness statusAll code-review findings are now addressed on the existing Current head:
The remaining challenge gates are external integration checks rather than known code blockers:
Do not merge until those two external checks are completed. |
Deploys the existing FastAPI app (webapp/app.py) as-is: - pyproject.toml [tool.vercel] entrypoint points at webapp.app:app, since webapp/app.py is outside Vercel's auto-detected entrypoint locations. setuptools ignores the table; sdist and wheel contents are unchanged. - vercel.json enables fluid compute and raises maxDuration to 60s, because a scan makes several sequential GitHub API calls. - requirements.txt lists only the runtime imports the web layer needs, so the function does not pull in the CLI-only dependencies. No change to scoring, checks, CLI contracts, or the published package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158GxB1urMdo6XYFAkHfSFT
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The first deployment returned 500 with ModuleNotFoundError: No module named 'fastapi'. A probe deployment confirmed Vercel's Python runtime installs only [project].dependencies from pyproject.toml: [project.optional-dependencies], PEP 735 [dependency-groups], and requirements.txt are all ignored. FastAPI must not become a dependency of the published `repopulse-cli` package, so the web-only requirements are installed from requirements.txt by a buildCommand that runs after the framework install and before the function bundle is assembled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158GxB1urMdo6XYFAkHfSFT
Records what each deployment file is for and, in particular, why the web dependencies cannot live in pyproject.toml: Vercel's Python runtime installs only [project].dependencies, so FastAPI would otherwise have to become a dependency of the published repopulse-cli package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158GxB1urMdo6XYFAkHfSFT
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4895d7e13c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A private repository the deployment's GITHUB_TOKEN could read returned 403 private_repository_not_supported, while a missing or inaccessible one returned 404 repository_not_found. An anonymous caller could use that difference to probe which private repository names the token can see. Both cases now return the identical 404 response. The privacy check still runs before any tree or file content is read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f8f36bcaea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
After repository A scanned successfully and a scan of B failed, the form kept showing B while state.repositoryUrl still held A. Compare then acted on A without saying so, contradicting the repository the form displayed. The failure path now resyncs the form to the retained selection, guarded on a previous successful report so a first failed scan does not wipe the input. Verified in Chrome against a real backend: after a failed scan of a non-existent repository, the form shows the previously scanned repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary
Optional web layer for the OpenAI WebMCP challenge: a one-page dashboard over the existing scan/compare engine, plus four read-only tools so a human and an agent share the same page state.
webapp/(extraweb); not packaged inrepopulse-cli.parse_github_url→GitHubClient→build_health_report/build_comparison.scan_repository,get_attention_items,get_check_details,compare_refsviadocument.modelContext.registerTool().GITHUB_TOKENis server-side; private repos rejected before tree/file reads.textContent/createElementonly.registerTool()throws and asynchronous registration rejections.404 repository_not_foundas an inaccessible one, so the API cannot be used to discover private repository names.vercel.json,requirements.txt,[tool.vercel]): no change to scoring, checks,HealthReport/ComparisonReportschemas, CLI contracts, the GitHub Action, or the published package.python -m buildsdist and wheel file listings are identical before and after.Docs: docs/webmcp-challenge.md (includes a Deployment section).
Verification on head
4895d7eQuality gates (local)
ruff check .mypy repopulsemypy webapppytestpython -m buildgit diff --checkrepopulse scan .3ssiri/RepoPulse: 100 / 100 - Excellentrepopulse scan https://github.com/3ssiri/RepoPulse3ssiri/RepoPulse: 100 / 100 - ExcellentCI on this head:
test (3.11),test (3.12),Action smoke test,CodeQL,Analyze Python— all green.Local API smoke (uvicorn + real GitHub API)
GET /api/health→ 200{"status":"ok","service":"repopulse-web","version":"0.3.6"}POST /api/scan→3ssiri/RepoPulse, 100/100,Excellent, 11 checks,scan_truncated=false,schema_version1.1POST /api/comparev0.3.5→v0.3.6→ delta 0, 11 unchanged, 0 improved, 0 regressedinvalid_repository_url; 300-char URL-derived ref → 400invalid_ref; 300-char body ref → 400invalid_ref; unknown ref → 404ref_not_foundAuthorizationheader or environment secret appears in any response body or server log.Browser — host shim, not a native WebMCP browser (Chromium 141.0.7390.37, Playwright)
These results were produced against a host shim implementing exactly the surface
webapp/static/app.jstargets —document.modelContext.registerTool(tool, { signal })andexecute(input, { signal }). The Chromium available to that session shipped no WebMCP implementation. For the native-browser run, see Native WebMCP browser run below.Against the real local server and the real GitHub API — 16/17 checks pass; the one non-pass is a
GET /favicon.ico404, a browser-default request the app has no route for (no JS runtime errors, no page errors).readOnlyHint+untrustedContentHintscan_repository→ returns the report and the visible dashboard updates (11 checks rendered, attention section shown)get_attention_items→ reads page state, issues zero network requestsget_check_details→ full detail for a real key; unknown key returnsavailable_keysrecoverycompare_refs→ returns the comparison and the visible dashboard updatesDeterministic second pass (12/12) for orderings that need controlled latency:
currentComparisonAbortSignalpassed toscan_repositoryaborts the fetch, leavesstatus: idle, re-enables both buttonsregisterTool()throw and asynchronous rejection:registration.abort()runs, badge falls back to WebMCP Unavailable, zero tools left registeredDeployment
Vercel project
repopulse-webmcp(teamassiri-f0925b28), linked to this repository,fluid: true,maxDuration: 60onwebapp/app.py, noGITHUB_TOKENset.The first deployment returned 500 (
ModuleNotFoundError: No module named 'fastapi'). A probe deploymentestablished that Vercel's Python runtime installs only
[project].dependenciesfrompyproject.toml—[project.optional-dependencies], PEP 735[dependency-groups]andrequirements.txtare all ignored.Since FastAPI must not become a dependency of the published
repopulse-cli, the web-only requirements areinstalled by a
buildCommandthat runs after the framework install.Production has been promoted off that broken build. The branch deployment carrying
4895d7ewaspromoted with
vercel promote; the servedwebapp/static/app.jswas byte-compared against the local4895d7ecopy before promoting. Production now answers:Verification against the live deployment
All of the following ran against
https://repopulse-webmcp.vercel.app(production alias, HTTPS).Live HTTPS API smoke
GET /api/health{"status":"ok","service":"repopulse-web","version":"0.3.6"}POST /api/scan3ssiri/RepoPulse100/100Excellent,scan_truncated=false,schema_version1.1,private=falsePOST /api/scanpsf/requests97/100Excellent,scan_truncated=false,schema_version1.1POST /api/comparev0.3.5→v0.3.6score_delta 0, 11 checks unchangedPOST /api/scannon-github hostinvalid_repository_urlPOST /api/scan300-char ref in the URLinvalid_refPOST /api/compare300-charbaseline_refin the bodyinvalid_refPOST /api/scan3ssiri/nope-does-not-existrepository_not_foundNo traceback, raw GitHub payload,
Authorizationheader or environment secret appears in any response body.Cold / warm latency on the deployed function
GET /api/healthPOST /api/scan(3ssiri/RepoPulse)Heaviest repositories, measured against the same production URL:
psf/requestsmicrosoft/vscodescan_truncated=falsetorvalds/linux(local backend)scan_truncated=truetorvalds/linuxis the first live confirmation that the truncation flag fires on a repository largeenough to hit the GitHub tree API's own
truncatedresponse — the renderers warn rather than reporting asilently partial scan.
maxDurationstays at 60 s: the slowest production scan measured is 6.92 s, and a15 s ceiling would have cut off a
microsoft/vscodescan measured at 15.5 s over a slower link.Native WebMCP browser run
Chrome 152.0.7977.65 (stable) ships a real WebMCP implementation: flag
enable-webmcp-testing,feature
WebMCPTesting, and aWebMCPCDP domain. The browser was launched with--enable-features=WebMCPTestingand driven from the agent side throughWebMCP.invokeTool/WebMCP.cancelInvocation— no shim, no injected script, and no direct call intopage functions.
Against the live HTTPS deployment — 11/11:
window.isSecureContext === true;typeof document.modelContext.registerTool === "function"with nothing injectedscan_repository,get_attention_items,get_check_details,compare_refs— no write tools, no issue creationscan_repository→status: Completed, and the visible dashboard grew from 228 to 1307 characters ofrendered report
get_attention_items→Completedwith zero new network requestsget_check_details("readme")→Completedwith zero new network requestsget_check_detailswith an unknown key → returnsavailable_keys, does not throwAgainst a local
uvicorn webapp.app:appwith aGITHUB_TOKENin the process environment — 21/21:everything above, plus
compare_refsrendering into the same dashboard, the human + agent shared-statesequence (agent scans A → human scans B through the page form → dashboard becomes B and the stale
comparison is cleared → agent scans A → dashboard returns to A), and
WebMCP.cancelInvocationproducingstatus: Canceled. Zero uncaught console errors in both runs.Why the heavy scenarios ran locally rather than on production
The deployment has no
GITHUB_TOKEN, so it uses GitHub's unauthenticated limit of 60 requests/hour peregress IP. One scan costs ~11 requests and one compare ~21, so the full 21-check sequence needs ~55 and
cannot complete reliably — Vercel spreads calls across several egress IPs, so a probe can succeed while the
run that follows it hits an exhausted one. Observed directly: a full production run returned
429 github_rate_limitedpartway through.The app degrades correctly under that limit — the 429 surfaces as
GitHub API rate limit exceeded. Try again later.with no traceback and no token, and propagates asstatus: Errorthrough the native WebMCP host rather than hanging.Still not verified
psf/requests)(
compare_refsand the shared-state sequence were exercised natively against a local backend only —see the rate-limit note above)
Known and deliberately out of scope:
GET /favicon.icoreturns 404 — the app has no favicon route.Changes since
4895d7ef8f36bc— private repositories no longer answer differently from inaccessible ones (see the Summary).Raised by an automated review on this PR; fixed test-first, with a test asserting the two responses are
byte-identical.
Re-run on
f8f36bc:pytest169 passed (one new test),ruff checkclean,mypy repopulse webappclean over 27 source files,
git diff --checkclean. CI on this head:test (3.11),test (3.12),Action smoke test,CodeQL,Analyze Python,Vercel— all green.The live-deployment results above were produced against the deployed
4895d7ebuild. They remainaccurate for
f8f36bc: the change only alters the response for a private repository, and the deploymenthas no
GITHUB_TOKEN, so it can see no private repository at all.A second finding in the same review — that
modelContextlives onnavigatorrather thandocument,so no tools register — was checked directly in the Chrome build used above and is not reproducible:
document.modelContextis an object whose constructor isModelContext,document.modelContext.registerToolis a function, and
navigator.modelContextisundefined. The browser's own WebMCP registry discoveredall four tools, which is what the run above exercises.
Remaining release gate
The two gates this PR was blocked on — the live HTTPS smoke and a native-browser WebMCP run — are now
closed. What remains open is the ChatGPT in-app browser run, and
compare_refsplus the shared-statesequence on the production URL specifically; both are blocked only by the missing
GITHUB_TOKENon thedeployment, not by application behaviour.