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

# Tools

Tools are capabilities the model can call while executing a task. The Qoder Agent SDK supports two kinds of tools:

* **Built-in tools**: Provided by Qoder CLI, such as reading files, searching, executing commands, and invoking subagents.
* **Custom tools**: Defined by SDK users through `tool()` and `createSdkMcpServer()`, then exposed to the model.

This guide focuses on defining custom tools. For the complete built-in tool list, see [Tools Reference - Built-in Tool List](/en/cli/sdk/references#built-in-tool-list).

<div id="built-in-tools" />

## Built-in Tools

When using built-in tools, you do not implement the tools yourself. You only control which tools are available, which tools are pre-authorized, and which tools are denied for the current session through `query()` options.

```typescript theme={null}
query({
  prompt: 'Read this repository and summarize risks in the authentication module. Do not modify files.',
  options: {
    auth: accessTokenFromEnv(),
    cwd: '/path/to/project',
    tools: ['Read', 'Grep', 'Glob'],
    allowedTools: ['Read', 'Grep', 'Glob'],
  },
});
```

Common built-in tools include `Read`, `Edit`, `Write`, `Bash`, `Glob`, `Grep`, `WebFetch`, `WebSearch`, and `Agent`. The complete list and names are defined by [Tools Reference - Built-in Tool List](/en/cli/sdk/references#built-in-tool-list). Input and output structures are documented in [Built-in Tool Input and Output Types](/en/cli/sdk/references#built-in-tool-input-and-output-types), such as [`FileReadInput` / `FileReadOutput`](/en/cli/sdk/references#filereadinput-filereadoutput), [`BashInput` / `BashOutput`](/en/cli/sdk/references#bashinput-bashoutput), and [`AgentInput` / `AgentOutput`](/en/cli/sdk/references#agentinput-agentoutput). If you need TypeScript-level unified representations, see [`ToolInputSchemas`](/en/cli/sdk/references#toolinputschemas) and [`ToolOutputSchemas`](/en/cli/sdk/references#tooloutputschemas).

***

<div id="custom-tools" />

## Custom Tools

Define a custom tool when you want the model to call your own business capability, such as order lookup, internal knowledge base search, approval system calls, or read-only database access.

Custom tools usually involve three steps:

1. Create a tool with `tool()`.
2. Register the tool in an MCP server with `createSdkMcpServer()`.
3. Attach the server through `mcpServers` in `query({ options })`, and control calls with permission settings.

***

<div id="custom-tool-integration-steps" />

## Custom Tool Integration Steps

First, here is a complete minimal example. The following sections then explain each step.

```typescript theme={null}
import {
  accessTokenFromEnv,
  createSdkMcpServer,
  query,
  tool,
} from '@qoder-ai/qoder-agent-sdk';
import { z } from 'zod';

const lookupOrder = tool(
  'lookup_order',
  'Look up an order by order ID.',
  {
    orderId: z.string().describe('Order ID, such as O-1001'),
  },
  async ({ orderId }) => {
    const order = await orders.find(orderId);

    if (!order) {
      return {
        isError: true,
        content: [{ type: 'text', text: `Order not found: ${orderId}` }],
      };
    }

    return {
      content: [{ type: 'text', text: JSON.stringify(order) }],
    };
  },
  { annotations: { readOnlyHint: true } },
);

const orderTools = createSdkMcpServer({
  name: 'orders',
  tools: [lookupOrder],
});

const messages = query({
  prompt: 'Check the status of order O-1001 and summarize it in one sentence.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
  },
});

for await (const message of messages) {
  if (message.type === 'result') {
    console.log(message.result);
  }
}
```

<div id="step-1-create-a-tool-with-tool" />

### Step 1: Create a Tool with `tool()`

This step defines the tool itself: its name, description, input parameters, execution logic, and metadata.

<div id="tool-arguments" />

#### `tool()` Arguments

`tool()` defines a tool. It has five arguments. See [`tool()`](/en/cli/sdk/references#tool) for the complete type.

| Argument      | Type                            | Required | Meaning                                                                                   |
| ------------- | ------------------------------- | -------- | ----------------------------------------------------------------------------------------- |
| `name`        | `string`                        | Yes      | Unique tool identifier within the current MCP server                                      |
| `description` | `string`                        | Yes      | Tool description for the model; explain when to use it, what it does, and what it returns |
| `inputSchema` | `Schema extends AnyZodRawShape` | Yes      | Zod raw shape that defines tool input parameters                                          |
| `handler`     | Function                        | Yes      | Async execution function that receives parsed parameters and returns `CallToolResult`     |
| `extras`      | `ToolExtras`                    | No       | Extra tool metadata, currently used for `annotations`                                     |

<div id="configure-input-parameters" />

#### Configure Input Parameters

`inputSchema` takes a Zod raw shape, meaning a field object, not `z.object(...)`. See [`AnyZodRawShape`](/en/cli/sdk/references#anyzodrawshape) for the type and [`InferShape`](/en/cli/sdk/references#infershape) for handler parameter inference.

```typescript theme={null}
{
  query: z.string().describe('Search keywords'),
  maxResults: z.number().int().min(1).max(10).optional()
    .describe('Maximum number of snippets to return'),
  source: z.enum(['docs', 'tickets', 'wiki']).default('docs')
    .describe('Where to search'),
}
```

Common patterns:

| Need               | Pattern                                 |
| ------------------ | --------------------------------------- |
| Required string    | `z.string().describe('...')`            |
| Optional parameter | `z.string().optional().describe('...')` |
| Default value      | `z.number().default(5)`                 |
| Enum               | `z.enum(['docs', 'tickets'])`           |
| Numeric range      | `z.number().min(1).max(10)`             |

<div id="configure-tool-metadata" />

#### Configure Tool Metadata

`extras.annotations` passes MCP tool annotations. The SDK keeps them on the tool definition and forwards them when registering the tool with the MCP server. See [`ToolExtras`](/en/cli/sdk/references#toolextras) and [`ToolAnnotations`](/en/cli/sdk/references#toolannotations) for the complete fields.

| Field             | Type      | Optional | Meaning                                                   |
| ----------------- | --------- | -------- | --------------------------------------------------------- |
| `title`           | `string`  | Yes      | Human-readable title for the tool                         |
| `readOnlyHint`    | `boolean` | Yes      | Marks the tool as read-only and not modifying state       |
| `destructiveHint` | `boolean` | Yes      | Marks that the tool may modify or delete data             |
| `openWorldHint`   | `boolean` | Yes      | Marks that the tool accesses external systems or networks |

These fields do not replace permission configuration. Whether a tool call is allowed is still determined by `tools`, `allowedTools`, `disallowedTools`, `permissionMode`, `canUseTool`, and hooks. Current `mcpServerStatus().tools[]` does not echo annotations back to the host application. If your host UI needs to show this information, keep your own mapping next to the tool definition.

Example:

```typescript theme={null}
tool(
  'search_docs',
  'Search internal product documentation.',
  { query: z.string().describe('Search keywords') },
  async ({ query }) => ({ content: [{ type: 'text', text: query }] }),
  {
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
  },
);
```

<div id="step-2-register-with-an-mcp-server" />

### Step 2: Register with an MCP Server

[`createSdkMcpServer()`](/en/cli/sdk/references#createsdkmcpserver) registers one or more tools as an in-process MCP server. The server name becomes part of the full tool name, so keep it short and stable.

```typescript theme={null}
const kbTools = createSdkMcpServer({
  name: 'kb',
  version: '1.0.0',
  tools: [searchDocs],
});
```

| Field     | How to set it               | Description                                                   |
| --------- | --------------------------- | ------------------------------------------------------------- |
| `name`    | For example `kb`, `orders`  | Server name; forms full tool names like `mcp__{name}__{tool}` |
| `version` | For example `'1.0.0'`       | Informational version, optional                               |
| `tools`   | `[searchDocs, lookupOrder]` | Tools registered to this server                               |

See [`CreateSdkMcpServerOptions`](/en/cli/sdk/references#createsdkmcpserveroptions) for the full option type and [`createSdkMcpServer()` return value](/en/cli/sdk/references#createsdkmcpserver-return-value) for the return value.

<div id="step-3-attach-to-query" />

### Step 3: Attach to `query()`

After you put the server in `options.mcpServers`, the CLI discovers its tools and calls back into your handler through the SDK when the model needs them.

```typescript theme={null}
query({
  prompt: 'Search docs for the refund policy and summarize it.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { kb: kbTools },
    allowedTools: ['mcp__kb__search_docs'],
  },
});
```

The full custom tool name format is:

```text theme={null}
mcp__{serverName}__{toolName}
```

For example, if the server name is `orders` and the tool name is `lookup_order`, the full name is `mcp__orders__lookup_order`. Use this full name in `allowedTools`, `disallowedTools`, `canUseTool`, hook matchers, and subagent `tools` configuration.

***

<div id="controlling-tool-permissions" />

## Controlling Tool Permissions

When the model calls tools, the SDK provides multiple permission layers. You can decide:

* Which tools are provided to the current session.
* Which tools are allowed by default.
* Which tools are explicitly denied.
* Whether the host application should make a dynamic decision before each tool call.

<div id="permission-control-overview" />

### Permission Control Overview

| Method                                            | Purpose                                                  | Granularity | Use case                                                    |
| ------------------------------------------------- | -------------------------------------------------------- | ----------- | ----------------------------------------------------------- |
| `tools`                                           | Limits the visible tool set for this session             | Session     | Narrow the tools the model can see at the source            |
| `allowedTools` / `disallowedTools`                | Pre-authorizes or denies specific tools                  | Tool        | You know exactly which tools to allow or deny               |
| `permissionMode`                                  | Sets the default permission policy for the whole session | Global      | Quickly switch plan mode, accept edits, or skip permissions |
| [`canUseTool`](/en/cli/sdk/references#canusetool) | Runs custom logic before each call                       | Call        | Decide dynamically based on arguments                       |
| `hooks.PreToolUse`                                | Intercepts tool calls through the hooks lifecycle        | Call        | You already use hooks and want shared auditing or blocking  |

These methods can be combined. A common pattern is to use `tools` to narrow the visible set, `allowedTools` / `disallowedTools` for static rules, and `canUseTool` for argument-level decisions.

<div id="method-1-tools-allowedtools-disallowedtools" />

### Method 1: `tools`, `allowedTools`, `disallowedTools`

`tools` controls the tools visible to this session. `allowedTools` and `disallowedTools` control permission rules. Custom MCP tools must use full names.

```typescript theme={null}
// Only expose read/search tools to this session.
query({
  prompt: 'Analyze the repository without editing files.',
  options: {
    tools: ['Read', 'Glob', 'Grep'],
    allowedTools: ['Read', 'Glob', 'Grep'],
  },
});

// Explicitly deny high-risk tools.
query({
  prompt: 'Review the project and report issues.',
  options: {
    disallowedTools: ['Bash', 'Write', 'Edit'],
  },
});

// Use full names for custom MCP tools.
query({
  prompt: 'Check order O-1001.',
  options: {
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
  },
});

// Disable all tools. The model can only answer from its context.
query({
  prompt: 'Explain what this SDK does at a high level.',
  options: { tools: [] },
});
```

When the same tool matches both allow and deny rules, the deny rule takes precedence.

<div id="method-2-permissionmode" />

### Method 2: `permissionMode`

`permissionMode` sets the default permission behavior for the whole session with one option.

```typescript theme={null}
query({
  prompt: 'Refactor the code.',
  options: {
    permissionMode: 'acceptEdits',
  },
});
```

| Mode                  | Effect                                                                                                    |
| --------------------- | --------------------------------------------------------------------------------------------------------- |
| `'default'`           | Standard permission behavior; sensitive operations are handled by rules or runtime policy                 |
| `'acceptEdits'`       | Automatically accepts file edit operations; other sensitive operations still follow the permission policy |
| `'bypassPermissions'` | Skips permission checks; must also set `allowDangerouslySkipPermissions: true`                            |
| `'yolo'`              | Compatibility alias for `'bypassPermissions'`; must also set `allowDangerouslySkipPermissions: true`      |
| `'plan'`              | Plan mode, suitable for asking the model to produce a plan first                                          |
| `'dontAsk'`           | Does not ask interactively; operations that are not pre-authorized or allowed by rules are denied         |
| `'auto'`              | Runtime capability decides allow or deny automatically                                                    |

<div id="method-3-canusetool" />

### Method 3: `canUseTool`

[`canUseTool`](/en/cli/sdk/references#canusetool) runs before a tool call. You can allow or deny based on the tool name, arguments, and approval context.

```typescript theme={null}
query({
  prompt: 'Check order O-1001.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
    async canUseTool(toolName, input, options) {
      if (toolName !== 'mcp__orders__lookup_order') {
        return {
          behavior: 'deny',
          message: 'Only order lookup is allowed in this workflow.',
          toolUseID: options.toolUseID,
        };
      }

      return {
        behavior: 'allow',
        updatedInput: input,
        toolUseID: options.toolUseID,
      };
    },
  },
});
```

Common return values:

| Return                                                     | Effect                                                             |
| ---------------------------------------------------------- | ------------------------------------------------------------------ |
| `{ behavior: 'allow' }`                                    | Allows execution with the original arguments                       |
| `{ behavior: 'allow', updatedInput: {...} }`               | Allows execution and replaces tool arguments                       |
| `{ behavior: 'deny', message: 'reason' }`                  | Denies execution; the model can see the reason and try another way |
| `{ behavior: 'deny', message: 'reason', interrupt: true }` | Denies and interrupts the current agent loop                       |

When using custom tools in a subagent, use the full tool name as well:

```typescript theme={null}
query({
  prompt: 'Use the order-support agent to check order O-1001.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['Agent'],
    agents: {
      'order-support': {
        description: 'Handles order lookup and explains order status.',
        prompt: 'Use order tools to answer order status questions clearly.',
        tools: ['mcp__orders__lookup_order'],
      },
    },
  },
});
```

<div id="method-4-hookspretooluse" />

### Method 4: `hooks.PreToolUse`

If you already use the hooks system, use `PreToolUse` to intercept or audit tool calls in one place.

```typescript theme={null}
query({
  prompt: 'Run the test command.',
  options: {
    allowedTools: ['Bash'],
    hooks: {
      PreToolUse: [
        {
          matcher: 'Bash',
          hooks: [
            async (input) => {
              const command = (input.tool_input as { command: string }).command;
              if (command.includes('rm -rf')) {
                return {
                  hookSpecificOutput: {
                    hookEventName: 'PreToolUse',
                    permissionDecision: 'deny',
                    permissionDecisionReason: 'rm -rf is not allowed',
                  },
                };
              }

              return {
                hookSpecificOutput: {
                  hookEventName: 'PreToolUse',
                  permissionDecision: 'allow',
                },
              };
            },
          ],
        },
      ],
    },
  },
});
```

For `canUseTool` parameter structure, see [`CanUseToolOptions`](/en/cli/sdk/references#canusetooloptions). For return structure, see [`PermissionResult`](/en/cli/sdk/references#permissionresult). For a more complete permission strategy, see [Permissions](/en/cli/sdk/permissions).

***

<div id="how-the-sdk-handles-tool-errors" />

## How the SDK Handles Tool Errors

Tool handlers have two error paths.

<div id="business-failure-return-iserror-true" />

### Business Failure: Return `isError: true`

For expected business failures, return `isError: true`. The SDK passes this [`CallToolResult`](/en/cli/sdk/references#calltoolresult) to the CLI. The model can see the failure content and may retry or choose another path.

```typescript theme={null}
return {
  isError: true,
  content: [{
    type: 'text',
    text: JSON.stringify({
      error: 'VALIDATION_ERROR',
      message: 'Only SELECT statements are allowed.',
    }),
  }],
};
```

Good cases for `isError: true`:

* Arguments are valid, but no business result exists, such as an order not found.
* A security policy rejects execution, such as only allowing `SELECT` queries.
* An external service returns a business error that can be explained.

<div id="unexpected-exception-handler-throws" />

### Unexpected Exception: Handler Throws

If a handler throws, the MCP layer converts the exception into an error result, and the agent loop does not crash just because a normal tool exception occurred. However, the model usually only sees the exception message, which is less controllable than explicitly returning `isError: true`.

```typescript theme={null}
const toolThatMayThrow = tool(
  'fetch_user',
  'Fetch a user by ID.',
  { userId: z.string() },
  async ({ userId }) => {
    const response = await userService.fetch(userId);
    if (!response.ok) {
      throw new Error('User service failed');
    }
    return { content: [{ type: 'text', text: await response.text() }] };
  },
);
```

Recommendation: use `isError: true` for predictable business failures, and throw only for truly unexpected exceptions.

***

<div id="tool-return-values" />

## Tool Return Values

Tool handlers return MCP [`CallToolResult`](/en/cli/sdk/references#calltoolresult). Text content is the most common:

```typescript theme={null}
return {
  content: [{ type: 'text', text: 'done' }],
};
```

You can also return structured JSON strings, which help the model understand and continue processing:

```typescript theme={null}
return {
  content: [{
    type: 'text',
    text: JSON.stringify({
      orderId: 'O-1001',
      status: 'shipped',
      eta: '2026-05-20',
    }),
  }],
};
```

Common content blocks; see [`McpToolResultContent`](/en/cli/sdk/references#mcptoolresultcontent) for the complete union type:

| Type              | Shape                                                            | Description                                                |
| ----------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- |
| Text              | `{ type: 'text', text }`                                         | Most common; suitable for natural language or JSON strings |
| Image             | `{ type: 'image', data, mimeType }`                              | `data` is base64                                           |
| Audio             | `{ type: 'audio', data, mimeType }`                              | `data` is base64                                           |
| Resource link     | `{ type: 'resource_link', uri, name?, description?, mimeType? }` | Returns a referenceable resource                           |
| Embedded resource | `{ type: 'resource', resource }`                                 | Returns text or binary resource content                    |

See [Tools Reference - CallToolResult](/en/cli/sdk/references#calltoolresult) for complete type definitions.

***

<div id="common-pitfalls" />

## Common Pitfalls

* When writing permission configuration for custom tools, use the full `mcp__server__tool` name.
* Pass a Zod raw shape as the third argument to `tool()`, not `z.object(...)`.
* Tool descriptions should explain when to use the tool, what it does, and what it returns. Avoid vague descriptions like `query` or `helper`.
* `readOnlyHint` is tool metadata and a scheduling hint, not a permission switch. Whether execution is allowed is still determined by permission configuration.
* Avoid putting a huge all-purpose business entry point into one universal tool. A tool should complete one clear class of action.

***

<div id="continue-reading" />

## Continue Reading

* [Tools Reference](/en/cli/sdk/references): Built-in tool list, `tool()`, `createSdkMcpServer()`, `CallToolResult`, and built-in tool input/output types.
* [MCP Integration](/en/cli/sdk/mcp): stdio, SSE, HTTP, OAuth, and other MCP server integration methods.
* [Permissions](/en/cli/sdk/permissions): `permissionMode`, `allowedTools`, `canUseTool`, and permission rule updates.
* [Subagent Guide](/en/cli/sdk/agents): Let different agents use different tool sets.
