Skip to main content
Glama
README.md
# MCP-прокси сервер для «Управление IT-отделом 8», редакция 4.0

## Что это

Прокси-сервер между MCP-клиентами (Claude Desktop, Cursor) и 1С:Предприятие. Транслирует MCP-протокол в JSON-RPC вызовы к HTTP-сервису 1С.

Предназначен для конфигурации [«Управление IT-отделом 8»](https://softonit.ru/catalog/products/it/) — встроенный в неё MCP-сервис предоставляет AI-агентам инструменты для работы с задачами, проектами, базой знаний, файлами и лентой уведомлений.

**Возможности:**
- Два транспорта: stdio (для нативных клиентов) и Streamable HTTP (для веб)
- Проксирование всех MCP-примитивов: Tools, Resources, Prompts
- Опциональная OAuth2 авторизация с per-user креденшилами

## Документация

Пошаговые руководства для решения «Управление IT-отделом 8», редакция 4.0:

- [Развертывание MCP-сервера на виртуальной машине](https://docs.softonit.ru/it/iskusstvennyy-intellekt/mcp/razvertyvanie-mcp-servera) — установка прокси на отдельной ВМ с Docker.
- [Подключение AI-клиентов к MCP-серверу](https://docs.softonit.ru/it/iskusstvennyy-intellekt/mcp/podklyuchenie-ai-klientov) — настройка Claude Desktop, Cursor и Claude Code, в том числе с OAuth2-авторизацией.

Ниже — техническая справка по самому прокси.

## Быстрый старт

### Требования

- **Node.js 22.19+**
- 1С:Предприятие 8.3.24+ с опубликованным HTTP-сервисом

### Установка

```bash
# Установка зависимостей
npm install

# Сборка
npm run build
```

### Выбор режима работы

> 📖 Пошаговая настройка клиентов (Claude Desktop, Cursor, Claude Code) с OAuth2-авторизацией — в статье [Подключение AI-клиентов](https://docs.softonit.ru/it/iskusstvennyy-intellekt/mcp/podklyuchenie-ai-klientov).

#### Stdio режим

Для локальных MCP-клиентов (Claude Desktop, Cursor).

Настройки указываются в конфигурации клиента через переменные окружения.

**Минимальная конфигурация клиента:**

```json
{
  "mcpServers": {
    "uit": {
      "command": "node",
      "args": ["dist/index.js"],
      "cwd": "/path/to/mcp",
      "env": {
        "MCP_ONEC_URL": "http://localhost/base",
        "MCP_ONEC_USERNAME": "admin",
        "MCP_ONEC_PASSWORD": "password"
      }
    }
  }
}
```

#### HTTP режим

Для веб-приложений и множественных клиентов.

Настройки указываются в файле `.env` в корне проекта или через переменные окружения:

```bash
# Скопируйте пример
cp .env.example .env
```

**Минимальный .env:**
```ini
MCP_ONEC_URL=http://localhost/base
MCP_ONEC_USERNAME=admin
MCP_ONEC_PASSWORD=password
```

**Запуск:**
```bash
node dist/index.js http --port 8000
```

**Подключение клиентов с нативной поддержкой HTTP** (Cursor, VS Code, Claude Code):

```json
{
  "mcpServers": {
    "uit": {
      "type": "http",
      "url": "http://localhost:8000/mcp"
    }
  }
}
```

**Подключение Claude Desktop к HTTP-серверу**

`claude_desktop_config.json` не поддерживает поле `"url"` (`"type": "http"`) — только stdio-запуск через `"command"`. Поэтому Claude Desktop подключается к HTTP-серверу через локальный мост [`mcp-remote`](https://www.npmjs.com/package/mcp-remote): он стартует по stdio и проксирует запросы на HTTP-endpoint. Нужен установленный Node.js (см. [Требования](#требования)) — его вызывает `npx`.

```json
{
  "mcpServers": {
    "uit": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://192.168.88.124:8000/mcp", "--allow-http"]
    }
  }
}
```

Замените `http://192.168.88.124:8000/mcp` на адрес вашего HTTP-сервера (для локального запуска — `http://localhost:8000/mcp`). Флаг `--allow-http` обязателен, если endpoint работает по обычному HTTP, а не HTTPS.

### Docker

Запуск в контейнере для изоляции и упрощения развертывания.

> 📖 Полное пошаговое руководство по развёртыванию на отдельной ВМ — [Развертывание MCP-сервера](https://docs.softonit.ru/it/iskusstvennyy-intellekt/mcp/razvertyvanie-mcp-servera).

#### Первичная установка

```bash
# 1. Клонируем репозиторий
git clone https://github.com/Diversus23/itmcp.git

# 2. Переходим в папку, где находится клонированный itmcp
cd itmcp

# 3. Скопировать конфигурацию
cp .env.docker.example .env

# 4. Отредактировать .env (обязательно: MCP_ONEC_URL, MCP_ONEC_USERNAME, MCP_ONEC_PASSWORD)

# 5. Запустить через docker compose
docker compose build --no-cache
docker compose up -d

# 6. Проверка, что все работает
curl http://localhost:8000/health
```

**Или напрямую через Docker:**

```bash
# Сборка образа
docker build -t 1c-mcp-proxy .

# Запуск с переменными окружения
docker run -d \
  -p 8000:8000 \
  -e MCP_ONEC_URL=http://host.docker.internal/base \
  -e MCP_ONEC_USERNAME=admin \
  -e MCP_ONEC_PASSWORD=password \
  --name mcp-proxy \
  1c-mcp-proxy
```

**Важно про сеть:**

- Если 1С на **том же хосте**: используйте `host.docker.internal` (Mac/Windows) или IP хоста `172.17.0.1` (Linux) вместо `localhost`
- Если 1С на **другом сервере**: указывайте его реальный адрес как обычно

**Логи:**

```bash
docker compose logs -f
```

**Остановка:**

```bash
docker compose down
```

#### Обновление в docker

Когда выходит новая версия необходимо пересобрать образ MCP в docker. Необходимо перейти в папку, куда клонировали репозиторий `itmcp`.

```bash
# 1. Получаем изменения новой версии
git pull

# 2. Останавливаем контейер
docker compose down

# 3. Пересобираем образ
docker compose build --no-cache

# 4. Запускаем новый образ (обновленный)
docker compose up -d
```

## Режимы работы

### Stdio режим

- Общение через stdin/stdout
- Используется локальными MCP-клиентами
- Логи идут в stderr
- OAuth2 не поддерживается (при `MCP_AUTH_MODE=oauth2` запуск завершится ошибкой)

### HTTP режим

**Endpoints:**

- `/mcp` - Streamable HTTP транспорт
- `/health` - проверка состояния
- `/info` - информация о сервере
- `/` - список endpoints

**Проверка работы:**
```bash
curl http://localhost:8000/health
```

## Режимы авторизации

### Без OAuth2 (по умолчанию)

```bash
MCP_AUTH_MODE=none  # по умолчанию
```

**Поведение:**
- Все обращения к 1С выполняются от одного пользователя
- Креденшилы задаются в конфигурации: `MCP_ONEC_USERNAME` и `MCP_ONEC_PASSWORD`
- Используется Basic Auth для всех запросов к 1С

### С OAuth2

```bash
MCP_AUTH_MODE=oauth2
MCP_PUBLIC_URL=http://your-server:8000
```

**Поведение:**
- OAuth2 доступен только в HTTP режиме (в stdio запуск завершится ошибкой)
- Каждый клиент авторизуется своими креденшилами 1С
- Креденшилы передаются через OAuth2 flow
- `MCP_ONEC_USERNAME` и `MCP_ONEC_PASSWORD` не используются (если заданы, будут проигнорированы)

**Поддерживаемые OAuth2 flows:**
- **Password Grant** - передача username/password напрямую
- **Authorization Code + PKCE** - авторизация через HTML-форму
- **Dynamic Client Registration** - автоматическая регистрация клиентов

**Дополнительные endpoints (для OAuth2):**
- `/.well-known/oauth-protected-resource` - Protected Resource Metadata
- `/.well-known/oauth-authorization-server` - Authorization Server Metadata
- `/register` - регистрация клиентов
- `/authorize` - HTML форма авторизации
- `/token` - получение/обновление токенов

## Конфигурация

Все настройки задаются через переменные окружения с префиксом `MCP_` или через CLI аргументы.

### Подключение к 1С

| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `MCP_ONEC_URL` | URL базы 1С | - | Всегда |
| `MCP_ONEC_USERNAME` | Имя пользователя | - | При `AUTH_MODE=none` |
| `MCP_ONEC_PASSWORD` | Пароль | - | При `AUTH_MODE=none` |
| `MCP_ONEC_SERVICE_ROOT` | Корень HTTP-сервиса | `mcp` | Нет |

### HTTP-сервер

| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `MCP_HOST` | Хост для прослушивания | `127.0.0.1` | Нет |
| `MCP_PORT` | Порт | `8000` | Нет |
| `MCP_CORS_ORIGINS` | CORS origins (JSON array) | `["*"]` | Нет |

### MCP

| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `MCP_SERVER_NAME` | Имя сервера | `Управление IT-отделом 8 MCP` | Нет |
| `MCP_SERVER_VERSION` | Версия | `1.0.0` | Нет |
| `MCP_SESSION_TTL_MS` | TTL HTTP-сессии `/mcp` без активности (мс); сессия с открытым SSE-стримом не истекает | `86400000` (24 ч) | Нет |
| `MCP_MAX_SESSIONS` | Максимум одновременных HTTP-сессий `/mcp`; при превышении вытесняется самая старая неактивная | `1000` | Нет |
| `MCP_ALLOWED_HOSTS` | Белый список `Host`-заголовков (без порта), защита от DNS rebinding; пусто — проверка отключена | (не задан) | Нет |
| `MCP_SAVE_FILE_DIR` | Белый каталог для `save_file`; в HTTP-режиме без настройки — служебный каталог во временной директории, в stdio — без ограничений | (не задан) | Нет |
| `MCP_LOG_LEVEL` | Уровень логирования | `INFO` | Нет |

Допустимые уровни: `DEBUG`, `INFO`, `WARNING`, `ERROR`

### OAuth2

| Переменная | Описание | По умолчанию | Обязательная |
|------------|----------|--------------|--------------|
| `MCP_AUTH_MODE` | Режим: `none` или `oauth2` | `none` | Нет |
| `MCP_PUBLIC_URL` | Публичный URL прокси | (определяется из запроса) | При `AUTH_MODE=oauth2` |
| `MCP_OAUTH2_CODE_TTL` | TTL authorization code (сек) | `120` | Нет |
| `MCP_OAUTH2_ACCESS_TTL` | TTL access token (сек) | `3600` | Нет |
| `MCP_OAUTH2_REFRESH_TTL` | TTL refresh token (сек) | `1209600` | Нет |
| `MCP_OAUTH2_STORE_PATH` | Путь к JSON-снапшоту токенов | (только в памяти; в Docker-образе — `/app/data/oauth2.json`) | Нет |
| `MCP_OAUTH2_REFRESH_GRACE_MS` | Окно идемпотентности refresh-rotation (мс) | `300000` | Нет |
| `MCP_OAUTH2_REVOKE_ON_REUSE` | Строгий режим: отзыв всей цепочки токенов при позднем повторном refresh | `false` | Нет |
| `MCP_AUTH_RATE_LIMIT_MAX_ATTEMPTS` | Неудачных попыток входа до блокировки; `0` — отключить | `5` | Нет |
| `MCP_AUTH_RATE_LIMIT_WINDOW_MS` | Окно накопления неудачных попыток (мс) | `900000` | Нет |
| `MCP_AUTH_RATE_LIMIT_BLOCK_MS` | Базовая длительность блокировки (мс) | `60000` | Нет |
| `MCP_AUTH_RATE_LIMIT_BY_IP` | Учитывать IP при подсчёте неудач; `false`/`0` — только по логину | `true` | Нет |
| `MCP_TRUST_PROXY` | Express `trust proxy`: `true`, число хопов или строка | (не задан) | За reverse proxy |

**Персистентность токенов.** При указанном `MCP_OAUTH2_STORE_PATH` access- и
refresh-токены сериализуются в JSON-файл (атомарная запись через временный
файл) каждые 30 секунд и при graceful shutdown. После рестарта прокси
загружает снапшот, отбрасывая истекшие записи. Это устраняет повторные
логины пользователей при перезапуске контейнера / краше процесса.

> **⚠️ Безопасность снапшота.** Файл содержит логины и пароли пользователей
> 1С в открытом виде (Basic Auth требуется для каждого запроса к 1С).
> Файл создаётся с правами `0o600` (только владелец) на POSIX-системах;
> на Windows mode игнорируется — обеспечьте ограниченные NTFS ACL на
> каталог. Не храните снапшот в общем томе, не коммитьте в репозиторий
> (`./data` уже игнорируется через `.gitignore`).

> **⚠️ Права каталога в Docker.** Процесс в контейнере работает под
> пользователем `node` (uid 1000). При bind-mount каталога хоста
> (`./data:/app/data`), созданного root'ом, запись снапшота будет падать
> с `EACCES` — прокси сообщит об этом `ERROR`-строкой при старте, а каждый
> перезапуск контейнера будет разлогинивать всех клиентов. Используйте
> именованный том (как в `docker-compose.yml`) или выполните на хосте
> `chown -R 1000:1000 ./data`.

**Refresh-rotation и параллельные сессии.** Повторное использование одного
и того же `refresh_token` — штатная ситуация для MCP-клиентов с несколькими
одновременными сессиями: несколько окон Claude Code делят один файл
креденшилов и при истечении access-токена все разом идут обновлять одну и
ту же пару, а опоздавшие приходят со старым токеном спустя минуты и часы.
Поэтому по умолчанию (лояльный режим) сервер хранит использованные
refresh-токены, пока жив выданный при ротации access-токен, и на повторное
использование идемпотентно возвращает актуальную пару, догоняя опоздавшую
сессию по всей цепочке ротаций — без реавторизации и без последствий для
остальных сессий. `MCP_OAUTH2_REFRESH_GRACE_MS` задаёт «горячее» окно
идемпотентности (используется в строгом режиме и как нижняя граница
хранения).

Строгий режим `MCP_OAUTH2_REVOKE_ON_REUSE=true`: повторное использование
после grace-window считается компрометацией и отзывает всю цепочку токенов
(RFC 6819 §5.2.2.3). Включайте его, только если каждый пользователь работает
строго из одного процесса-клиента: при параллельных сессиях он приводит к
внезапному массовому разлогину («MCP server requires re-authorization»).

**Защита от перебора паролей.** Эндпоинты `/authorize` и `/token`
(password grant) считают неудачные попытки входа отдельно по IP клиента и
по логину 1С. После `MCP_AUTH_RATE_LIMIT_MAX_ATTEMPTS` неудач в окне
`MCP_AUTH_RATE_LIMIT_WINDOW_MS` ключ блокируется на
`MCP_AUTH_RATE_LIMIT_BLOCK_MS`; каждая следующая серия неудач удваивает
блокировку (экспоненциальный backoff, потолок — 1 час). Заблокированный
запрос получает `429 Too Many Requests` с заголовком `Retry-After`, не
доходя до 1С. Успешный вход сбрасывает счётчик. Ошибки соединения с 1С
неудачными попытками не считаются.

> **⚠️ За reverse proxy.** Если прокси работает за nginx/traefik, задайте
> `MCP_TRUST_PROXY` (например, `1` — число прокси-хопов), иначе все запросы
> будут видны с IP прокси: блокировка по IP заблокирует всех пользователей
> сразу. Дополнительно рекомендуется включить платформенную защиту от
> подбора паролей в самой 1С (платформа 8.3.21+).

> **Общий IP у всех клиентов.** В окружениях, где реальный IP клиента
> недоступен и все запросы видны с одного адреса (Docker Desktop на
> Windows/Mac, прокси без `X-Forwarded-For`), блокировка по IP срабатывает
> на всех сразу. В этом случае задайте `MCP_AUTH_RATE_LIMIT_BY_IP=false` —
> останется независимая блокировка по логину.

### CLI аргументы

Переопределяют переменные окружения:

```bash
node dist/index.js http \
  --onec-url http://server/base \
  --onec-username admin \
  --onec-password secret \
  --auth-mode oauth2 \
  --public-url http://proxy:8000 \
  --port 8000 \
  --log-level DEBUG
```

Полный список аргументов:
```bash
node dist/index.js --help
```

## Архитектура

### Общая схема

```
+------------------+
|   MCP Client     |  (Claude Desktop, Cursor)
|  (stdio/HTTP)    |
+--------+---------+
         | MCP Protocol
         v
+--------------------+
|  Node.js Proxy     |
|  - mcp-proxy       |  Проксирование MCP -> JSON-RPC
|  - http-server     |  Express + Streamable HTTP + OAuth2
|  - stdio-server    |  Stdio транспорт
|  - onec-client     |  HTTP-клиент для 1С
+--------+-----------+
         | JSON-RPC over HTTP
         | Basic Auth (username:password)
         v
+--------------------+
|  1C HTTP Service   |  /hs/mcp/rpc
|  МСP-подсистема    |  39 tools, resources, prompts
+--------------------+
```

### Модули

- **`index.ts`** - точка входа
- **`main.ts`** - CLI парсинг и запуск
- **`config.ts`** - конфигурация через Zod
- **`mcp-proxy.ts`** - ядро MCP-сервера (проксирование)
- **`onec-client.ts`** - HTTP-клиент для 1С
- **`http-server.ts`** - Express + Streamable HTTP + OAuth2
- **`stdio-server.ts`** - stdio транспорт
- **`auth/oauth2.ts`** - OAuth2 авторизация (Store + Service)
- **`logger.ts`** - логирование в stderr

### Проксирование MCP-примитивов

Все MCP-запросы транслируются в JSON-RPC к 1С:

**Tools (инструменты):**
- `tools/list` -> список доступных инструментов
- `tools/call` -> вызов инструмента с аргументами

**Resources (ресурсы):**
- `resources/list` -> список доступных ресурсов
- `resources/read` -> чтение содержимого ресурса

**Prompts (промпты):**
- `prompts/list` -> список доступных промптов
- `prompts/get` -> получение промпта с параметрами

## Интеграция с 1С

Прокси ожидает HTTP-сервис в 1С по адресу:
```
{MCP_ONEC_URL}/hs/{MCP_ONEC_SERVICE_ROOT}/
```

Например: `http://localhost/base/hs/mcp/`

### Endpoints 1С

1. **`GET /health`**
   - Проверка доступности сервиса
   - Ответ: `{"status": "ok"}`
   - Используется для валидации креденшилов в OAuth2

2. **`POST /rpc`**
   - JSON-RPC endpoint для всех MCP-операций
   - Content-Type: `application/json`
   - Basic Auth: `username:password`

### Формат JSON-RPC запроса

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}
```

### Формат JSON-RPC ответа

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_metadata",
        "description": "Получить метаданные объекта",
        "inputSchema": {}
      }
    ]
  }
}
```

## Разработка

```bash
# Установка зависимостей (заодно ставит git-хуки через husky)
npm install

# Сборка
npm run build

# Сборка в watch-режиме
npm run dev

# Запуск stdio
npm start

# Запуск HTTP
npm run start:http
```

### Контроль качества

```bash
npm run typecheck      # проверка типов без сборки
npm run lint           # ESLint
npm run lint:fix       # ESLint с автоисправлением
npm run format         # Prettier: отформатировать
npm run format:check   # Prettier: только проверка
npm test               # запуск тестов (Vitest)
npm run test:watch     # тесты в watch-режиме
npm run test:coverage  # тесты с отчётом о покрытии
npm run check          # format:check + lint + typecheck + test (как в CI)
```

Перед каждым коммитом `husky` + `lint-staged` автоматически прогоняют ESLint
и Prettier по изменённым файлам. CI (GitHub Actions) дополнительно проверяет
сборку на Node 22 и 24, собирает Docker-образ и запускает CodeQL.

История изменений — в [CHANGELOG.md](CHANGELOG.md).

---

**MIT License**