> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/api/en/preparation/client-tools.md).

# Client Tools

Let AI assistants call tools executed by your application (such as querying systems accessible only on an internal network). Learn how to declare client tools in the conversation API, identify model c

Client tools let AI assistants call tools that run in your application. You declare a list of tools each time you call the conversation API. When the model decides it needs a tool, MaiAgent returns which tool to call and what arguments to pass instead of executing it itself. Your application executes the tool locally and sends the result back to the conversation. The model then uses the result to continue answering or call the next tool.

Use client tools for actions only your application can perform, such as querying systems accessible only on your company's internal network, using a browser extension to interact with the currently open page, or accessing resources on a user's device through an app.

## Use Case <a href="#scenario" id="scenario"></a>

An engineer at an e-commerce company integrates an AI assistant into the company's internal customer service console. The order system is accessible only on the internal network. A customer service representative enters “A customer asks when order A1024 will arrive” in the console. When calling the conversation API, the console also declares an order lookup tool. The model returns which order to look up. The console retrieves the shipping status on the internal network and sends it back to the same conversation. The model uses this information to write a reply for the customer. The order system does not need to be exposed externally, and the console sends only the data the model needs.

## Requirements <a href="#requirements" id="requirements"></a>

Client tools are available to all organizations without a separate application. For the model to call them, both conditions must be met:

* The AI assistant's <mark style="color:blue;">response mode</mark> is set to <mark style="color:blue;">Agent mode</mark>.
* The model used by the AI assistant supports tool calling.

{% hint style="warning" %}
If these conditions are not met, the API does not return an error. It **silently ignores** the client tools you declare, and the model answers without them. If the model never calls your tools, check the assistant's response mode and model first.
{% endhint %}

## Workflow <a href="#flow" id="flow"></a>

{% stepper %}
{% step %}

### Declare Tools and Send a Message <a href="#declare" id="declare"></a>

Call `POST /api/v1/chatbots/{chatbotId}/completions/` and include tool definitions in `message.clientTools`.
{% endstep %}

{% step %}

### Receive a Tool Call Request <a href="#receive-call" id="receive-call"></a>

When the model decides to call your tool, an item with `type` set to `client_tool_call` appears in the response's `contentPayload.items`, and **the turn ends immediately**.
{% endstep %}

{% step %}

### Execute the Tool in Your Application <a href="#run-tool" id="run-tool"></a>

Execute the corresponding tool using `name` and `input`.
{% endstep %}

{% step %}

### Return the Result to the Same Conversation <a href="#send-result" id="send-result"></a>

Call the conversation API again with the same `conversation`, put the execution result in `message.content`, and **include `clientTools` again**. The model continues answering. If it needs other tools, it returns to step 2 until it produces the final text.
{% endstep %}
{% endstepper %}

## Declare Tools <a href="#tool-definition" id="tool-definition"></a>

```json
{
  "conversation": "<Conversation ID; optional for the first message>",
  "isStreaming": false,
  "message": {
    "content": "A customer asks when order A1024 will arrive.",
    "clientTools": [
      {
        "name": "get_order",
        "description": "Look up an order's shipping status and estimated delivery date by order number",
        "inputSchema": {
          "type": "object",
          "properties": {
            "order": { "type": "string", "description": "Order number" }
          },
          "required": ["order"]
        }
      }
    ]
  }
}
```

| Field       | Type   | Description                                                                                                                                                       |
| ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name        | string | Tool name. Only alphanumeric characters, underscores `_`, and hyphens `-` are allowed. Length must be 1–64 characters, and names must be unique within a request. |
| description | string | A description of the tool's purpose. The model uses it to decide when to use the tool, so write it clearly.                                                       |
| inputSchema | object | JSON Schema for the tool's arguments. Must not exceed 4,000 characters when serialized.                                                                           |

{% hint style="info" %}

* You can declare up to 20 client tools per request.
* The tool list is valid only for **that request** and is not stored in the conversation. Include `clientTools` again with every subsequent message, including messages that return tool results. Otherwise, the model cannot call these tools in that turn.
  {% endhint %}

## Tool Call Request Format <a href="#tool-call-format" id="tool-call-format"></a>

When the model requests a tool call, `contentPayload.items` includes an item like the following. There may be multiple items at once:

```json
{
  "type": "client_tool_call",
  "toolCallId": "call_abc123",
  "name": "get_order",
  "input": { "order": "A1024" },
  "startTimestamp": "2026-10-01T08:30:00.000000+00:00"
}
```

| Field          | Type           | Description                                                                       |
| -------------- | -------------- | --------------------------------------------------------------------------------- |
| type           | string         | Always `client_tool_call`.                                                        |
| toolCallId     | string \| null | Call identifier generated by the model.                                           |
| name           | string         | Name of the tool to execute, matching the `name` you declared.                    |
| input          | object         | Arguments supplied by the model, corresponding to the `inputSchema` you declared. |
| startTimestamp | string \| null | Time the call started.                                                            |

### Non-Streaming and Streaming Differences <a href="#streaming" id="streaming"></a>

* **Non-streaming** (`isStreaming: false`): Tool call items are in `contentPayload.items` in the response body (`done: true`).
* **Streaming** (`isStreaming: true`): Tool call items are sent as separate events. Each event has `done` set to `false`, an empty `content` string, and `client_tool_call` in `contentPayload.items`. The final `done: true` event does not repeat these items, so collect them during streaming.

{% hint style="warning" %}
All response field names are converted to camelCase, including argument names inside `input`. For example, `order_id` defined in your `inputSchema` becomes `orderId` in `input`. Use a single lowercase English word for argument names (such as `order` or `keyword`), or accept both forms when parsing.
{% endhint %}

## Return Execution Results <a href="#return-result" id="return-result"></a>

There is currently no dedicated endpoint for submitting tool results. Send the next regular message in **the same conversation**, putting the result in `message.content`:

```json
{
  "conversation": "<The same conversation ID>",
  "isStreaming": false,
  "message": {
    "content": "Execution result for tool get_order (toolCallId: call_abc123):\n<Lookup result>",
    "clientTools": [ "...The same tool definitions as in the previous request..." ]
  }
}
```

* **Multiple tool calls at once**: Execute all of them, then combine each tool's results into a single message.
* **Include the tool name and `toolCallId`**: The backend does not use `toolCallId` for matching, but including it in the result text helps the model identify which call the result belongs to.
* **Execution failure or user rejection**: Explain this in text as well, such as “get\_order failed: order not found” or “The user declined this operation.” The model adjusts its answer or uses another approach accordingly.
* **Result length**: Returned content counts toward the model's input. For large amounts of data, extract the required portions first.

## Call Multiple Tools Across Turns <a href="#multi-step" id="multi-step"></a>

Each cycle of “the model requests a call → you return the result” is one turn. The model can call tools over multiple turns until its response no longer contains `client_tool_call` and instead returns the final text. Set a turn limit in your application to prevent an infinite loop if something goes wrong.

## Frequently Asked Questions <a href="#faq" id="faq"></a>

<details>

<summary>The model never calls my tool</summary>

First, confirm that the assistant's response mode is Agent mode and that the model supports tool calling. If either condition is not met, tools are ignored without an error. Then check whether `description` clearly describes the tool's purpose and when to use it, and whether the message includes `clientTools`.

</details>

<details>

<summary>The model says it cannot use tools after I return a result</summary>

The message returning the result must also include `clientTools`. The tool list is valid only for the current request.

</details>

<details>

<summary>I receive a 400 error</summary>

Check the tool definitions: `name` must contain only alphanumeric characters, underscores, and hyphens, and must be unique; `description` must be a string; `inputSchema` must be an object and must not exceed 4,000 characters when serialized; and the number of tools must not exceed 20.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/api/en/preparation/client-tools.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
