Skip to content

fix(api): four leftovers from the 49-check live-verification sweep - #10490

Merged
MarkusNeusinger merged 3 commits into
mainfrom
fix/ai-access-verify-followups
Aug 19, 2026
Merged

fix(api): four leftovers from the 49-check live-verification sweep#10490
MarkusNeusinger merged 3 commits into
mainfrom
fix/ai-access-verify-followups

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

  • og cards now send Access-Control-Allow-Origin: * to foreign origins — the global CORSMiddleware answers only the site's own allowlist, so chat UIs fetching a card in-page were blocked (the same in-page-fetch failure class the GCS bucket CORS fix removed). setdefault keeps CORSMiddleware's own header for allowlisted origins.
  • llms-full.txt lines list implementations as {language}/{library} so the GCS render URL template is instantiable from the file alone; the bare library id forced a second /specs/{id} call per image.
  • Error responses on prerendered pages echo the public path — crawlers hitting a dead spec URL were shown the internal /seo-proxy/{slug} routing prefix in the JSON body.
  • MCP serverInfo reports the app version via FastMCP(version=APP_VERSION) — it showed fastmcp's package version 3.4.5 while /health said 3.1.0.

Plan

Follow-ups found by the post-deploy live-verification sweep (49 checks across crawler UAs, machine files, REST, images/CORS, JSON-LD, MCP; zero failures on everything already deployed). This commit was originally pushed to the #10489 branch but raced its squash-merge — recovered per the repo's merge-race procedure by cherry-picking onto fresh main; the recreated stale branch was deleted.

Test plan

  • ruff check + ruff format --check clean; mypy api core clean; 1,750 unit + 67 integration tests pass (new tests: og ACAO for a foreign Origin, public path in 404 bodies, llms-full {language}/{library} format, MCP version == APP_VERSION)
  • After merge + deploy: curl -sI -H "Origin: https://chat.openai.com" https://api.anyplot.ai/og/home.png | grep -i access-control-allow-origin*; curl https://anyplot.ai/llms-full.txt | head shows {language}/{library} entries; MCP initialize serverInfo.version == 3.1.0

- og cards send Access-Control-Allow-Origin: * to foreign origins (the
  global CORSMiddleware only answers the site's own allowlist, so chat
  UIs fetching the card in-page were blocked - the same failure class
  the GCS bucket CORS fix removed)
- llms-full.txt lines list implementations as {language}/{library} so
  the render URL template is instantiable from the file alone (the bare
  library id forced a second /specs/{id} call per image)
- error responses on prerendered pages echo the public path, never the
  internal /seo-proxy prefix (crawlers saw the internal routing on every
  dead spec URL)
- MCP serverInfo reports the app version via FastMCP(version=...) - it
  showed fastmcp's package version 3.4.5 while /health said 3.1.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 19:25
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 applies four small API fixes discovered during a post-deploy live-verification sweep, improving cross-origin OG image consumption, the standalone usability of llms-full.txt, the correctness of error-path reporting for prerendered routes, and MCP server version reporting.

Changes:

  • Add Access-Control-Allow-Origin handling for /og/* responses to support cross-origin in-page fetches.
  • Update llms-full.txt to list implementations as {language}/{library} so render URLs can be derived without extra API calls.
  • Ensure JSON error bodies reflect the public (non-/seo-proxy) path, and set MCP serverInfo.version to the app version.

Reviewed changes

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

Show a summary per file
File Description
api/main.py Adds OG CORS header logic in the cache-header middleware.
api/routers/seo.py Changes llms-full.txt format to {language}/{library} and updates header docs accordingly.
api/exceptions.py Translates /seo-proxy/* internal paths to public paths in JSON error responses.
api/mcp/server.py Sets FastMCP server version to APP_VERSION for correct MCP serverInfo.
tests/unit/api/test_seo_helpers.py Updates mocks and asserts for new llms-full.txt format.
tests/unit/api/test_routers.py Adds/updates tests for llms-full.txt, OG CORS header, and public-path error bodies.
tests/unit/api/mcp/test_tools.py Adds protocol-level test asserting MCP server version matches APP_VERSION.
CHANGELOG.md Documents the live-verification follow-up fixes under Unreleased.

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

Comment thread api/main.py
@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 review on #10490: the ACAO block sat behind the method/status
guard, so a cross-origin caller of a 404 og card got an opaque response
and could not even read the status. Moved before the guard, with a test.

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

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 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

api/exceptions.py:112

  • _public_path() treats any path starting with "/seo-proxy" as internal. That will also rewrite unrelated paths like "/seo-proxy-foo" (if they ever exist), which would produce an incorrect public path in error JSON. Since all intended internal routes are either exactly "/seo-proxy" or start with "/seo-proxy/", make the prefix check strict to avoid accidental rewrites.
    path = request.url.path
    if path.startswith("/seo-proxy"):
        return path.removeprefix("/seo-proxy") or "/"
    return path

@MarkusNeusinger
MarkusNeusinger merged commit bcd61c7 into main Aug 19, 2026
10 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/ai-access-verify-followups branch August 19, 2026 19:53
MarkusNeusinger added a commit that referenced this pull request Aug 19, 2026
## Summary
- The bot renderings of `/libraries`, `/stats`, `/about`, `/legal` and
`/palette` were 29–59-word title+description stubs — a crawler following
llms.txt's own link descriptions ("the fifteen supported plotting
libraries", …) found none of the promised content (AI-access audit
2026-08-19; confirmed by the machine-files verifier in the post-deploy
sweep).
- Each page now derives its body from its single source of truth, so
none can drift: `/libraries` renders the full registry from
`core/constants.py` (grouped by language, with versions, descriptions,
doc links); `/stats` shows the live catalogue counts via the same cached
`/stats` data the startup prewarm fills (registry-derived fallback
without a DB); `/about` carries the pipeline story mirroring
`AboutPage.tsx` with registry-derived counts; `/legal` carries the
operator/privacy/hosting facts mirroring `LegalPage.tsx`; `/palette`
lists all 8 categorical hexes plus the amber/neutral/muted anchors
straight from `core/palette.py`.

## Plan
One of the three "for later" items from the AI-access verification
sweep, done on request. Independent of #10490 (different `seo.py` hunks)
and of the nginx charset PR.

## Test plan
- [x] `ruff check` + `ruff format --check` clean; `mypy api core` clean;
1,751 unit + 67 integration tests pass (new/updated tests assert the
registry list per language, the palette hexes from the module,
operator/privacy facts, the pipeline copy, and both stats bodies — live
counts with DB, registry fallback without)
- [ ] After merge + deploy: `curl -A Googlebot
https://anyplot.ai/libraries` lists all 15 libraries; `/stats` shows the
live counts; `/palette` carries `#009E73`

---------

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