Кейс · Веб и SaaS, LLM и агенты
Типичный Иркутск сайт городского медиа об Иркутске и Байкале
«Типичный Иркутск» — городское медиа об Иркутске и Байкале. Мы довели стороннюю открытую тему до своего дизайна и разделов, перевезли сайт на общий сервер, не задев соседние сайты, и открыли его ИИ-агенту: через MCP агент готовит черновики для редакции.
Паспорт проекта
- Клиент
- ООО «Иркутск Сегодня»
- Отрасль
- Медиа
- Срок
- 7 недель
- Команда
- Команда E404 и ИИ-агент
Стек

Задача
«Типичный Иркутск» — городское медиа ООО «Иркутск Сегодня» об Иркутске, области и Байкале. Шесть разделов: еда, туризм, события, наука, Байкал и новости. Адрес — irk.media, в соцсетях — @irkmedia; раньше сайт так и назывался — irk.media.
Сайт вырос из сторонней открытой темы и сервисов, которые шли с ней. Когда проект взяли под git, проблем хватало:
- не работала ни одна публичная форма — все отвечали 403: общий секрет сайта и сервиса заявок нигде не был задан;
- защита REST обходилась — аноним мог получить весь
wp/v2; - в коде лежало чужое — плагины другого сайта, 21 спецпроект и архивы из поставки темы, 53 скрипта миграции;
- сайт жил в Docker-стеке из 7 контейнеров, а работать ему предстояло на общем сервере.
Ограничения были жёсткие. Сервер общий: рядом работают другие сайты владельца издания, и ни одно наше действие не должно было их задеть. Локальной копии у сайта нет — только боевая. А ещё писать на сайт должен уметь ИИ-агент, значит, ему нужен доступ, который нельзя превратить в захват админки.
В октябре 2026 опубликованных материалов на сайте ещё нет: редакция готовит первые тексты. Главная с лентой, статья, комментарии и экраны редакции сняты на стенде с тем же кодом и тестовыми материалами. Цифр аудитории в кейсе нет.

Что сделали
Лента и разделы
У каждого из шести разделов свой цвет и значок. На компьютере разделы слева, на телефоне — лентой вверху и в боковом меню, а навигация — вкладками внизу: «Лента», «Разделы», «Закладки», «Поиск». На главной — сводка новостей, карточки, «Главное за неделю» и блок «У нас можно» с темами для постов читателей.



Статья
Текст набирают в классическом редакторе WordPress, а цитату, врезку-совет, сноску, кнопку, маркер и «Читайте также» ставят кнопками темы. Комментарии — после входа через VK ID или Яндекс ID, с ветками ответов, оценками и модерацией.




Закладки без регистрации
Лайков на сайте нет — вместо них закладки. Они хранятся в браузере читателя — без входа и без сервера, до 200 штук.
- Читатель нажимает «Сохранить» под заголовком статьи.
- Подсказка «Сохранено. Все закладки» ведёт к списку.
- На странице «Закладки» — всё сохранённое, с разделом и датой.


Новость от читателя
Форма «Прислать новость» принимает заголовок, текст, место, дату, раздел и до 10 файлов. Без согласия на обработку данных форма не уйдёт, а публиковать имя и контакты автора можно только по отдельной галочке. Вместе с новостью сохраняются время согласия, его текст и версия документа.


Редактор видит новость в админке, непрочитанные — счётчиком в меню. Файлы читателя видны только редакции. Фото для публикации редактор переносит «копией в медиатеку» — без метаданных снимка, где бывает место съёмки.


Опечатки — сразу в Telegram
Читатель выделяет слово с ошибкой и нажимает Ctrl+Enter — сообщение уходит в Telegram-чат редакции.

ИИ-агент готовит черновики
Для ИИ-агента редакции у сайта свой сервер MCP. В нём девять инструментов: найти, прочитать, создать и изменить запись, убрать её в корзину, загрузить картинку, посмотреть рубрики, найти и сделать короткую ссылку irk.me. Запись агента по умолчанию — черновик. Опубликовать сразу или по расписанию агент может только явно, и редакция получает об этом письмо. Фото агент ставит только с автором и источником, а сайт перекодирует его без EXIF.
Агенту открыты ровно девять инструментов — и ни одного запроса к сайту в обход них.


Правила маркировки живут прямо в описаниях инструментов. Instagram* и Facebook* — со звёздочкой и сноской о Meta, иностранный агент — со статусом при каждом упоминании. Рекламу агент не публикует: её помечает редакция.
* Принадлежат компании Meta, признанной экстремистской организацией и запрещённой в России.
Как устроено

Основу не меняли: сайт с самого начала стоял на WordPress и сторонней открытой теме — её мы довели до своего дизайна и разделов. Свои плагины отвечают за комментарии, просмотры, SEO, формы, рассылку, уведомление о cookies и сервер MCP. Рядом работают три маленьких сервиса: приём опечаток, счётчики репостов и сократитель ссылок irk.me.
Лишнее убрали — 1 943 файла: плагины другого сайта, спецпроекты, архивы и скрипты миграции из поставки темы, Redis. Следом ушли сервис профилей и бот модерации комментариев; без сервиса профилей скрипты темы похудели с 44,6 до 28,9 КБ.
Docker остался только для тестов: каждый прогон поднимает одноразовый стенд с тем же ядром WordPress, что на сервере, и убирает его за собой. На стенде проверяем, на сервер выкладываем — третьей копии сайта нет.
Инженерные решения
Ключ агента — только к его серверу
Проблема. Проверка безопасности показала: пароль приложения, выданный агенту, открывал весь REST сайта, а не только девять инструментов. С правами роли агент мог удалить рубрики, одобрить любой комментарий, сменить себе почту и пароль. Агент, который выполнит чужую инструкцию из прочитанного текста, мог бы так дойти до захвата админки.
Что пробовали. Защита REST от анонимов на сайте уже решала по адресу запроса. Та же проверка нашла её обход: WordPress берёт маршрут из поля rest_route, и тело POST-запроса важнее адреса. Значит, адрес — ненадёжный признак.
Решение. Решать по маршруту, который сервер уже собрался выполнить. Заодно роль агента потеряла право управлять рубриками и менять свою учётную запись.
function irk_mcp_rest_only( $result, $server, $request ) {
if ( null !== $result || ! irk_mcp_is_agent() ) {
return $result;
}
$route = untrailingslashit( $request->get_route() );
if ( '/mcp/irkmedia' === $route
|| 0 === strpos( $route, '/mcp/irkmedia/' ) ) {
return $result;
}
return new WP_Error(
'irk_mcp_rest_only',
'Агенту открыт только сервер MCP — /wp-json/mcp/irkmedia.',
array( 'status' => 403 )
);
}
add_filter( 'rest_pre_dispatch', 'irk_mcp_rest_only', 10, 3 );
Что получилось. После выкладки ключ агента на /wp/v2/categories получает 403, а инструменты работают. Для кейса проверили ещё раз на стенде: агент загрузил обложку и создал черновик, а тем же ключом запрос /wp/v2/users/me — 403.
Переезд на общий сервер, не задев соседей
Проблема. Сайт переезжал из Docker на общий сервер, где уже работали другие сайты владельца. Общий PHP-FPM там настроен так, что его перезагрузка обрывает запросы irk.today.
Решение. До первого шага сняли ответы соседних сайтов и контрольные суммы их конфигов и сверяли их после каждого шага. У irk.media свой PHP-FPM — отдельный процесс, и выкладка перезагружает только его. Сама выкладка идёт так:
- На сервер уходит коммит, на котором прошли все проверки.
- До переключения — копия трёх баз.
- Переключение одной ссылкой: до него сайт работает на прежнем релизе.
- Шесть проверок: главная, лента, вход, приём опечаток, irk.me и его база.
- Проверка не прошла — сайт сам возвращается на прежний релиз.
Что получилось. После переезда ответы соседей и контрольные суммы их nginx, PHP и crontab совпали с исходными. А за одну неделю октября в журнале проекта — 41 релиз.
Сначала тест, потом код, потом глаза
Четыре аудита за двенадцать дней нашли 146 пунктов. Исправляли партиями: сначала тест, который падает на прежнем коде, потом код, полный прогон проверок и выкладка. В одной только партии на прежнем коде падали 112 новых проверок.
Но эта же партия прошла все проверки, а на первых снимках живого сайта нашлись четыре видимые недоработки: карточки сообщений от края до края, сообщение пустой рубрики в одну ячейку сетки, обрубок поля поиска на странице 404 и съехавшие точки в меню. Их исправили в тот же день.
Тесты проверяют только то, что им велели, — поэтому после выкладки, меняющей вид, мы смотрим снимки страниц.
Результат
| Что | До | После |
|---|---|---|
| Публичные формы | не работала ни одна: 403 | новость читателя доходит до редакции — проверено на стенде |
| Чужой и лишний код | плагины другого сайта, спецпроекты, архивы и скрипты из поставки темы | удалено 1 943 файла |
| Где работает сайт | Docker-стек из 7 контейнеров | общий сервер, свой PHP-FPM |
| Автоматические проверки | 41 в тестах репозитория и 49 сквозных | 1 228 перед каждой выкладкой |
| Lighthouse, главная, телефон | нет данных | 100 по всем четырём категориям, основной контент — за 1,6–1,7 с |
Lighthouse 13.5.0 дал главной 100 по производительности, специальным возможностям, рекомендациям и SEO — и на телефоне, три замера подряд, и на компьютере. На компьютере основной контент появляется за 0,4 с, главная весит 116–117 КиБ. Оговорка: на главной пока нет материалов — с лентой и обложками цифры будут другими.


Сайт работает; пока материалов нет, пустая главная зовёт читателей написать в редакцию.

Поделиться записью



Комментарии
Писать можно, войдя через VK ID, через Яндекс ID или по коду из письма. У комментария — имя и фото из профиля VK или Яндекса либо имя, которое вы укажете при входе по почте. Как мы обращаемся с данными — в политике конфиденциальности.