Skip to main content
Glama
local-corporate-kb

local-corporate-kb

by arptra

Локальная корпоративная база знаний для Qwen Code

Это локальный MVP корпоративного RAG: документы индексируются Python-процессом, embeddings сохраняются в проверяемый файловый кэш, а при поиске целиком находятся в RAM. Qwen Code остаётся единственной генеративной моделью и получает найденные фрагменты через read-only MCP tools по локальному stdio или удалённому Streamable HTTP. MCP-сервер не формулирует финальные ответы, не исполняет shell-команды и не изменяет документы.

Проект рассчитан на Python 3.12 и standalone FastMCP==3.4.4. Запуск после установки не зависит от uv: все runtime-скрипты вызывают Python из .venv напрямую. Для разработки доступна воспроизводимая установка через uv.lock, а для корпоративных машин — отдельная установка через обычный pip.

Отдельные пошаговые инструкции:

Архитектура

Confluence export
       ↓
knowledge/*.md, *.html, *.txt
       ↓
loader + normalizer + structural chunker
       ↓
local feature hashing (по умолчанию) или локальная embedding-модель
       ↓
NumPy matrix in RAM
       ↓
MCP stdio / Streamable HTTP
       ↓
Qwen Code CLI

В удалённом режиме тот же индекс один раз загружается в память серверного процесса, после чего к нему одновременно подключаются Qwen Code CLI с разных машин:

Qwen CLI ─┐
Qwen CLI ─┼─ HTTPS / Bearer token ─ MCP Streamable HTTP ─ in-memory index
Qwen CLI ─┘

DocumentLoader безопасно обходит только KB_KNOWLEDGE_DIR, нормализует Markdown/TXT и переводит экспортированный HTML в Markdown-подобный текст. StructuralChunker сохраняет путь заголовков, списки, таблицы и code fences. KnowledgeService координирует кэш и работает только через интерфейс KnowledgeStore; MCP-слой не знает о NumPy.

В RAM находятся документы, чанки, отображение chunk_id -> index и нормализованная NumPy-матрица [chunk_count, embedding_dimension]. Cosine similarity считается как matrix @ query_vector.

Экономия контекста Qwen

Поиск не передаёт модели все найденные тексты. Сервер сначала находит до 12 кандидатов внутри индекса, затем отдаёт максимум 3 наиболее релевантные выдержки из разных документов: до 260 условных токенов на выдержку и до 1000 на один ответ инструмента. Выдержка выбирается по словам вопроса и сохраняет ссылку на источник. Это ограничивает расход контекста, даже если в базе десятки тысяч страниц.

Если выбранный результат требует деталей, Qwen вызывает kb_get_chunk с chunk_id; полный текст не загружается автоматически. kb_get_document также возвращает ограниченный извлекаемый фрагмент. Лимиты настраиваются через KB_SEARCH_* и KB_DOCUMENT_CONTEXT_TOKENS в .env.example.

Защищённый kb_run_context_benchmark сравнивает прежние top-5 полных чанков с текущими top-3 выдержками: Hit@K, оценку токенов, точные JSON bytes, процент сжатия и latency. Инструмент требует отдельный KB_BENCHMARK_PASSWORD, который Qwen запрашивает перед каждым вызовом.

В HTTP-режиме по /admin доступна встроенная парольная панель: usage-счётчики, живая системная нагрузка, память и uptime процесса, список документов, загрузка Markdown/HTML/TXT и фоновая инкрементальная переиндексация. Декларативные MCP search-tools по-прежнему доступны через защищённый API, но технический редактор schemas из основной панели убран.

На диске в .cache/kb/ находятся только:

  • manifest.json — версии схемы, идентичность модели, chunking config и knowledge hash;

  • documents.json — нормализованные документы и metadata;

  • chunks.json — чанки без отдельной копии embedding;

  • embeddings.npy — матрица без pickle.

Это не Vector DB: нет отдельного сервиса хранения, индекса ANN или SQL. Удалённый HTTP — только read-only MCP-фасад; поиск выполняется полным cosine scan по NumPy-матрице в памяти, а диск используется для ускорения старта.

Related MCP server: rag-retriever-mcp

Первый запуск

Подключение сотрудника к удалённой базе

RAG, индекс и документы находятся только на сервере. Для старых версий Qwen сотруднику передаётся один файл clients/corporate_kb_stdio_proxy.py. Qwen запускает его локально через uv как stdio MCP, а Python-процесс ходит к удалённому серверу через обычные HTTP GET-запросы. Node.js, npx, mcp-remote, Nginx, копия проекта, .venv, документы и индекс на клиенте не нужны.

Готовый settings находится в examples/qwen-uv-stdio-settings.example.json, а полная инструкция для сотрудника — в README.client.md.

Новые версии Qwen также могут подключаться к /mcp напряму через Streamable HTTP; скрипт install.sh оставлен как опциональный способ для таких клиентов. Скопируйте из неё mcpServers в ~/.qwen/settings.json и замените четыре placeholder:

  • REPLACE_WITH_ABSOLUTE_UV_PATH — результат which uv или where uv (в Windows uv.exe);

  • REPLACE_WITH_ABSOLUTE_PATH — каталог, в котором сотрудник сохранил единственный .py-файл;

  • REPLACE_WITH_SERVER_IP_OR_DOMAIN — адрес удалённого сервера;

  • REPLACE_WITH_SERVER_TOKEN — Bearer-токен.

Qwen запускает этот файл как локальный MCP по stdio командой uv run. Скрипт содержит inline dependency на FastMCP, поэтому uv сам создаёт изолированное кэшированное окружение. Локальный MCP обращается к удалённому RAG только через обычные авторизованные JSON GET endpoints /api/v1/*. node, npx, mcp-remote, локальная копия документов и локальный индекс не нужны.

Установка серверной части

Убедитесь, что доступен Python 3.12. На корпоративной машине рекомендуется pip-вариант: он не читает uv.lock, не запускает uv и не зависит от установленной в системе версии uv. Hugging Face, PyTorch и sentence-transformers в базовую установку не входят:

./scripts/setup-pip.sh
source ./scripts/activate-venv.sh

Для разработки с точными версиями из lock-файла остаётся вариант:

./scripts/setup-venv.sh
source ./scripts/activate-venv.sh

Оба варианта создают одинаковую .venv; runtime-команды используют только .venv/bin/python.

Полностью локальный режим по умолчанию использует hash provider и не требует модели или сети:

./scripts/dev.sh index-hash
./scripts/dev.sh search-hash

Hash provider строит локальные lexical vectors из слов и символьных триграмм. Он пригоден для полностью автономного поиска по совпадающей терминологии, но не понимает смысл и синонимы так же хорошо, как semantic embedding model.

Для качественного semantic search сначала положите заранее полученные и одобренные model files в локальный каталог. Этот проект не скачивает их. Например:

models/Qwen3-Embedding-0.6B/

После этого активируйте окружение, укажите только локальный путь и постройте индекс:

./scripts/dev.sh install-pip-semantic
source ./scripts/activate-venv.sh
export KB_EMBEDDING_PROVIDER=sentence_transformers
export KB_EMBEDDING_MODEL="$KB_PROJECT_ROOT/models/Qwen3-Embedding-0.6B"
export KB_EMBEDDING_LOCAL_FILES_ONLY=true

./scripts/dev.sh index-semantic
./scripts/dev.sh search "Какой сервис владеет дневными лимитами?"

local_files_only=true, HF_HUB_OFFLINE=1 и TRANSFORMERS_OFFLINE=1 запрещают обращения к Hugging Face. Если model files отсутствуют, индексирование завершится понятной ошибкой без попытки скачивания. По умолчанию выбирается CUDA, затем MPS, затем CPU.

scripts/start-mcp.sh по умолчанию запускает MCP с KB_EMBEDDING_PROVIDER=hash, поэтому обычное подключение Qwen полностью offline. Для локальной semantic-модели явно передайте provider и путь в environment Qwen-конфигурации. Все runtime wrappers вызывают Python из готовой .venv напрямую: после установки они не обращаются к package registry и не меняют окружение.

CLI

./.venv/bin/python -m corporate_kb.cli index
./.venv/bin/python -m corporate_kb.cli index --force
./.venv/bin/python -m corporate_kb.cli search "Как рассчитывается дневной лимит?" --top-k 5
./.venv/bin/python -m corporate_kb.cli search "Как рассчитывается дневной лимит?" --service limits-service
./.venv/bin/python -m corporate_kb.cli documents
./.venv/bin/python -m corporate_kb.cli stats
./.venv/bin/python -m corporate_kb.cli eval --top-k 5

У search, documents, stats и eval есть --json. В этом режиме stdout содержит только JSON, а логи остаются в stderr.

Если кэша нет или он несовместим, обычный поиск при KB_AUTO_INDEX=false завершится практичным сообщением Run: ./scripts/dev.sh index. Это предотвращает неожиданную сетевую активность во время MCP discovery.

Подключение к Qwen Code

Если MCP-серверы хранятся в отдельном каталоге, установите туда автономную runtime-копию. Скрипт создаёт подкаталог corporate-kb, копирует только необходимые файлы, создаёт собственный .venv, ставит locked runtime dependencies без dev-пакетов, строит hash-индекс и печатает готовый server entry для Qwen:

./scripts/install-mcp-server.sh /absolute/path/to/mcp-servers

Если версия uv на целевой машине отличается или uv запрещён политиками, установите ту же runtime-копию через pip:

./scripts/install-mcp-server.sh /absolute/path/to/mcp-servers --pip

Для закрытого окружения можно сначала только скопировать файлы, затем настроить корпоративный Python package registry и завершить установку командами, которые напечатает скрипт:

./scripts/install-mcp-server.sh /absolute/path/to/mcp-servers --copy-only

Скопируйте examples/qwen-settings.example.json в .qwen/settings.json проекта и замените все /ABSOLUTE/PATH/... реальными абсолютными путями. Не рассчитывайте на раскрытие ${PROJECT_ROOT} в JSON. В command указан абсолютный путь к .venv/bin/python, а в args — запуск модуля corporate_kb.mcp.server. Поэтому Qwen не зависит от глобальных python, uv, PATH, shell activation или wrapper-скрипта.

Минимальная форма server entry:

{
  "command": "/absolute/path/to/repository/.venv/bin/python",
  "args": ["-m", "corporate_kb.mcp.server"],
  "cwd": "/absolute/path/to/repository",
  "env": {
    "PYTHONPATH": "/absolute/path/to/repository/src"
  }
}

Альтернатива через CLI (выполняйте из корня этого репозитория, подставив абсолютные пути):

qwen mcp add \
  --scope project \
  --timeout 120000 \
  -e KB_KNOWLEDGE_DIR=/absolute/path/to/repository/knowledge \
  -e KB_CACHE_DIR=/absolute/path/to/repository/.cache/kb \
  -e KB_EMBEDDING_PROVIDER=hash \
  -e KB_EMBEDDING_LOCAL_FILES_ONLY=true \
  -e HF_HUB_OFFLINE=1 \
  -e TRANSFORMERS_OFFLINE=1 \
  -e PYTHONUNBUFFERED=1 \
  -e PYTHONNOUSERSITE=1 \
  -e PYTHONPATH=/absolute/path/to/repository/src \
  -e KB_AUTO_INDEX=false \
  local-corporate-kb \
  /absolute/path/to/repository/.venv/bin/python \
  -m corporate_kb.mcp.server

stdio — транспорт по умолчанию, поэтому --transport http здесь не нужен. Синтаксис команды сверен с официальной документацией Qwen Code, но в среде разработки этого репозитория qwen не был установлен, и команда локально не выполнялась. JSON-конфигурация также задаёт cwd и trust: false; статического фильтра tools в ней нет.

Проверка подключения:

qwen
/mcp

Тестовый запрос:

Используй corporate knowledge MCP.
Найди, какой сервис владеет дневными лимитами,
объясни правило и обязательно укажи использованные источники.

Встроенные tools сервера:

  • kb_search — поиск с top_k, min_score, metadata filters и компактными выдержками;

  • kb_get_chunk — лениво загружает один ограниченный фрагмент по chunk_id;

  • kb_run_context_benchmark — защищённый паролем read-only замер качества и сжатия;

  • kb_get_document — ограниченный извлекаемый фрагмент документа по document_id;

  • kb_list_documents — metadata документов без embeddings;

  • kb_stats — состояние индекса и абсолютные пути.

Не задавайте статический includeTools в Qwen settings, если используете управляемые tools из UI: клиентский allowlist скроет новые схемы от LLM. Прямой HTTP-клиент обновляет /mcp discovery, а однофайловый stdio proxy перечитывает каталог после перезапуска Qwen.

Ручной запуск stdio server:

KB_LOG_LEVEL=DEBUG ./.venv/bin/python -m corporate_kb.mcp.server

stdout зарезервирован для MCP-протокола; все application logs направляются в stderr.

Удалённый MCP по HTTP

Удалённый режим заранее загружает готовый индекс и только после этого открывает порт. Поэтому все подключённые Qwen CLI используют один прогретый процесс и не строят embeddings при каждом запросе. Endpoint реализует рекомендованный для удалённых MCP-серверов Streamable HTTP, а не устаревший SSE.

1. Подготовить сервер

Скопируйте репозиторий и документы на сервер, установите runtime и один раз постройте индекс:

cd /opt/corporate-kb
./scripts/setup-pip.sh --no-dev
./scripts/dev.sh index-hash

Сгенерируйте отдельный секрет длиной не менее 32 символов:

openssl rand -hex 32

Для прямого запуска внутри доверенной сети или VPN задайте секрет и запустите listener на всех сетевых интерфейсах:

export KB_MCP_HTTP_BEARER_TOKEN='PASTE_GENERATED_TOKEN'
export KB_MCP_HTTP_HOST='0.0.0.0'
export KB_MCP_HTTP_PORT='8000'
export KB_AUTO_INDEX='false'

./scripts/start-mcp-http.sh

Публичный health check не раскрывает тексты документов:

curl http://10.0.0.5:8000/health

Для stdio-клиента сервер также предоставляет защищённый read-only JSON API. Например, проверка поиска использует обычный GET и тот же Bearer-токен:

curl -G 'http://10.0.0.5:8000/api/v1/search' \
  -H 'Authorization: Bearer PASTE_GENERATED_TOKEN' \
  --data-urlencode 'query=какой сервис владеет дневными лимитами' \
  --data-urlencode 'top_k=3'

Доступны /api/v1/search, /api/v1/document, /api/v1/chunk, защищённый /api/v1/admin/context-benchmark, /api/v1/documents и /api/v1/stats. Они используют тот же прогретый индекс, что и MCP tools, не строят embeddings на клиенте и не изменяют документы.

Сам /mcp требует заголовок Authorization: Bearer .... Ограничения по Host, Origin, домену или IP нет: сервер принимает клиента с любого адреса, если передан правильный токен. KB_AUTO_INDEX=false гарантирует, что удалённый процесс не начнёт неожиданную переиндексацию.

Проверяйте с клиентской машины не только /health, но и настоящий MCP initialize:

curl -i --max-time 15 \
  'http://10.0.0.5:8000/mcp' \
  -H 'Authorization: Bearer PASTE_GENERATED_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}'

Ожидается HTTP/1.1 200. 401 означает неверный токен, 404 — неверный путь, а 421 — что запущена старая сборка с Host allowlist. Прямой FastMCP listener, запущенный через Python или uv, использует обычный HTTP. https:// указывайте только при наличии TLS reverse proxy; иначе Qwen обычно сообщает TypeError: fetch failed.

2. Подключить Qwen CLI

Перед раздачей впишите в корневой install.sh публичный HTTPS-адрес сервера и созданный токен:

default_mcp_url="https://kb.company.example/mcp"
default_mcp_token="THE_SERVER_TOKEN"

Сотруднику передаётся только этот один файл. В любом каталоге он выполняет:

bash install.sh

Скрипт не скачивает репозиторий, документы или Python-зависимости и не создаёт каталог RAG. Он только добавляет подключение corporate-kb в пользовательскую конфигурацию уже установленного Qwen Code. После запуска сотрудник перезапускает qwen и проверяет соединение через /mcp.

Bearer-токен внутри готового скрипта является секретом: раздавайте файл через защищённый корпоративный канал. Для отзыва доступа замените токен на сервере и выпустите новый скрипт.

3. Доступ через интернет

Не передавайте Bearer-токен по открытому интернету через обычный HTTP. Оставьте backend на 127.0.0.1:8000, а наружу опубликуйте его как HTTPS через Nginx, Caddy, ingress или корпоративный API gateway:

export KB_MCP_HTTP_BEARER_TOKEN='PASTE_GENERATED_TOKEN'
export KB_MCP_HTTP_HOST='127.0.0.1'
./scripts/start-mcp-http.sh

Минимальные существенные параметры location для Nginx:

location /mcp {
    proxy_pass http://127.0.0.1:8000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_buffering off;
    proxy_read_timeout 3600s;
}

После этого клиент подключается к https://kb.example.com/mcp. TLS-сертификат и сетевой доступ настраиваются на reverse proxy; порт 8000 не должен быть открыт наружу.

Когда документы изменились, выполните ./scripts/dev.sh index-hash и перезапустите HTTP-процесс. Уже работающий процесс намеренно продолжает обслуживать согласованную старую версию индекса до рестарта.

Добавление Confluence-страницы

Экспортируйте страницу в HTML либо сохраните её как Markdown и положите внутрь knowledge/. Поддерживаются .md, .markdown, .html, .htm, .txt. Скрытые каталоги, .git, .cache, __pycache__, node_modules, бинарные и неподдерживаемые файлы игнорируются. После изменения перестройте индекс; при обычном запуске несовпадение knowledge_hash также инвалидирует кэш.

Пример front matter:

---
document_type: service
service: limits-service
domain: payments
status: current
authority: confluence
authority_priority: 80
owner: limits-team
source_id: "confluence-12345"
source_url: "https://confluence.example.com/pages/12345"
last_reviewed: "2026-07-20"
custom_field: "неизвестные поля тоже сохраняются"
---

# Limits Service

Без front matter заголовок берётся из первого H1 или имени файла, source_id — из относительного пути, status=current, authority=local_file, authority_priority=50.

Кэш и конфигурация

Пересобрать кэш:

./scripts/dev.sh index

Полностью удалить его можно командой rm -rf .cache/kb, после чего снова выполнить kb index. Запись каждого файла атомарна, а manifest.json заменяется последним. Повреждение JSON/NumPy, несовпадение схемы, модели, dimension, query instruction, chunking config или knowledge hash приводит к понятной invalidation, а не к неясной NumPy-ошибке.

Все параметры перечислены в .env.example. Основные:

  • KB_EMBEDDING_PROVIDER=sentence_transformers|hash;

  • KB_EMBEDDING_MODEL=./models/Qwen3-Embedding-0.6B — локальный каталог model files;

  • KB_EMBEDDING_LOCAL_FILES_ONLY=true — fail-closed запрет сетевой загрузки модели;

  • KB_EMBEDDING_DEVICE=auto|cpu|mps|cuda;

  • KB_EMBEDDING_DIMENSION=1024;

  • KB_CHUNK_SIZE_TOKENS=700, KB_CHUNK_HARD_MAX_TOKENS=900, KB_CHUNK_OVERLAP_TOKENS=80;

  • KB_AUTO_INDEX=false.

  • KB_MCP_HTTP_HOST, KB_MCP_HTTP_PORT, KB_MCP_HTTP_PATH;

  • KB_MCP_HTTP_BEARER_TOKEN — обязательный секрет для HTTP-режима;

Относительные пути разрешаются относительно текущего project working directory; kb stats показывает итоговые абсолютные пути.

Проверки

./scripts/dev.sh lint
./scripts/dev.sh typecheck
./scripts/dev.sh test
./scripts/dev.sh check

Обычный shell-скрипт scripts/dev.sh также объединяет повседневные команды:

./scripts/dev.sh install
./scripts/dev.sh install-pip
./scripts/dev.sh install-semantic
./scripts/dev.sh install-pip-semantic
./scripts/dev.sh test
./scripts/dev.sh lint
./scripts/dev.sh typecheck
./scripts/dev.sh index-hash
./scripts/dev.sh search-hash
./scripts/dev.sh index
./scripts/dev.sh search
./scripts/dev.sh index-semantic
./scripts/dev.sh eval
./scripts/dev.sh serve
./scripts/dev.sh serve-http

Тесты всегда инжектируют hash provider и не требуют интернета, Hugging Face, GPU, Qwen Code, Docker или внешней БД. Интеграционные тесты проверяют как in-memory MCP transport, так и HTTP handshake через ASGI без открытия сетевого порта.

Ограничения MVP и развитие

  • Полный brute-force cosine scan подходит для небольшой локальной базы, но не для миллионов чанков.

  • При изменении документов неизменившиеся chunks и их embeddings переиспользуются из предыдущего кэша; полный пересчёт нужен только для новых или изменившихся chunks.

  • Нет Confluence REST API, OAuth, фоновой синхронизации и HTML-адаптеров под каждый вариант экспорта.

  • Нет reranker, hybrid/BM25 retrieval и отдельной оценки authority при ранжировании.

  • Точный token counter реальной модели не используется для предварительного chunking: интерфейс TokenCounter отделён, поэтому его можно подключить без связи chunker с SentenceTransformer.

  • Статический Bearer-токен даёт всем клиентам одинаковые права; для персональных учётных записей, отзыва сессий и аудита нужен внешний gateway/IdP либо полноценный OAuth.

Для перехода на настоящую Vector DB нужно реализовать PostgresKnowledgeStore или QdrantKnowledgeStore с тем же контрактом KnowledgeStore, выбрать реализацию при сборке KnowledgeService и сохранить API сервиса/MCP без изменений. Следующим этапом стоит добавить инкрементальный cache manifest, batch upsert, hybrid retrieval и production evaluation corpus.

Available Tools

4 tools
kb_get_documentA

Return one complete normalized document after kb_search identifies its document_id.

Inputs: document_id (required). Returns: object

kb_list_documentsB

List filtered document metadata without document bodies or embeddings.

Inputs: limit, domain, status, service, +1 more. Returns: object

kb_statsB

Return index counts, identity, timestamps, and resolved local directories.

Inputs: no named parameters. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: kb_search for searching, kb_get_document for retrieving full documents, kb_list_documents for listing metadata, and kb_stats for index statistics. No overlap or ambiguity.

    Naming Consistency5/5

    All tools follow a consistent 'kb_verb' pattern with snake_case (e.g., kb_search, kb_get_document). Naming is predictable and uniform.

    Tool Count5/5

    With 4 tools, the server is well-scoped for a corporate knowledge base: search, retrieval, listing, and statistics. Each tool adds value without redundancy.

    Completeness5/5

    The tool surface covers the core operations for a read-only knowledge base: search, get full document, list metadata, and view stats. No obvious gaps given the intended use case.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Local-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.
      3
      14 npm
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      A local-first document retrieval engine that mounts as an MCP tool for agents to index files, search for relevant passages, and let the agent's own LLM answer.
      4
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      A local RAG knowledge base MCP server that exposes semantic document search as tools using zvec for vector storage and Qwen3-Embedding for text embedding.
      1
      -
    • A
      license
      A
      quality
      B
      maintenance
      Indexes local Markdown/text files into a SQLite database with vector embeddings and provides MCP tools for semantic search without cloud dependencies.
      3
      1
      AGPL 3.0