mcp-wb
README.md
# mcp-wb
Remote MCP-сервер, который подключает Claude к общению с покупателями на Wildberries: отзывы, вопросы и чаты. Разворачивается как **кастомный коннектор организации** — каждый сотрудник подключает его у себя в Claude, а токен Wildberries остаётся только на сервере.
## Что умеет
**Чтение**
| Инструмент | Что делает |
| --- | --- |
| `wb_overview` | Сводка: сколько отзывов и вопросов ждут ответа, есть ли непросмотренные, сколько чатов |
| `wb_feedbacks_list` / `wb_feedback_get` / `wb_feedbacks_archive` / `wb_feedbacks_count` | Отзывы |
| `wb_questions_list` / `wb_question_get` / `wb_questions_count` | Вопросы |
| `wb_chats_list` / `wb_chat_events` | Чаты и лента сообщений с курсором |
| `wb_whoami` | Под кем работает коннектор и что ему разрешено |
**Ответы клиентам — только через черновик**
| Инструмент | Что делает |
| --- | --- |
| `wb_draft_feedback_reply` | Готовит ответ на отзыв |
| `wb_draft_feedback_answer_edit` | Готовит правку опубликованного ответа (WB даёт одну попытку за 60 дней) |
| `wb_draft_question_answer` | Готовит ответ на вопрос |
| `wb_draft_chat_message` | Готовит сообщение в чат |
| `wb_drafts_list` / `wb_draft_discard` | Просмотр и отмена черновиков |
| **`wb_draft_send`** | **Единственный инструмент, который что-то отправляет покупателю.** Требует `confirm="ОТПРАВИТЬ"` |
| `wb_question_reject` / `wb_question_mark_viewed` | Отклонение вопроса и снятие метки «не просмотрен» |
Каждая отправка пишется в журнал `audit` с почтой сотрудника, текстом и результатом.
## Почему устроено именно так
**Токен Wildberries — персональный, и он не покидает сервер.** Лимиты WB для категории «Вопросы и отзывы» у базового токена — 5 запросов в час, у чатов — 1 запрос в час; работать на них нельзя. Персональный токен даёт 3 запроса в секунду, но по правилам WB его запрещено передавать третьим лицам и использовать в облачных сервисах — он предназначен для систем на собственной или арендованной инфраструктуре. Отсюда: свой VPS, один токен на сервере, сотрудники ходят через OAuth.
**Личность сотрудника устанавливает наш сервер.** Claude не передаёт MCP-серверу ни email, ни ID пользователя, а вариант «сервер без авторизации» админка организации не поддерживает. Поэтому сервер сам работает как OAuth 2.1 authorization server (PKCE, Dynamic Client Registration, RFC 9728/8414), а личность берёт у Google, Яндекса или из одноразового кода.
**Роли.** `ALLOWED_EMAIL_DOMAINS` / `ALLOWED_EMAILS` решают, кто вообще войдёт. `RESPONDER_EMAILS` и `ADMIN_EMAILS` — кто может отправлять. Остальные получают токен без права `wb:write` и физически не могут вызвать `wb_draft_send`.
## Требования
- VPS с публичным IP, доступный из интернета (Claude ходит на сервер со своей стороны — сервер за NAT или VPN не подойдёт)
- Домен с A-записью на этот IP
- Docker и Docker Compose
- Персональный токен WB с категориями «Вопросы и отзывы» и «Чат с покупателями»
- Права **Owner** или **Primary Owner** в организации Claude (только они добавляют коннекторы)
## Развёртывание
### 1. Токен Wildberries
Личный кабинет продавца → **Настройки → Доступ к API** → **Создать токен** → вкладка «Для интеграции вручную» → тип **Персональный**. Отметьте категории **«Вопросы и отзывы»** и **«Чат с покупателями»**, уровень доступа — **Чтение и запись**. Токен показывается один раз, живёт 180 дней — поставьте напоминание о замене.
### 2. Конфигурация
```bash
git clone <репозиторий> mcp-wb && cd mcp-wb
cp .env.example .env
```
Заполните `.env`. Обязательный минимум:
```
PUBLIC_URL=https://mcp-wb.вашдомен.ru
MCP_DOMAIN=mcp-wb.вашдомен.ru
ACME_EMAIL=admin@вашдомен.ru
WB_TOKEN=<токен из шага 1>
SESSION_SECRET=<openssl rand -hex 32>
ALLOWED_EMAIL_DOMAINS=вашдомен.ru
ADMIN_EMAILS=вы@вашдомен.ru
RESPONDER_EMAILS=менеджер1@вашдомен.ru,менеджер2@вашдомен.ru
```
Проверьте токен до запуска:
```bash
npm install && npx tsx scripts/probe-wb.ts
```
### 3. Вход сотрудников
**Google Workspace** (`IDENTITY_PROVIDER=google`) — в Google Cloud Console создайте OAuth-клиент типа «Web application», в Authorized redirect URIs добавьте:
```
https://mcp-wb.вашдомен.ru/idp/google/callback
```
`GOOGLE_CLIENT_ID` и `GOOGLE_CLIENT_SECRET` — в `.env`.
**Яндекс ID** (`IDENTITY_PROVIDER=yandex`) — приложение на oauth.yandex.ru, Callback URI:
```
https://mcp-wb.вашдомен.ru/idp/yandex/callback
```
**Без внешнего IdP** (`IDENTITY_PROVIDER=invite`) — выдавайте одноразовые коды:
```bash
docker compose exec mcp-wb node dist/scripts/invite.js ivan@вашдомен.ru
```
### 4. Запуск
```bash
docker compose up -d --build
```
Caddy сам получит сертификат Let's Encrypt. Проверка:
```bash
curl https://mcp-wb.вашдомен.ru/.well-known/oauth-protected-resource/mcp
```
Должен вернуться JSON с `"resource": "https://mcp-wb.вашдомен.ru/mcp"`.
### 5. Подключение к организации Claude
1. **Settings → Connectors** в организации (пункт виден только Owner).
2. **Add** → **Custom** → **Web**.
3. URL: `https://mcp-wb.вашдомен.ru/mcp`
4. Advanced settings трогать не нужно: сервер поддерживает Dynamic Client Registration и зарегистрирует Claude сам.
5. Сохраните — коннектор появится у всех сотрудников организации.
### 6. Что делает каждый сотрудник
Открывает Claude → **Connectors** → находит коннектор → **Connect** → входит корпоративной почтой. Дальше можно писать обычным языком: «покажи неотвеченные отзывы за неделю», «подготовь ответ на отзыв X».
Проверить свои права: `wb_whoami`.
## Эксплуатация
**Журнал действий.** Всё, что ушло покупателю, лежит в таблице `audit` файла базы:
```bash
docker compose exec mcp-wb \
node -e "const d=require('better-sqlite3')('/data/mcp-wb.db');console.table(d.prepare('SELECT datetime(ts,\"unixepoch\") t,actor,action,target,outcome FROM audit ORDER BY ts DESC LIMIT 30').all())"
```
**Отозвать доступ у сотрудника** — уберите его из `ALLOWED_EMAILS` / отключите в Google Workspace и перезапустите контейнер. Действующие токены проверяют список доступа при каждом запросе, так что доступ пропадёт сразу.
**Замена токена WB** — раз в 180 дней: новый токен в `.env`, затем `docker compose … up -d`.
**Лимиты.** Клиент сам держит темп: 3 запроса/с для отзывов и вопросов, 10 за 10 с для чатов, с ожиданием по заголовку `X-Ratelimit-Retry` при 429.
## Разработка
```bash
npm install
npm run dev # tsx watch
npm run typecheck
WB_SANDBOX=true npm run dev # песочница WB (только отзывы и вопросы, чата в ней нет)
```
Для локального прогона OAuth поставьте `PUBLIC_URL=http://localhost:3000` и `IDENTITY_PROVIDER=invite`.
## Границы
- Чат: у WB нет sandbox-хоста, тестировать можно только на боевом кабинете.
- Ответы на отзывы и вопросы проходят модерацию WB и публикуются не мгновенно.
- Правка ответа — одна попытка в течение 60 дней, дальше текст фиксируется навсегда.
- Заявки на возврат (`returns-api`) требуют отдельной категории токена и в этой версии не подключены.
- Сервер работает без сессий: на каждый HTTP-запрос создаётся свой экземпляр MCP-сервера, поэтому серверных уведомлений в сторону клиента нет.