Skip to content

Recover MCP Apps notebook URL when hosts drop _meta#212

Merged
rhennigan merged 5 commits into
mainfrom
bugfix/mcp-apps-notebook-workaround
Jul 8, 2026
Merged

Recover MCP Apps notebook URL when hosts drop _meta#212
rhennigan merged 5 commits into
mainfrom
bugfix/mcp-apps-notebook-workaround

Conversation

@rhennigan

@rhennigan rhennigan commented Jul 8, 2026

Copy link
Copy Markdown
Member

Problem

MCP Apps viewers embed a cloud notebook by reading notebookUrl from the tool result's _meta/structuredContent. Some hosts strip both from tool results (ext-apps#696), so the viewer never receives the URL and can only show the plain text/image fallback instead of the interactive notebook.

Solution

For UI-enhanced cloud-notebook results, the tool now also embeds the URL in the one channel the host does not strip — the tool result content — inside a self-describing marker:

<internal>This tool call was displayed to the user as an interactive notebook, which they can already see. The URL below only renders that notebook; you do not need to read, repeat, visit, or otherwise use it. <url>https://.../<id>.nb</url></internal>

The wrapper text is addressed to the model (the notebook is already shown; don't act on the URL). Each viewer's findNotebookUrl falls back to extractNotebookUrlMarker to pull the URL out locally — no server round-trip — and strips the whole <internal>...</internal> block from the display so the user never sees it.

Why two commits

The first commit implemented this via a server round-trip: an opaque <nbid> id in the content, resolved through an app-initiated resources/read. That turned out not to work on the target hosts — a host that drops _meta also does not forward app resources/read, answering with JSON-RPC -32601 "Method not found". The second commit pivots to the self-contained marker above and removes the resolver. The history is kept intentionally to record what was tried.

Changes

  • Server (Kernel/UIResources.wl): makeNotebookUIResult builds the result and appends the URL marker for cloud URLs (inline notebooks are delivered via _meta only).
  • Viewers (evaluator-viewer, wolframalpha-viewer, notebook-viewer): findNotebookUrlextractNotebookUrlMarker fallback; stripAgentOnlyText removes the marker; notebook-viewer uses it as a result-path fallback (it normally gets its URL from the tool input).
  • Tests (Tests/MCPApps.wlt) and docs (docs/mcp-apps.md) updated.

Test plan

  • Tests/MCPApps.wlt, Tests/WolframAlpha-UI.wlt, Tests/WolframLanguageEvaluator-UI.wlt, Tests/EvaluatorSessions.wlt — all pass (151/151 in-process).
  • End-to-end against real tool output: extracted URL matches _meta.notebookUrl; marker stripped from the user-facing display; real output preserved.
  • All three viewer HTML files pass a JS syntax check; CodeInspector clean on UIResources.wl.
  • Verify in a real host that drops _meta (e.g. Claude Desktop) that the notebook now embeds. (The resources/read failure this replaces was observed there; the marker path depends only on tool content being delivered, which is.)

Note: Tests/StartMCPServer.wlt spawns wolframscript subprocesses and can't run in the sandbox used here; it should run in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_015Y8TNLJ63zJor51rq99TqQ

rhennigan and others added 2 commits July 8, 2026 15:46
Some MCP hosts strip _meta and structuredContent from tool results
(ext-apps#696), so the viewer apps never receive the notebookUrl and can
only show the text/image fallback.

Cloud-notebook results now also carry an opaque <nbid>HEXID</nbid> marker
in their (non-dropped) text content, where HEXID is the deployed
notebook's base file name. When a viewer gets a result with no
notebookUrl, it extracts the id and issues a resources/read for
ui://wolfram/notebook-url/<id>; the server reconstructs the cloud URL
from the id statelessly (a CloudObject URL is derived locally from the
base name and the user's cloud path) and returns it as the resource
text, which hosts do not strip. resources/read is used instead of
tools/call because the spec lets apps read any resource URI without the
resolver becoming a model-visible tool.

Server:
- makeNotebookUIResult builds the UI result and appends the marker
  (cloud URLs only; inline notebooks have no reconstructable id)
- readUIResource routes ui://wolfram/notebook-url/<hexId> to
  readNotebookURLResource, validating the id is hex before rebuilding

Viewers: strip the marker in stripAgentOnlyText, extract the id, resolve
via resources/read, then embed; a result-sequence guard discards a stale
resolve. notebook-viewer applies the same recovery as a result-path
fallback (it normally gets its URL from the tool input).

Adds tests in MCPApps.wlt and documents the workaround in mcp-apps.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y8TNLJ63zJor51rq99TqQ
The previous commit recovered the URL by having the app read
ui://wolfram/notebook-url/<hexId> via resources/read. That does not work
on the hosts we care about: a host that drops _meta/structuredContent
also does not forward app-initiated resources/read, answering it with
JSON-RPC -32601 "Method not found", so the id -> URL round-trip never
completes.

Switch to a self-contained approach. The one channel that survives is the
tool result content, so makeNotebookUIResult now embeds the URL directly
in the (non-dropped) text content inside an
<internal>...<url>...</url></internal> marker. The wrapper text is
addressed to the model (the notebook is already shown; do not use the
URL); the viewers extract the URL locally via findNotebookUrl ->
extractNotebookUrlMarker with no server round-trip, and strip the whole
marker from the display.

Removes the now-dead server resolver (readNotebookURLResource,
resolveNotebookURLFromID, the hex-id helpers, the ui://wolfram/notebook-url
routing) and reverts readUIResource to its registry-only form, along with
the client resolveNotebookUrl/latestResultSeq machinery. Net -222 lines.

Verified end-to-end against real tool output: the extracted URL matches
_meta.notebookUrl, the marker is stripped from the user-facing display,
and the real output is preserved. Updates tests and docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y8TNLJ63zJor51rq99TqQ
Copilot AI review requested due to automatic review settings July 8, 2026 16:29

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 MCP Apps notebook embedding reliability in hosts that strip _meta and structuredContent from tool results by adding a content-based fallback: cloud notebook URLs are embedded into the tool result content as a self-describing <internal>…<url>…</url></internal> marker that viewers can extract locally and then strip from user-facing text.

Changes:

  • Server-side: add makeNotebookUIResult to include notebookUrl in _meta/StructuredContent and (for cloud URLs) append a URL marker into content.
  • Viewer-side: add extractNotebookUrlMarker fallback to recover the URL from content when _meta/structuredContent are missing, and strip <internal>…</internal> blocks from displayed text.
  • Tests/docs: add coverage for marker creation/extraction behavior and document the dropped-_meta workaround.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Tests/MCPApps.wlt Adds tests validating marker appending, extractability, and $Failed behavior.
Kernel/UIResources.wl Implements makeNotebookUIResult and marker helpers used by UI-enhanced tool results.
Kernel/Tools/WolframLanguageEvaluator.wl Switches evaluator UI result construction to makeNotebookUIResult.
Kernel/Tools/WolframAlpha.wl Switches WolframAlpha UI result construction to makeNotebookUIResult.
Kernel/CommonSymbols.wl Declares makeNotebookUIResult as a shared symbol in the Common context.
docs/mcp-apps.md Documents the marker-based URL recovery behavior and constraints.
Assets/Apps/evaluator-viewer.html Adds marker extraction fallback + strips <internal>…</internal> from rendered text.
Assets/Apps/wolframalpha-viewer.html Adds marker extraction fallback + strips <internal>…</internal> from rendered text.
Assets/Apps/notebook-viewer.html Adds result-based URL recovery fallback when tool input isn’t delivered.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Kernel/Tools/WolframLanguageEvaluator.wl
Comment thread Kernel/Tools/WolframAlpha.wl
Comment thread Kernel/UIResources.wl
rhennigan and others added 2 commits July 8, 2026 16:41
The tool-result comments described an opaque <nbid> content marker, but the
implementation embeds the URL in an <internal>...<url>...</url></internal>
marker (see notebookURLMarkerText in UIResources.wl). Align the comments in
both tool files with the actual marker format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTmWMZ5thpvab5pgqUX4LJ
makeNotebookUIResult populates both _meta and structuredContent for every
deployed value, so 'delivered via _meta only' was inaccurate. Note that
inline notebooks go through _meta/structuredContent and are never embedded
in the content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTmWMZ5thpvab5pgqUX4LJ
Copilot AI review requested due to automatic review settings July 8, 2026 16:41

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

Comment thread Kernel/UIResources.wl
Comment thread Assets/Apps/evaluator-viewer.html
Comment thread Assets/Apps/wolframalpha-viewer.html
Comment thread Assets/Apps/notebook-viewer.html
extractNotebookUrlMarker returned the FIRST <internal>...<url>...</url>
</internal> marker in the content. Because the text content is built from
tool output (stringResult) that can carry externally-sourced text, a crafted
marker injected earlier in the content could override the server-appended URL
when a host drops _meta/structuredContent, causing the viewer to load an
attacker-chosen notebook.

The server always appends its marker as the final content item
(appendNotebookURLMarker), so scan for the LAST marker in document order
instead. This also holds if a host concatenates the text items, since the
trusted marker remains last. Legitimate single-marker results are unchanged.

Applied identically to all three viewers (evaluator, notebook, wolframalpha).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YoEwcu8kbrESPvQFsXyxp
Copilot AI review requested due to automatic review settings July 8, 2026 16:57

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

Comment thread Assets/Apps/evaluator-viewer.html
Comment thread Assets/Apps/wolframalpha-viewer.html
Comment thread Assets/Apps/notebook-viewer.html
Comment thread Assets/Apps/notebook-viewer.html
@rhennigan rhennigan merged commit 17babd0 into main Jul 8, 2026
2 checks passed
@rhennigan rhennigan deleted the bugfix/mcp-apps-notebook-workaround branch July 8, 2026 18:41
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