Skip to content

fix(app): reach the AI clients the UA gate and empty shell were losing - #10488

Merged
MarkusNeusinger merged 4 commits into
mainfrom
fix/ai-access-app
Aug 19, 2026
Merged

fix(app): reach the AI clients the UA gate and empty shell were losing#10488
MarkusNeusinger merged 4 commits into
mainfrom
fix/ai-access-app

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • Broadens the nginx crawler UA map: ~*grok now catches the bare Grok token xAI's fetcher was seen sending (it fell through to the empty SPA shell — the exact "insufficient relevant content" a real Grok session reported), and adds the documented Amazonbot and meta-externalagent tokens that the existing amzn-* / meta-externalfetcher entries never matched, plus best-effort Diffbot / Firecrawl.
  • Rewrites llms.txt for machine consumers: a REST API section (base URL, retrieval endpoints, OpenAPI link), the GCS render URL pattern (themes, responsive widths, WebP), CORS-open GitHub raw source URLs, a worked three-step "fetch one plot" recipe, and an honest note that prerendered page HTML is UA-gated while these URLs work for every client.
  • Serves anyplot.ai/llms-full.txt by proxying the API's generated whole-catalogue index (endpoint shipped in fix(api): open the machine interface AI agents were locked out of #10487) — previously the URL was a soft-404 answering 200 with the homepage shell.
  • Adds a <noscript> fallback to the SPA shell so JS-less clients not on the UA allowlist see pointers to llms.txt, llms-full.txt, the JSON API and GitHub instead of a completely empty page.
  • Extends the daily bot-serving monitor with the new UA tokens and an llms-full.txt assertion (timeout recomputed for 23 checks).

Plan

App-side half of the fix order from the 2026-08-19 AI-access audit (steps 4 and 5); the api-side half is #10487.

Merge order: #10487 first, then this PR. The llms-full.txt nginx location proxies to the API endpoint that #10487 adds — merging this first would 404 that one path until the API deploys (everything else in this PR is independent).

Test plan

  • yarn lint, yarn fm:check, yarn type-check, yarn test, yarn build all green; noscript block verified present in dist/index.html
  • nginx change follows the existing location = /llms.txt + @seo_proxy patterns verbatim (proxy headers, TLS verify depth 4); no local nginx syntax check possible in this environment (no docker) — flagging per the known Cloud-Build-deploy verification gap
  • After deploy: curl -A "Grok/1.0" https://<origin>/scatter-basic returns the prerendered title; curl https://anyplot.ai/llms-full.txt returns the catalogue index; curl -A "Amazonbot/0.1" … prerendered — the extended bot-serving-check covers all three daily

App-side half of the 2026-08-19 AI-access audit fixes (api side: #10487):

- nginx UA map: ~*grok (bare 'Grok' token fell through — the reported
  'insufficient relevant content'), Amazonbot, meta-externalagent (both
  documented tokens the amzn-*/meta-externalfetcher entries never matched),
  Diffbot, Firecrawl
- llms.txt rewritten for machine consumers: REST API section, GCS render
  URL pattern, GitHub raw URLs, worked fetch-one-plot recipe, honest note
  that prerendered HTML is UA-gated
- anyplot.ai/llms-full.txt proxied to the API's generated catalogue index
  (was a soft-404: SPA shell with HTTP 200)
- noscript fallback in the SPA shell pointing JS-less clients at llms.txt,
  llms-full.txt, the API and GitHub
- bot-serving-check: new UA tokens + llms-full.txt assertion, timeout
  recomputed for 23 checks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 18:35
MarkusNeusinger and others added 2 commits August 19, 2026 20:36
An agent asked for 'bar charts' had no documented way to search: the
filter endpoint's params (plot=, lib=, lang=, spec=, data=, dom=, feat=;
comma=OR, repeated=AND) are now in the API section with a live-verified
example, and the fetch-one-plot recipe points at grepping llms-full.txt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves machine/agent access to anyplot’s catalogue by fixing user-agent (UA) allowlisting gaps that caused some AI/crawler clients to receive the empty SPA shell, and by adding explicit machine-oriented discovery entrypoints (llms.txt + llms-full.txt) plus monitoring to prevent regressions.

Changes:

  • Extend nginx bot UA mapping (including broadening Grok matching and adding documented Amazon/Meta tokens) and add an nginx proxy for /llms-full.txt to the API-generated catalogue index.
  • Rewrite app/public/llms.txt to document reliable, UA-independent retrieval paths (API, GCS renders, GitHub raw source) with a concrete “fetch one plot” recipe.
  • Add a <noscript> fallback to the SPA shell and extend the daily bot-serving monitor to cover the new UA tokens and /llms-full.txt.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
CHANGELOG.md Documents the crawler allowlist fix, noscript fallback, and improved llms.txt + /llms-full.txt behavior under [Unreleased].
app/public/llms.txt Updates llms.txt to be machine-consumable with API/GCS/GitHub retrieval guidance and a worked example.
app/nginx.conf Expands UA bot mapping and adds location = /llms-full.txt proxying to the API endpoint.
app/index.html Adds a <noscript> fallback directing JS-less clients to machine-readable entrypoints.
.github/workflows/bot-serving-check.yml Extends synthetic monitoring coverage for additional UAs and asserts /llms-full.txt content.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/bot-serving-check.yml Outdated
Copilot AI review requested due to automatic review settings August 19, 2026 18:38
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/public/llms.txt:10

  • The llms.txt intro says non-allowlisted clients "get an empty app shell", but this PR adds a fallback in app/index.html, so JS-less clients (including most fetchers that don't execute JS) will now receive that fallback content. Consider rewording to clarify they won’t get the prerendered per-route HTML (they’ll get the SPA shell plus a minimal fallback), and that the API/raw URLs are UA-independent.
every plot page carries the runnable source code (MIT-licensed). catalog pages are served as
prerendered HTML to recognised crawler user agents — if your client is not on that list you
get an empty app shell, so prefer the API and raw URLs below: they work for every client,
no special user agent required.

Copilot review on #10488: the timeout comment claimed 23 checks; the
script performs 22 retried check() calls plus one non-retried
trailing-slash probe. Ceiling unchanged (38 min).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 19, 2026 18:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/public/llms.txt:21

  • The responsive image filename examples are inconsistent: it lists plot-light_400.png but then only _800, _1200 (missing the plot-light prefix). Elsewhere in the repo the convention is plot-light_800.png / plot-light_1200.png, so this is likely to mislead clients copying the URL pattern.
   `https://storage.googleapis.com/anyplot-images/plots/bar-error/python/seaborn/plot-light.png`
   (dark theme: `plot-dark.png`; smaller: `plot-light_400.png`, `_800`, `_1200`; WebP: `plot-light.webp`).

@MarkusNeusinger
MarkusNeusinger merged commit fc097cf into main Aug 19, 2026
10 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/ai-access-app branch August 19, 2026 18:54
MarkusNeusinger added a commit that referenced this pull request Aug 19, 2026
## Summary
- Fixes MCP `get_implementation` for the non-Python 28% of the
catalogue: the repository call carried no language, and its `python`
default made all 1,004 R/Julia/JavaScript implementations answer a false
"not found". The language now comes from the library's own DB row
(verified live for ggplot2/makie/d3/echarts/chartjs/muix/highcharts in
the audit).
- Fixes every discovery tool's `website_url`: `list_specs`,
`search_specs_by_tags` and `get_spec_detail` pointed at
`anyplot.ai/python/{spec}`, which 301s into a 404; all now point at the
hub. `get_spec_detail`'s per-implementation URLs — silently discarded by
`SpecDetailResponse` coercion — are attached after `model_dump` and
survive.
- Makes stateless MCP HTTP real: `stateless_http=True` is passed
explicitly to `http_app()`. The former
`os.environ.setdefault("FASTMCP_STATELESS_HTTP", "true")` ran after
fastmcp's `Settings` were instantiated and never engaged (reproduced
against fastmcp 3.4.5), leaving sessions pinned to one Cloud Run
instance with max-instances=3 and no affinity.
- Adds a `libraries` filter to `get_spec_detail` — the full 15-library
response carries ~0.5 MB of source, a context-window hazard for the
agents the tool serves.
- Gives the bot-served `/mcp` page its actual content (endpoint URL,
`claude mcp add` snippet, the six tools, JSON-API/llms-full.txt
fallbacks) — it rendered as title + one line for the exact audience it
exists for.
- Documents the `anyplot-images` bucket CORS policy (applied 2026-08-19)
in the project guide with the re-apply procedure — bucket metadata does
not deploy with the repo, so a rebuild would silently drop it.

## Plan
The targeted MCP pass from the 2026-08-19 AI-access audit — the final
step of the fix order after #10487 (api) and #10488 (app), as agreed.
Branch is rebased on the merged main.

## Test plan
- [x] `ruff check` + `ruff format --check` clean; `mypy api core` clean
- [x] 1,681 unit + 67 integration tests pass, including new tests:
language resolution from the library row (python and r), `website_url`
correctness on all three tools, per-implementation URLs surviving the
Pydantic roundtrip, the `libraries` filter, and the `/mcp` bot-page
content
- [x] fastmcp behavior verified directly: `settings.stateless_http`
stays `False` under the old env-setdefault route and `http_app` accepts
`stateless_http` explicitly
- [ ] After deploy: MCP `initialize` + `tools/call get_implementation
{spec_id: "audiogram-clinical", library: "ggplot2"}` returns code (was
"not found"); a request without `mcp-session-id` is accepted
(stateless); `curl -A GPTBot https://anyplot.ai/mcp` shows the tool list

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
MarkusNeusinger added a commit that referenced this pull request Aug 19, 2026
## Summary
- `llms.txt` and `robots.txt` carry UTF-8 punctuation (em dashes,
arrows) but were served as bare `text/plain` without a charset — a
strict client decodes them as Latin-1 mojibake (found by the
machine-files verifier in the 2026-08-19 live sweep; `llms-full.txt`,
generated by the API, already declares `charset=utf-8`).
- `charset utf-8;` in both nginx server blocks (main +
python.anyplot.ai). It applies to nginx's default `charset_types`;
proxied responses that already declare a charset keep theirs.
- The daily bot-serving monitor now asserts the charset on `llms.txt`
(timeout comment recomputed: 22 retried checks + two non-retried
probes).

## Plan
One of the three "for later" items from the AI-access verification
sweep, done on request. Independent of #10490 and #10491.

## Test plan
- [x] Config-only change following the existing block structure; no
local nginx syntax check possible here (no docker) — same known
verification gap as #10488, flagged per CLAUDE.md
- [ ] After merge + deploy: `curl -sI https://anyplot.ai/llms.txt | grep
-i content-type` → `text/plain; charset=utf-8`; same for `robots.txt`;
the extended daily monitor guards it from then on

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants