Skip to main content
useCallTool lets a view call one of your server’s tools and tracks the call’s state, so an interaction in the view can run server logic and render the result. The model does not see a view-initiated call. Import it from your generated helpers.ts: the typed hook infers the tool’s argument and response types from your server, so you pass only the name.

Examples

Trigger a call and read the result

A shopper checks out their cart without leaving the view. The button shows progress while the server creates the checkout, then turns into a Pay now link, or explains why checkout could not start.
import { useCallTool } from "../helpers.js"; // generated, type-safe from server schema
import { useOpenExternal } from "skybridge/web";

function Cart({ items }: { items: string[] }) {
  const { callTool, isPending, isSuccess, isError, data, error } =
    useCallTool("create-checkout");
  const openExternal = useOpenExternal();

  return (
    <div>
      <button
        disabled={isPending}
        onClick={() =>
          callTool(
            { items },
            { onError: (e) => console.error("Checkout failed", e) },
          )
        }
      >
        {isPending ? "Checking out..." : "Check out"}
      </button>
      {isSuccess && (
        <button onClick={() => openExternal(data.structuredContent.checkoutUrl)}>
          Pay now
        </button>
      )}
      {isError && <p>Could not start checkout: {String(error)}</p>}
    </div>
  );
}

Await the call

A shopper checks out and goes straight to payment. The view waits for the checkout, opens the payment page, and recovers if the request never completes.
import { useCallTool } from "../helpers.js"; // generated, type-safe from server schema
import { useOpenExternal } from "skybridge/web";

function Cart() {
  const { callToolAsync, isPending } = useCallTool("create-checkout");
  const openExternal = useOpenExternal();

  const checkout = async () => {
    try {
      const { structuredContent } = await callToolAsync({ items: ["jacket"] });
      openExternal(structuredContent.checkoutUrl);
    } catch (error) {
      console.error("Checkout failed", error);
    }
  };

  return (
    <button disabled={isPending} onClick={checkout}>
      Check out
    </button>
  );
}

Type Parameters

The generated helper infers both from your server. You set them by hand only when importing useCallTool from skybridge/web directly, which is not recommended.

ToolArgs

ToolArgs extends Record<string, unknown> | null = null;
The arguments the tool accepts. Defaults to null, for a tool that takes no arguments.

ToolResponse

ToolResponse extends Partial<{
  structuredContent: Record<string, unknown>;
  meta: Record<string, unknown>;
}> = Record<string, never>;
The shape of the tool’s structuredContent and meta. Defaults to an empty object. Whatever you set here types those two fields on data.

Parameters

name

name: string;
Required. The name of the tool to call. It must match a tool registered on your MCP server.
A view can call a tool only when the tool’s _meta.ui.visibility includes "app", which is the default behavior. See Register Tools.

Returns

useCallTool returns two call functions plus the live state of the most recent call.

callTool

callTool(toolArgs: ToolArgs, sideEffects?: SideEffects): void;
callTool(sideEffects?: SideEffects): void; // overload available when the tool has no required arguments
Starts the call and tracks its state on the hook. Returns void: read the outcome from status / data / error. Drop toolArgs via the second overload when the tool has no required arguments. sideEffects Optional callbacks bound to this specific call, firing even when a later call supersedes it.
type SideEffects = {
  // the call completed: the host returned a response (which may carry data.isError === true)
  onSuccess?: (data: CallToolResponse & ToolResponse, toolArgs: ToolArgs) => void;
  // the call failed to complete: the host rejected it
  onError?: (error: unknown, toolArgs: ToolArgs) => void;
  // runs after onSuccess or onError, with the other argument undefined
  onSettled?: (
    data: (CallToolResponse & ToolResponse) | undefined,
    error: unknown | undefined,
    toolArgs: ToolArgs,
  ) => void;
};

callToolAsync

callToolAsync(toolArgs: ToolArgs): Promise<CallToolResponse & ToolResponse>;
callToolAsync(): Promise<CallToolResponse & ToolResponse>; // overload available when the tool has no required arguments
Tracks state on the hook exactly like callTool, and also returns a promise. It resolves with the response when the call completes, and rejects with the thrown value when the call fails to complete. It accepts no sideEffects.

status

status: "idle" | "pending" | "success" | "error";
  • "idle": no call has started.
  • "pending": a call is in flight.
  • "success": the most recent call completed and the host returned a response. The response may carry data.isError === true.
  • "error": the most recent call failed to complete and the host rejected it.

isIdle, isPending, isSuccess, isError

isIdle: boolean;
isPending: boolean;
isSuccess: boolean;
isError: boolean;
Each is true when status equals the matching value and false otherwise. Exactly one is true at any time. isError here is the call-level flag (status is "error"). It is not data.isError, which marks a completed call whose tool reported a failure.

data

data: (CallToolResponse & ToolResponse) | undefined;
The response, set only while status is "success". It is undefined in every other state. A new call clears it to undefined as it enters "pending". A tool that reports a failure still lands here, with isError: true; only a call that fails to complete sets error instead. CallToolResponse is the fixed part of every response, which your ToolResponse type parameter narrows on structuredContent and meta:
type CallToolResponse = {
  content: ContentBlock[]; // the MCP content blocks the tool returned
  structuredContent: Record<string, unknown>;
  isError: boolean; // true when the tool itself reported a failure
  meta?: Record<string, unknown>;
};

error

error: unknown | undefined;
The thrown value, set only while status is "error". It is undefined in every other state. A tool that completes but reports its own failure is a "success", not an "error".

Create Views

Call tools back from a view in context

generateHelpers

The typed useCallTool that infers from your server

useToolInfo

Read the tool result the view mounted with