Documentation Index
Fetch the complete documentation index at: https://docs-staging.skybridge.tech/llms.txt
Use this file to discover all available pages before exploring further.
How to get data into your view and fetch more when the user interacts.
Two Patterns
| Pattern | Hook | When to use |
|---|
| Initial hydration | useToolInfo | Data passed when view loads |
| User-triggered | useCallTool | User clicks button, needs more data |
When the host calls your view’s tool, the server returns structuredContent. Access it with useToolInfo:
import { useToolInfo } from "skybridge/web";
type FlightData = {
flights: Array<{ id: string; name: string; price: number }>;
};
export function FlightView() {
const { isSuccess, output } = useToolInfo<FlightData>();
if (!isSuccess) {
return <div>Loading...</div>;
}
const { flights } = output.structuredContent;
return (
<ul>
{flights.map(flight => (
<li key={flight.id}>{flight.name} - ${flight.price}</li>
))}
</ul>
);
}
Status Flags
const { isIdle, isPending, isSuccess, isError, output, error } = useToolInfo();
| Flag | Meaning |
|---|
isIdle | View hasn’t received data yet |
isPending | Tool is still executing |
isSuccess | Data is available in output |
isError | Tool failed, check error |
When the user clicks something and you need more data:
Tool Accessibility: In Apps SDK (ChatGPT), tools must have _meta["openai/widgetAccessible"] set to true to be callable from views. In MCP Apps, all tools are accessible by default. See useCallTool API for details.
import { useCallTool } from "skybridge/web";
export function FlightView() {
const { callTool, isPending, data, isError, error } = useCallTool("get_flight_details");
const handleViewDetails = (flightId: string) => {
callTool({ flightId });
};
return (
<div>
<button onClick={() => handleViewDetails("AF123")} disabled={isPending}>
{isPending ? "Loading..." : "View Details"}
</button>
{isError && <p>Error: {String(error)}</p>}
{data && <pre>{JSON.stringify(data.structuredContent, null, 2)}</pre>}
</div>
);
}
With Callbacks
const { callTool } = useCallTool("book_hotel");
const handleBooking = () => {
callTool(
{ hotelId: "123" },
{
onSuccess: (response) => {
console.log("Booking confirmed:", response.structuredContent);
setBookingComplete(true);
},
onError: (error) => {
console.error("Booking failed:", error);
setShowErrorModal(true);
},
onSettled: () => {
// Runs after success or error
setSubmitting(false);
},
}
);
};
With Async/Await
const { callToolAsync, isPending } = useCallTool("check_availability");
const handleCheck = async (date: string) => {
try {
const result = await callToolAsync({ date });
if (result.structuredContent.available) {
setStep("payment");
} else {
setShowUnavailableMessage(true);
}
} catch (error) {
setError(error);
}
};
Type Safety with generateHelpers
Use generateHelpers for autocomplete and type inference. See generateHelpers for setup.
// Import from your typed helpers file, not skybridge/web
import { useCallTool } from "../skybridge";
const { callTool, data } = useCallTool("search-hotels");
// ^ autocomplete for tool names
callTool({ city: "Paris", checkIn: "2025-12-15" });
// ^ type-checked inputs
Common Patterns
Loading State
function ProductView() {
const { isPending, data } = useCallTool("get_product");
if (isPending) {
return <Skeleton />;
}
if (!data) {
return <EmptyState />;
}
return <ProductDetails product={data.structuredContent} />;
}
Refetching
function StockView() {
const { callTool, data, isPending } = useCallTool("get_stock_price");
const [lastFetch, setLastFetch] = useState<Date | null>(null);
const refresh = () => {
callTool({ symbol: "AAPL" }, {
onSuccess: () => setLastFetch(new Date()),
});
};
return (
<div>
<button onClick={refresh} disabled={isPending}>
{isPending ? "Refreshing..." : "Refresh"}
</button>
{lastFetch && <p>Last updated: {lastFetch.toLocaleTimeString()}</p>}
{data && <StockPrice price={data.structuredContent.price} />}
</div>
);
}
Chained Calls
function BookingView() {
const { callToolAsync: checkAvailability } = useCallTool("check_availability");
const { callToolAsync: createBooking } = useCallTool("create_booking");
const handleBook = async (roomId: string, dates: DateRange) => {
const availability = await checkAvailability({ roomId, ...dates });
if (!availability.structuredContent.available) {
setError("Room not available for these dates");
return;
}
const booking = await createBooking({
roomId,
...dates,
priceId: availability.structuredContent.priceId,
});
setConfirmation(booking.structuredContent);
};
}
Anti-patterns
Don’t fetch on mount
// Bad: Fetches data on every mount
function BadView() {
const { callTool } = useCallTool("get_data");
useEffect(() => {
callTool({}); // Don't do this!
}, []);
}
// Good: Use initial data from structuredContent
function GoodView() {
const { output } = useToolInfo();
// Data is already here from the tool call
}
Don’t ignore loading states
// Bad: Assumes data is always present
function BadView() {
const { data } = useCallTool("search");
return <div>{data.structuredContent.results.length}</div>; // Crashes!
}
// Good: Handle all states
function GoodView() {
const { data, isPending, isError } = useCallTool("search");
if (isPending) return <Loading />;
if (isError) return <Error />;
if (!data) return null;
return <div>{data.structuredContent.results.length}</div>;
}