- Python 89.8%
- Dockerfile 10.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 19s
|
||
| .forgejo/workflows | ||
| src/mineru_mcp | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| pyproject.toml | ||
| pytest.ini | ||
| README.md | ||
| renovate.json | ||
| requirements.txt | ||
MinerU MCP Server
MCP-сервер для интеграции с self-hosted MinerU через протокол Model Context Protocol (MCP) с транспортом Streamable HTTP.
🤖 Разработка с участием ИИ (Vibe Coding)
Этот проект создается с активным использованием ИИ-инструментов (включая модели семейства Qwen). ИИ берет на себя генерацию кода и рутину, а архитектура, ревью и тестирование остаются под полным контролем человека для гарантии качества и безопасности.
Описание
MinerU MCP Server — это микросервис, который предоставляет LLM-агентам возможность извлекать текст и структурированные данные из документов (PDF, изображения, DOCX, PPTX, XLSX) через стандартизированный протокол MCP.
Сервер абстрагирует сложность асинхронного API MinerU, предоставляя агенту единый атомарный инструмент parse_document, который внутри себя управляет загрузкой файлов, отправкой задач, опросом статуса и возвратом результата.
✨ Возможности
- ✅ Streamable HTTP транспорт — современный стандарт MCP
- ✅ Асинхронная обработка — внутренний поллинг задач MinerU, для LLM выглядит как один вызов
- ✅ Гибкая передача файлов — поддержка публичных URL и Base64 для локальных файлов
- ✅ Настраиваемые параметры — таймауты, бэкенд парсинга, уровень детализации через переменные окружения
- ✅ Оптимизация контекста — по умолчанию возвращает только Markdown, структурированные данные опциональны
- ✅ Надежная обработка ошибок — понятные сообщения для LLM с рекомендациями по исправлению
- ✅ Автоматическая очистка — временные файлы удаляются после обработки
- ✅ Готовность к развертыванию — разделение ответственности, тесты, Docker, non-root пользователь
- ✅ CI/CD интеграция — тесты выполняются при сборке Docker-образа
📁 Структура проекта
mineru-mcp-server/
├── .gitignore
├── docker-compose.yml # Оркестрация контейнеров
├── Dockerfile # Сборка образа с автоматическим запуском тестов
├── pyproject.toml # Конфигурация Python-пакета (src layout)
├── pytest.ini # Конфигурация pytest
├── README.md # Документация
├── requirements.txt # Зависимости проекта
├── src/
│ └── mineru_mcp/ # Папка приложения
│ ├── __init__.py # Делает директорию Python-пакетом
│ ├── config.py # Управление переменными окружения
│ ├── file_handler.py # Логика работы с файлами (URL, base64, temp)
│ ├── mineru_client.py # Клиент MinerU API (отправка, поллинг, парсинг)
│ ├── mcp_tools.py # Определение MCP инструментов
│ └── main.py # Точка входа (запуск Uvicorn)
└── tests/
├── __init__.py # Делает директорию тестов Python-пакетом
├── conftest.py # Фикстуры и моки (respx)
├── test_file_handler.py # Тесты обработки файлов
└── test_mineru_client.py # Тесты клиента MinerU
🚀 Быстрый старт
Предварительные требования
- Docker и Docker Compose
- Self-hosted экземпляр MinerU (или доступ к нему по сети)
- (Опционально) Python 3.11+ для локальной разработки
1. Клонирование репозитория
git clone https://git.maydayoffice.kz/maydayoffice/mineru-mcp-server.git
cd mineru-mcp-server
2. Настройка переменных окружения
Отредактируйте docker-compose.yml и укажите адрес вашего MinerU:
environment:
- MINERU_URL=http://mineru:8000 # Замените на адрес вашего MinerU
- MINERU_BACKEND=hybrid-engine
- MCP_TIMEOUT_SECONDS=600
- MCP_POLL_INTERVAL_SECONDS=5
- MCP_PORT=8001
- DEFAULT_EFFORT=medium
- LOG_LEVEL=INFO
- MCP_API_KEY=your-super-secret-api-key-12345
3. Запуск через Docker Compose
# Сборка и запуск всех сервисов
docker-compose up -d
# Просмотр логов
docker-compose logs -f mineru-mcp
MCP-сервер будет доступен по адресу: http://localhost:8001/mcp
⚙️ Конфигурация
Все параметры настраиваются через переменные окружения в docker-compose.yml:
| Переменная | Описание | Пример / По умолчанию |
|---|---|---|
MINERU_URL |
Адрес self-hosted MinerU API | http://mineru:8000 |
MINERU_BACKEND |
Бэкенд парсинга | hybrid-engine (также: pipeline, vlm-engine, vlm-http-client, hybrid-http-client) |
MINERU_LANG_LIST |
Список языков для улучшения OCR (через запятую) | cyrillic (также: east_slavic, ch, korean, arabic, devanagari и др.) |
MCP_TIMEOUT_SECONDS |
Максимальное время ожидания задачи (сек) | 600 (10 минут) |
MCP_POLL_INTERVAL_SECONDS |
Интервал опроса статуса задачи (сек) | 5 |
MCP_PORT |
Порт MCP-сервера | 8001 |
DEFAULT_EFFORT |
Уровень детализации (только для hybrid-бэкендов) | medium (или high для максимальной точности) |
LOG_LEVEL |
Уровень логирования | INFO (или DEBUG, WARNING, ERROR) |
💡 Совет: Параметр
lang_listнаиболее эффективен при использовании бэкендаpipeline, но может быть указан для любого бэкенда. Для документов на русском и казахском языках оптимально использоватьcyrillicилиeast_slavic.
Использование
Инструмент parse_document
Основной инструмент для извлечения текста и структуры из документов.
Параметры
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
source_type |
string | ✅ Да | Тип источника: "url" или "base64" |
source |
string | ✅ Да | URL файла или base64-строка с содержимым |
filename |
string | ✅ Да | Имя файла с расширением (например, report.pdf) |
include_structured_data |
boolean | ❌ Нет | Если true, возвращает также структурированные данные (по умолчанию false) |
Пример 1: Парсинг по публичному URL
{
"source_type": "url",
"source": "https://example.com/document.pdf",
"filename": "document.pdf",
"include_structured_data": false
}
Пример 2: Парсинг локального файла через Base64
{
"source_type": "base64",
"source": "JVBERi0xLjQKJeLjz9M...",
"filename": "local-document.pdf",
"include_structured_data": true
}
Важные замечания
️ Передача файлов:
- Если вы используете
source_type="url", URL должен быть публичным и доступным из интернета. - Если файл находится на вашей локальной машине или в закрытой сети, обязательно используйте
source_type="base64". - Если вы не уверены, доступен ли URL, сразу используйте
source_type="base64"— это гарантированно сработает.
⚠️ Формат ответа:
- По умолчанию возвращается только Markdown (
return_md=true), что оптимально для LLM. - Если вам нужны структурированные данные (таблицы, списки контента), установите
include_structured_data=true.
🔗 Интеграция с LiteLLM
LiteLLM выступает как MCP Server Gateway, проксируя запросы от агентов к вашему MCP-серверу.
Конфигурация LiteLLM
Добавьте в ваш litellm_config.yaml:
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
mcp_servers:
- name: mineru_parser
url: http://host.docker.internal:8001
transport: streamable_http
Примечание:
- Если LiteLLM запущен на хосте (не в Docker), используйте
http://localhost:8001 - Если LiteLLM в Docker-контейнере, используйте
http://host.docker.internal:8001 - LiteLLM автоматически управляет сессиями MCP (
Mcp-Session-Id)
🧪 Тестирование
Локальный запуск тестов
# Установка зависимостей
pip install -r requirements.txt
# Запуск тестов
PYTHONPATH=src pytest tests/ -v
Тестирование через Docker
Тесты автоматически выполняются при сборке Docker-образа. Если тесты падают, сборка прерывается:
docker-compose build --no-cache mineru-mcp
Ручное тестирование через curl
Шаг 1: Инициализация сессии
curl -v -X POST http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "curl-test",
"version": "1.0"
}
}
}'
Скопируйте Mcp-Session-Id из заголовков ответа.
Шаг 2: Вызов инструмента
curl -X POST http://localhost:8001/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: ВАШ_SESSION_ID" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "parse_document",
"arguments": {
"source_type": "url",
"source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"filename": "dummy.pdf",
"include_structured_data": false
}
}
}'
🏗 Архитектура
Поток данных
[Агент / LLM]
│
▼ (JSON-RPC через Streamable HTTP)
[LiteLLM Gateway]
│
▼ (проксирование с Mcp-Session-Id)
[MCP Сервер] ← этот проект
│
├─► Скачивает файл по URL или декодирует Base64
├─► Сохраняет во временный файл
├─► Отправляет задачу в MinerU (/tasks)
├─► Опрашивает статус (/tasks/{id}) каждые N секунд
├─► Получает результат (/tasks/{id}/result)
├─► Парсит ответ (учитывает структуру dict в MinerU v3.x)
└─► Удаляет временный файл
│
▼
[MinerU API]
Ключевые решения
-
Атомарный инструмент с внутренним поллингом
Агент видит один вызовparse_document, хотя внутри происходит множество HTTP-запросов к MinerU. Это упрощает логику агента и избегает таймаутов. -
Гибридная передача файлов (URL + Base64)
- URL: для публичных файлов, экономит память и трафик
- Base64: для локальных файлов, гарантированно работает за NAT
-
Обработка структуры ответа MinerU v3.x
MinerU возвращаетresultsкак словарь (dict), где ключи — имена файлов. Код корректно распаковывает эту структуру. -
Без SSRF-проверок
Вместо попыток валидации URL (что ненадежно), сервер честно пытается скачать файл и возвращает понятную ошибку с рекомендацией использовать base64, если URL недоступен.
🔧 Разработка
Локальный запуск без Docker
# Установка зависимостей
pip install -r requirements.txt
# Установка переменных окружения
export MINERU_URL=http://localhost:8000
export MINERU_BACKEND=hybrid-engine
export MCP_PORT=8001
# Запуск сервера
PYTHONPATH=src python -m mineru_mcp.main
Добавление новых инструментов
- Откройте
src/mineru_mcp/mcp_tools.py - Добавьте новую функцию с декоратором
@mcp.tool() - Реализуйте логику в соответствующем модуле (
mineru_client.pyилиfile_handler.py) - Напишите тесты в
tests/ - Запустите тесты:
PYTHONPATH=src pytest tests/ -v
🐛 Troubleshooting
Проблема: ImportError: cannot import name 'StreamableHTTPServer'
Решение: Убедитесь, что используется MCP SDK версии 1.x. В requirements.txt указано mcp>=1.0.0,<2.0.0.
Проблема: TypeError: FastMCP.run() got an unexpected keyword argument 'host'
Решение: В MCP SDK 1.x метод run() не принимает host и port. Используйте uvicorn для запуска ASGI-приложения, как показано в main.py.
Проблема: Bad Request: Missing session ID
Решение: Протокол MCP Streamable HTTP требует инициализации сессии. При ручном тестировании через curl сначала выполните initialize, получите Mcp-Session-Id и передавайте его в заголовке. LiteLLM делает это автоматически.
Проблема: Not Acceptable: Client must accept both application/json and text/event-stream
Решение: В заголовке Accept укажите оба формата: application/json, text/event-stream.
Проблема: Парсинг завершен, но текст не найден
Решение: Проверьте логи сервера (docker-compose logs -f mineru-mcp). Убедитесь, что:
- MinerU доступен по адресу
MINERU_URL - Файл корректно скачался или декодирован
- Структура ответа MinerU соответствует ожидаемой (словарь
results)
Проблема: Таймаут при парсинге больших документов
Решение: Увеличьте MCP_TIMEOUT_SECONDS в docker-compose.yml (по умолчанию 600 секунд = 10 минут).
📊 Производительность
- Время парсинга: зависит от размера файла и настроек MinerU (обычно 5-30 секунд для PDF до 100 страниц)
- Потребление памяти: минимальное, временные файлы удаляются сразу после обработки
- Масштабируемость: каждый запрос обрабатывается асинхронно, поддерживается параллельная обработка нескольких документов
🔒 Безопасность
- Контейнер запускается от имени non-root пользователя (
mcpuser) - Временные файлы создаются в
/tmpс уникальными UUID-именами - Нет SSRF-проверок, но есть понятные ошибки при недоступности URL
- Все зависимости фиксируются в
requirements.txtс версиями
Лицензия
MIT
Полезные ссылки
🤝 Вклад в проект
- Fork репозитория
- Создайте ветку для фичи (
git checkout -b feature/amazing-feature) - Закоммитьте изменения (
git commit -m 'Add amazing feature') - Запустите тесты (
PYTHONPATH=src pytest tests/ -v) - Запушьте ветку (
git push origin feature/amazing-feature) - Откройте Pull Request
📧 Контакты
По вопросам и предложениям обращайтесь к автору проекта.