Skip to content

feat: add MCP Apps support (renders_ui DSL, weather dashboard demo)#212

Open
crisnahine wants to merge 1 commit intoseuros:masterfrom
crisnahine:mcp-apps
Open

feat: add MCP Apps support (renders_ui DSL, weather dashboard demo)#212
crisnahine wants to merge 1 commit intoseuros:masterfrom
crisnahine:mcp-apps

Conversation

@crisnahine
Copy link
Copy Markdown
Contributor

@crisnahine crisnahine commented Apr 15, 2026

implements MCP Apps support per ext-apps draft spec (2026-01-26). closes #202.

changes

  • ActionMCP::MIME_TYPE_APP_HTML constant (text/html;profile=mcp-app)
  • renders_ui "ui://..." class macro on Tool. validates URI scheme and visibility values at class-load time. emits nested _meta.ui.resourceUri only, no legacy flat _meta["ui/resourceUri"] anywhere (pinned by a negative test).
  • Capability#client_supports_ui? instance helper. key-presence check on capabilities.extensions["io.modelcontextprotocol/ui"]. lives on Capability so any tool/prompt/resource_template can call it.
  • WeatherDashboardTemplate at ui://weather/dashboard with self-contained HTML. csp connectDomains and prefersBorder are emitted on the resource content (resources/read), following the apps.mdx canonical examples.
  • existing WeatherTool gets renders_ui. text and structured output are preserved.

tests

test/action_mcp/mcp_apps_test.rb, 13 tests:

  • renders_ui happy path with and without visibility:
  • composition with meta(...) on orthogonal keys
  • negative test pinning that the deprecated flat ui/resourceUri key is never emitted
  • URI validation: non-String, non-ui://, empty
  • visibility validation: unknown values
  • client_supports_ui?: extension present, extension absent, no session, nil capabilities
  • weather tool/template end-to-end

spec compliance

  • _meta.ui.resourceUri nested only, per apps.mdx Tool Metadata section
  • tools/list filtering by visibility is host-side per apps.mdx, no server-side filter added
  • csp/permissions/prefersBorder are emitted on the resources/read content payload, following the apps.mdx canonical examples

notes

  • rebased onto current master (clean, no conflicts)
  • bin/rails test: 794 tests, 0 failures
  • bin/rubocop: clean

@crisnahine crisnahine force-pushed the mcp-apps branch 2 times, most recently from 3aca819 to 4113d32 Compare April 15, 2026 10:34
@crisnahine crisnahine marked this pull request as ready for review April 15, 2026 10:37
@crisnahine
Copy link
Copy Markdown
Contributor Author

rebased onto master, CI green on the new base.

three corrections since the previous push:

  • dropped the class-level meta ui: block on the dashboard template. csp/prefersBorder are content-only per apps.mdx, they belong on resolve, not on the listing.
  • added validation-branch coverage on renders_ui (non-String URI, non-Array visibility, empty array, unknown values, duplicates) plus the no-session and nil-capabilities paths on client_supports_ui?. 14 tests now.
  • HTML entity for the sun glyph in the demo HEREDOC instead of unicode escape.

per ext-apps draft spec (2026-01-26). closes seuros#202.

- ActionMCP::MIME_TYPE_APP_HTML constant for text/html;profile=mcp-app
- renders_ui "ui://..." class macro on Tool with optional visibility
  kwarg. validates URI scheme and visibility values at class-load
  time. emits nested _meta.ui.resourceUri only, no legacy flat
  _meta["ui/resourceUri"] anywhere (pinned by a negative test).
- Capability#client_supports_ui? instance helper. key-presence check
  on capabilities.extensions["io.modelcontextprotocol/ui"]. lives on
  Capability so any tool/prompt/resource_template can call it.
- weather dashboard resource template at ui://weather/dashboard with
  self-contained HTML. csp connectDomains and prefersBorder are
  emitted on the resource content (resources/read), following the
  apps.mdx canonical examples.
- existing weather tool gets renders_ui. text and structured output
  are preserved.

per apps.mdx, tools/list filtering by visibility is host-side; no
server-side filter is added.

ToolsRegistry.items is snapshotted and restored in setup/teardown
since Class.new(ActionMCP::Tool) fires the inherited hook before
abstract! can run, briefly leaking the unnamed class under the ""
key. matches the snapshot/restore pattern in
resource_templates_registry_test.rb.
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.

Proposal: Add MCP Apps support (interactive UI rendering)

1 participant