Skip to content
Sneak peek — you found hosted ggui early · official launch soon

React Native host helpers (MCP Apps)

read as .md

@ggui-ai/mcp-apps-react-native is a host-helper library, not an SDK. It gives your app the host half of the MCP Apps contract — mount a UI resource, answer ui/initialize, relay tools/call, open links, tear down. Rendering semantics (freeze latch, history epochs, error surfaces) live inside the mounted view, not in your tree.

Terminal window
npm install @ggui-ai/mcp-apps-react-native@0.9.0

Peer dependencies, installed by your app: react (18 or 19), react-native (>= 0.70), react-native-webview (>= 13), and @modelcontextprotocol/sdk.

Import path Contents
@ggui-ai/mcp-apps-react-native <McpAppIframe>, <GguiProvider>, useInvoke, <UiFeedback>, theme
@ggui-ai/mcp-apps-react-native/chat-helpers Message-grouping + render-extraction helpers

Give the component the UI resource from a tool result plus the render metadata carried on _meta["ai.ggui/render"]. It mounts the resource in a WebView and runs the host side of the handshake:

import { useRef } from "react";
import { McpAppIframe } from "@ggui-ai/mcp-apps-react-native";
import type { McpAppIframeProps, McpAppIframeRef } from "@ggui-ai/mcp-apps-react-native";
export function AgentCard({
resource,
meta,
callTool,
}: {
resource: McpAppIframeProps["resource"];
meta: McpAppIframeProps["meta"];
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
}) {
const ref = useRef<McpAppIframeRef>(null);
return (
<McpAppIframe
ref={ref}
resource={resource}
meta={meta}
containerDimensions={{ width: 360, maxHeight: 640 }}
onToolCall={callTool}
onError={(err) => console.warn("render error", err.kind)}
onObserve={(event) => console.log("observe", event.kind)}
onLifecycle={(event) => console.log("lifecycle", event.state)}
/>
);
}
Prop Type Notes
resource ResourceContents Required. text mounts as inline HTML, blob as a data URL, otherwise the uri is loaded (http(s) only).
meta McpAppAiGguiRenderMeta First-party ggui renders only. Delivered as a ui/notifications/tool-result right after the initialize response.
onToolCall (toolName, args) => Promise<unknown> Relay for tools/call. Without it every call is rejected no-tool-handler.
onUpdateModelContext (params) => Promise<void> | void Receives ui/update-model-context. Without it the host answers method_not_supported.
containerDimensions { width?, height?, maxWidth?, maxHeight? } Styles the outer <View> and is echoed on ui/initialize.result.hostContext.
permissions { camera?, microphone?, geolocation?, clipboardWrite? } Platform WebView gating. Never reaches ui/initialize.
locale string Forwarded as hostContext.locale. Defaults to en-US — RN has no navigator.
onError (err: ProtocolError) => void Classified failures, including the ggui:bootstrap-failed envelope. Handlers must not throw.
onObserve (event: ObservabilityEvent) => void The ggui:observe channel. Tolerate unknown event.kind.
onLifecycle (event: McpAppLifecycleEvent) => void Also mirrored on the outer <View> as accessibilityValue={{ text: state }} whether or not you bind the callback.

The imperative ref exposes one method, dispatchAction(name, data), which posts a fire-and-forget JSON-RPC notification into the WebView.

ui/open-link with an http(s) URL is delegated to Linking.openURL; other schemes are rejected unsupported-scheme. Set meta only for WebViews you spawned from ggui’s own render resource URI — passing it to third-party MCP App content leaks outer-app state across the adapter boundary.


The two packages are twins, and a parity test enforces it. chat-helpers/message-groups.ts, chat-helpers/render.ts, and chat-helpers/useRafThrottled.ts are byte-identical across both packages, and components/GguiProvider.tsx and components/UiFeedback.tsx are documented platform-delta twins whose exported surfaces must match. A one-sided edit fails both suites.

Two deliberate differences:

  • useMcpAppsChat is web-only. RN’s chat-helpers ships the platform-neutral subset — invokeMessageToContentGroups, contentGroupsToConversationMessages, conversationMessagesToInvokeHistory, extractRenderFromToolResult, extractSessionIdFromToolResult, useRafThrottled. Drive the stream with useInvoke (mounted inside <GguiProvider>) and own your message history — the pattern is the chat-own-storage cookbook.
  • <McpAppIframe> is RN-only, for the sandbox-proxy reason above.

Everything else lines up: the same ProtocolError union, the same ObservabilityEvent catalog, the same protocol types, and a React Native theme system (ThemeProvider, useTheme) mirroring the web design tokens.

The legacy in-tree render family — GguiRender, DynamicComponent, WebViewRenderer, NativeRegistry, and their WebSocket stack — was deleted from this package. Generated component code runs inside the WebView, not in your React Native tree; there is no in-process renderer to import.