MCP Content Publisher
by ESkuratov
README.md
# MCP Content Publisher
MCP-сервер для публикации контента и сбора метрик на социальных платформах.
Реализует протокол [MCP (Model Context Protocol)](https://modelcontextprotocol.io/).
## Платформы
| Платформа | Публикация | Метрики | Статус |
|---|---|---|---|
| **Telegram** | ✅ Bot API (sendMessage / sendPhoto) | 🚧 Заглушка (V2: Telethon) | Работает, протестировано |
| **YouTube** | 🚧 Заглушка | 🚧 Заглушка | План |
| **Instagram** | 🚧 Заглушка | 🚧 Заглушка | План |
## Установка
```bash
# Установка через uv
uv sync
# С тестовыми зависимостями
uv sync --group test
```
## Настройка
Скопируйте `.env.example` в `.env` и укажите токен бота:
```bash
cp .env.example .env
```
```
# TELEGRAM
TELEGRAM_BOT_TOKEN="токен_от_BotFather"
```
Токен создаётся через [@BotFather](https://t.me/BotFather) в Telegram.
## Запуск
```bash
# stdio (для интеграции с MCP-хостами — Claude Desktop, Cline и др.)
uv run mcp-content-publisher
# SSE (для отладки и удалённого доступа)
uv run mcp-content-publisher --transport sse --host 127.0.0.1 --port 8001
```
## Деплой на VPS (Docker)
### Локальная сборка и проверка
```bash
# Сборка образа
docker build -t mcp-content-publisher .
# Запуск контейнера (порт только на localhost)
docker run -d --name mcp-content-publisher \
-p 127.0.0.1:8001:8001 \
--env-file .env \
mcp-content-publisher
# Проверка healthcheck
docker ps
```
### Через docker-compose
```bash
# На VPS: скопировать проект, создать .env
cp .env.example .env
# → вписать TELEGRAM_BOT_TOKEN
# Сборка и запуск
docker compose up -d --build
# Логи
docker compose logs -f
# Остановка
docker compose down
```
### Если VPS не достаёт до Telegram (прокси)
Симптом: `publish_post` возвращает `HTTP request failed:` (пустая ошибка), хотя
остальной интернет с сервера работает. Часто это **маршрутизация провайдера к
подсетям Telegram**, а не Docker. Быстрая проверка с хоста (таймаут → прокси нужен):
```bash
curl -s -o /dev/null -w "%{http_code}\n" https://api.telegram.org/bot<TOKEN>/getMe
```
Решение — направить исходящий HTTPS контейнера через прокси (должен принимать
HTTP CONNECT или SOCKS5). Добавьте в `.env`:
```
HTTPS_PROXY="http://user:pass@proxy_host:port"
HTTP_PROXY="http://user:pass@proxy_host:port"
NO_PROXY="127.0.0.1,localhost"
```
> `NO_PROXY=127.0.0.1,localhost` обязателен — иначе healthcheck (SSE на
> localhost) тоже уйдёт через прокси. Публикация медиа (multipart → Bot API)
> идёт тем же путём — прокси покрывает и её.
Пересоздать контейнер, чтобы применить `.env`:
```bash
docker compose up -d --force-recreate
```
Проверка изнутри контейнера (ожидается `200`):
```bash
docker exec mcp-content-publisher python -c \
"import urllib.request,os; r=urllib.request.urlopen('https://api.telegram.org/bot'+os.environ['TELEGRAM_BOT_TOKEN']+'/getMe', timeout=15); print(r.status)"
```
### Подключение MCP-клиента (через SSH-туннель)
> **Безопасность:** сервер не имеет аутентификации, поэтому **не должен быть доступен снаружи**. В Docker-конфиге порт проброшен только на `127.0.0.1`, а сервер слушает только localhost. Доступ — исключительно через SSH-туннель.
На локальной машине поднимите туннель до VPS:
```bash
ssh -L 8001:127.0.0.1:8001 root@<IP-VPS>
```
После этого сервер доступен локально:
```
http://127.0.0.1:8001/sse
```
Пример конфигурации для Claude Desktop / Cline:
```json
{
"mcpServers": {
"content-publisher": {
"transport": "sse",
"url": "http://127.0.0.1:8001/sse"
}
}
}
```
### Внешний доступ (не рекомендуется)
Если всё же нужно открыть сервер наружу (например, за reverse-proxy с basic-auth), потребуется:
1. Пробросить порт на все интерфейсы в `docker-compose.yml`:
```yaml
ports:
- "8001:8001"
```
2. Разрешить внешний Host-заголовок в `.env` (MCP SDK блокирует незнакомые Host заголовки кодом `421` — защита от DNS-rebinding):
```
MCP_ALLOWED_HOSTS="<IP-или-домен>:*"
```
- Несколько хостов — через запятую: `"5.129.207.137:*,mcp.example.com:*"`
- `MCP_ALLOWED_HOSTS="*"` — отключить защиту (любой Host)
- Не задано — только localhost (по умолчанию)
`127.0.0.1`, `localhost`, `[::1]` разрешены всегда (нужны для healthcheck).
## Инструменты MCP
### `publish_post`
Опубликовать пост на указанной платформе.
**Параметры:**
- `platform` (str): `telegram` | `youtube` | `instagram`
- `text` (str): Текст поста. Поддерживает HTML-разметку (`<b>`, `<i>`, `<code>`, `<a href="...">`)
- `channel` (str): Канал для публикации — @username, chat ID или invite link
- `media_urls` (list[str], optional): изображение/видео — **URL** (`https://…`), **локальный путь** (`/app/output/cover.png`) или `file://` URL
- `schedule_at` (str, optional): Время публикации в ISO-8601
**Пример:**
```json
{
"platform": "telegram",
"text": "<b>Привет!</b> Это тестовый пост из MCP сервера",
"channel": "-1001234567890"
}
```
**Ответ:**
```json
{
"post_id": "42",
"status": "published",
"url": "https://t.me/channel/42",
"error": null
}
```
**Медиа (фото):** если передать в `media_urls` локальный путь или `file://` URL —
файл загружается в Telegram через **multipart** (`sendPhoto` с `files`), внешние
URL и публичный хостинг не нужны. Это важно на серверах, где Telegram заблокирован
(см. секцию про прокси) и для локально-генерируемых обложек. HTTP(S)-URL и
`file_id` по-прежнему передаются как есть.
### `get_metrics`
Получить метрики опубликованного поста.
**Параметры:**
- `platform` (str): Платформа
- `post_id` (str): ID поста на платформе
**Ответ:**
```json
{
"post_id": "42",
"platform": "telegram",
"views": 150,
"reactions": 12,
"reposts": 3,
"comments": 2
}
```
> **Примечание:** Telegram Bot API не отдаёт реальные просмотры/реакции. В MVP возвращаются mock-данные. В V2 планируется Telethon (MTProto) для реальных метрик.
### `get_channel_stats`
Получить общую статистику канала.
**Параметры:**
- `platform` (str): Платформа
- `channel` (str): Имя/ID канала
**Ответ:**
```json
{
"platform": "telegram",
"channel": "@channel",
"subscribers": 1000,
"posts_this_week": 5,
"avg_views": 200
}
```
## Форматирование текста
Telegram провайдер автоматически определяет режим разметки:
- **HTML** — если в тексте есть HTML-теги (`<b>`, `<i>`, `<code>`, `<a>`)
- **HTML (по умолчанию)** — для обычного текста (не требует экранирования)
Поддерживаемые HTML-теги в Telegram: `<b>`, `<i>`, `<u>`, `<s>`, `<code>`, `<pre>`, `<a href="...">`
## Тестирование
```bash
# Запуск всех тестов
uv run pytest tests/ -v
# Только unit-тесты
uv run pytest tests/test_publish_server.py -v
# Только интеграционные тесты (MCP протокол)
uv run pytest tests/test_mcp_server_integration.py -v
```
### Что тестируется
- **Модели** — Pydantic-схемы (PublishContent, PublishResult, PostMetrics, ChannelStats)
- **Провайдеры** — Telegram (токен, публикация, метрики), YouTube/Instagram (mock)
- **Реестр провайдеров** — синглтон, неизвестные платформы
- **MCP сервер** — регистрация инструментов, вызов через MCP протокол
## Структура проекта
```
mcp-content-publisher/
├── src/mcp_content_publisher/
│ ├── server.py # MCP сервер (точка входа)
│ ├── models.py # Pydantic-схемы
│ └── providers/
│ ├── base.py # Базовый класс PublishProvider
│ ├── telegram.py # Telegram Bot API
│ ├── youtube.py # YouTube (заглушка)
│ └── instagram.py # Instagram (заглушка)
├── tests/
│ ├── test_server.py # Тесты MCP сервера
│ ├── test_publish_server.py # Тесты провайдеров и моделей
│ └── test_mcp_server_integration.py # Интеграционные тесты
├── .env.example
└── pyproject.toml
```
## Разработка
```bash
# Установка с dev-зависимостями
uv sync --group test
# Запуск тестов
uv run pytest
# Проверка типов
uv run mypy src/
```
TDQS
A3.6/5.0
Scored across 3 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: publishing a post, getting metrics for a post, and getting channel statistics. No overlap or ambiguity.
Naming Consistency5/5
All tools follow a consistent verb_noun snake_case pattern (publish_post, get_metrics, get_channel_stats), making it easy to predict functionality.
Tool Count4/5
With only 3 tools, the server is minimal but functionally complete for basic publishing and stats retrieval. Slightly limited but appropriate for its stated purpose.
Completeness2/5
The server provides publish and read operations but lacks update/delete tools for posts, and no tool to list or manage scheduled posts. Significant lifecycle gaps exist.
Maintenance
ActivitySlowing
ResponsivenessNo issues