Skip to main content
Glama
SkySai1

Open WebUI Knowledge MCP

by SkySai1
README.md
# Open WebUI Knowledge MCP

Компактный MCP для Goose: управление знаниями и поиск в Qdrant **через Open WebUI**.

```text
Goose / MCP client → этот MCP → Open WebUI → Qdrant
                                    ↓
                       настроенные embedding / reranking
```

MCP использует только Open WebUI HTTP API. Open WebUI отвечает за загрузку,
chunking, embeddings, хранение оригиналов, векторов и поиск. MCP возвращает
фрагменты, а не генерирует итоговый ответ. Прямых клиентов Qdrant/Ollama и ML-библиотек
в зависимостях MCP нет.

## Установка

Нужны Python 3.10+, uv и уже запущенный Open WebUI, настроенный на Qdrant.
В каталоге репозитория:

```sh
uv venv --python 3.12
uv pip install --python .venv/bin/python -e '.[dev]'
cp .env.example .env
```

Укажите в `.env` URL и API key **Open WebUI**. Запуск из терминала:

```sh
set -a
. ./.env
set +a
.venv/bin/openwebui-rag-mcp
```

Сервер ожидает MCP-сообщения на stdin; произвольного вывода в stdout нет.
Сам MCP не читает `.env`: пример выше экспортирует его значения в окружение.
Для обычной установки без инструментов разработки замените `'.[dev]'` на `.`.

## Настройка Open WebUI → Qdrant

Эти переменные задаются **процессу/контейнеру Open WebUI**, а не MCP:

```dotenv
VECTOR_DB=qdrant
QDRANT_URI=http://qdrant:6333
QDRANT_API_KEY=
RAG_EMBEDDING_ENGINE=ollama
RAG_OLLAMA_BASE_URL=http://ollama:11434
RAG_EMBEDDING_MODEL=your-installed-embedding-model
```

`qdrant` и `ollama` в примере — имена сервисов в одной контейнерной сети; замените
адреса на доступные Open WebUI. Проверьте сохранённые настройки Documents в Admin
Panel: часть параметров Open WebUI хранится в его БД. Модель выбирает администратор.
Это настройка backend для баз, которыми управляет Open WebUI; существующие
произвольные коллекции Qdrant автоматически базами Open WebUI не становятся.
[Описание переменных Open WebUI](https://docs.openwebui.com/reference/env-configuration/).

Hybrid search и reranker настраиваются в Open WebUI. MCP сохраняет выбранный режим
и передаёт `k`/`k_reranker` для конкретного поиска. Совместимость reranker с Ollama
зависит от возможностей Open WebUI и выбранного адаптера; MCP не эмулирует `/rerank`.
Для первого запуска можно использовать обычный vector search без reranker.

В Open WebUI разрешите API keys и создайте ключ пользователя с нужными правами
на Knowledge/Files/Retrieval API. Если включены ограничения endpoints, разрешите
используемые ниже пути. `rag_health` проверяет только авторизованный Knowledge API,
а не фактическую доступность Qdrant или моделей.

## Окружение MCP

| Переменная | По умолчанию | Значение |
|---|---|---|
| `OPENWEBUI_URL` | `http://localhost:3000` | Базовый URL; поддерживается path prefix |
| `OPENWEBUI_API_KEY` | обязательно | Bearer token Open WebUI |
| `OPENWEBUI_TIMEOUT` | `120` | Timeout каждой HTTP-операции, секунды |
| `OPENWEBUI_VERIFY_TLS` | `true` | Проверять TLS, строго `true`/`false` |
| `OPENWEBUI_MAX_PAGES` | `1000` | Предел пагинации; превышение возвращает ошибку |
| `RAG_TOP_K` | `8` | Максимум фрагментов, 1–100 |
| `RAG_MAX_CHUNK_CHARS` | `7000` | Лимит текста фрагмента в выдаче |
| `RAG_MAX_FILE_CHARS` | `50000` | Лимит извлечённого текста файла в выдаче |
| `RAG_MAX_INPUT_CHARS` | `1000000` | Лимит входного текста/query в символах |

Усечение всегда обозначается `truncated`; у файла есть `total_chars`.
`RAG_SCORE_THRESHOLD` старого прототипа удалён: MCP не может одинаково трактовать
оценки всех режимов retrieval. Threshold задаётся в Open WebUI.
Невалидная конфигурация завершает запуск с кодом 2 и сообщением в stderr.
Недоступный Open WebUI при старте логируется; MCP остаётся запущен и может
восстановиться при следующем вызове.

## Goose

Добавьте stdio extension через интерфейс Goose или объедините этот блок со своим
`~/.config/goose/config.yaml`. Замените путь и ключ своими значениями:

```yaml
extensions:
  openwebui_knowledge:
    name: openwebui_knowledge
    type: stdio
    enabled: true
    cmd: /absolute/path/to/GooseMCPopenwebui/.venv/bin/openwebui-rag-mcp
    args: []
    timeout: 600
    envs:
      OPENWEBUI_URL: http://localhost:3000
      OPENWEBUI_API_KEY: replace-with-your-key
      OPENWEBUI_TIMEOUT: "120"
      OPENWEBUI_VERIFY_TLS: "true"
      RAG_TOP_K: "8"
```

Timeout Goose учитывает, что запись состоит из нескольких HTTP-запросов.
Используйте абсолютный путь: запуск уже установленного пакета не требует PyPI.
[Формат конфигурации Goose](https://github.com/aaif-goose/goose/blob/main/documentation/docs/guides/config-files.md).

## Tools

`knowledge_id` — ID базы Open WebUI; `file_id` — ID документа. Это разные сущности.

| Tool | Назначение |
|---|---|
| `rag_health` | Проверить доступ к Knowledge API |
| `knowledge_list` | Все доступные базы, кратко и с пагинацией |
| `knowledge_create(name, description="")` | Создать базу |
| `knowledge_get(knowledge_id)` | Сведения о базе и краткий список её файлов |
| `knowledge_add(knowledge_id, text, title="knowledge.txt", source=null, metadata=null)` | Загрузить UTF-8 `.txt`, проверить обработку и прикрепить к базе |
| `knowledge_update(knowledge_id, file_id, text)` | Изменить текст общего файла, подтвердить чтением, обновить индекс базы |
| `knowledge_delete(knowledge_id, file_id)` | Отсоединить файл от базы с `delete_file=false` |
| `knowledge_get_file(file_id)` | Извлечённый текст файла |
| `knowledge_search(query, knowledge_ids=null, top_k=null)` | Найти релевантные фрагменты |

Старые имена `rag_list_knowledge`, `rag_search`, `rag_get_file` сохранены как aliases
с теми же входными параметрами. Формат результатов обновлён; это не полная обратная
совместимость старого прототипа.

Пример последовательности arguments:

```json
{"name":"Рабочие заметки","description":"Решения команды"}
```

Из ответа `knowledge_create` возьмите `knowledge_id`:

```json
{"knowledge_id":"<id-базы>","text":"Согласовали выпуск в пятницу.","title":"Решение","metadata":{"project":"demo"}}
```

Из ответа `knowledge_add` возьмите `file_id` для чтения, обновления или удаления.
Поиск:

```json
{"query":"Когда выпуск?","knowledge_ids":["<id-базы>"],"top_k":5}
```

`knowledge_ids=null` ищет во всех доступных базах, `[]` — ни в одной. MCP отправляет
один retrieval-запрос для выбранного набора и сохраняет порядок Open WebUI.
Каждый результат содержит `text`, `truncated`, `chunk_id`, `file_id`, `knowledge_id`,
`title`, `source`, `metadata`, `score`, `distance`. Отсутствующие upstream поля — `null`.
Score/distance сохраняются без преобразований: поле `distances` в разных режимах
Open WebUI может содержать разные типы оценок. Отдельные vector/reranker scores
и достоверное число chunks API не гарантирует. Embeddings из ответов исключаются.

## Контракт API и ограничения MVP

Контракт сверялся с официальным исходным кодом Open WebUI `main` 22.09.2026:
[Knowledge API](https://github.com/open-webui/open-webui/blob/main/backend/open_webui/routers/knowledge.py),
[Files API](https://github.com/open-webui/open-webui/blob/main/backend/open_webui/routers/files.py),
[Retrieval API](https://github.com/open-webui/open-webui/blob/main/backend/open_webui/routers/retrieval.py).
Live-совместимость с конкретным установленным релизом пока не проверена.

| Операция | HTTP API |
|---|---|
| Базы | `GET /api/v1/knowledge/`, `POST /api/v1/knowledge/create` |
| База / файлы | `GET /api/v1/knowledge/{id}`, `GET /api/v1/knowledge/{id}/files` |
| Загрузка | `POST /api/v1/files/?process=true&process_in_background=false` (multipart) |
| Обработка | `GET /api/v1/files/{id}/process/status` |
| Привязка / переиндексация / удаление связи | `POST /api/v1/knowledge/{id}/file/{add,update,remove}` |
| Извлечённый текст | `GET /api/v1/files/{id}/data/content` |
| Изменение текста | `POST /api/v1/files/{id}/data/content/update` |
| Поиск | `POST /api/v1/retrieval/query/collection` с `collection_names`, `query`, `k`, `k_reranker` |

Список баз поддерживает `items/total`, `data/total` и старый плоский массив.
Файлы читаются из вложенного `files` старых ответов либо из отдельного paginated API.
Retrieval поддерживает одну вложенную строку результатов и плоский массив.
Это совместимость форматов, а не обещание поддержки любого релиза. Для мутаций
нужны перечисленные endpoints и поддержка `delete_file=false` установленной версией.

- Записи неатомарны. Ошибка возвращается с MCP `isError=true`, `ok=false`, `stage`,
  известным `file_id` и `outcome=partial_or_unknown`. При timeout запрос мог завершиться:
  сначала проверьте Open WebUI, затем решайте, повторять ли операцию. Автоповторов нет.
- При сбое привязки загруженный файл сохраняется в Open WebUI; его можно проверить
  и прикрепить через UI. MCP не удаляет его автоматически.
- Update меняет **общий файл**. Другие базы, использующие его, могут измениться;
  поведение обновления их индексов зависит от версии Open WebUI. Индекс указанной базы
  обновляется отдельным вызовом. Исходный скачиваемый файл и извлечённый текст могут
  отличаться после редактирования — MCP читает именно извлечённый текст.
- Delete удаляет связь и поручает удаление векторов Open WebUI. Сам файл и другие
  базы сохраняются. Некоторые ошибки очистки/поиска Open WebUI скрывает внутри
  успешного HTTP-ответа; MCP не может независимо подтвердить состояние Qdrant.
- Source/title/metadata передаются как metadata загрузки (Open WebUI сохраняет их
  в `file.meta.data`); попадание произвольных полей в retrieval metadata зависит от backend.
  Изменение metadata, metadata-filter, удаление всей базы и внешние read-only Knowledge
  Sources не входят в CRUD MVP.
- Конкурирующие записи сериализуются внутри одного MCP-процесса. Транзакций между
  несколькими MCP или пользователями Open WebUI нет.

## Разработка и проверки

```sh
.venv/bin/pytest -q
.venv/bin/ruff check src/openwebui_rag_mcp tests
.venv/bin/ruff format --check src/openwebui_rag_mcp tests
```

Все HTTP-вызовы тестов mock'аются через httpx. Отдельный тест запускает реальный
stdio subprocess, выполняет MCP initialize/list_tools/call_tool и проверяет
восстановление после ошибки tool. Qdrant/Ollama/Open WebUI для unit-тестов не нужны.
План и оценка исходного кода — [TODO.md](TODO.md), инструкции — [AGENTS.md](AGENTS.md).