Про MCP (Model Context Protocol) сейчас не писал разве что самый ленивый. Обычно его используют, чтобы собрать агента: дать модели доступ к почте, календарю, паре внешних API — чтобы она бронировала встречи или присылала мемчик каждое утро. А вот про то, что возможности MCP шире и применять его можно не только привычным способом, написано мало.
Например, у меня возникла такая задача: есть куча файлов, на которые хорошо бы натравить нейросеть, чтобы она искала ответы прямо по ним, а не выдумывала. Файлы разнородные, среди них тяжелые PDF. Закинуть их в модель целиком не выйдет: контекстное окно забьется раньше, чем она доберется до сути. Нужен способ скармливать модели не весь архив разом, а только нужный кусок.
Мне пришла идея применить MCP «с другой стороны» — не для агента, а для аккуратного доступа к большому архиву локальных документов. В этой статье расскажу, как это устроено: почему обычный RAG для такой задачи подходит не всегда, чем его заменить, как собрать маршрут от PDF до базы знаний и где грабли. А заодно покажу, что получается на выходе: доступ к локальным документам, который экономит контекстное окно и токены, не тащит данные в облако и сохраняет след для аудита.
А почему не построить классический RAG?
RAG — нормальный подход, я ничего против него не имею. Векторная база действительно умеет искать по большому корпусу: ты раскладываешь смысл документов по координатам и потом ищешь похожее по близости. Примерно как абстрактное синтаксическое дерево для кода — только вместо синтаксиса семантика.
Но здесь важно не путать две вещи. RAG — это паттерн: извлечение нужной информации по близости использовали в графовых базах задолго до всякого ИИ (я когда-то контрибьютил в графовую БД, и там RAG-подобный подход спокойно жил рядом с алгоритмом Дейкстры).
MCP — это транспорт, протокол: он говорит модели, как и где подцепить источник. Что это за источник — RAG, граф, обычная папка с файлами, — протоколу все равно. Это не альтернативы друг другу, это разные слои.
Плюс есть несколько важных моментов. Во-первых, цена входа: RAG тянет за собой инфраструктуру: эмбеддинг-модель на 8–10 Гбайт, векторную БД, индексацию по расписанию и инженера, который все это поддерживает. Переиндексация по расписанию любит подкинуть сюрприз: попадешь в неудачное окно — получишь неконсистентные данные, потому что часть уже обновилась, а часть еще нет.
Во-вторых — аудит. Если модель ошиблась (сходила не туда, выбрала не тот фрагмент или вообще нагаллюцинировала), в чистом RAG ты не разберешься, почему так вышло. Вот тут и появляется альтернатива: подключить источники через MCP так, чтобы доступ остался управляемым и прозрачным.
Оглавление вместо архива
Модели не обязательно видеть весь архив. Достаточно дать ей компактную карту — индекс, который работает как оглавление. Сначала модель смотрит в него, понимает, куда идти, и только потом забирает конкретный кусок. Контекстное окно тратится не на всю базу знаний, а на один нужный фрагмент.
Идея индексного файла не нова — ее задала спецификация llms.txt (Джереми Ховард, сентябрь 2024). Это и есть тот самый индекс: ты складываешь информацию в хранилище (Git, файловая папка — что угодно), а модели отдаешь индекс. Захотела модель что-то найти — лезет в него, понимает, где это лежит, идет туда и берет. Без постоянного хранения всего архива в окне.
Кстати, приятный побочный эффект MCP: к одному источнику можно подключаться из разных клиентов. Индекс один, а ходить к нему могут хоть три разные модели в трех разных интерфейсах.
Подход рабочий и довольно хороший. Но у готовых реализаций есть ограничение: они хотят, чтобы информация уже была разложена по полочкам — структурирована, отформатирована, категоризирована. Чаще всего такие штуки я встречал в связке с URL: есть готовый API, structured-данные, ставь MCP-сервер и пользуйся. А что делать, если у тебя не аккуратный API, а свалка локальных PDF?
Главный index.md отдает модели общую карту полки: для коротких документов — сразу ссылку на файл, для длинных — ссылку на их субиндекс. Субиндекс — такое же оглавление, но уже внутри одного документа.
Корневой index.md:
# Network Equipment DocumentationБаза знаний по сетевому оборудованию для курса.Руководства, даташиты, спецификации протоколов.## Маршрутизаторы и коммутаторы- [MikroTik RouterOS 7 — Reference Manual](docs/routers/mikrotik-routeros/subindex.md) Полное руководство, ~400 страниц, разбито на главы.- [MikroTik hAP ac3 — Datasheet](docs/routers/mikrotik-hap-ac3.md)- [Cisco Catalyst 9300 — Configuration Guide](docs/switches/cisco-catalyst-9300/subindex.md)## Сетевые протоколы- [BGP — RFC 4271 (краткое содержание)](docs/protocols/bgp-rfc4271.md)- [OSPF — операции и траблшутинг](docs/protocols/ospf-operations.md)## Беспроводные сети- [802.11 Wi-Fi — обзор стандартов](docs/wireless/wifi-standards.md)- [WPA3 — Security Whitepaper](docs/wireless/wpa3-security.md)Субиндекс длинного документа — docs/routers/mikrotik-routeros/subindex.md: # MikroTik RouterOS 7 — Reference ManualГлавы в порядке оригинала. Каждая секция — отдельный файл.## Часть II. Маршрутизация- [004 — Статическая маршрутизация](004-static-routing.md)- [005 — Протокол OSPF](005-ospf.md)- [006 — Протокол BGP](006-bgp.md)- [007 — Policy-based Routing и failover](007-policy-routing.md)## Часть III. Firewall и безопасность- [008 — Firewall: цепочки и правила](008-firewall-chains.md)- [009 — NAT и маскарадинг](009-nat.md)- [010 — Защита от DDoS](010-ddos-protection.md)
DocShelf: полка вместо свалки
Раз готовая инфраструктура с неба не падала, пришлось тряхнуть стариной и сделать ее самому.
Так появился DocShelf — инструмент, который превращает кучу локальных PDF в нормальную полку документов. С понятными разделами, индексами и маршрутом от вопроса к источнику. По сути, это MCP-сервер, который одной рукой держится за модель, а другой — за подготовленную структуру. С той разницей, что структуру он еще и готовит сам.
Почему вообще не отдать модели PDF как есть? Потому что PDF удобен примерно никак. Для парсера это тяжелый формат, для модели — дорогой по токенам. Плюс PDF огромен по объему, а нам нужно ровно обратное — хранить маленькие структурированные куски без картинок и лишней разметки.
Поэтому DocShelf переводит PDF в Markdown. MD читается и моделью, и человеком, рендерится в нормальный вид с заголовками, а места занимает мало. И ретривим мы потом не из PDF, а из этих MD.
Реальный корпус, на котором все это обкатывалось, — курс по сетям: документация к сетевому оборудованию, даташиты на микротики, спеки, всякое сопутствующее железо. Куча оцифрованных документов, которые модель должна была использовать, а не выдумывать.
Пайплайн
Целиком маршрут выглядит так:
PDF → Markdown → секции → index.md / subindex.md → Git → DocShelf MCP → LLM
Сначала PDF переводится в Markdown и чистится. Дальше большие документы режутся на секции — иначе даже аккуратный MD останется неподъемным куском. Из секций собирается индекс: заголовки плюс ссылки на категории. Для совсем крупных документов одного индекса мало, поэтому появляется еще уровень вложенности — сабиндексы. Готовая структура кладется в Git (или другое хранилище), а DocShelf MCP подключает ее к модели.
Отдельная история — размер чанка. Чанк — это фрагмент, на который режется документ (те самые секции из схемы выше): модель забирает не файл целиком, а только эти куски. Слишком мелко нарежешь — модель начнет забрасывать базу десятком запросов на каждый вопрос. Слишком крупно — притащит в окно лишнее и снова его забьет.
Трасса одного вопроса выглядит так: пользователь спрашивает → модель читает основной индекс → находит нужный раздел → при необходимости проваливается в сабиндекс → открывает конкретный md → забирает нужный кусок → собирает ответ. В контекст при этом попадает не вся база, а один индекс и один фрагмент. Сабиндексы, к слову, — это не только про большие документы. Это еще и способ не раздувать основной индекс, когда документов становится много.
Например, вопрос студента: как настроить failover между двумя WAN-каналами на MikroTik RouterOS 7?
Модель читает основной index.md (~3 КБ в контексте), видит запись про RouterOS 7 и запрашивает ее субиндекс: fetch(«docs/routers/mikrotik-routeros/subindex.md»), еще +1,5 КБ. В субиндексе находит главу 007 Policy-based Routing и failover и тянет уже саму секцию: fetch(«docs/routers/mikrotik-routeros/007-policy-routing.md»), +12 КБ. Внутри — нужный раздел про failover, по нему и собирается ответ со ссылкой на источник.
Теперь посчитаем контекст. Полка целиком — около 85 МБ. За весь запрос в окно попало ~16,5 КБ: индекс, субиндекс и одна секция. Это меньше 0,02% от объема полки. И без векторной базы, и без переиндексации.
Грабли
PDF парсится плохо чаще, чем хотелось бы. На сканах все совсем грустно. Кириллица при конвертации любит ломаться — это, пожалуй, самая частая боль. А еще конвертер легко выдает формально валидный, но нечитаемый Markdown — сплошную простыню текста через пробелы и точки с запятой, без заголовков и структуры. С такой кашей модель работает дорого, а человек не может проверить глазами — а значит, не поймет, где конвертация сломалась, и не отладит индекс.
Пара слов про сам конвертер. В DocShelf их два на выбор. По умолчанию работает pymupdf4llm. Быстрый, без GPU, его хватает примерно на 95% технических документов с нормальным текстовым слоем: спеки, даташиты, экспорт из Word или LaTeX. Для сложных случаев (таблицы, формулы, многоколоночная верстка) есть marker-pdf с флагом quality=»high»: он умнее разбирает структуру, но тянет PyTorch (~2 Гбайт) и работает 10–60 секунд на документ. Зависимость подгружается отложенно: не включаешь quality=»high» — тяжелый PyTorch не грузится. Для закрытого контура веса модели надо заранее положить в образ.
Перед нарезкой DocShelf прогоняет markdown через очистку: схлопывает лишние пустые строки и понижает мусорные H1, куски CLI-вывода или колонтитулы, которые конвертер принял за заголовок. Дальше режет по границам H2, секции получают имена вида NNN-slug.md. Операция идемпотентна: при повторном прогоне старая разбивка стирается и собирается заново.
Отдельно про кириллицу. В теле документа DocShelf ее не «лечит» — markdown сохраняется как есть. На практике быстрый конвертер с кириллицей обычно справляется, а где спотыкается на сложной верстке, помогает переключение на marker-pdf. Unicode-aware обработка нужна в другом месте — в именах файлов: заголовок на кириллице нормализуется и транслитерируется в ASCII-подобный slug, чтобы ссылки оставались стабильными. Если документ все же пришел кашей — это видно глазами, и его прогоняем через quality=»high», либо оставляем целым (split=False).
Если скан без текстового слоя, требуется OCR (Tesseract, PaddleOCR) еще до попадания на «полку». Это сознательно вынесено за рамки DocShelf.
Кроме проблем с конвертацией были и другие: то модель тащит лишнее, то бегает за данными слишком часто, то не находит нужный раздел. Собрал самые частые из них и лечение в одну таблицу:
|
Симптом |
Причина |
Решение |
|
Модель приносит много лишнего |
Секции слишком крупные |
Уменьшить размер чанка |
|
Модель делает слишком много запросов |
Секции слишком мелкие |
Укрупнить фрагменты |
|
Ответы странно выглядят на русском |
Сломалась кириллица при конвертации |
Проверить Markdown вручную |
|
Нужный раздел не находится |
Индекс плохо описывает документы |
Переписать заголовки и описания |
Git (и не только Git)
По умолчанию DocShelf работает с Git-репозиторием — это самый удобный формат. Git есть почти в любой инженерной среде, его можно поднять в закрытом контуре, и какой-нибудь GitLab есть почти у всех. Плюсы: версионирование, pull request, review, ограничения доступа, полный трек изменений. Видно, кто и когда поменял документ, что нужно не только для удобства, но и для расследования: можно понять, на какой версии документа модель строила ответ.
Добавить документ — git push. Не надо ничего перезаливать в контекстное окно или в хранилище проекта: обновил индексный файл, остальное приехало через обычный git-флоу.
Но Git — не догма. DocShelf, по-хорошему, все равно, откуда читать: ему важно, чтобы в индексе были правильные ссылки на категории, а сама информация была достижима. Поэтому «полка» спокойно ложится на объектные хранилища (S3, MinIO), на nginx, на обычную файловую систему с папками. Если у архитектора в контуре регламентный запрет на Git за периметром, можно использовать другое хранилище.
Вот как выглядит репозиторий «полки»:
network-shelf/├── .docshelf.json├── index.md└── docs/ ├── routers/ │ ├── mikrotik-routeros/ │ │ ├── subindex.md │ │ ├── 001-introduction.md │ │ ├── 004-static-routing.md │ │ ├── 007-policy-routing.md │ │ └── ... │ ├── mikrotik-hap-ac3.md │ └── mikrotik-hap-ac3.meta.json ├── protocols/ │ └── bgp-rfc4271.md └── wireless/ └── wifi-standards.md
Конфиг .docshelf.json:
{ "name": "Network Equipment Documentation", "base_url": "https://gitea.internal.local/training/network-shelf/raw/branch/main", "default_language": "ru"}
Закрытый контур
Теперь то, ради чего все затевалось. В закрытом контуре интернета может не быть вообще, документы наружу не отдать, а модель — локальная или on-premise. DocShelf в эту схему ложится идеально, потому что не ходит ни в интернет, ни в облако — только до разрешенного хранилища. Модель получает не весь архив, а конкретный фрагмент, и данные остаются внутри периметра.
Ключевой момент — независимость от вендора. Подход не привязан к модели: работает и с ChatGPT, и с Claude, и с DeepSeek, и с GigaChat. Причем с теми их версиями, которые умеют жить on-premise, в закрытом контуре. Это важно, потому что многие чужие MCP-серверы требуют авторизации под конкретным логином и работают только с GPT.
Облачные конкуренты вроде Context7, GitMCP, DeepWiki физически не работают за периметром: это SaaS, им нужен доступ в интернет. То же касается обвязок, которым нужен выход в облако. DocShelf не требует ничего из этого.
Мы почти всегда работаем в закрытых контурах — наши заказчики не выносят свои данные наружу. И обучение у нас такое же: курсы с закрытыми материалами, уникальные оцифрованные учебники, методички, роадмапы — то, чем мы делиться не можем или не хотим. Здесь DocShelf и оказывается на своем месте: модель остается внутри периметра и работает строго по нашей базе знаний.
Когда DocShelf не нужен
Если документов мало, то хватит обычного контекстного окна или проекта, заводить индекс незачем. Если у вас уже есть хороший RAG, который устраивает по качеству, скорости, аудиту и поддержке, — не меняйте его только потому, что хочется MCP. Если нужен полноценный семантический поиск по хаотичному корпусу — эмбеддинги, скорее всего, справятся лучше. А если индекс некому поддерживать, то подход начнет деградировать в тот же день.
И отдельно: MCP-сервер сам по себе — это риск. Небрежно выставленный наружу сервер — это дыра, и таких в интернете светится в избытке. Поднимаете MCP в контуре — обносите его забором так же тщательно, как и все остальное.
ссылка на оригинал статьи https://habr.com/ru/articles/1066908/