Skip to content

feat(mcp): interactive recent-meetings UI widget (MCP Apps / SEP-1865)#1495

Merged
r3dbars merged 1 commit into
mainfrom
feat/mcp-ui-recent-meetings
Jul 7, 2026
Merged

feat(mcp): interactive recent-meetings UI widget (MCP Apps / SEP-1865)#1495
r3dbars merged 1 commit into
mainfrom
feat/mcp-ui-recent-meetings

Conversation

@r3dbars

@r3dbars r3dbars commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

Adds the first interactive UI surface to Transcripted's MCP server: a
show_recent_meetings tool that renders a card list of recent meetings inside
a rendering-capable agent client — each card with a ▶ Play control for the
meeting audio and a View raw transcript toggle. Not a wall of text; real,
renderable UI in the chat client.

This is the minimal first slice of the just-merged
MCP Apps design (#1491), Interpretation 2.
It reuses the existing read-only data surface (list_meetings / read_meeting /
the audio/<stem>_audio/ bundles) and adds a UI layer on top — no new data
plumbing.

How it works (MCP Apps / SEP-1865)

Built against the official MCP Apps extension (io.modelcontextprotocol/ui,
spec 2026-01-26 — the standardized successor to mcp-ui):

  • Registers ui://transcripted/recent-meetings.html (MIME text/html;profile=mcp-app),
    served via resources/read.
  • show_recent_meetings links to it via _meta.ui.resourceUri and
    _meta["openai/outputTemplate"] (ChatGPT/Apps SDK alias).
  • The tool result carries three things so it renders across host families:
    a text fallback, the data-baked widget HTML as an inline embedded
    resource
    (mcp-ui rawHtml pattern), and structuredContent (the meeting
    model) for hosts that push tool output into the rendered template.

Privacy: nothing leaves this Mac

The widget is fully self-contained — inline CSS/JS, no CDN, no network. Audio
and transcripts are read from the local capture library and travel with the
widget over the local stdio transport to the local client. No remote host is in
the loop. The sandbox runs under the MCP Apps default CSP
(connect-src 'none'; media-src 'self' data:), so the widget cannot reach the
network; audio plays from a data:audio/mp4;base64,… URI, which the default
policy allows. This matches the app's "your content never leaves this Mac"
posture exactly.

Renders today in: … / needs client support for: … (honest)

MCP Apps shipped 2026-01-26 and host support is uneven. Grounded in the spec +
client docs:

Client Renders inline today?
ChatGPT (Apps SDK) Yes
VS Code (Copilot MCP) Yes
Cursor ≥ 2.6 Yes (baked-in data path renders even with its known _meta-stripping bug)
Goose Yes
Claude Desktop / claude.ai Announced, but unverifiedext-apps#671 reports some builds fetch the resource but paint no iframe. Test on your build.
Claude Code (CLI) No — terminal host; shows the text fallback, not inline UI

So: renders inline today in ChatGPT, VS Code, Cursor, and Goose. Needs client
support in Claude Desktop/web. The Claude Code CLI shows the text list
(by
design — no iframe surface).

Did I get it rendering in a real client?

No local desktop client reliably renders MCP Apps today (Claude Code is a
terminal; Claude Desktop's rendering is contested per #671), so I verified where
it counts:

  1. Standalone in a real browser engine — the same sandboxed-iframe surface a
    rendering host uses. Cards render, ▶ Play injects a working <audio>
    player (real duration), View raw transcript expands the real transcript,
    light + dark both clean, zero console errors. Driven against 162 real
    meetings
    (106 with audio).
  2. Full stdio JSON-RPC pathinitializeresources/list
    tools/listtools/call show_recent_meetingsresources/read, asserting
    the capabilities, _meta, embedded resource, and structuredContent shapes.

docs/mcp-ui-recent-meetings.md documents the per-client matrix and the exact
steps to render it in a rendering-capable client.

Tests

  • 11 new tests (RecentMeetingsWidgetTests): builder (transcript+audio embed,
    Mix-over-Mic preference, size-cap fallbacks, budget), HTML self-containment +
    theme-awareness + script-breakout neutralization, and the tool/resource wire
    shape.
  • Full MCP package suite green: 170 tests, 0 failures.
  • repo-hygiene checks (duplicate-declaration, agent-preflight) pass locally.

Known limitations (minimal slice)

  • Inline audio is size-capped (per-meeting + per-widget budgets); longer
    recordings fall back to showing their local audio/<stem>_audio/ path. The next
    step is lazy fetch over the postMessage bridge (get_meeting_audio on a play
    click), which scales past the inline budget but needs a host that forwards
    widget→server tool calls.
  • One widget (recent meetings). Transcript browser / person card are follow-ups.

🤖 Generated with Claude Code

Expose an interactive "recent meetings" widget from the Transcripted MCP
server via the MCP Apps extension (io.modelcontextprotocol/ui, SEP-1865):
a card list of the N most recent meetings, each with inline audio playback
and a raw-transcript view, rendered inside a rendering-capable agent client.

- show_recent_meetings tool returns a self-contained text/html;profile=mcp-app
  widget as an inline embedded resource + structuredContent + a text fallback,
  linked via _meta.ui.resourceUri and openai/outputTemplate.
- ui://transcripted/recent-meetings.html registered as a readable resource.
- Reuses the existing meeting/transcript/audio access; adds a UI layer, not
  new data plumbing. Audio is base64-inlined (size-capped) and plays from a
  data: URI under the MCP Apps default sandbox CSP.
- Fully local: no network, no CDN. Local audio/transcripts travel with the
  widget over the local stdio transport; nothing leaves the Mac.
- Theme-aware (light/dark). 11 new tests; full MCP suite green (170).

Renders inline today in ChatGPT, VS Code, Cursor, and Goose. Claude
Desktop/web is announced but unverified on some builds; the Claude Code CLI
shows the text fallback (no iframe surface). See docs/mcp-ui-recent-meetings.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@r3dbars r3dbars merged commit 21b3f19 into main Jul 7, 2026
6 checks passed
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.

1 participant