# MCP Server

## Подключение к MCP-серверу {#connection}

### Два способа подключения

Адрес сервера: `https://mcp.webask.io/mcp/v1`.

WebAsk поддерживает **OAuth 2.1** и **API-ключ**. Оба способа сохраняют права пользователя и ограничения тарифа.

| Способ | Что нужно | Область доступа |
|---|---|---|
| OAuth — кнопка «Разрешить» | Добавить адрес сервера в клиент с поддержкой авторизации MCP, войти в WebAsk и выбрать воркспейс | Только выбранный воркспейс; 159 инструментов из текущего реестра |
| API-ключ | Создать ключ в WebAsk и передать `Authorization: Bearer <API_KEY>` | Все воркспейсы, к которым у пользователя есть доступ; полный реестр из 163 инструментов |

Фактический каталог подключения получите через `tools/list`. Полные параметры и ответы — в [справочнике инструментов](/dev/api/mcp-tools#tools-overview).

### Подключение через OAuth

1. Добавьте в клиенте удалённый MCP-сервер с адресом `https://mcp.webask.io/mcp/v1`.
2. Клиент откроет WebAsk: войдите в аккаунт, выберите воркспейс и нажмите «Разрешить».
3. Клиент получает токен и сам обновляет его. API-ключ вручную передавать не нужно.

Выбор воркспейса определяет доступ: `user://me` и `workspace://list` показывают только его. Запросы к опросам, папкам или `workspace_id` другого воркспейса получают отказ доступа. Права участника внутри выбранного воркспейса остаются такими же, как в кабинете.

Четыре инструмента уровня аккаунта требуют API-ключ и не входят в OAuth-каталог:
`manage_user_sessions`, `update_user_profile`, `get_partner_program`, `manage_partner_source`.
Попытка вызвать их с OAuth-токеном получает JSON-RPC `-32602` «Инструмент не найден».

Токен действует только на `/mcp/v1`, со scope `mcp`. В REST API v1–v3 он не принимается. Тариф проверяется для выбранного воркспейса. Если доступ пользователя или тариф закончился, запросы получают отказ.

#### Обнаружение сервера авторизации

Клиент выполняет discovery автоматически. Для своей интеграции доступны:

| Метаданные | Адрес |
|---|---|
| Защищённый ресурс | `https://mcp.webask.io/.well-known/oauth-protected-resource/mcp/v1` |
| Сервер авторизации | `https://mcp.webask.io/.well-known/oauth-authorization-server` |
| Страница согласия | `https://api.webask.io/oauth/mcp/authorize` |
| Регистрация публичного клиента | `https://api.webask.io/oauth/mcp/register` |
| Обмен кода и обновление токена | `https://api.webask.io/oauth/mcp/token` |

`issuer` — `https://mcp.webask.io`; адреса согласия, регистрации и обмена токена находятся на `api.webask.io`. Клиент использует адреса из метаданных.

Без токена или с просроченным OAuth-токеном сервер отвечает HTTP `401` и передаёт:

```http
WWW-Authenticate: Bearer resource_metadata="https://mcp.webask.io/.well-known/oauth-protected-resource/mcp/v1", scope="mcp"
```

Неверный API-ключ отвечает HTTP `403` без этого заголовка. При таком отказе исправьте ключ в конфигурации.

#### Регистрация и токены

Регистрация принимает JSON с обязательным непустым `redirect_uris` (до 10 адресов), необязательным `client_name` (до 190 символов) и `token_endpoint_auth_method: "none"`. Клиент публичный, секрет не выдаётся. Разрешены HTTPS-адреса, HTTP для localhost/127.0.0.1/[::1] и собственные схемы приложений. Фрагменты `#`, запятые и схемы `javascript:`, `data:`, `file:`, `vbscript:` запрещены.

Ответ регистрации — HTTP `201`, с `client_id`, `redirect_uris`, `grant_types: ["authorization_code", "refresh_token"]`, `token_endpoint_auth_method: "none"`, `scope: "mcp"`. Лимит — 30 регистраций в час с одного IP, затем HTTP `429`. Повторная регистрация создаёт новую запись.

Authorization Code использует PKCE **S256**. Код действует 10 минут; обмен требует `code_verifier`. Токен доступа живёт 1 сутки, refresh-токен — 1 год. Другой scope не принимается. При отказе пользователя клиент получает `error=access_denied` с исходным `state`.

`redirect_uri` должен совпадать с зарегистрированным; у HTTP loopback-адресов localhost/127.0.0.1/[::1] допускается другой порт. Воркспейсы без MCP в тарифе недоступны для выбора. Повторное «Разрешить» с другим воркспейсом меняет выбор для этой пары клиента и пользователя, в том числе для уже выданных токенов.

### Подключение через API-ключ

1. Войдите в WebAsk и откройте **Настройки → API / MCP**.
2. Создайте API-ключ и скопируйте его — он показывается один раз.
3. Передайте его в заголовке `Authorization: Bearer <API_KEY>`.

#### Cursor

Файл `~/.cursor/mcp.json`. Пример для подключения по API-ключу:

```json
{
  "mcpServers": {
    "webask": {
      "url": "https://mcp.webask.io/mcp/v1",
      "headers": {"Authorization": "Bearer <API_KEY>"}
    }
  }
}
```

Для OAuth в клиенте с поддержкой авторизации MCP оставьте только `url`, без фиксированного заголовка `Authorization`.

#### VS Code

Файл `.vscode/mcp.json`. Здесь верхний ключ — `servers`, а транспорт — `http`:

```json
{
  "servers": {
    "webask": {
      "type": "http",
      "url": "https://mcp.webask.io/mcp/v1",
      "headers": {"Authorization": "Bearer ${input:webask-api-key}"}
    }
  },
  "inputs": [
    {"type": "promptString", "id": "webask-api-key", "description": "API-ключ WebAsk", "password": true}
  ]
}
```

Для OAuth уберите `headers` и `inputs`; клиент запросит авторизацию. См. [настройку MCP в VS Code](https://code.visualstudio.com/docs/agent-customization/mcp-servers).

#### Claude Desktop через локальный мост

Если ваша версия клиента подключает удалённые серверы через stdio, можно использовать сторонний пакет [mcp-remote](https://github.com/geelen/mcp-remote). Пример `claude_desktop_config.json` для API-ключа:

```json
{
  "mcpServers": {
    "webask": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote@latest", "https://mcp.webask.io/mcp/v1",
        "--header", "Authorization:${WEBASK_AUTH}"
      ],
      "env": {"WEBASK_AUTH": "Bearer <API_KEY>"}
    }
  }
}
```

Нужен установленный Node.js. Если клиент умеет удалённые подключения и OAuth напрямую, добавьте адрес сервера в его интерфейсе. Пакет `@modelcontextprotocol/server-fetch` читает веб-страницы и не служит мостом к WebAsk MCP.

### Адрес и лимиты

Адрес сервера — `https://mcp.webask.io/mcp/v1`.

Лимит — **180 запросов в минуту на пользователя**. Ссылки на экспорт действуют **1 час**. Дополнительные права и тарифные условия каждого инструмента описаны в справочнике.


## Ресурсы: чтение данных аккаунта {#resources}

### Обнаружение ресурсов

`resources/list` возвращает 6 конкретных URI: `user://me`, `workspace://list`, `folder://list`, `quiz://list`, `theme://list`, `promocode://list`.

`resources/templates/list` возвращает 13 шаблонов URI:

| Шаблон | Назначение |
|---|---|
| `quiz://{id}/structure` | Структура опроса |
| `quiz://{id}/answers` | Ответы |
| `quiz://{id}/summary` | Сводка |
| `quiz://{id}/report` | Отчёт |
| `quiz://{id}/texts` | Стандартные надписи |
| `quiz://{id}/variables` | Скрытые переменные |
| `quiz://{id}/hidden_options` | Скрытые опции опроса |
| `quiz://{id}/widgets_hidden` | Скрытые опции вопросов |
| `quiz://{id}/report_filters` | Фильтры отчёта |
| `quiz://{id}/report/{report_uuid}/files` | Файлы вопроса в отчёте |
| `quiz://{id}/report/{report_uuid}/inputs` | Текстовые ответы вопроса |
| `theme://{id}/details` | Тема оформления |
| `promocode://{id}/codes` | Коды списка промокодов |

Перед `resources/read` замените подстановки реальными идентификаторами: например, `quiz://123/structure`. Буквальная строка с `{id}` не адресует конкретный опрос. Полный HTTP-запрос приведён в [разделе протокола](/dev/api/mcp#protocol).

OAuth ограничивает все ресурсы выбранным воркспейсом; API-ключ учитывает все доступные пользователю воркспейсы. Отказ по доступу нельзя обходить повторением запроса.

### Чтение и права

Ресурсы предназначены только для **чтения** данных. Для их вызова отправляется запрос `resources/read` с соответствующим URI.

**Отказ по доступу.** Ресурс отвечает объектом `{"error": "<код>", "message": "<пояснение>"}`. Коды: `user_not_authenticated`, `quiz_not_found`, `quiz_in_trash`, `workspace_not_found`, `access_denied`.

- `access_denied` означает, что текущему подключению недоступен объект: нет доступа к воркспейсу, папке опроса или объект находится за границей выбранного через OAuth воркспейса. Доступные пространства показывает `workspace://list`; доступ к папкам определяют права участника. Повтор того же запроса не выдаёт доступ: проверьте выбранное пространство и попросите владельца открыть нужную папку.
- `quiz_in_trash` — опрос в корзине своего пространства: читать и править нельзя, вернуть — `restore_quiz`. Чужой опрос в корзине остаётся `quiz_not_found`, чтобы по коду ответа нельзя было узнать о чужих данных.

Инструменты на те же случаи отвечают текстом с тем же пояснением.

**Отказ по праву участника.** Ресурс отвечает `{"error": "right_denied", "right": "<код права>", "right_value": "allowed|partially|forbidden|missing", "role": "<роль>", "blocked": null|"invite_not_accepted"|"email_not_confirmed"|"not_member", "message": "<пояснение>"}`. В `message` — имя права, его значение у роли (`partially` над чужим объектом называется отдельно: «только свои, а этот создал другой участник»), отличие по папке, если оно есть, и кто может право выдать. Инструменты на тот же отказ отвечают этим же текстом. Полный набор своих прав — `user://me`, поле `workspaces`.

> Если ваш клиент не поддерживает `resources/read`, используйте соответствующий `get_*` инструмент. Его данные доступны JSON-текстом в `content[0]`.
>
> | Ресурс | Tool-эквивалент |
> |--------|----------------|
> | `user://me` | `get_user_me` |
> | `workspace://list` | `get_workspace_list` |
> | `folder://list` | `get_folder_list` |
> | `quiz://list` | `get_quiz_list` |
> | `quiz://{id}/structure` | `get_quiz_structure` |
> | `quiz://{id}/answers` | `get_quiz_answers` |
> | `quiz://{id}/hidden_options` | `get_quiz_hidden_options` |
> | `quiz://{id}/texts` | `get_quiz_texts` |
> | `quiz://{id}/variables` | `get_quiz_variables` |
> | `quiz://{id}/summary` | `get_quiz_summary` |
> | `quiz://{id}/report` | `get_quiz_report` |
> | `quiz://{id}/widgets_hidden` | `get_quiz_widgets_hidden` |
> | `quiz://{id}/report_filters` | `get_quiz_report_filters` |
> | `quiz://{id}/report/{uuid}/files` | `get_quiz_report_files` |
> | `quiz://{id}/report/{uuid}/inputs` | `get_quiz_report_inputs` |
> | `theme://list` | `get_theme_list` |
> | `theme://{id}/details` | `get_theme_details` |
> | `promocode://list` | `get_promocode_list` |
> | `promocode://{id}/codes` | `get_promocode_codes` |

#### Пользователь и воркспейсы

##### `user://me`
Возвращает базовую информацию о текущем авторизованном пользователе.
**Структура ответа:**

| Поле | Тип | Описание |
|------|-----|----------|
| `id` | `integer` | ID пользователя |
| `email` | `string` | Почта пользователя |
| `language` | `string` | Язык интерфейса |
| `workspace` | `object` | Текущее рабочее пространство |
| `workspaces` | `array` | Права пользователя по каждому рабочему пространству, где он владелец или принятый участник |

Элемент `workspaces[]`:

| Поле | Тип | Описание |
|------|-----|----------|
| `id`, `name` | `integer`, `string` | Рабочее пространство |
| `is_owner` | `boolean` | Владелец: право не проверяется, в `access` все значения `allowed` |
| `role`, `role_id` | `string\|null`, `integer\|null` | Роль участника; у владельца `null` |
| `access` | `object` | Имя права → `allowed` (разрешено), `partially` (только свои объекты), `forbidden` (запрещено). Право, которого в роли нет, считается запрещённым |
| `folder_access` | `array` | Отличия по папкам: `{ folder_id, name, access }`. В этих папках действуют эти значения, в остальных — `access` |
| `blocked` | `string\|null` | `invite_not_accepted` — приглашение не принято, `email_not_confirmed` — почта не подтверждена. Пока причина не снята, права не действуют независимо от значений |

Ассистенту это стоит читать до действий, а не после отказа: по `access` видно, что разрешено, без пробных вызовов. Тот же расчёт применяет проверка права при вызове инструмента — расхождений между «сказано» и «сделано» нет.

##### `workspace://list`
Возвращает список доступных рабочих пространств. Для OAuth — только выбранное пространство, для API-ключа — все доступные пользователю.
**Структура ответа:** Общий объект с ключами:

| Поле | Тип | Описание |
|------|-----|----------|
| `total` | `integer` | Общее количество всех пространств |
| `owned` | `array` | Массив ваших пространств (`id`, `name`, `is_default`) |
| `shared` | `array` | Массив гостевых пространств (`id`, `name`, `owner_id`) |

---

#### Папки и опросы

##### `folder://list`
Список папок в области доступа текущего подключения.
**Структура ответа:** 

| Поле | Тип | Описание |
|------|-----|----------|
| `total` | `integer` | Общее количество папок |
| `items` | `array` | Массив папок: `[{"id": 1, "name": "Название"}]` |

##### `quiz://list`
Список опросов в области доступа подключения. Ресурс не применяет query-фильтры и возвращает `total` и `items`. Для фильтра `type` (`regular`/`micro`) и пагинации используйте [get_quiz_list](/dev/api/mcp-tools#tools-read): при непустых аргументах инструмент также возвращает `returned`, количество элементов текущей выдачи.
**Структура ответа:**

| Поле | Тип | Описание |
|------|-----|----------|
| `total` | `integer` | Общее количество подходящих опросов |
| `items` | `array` | Массив опросов: `[{"id": 1, "name": "Тест", "answer_count": 0, "url_shared": "..."}]` |

---

#### Структура и конфигурация опроса

##### `quiz://{id}/structure`
Полная структура элементов и виджетов опроса. Это главный ресурс для работы с содержимым форм.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Структура ответа:**

| Поле | Тип | Описание |
|------|-----|----------|
| `ids` | `array` | Массив UUID виджетов, определяющий их порядок вывода |
| `entities` | `object` | Полная конфигурация и настройки каждого виджета (ключ - UUID) |
| `options` | `array` | Дополнительные внутренние опции |
| `access_limits` | `object` | Ограничения прохождения, только на чтение (менять — `update_quiz_settings`) |

**`access_limits`** отвечает на два вопроса, которые по публичному ответу опроса не разобрать: почему опрос отвечает отказом и почему человек не проходит его второй раз.

| Поле | Тип | Что значит |
|------|-----|------------|
| `available_devices` | `array` | Разрешённые типы устройств (`desktop`, `mobile`, `tablet`). Клиент, устройство которого не распознано — например запрос без обычного user-agent, — под фильтр не попадает и получает отказ «устройство не допущено» (`blocked_by_device`) |
| `session_check_method` | `array` | Чем опрос узнаёт прежнего респондента (`cookie` и прочие) |
| `multiple_ip` | `boolean` | Разрешено ли несколько прохождений с одного IP |
| `limit_answer_count_enabled`, `limit_answer_count_value`, `limit_answer_count_mode` | `boolean`, `integer`, `string` | Общий предел числа ответов. Исчерпан — опрос закрывается для всех сразу, а не для конкретного человека |
| `ip_list_enabled` | `boolean` | Включены ли белый и чёрный списки IP |
| `use_password` | `boolean` | Вход по паролю |
| `show_captcha` | `boolean` | Капча перед прохождением |
| `close_quiz_enabled` | `boolean` | Опрос закрыт автором |

Версии опроса у поля нет: значения читаются из локальной версии, то есть показывают то, что применится после публикации. Локальной версии нет — приходит пустой объект.

##### `quiz://{id}/texts`
Пользовательские и стандартные надписи внутри опроса (например, текст "Далее", "Завершить").
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Структура ответа:** Массив объектов текста:

| Поле | Тип | Описание |
|------|-----|----------|
| `code` | `string` | Системный код надписи (`button_next`, `button_done` и др.) |
| `text` | `string` | Пользовательский текст надписи |

##### `quiz://{id}/variables`
Скрытые переменные опроса (extra fields).
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Структура ответа:** Массив объектов скрытых переменных:

| Поле | Тип | Описание |
|------|-----|----------|
| `field_id` | `string` | UUID скрытой переменной |
| `name` | `string` | Имя переменной |

##### `quiz://{id}/hidden_options`
Служебные конфигурационные опции всего опроса в целом.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Структура ответа:** Массив активных скрытых опций:

| Поле | Тип | Описание |
|------|-----|----------|
| `id` | `integer` | Внутренний ID опции |
| `name` | `string` | Ключ опции (например, `redirect_url`) |
| `value` | `string` | Значение опции |

##### `quiz://{id}/widgets_hidden`
Скрытая мета-информация конкретных виджетов опроса.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Структура ответа:** Массив опций, привязанных к виджетам:

| Поле | Тип | Описание |
|------|-----|----------|
| `id` | `integer` | Внутренний ID опции |
| `widget_uuid`| `string` | UUID виджета |
| `name` | `string` | Ключ опции |
| `value` | `string` | Значение опции |

---

#### Ответы и сводная аналитика

##### `quiz://{id}/answers`
Список всех ответов респондентов (submissions). Каждая запись содержит уникальный `answer_id`.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

**Query-параметры (пример: `?limit=50&is_complete=1`):**

| Параметр | Тип | Возможные значения | Описание |
|----------|-----|--------------------|----------|
| `limit` | `integer` | от `1` до `100` | Лимит записей (по умолчанию 20) |
| `offset` | `integer` | `любое число` | Сдвиг для пагинации (по умолчанию 0) |
| `date` | `string` | `Y-m-d` | Получить результаты только за конкретный день |
| `is_complete`| `boolean` | `1`/`0`, `true`/`false`| Фильтр по завершённости (true — завершённые) |
| `sort` | `string` | `date_end`, `date_start` | Поле сортировки (по дате старта или завершения) |
| `order` | `string` | `desc`, `asc` | Направление сортировки |
| `is_screenout` | `boolean` | `true`/`false` | Показать отсеянных логикой опроса (по умолчанию их в выдаче нет) |
| `is_deleted` | `boolean` | `true`/`false` | Выбрать мягко удалённые ответы из корзины (по умолчанию исключены); скрытие `is_hide` — отдельный признак |
| `unique_link_group_id` | `integer` | ID набора | Только ответы набора уникальных ссылок (`manage_quiz_unique_links`), на любом тарифе |
| `unique_link_ids` | `array` | JSON-массив или через запятую: `[501,502]`, `501,502` | Только ответы этих ссылок набора; без `unique_link_group_id` не учитывается |

Инструмент `get_quiz_answers` принимает те же параметры, включая `is_screenout`, `is_deleted` и срез по уникальным ссылкам. В проверенной реализации JSON `is_complete: false` этого инструмента теряется при преобразовании query и работает как отсутствие фильтра. Для незавершённых ответов используйте ресурс `quiz://123/answers?is_complete=false`; подробности — в [справочнике чтения](/dev/api/mcp-tools#tools-read). У каждого ответа есть поле `unique_link` — `{id, label, group_id, group_name}` или `null` для основного адреса опроса.

**Структура ответа:** Объект с данными пагинации:

| Поле | Тип | Описание |
|------|-----|----------|
| `total` | `integer` | Общее количество подходящих ответов |
| `offset` | `integer` | Текущий сдвиг |
| `items` | `array` | Массив детальных ответов респондента с `answer_id` |

##### `quiz://{id}/summary`
Сводная аналитика опроса (воронка конверсий, типы устройств, география).

У опроса на сайте (`type = micro`) форма ответа другая: `quiz_type`, `is_connected`, `site_domain` и воронка показов на чужой странице. Своей страницы у такого опроса нет, поэтому визитов тоже нет, и обычная сводка отдала бы нули. Состав полей — в описании `get_quiz_summary`, [tools-read.md](/dev/api/mcp-tools#tools-read).
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

---

#### Развёрнутые отчёты (Reports)

##### `quiz://{id}/report`
Детальный отчёт по виджетам со статистикой. При наличии фильтров возвращает `report_uuid`.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

Параметры чтения отчёта зависят от сохранённых фильтров и статусов. `is_complete` не гарантирует отдельный срез незавершённых ответов: у отчёта приоритет имеют сохранённые `statuses`. Для явных параметров, дат и отбора вопросов используйте [get_quiz_report и generate_filtered_report](/dev/api/mcp-tools#tools-read); условия формирования и сохранения фильтров описаны в [инструментах отчётов](/dev/api/mcp-tools#tools-reports).

##### `quiz://{id}/report_filters`
Возвращает сохранённые фильтры пользователя для отчётов этого опроса.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |

##### `quiz://{id}/report/{report_uuid}/inputs`
Текстовые/числовые ответы респондентов на input-like виджеты (`input`, `fio`, `email`, `phone`, `datetime`, `number`). Для `number` поле `result` возвращается строкой.

**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |
| `report_uuid`| `string` | (Обязательный) UUID отчёта |

**Query-параметры:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `widget_id` | `string` | ID (UUID) виджета |
| `widget_hash`| `string` | Hash виджета (из структуры) |
| `limit` | `integer`| Лимит записей (по умолчанию 50) |
| `offset` | `integer`| Сдвиг (пагинация, начиная с 0) |

##### `quiz://{id}/report/{report_uuid}/files`
Загруженные пользователями файлы (ответы на виджет загрузки файла). 
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID опроса |
| `report_uuid`| `string` | (Обязательный) UUID отчёта |

**Query-параметры:** Как у `inputs` (`widget_id`, `widget_hash`, `limit`, `offset`).

---

#### Темы оформления

##### `theme://list`
Список тем оформления (публичных и приватных из workspace).
**Структура ответа:** Массив объектов тем:

| Поле | Тип | Описание |
|------|-----|----------|
| `id` | `integer` | ID темы оформления (нужен для `apply_quiz_theme`) |
| `name` | `string` | Название темы |

##### `theme://{id}/details`
Детальные настройки указанной темы: цвета фона, шрифты, закругления кнопок и т.д.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID темы |

---

#### Промокоды

##### `promocode://list`
Списки групп промокодов в рабочем пространстве.
**Query-параметры:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id`| `integer` | (Обязательный) ID рабочего пространства |

##### `promocode://{id}/codes`
Коды в конкретном списке.
**Параметры ресурса:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `id` | `integer` | (Обязательный) ID списка промокодов |

**Query-параметры:**

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id`| `integer` | (Обязательный) ID рабочего пространства |
| `quiz_id` | `integer` | Только коды, выданные этим опросом. Не передан — коды всего списка |
| `search`, `used`, `sort`, `order`, `limit`, `offset` | — | Поиск и сортировка, как в инструменте `get_promocode_codes` |

---


## Технические детали протокола {#protocol}

### Транспорт и версии

Эндпоинт — `POST https://mcp.webask.io/mcp/v1`, транспорт HTTP, формат JSON-RPC 2.0. Для авторизации передайте OAuth-токен или API-ключ в заголовке `Authorization: Bearer <ACCESS_TOKEN_OR_API_KEY>`.

Поддерживаемые ревизии реализации WebAsk: `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`.

До `2025-11-25` включительно клиент начинает с `initialize`. Для `2026-07-28` клиент указывает версию в каждом запросе; `initialize` не требуется. `server/discover` возвращает поддерживаемые версии и возможности сервера. Примеры ниже описывают контракт WebAsk на 08.10.2026.

### Методы

| Метод | Назначение |
|---|---|
| `server/discover` | Версии, возможности и описание сервера |
| `initialize` | Согласование подключения для прежних ревизий |
| `tools/list` | Инструменты, доступные текущему подключению |
| `tools/call` | Вызов инструмента с `name` и `arguments` |
| `resources/list` | 6 ресурсов с конкретным URI |
| `resources/templates/list` | 13 шаблонов URI с подстановками |
| `resources/read` | Чтение конкретного URI, шаблон нужно раскрыть |
| `prompts/list` | Список шаблонов запросов |
| `prompts/get` | Получение шаблона по имени |

### Запросы ревизии 2026-07-28

Каждый запрос содержит совпадающую версию в заголовке `MCP-Protocol-Version` и `params._meta["io.modelcontextprotocol/protocolVersion"]`, а метод — в заголовке `Mcp-Method` и поле `method`.

Для `tools/call`, `resources/read` и `prompts/get` требуется ещё `Mcp-Name`:

| Метод | Значение `Mcp-Name` должно совпадать с |
|---|---|
| `tools/call` | `params.name` — имя инструмента |
| `resources/read` | `params.uri` — полный URI ресурса |
| `prompts/get` | `params.name` — имя шаблона |

Для URI применяется именно `Mcp-Name`. Цель с символами, которые нельзя передать в заголовке напрямую, кодируется в виде `=?base64?…?=`.

#### Список инструментов

```http
POST /mcp/v1 HTTP/1.1
Host: mcp.webask.io
Authorization: Bearer <ACCESS_TOKEN_OR_API_KEY>
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28"}}
}
```

#### Вызов инструмента чтения

```http
POST /mcp/v1 HTTP/1.1
Host: mcp.webask.io
Authorization: Bearer <ACCESS_TOKEN_OR_API_KEY>
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_quiz_structure

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_quiz_structure",
    "arguments": {"quiz_id": 123},
    "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28"}
  }
}
```

#### Чтение ресурса

```http
POST /mcp/v1 HTTP/1.1
Host: mcp.webask.io
Authorization: Bearer <ACCESS_TOKEN_OR_API_KEY>
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: resources/read
Mcp-Name: quiz://123/structure

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "resources/read",
  "params": {
    "uri": "quiz://123/structure",
    "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28"}
  }
}
```

Успешный `result` этой ревизии содержит `resultType: "complete"`. Подсказки кеширования:

| Ответ | `ttlMs` | `cacheScope` |
|---|---|---|
| Списки инструментов, ресурсов, шаблонов ресурсов и prompts | `300000` | `private` |
| `resources/read` | `0` | `private` |
| `server/discover` | `3600000` | `public` |

Эти поля относятся к новой ревизии. В старые ответы они не добавляются.

### Запросы прежних ревизий

Пример согласования `2025-06-18`:

```http
POST /mcp/v1 HTTP/1.1
Host: mcp.webask.io
Authorization: Bearer <ACCESS_TOKEN_OR_API_KEY>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": {"name": "example-client", "version": "1.0"}
  }
}
```

После согласования пример тела запроса шаблонов ресурсов:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/templates/list",
  "params": {}
}
```

Для новой ревизии добавьте `_meta` и совпадающие заголовки, как в примерах выше.

### Данные в результате инструмента

`tools/call` возвращает результат в JSON-RPC `result`. У большинства инструментов `content[0]` — текст сообщения, а дополнительные данные продублированы JSON-текстом в `content[1]` и соседними ключами `result`. При отсутствии дополнительных данных второй блок отсутствует. Иллюстративный полный ответ прежней ревизии:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {"type": "text", "text": "Список получен"},
      {"type": "text", "text": "{\"items\":[],\"total\":0}"}
    ],
    "isError": false,
    "items": [],
    "total": 0
  }
}
```

Инструменты, которые читают ресурсы (например, `get_quiz_structure`), возвращают JSON данных текстом уже в `content[0]`. Поэтому клиент не должен считать, что данные всегда во втором блоке. `resources/read` использует отдельную оболочку `result.contents` с URI, MIME-типом и текстом ресурса.

В справочнике фрагменты с заголовком «Данные результата» или «Полезная нагрузка» показывают содержимое данных без всей оболочки. Клиент, который передаёт модели только `content`, должен сохранить все текстовые блоки.

### Ошибки

Ошибка инструмента — `result.isError: true`. Сообщение лежит в `content`, а детали валидации при их наличии также повторяются JSON-текстом в следующем блоке. Это отличается от ошибки JSON-RPC, где верхнее поле `error` заменяет `result`.

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "error": {"code": -32602, "message": "Tool not found: nonexistent_tool"}
}
```

| Код JSON-RPC | Причина | HTTP |
|---|---|---|
| `-32022` | Ревизия не поддерживается; `data` сообщает запрошенную и поддерживаемые версии | `400` |
| `-32020` | Обязательного заголовка нет или версия, метод, цель расходятся с телом | `400` |
| `-32601` | Неизвестный метод | `404` для `2026-07-28`, `200` для прежних |
| `-32602` | Неверные параметры или недоступное имя инструмента | Зависит от обработчика запроса |

Если в примере `tools/call` заменить `Mcp-Name: get_quiz_structure` на другое имя или удалить этот заголовок, новая ревизия вернёт HTTP `400` / `-32020`. Если версия не поддерживается, используйте одну из версий из ответа `-32022`.


## Типичные сценарии использования {#scenarios}

#### 1. Получить список опросов и структуру конкретного

```
1. Вызываем tool: get_quiz_list → получаем список опросов с ID
   (или читаем ресурс: quiz://list — для клиентов с поддержкой resources/read)
2. Вызываем tool: get_quiz_structure(quiz_id) → получаем структуру виджетов
   (или читаем ресурс: quiz://{id}/structure)
```

#### 2. Работа с ответами

```
1. Вызываем tool: get_quiz_answers(quiz_id) → получаем ответы (с answer_id)
   (или читаем ресурс: quiz://{id}/answers)
2. Вызываем tool: tag_answer (добавить теги) или toggle_answer_visibility (скрыть/показать)
```

#### 3. Экспорт с фильтрацией

```
1. Вызываем tool: generate_filtered_report (quiz_id, dateFrom/dateTo в формате d.m.Y, фильтры)
   → получаем report_uuid фильтрованного отчёта
   ⚠ Формат дат здесь d.m.Y (например 01.01.2024), тогда как get_quiz_report использует Y-m-d
2. Вызываем tool: get_quiz_report_files(quiz_id, report_uuid, widget_id, widget_hash)
   или get_quiz_report_inputs(quiz_id, report_uuid, widget_id, widget_hash) → детальные ответы по виджетам
   (widget_id и widget_hash берутся из get_quiz_structure)
3. Вызываем tool: export_filtered_report_pdf (quiz_id) → получаем ссылку на PDF
   ⚠ Инструменты export_filtered_report_pdf и export_filtered_report_word принимают только quiz_id;
     report_uuid передавать не нужно — экспорт строится по последнему состоянию отчёта опроса
```

#### 4. Добавление промокодов

```
1. Вызываем tool: create_promocode_group → создаём список с первыми кодами → получаем list_id
2. (при необходимости) Вызываем tool: add_promocodes → добавляем ещё коды в список
3. Вызываем tool: get_promocode_codes(list_id, workspace_id) → проверяем добавленные коды
   (или читаем ресурс: promocode://{list_id}/codes)
```

#### 5. Применить тему и сразу получить структуру опроса

```
1. Вызываем tool: get_theme_list → получаем список доступных тем с ID
   (или читаем ресурс: theme://list)
2. Вызываем tool: apply_quiz_theme (quiz_id, theme_id)
   → в ответе сразу доступно поле quiz с widgets, hidden_options и meta — дополнительный
     вызов get_quiz_structure не нужен
```

### Безопасное изменение логики

1. Прочитайте актуальную структуру через `get_quiz_structure` и сохраните существующие карты условий.
2. Измените нужное правило в локальной копии **полной карты**. Значение `savedLogic[widget_uuid]` — массив групп правил.
3. Передайте полную изменённую карту в `update_quiz_logic`. Переданная карта заменяет прежнюю целиком; пропущенные карты сохраняются с очисткой ссылок на удалённые вопросы. Пустой объект `{}` очищает карту.
4. Прочитайте структуру повторно и проверьте результат. Требуется право `edit_quiz`; изменения конструктора действуют для респондентов после публикации.

Пример полной структуры логики и условий — в [разделе редактирования опроса](/dev/api/mcp-tools#tools-quiz-content).
