> ## Documentation Index
> Fetch the complete documentation index at: https://prefab.prefect.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Call MCP Tool

> Call a server-side tool from a UI interaction.

export const ComponentPreview = ({json, height, resizable, bare, hideJson, playground, children}) => {
  const hostRef = React.useRef(null);
  const handleRef = React.useRef(null);
  const cardRef = React.useRef(null);
  const containerRef = React.useRef(null);
  const [previewWidth, setPreviewWidth] = React.useState(null);
  const [isDragging, setIsDragging] = React.useState(false);
  const [playgroundHref, setPlaygroundHref] = React.useState(null);
  React.useEffect(function () {
    if (!playground) return;
    var isLocal = location.hostname === "localhost" || location.hostname === "127.0.0.1";
    var path = isLocal ? "/playground" : "/docs/playground";
    var url = new URL(path, window.location.href);
    url.hash = "code=" + playground;
    setPlaygroundHref(url.href);
  }, [playground]);
  var jsonStr = typeof json === "string" ? json : JSON.stringify(json, null, 2);
  React.useEffect(function () {
    if (cardRef.current) {
      var card = cardRef.current.closest(".group");
      if (card) card.classList.remove("group");
    }
    var host = hostRef.current;
    if (!host || !json) return;
    function mount() {
      if (!window.__prefab || !host) return;
      var dark = document.documentElement.classList.contains("dark");
      handleRef.current = window.__prefab.mountPreview(host, jsonStr, {
        dark: dark
      });
      if (bare && host.shadowRoot) {
        var m = host.shadowRoot.querySelector("[data-prefab-mount]");
        if (m) m.style.background = "transparent";
      }
    }
    if (window.__prefab) {
      mount();
    } else {
      if (!window.__prefabLoading) {
        if (!window.__prefabReady) {
          var s = document.createElement("script");
          s.src = "/renderer.js";
          document.head.appendChild(s);
        }
        window.__prefabLoading = new Promise(function (resolve) {
          function check() {
            if (window.__prefabReady) {
              window.__prefabReady.then(resolve);
            } else {
              setTimeout(check, 10);
            }
          }
          check();
        });
      }
      window.__prefabLoading.then(mount);
    }
    var observer = new MutationObserver(function () {
      var dark = document.documentElement.classList.contains("dark");
      if (handleRef.current) handleRef.current.setDark(dark);
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return function () {
      observer.disconnect();
      if (handleRef.current) {
        handleRef.current.unmount();
        handleRef.current = null;
      }
    };
  }, [json]);
  function startResize(e) {
    e.preventDefault();
    var startX = e.clientX;
    var startW = hostRef.current ? hostRef.current.offsetWidth : 0;
    var maxW = containerRef.current ? containerRef.current.offsetWidth - 10 : startW;
    setIsDragging(true);
    function onMove(ev) {
      var w = Math.max(260, Math.min(startW + (ev.clientX - startX), maxW));
      setPreviewWidth(w);
    }
    function onUp() {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
      setIsDragging(false);
    }
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  }
  if (bare) {
    return <div ref={hostRef} />;
  }
  if (resizable) {
    return <Card>
        <div ref={cardRef} />
        <div ref={containerRef} style={{
      display: "flex",
      position: "relative",
      overflow: "hidden"
    }}>
          <div ref={hostRef} style={{
      flex: previewWidth ? "none" : 1,
      width: previewWidth ? previewWidth + "px" : undefined,
      minWidth: 0
    }} />
          <div onMouseDown={startResize} style={{
      width: "14px",
      flexShrink: 0,
      cursor: "col-resize",
      background: isDragging ? "var(--border, #e5e7eb)" : "color-mix(in srgb, var(--border, #e5e7eb) 40%, transparent)",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      transition: "background 0.15s",
      userSelect: "none"
    }}>
            <svg width="6" height="24" style={{
      opacity: 0.5
    }}>
              <circle cx="2" cy="4" r="1.2" fill="currentColor" />
              <circle cx="2" cy="9" r="1.2" fill="currentColor" />
              <circle cx="2" cy="14" r="1.2" fill="currentColor" />
              <circle cx="2" cy="19" r="1.2" fill="currentColor" />
              <circle cx="5" cy="4" r="1.2" fill="currentColor" />
              <circle cx="5" cy="9" r="1.2" fill="currentColor" />
              <circle cx="5" cy="14" r="1.2" fill="currentColor" />
              <circle cx="5" cy="19" r="1.2" fill="currentColor" />
            </svg>
          </div>
          <div style={{
      flex: previewWidth ? 1 : "0 0 24px",
      minWidth: "24px",
      background: "repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--border, #e5e7eb) 3px, var(--border, #e5e7eb) 4px)",
      opacity: 0.4
    }} />
          {previewWidth && <div style={{
      position: "absolute",
      bottom: "8px",
      right: "8px",
      background: "var(--background, white)",
      border: "1px solid var(--border, #e5e7eb)",
      borderRadius: "4px",
      padding: "1px 6px",
      fontSize: "11px",
      fontFamily: "monospace",
      color: "var(--muted-foreground, #6b7280)",
      pointerEvents: "none"
    }}>
              {previewWidth}px
            </div>}
        </div>
        {playgroundHref && <div style={{
      display: "flex",
      justifyContent: "flex-end",
      padding: "4px 0 8px"
    }}>
            <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
      fontSize: "11px",
      opacity: 0.4,
      textDecoration: "none",
      textDecorationLine: "none",
      borderBottom: "none",
      boxShadow: "none",
      display: "inline-flex",
      alignItems: "center",
      gap: "3px",
      transition: "opacity 0.15s",
      fontWeight: 400
    }} onMouseOver={function (e) {
      e.currentTarget.style.opacity = "0.6";
    }} onMouseOut={function (e) {
      e.currentTarget.style.opacity = "0.4";
    }}>
              Edit in Playground
              <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
            </a>
          </div>}
        <div style={{
      marginBottom: "-2rem"
    }}>{children}</div>
      </Card>;
  }
  return <Card>
      <div ref={cardRef} />
      <div ref={hostRef} />
      {playgroundHref && <div style={{
    display: "flex",
    justifyContent: "flex-end",
    padding: "4px 0 8px"
  }}>
          <a href={playgroundHref} target="_blank" rel="noopener noreferrer" style={{
    fontSize: "11px",
    opacity: 0.4,
    textDecoration: "none",
    textDecorationLine: "none",
    borderBottom: "none",
    boxShadow: "none",
    display: "inline-flex",
    alignItems: "center",
    gap: "3px",
    transition: "opacity 0.15s",
    fontWeight: 400
  }} onMouseOver={function (e) {
    e.currentTarget.style.opacity = "0.6";
  }} onMouseOut={function (e) {
    e.currentTarget.style.opacity = "0.4";
  }}>
            Edit in Playground
            <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
          </a>
        </div>}
      <div style={{
    marginBottom: "-2rem"
  }}>{children}</div>
    </Card>;
};

`CallTool` is the bridge between your UI and your server logic. When a user clicks a button or submits a form, `CallTool` invokes a server tool with the arguments you specify — the same tools you define with `@mcp.tool()`.

<ComponentPreview json={{"view":{"type":"Button","label":"Refresh Data","variant":"default","size":"default","disabled":false,"onClick":{"action":"toolCall","tool":"get_latest_data","arguments":{}}}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQnV0dG9uCmZyb20gcHJlZmFiX3VpLmFjdGlvbnMubWNwIGltcG9ydCBDYWxsVG9vbAoKQnV0dG9uKCJSZWZyZXNoIERhdGEiLCBvbl9jbGljaz1DYWxsVG9vbCgiZ2V0X2xhdGVzdF9kYXRhIikpCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Button
    from prefab_ui.actions.mcp import CallTool

    Button("Refresh Data", on_click=CallTool("get_latest_data"))
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "type": "Button",
        "label": "Refresh Data",
        "variant": "default",
        "size": "default",
        "disabled": false,
        "onClick": {"action": "toolCall", "tool": "get_latest_data", "arguments": {}}
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

The renderer proxies the call through the Prefab SDK, so the tool executes server-side with full access to your backend.

## Passing Arguments

Use `.rx` or `Rx("key")` to pass client state to the tool. Form controls with a `name` automatically sync to state — `Input(name="city")` updates `city` on every keystroke, no `SetState` needed.

<ComponentPreview json={{"view":{"cssClass":"gap-3","type":"Column","children":[{"name":"city","type":"Input","inputType":"text","placeholder":"Enter a city...","disabled":false,"readOnly":false,"required":false},{"type":"Button","label":"Get Weather","variant":"default","size":"default","disabled":false,"onClick":{"action":"toolCall","tool":"get_weather","arguments":{"location":"{{ city }}"}}}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgSW5wdXQsIEJ1dHRvbiwgQ29sdW1uCmZyb20gcHJlZmFiX3VpLmFjdGlvbnMubWNwIGltcG9ydCBDYWxsVG9vbAoKd2l0aCBDb2x1bW4oZ2FwPTMpOgogICAgY2l0eSA9IElucHV0KG5hbWU9ImNpdHkiLCBwbGFjZWhvbGRlcj0iRW50ZXIgYSBjaXR5Li4uIikKICAgIEJ1dHRvbigKICAgICAgICAiR2V0IFdlYXRoZXIiLAogICAgICAgIG9uX2NsaWNrPUNhbGxUb29sKAogICAgICAgICAgICAiZ2V0X3dlYXRoZXIiLAogICAgICAgICAgICBhcmd1bWVudHM9eyJsb2NhdGlvbiI6IGNpdHkucnh9LAogICAgICAgICksCiAgICApCg">
  <CodeGroup>
    ```python Python icon="python" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    from prefab_ui.components import Input, Button, Column
    from prefab_ui.actions.mcp import CallTool

    with Column(gap=3):
        city = Input(name="city", placeholder="Enter a city...")
        Button(
            "Get Weather",
            on_click=CallTool(
                "get_weather",
                arguments={"location": city.rx},
            ),
        )
    ```

    ```json Protocol icon="brackets-curly" theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    {
      "view": {
        "cssClass": "gap-3",
        "type": "Column",
        "children": [
          {
            "name": "city",
            "type": "Input",
            "inputType": "text",
            "placeholder": "Enter a city...",
            "disabled": false,
            "readOnly": false,
            "required": false
          },
          {
            "type": "Button",
            "label": "Get Weather",
            "variant": "default",
            "size": "default",
            "disabled": false,
            "onClick": {
              "action": "toolCall",
              "tool": "get_weather",
              "arguments": {"location": "{{ city }}"}
            }
          }
        ]
      }
    }
    ```
  </CodeGroup>
</ComponentPreview>

Type a city name, click the button, and the current input value flows into the `location` argument.

## Combined with Client Actions

Pass a list to execute multiple actions from a single interaction. A common pattern: update UI state before making the server call, so the user sees immediate feedback.

```python Combined Actions theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import Button
from prefab_ui.actions import SetState
from prefab_ui.actions.mcp import CallTool
from prefab_ui.rx import Rx

dataset = Rx("dataset")

Button("Analyze", on_click=[
    SetState("status", "analyzing..."),
    CallTool("run_analysis", arguments={"data": dataset}),
])
```

The `SetState` executes first (instant), then `CallTool` fires the server request.

## Handling Results

The tool's return value is available as `$result` (Python: `RESULT`) inside `on_success` callbacks. Use `SetState` to write it into client-side state:

```python Writing results to state theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import Button
from prefab_ui.actions import SetState
from prefab_ui.actions.mcp import CallTool
from prefab_ui.rx import RESULT

Button(
    "Search",
    on_click=CallTool(
        "search",
        arguments={"q": "{{ query }}"},
        on_success=SetState("results", RESULT),
    ),
)
```

`$result` is the success counterpart of `$error`: one is available in `on_success`, the other in `on_error`. You can combine them with any action — `ShowToast`, `AppendState`, or chain multiple actions in a list:

```python Callbacks with result theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from prefab_ui.components import Button
from prefab_ui.actions import AppendState, SetState, ShowToast
from prefab_ui.actions.mcp import CallTool
from prefab_ui.rx import ERROR, RESULT

Button(
    "Add Item",
    on_click=CallTool(
        "create_item",
        arguments={"name": "{{ item_name }}"},
        on_success=[
            AppendState("items", RESULT),
            ShowToast("Item created!", variant="success"),
        ],
        on_error=ShowToast(ERROR, variant="error"),
    ),
)
```

## Callable References (FastMCP)

When using Prefab with [FastMCP](/running/fastmcp), you can pass a function reference instead of a tool name string. The framework resolves it to the correct tool name at serialization time, including any namespace prefixes or global keys:

```python theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
from fastmcp import FastMCP, FastMCPApp
from prefab_ui.actions import SetState
from prefab_ui.actions.mcp import CallTool
from prefab_ui.rx import RESULT

app = FastMCPApp("My App")

@app.tool()
def search(q: str) -> list[dict]:
    return [{"name": "Arthur"}, {"name": "Ford"}]

@app.ui()
def browse():
    # Function reference — resolved automatically
    Button("Search", on_click=CallTool(
        search,
        arguments={"q": "{{ query }}"},
        on_success=SetState("results", RESULT),
    ))
```

Callable references provide two advantages over string names. First, the resolver handles name mangling (global keys, namespace prefixes) so your code doesn't need to know the final tool name. Second, the resolver can inspect the tool's metadata and inject hints into the serialized action — for example, `unwrapResult`.

String names are also resolved when a resolver is active. `CallTool("save_contact")` will pass the string `"save_contact"` through the resolver, which can map it to a global key like `"save_contact-a1b2c3d4"`. This means you can use string names in a FastMCPApp context and still get correct name resolution — useful when you don't have a direct reference to the tool function (e.g., tools defined in mounted sub-apps).

### Automatic Result Unwrapping

MCP requires `structuredContent` to be a JSON object, so FastMCP wraps non-object tool returns (lists, strings, numbers) in a `{"result": X}` envelope. Without correction, `$result` in your `on_success` callback would contain this envelope instead of the actual data.

When you use a callable reference, the resolver detects this wrapping and sets `unwrapResult: true` on the serialized action. The renderer sees the flag and automatically extracts the inner value, so `$result` contains exactly what your tool returned.

String names go through the same resolver, so they also benefit from `unwrapResult` when the resolver can identify the tool. However, if the resolver doesn't recognize a string name (e.g., a dynamically constructed name), it may not be able to determine whether unwrapping is needed.

## API Reference

<Card icon="code" title="CallTool Parameters">
  <ParamField body="tool" type="str | Callable" required>
    Name of the server tool to call, or a callable reference to the tool function. Can be passed as a positional argument.
  </ParamField>

  <ParamField body="arguments" type="dict[str, Any]" default="{}">
    Arguments to pass to the tool. Values support `{{ key }}` interpolation to reference client-side state at call time.
  </ParamField>

  <ParamField body="on_success" type="Action | list[Action] | None" default="None">
    Action(s) to run when the tool call succeeds. The returned value is available as `$result`.
  </ParamField>

  <ParamField body="on_error" type="Action | list[Action] | None" default="None">
    Action(s) to run when the tool call fails. The error message is available as `$error`.
  </ParamField>
</Card>

## Protocol Reference

```json CallTool theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
{
  "action": "toolCall",
  "tool": "string (required)",
  "arguments?": "object"
}
```

For the complete protocol schema, see [CallTool](/protocol/call-tool).
