Skip to content

fix(mcp): make every tool's output true for the whole catalogue - #10489

Merged
MarkusNeusinger merged 5 commits into
mainfrom
fix/mcp-ai-access
Aug 19, 2026
Merged

fix(mcp): make every tool's output true for the whole catalogue#10489
MarkusNeusinger merged 5 commits into
mainfrom
fix/mcp-ai-access

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

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

  • ruff check + ruff format --check clean; mypy api core clean
  • 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
  • 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

MarkusNeusinger and others added 3 commits August 19, 2026 20:55
Targeted MCP pass from the 2026-08-19 AI-access audit:

- get_implementation resolves the language from the library's own DB row;
  the repository's language_id='python' default made all 1,004 R/Julia/JS
  implementations (28% of the catalogue) answer a false 'not found'
- website_url in list_specs / search_specs_by_tags / get_spec_detail
  pointed at anyplot.ai/python/{spec}, which 301s into a 404; now the hub
- get_spec_detail attaches per-implementation website_urls AFTER
  model_dump - SpecDetailResponse coercion silently dropped them before -
  and gains an optional 'libraries' filter so a 15-library spec's ~0.5 MB
  response can be trimmed to the libraries actually asked for
- stateless MCP HTTP is now real: passed explicitly to http_app();
  the former os.environ.setdefault ran after fastmcp's Settings were
  instantiated and never engaged, leaving sessions instance-pinned

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CORS policy applied 2026-08-19 (AI-access audit) is bucket metadata,
not code — without this runbook entry a bucket rebuild would silently
drop it and cross-origin image embedding would break again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The page whose entire audience is AI agents rendered as title plus a
one-line description; it now carries the endpoint URL, a setup snippet,
the six tools and fallback pointers (JSON API, llms-full.txt). Includes
the changelog entries for the whole MCP pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 18:58
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 fixes MCP correctness and discoverability across the full anyplot catalogue by (1) resolving non-Python implementations correctly, (2) repairing broken website_url outputs, and (3) making FastMCP stateless HTTP actually take effect for horizontal scaling. It also adds a libraries filter to reduce get_spec_detail payload size, improves the bot-rendered /mcp page content, and documents the anyplot-images bucket CORS policy.

Changes:

  • Fix MCP tool outputs: correct language resolution for get_implementation, correct hub website_url for discovery tools, and preserve per-implementation URLs in get_spec_detail.
  • Enable stateless MCP HTTP mode by explicitly passing stateless_http=True to http_app().
  • Improve agent-facing documentation and safety: add libraries filtering, enrich /seo-proxy/mcp bot HTML, and document GCS CORS policy + reapply steps.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
api/mcp/server.py Fixes MCP tool correctness (language resolution, URLs), adds libraries filter, preserves per-implementation URLs post-Pydantic coercion.
api/main.py Enables stateless MCP HTTP mode via explicit http_app(..., stateless_http=True).
api/routers/seo.py Adds a real bot-visible /mcp body with endpoint, setup snippet, and tool list.
tests/unit/api/mcp/test_tools.py Adds/updates unit coverage for hub URLs, per-implementation URLs, libraries filter, and non-Python language resolution.
tests/unit/api/test_routers.py Adds bot-page test ensuring /seo-proxy/mcp includes connection instructions and tool names.
agentic/docs/project-guide.md Documents the anyplot-images bucket CORS config and re-apply procedure.
CHANGELOG.md Records the MCP correctness fixes, stateless HTTP fix, /mcp bot-page improvements, and CORS documentation under [Unreleased].

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

Comment thread tests/unit/api/test_routers.py Outdated
Comment thread api/mcp/server.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!

… semantics

Copilot review on #10489: the /mcp bot-page test now asserts every
documented tool name, and the get_spec_detail docstring states that an
empty libraries list means 'all' — agents commonly send [] for 'no
filter', and answering it with zero implementations would read as a
missing spec.

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

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

Suppressed comments (2)

tests/unit/api/mcp/test_tools.py:283

  • The docstring says the response is “multi-megabyte”, but the surrounding code/comments and CHANGELOG describe this payload as ~0.5 MB. Keeping the test description accurate will avoid overstating the size and confusing future readers.
    """The optional libraries filter trims a multi-megabyte response to what's asked for."""

api/routers/seo.py:928

  • The claude mcp add snippet here uses a different argument order than the human /mcp page (app/src/pages/McpPage.tsx:147). For bots/agents copying this, it’s safer to match the documented CLI form (claude mcp add anyplot --transport http …).
    "<pre><code>claude mcp add --transport http anyplot https://api.anyplot.ai/mcp/</code></pre>"

@MarkusNeusinger
MarkusNeusinger merged commit 0419557 into main Aug 19, 2026
10 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/mcp-ai-access branch August 19, 2026 19:15
@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Added one commit from the post-deploy live-verification sweep (49 checks across crawler UAs, machine files, REST, images/CORS, JSON-LD, MCP — zero failures on everything #10487/#10488 deployed): og-card CORS for foreign origins, llms-full.txt lines as {language}/{library}, no more /seo-proxy prefix in error bodies, and MCP serverInfo reporting the app version. All gates re-run green (1,750 tests).

@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Correction: the verification-sweep commit mentioned above raced this PR's squash-merge and never landed here — it ships as #10490 (recovered by cherry-picking onto the merged main; the stale recreated branch was deleted).

MarkusNeusinger added a commit that referenced this pull request Aug 19, 2026
…10490)

## 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
- [x] `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

---------

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