Skip to content

React SDK

The React SDK lets you embed ggui-generated UIs directly inside your React application. <McpAppIframe> is the canonical surface for hosts; the legacy <GguiSession>-family primitives (retained for devtool only) give in-process stack visibility for debugging.

Terminal window
npm install @ggui-ai/react
import { McpAppIframe, type ProtocolError } from "@ggui-ai/react";
import { useEffect, useState } from "react";
type ResourceContents = { uri: string; mimeType: string; text: string };
function App({ sessionId }: { sessionId: string }) {
const [resource, setResource] = useState<ResourceContents | null>(null);
useEffect(() => {
// Fetch the MCP Apps `{contents:[{uri,mimeType,text}]}` envelope from your
// session-resource endpoint.
// 🟢 OSS: GET http://127.0.0.1:6781/s/<shortCode>/resource
// 🟣 Hosted Guuey: GET /ggui/session-resource?session=<id> (Cognito Bearer)
fetch(`/ggui/session-resource?session=${sessionId}`, {
headers: { Authorization: `Bearer ${idToken}` },
})
.then((r) => r.json())
.then((body) => setResource(body.contents[0]));
}, [sessionId]);
if (!resource) return <p>Loading…</p>;
return <McpAppIframe resource={resource} onError={(err: ProtocolError) => console.error(err)} />;
}

The iframe boots the @ggui-ai/renderer bundle off the URL advertised in the resource’s _meta.ggui.bootstrap.rendererUrl, opens the channel-3 WebSocket back to the server, and renders stack items. Your host code stops there. For the complete protocol contract, see Implementing the ggui Protocol.


Legacy primitives (Phase 3 retirement targets)

Section titled “Legacy primitives (Phase 3 retirement targets)”

Everything below this line documents the pre-<McpAppIframe> primitives. They still work and still ship; the @ggui-ai/devtool routes still import them for in-process stack visibility during debugging. The internal shells (<ChatShell> / <AgentShell> / <FullscreenShell>) were already migrated off these primitives in Phase 3 Wave 2 — they now wrap <McpAppIframe> internally. New consumer code should not import from this set — use <McpAppIframe> above.

import { GguiProvider, GguiSession, StackItemRenderer } from "@ggui-ai/react";
function App() {
return (
<GguiProvider appId="app_..." wsEndpoint="wss://ws.guuey.com">
<GguiSession sessionId="session_...">
{({ stack, action, connectionStatus }) => (
<div>
{stack.map((item) => (
<StackItemRenderer key={item.id} stackItem={item} onAction={action} />
))}
<p>Connection: {connectionStatus}</p>
</div>
)}
</GguiSession>
</GguiProvider>
);
}

🟢 Running OSS / self-hosted? Point wsEndpoint at your local ggui serve WebSocket (default ws://127.0.0.1:6781/ws). The legacy primitives are protocol-agnostic.


Root provider that supplies app configuration to all ggui components via React Context.

<GguiProvider appId="app_..." wsEndpoint="wss://ws.guuey.com">
{children}
</GguiProvider>
PropTypeRequiredDescription
appIdstringYesYour ggui app ID
wsEndpointstringNoWebSocket endpoint URL. If omitted, real-time features are disabled
adapters('voice' | 'camera')[]NoDevice adapters to initialize

Manages a session’s lifecycle, WebSocket connection, and canonical {@link ActionEnvelope} emission. Must be inside a <GguiProvider>.

<GguiSession
sessionId={sessionId}
onInteraction={(envelope) => console.log(envelope.type, envelope.payload)}
onError={(error) => console.error(error)}
>
{({ action, connectionStatus }) => (
<div>
{connectionStatus !== "connected" && <p>Reconnecting...</p>}
<button onClick={() => action({ rating: 5 })}>Submit</button>
</div>
)}
</GguiSession>

Key props:

PropTypeDescription
sessionIdstringSession ID to connect to
userTokenstringUser authentication token
onInteraction(envelope: ActionEnvelope) => voidCalled for interaction:* event types. Receives the canonical flat envelope
onBeforeAction<T>(data: T, meta: ActionMeta) => T | undefinedMiddleware before action emission. Return undefined to cancel; return data to proceed
onStackPush(stackItem: StackItem) => voidCalled when a card is pushed
onError(error: Error) => voidError handler. Receives ClientContractViolationError for contract violations

ActionMeta is the minimal context passed to onBeforeAction — just {sessionId, stackIndex}. The legacy diagnostic bag (device info, interface context) was retired when the emitter migrated to canonical ActionEnvelope; those values live on the session, not per-delivery.

Render prop API (SessionApi):

FieldTypeDescription
action<T>(data: T) => voidSubmit user interaction data. Emits a canonical data:submit ActionEnvelope
send(msg: WebSocketMessage) => voidSend a raw WebSocket message. Internal SDK use (feedback, diagnostics)
connectionStatusConnectionStatus'connecting' | 'connected' | 'disconnected' | 'reconnecting'
stackStackItem[]Current session stack
sessionIdstringSession ID

User text messages (channel 1) flow through useInvoke() at the consumer layer — not through SessionApi. GguiSession exposes stack + action + raw-send via its render prop.


Renders compiled JavaScript components at runtime. Takes compiled ESM code and dynamically imports it.

<DynamicComponent code={compiledJsCode} props={{ title: "Hello" }} />
PropTypeRequiredDescription
codestringYesCompiled JavaScript code (ESM module with default export)
propsRecord<string, unknown>NoProps to pass to the rendered component
fallbackReactNodeNoUI shown while loading
onError(error: Error) => voidNoError handler

Handles the controller-template pattern automatically. If a stack item has a controller, it wraps the template; otherwise renders the template directly.

<StackItemRenderer stackItem={item} onAction={action} onError={console.error} />

Error boundary that catches runtime errors and sends them to the server for automatic repair. Premium feature.

<SelfRepairBoundary
sessionId="session_123"
appId="app_456"
stackItemId="stack_789"
config={{ enabled: true, maxAttempts: 3, retryDelayMs: 1000, showRepairUI: true }}
onReportError={reportError}
>
<DynamicComponent code={compiledCode} />
</SelfRepairBoundary>

Access the GguiProvider context. Must be used within a <GguiProvider>.

const { appId, wsEndpoint, adapters } = useGguiContext();

Manage a WebSocket connection with automatic reconnection. Returns sendAction for emitting canonical {@link ActionEnvelope} messages.

const { status, sendAction, send, lastError } = useWebSocket({
url: wsEndpoint || "",
sessionId,
appId,
onMessage: (msg) => console.log(msg),
});
// Emit a canonical ActionEnvelope — wrapped into `{type: 'action', payload: envelope}`
sendAction({
sessionId,
type: "data:submit",
payload: { action: "submit", data: { rating: 5 } },
stackIndex: 0,
});

Most application code should prefer GguiSession’s action render-prop — it builds the envelope, runs contract validation, and threads onBeforeAction middleware for you. useWebSocket.sendAction is the low-level path for custom transports.


Channel 1 — conversational surface between the user and the wrapped-agent HTTP endpoint. Returns a Vercel-AI-SDK-style messages array plus a send function.

const { messages, send, status } = useInvoke({ agentUrl: "/api/chat" });

See @ggui-ai/protocol/invoke types (ContentBlock, InvokeTurn) for the event shape. Channel 1 is product-mandatory (the default ggui serve product runs it) but protocol-optional — BYO chat frontends can plug directly into channels 2 + 3 and skip useInvoke.


The tool system enables controllers to fetch and transform data at runtime. Tools are registered globally and executed via hooks.

Execute a single tool and manage its loading/error state.

function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useTool({
config: {
tool: "fetch",
config: { endpoint: `/api/users/${userId}` },
},
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{data.name}</div>;
}

Resolve multiple data bindings in topological order based on dependencies.

const { data, loading, errors } = useBindings({
bindings: {
user: { tool: "auth", config: { field: "currentUser" } },
profile: {
tool: "fetch",
config: { endpoint: "/api/users/{user.id}/profile" },
dependsOn: ["user"],
},
},
});

ToolDescription
fetchHTTP requests with caching and response extraction
authAccess authentication context data
storageRead from localStorage/sessionStorage
chainSequential tool execution with {prev} interpolation
transformData transformation (pick, omit, rename, flatten, map)
mergeCombine multiple data sources (shallow, first-wins, deep)

Import from @ggui-ai/react/testing for Node.js test helpers:

import {
setupMockTools,
resolveBindingsForTest,
validateComponentCode,
validateControllerCode,
createTestContext,
} from "@ggui-ai/react/testing";
beforeEach(() => {
setupMockTools({
auth: { id: "user-123", name: "Test User" },
fetch: { "/api/users": [{ id: 1, name: "Alice" }] },
storage: { theme: "dark" },
});
});

See the Testing cookbook for complete patterns.