> ## 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）

工具是模型执行任务时可以调用的能力。Qoder Agent SDK 支持两类工具：

* **内置工具**：由 Qoder CLI 提供，例如读文件、搜索、执行命令、调用子 Agent。
* **自定义工具**：由 SDK 使用者通过 `tool()` 和 `createSdkMcpServer()` 自定义工具并暴露给模型调用。

本文重点讲如何自定义工具。内置工具完整列表见 [Tools Reference - 内置工具列表](/zh/cli/sdk/references#内置工具列表)。

<div id="内置工具" />

## 内置工具

使用内置工具时，你不需要实现工具本身，只需要在 `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'],
  },
});
```

常用内置工具包括 `Read`、`Edit`、`Write`、`Bash`、`Glob`、`Grep`、`WebFetch`、`WebSearch`、`Agent` 等。完整清单和名称以 [Tools Reference - 内置工具列表](/zh/cli/sdk/references#内置工具列表) 为准；输入输出结构见 [内置工具输入输出类型](/zh/cli/sdk/references#内置工具输入输出类型)，例如 [`FileReadInput` / `FileReadOutput`](/zh/cli/sdk/references#filereadinput-filereadoutput)、[`BashInput` / `BashOutput`](/zh/cli/sdk/references#bashinput-bashoutput)、[`AgentInput` / `AgentOutput`](/zh/cli/sdk/references#agentinput-agentoutput)。如果需要在 TypeScript 层统一表示工具输入或输出，参考 [`ToolInputSchemas`](/zh/cli/sdk/references#toolinputschemas) 和 [`ToolOutputSchemas`](/zh/cli/sdk/references#tooloutputschemas)。

***

<div id="自定义工具" />

## 自定义工具

当你希望模型调用自己的业务能力时，可以自定义工具。例如查询订单、搜索内部知识库、调用审批系统、访问只读数据库等。

自定义工具通常分三步：

1. 用 `tool()` 创建工具。
2. 用 `createSdkMcpServer()` 把工具注册到一个 MCP server。
3. 在 `query({ options })` 中通过 `mcpServers` 接入，并用权限配置控制调用。

***

<div id="自定义工具接入步骤" />

## 自定义工具接入步骤

先看一个完整的最小示例，然后按三步拆开说明每一步可以配置什么：

```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="第一步使用-tool-创建工具" />

### 第一步：使用 `tool()` 创建工具

这一步负责定义工具本身，包括工具名、描述、输入参数、执行逻辑和工具元信息。

<div id="tool-入参" />

#### `tool()` 入参

`tool()` 用来定义一个工具。它有 5 个入参，完整类型见 [`tool()`](/zh/cli/sdk/references#tool)。

| 入参            | 类型                              | 是否必填 | 语义                                   |
| ------------- | ------------------------------- | ---- | ------------------------------------ |
| `name`        | `string`                        | 是    | 工具在当前 MCP server 内的唯一标识              |
| `description` | `string`                        | 是    | 给模型看的工具说明，描述工具何时使用、做什么、返回什么          |
| `inputSchema` | `Schema extends AnyZodRawShape` | 是    | 定义工具输入参数的 Zod raw shape              |
| `handler`     | Function                        | 是    | 接收解析后的参数并返回 `CallToolResult` 的异步执行函数 |
| `extras`      | `ToolExtras`                    | 否    | 工具额外元信息，目前用于传 `annotations`          |

<div id="配置输入参数" />

#### 配置输入参数

`inputSchema` 传 Zod raw shape，也就是字段对象，不是 `z.object(...)`。类型参考 [`AnyZodRawShape`](/zh/cli/sdk/references#anyzodrawshape)；handler 参数推导参考 [`InferShape`](/zh/cli/sdk/references#infershape)。

```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'),
}
```

常见写法：

| 需求    | 写法                                      |
| ----- | --------------------------------------- |
| 必填字符串 | `z.string().describe('...')`            |
| 可选参数  | `z.string().optional().describe('...')` |
| 默认值   | `z.number().default(5)`                 |
| 枚举值   | `z.enum(['docs', 'tickets'])`           |
| 数字范围  | `z.number().min(1).max(10)`             |

<div id="配置工具元信息" />

#### 配置工具元信息

`extras.annotations` 用来传 MCP 工具注解。SDK 会把它原样保存到工具定义上，并在 `createSdkMcpServer()` 注册工具时传给 MCP server。完整字段见 [`ToolExtras`](/zh/cli/sdk/references#toolextras) 和 [`ToolAnnotations`](/zh/cli/sdk/references#toolannotations)。

字段说明：

| 字段                | 类型        | 可选 | 语义             |
| ----------------- | --------- | -- | -------------- |
| `title`           | `string`  | 是  | 工具的人类可读标题      |
| `readOnlyHint`    | `boolean` | 是  | 标记工具只读，不修改任何状态 |
| `destructiveHint` | `boolean` | 是  | 标记工具可能修改或删除数据  |
| `openWorldHint`   | `boolean` | 是  | 标记工具会访问外部系统或网络 |

注意：这些字段不会替代权限配置。是否允许调用工具仍由 `tools`、`allowedTools`、`disallowedTools`、`permissionMode`、`canUseTool` 和 hooks 决定。当前 `mcpServerStatus().tools[]` 也不会把 annotations 回显给宿主应用；如果宿主 UI 需要展示这些信息，请在自己的工具定义侧保留映射。

示例

```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="第二步注册到-mcp-server" />

### 第二步：注册到 MCP server

[`createSdkMcpServer()`](/zh/cli/sdk/references#createsdkmcpserver) 把一个或多个工具注册为同进程 MCP server。server 名会进入完整工具名，因此建议短、稳定。

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

| 字段        | 怎么填                         | 说明                                      |
| --------- | --------------------------- | --------------------------------------- |
| `name`    | 如 `kb`、`orders`             | server 名，会组成完整工具名 `mcp__{name}__{tool}` |
| `version` | 如 `'1.0.0'`                 | 信息性版本号，可省略                              |
| `tools`   | `[searchDocs, lookupOrder]` | 注册到这个 server 的工具列表                      |

完整 option 类型见 [`CreateSdkMcpServerOptions`](/zh/cli/sdk/references#createsdkmcpserveroptions)，返回值见 [`createSdkMcpServer()` 返回值](/zh/cli/sdk/references#返回值)。

<div id="第三步接入-query" />

### 第三步：接入 `query()`

把 server 放进 `options.mcpServers` 后，CLI 会发现其中的工具，并在模型需要时通过 SDK 调回你的 handler。

```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'],
  },
});
```

自定义工具的完整名称格式是：

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

例如 server 名是 `orders`，tool 名是 `lookup_order`，完整工具名就是 `mcp__orders__lookup_order`。这个完整名称会用于 `allowedTools`、`disallowedTools`、`canUseTool`、hooks matcher 和子 Agent 的 `tools` 配置。

***

<div id="控制-tool-权限" />

## 控制 tool 权限

当模型调用工具时，SDK 提供多层权限控制。你可以决定：

* 本次会话提供哪些工具。
* 哪些工具可以默认放行。
* 哪些工具明确禁止。
* 每次工具调用前是否交给宿主应用动态判断。

<div id="权限控制方式总览" />

### 权限控制方式总览

| 方式                                                | 作用                  | 粒度  | 适用场景                    |
| ------------------------------------------------- | ------------------- | --- | ----------------------- |
| `tools`                                           | 限制本次会话可见工具集合        | 会话级 | 想从源头收窄模型能看到的工具          |
| `allowedTools` / `disallowedTools`                | 预授权或禁止指定工具          | 工具级 | 明确知道要允许或禁止哪些工具          |
| `permissionMode`                                  | 设置整次会话的默认权限策略       | 全局  | 快速切换计划模式、自动接受编辑、跳过权限等   |
| [`canUseTool`](/zh/cli/sdk/references#canusetool) | 每次调用前执行自定义判断逻辑      | 调用级 | 需要根据参数内容动态决策            |
| `hooks.PreToolUse`                                | 通过 hooks 生命周期拦截工具调用 | 调用级 | 已经使用 hooks 体系，需要统一审计或拦截 |

这些方式可以组合使用。常见做法是：先用 `tools` 收窄可见工具集合，再用 `allowedTools` / `disallowedTools` 设置静态规则，最后用 `canUseTool` 做参数级判断。

<div id="方式一toolsallowedtoolsdisallowedtools" />

### 方式一：`tools`、`allowedTools`、`disallowedTools`

`tools` 控制本次会话可见的工具集合；`allowedTools` 和 `disallowedTools` 控制权限规则。自定义 MCP 工具要使用完整工具名。

```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: [] },
});
```

当同一个工具同时匹配允许和禁止规则时，禁止规则优先。

<div id="方式二permissionmode" />

### 方式二：`permissionMode`

`permissionMode` 用一行配置设置整次会话的默认权限行为。

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

| 模式                    | 效果                                                                          |
| --------------------- | --------------------------------------------------------------------------- |
| `'default'`           | 标准权限行为，敏感操作按规则或运行时策略处理                                                      |
| `'acceptEdits'`       | 自动接受文件编辑类操作，其他敏感操作仍按权限策略处理                                                  |
| `'bypassPermissions'` | 跳过权限检查，必须同时设置 `allowDangerouslySkipPermissions: true`                       |
| `'yolo'`              | `'bypassPermissions'` 的兼容别名，也必须同时设置 `allowDangerouslySkipPermissions: true` |
| `'plan'`              | 计划模式，适合先让模型产出方案                                                             |
| `'dontAsk'`           | 不进行交互询问，未预授权或未被规则允许的操作会被拒绝                                                  |
| `'auto'`              | 由运行时能力自动判断 allow 或 deny                                                     |

<div id="方式三canusetool" />

### 方式三：`canUseTool`

[`canUseTool`](/zh/cli/sdk/references#canusetool) 会在工具调用前执行。你可以根据工具名、参数内容和审批上下文返回允许或拒绝。

```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,
      };
    },
  },
});
```

常见返回值：

| 返回                                                         | 效果                  |
| ---------------------------------------------------------- | ------------------- |
| `{ behavior: 'allow' }`                                    | 允许执行，使用原始参数         |
| `{ behavior: 'allow', updatedInput: {...} }`               | 允许执行，并替换工具参数        |
| `{ behavior: 'deny', message: 'reason' }`                  | 拒绝执行，模型能看到原因并尝试其他方式 |
| `{ behavior: 'deny', message: 'reason', interrupt: true }` | 拒绝并中断当前 agent loop  |

在子 Agent 中使用自定义工具时，也使用完整工具名：

```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="方式四hookspretooluse" />

### 方式四：`hooks.PreToolUse`

如果你已经使用 hooks 体系，可以通过 `PreToolUse` 统一拦截或审计工具调用。

```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',
                },
              };
            },
          ],
        },
      ],
    },
  },
});
```

`canUseTool` 的参数结构见 [`CanUseToolOptions`](/zh/cli/sdk/references#canusetooloptions)，返回结构见 [`PermissionResult`](/zh/cli/sdk/references#permissionresult)。更完整的权限策略见 [权限控制](/zh/cli/sdk/permissions)。

***

<div id="sdk-如何处理-tool-返回的错误" />

## SDK 如何处理 tool 返回的错误

工具 handler 有两类错误路径。

<div id="业务失败返回-iserror-true" />

### 业务失败：返回 `isError: true`

可预期的业务失败推荐返回 `isError: true`。SDK 会把这个 [`CallToolResult`](/zh/cli/sdk/references#calltoolresult) 交给 CLI，模型能看到失败内容，并可能重试或选择其他方式。

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

适合使用 `isError: true` 的场景：

* 参数合法但业务上找不到结果，例如订单不存在。
* 安全策略拒绝执行，例如只允许 `SELECT` 查询。
* 外部服务返回可理解的业务错误。

<div id="非预期异常handler-抛错" />

### 非预期异常：handler 抛错

如果 handler 抛出异常，MCP 层会把异常转换成错误结果，agent loop 不会因为普通工具异常直接崩掉。但模型通常只能看到异常消息，格式和内容不如显式返回 `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() }] };
  },
);
```

建议：业务上可预期的失败用 `isError: true`；真正意外的异常再抛出。

***

<div id="tool-返回值" />

## Tool 返回值

工具 handler 返回 MCP 的 [`CallToolResult`](/zh/cli/sdk/references#calltoolresult)。最常用的是文本内容：

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

也可以返回结构化 JSON 字符串，方便模型理解和继续处理：

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

常见内容块；完整联合类型见 [`McpToolResultContent`](/zh/cli/sdk/references#mcptoolresultcontent)：

| 类型   | 形状                                                               | 说明                   |
| ---- | ---------------------------------------------------------------- | -------------------- |
| 文本   | `{ type: 'text', text }`                                         | 最常用，适合自然语言或 JSON 字符串 |
| 图片   | `{ type: 'image', data, mimeType }`                              | `data` 是 base64      |
| 音频   | `{ type: 'audio', data, mimeType }`                              | `data` 是 base64      |
| 资源链接 | `{ type: 'resource_link', uri, name?, description?, mimeType? }` | 返回可引用资源              |
| 内嵌资源 | `{ type: 'resource', resource }`                                 | 返回文本或二进制资源内容         |

完整类型定义见 [Tools Reference - CallToolResult](/zh/cli/sdk/references#calltoolresult)。

***

<div id="常见踩坑" />

## 常见踩坑

* 权限配置里写自定义工具时，要写 `mcp__server__tool` 完整名称。
* `tool()` 的第三个参数传 Zod raw shape，不要传 `z.object(...)`。
* 工具描述要写“什么时候用、做什么、返回什么”，不要只写 `query`、`helper` 这类模糊描述。
* `readOnlyHint` 是工具元信息和调度提示，不是权限开关；是否允许执行仍由权限配置决定。
* 不要把大而全的业务入口都塞进一个万能工具。一个工具最好完成一类清晰动作。

***

<div id="继续阅读" />

## 继续阅读

* [Tools Reference](/zh/cli/sdk/references)：内置工具列表、`tool()`、`createSdkMcpServer()`、`CallToolResult`、内置工具输入输出类型。
* [MCP 集成](/zh/cli/sdk/mcp)：stdio、SSE、HTTP、OAuth 等 MCP server 接入方式。
* [权限控制](/zh/cli/sdk/permissions)：`permissionMode`、`allowedTools`、`canUseTool`、权限规则更新。
* [子 Agent 使用指南](/zh/cli/sdk/agents)：让不同 Agent 使用不同工具集。
