Skip to documentation
svelte/elevenlabs Docs

Docs Client tools

Client tools

Register named tools, share them through context, answer MCP approval requests, and observe agent side tool calls.

How tools work

A client tool is a function the agent can ask your browser to run. The server sends a client_tool_call event with a tool name, an argument object, a call id, and an expects_response flag. The controller looks up a handler, executes it, converts the return value to a protocol string, and sends a client_tool_result when the server is waiting for one.

  • Named tools are matched by the key in clientTools or by registerClientTool.
  • The fallback receives every call that has no named handler.
  • Unhandled calls still receive an error result and trigger onUnhandledClientToolCall.

Return values follow a simple rule: a string is sent unchanged, undefined or null becomes Client tool execution successful., and every other value is serialized with JSON.stringify. A thrown error produces an error result with error_type: 'external_client'.

Named tools

TS
const conversation = useConversation({  agentId: 'agent_your_public_id',  clientTools: {    get_order: async ({ orderId }) => {      const id = String(orderId ?? '');      const order = await fetchOrder(id);      return { id, status: order.status };    }  }});

The first argument is typed as Record<string, unknown>, so narrow each field before using it. The second argument is the full ClientToolCallRequest, including tool_call_id and the raw parameters.

Register a tool later in the session when the handler depends on current component state:

TS
const remove = conversation.registerClientTool('get_order', async ({ orderId }) => {  return { orderId, status: 'shipped' };});// The registration is removed when you call the returned function.remove();

Named tools always win over the fallback handler.

Provider tools

useConversationClientTool registers a named tool on the nearest ConversationProvider. The registration follows the component lifecycle and is removed on unmount, which keeps tool ownership close to the UI that implements it.

ToolRow.svelte
<script lang="ts">  import { useConversationClientTool } from 'svelte-elevenlabs';  useConversationClientTool('get_order', async ({ orderId }) => {    return { orderId, status: 'shipped' };  });</script><span>Tool ready</span>
App.svelte
<script lang="ts">  import { ConversationProvider } from 'svelte-elevenlabs';  import ToolRow from './ToolRow.svelte';</script><ConversationProvider options={{ agentId: 'agent_your_public_id' }}>  <ToolRow /></ConversationProvider>

Call the hook during component initialisation, directly in <script>. It reads the provider context, so it must render inside a provider. Use getConversationContext() when a component needs the controller itself.

Fallback handler

TS
const conversation = useConversation({  agentId: 'agent_your_public_id',  onClientToolCall: async (call) => {    return { tool: call.tool_name, parameters: call.parameters };  },  onUnhandledClientToolCall: (call) => {    console.warn('No handler for', call.tool_name);  }});

The fallback is useful for a small number of dynamic tools or for a generic proxy to your backend. It does not replace named tools: a matching named handler is always preferred. Keep the fallback fast and avoid returning secrets or large payloads.

MCP approvals

When an MCP tool call reaches the awaiting_approval state, the controller can ask your UI for a decision. The handler must resolve to a boolean. Anything else is treated as a denial and publishes a protocol-error.

TS
const conversation = useConversation({  agentId: 'agent_your_public_id',  onMCPToolApprovalRequest: async (call, { signal }) => {    const approved = await confirmInYourUi(call.tool_name, call.parameters);    if (signal.aborted) return false;    return approved;  }});

The controller sends exactly one mcp_tool_approval_result per call. The signal aborts when the session ends or the call leaves the approval state, so a late answer cannot reach the server.

If the decision belongs to a component that is not the controller owner, call sendMCPToolApprovalResult(toolCallId, approved) from that component after the user acts. onMCPToolCall and onMCPConnectionStatus expose the lifecycle updates for your approval UI.

Agent tools

Server side tool calls are observable but not executed by the browser:

  • onAgentToolRequest receives the request before the agent tool runs.
  • onAgentToolResponse receives the completed result.
  • onGuardrailTriggered reports a guardrail that fired during the turn.

The built-in end_call tool is honored automatically: when the agent reports it, the controller stops the session and preserves the transcript.

Debugging

Set debug: true to print controller diagnostics through console.debug. For a structured stream inside your own panel, use onDebug plus onIncomingEvent and onOutgoingEvent.

The protocol types for every incoming and outgoing frame live in API reference. Start there when a tool call has an unexpected shape.

Edit this page on GitHub