feat: serve React UI views from Python toolsets#11
Merged
Conversation
A toolset can ship a view: a build-time HTML bundle a UI-capable host
renders in an iframe, fed the tool's structuredContent. Opt in with a
VIEWS ({tool_name: view_id}) export and a bundle at
<package>/views/<view_id>.html.
The runtime does two standard-MCP things: serves each view as a
ui://<toolset>/<view_id> resource and stamps the owning tool's _meta with
that URI (the mcp-ui / Apps-SDK convention). Nothing executes at call
time, so the runtime stays pure-Python. VIEWS entries and bundles are
validated at build_server time, the same gate as the ToolResult contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agent reads each connected toolset's ui:// view bundles via get_resources and matches them to tools by their _meta — standard MCP, no bespoke endpoints. A view renders in a sandboxed iframe (srcDoc, opaque origin, allow-scripts only), fed the tool's structuredContent; the iframe's postMessage interactions come back as user messages via sendUserMessage, so the loop advances the chat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A worked example of the view round-trip against the public Microsoft Planetary Computer STAC catalog. search_collections returns a thumbnail gallery; picking one drives show_map, which renders the collection's data as map tiles (via the catalog's mosaic tiler) over its extent. The gallery and map views are built from ui/ (Vite + React) into the package's views/ dir; interactions post back to advance the chat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new-toolset --with-ui scaffolds a Vite + React ui/ with an example view - build-views builds and typechecks every toolset UI - Dockerfile gains a node build stage that inlines views into the wheel - CI ui job typechecks, bundles and validates VIEWS<->bundle wiring - document the view contract in README and CLAUDE - pin all GitHub Actions to commit SHAs at their latest majors Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Short lil demo of what the stac-explorer looks like. Screen.Recording.2026-07-23.at.09.53.08.mov |
yellowcap
approved these changes
Jul 23, 2026
yellowcap
left a comment
Member
There was a problem hiding this comment.
Implementation looks solid, works well locally, except the planetary computer tiles. They are requested but fail. I assume that is because I dont have a key or so? Is this expected to work without keys? If not, maybe add to .env.example
Contributor
Author
No, it should work without keys, it's all public. Perhaps the dataset didn't have tiles? Did you get obvious errors to share? @yellowcap |
Member
|
ok all good, it was blocked by a browser plugin 😅 |
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 I am changing
Toolsets can now ship UI views — small frontend components (a map, a
gallery, a chart) that a UI-capable MCP host renders inline in the chat and
feeds the tool's result. This works in Claude web / mcp-ui clients and in this
repo's own Chainlit agent, so a tool answer can be an interactive map instead of
a wall of text, and clicking inside it drives the next tool call.
Crucially, the runtime stays pure-Python. A view is a build-time HTML bundle
(React/Vite); nothing new executes at call time. Adopting it is one export
(
VIEWS = {tool_name: view_id}) plus a bundle — the credential and structured-return contracts are unchanged, and a plain MCP client still gets the tool's
text/
structuredContentas before (views are progressive enhancement).Includes a worked example (
stac-explorer) against the public PlanetaryComputer STAC catalog: search → thumbnail gallery → pick → map with real data
tiles over the collection's extent.
How I did it
packages/mcp-runtime/src/mcp_runtime/views.py(new) — the whole runtimesurface: serve each view as a
ui://<toolset>/<view_id>MCP resource andstamp the owning tool's
_metawith that URI (the mcp-ui / Apps-SDKconvention).
VIEWSand bundles are validated atbuild_servertime.packages/mcp-runtime/src/mcp_runtime/server.py— wire the two calls intobuild_server(~3 lines).packages/mcp-agent/src/mcp_agent/web.py+public/elements/McpView.jsx— the Chainlit agent reads bundles via
get_resourcesand matches them totools by
_meta(standard MCP, no bespoke endpoints), rendering each in asandboxed iframe (
srcDoc, opaque origin,allow-scriptsonly). Interactionspost back as user messages so the loop advances the chat.
toolsets/stac-explorer/(new) — the example: two async tools + a Vite +React
ui/(host bridge inui/src/host.ts);show_mapreturns a georef'dXYZ
tile_urlfrom the catalog's mosaic tiler.new-toolset --with-uiscaffolds aui/;scripts/build-viewsbuilds/typechecks all UIs; the
Dockerfilegains a node stage that inlinesviews into the wheel; a CI
uijob typechecks, bundles and validatesVIEWS↔bundle wiring. GitHub Actions pinned to SHAs at latest majors.README.md, notes inCLAUDE.md.How you can test it
Automated:
./scripts/test(view runtime tests inpackages/mcp-runtime/tests/test_views.py) and./scripts/build-views(typecheck + bundle every UI).
End to end (needs node + a chat provider, e.g.
uv add langchain-openaiwith
PROVIDER_MODEL/PROVIDER_API_KEYset):In the chat: "find denver land cover data" → a thumbnail gallery renders →
click Show on map → the model calls
show_mapand a Leaflet map renders theclassified land-cover tiles over Colorado. Try "show me hawaii datasets" for
a collection without a tiler (the map falls back to the extent box, gracefully).
Standard-MCP check (no model): with the server running, tool
_metaand theui://resources are visible over plain MCP —uv run mcp-cli listshows thetools, and a
MultiServerMCPClient.get_resources()call returns the bundles.🤖 Generated with Claude Code