Skip to main content
A tool the model calls normally runs on your server. useRegisterViewTool registers a tool that runs in the view instead: the model invokes it and the handler executes in your component, so the model can mutate view state or do any work that does not need the backend.

Example

The model adds an on-screen product to the cart by calling a tool the view handles itself: the handler updates the live cart and reports back what it added.
import * as z from "zod";
import { useRegisterViewTool, useViewState } from "skybridge/web";

function Carousel({ products }: { products: Product[] }) {
  const [{ cart }, setState] = useViewState({ cart: [] }); // synced with the model, survives remount

  useRegisterViewTool(
    {
      name: "carousel_add_to_cart",
      title: "Add to cart",
      description: "Add a product from the carousel to the cart by its id.",
      inputSchema: { productId: z.string() },
      annotations: { readOnlyHint: false },
    },
    ({ productId }) => {
      const product = products.find((p) => p.id === productId);
      setState((s) => ({ cart: [...s.cart, productId] }));
      return {
        content: [{ type: "text", text: `Added ${product?.name ?? productId} to the cart.` }],
        structuredContent: { cart: [...cart, productId] },
      };
    },
  );

  return <ProductGrid products={products} />;
}

Parameters

config

config: ViewToolConfig<TInput>;
Required. Declares the tool the view exposes, mirroring the server-side registerTool config. Namespace name (for example carousel_add_to_cart) so it does not clash with a server tool.
type ViewToolConfig<TInput> = {
  name: string; // tool name the model calls
  title?: string;
  description?: string; // model-facing: decides when the model calls this tool
  inputSchema?: TInput; // Zod raw shape; types the handler args
  annotations?: ToolAnnotations; // standard MCP hints, e.g. readOnlyHint
};

handler

handler: (args: InferViewToolArgs<TInput>) => ViewToolResult | Promise<ViewToolResult>;
Required. Runs when the model calls the tool, receiving the arguments typed from config.inputSchema (each field’s optionality preserved). It returns the MCP CallToolResult; the model sees content and structuredContent as the tool result in the conversation.
type ViewToolResult = {
  content?: ContentBlock[]; // returned to the model; defaults to []
  structuredContent?: Record<string, unknown>; // typed result the model reads
  isError?: boolean; // true marks the call as failed
  _meta?: Record<string, unknown>;
};

Lifecycle

The hook registers the tool on mount, removes it on unmount, and re-registers only when name changes. The handler always runs its latest version, so it sees current state; changes to the other config fields reach the host only on re-registration.

Create Views

Wire a view tool into a running view

useCallTool

The inverse: call a server tool from the view

useToolInfo

Read the tool result the view mounted with