# Инструменты для агентов (MCP)

> Объявите инструмент в манифесте, реализуйте его в карточке — и подключённые агенты смогут вызывать его через MCP-сервер NeuroSquad; результаты, картинки, ошибки, прогресс, отмена и тайм-ауты.

Source: https://docs.neurosquad.ai/ru/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_run_tests` для карточки с именем
`test-radar` — с вашей `inputSchema` и необязательным аргументом `card`, который добавляет приложение
(идентификатор или имя — чтобы выбрать одну карточку, когда подключено несколько копий). Описание
доходит до модели с префиксом `[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/ru/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` | Идентификатор вызова. |
| `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 МБ.

## Время

- Тайм-аут по умолчанию 30 с или `timeoutMs` инструмента (до 120 с). По дедлайну приложение сообщает
агенту, что время вышло, и отправляет карточке `tools.cancel` — `call.signal` срабатывает, а всё,
что вы вернёте после этого, отбрасывается.
- Вызовы, пришедшие до регистрации обработчика (например, пока карточка ещё грузит данные), ждут
`handle()` до своего дедлайна.
- Вызов к карточке, чья страница усыплена, будит её (до 10 с); если её воркспейс не открыт, агенту
говорят открыть воркспейс, где лежит эта карточка.
- Каждый вызов идёт по стрелке: её подпись показывает инструмент и ваши строки прогресса, а сам
вызов остаётся в [журнале стрелки](https://docs.neurosquad.ai/ru/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`.
