news-agent
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@news-agentWhat's the latest news on the Baltic region?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
News Agent — установка и запуск
Полностью автономная система: fetcher → MCP-сервер → claude -p по cron. Никакого Cowork, никакого Claude Desktop. Биллится через Max-подписку.
0. Что у нас по итогу
D:\newDay\
├── config.json ← секреты и настройки (в git не попадает)
├── config.example.json ← шаблон config.json без секретов
├── sources.txt ← RSS-источники: name | url | lang | outlet | camp
├── sources.py ← единственный парсер sources.txt (fetcher, clusterer, MCP)
├── inbox.json ← fetcher пишет, MCP читает
├── clusters.json ← clusterer пишет, MCP читает (событийные кластеры)
├── judge_scores.json ← judge_stage пишет (оценки Jev по кластерам), MCP читает
├── state.json ← MCP читает/пишет атомарно
├── archive.jsonl ← новости старше 72 часов (fetcher дописывает, search_news/cite читают)
├── fetch_errors.json ← последний отчёт фетчера
├── fetcher.py ← парсер RSS
├── doh.py ← DNS-over-HTTPS шим для fetcher (опционально)
├── clusterer.py ← событийная кластеризация (эмбеддинги, CPU-only)
├── judge.py ← типизированные суждения: Choice/Score/Noul, бэкенды Jev и claude -p
├── judge_stage.py ← стадия судьи после clusterer (см. §6b)
├── jev_api_key ← ключ TypeSafe одной строкой (в git не попадает)
├── mcp_server.py ← FastMCP сервер
├── requirements.txt
├── .mcp.json ← конфиг для claude -p
├── prompts\
│ ├── watchman.md ← промпт для алертов (часовой)
│ ├── digest.md ← промпт для дайджеста (раз в сутки)
│ └── reader.md ← промпт читателя-сабагента (Sonnet), один сюжет
├── scripts\
│ ├── run-fetcher.ps1 ← каждые 15 мин (fetcher + clusterer + judge_stage)
│ ├── run-watchman.ps1 ← раз в час (ночью реже)
│ ├── run-mcp-http.ps1 ← MCP-сервер по HTTP, с логона, в цикле
│ ├── run-tunnel.ps1 ← cloudflared-туннель к нему, с логона, в цикле
│ ├── run-feedback-collector.ps1 ← каждые 15 мин; LLM только для ответов на вопросы (§6a)
│ └── sync-digest-skill.py ← собирает SKILL.md задачи дайджеста из prompts/digest.md
├── experiments\ ← лаборатории: cluster_lab.py, judge_lab.py, judge_shadow_compare.py
├── cache\ ← кеш read_full + embeddings.npz
└── logs\ ← логи запусковRelated MCP server: AI News MCP
1. Установка зависимостей
cd D:\newDay
python -m pip install -r requirements.txtПроверь что Python в PATH (нужен 3.11+):
python --version2. Заполни config.json
Скопируй config.example.json в config.json и заполни:
tg_bot_token— от @BotFathertg_chat_id— основной канал «News Claude»tg_log_chat_id— лог-канал «Log Claude News»tg_test_chat_id— тестовый канал (создай если ещё нет)telegraph_token— получить так:curl "https://api.telegra.ph/createAccount?short_name=ClaudeNews&author_name=Claude%20News"В ответе будет
access_token— его сюда.mcp_bearer_token— любая длинная случайная строка; её ждёт HTTP-режим MCP-сервера (scripts/run-mcp-http.ps1) от облачных рутинdoh_resolver_url— (опционально) URL DoH-резолвера видаhttps://host/dns-query. Если задан, fetcher резолвит все хосты через него в обход системного DNS (помогает при DNS-блокировках провайдера). Пусто — DoH выключен, используется системный DNS. Можно задать и переменной окруженияNEWS_DOH_URL, она имеет приоритет над config.json.test_mode: true— пока тестируем, потом переключим в falseqa_enabled/qa_model/qa_effort/qa_max_per_run/qa_timeout_s— (опционально, с 09.09.2026) ответы на вопросы под постами, см. §6a. По умолчанию включено, Sonnet 5, effort medium, не больше 5 ответов за прогон коллектора.qa_enabled: falseвыключает полностью.judge_backend/judge_mode/judge_window_hours/judge_rpm/judge_workers/judge_pairs/judge_max_states/judge_timeout_s— (с 22.09.2026) слой суждений Jev, см. §6b. По умолчаниюjev+shadow: сайдкар пишется, промпты его не видят.judge_backend: "off"илиjudge_mode: "off"выключает стадию целиком.
config.json в .gitignore — токены в репозиторий не уходят.
sources.txt (с 22.09.2026) — пять колонок через |: имя, URL, язык
(en/ru/et/uk/lv/lt/fi…), редакция (outlet: ERR News / ERR rus / ERR est →
ERR, чтобы кластер считал независимые редакции, а не ленты) и лагерь
(baltic / western / russian / ukrainian, для подбора текстов разных
лагерей в reader_packet). Двухколоночные строки по-прежнему работают.
Читает всё sources.py; словарей SOURCE_LANG / SOURCE_OUTLET в коде больше
нет.
3. Первый прогон fetcher вручную
cd D:\newDay
python fetcher.pyДолжно появиться:
inbox.jsonс массивом статейfetch_errors.jsonс отчётомВ консоли —
+Nдля каждого источникаВ начале лога —
DoH active (all hosts via ...)если резолвер задан, иначеDoH unavailable — falling back to system DNS(это нормально)
Если какие-то источники постоянно дают ошибку — закомментируй их в
sources.txt (поставь # в начале строки) или замени URL.
4. Проверь MCP-сервер вручную
python mcp_server.pyСервер запустится в stdio-режиме — он будет ждать команд через stdin. Просто проверь что не упал с ошибкой импорта. Ctrl+C для выхода.
5. Тестовый прогон watchman
cd D:\newDay
.\scripts\run-watchman.ps1Что должно произойти (с 04.09.2026 — два вызова):
Claude Code запустится в headless режиме (Sonnet 5, --effort high, --strict-mcp-config: только news-agent)
watchman_context → окно, alerted_24h, alerts_today, очередь, кластеры строками (вместо get_state + cleanup_state + list_clusters)
применит четыре фильтра; get_cluster только для o=1-кандидатов
watchman_finish → алерты в основной канал + комментарий-методология под каждым + лог решений + патч state одним вызовом
ночью (01–07) скрипт сам пропускает часы, кроме 03:00 и 06:00
Лог запуска будет в D:\newDay\logs\watchman-YYYY-MM-DD.log.
Если что-то пошло не так — там видно где упало.
6. Тестовый прогон digest
Дайджест живёт не в Task Scheduler, а в задаче Claude Desktop (см. §7). Тестовый прогон — кнопка «Run now» у задачи в Code-табе → Routines.
Что должно произойти (пайплайн с 02.09.2026 — механика на сервере):
digest_context + digest_baltic_extra → карта суток по кластерам, балтийский добор, anti-repeat индекс с маркерами ↻/~ в строках кластеров, статистика
ШАГ 3.5: читатели — сабагенты Agent(model=sonnet) по промпту prompts/reader.md, по одному на Baltic/World-сюжет, параллельно; каждый делает три вызова: reader_packet (кластер + близнецы + полные тексты 4 статей разных редакций одним ответом) → verify_card (цитаты/числа/расхождения, которых нет в текстах, выбрасываются) → карта
publish_digest(структура JSON, methodology, topics, notes) → сервер рендерит Telegraph-HTML, публикует, шлёт пост в канал, комментарий- методологию под ним, тех.лог в лог-канал и записывает прогон в state — всё одним вызовом (с 04.09.2026; до этого 7.5/8/9 были отдельными вызовами, и прогон 03.09 оборвался после публикации, не записав прогон)
record_digest_run / send_telegram_comment / send_telegram — fallback
с 06.09 сервер помнит, что напечатал: topics[].printed_text в записи прогона, digest_context отдаёт anti_repeat.prev_text по совпавшим темам и флаг old=n/m в строках кластеров; все резолвы статей идут через inbox + archive.jsonl (статья может истечь из 72ч-окна прямо во время прогона)
промпт задачи планировщика (SKILL.md) собирается из prompts/digest.md скриптом scripts/sync-digest-skill.py — после правки digest.md запускать его; модель больше не читает файл в начале прогона (04.09 это стоило 4 хода)
prompts/digest.md переписан 05.09.2026 компактно (33k → ~29k знаков) после двух разборов прогонов, без потери правил: ИНСТРУМЕНТЫ → ШАГ 1–9 → блок «ИСТОЧНИКИ И ЦИТАТЫ» (все правила про цитаты, язык, [[art_id]], sources и инлайн-атрибуцию в одном месте); anti-repeat целиком в 3.0 (общие режимы A/B/C) и 4.0 (ужесточения World/Tech, войны); исторические пояснения («05.09 так случилось…») из промпта убраны — мотивация правил живёт в памяти проекта и в git-истории. Заголовки «ШАГ N» — стабильные перекрёстные ссылки, не перенумеровывать Старые тулы (list_news, publish_telegraph, update_state, …) остались как fallback. Для разовых вопросов вне прогонов есть
search_news(query, lang, since_iso)— полнотекстовый поиск по inbox и archive.jsonl (все термины подстрокой,lang— языковая зона источника en/ru/et/uk); id из архива работают в cite/read_full. В промпты watchman/digest он намеренно не добавлен: им хватает контекстных тулов, а лишний ход дороже модели.
Серверные тесты: .venv/Scripts/python.exe scripts/test_digest_tools.py,
test_watchman_reader_tools.py, test_search_news.py, test_fetcher_archive.py.
Открой Telegraph-ссылку — проверь:
TOC сверху
Якори #Baltic / #World / #Tech кликаются
Все ссылки рабочие (потому что они теперь не из памяти LLM)
6a. Вопросы под постами (Q&A, с 09.09.2026)
Два способа получить «что это такое» по непонятному термину в выпуске:
«Справка» в карточке. Поле
backgroundв элементах baltic[]/world[] дайджеста: 1–2 предложения общих сведений (что такое PISA, как считают Euribor…), когда сюжет держится на термине, который карточка не объясняет. Сервер рендерит его курсивом с подписью «Справка.» между «Суть» и «Что подчёркивают разные стороны» — так видно, что это общие знания модели, а не цитата из источников. Правила — в prompts/digest.md, ШАГ 3.Вопрос реплаем. Ответь под постом в группе обсуждений сообщением с «?» или начинающимся с «что такое / объясни / как считается / почему…». Коллектор (
feedback_collector.py, каждые 15 мин) распознаёт вопрос, собирает контекст — текст разбора той темы выпуска, о которой спрашивают (по пересечению слов с headline/entities/printed_text), плюс заголовки и summary статей-источников из inbox/archive; для алерта — заголовок, текст поста и статьи — и делает ОДИН вызовclaude -p(Sonnet 5, effort medium,--tools "", без MCP, промпт через stdin). Ответ приходит реплаем в ту же ветку обычным текстом, в течение ~15 минут. Вопросы попадают в feedback_log.json сtype: "question"и блокомanswer(text / msg_id / model или error); обычные реплаи —type: "feedback". Настройкиqa_*в config.json;qa_enabled: false— выключить.
Тесты: scripts/test_feedback_qa.py (claude и Telegram замоканы) и
проверка рендера «Справки» в scripts/test_digest_tools.py.
6b. Слой суждений (TypeSafe Jev, с 22.09.2026)
Между кластеризатором и редакторами (Sonnet-watchman, Opus-дайджест)
стоит дешёвый типизированный судья: judge_stage.py запускается третьим
шагом в run-fetcher.ps1 и для каждого кластера, активного в последние
judge_window_hours (30 ч), задаёт Jev вопросы из своих текстов:
по одному Noul на каждый пункт
user_concernsиз config.json («событие — реальное развитие внутри этого интереса читателя, а не упоминание темы»);Choice секции дайджеста
baltic / world / tech / skip;Score масштаба последствий (4 конкретных уровня);
Noul «конкретное новое событие, а не пересказ/аналитика/анонс»;
Noul «только заявления чиновников без события».
Судится только новое/изменившееся (хеш заголовка + текстов сэмпла; кэш
ответов cache/judge_cache.jsonl), пары кросс-языковых близнецов из
related_ids получают P(same_story) как подсказку к слиянию. Ранжирование
делает код, а не модель: composite() в judge_stage.py собирает оценку
0..1 из распределений (интерес × масштаб × новизна × не-только-заявления,
всё умножено на 1−P(skip)); веса можно крутить без пересуживания. Правило
«отсёк бы»: P(skip) ≥ 0.7 и максимальный интерес < 0.3.
Результат — judge_scores.json (ключ = id кластера). Что с ним делает
сервер, решает judge_mode:
режим | watchman_context / digest_context |
| сайдкар игнорируется |
| без изменений; каждый свежий вердикт дописывается в |
| в строках кластеров появляется |
|
|
Отказоустойчивость: нет ключа / SDK / сети, judge_backend: off — стадия
выходит с кодом 0, сайдкар не трогается; сервер считает сайдкар старше 6 ч
или покрывающий меньше половины окна отсутствующим и работает по охвату,
как раньше. Jev никогда не решает про BREAKING и ничего не шлёт.
Проверка перед включением rank/filter — теневой период 2–3 дня, потом:
D:\newDay\.venv\Scripts\python.exe experiments\judge_shadow_compare.py(сколько выборов Opus/watchman судья отсёк бы, recall@k порядка по
оценке против порядка по охвату). Лаборатория на замороженном снимке:
experiments\judge_lab.py --data experiments\snapshot_<дата> --backend jev --questions v2|v1; тест — scripts\test_judge_stage.py.
7. Регистрация задач
Актуально на 05.09.2026. Пять задач в Task Scheduler и одна в Claude Desktop; ниже — что реально зарегистрировано, а не «как задумывалось».
Task Scheduler
Общее для всех: пользователь docto, «Run whether user is logged on or
not» (хранится пароль), обычные права (не highest), «If the task is
already running → Do not start a new instance». Ни у одной нет автоперезапуска
при падении: периодические просто ждут следующего тика, серверные крутятся
в бесконечном цикле внутри своего .ps1.
Задача | Триггер | Действие | Заметки |
| каждые 15 мин, с 08:07:45 |
| fetcher + clusterer + judge_stage; «only on AC» включён, «Run task as soon as possible after a scheduled start is missed» включён; лимит 72 ч |
| каждый час, с 09:00 |
| watchman; ночное прореживание (01–07 только 03:00 и 06:00) — внутри скрипта, триггер об этом не знает; лимит 72 ч |
| при логоне |
|
|
| при логоне |
|
|
| каждые 15 мин, с 18:08 |
|
|
Проверить всё разом:
Get-ScheduledTask | Where-Object { $_.TaskName -match 'Claude|newsday' } |
ForEach-Object { $i = $_ | Get-ScheduledTaskInfo
[pscustomobject]@{ Task=$_.TaskName; State=$_.State; LastRun=$i.LastRunTime;
Result=$i.LastTaskResult; Next=$i.NextRunTime } } | Format-Table -AutoSizeResult 0 — нормальный выход, 267009 (0x41301) — «ещё выполняется», для
двух серверных задач это штатное состояние. Менять задачи из обычной сессии
нельзя (Access is denied) — только из консоли администратора.
Рестарт MCP-сервера после правки mcp_server.py (туннель трогать не нужно):
Stop-ScheduledTask "Claude News MCP HTTP"; Start-ScheduledTask "Claude News MCP HTTP"Зарегистрировать задачу заново, если планировщик чистый: Task Scheduler →
Create Task, имя и триггер из таблицы, Action → Start a program →
powershell.exe с аргументами из таблицы, Settings → «Allow task to be run
on demand», для серверных задач снять «Stop the task if it runs longer than».
Claude Desktop (дайджест)
Дайджест — scheduled task самого Claude Desktop (Code-таб → Routines), а не
Task Scheduler: cron 8 9 * * * (09:08 по местному; «09:00» в тексте —
округление), запускается как обычная сессия Claude Code с cwd D:\newDay,
permissionMode auto, и его прогоны видны в списке сессий.
Промпт задачи —
%USERPROFILE%\.claude\scheduled-tasks\newday-watchman\SKILL.md(имя каталога историческое). Он собирается изprompts\digest.mdскриптомpython scripts\sync-digest-skill.py; править исходник, потом запустить скрипт, руками SKILL.md не трогать.Реестр задач —
%APPDATA%\Claude\claude-code-sessions\<account>\<session>\scheduled-tasks.json; там же полеmodel(сейчасclaude-opus-5) и список одобренных тулов (approvedPermissions); effort —effortLevelвD:\newDay\.claude\settings.json.Реестр живёт в профиле приложения: переустановка Claude Desktop его стирает, SKILL.md при этом остаётся — задачу придётся создать заново.
8. Переключение в боевой режим
Когда тесты прошли:
Отредактируй
config.json→"test_mode": falseСледующий запуск пойдёт в основной канал.
Откатиться обратно — поменять флаг и снова true. На лету, перезапуск
тасков не нужен.
Архитектурные принципы (чтобы помнить как оно работает)
Fetcher детерминистичен. Только Python, без LLM. Парсит, дедуплицирует, присваивает стабильные id (sha1 от URL). Можно перезапускать сколько угодно раз — id у одной и той же статьи не поменяется.
1a. Clusterer тоже детерминистичен и тоже без LLM. Запускается после fetcher (в том же run-fetcher.ps1, падение некритично). Эмбеддит title+summary мультиязычной моделью multilingual-e5-base строго на CPU (машина игровая, GPU не трогаем), кластеризует 72ч-окно по событиям: базовый порог 0.90 + якорь-основатель против дрейфа центроида; комы
30 статей пересклеиваются внутри порогом 0.93 (storyline → события); кластеры-близнецы одного события на разных языках связываются через related_ids (кросс-языковая похожесть систематически ниже — у кросс-языковых кандидатов квота в related-списке). id кластера = id первой статьи (стабилен, пока она в окне). Векторы кешируются в cache/embeddings.npz — статья эмбеддится один раз в жизни. LLM видит кластеры через list_clusters / get_cluster; поле outlets считает независимые редакции (Pravda RU+UA = одна редакция), это правильная основа для фильтра «подтверждено ≥2 источниками».
MCP — единственный канал для LLM работать с данными. Промпт не делает web-fetch и не парсит RSS. Только
list_news,read_full,cite,publish_*,send_*,get/update_state.
2a. Механика дайджеста живёт на сервере, у модели — только суждение.
digest_context (компактная карта суток + балтийский добор по кластерам
готовый anti-repeat индекс и детерминированные маркеры повторов),
publish_digest(JSON-структура → HTML-шаблон Telegraph + пост в канал),record_digest_run(запись прогона из серверного кеша digest_run_cache.json). До этого модель сама ужимала 67 КБ кластеров скриптами, матчила 67 КБ балтийских лент по заголовкам, строила индекс из 33 КБ state и писала 13 КБ HTML руками — ~половина 10-минутного прогона.
2b. Читатели проверяются кодом, не доверием. Sonnet-читатели (prompts/reader.md)
дают ×5 покрытие статей, но в эксперименте 02.09 выдумывали «дословные»
цитаты и складывали числа сами. Поэтому verify_card проверяет каждую
цитату подстрокой по тексту статьи (кеш read_full, до 60 КБ), число —
как отдельное число в тексте, а «расхождение» требует двух проверенных
цитат из двух статей. Что не нашлось — выбрасывается до того, как
попадёт главной модели.
URL никогда не покидают MCP-сервер в виде «голого» текста. LLM получает только id. Когда нужно процитировать — ставит
[[art_xxx]]плейсхолдер, сервер при отправке/публикации заменяет на реальную ссылку. Галлюцинации URL невозможны архитектурно.Состояние атомарно. state.json пишется через temp + rename. Никаких race conditions между fetcher / watchman / digest.
Fallback-логика в промпте. Если Telegraph упал — отправь короткую версию в TG. Если list_news пустой — не отправляй ничего, пиши в лог.
Если что-то сломалось
Логи:
D:\newDay\logs\— отдельный файл на каждый день и каждый таскТех.отчёт fetcher:
D:\newDay\fetch_errors.json— последний прогонСостояние:
D:\newDay\state.json— можно глазами посмотреть что watchman помнитInbox:
D:\newDay\inbox.json— посмотреть что fetcher собралАрхив:
D:\newDay\archive.jsonl— всё, что вышло за 72 часа (с 05.09.2026; раньше терялось), ~2 МБ/сутки, ротации нет
Стоимость
Биллится через подписку (Max 5x). API-биллинг = 0 (ANTHROPIC_API_KEY
чистится в скриптах). Что реально стоит прогон — в API-эквиваленте по
usage из транскриптов сессий (~/.claude/projects/D--newDay/*.jsonl):
до 04.09.2026: watchman (Sonnet, 12–16 ходов, 24/сутки) ~$12/сутки; дайджест на Fable 5.1 (43 хода, xhigh) ~$29 + 9 читателей по 28 ходов ~$11 — итого ~$52/сутки, три четверти всего расхода подписки.
с 04.09.2026: дайджест на Opus 5 (модель задачи в реестре %APPDATA%\Claude\claude-code-sessions\…\scheduled-tasks.json), effort high (.claude/settings.json → effortLevel), читатели по 3 вызова, publish_digest закрывает прогон одним вызовом; watchman на двух тулах, --effort high, ночью 19 прогонов вместо 24. Ожидание ~$8–10/сутки.
Главный принцип: платят не за модель, а за ходы — на каждом ходу агент заново отправляет весь контекст (40–60k токенов обвязки Claude Code + всё накопленное). Меньше ходов = дешевле при той же модели.
Что улучшить позже
Добавить парсинг HTML-источников без RSS (через BeautifulSoup в fetcher.py)
This server cannot be deployed
Maintenance
Related MCP Connectors
The only News based AI MCP your agents will ever need — custom categories, global regions, and time-scoped results in one tool. We use multi-vector & sparse-hybrid search to search through thousands of articles across the world to find the exact news you're looking for.
Real-time corroborated news events + 5-year archive, for agents. Free tier, no key.
News MCP: real-time headlines & custom news search across 7 regions. Free, just sign in with Google.
News MCP: real-time headlines & custom news search across 7 regions. Free, just sign in with Google.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables Claude to fetch, parse, and manage RSS/Atom feeds through the Model Context Protocol. It supports feed registration and retrieval, allowing users to access article summaries and full content from multiple sources via natural language.1-
- FlicenseNot gradedqualityDmaintenanceLocal MCP server that ingests and normalizes recent AI, tech, and research news from multiple sources, exposing tools for media agents to query and retrieve article content on demand.-
- FlicenseNot gradedqualityCmaintenanceEnables global news retrieval across multiple countries and categories (world, country, technology, science) via RSS feeds, exposing reusable MCP tools for fetching and searching news articles with no API keys required.-
- AlicenseBqualityCmaintenanceMCP server that fetches, ranks, and summarizes global news from 28 RSS sources across 12 categories, exposing 16 tools for LLMs to query technology, AI, finance, politics, and more.416MIT