Skip to main content
To host MCP Apps in an iOS or Android app, you can load each tool’s HTML view in a WKWebView or WebView, or you can draw the tool’s UI with the platform’s own UI toolkit. The Metabind SDKs do the second. An MCP App built on Metabind renders as SwiftUI on iOS, macOS, and visionOS, and as Jetpack Compose on Android. The same MCP App renders as HTML in Claude, ChatGPT, and other MCP hosts. This page covers how MCP Apps are usually rendered, what changes when the UI renders natively, how the SDKs and the Metabind server agree on a format, and the minimum code for iOS and Android.

How MCP Apps are usually rendered

The MCP Apps extension to MCP (SEP-1865) delivers a tool’s user interface as a ui:// resource with the MIME type text/html;profile=mcp-app. A tool names its resource in _meta.ui.resourceUri. The host reads the resource, loads the HTML in a sandboxed iframe, and exchanges JSON-RPC 2.0 messages with it over postMessage: tool input, tool results, and requests from the view such as tool calls. Web hosts usually do this with the MCP-UI client SDK (@mcp-ui/client) or the App Bridge from the ext-apps SDK (@modelcontextprotocol/ext-apps/app-bridge). A native app does the same thing with a WKWebView on Apple platforms or a WebView on Android: it loads the HTML and implements the postMessage bridge. This works with every MCP App server, because the HTML view is the format the MCP Apps specification defines. The result is a web page inside your app. The page draws its own controls, sets its own text sizes, and exposes the accessibility tree its HTML builds.

What changes when the UI renders natively

On Metabind, an Interactive Tool’s UI is a BindJS component. The BindJS runtime runs the component’s code in an isolated JavaScript context and produces a view tree. The SDK draws that tree with SwiftUI or Jetpack Compose, as part of your app’s view hierarchy.
  • Controls. BindJS Button, Toggle, TextField, Slider, and List render as the corresponding SwiftUI and Compose components. See the mapping table in Native rendering.
  • Text size. BindJS text styles such as body, headline, and caption map to SwiftUI text styles, which scale with Dynamic Type. On Android, text sizes are set in sp, which follows the system font size setting.
  • Accessibility. accessibilityLabel, accessibilityHint, and accessibilityValue map to SwiftUI accessibility modifiers and to Compose semantics. VoiceOver and TalkBack read tool UI the same way they read the rest of your app.
  • Gestures and animation. Scrolling, gestures, and animation run through SwiftUI and Compose, so they behave like the rest of your app.
  • Performance. There is no web view to create and no page to load for each result. On Apple platforms, MCPAppsClient caches a project’s decoded component package, so later results from the same package version skip the full decode. The Apple client also accepts prefetchUIResources in MCPAppsClient.Configuration, which fetches every ui:// resource as soon as listTools() returns.
Components call back into your app through useMCPHost(): tool calls, follow-up messages, model context updates, and opening links. The Assistant SDK wires these on both platforms.

How the format is negotiated

The SDKs and the Metabind server agree on a format during the MCP handshake, using the extension MCP Apps hosts already use to declare HTML support.
1

The client declares the formats it renders

On initialize, MCPAppsClient lists the MIME types it can render in the io.modelcontextprotocol/ui extension. The iOS and Android clients send:
2

The server returns the format the client declared

The Metabind server stores those MIME types with the MCP session. When the client reads a tool’s ui:// resource, the server returns the BindJS bundle (application/vnd.bindjs+json) to a client that declared it, and the HTML view (text/html;profile=mcp-app) to a client that did not. The tool definition and the resource URI are the same for every client.
3

The SDK renders by MIME type

The SDK picks a renderer from the MIME type of the resource it receives. BindJS renders as SwiftUI or Compose. HTML renders in a WKWebView with a restrictive Content Security Policy and external navigation blocked on Apple platforms, and in a WebView on Android. Host calls through useMCPHost() apply to BindJS content.
Claude, ChatGPT, and other MCP hosts render HTML views, so they receive the HTML representation and render the same component in their sandboxed iframe with the BindJS React renderer. You publish once, and each client receives the format it declared. On Apple platforms, the declared MIME types come from the client’s content resolvers. The default is [BindJSResolver(), HTMLResolver()]. To declare BindJS without the HTML fallback, pass one resolver:
The Android MCPAppsClient always declares both types.

Quick start: iOS, macOS, and visionOS

Add https://github.com/metabindai/metabind-apple with Swift Package Manager and link the product you need: MCPAppsHost to render individual tool results, or MetabindAI for the Assistant SDK. See installation and authentication patterns. The MCP server URL for a Metabind project is https://mcp.metabind.ai/<org>/projects/<project>.

Render a single tool result with MCPAppsHost

Use MCPAppsHost when your app decides which tool to call, for example on a product screen or a dashboard tile, and you don’t need a conversation.
MCPAppSession reads the tool’s ui:// resource, calls the tool, and moves through the loading, active, and completed phases. MCPAppView renders each phase. Pass the tool definition from listTools(), because it carries the resource URI. A SimpleMCPToolCall without a tool definition still calls the tool, and the session shows the tool’s text and image content instead of a view. Other options:
  • If you already called the tool, MCPAppSession(toolCall:completedWith:server:) fetches the view and renders your result.
  • For your own loading and error UI, use MCPAppView(session:content:placeholder:) or MCPAppView(session:phase:).
  • To give components host capabilities outside the Assistant SDK, attach a bridge with .mcpHostBridge(_:).
MCPAppsHost contains no language model or agent loop. It connects to any MCP server over HTTP. A resource the server returns as HTML displays in the WKWebView fallback.

Embed the Assistant SDK

MetabindAI adds the conversation on top of MCPAppsHost. MetabindAssistantView runs the conversation through the Metabind Agent proxy and renders each Interactive Tool result inline as SwiftUI:
MetabindAssistant builds its MCPAppsClient from serverURL and serverHeaders, so format negotiation works as described above. One Metabind API key authenticates both the MCP server and the Agent proxy. The iOS SDK guide covers bring-your-own-key mode and previews. Custom host UI covers replacing the chat surface.

Quick start: Android

Add ai.metabind:metabindai-android, which brings in ai.metabind:mcpappshost-android and the BindJS Compose renderer, ai.metabind:bindjs-android. The packages are published to GitHub Packages; see installation for the repository configuration.

Embed the Assistant SDK

The assistant derives the MCP server URL from orgId and projectId, runs the conversation through the Agent proxy, and renders each Interactive Tool result as Compose. Keep the MetabindAssistant at a longer-lived scope, such as a ViewModel, and call close() when you discard it. The Android SDK has no bring-your-own-key provider. The Agent proxy holds the language model key, so none ships in your APK. See the Android SDK guide.

Place a tool result outside the chat

MetabindToolView is the renderer MetabindAssistantView uses for each tool result. Use it to show a result on another screen, in a sheet, or in a tab. It takes the assistant, which supplies host calls, and a ToolUIContent from assistant.toolUIContent, which is keyed by tool call ID, the same ID as the TOOL message the call produced:

Fetch a tool’s UI with MCPAppsClient

MCPAppsClient in :mcpappshost performs the same negotiation without the assistant. ToolUIContent.fromResource, in :metabindai, parses the resource into ToolUIContent.BindJS or ToolUIContent.Html:
To render ToolUIContent.BindJS without a MetabindAssistant, drive BindJSView from bindjs-android directly. MetabindToolView.kt shows the runtime setup and host wiring.

Web

On the web, the Web SDK renders Interactive Tool output as HTML in sandboxed iframes through @mcp-ui/client, the same way MCP hosts do.

Native rendering

How one BindJS definition becomes SwiftUI, Jetpack Compose, and React.

iOS SDK

Install, configure, and embed the Assistant SDK on iOS, macOS, and visionOS.

Android SDK

Install, configure, and embed the Assistant SDK on Android.

Web SDK

The Assistant SDK for React apps.

Custom host UI

Replace the default chat surface with your own.