# 给智能体的工具（MCP）

> 在清单中声明工具、在卡片中实现它，让相连的智能体通过 NeuroSquad 的 MCP 服务器调用——结果、图片、错误、进度、取消和超时。

Source: https://docs.neurosquad.ai/zh/card-sdk/api/tools

卡片可以赋予智能体新的能力。在清单中声明一个工具并在卡片中实现它，所有通过箭头与卡片相连的智能体都会在工具列表中看到它——
经由应用本来就为智能体运行的 MCP 服务器（Claude Code、Codex、Qwen Code）。不需要任何权限：用户画出的箭头就是同意。

## 声明

```json
"tools": [
  {
    "name": "run_tests",
    "title": "Run the tests",
    "description": "Runs the project's tests in the connected terminal and returns the failing tests with their messages, or 'All tests passed'.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "filter": { "type": "string", "maxLength": 200, "description": "Only tests whose name contains this" }
      }
    },
    "timeoutMs": 120000
  }
]
```

智能体看到的名称是 **`<card name with - → _>_<tool>`**——名为 `test-radar` 的卡片对应 `test_radar_run_tests`——
参数是你的 `inputSchema`，外加应用添加的可选参数 `card`（id 或名称，用于在连了多张副本时指定其中一张）。
描述在交给模型时会加上前缀 `[Custom card "<displayName>" by <author>, community code]`。如果两个卡片包会产生同一个名称，先安装的那个保留它，后安装的会加上 `_2`、`_3`。

应用在安装时会拒绝的名称：与 NeuroSquad 自有工具（`canvas_spawn_card`、`terminal_send_keys`、`browser_navigate`……）相同的对外名称、
任何包含 `__` 的名称（为用户安装的 MCP 服务器保留），以及属于内置工具族的卡片 `name`，例如 `telegram`、`squad`、`ports` 或 `mcp`
（参见[保留名](https://docs.neurosquad.ai/zh/card-sdk/manifest#identity)）。新增工具或改变工具描述措辞的更新会再次征求用户同意。

描述是写给模型看的：这个工具做什么、何时使用、返回什么。参数要少且有约束（`enum`、`maxLength`、`minimum`……）；应用会在你的卡片看到之前校验它们。

## 实现

```ts
import { toolError } from '@neurosquad/card-sdk'

card.tools.handle<{ filter?: string }>('run_tests', async ({ filter }, call) => {
  call.progress('running the tests…')                  // shown on the arrow
  const terminal = (await card.agents.list()).find((a) => a.kind === 'shell' && a.connected)
  if (!terminal) return toolError('Connect a terminal card to the Test radar card first.')

  const result = await card.terminals.run(terminal.id, `npm test -- ${filter ?? ''}`, {
    timeoutMs: 110_000
  })
  if (call.signal.aborted) return                       // the agent gave up; nothing is sent
  return result.exitCode === 0 ? 'All tests passed' : result.output
})
```

处理函数会收到校验过的参数和一个 `call`：

| `call.` | 说明 |
| --- | --- |
| `callId` | 本次调用的 id。 |
| `tool` | 清单中的工具名。 |
| `agent` | 发起调用的智能体的 `{ id, name }`。 |
| `deadline` | 应用放弃等待的时间戳（毫秒）。 |
| `signal` | 一个 `AbortSignal`，在取消或到达截止时间时中止。 |
| `progress(message)` | 工作期间在箭头上显示的一行短文字（≤ 200）；限流为每秒 4 次。 |

**你的返回值**就是工具结果：

| 返回 | 智能体得到 |
| --- | --- |
| 字符串 | 这段文本 |
| `undefined` | `OK` |
| 其他任意 JSON 值 | 格式化后的 JSON 文本 |
| `toolText(text)` | 文本（与返回字符串相同） |
| `toolImage(base64, 'image/png', caption?)` | 一张图片，以及作为文本的说明文字 |
| `toolError(message)` | 一个失败结果（`isError: true`），模型可以读到并作出反应 |
| `ToolResultPayload` | 原样：`{ content: [{ type: 'text', text } or { type: 'image', data, mimeType }], isError? }`，1–16 个部分 |

**抛出异常**也会把错误返回给智能体，内容为异常消息（并在卡片日志中写一条警告）。结果 ≤ 1 MB。

## 时间

- 默认超时 30 秒，或工具的 `timeoutMs`（最长 120 秒）。到达截止时间时，应用告诉智能体调用超时，并向你的卡片发送 `tools.cancel`——
`call.signal` 会中止，你之后返回的任何内容都会被丢弃。
- 在你的处理函数注册之前到达的调用（例如卡片仍在加载数据时）会等待 `handle()`，直到截止时间。
- 调用页面已暂停的卡片会唤醒它（最多 10 秒）；如果它所在的工作区没有打开，智能体会被告知去打开包含这张卡片的工作区。
- 每次调用都会显示在箭头上：箭头标签显示工具名和你的进度文字，并记入[箭头日志](https://docs.neurosquad.ai/zh/canvas/arrows#arrow-log)。

## 开关工具

```ts
await card.tools.setEnabled('run_tests', false) // hidden from agents (e.g. until the user signs in)
await card.tools.setEnabled('run_tests', true)
```

这会作用于你卡片的所有副本。

## 配合 React

```tsx
import { useTool } from '@neurosquad/card-sdk/react'

export function Scratchpad({ text }: { text: string }) {
  useTool('read_scratchpad', () => text || '(empty)') // the latest `text` is always used
  return <pre>{text}</pre>
}
```

> 工具结果会直接进入智能体的上下文。你返回的任何来自网页、文件或用户的内容都应视为不可信：注明来源、保持简短，
> 并且绝不要让智能体能调用的工具悄悄做出破坏性操作——先用 `card.ui.confirm` 询问用户。
