yt-intel MCP Server
by Talhaz
README.md
# yt-intel MCP Server — The Markup (Automation 04)
An MCP server exposing [`../yt`](../yt) (yt-intel)'s channel data as diagnostic
tools — works with any MCP client (Claude Desktop, Claude Code, Cursor, Codex,
or anything else speaking MCP), not tied to one product. Sibling to
[`../yt`](../yt), [`../storyboard`](../storyboard), and
[`../scriptwriter`](../scriptwriter).
## What this is for
Answering "how is this channel actually doing, and what should I do next"
directly from an editor or chat client, without opening the yt-intel web UI.
Nine tools, organized around the diagnostic questions a producer actually asks
in sequence, not one tool per database table:
**Channel health**
- `channel_overview` — subscriber/view growth trend, shorts vs. long-form
split, upload cadence
- `list_videos` — filterable/sortable base listing
**Performance diagnosis**
- `diagnose_video` — the "why is this video doing what it's doing" tool: stats,
analytics, geo, traffic sources, algorithm alignment, momentum, and the hook
- `find_underperformers` / `find_winners` — ranked lists annotated with a
Type-1 (execution/bad hook) vs. Type-2 (topic ceiling) diagnosis per the
house Topic Selection Checklist's own Section 8 logic
- `search_tag_gaps` — search terms driving views with no matching tag
**Content search** — Postgres full-text search (the GIN indexes already in
yt-intel's schema — `ix_transcripts_fts`, `ix_videos_title_fts` — were built
and unused; this is what finally uses them), not a naive `LIKE` scan:
- `search_transcripts` — ranked, returns highlighted snippets, not just IDs
- `search_videos` — same, over title + description
**Topic/script vetting** — reuses `scriptwriter`'s already-built, already-
tested logic directly (a local path dependency, not a copy):
- `check_topic` — the Top Country/Best Source data check before committing to
a topic
- `qa_script` — the full mechanical QA checklist (word count/pace, bracket
verification, duplicate-fact detection, timestamp math)
## Why Postgres full-text search, not Elasticsearch
At ~67 videos and a few hundred KB of transcript text, this is far below the
scale where Elasticsearch's distributed architecture earns its operational
cost (a second service to deploy and keep in sync, on a 2-4GB VPS shared with
three other apps). Every source on this compared agrees Postgres full-text
search handles the large majority of use cases with zero added infrastructure,
and the GIN indexes this needs already exist in yt-intel's schema, unused.
`pgvector` (semantic/meaning-based search) is the natural v2 if keyword search
proves insufficient in practice — not Elasticsearch, at this scale.
## Quick start
`check_topic`/`qa_script` need `../scriptwriter` present as a sibling
directory and installed FIRST — it's not in this project's own dependency
list (a `file://` path dependency proved fragile: an absolute path only
resolves on one machine, and pip's handling of a relative one was
inconsistent enough to break a real Docker build — see `pyproject.toml`'s
own note and `Dockerfile`'s comment).
```bash
python -m venv .venv
./.venv/Scripts/python.exe -m pip install -e ../scriptwriter # first
./.venv/Scripts/python.exe -m pip install -e ".[dev]" # Windows
cp .env.example .env # YTINTEL_DATABASE_URL, OWN_CHANNEL_ID
```
Run locally over stdio (for Claude Desktop / Cursor / Codex config):
```bash
python -m ytintel_mcp.server
```
Run over HTTP (for a remote/VPS deployment):
```bash
YTINTEL_MCP_TRANSPORT=http python -m ytintel_mcp.server
```
## Connecting a local MCP client (Claude Desktop / Cursor / Codex)
Each client spawns this server as a subprocess over stdio — point it at this
project's venv Python and the module:
```json
{
"mcpServers": {
"ytintel": {
"command": "D:/Axion/ytintel-mcp/.venv/Scripts/python.exe",
"args": ["-m", "ytintel_mcp.server"],
"env": {
"YTINTEL_DATABASE_URL": "postgresql+psycopg://yt:yt@localhost:5432/yt_intel",
"OWN_CHANNEL_ID": "UCODE52XZvkuimEZfGD10Bcw"
}
}
}
}
```
Claude Desktop: `claude_desktop_config.json` (Settings → Developer → Edit
Config). Cursor: Settings → MCP → Add new MCP server (same JSON shape).
Codex: its own MCP server config, same `command`/`args`/`env` fields.
## Deploying to the VPS — together with scriptwriter
**This project has a local path dependency on `../scriptwriter`** (for
`check_topic`/`qa_script`, which import scriptwriter's `domain/` modules
directly rather than vendoring copies — see `pyproject.toml`). That means the
Docker image can only be built where BOTH projects exist side by side, and
the two must be deployed together, not independently. Concretely, on the VPS:
```bash
# 1. Clone (or already have) BOTH projects as siblings under the same parent,
# e.g. ~/Axion/scriptwriter and ~/Axion/ytintel-mcp — mirroring this dev
# machine's D:\Axion layout. The path dependency in ytintel-mcp's
# pyproject.toml is an ABSOLUTE dev-machine path
# (file:///D:/Axion/scriptwriter) that only matters locally — the
# Dockerfile does NOT use it; it installs scriptwriter from the shared
# build context instead (see Dockerfile's own header comment), so the
# exact clone path on the VPS doesn't need to match this dev machine's.
cd ~/Axion
git clone <scriptwriter repo> scriptwriter
git clone <ytintel-mcp repo> ytintel-mcp
# 2. scriptwriter's own .env (needed for its own deploy — OPENAI_API_KEY /
# MISTRAL_API_KEY, YTINTEL_DB_PASSWORD, YTINTEL_NETWORK_NAME — see
# ../scriptwriter/README.md's own Deployment section) and ytintel-mcp's
# .env (same YTINTEL_DB_*/YTINTEL_NETWORK_NAME vars, plus OWN_CHANNEL_ID)
cp scriptwriter/.env.example scriptwriter/.env && nano scriptwriter/.env
cp ytintel-mcp/.env.example ytintel-mcp/.env && nano ytintel-mcp/.env
chmod 600 scriptwriter/.env ytintel-mcp/.env
# 3. Confirm yt-intel's actual Docker network name BEFORE either deploy —
# both .env files' YTINTEL_NETWORK_NAME must match this exactly:
docker network ls | grep default
# 4. Deploy scriptwriter first (no cross-project build dependency, so order
# doesn't strictly matter, but this mirrors provisioning it before the
# tool that references its code)
cd ~/Axion/scriptwriter
docker compose -f docker-compose.prod.yml up -d --build
# 5. Deploy ytintel-mcp — note the build context is the AXION ROOT, not this
# directory (the Dockerfile COPYs ../scriptwriter into the image):
cd ~/Axion
docker compose -f ytintel-mcp/docker-compose.prod.yml up -d --build
```
Port 8003 (yt-intel=8000, storyboard=8001, scriptwriter=8002, this=8003),
bound to `127.0.0.1` like the others — add it to the same Caddy reverse proxy
if a remote MCP client needs to reach it over the network (streamable-http,
not stdio, is what a remote deployment serves — see `config.py`'s
`YTINTEL_MCP_TRANSPORT`).
**Redeploying after a scriptwriter code change**: because the image bakes in
a copy of scriptwriter's code at build time (not a live mount), ytintel-mcp's
image must be rebuilt (`docker compose -f ytintel-mcp/docker-compose.prod.yml
up -d --build`) whenever `domain/topic_scoring.py` or `domain/script_qa.py`
changes on scriptwriter's side — a plain `git pull` on scriptwriter alone
does not update the already-built ytintel-mcp container.
## Testing
```bash
./.venv/Scripts/python.exe -m pytest -q
./.venv/Scripts/python.exe -m ruff check .
./.venv/Scripts/python.exe -m mypy src
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing