Skip to main content
Glama
SergeyLT

Media Library MCP

by SergeyLT
README.md
# Media Library MCP

MCP-сервер и Telegram-бот для ведения личной медиатеки: фильмов, сериалов, анимации, книг и игр.

[![CI](https://github.com/SergeyLT/mcp-mediateka/actions/workflows/ci.yml/badge.svg)](https://github.com/SergeyLT/mcp-mediateka/actions/workflows/ci.yml)

## О проекте

Media Library MCP хранит записи медиатеки в SQLite и предоставляет их через [Model Context Protocol](https://modelcontextprotocol.io/). Telegram-бот принимает запрос на естественном языке, передаёт модели только актуальные схемы MCP-инструментов и возвращает её ответ пользователю.

- MCP-сервер отделён от интерфейса: к одной медиатеке могут подключаться Telegram-бот и другие совместимые клиенты.
- Данные хранятся в локальном SQLite-файле; Docker Compose сохраняет его в именованном volume.
- Вызовы инструментов описаны строгими JSON-схемами и валидируются до работы с базой.
- Логи выводятся в stdout строками JSON и не содержат текста сообщений, ключей и иных секретов.
- Проверки не обращаются к Telegram или OpenAI: модель заменяется фейком, а
  интеграционный тест поднимает MCP-сервер только на локальном loopback-порту.

## Возможности

- Добавление фильмов, сериалов, анимации, книг и игр.
- Поиск по названию, автору/создателю и жанру с частичным совпадением.
- Фильтрация по типу, статусу, жанру, году выпуска и личной оценке.
- Сопоставление распространённых русских и английских названий жанров, например
  «фантастика» / `science fiction` / `sci-fi`.
- Редактирование и удаление записей, включая очистку дублей.
- Подтверждение перед добавлением записи с похожим названием.
- Статистика коллекции.
- Диалог с Telegram-ботом на естественном языке через OpenAI Responses API и MCP-инструменты.
- Набор из 100 демо-записей для первого запуска.

## Стек

| Задача | Технология |
| --- | --- |
| MCP-сервер | MCP Python SDK, Streamable HTTP |
| Telegram-интерфейс | aiogram |
| Модель и tool calling | OpenAI Responses API |
| Хранилище | SQLite, SQLAlchemy async, aiosqlite |
| Конфигурация | pydantic-settings |
| Контейнеры | Docker, Docker Compose |
| Проверки | pytest, Ruff, mypy |

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

```text
.
├── mcp_server/
│   ├── server.py          # запуск Streamable HTTP MCP-сервера
│   ├── tools.py           # MCP-инструменты и их схемы
│   ├── db.py              # async-репозиторий SQLite
│   ├── models.py          # ORM-модель и перечисления предметной области
│   ├── schemas.py         # входные и выходные Pydantic-схемы
│   ├── seed.py            # 100 детерминированных демо-записей
├── telegram_bot/
│   ├── bot.py             # запуск long polling
│   ├── handlers.py        # Telegram-команды и текстовый диалог
│   ├── agent.py           # цикл OpenAI Responses API ↔ MCP-инструменты
│   ├── mcp_client.py      # Streamable HTTP MCP-клиент
│   └── config.py          # конфигурация Telegram-бота
├── shared/logging.py      # JSON-логирование stdout
├── tests/                 # unit- и локальные HTTP-интеграционные тесты
├── docs/
│   └── manual-test-scenarios.md # сценарии ручной проверки демо-базы
├── Dockerfile.mcp         # образ MCP-сервера
├── Dockerfile.bot         # образ Telegram-бота
└── docker-compose.yml     # совместный локальный запуск
```

Бот не обращается к базе напрямую: он получает список инструментов у MCP-сервера, модель выбирает нужный из них, а клиент выполняет вызов через Streamable HTTP. Это оставляет работу с данными в одном слое и позволяет использовать MCP-сервер независимо от Telegram.

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

Нужны Python 3.12+, [uv](https://docs.astral.sh/uv/) и Docker Desktop для варианта с контейнерами.

Скопируйте шаблон настроек:

```powershell
Copy-Item .env.example .env
```

Заполните в `.env` как минимум:

```dotenv
TELEGRAM_BOT_TOKEN=токен_из_BotFather
ALLOWED_USER_IDS=ваш_telegram_user_id
OPENAI_API_KEY=ваш_ключ_OpenAI
```

`OPENAI_MODEL` задаёт модель, а `MAX_TOOL_CALLS` ограничивает число MCP-вызовов для одного сообщения. Не добавляйте `.env` в репозиторий.

### Локальный запуск без Docker

```powershell
uv sync --all-extras
uv run media-mcp-server
```

Во втором терминале:

```powershell
uv run media-library-bot
```

По умолчанию сервер доступен по `http://127.0.0.1:8000/mcp`, поэтому для запуска без Docker установите в `.env`:

```dotenv
MCP_SERVER_URL=http://127.0.0.1:8000/mcp
```

### Docker Compose

```powershell
docker compose up --build
```

Compose передаёт боту внутренний адрес `http://mcp-server:8000/mcp` автоматически. База медиатеки остаётся в именованном volume `media_data` после пересоздания контейнеров. Чтобы остановить сервисы, используйте `docker compose down`; эта команда не удаляет volume.

MCP-сервер публикует порт только на loopback-интерфейсе. Его endpoint готовности
`http://127.0.0.1:8000/health` используется Docker healthcheck; Telegram-бот
запускается только после успешной проверки MCP-сервера.

## Использование Telegram-бота

После `/start` можно писать обычные запросы, например:

```text
Добавь фильм «Прибытие» в планы, режиссёр Дени Вильнёв, жанр фантастика.
```

```text
Какие работы типа animation со статусом completed я оценил на 9 или 10?
```

```text
Покажи статистику моей медиатеки.
```

```text
Покажи завершённые фильмы жанра фантастика после 2000 года с оценкой от 8.
```

Если при добавлении найдётся похожее название, бот покажет существующие записи.
`/confirm` добавит новую запись намеренно, `/cancel` отменит операцию. Похожесть
определяется по нормализованному названию, поэтому окончательное решение всегда
остаётся за пользователем.

Команды:

- `/tools` — показать доступные MCP-инструменты;
- `/reset` — очистить короткую историю текущего диалога;
- `/confirm` — подтвердить добавление похожей записи;
- `/cancel` — отменить ожидающее добавление;
- `/help` — показать подсказку.

## Использование MCP-сервера

Streamable HTTP endpoint: `http://<host>:8000/mcp`.

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

```python
import asyncio

from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client


async def main() -> None:
    async with streamable_http_client("http://127.0.0.1:8000/mcp") as streams:
        read_stream, write_stream, _ = streams
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()
            result = await session.call_tool("get_media_stats", {})
            print(result.structuredContent)


asyncio.run(main())
```

Сервер предоставляет инструменты `list_media`, `search_media`, `get_media`,
`add_media`, `find_similar_media`, `update_media`, `delete_media`,
`update_media_status`, `rate_media` и `get_media_stats`. Их актуальные входные
JSON-схемы возвращаются стандартным запросом MCP `tools/list`.

Для ручной проверки Telegram-бота, фильтров, защиты от дублей и healthcheck
используйте [сценарии тестирования](docs/manual-test-scenarios.md).

## Тестирование

```powershell
uv run ruff format .
uv run ruff check . --fix
uv run mypy mcp_server telegram_bot shared
uv run pytest
```

Тесты проверяют создание и повторный seed базы, поиск с Unicode и частичным
совпадением, фильтры, изменение и удаление записей, обнаружение дублей,
регистрацию MCP-инструментов, отказ транспорта, лимит tool calls и цикл
«модель → инструмент → итоговый ответ». Отдельный интеграционный тест
поднимает локальный Streamable HTTP MCP-сервер, вызывает его настоящим
MCP-клиентом и проверяет healthcheck и отказ невалидных аргументов.

GitHub Actions запускает эти проверки и собирает Docker Compose при каждом push и pull request.

## Возможные улучшения

- Поддержка обложек, вложений и ссылок на внешние сервисы.
- Импорт и экспорт коллекции в JSON или CSV.
- Полнотекстовый поиск и персональные рекомендации.
- Авторизация нескольких пользователей и изоляция их коллекций.
- Кэширование схем MCP-инструментов и пул долгоживущих соединений.
- Наблюдаемость: метрики, трассировка и централизованное хранение логов.

## Лицензия

[MIT](LICENSE)

## Автор

[SergeyLT](https://github.com/SergeyLT)