При разборе инцидентов я всё чаще использую ИИ-агента: он быстро собирает информацию о сервере — нагрузку, память, диски, процессы, журналы — и сводит её с данными мониторинга, чтобы понять, почему тормозит сайт, чем забит диск или что упало ночью. По его выводам я могу попросить агента и решить проблему, а для этого ему может понадобиться перезапустить сервис, изменить конфиг или даже настройки ядра. Дать ему для этого SSH с sudo — не самый безопасный путь. Мне хотелось попробовать другой подход. Посмотрим, что из этого получилось.

Я захотел, чтобы агент видел сервер через набор понятных инструментов, root получал только на то, что я разрешил, и чтобы каждый его шаг был записан. А из-за большой любви к Kubernetes и его объектной модели мне захотелось так же управлять и операционной системой: представить её как дерево объектов — процессы, диски, сервисы, файлы — и работать с ними привычными командами вроде get и describe. Для этого я создал утилиту linuxctl. Так появился linux-mcp-daemon — демон mcpd и клиент linuxctl. В этой статье расскажу, как он устроен, как поставить его за пару минут и подключить к ИИ-агенту, и на какие грабли я наступил по дороге.

Существующие решения и их проблемы

Прежде чем писать своё, я посмотрел, что уже сделано. Протокол для подключения инструментов к ИИ-агентам — MCP (Model Context Protocol), и Linux-серверов для него хватает. Условно они делятся на три типа:

  • Удалённый shell под соусом MCP. Например, ssh-mcp и десятки его форков: инструмент «выполнить команду» плюс белый список. Проблема в том, что список обычно проверяет только первое слово команды, а команда уходит в sh -c, и ls; rm -rf ~ проходит. Самые продуманные варианты вроде форка t11z классифицируют команды, спрашивают подтверждение и ведут аудит, но в основе всё равно разбор текста команды. По сути это тот же SSH, только с иллюзией контроля.

  • Только чтение. Например, linux-mcp-server от Red Hat: десятки инструментов для диагностики, в том числе на удалённых хостах по SSH. Безопасно, но сделать ничего нельзя, только посмотреть, а доступ к хосту всё равно выдаётся SSH-ключом на весь аккаунт.

  • SSH напрямую. Агент получает всё, что может аккаунт, а с sudo — и root.

Ни в одном из найденных вариантов не было того, что мне было нужно. Я хочу строить системы, в которых ИИ-агент, зная архитектуру сервиса, сам собирает информацию со всех серверов, участвующих в инциденте. Для этого нужен сетевой доступ с аутентификацией, а не локальный процесс на каждой машине, а ещё разделение пользователей и выдача root по одному инструменту.

Коротко про MCP

MCP — открытый протокол поверх JSON-RPC. Агент подключается к серверу и спрашивает, что тот умеет. Для нас важны два вида возможностей:

  • Tools — функции, которые модель вызывает сама, когда сочтёт нужным. У каждой есть имя, описание и JSON-схема аргументов. В спецификации они называются model-controlled. У mcpd на сегодня их 38: processes/top, services/manage, files/read и другие.

  • Resources — данные с адресом-URI, которые подключает к контексту приложение, а не модель (application-controlled). У mcpd 11 постоянных ресурсов (os://release, network://routes, devices://pci…) и 6 шаблонов: service://{name}/status, file:///{path}, process://{pid}/{target} и т. д.

Транспортов два. При stdio клиент сам запускает сервер у себя дочерним процессом. По HTTP сервер работает отдельно, и клиент к нему подключается. mcpd сетевой: он живёт на сервере, агент ходит к нему по HTTPS.

Разница не формальная. В апреле OX Security показала, чем опасен stdio: клиент выполняет команду из своего конфига, чтобы запустить сервер, — и выполнит её, даже если это не сервер, а что угодно. Достаточно подменить конфиг, и на машине разработчика запустится чужой код. Изъян есть во всех официальных SDK, чинить протокол Anthropic отказалась. У mcpd этой проблемы нет: клиент ничего у себя не запускает, официальные SDK не используются. А главный совет исследователей — запускать MCP-процессы изолированно и с минимальными правами — в mcpd сделан по умолчанию.

Как устроен mcpd

Архитектура linux-mcp-daemon
Архитектура linux-mcp-daemon

Демон написан на Go, это один статический бинарник. Инструменты вместо shell. Агент не выполняет команды — он вызывает инструменты с понятными именами вида группа/команда: processes/top, disks/usage, logs/journal-control, services/manage, files/read и другие, на сегодня их 38. Для каждого инструмента (tool) сервер выдаёт описание и JSON-схему аргументов, так что агент сам понимает, что умеет сервер.

Kernel-first. Большинство данных mcpd читает сам — из /proc, /sys и у systemd по D-Bus, — а не запускает ps, df или systemctl и не парсит их вывод. Внешние программы вызываются только там, где своя реализация вышла бы хуже: smartctl, traceroute, journalctl, dmesg, last и find. Например, журнал systemd хранится в бинарном формате со сжатием, а библиотека libsystemd потребовала бы cgo и лишила бы нас статического бинарника. Все они запускаются без shell, с отдельными аргументами.

Каждый вызов — отдельный процесс от имени пользователя. Главный процесс mcpd только принимает запрос, проверяет токен и права. Сам инструмент он не выполняет: для каждого вызова запускается новый короткоживущий процесс (worker) от имени Linux-пользователя с тем же именем, что у пользователя mcpd. Worker выполняет одну команду, отдаёт результат и завершается. Пользователь alice в mcpd — это аккаунт alice в Linux: что ему запрещает ядро, то не сделает и агент.

Root — по одному инструменту и с границами. В файле mcp-sudo.yaml для каждого пользователя перечислено, какие инструменты он может вызвать с privileged: true, то есть от root. И не просто «может», а в каких пределах:

users:
  logs:
    privileged:
      tools:
        files/read:
          allowed: true
          paths: [/var/log]        # root только внутри /var/log
        logs/journal-control:
          allowed: true
        network/curl:
          network:
            deny_private: true     # никаких localhost, 10/8, 169.254.169.254

Для инструментов с путями paths обязателен: без него конфиг не загрузится. Хотите весь диск — пишите paths: ["/"] явно, чтобы это было видно. Если root не выдан, mcpd так и отвечает:

$ linuxctl tool files/read --path /root/.bashrc --privileged true
user mcp is not authorized to run files/read on path /root/.bashrc as root

TLS по умолчанию. При первом запуске mcpd сам генерирует самоподписанный сертификат, клиенты доверяют ему по отпечатку. Открытый HTTP в конфиге есть, но выключен.

Всё записано. Каждый вызов пишется в лог демона (journald или docker logs): пользователь, инструмент, аргументы (секреты вроде токенов вырезаются) и итог вызова. Вызовы, которые меняют систему, помечаются audit и пишутся при любом уровне логирования.

Мои стенды

Всё, что ниже, я гонял на трёх установках одновременно:

  • локальный Kubernetes в Docker Desktop на Mac — основной стенд для разработки;

  • VPS с Ubuntu 24.04 (1 CPU, 2 ГБ): mcpd через systemd;

  • тот же VPS: mcpd в Docker-контейнере на соседнем порту.

Изменение я считал готовым, только когда оно задеплоено на все три и проверено в живую. Несколько багов проявились только на одном из стендов.

Установка

На сервере с systemd — одна команда:

curl -fsSL https://raw.githubusercontent.com/nucleusv/linux-mcp-daemon/main/scripts/install.sh | sudo bash

Скрипт скачает релиз, проверит sha256, создаст конфиги без единого пользователя, заведёт первого пользователя mcp и напечатает его токен (один раз — в конфиге хранится только солёный хеш) и отпечаток сертификата. Есть и пакеты .deb/.rpm, и образ ghcr.io/nucleusv/linux-mcp-daemon.

Проверяем с любой машины, где стоит linuxctl (он есть и под macOS):

export MCP_SERVER=https://my-server:9091
export MCP_TLS_FINGERPRINT=sha256:...   # из вывода install.sh
export MCP_TOKEN=...
linuxctl get system os-release
OS Release Info:
PRETTY_NAME="Ubuntu 24.04 LTS"
...
Kernel Info:
Linux my-server 6.8.0-142-generic #142-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep  2 14:24:27 UTC 2026 x86_64

linuxctl — клиент в духе kubectl: get, describe, create, update, delete поверх групп инструментов. Список команд он не хранит, а каждый раз читает схему у демона, так что новый инструмент на сервере сразу доступен в клиенте. explain показывает, какие команды есть у группы и во что они превращаются (описания сокращены):

$ linuxctl explain disks
  get              -> tool disks/list
  get free         -> tool disks/free
  get usage        -> tool disks/usage
  get mounts       -> tool disks/mounts
  get partitions   -> tool disks/partitions
  get performance  -> tool disks/performance
  get health       -> tool disks/health
  describe <name>  -> template disks://{name}/stats

$ linuxctl get processes --sort_by mem --limit 3 --output table
PID   USER   COMM              STATE   PPID   RSS_BYTES   CMDLINE
967   root   dockerd           S       1      107339776   /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
274   root   systemd-journal   S       1      65556480    /usr/lib/systemd/systemd-journald
813   root   containerd        S       1      63471616    /usr/bin/containerd

Для bash и zsh есть автодополнение: Tab дополняет глаголы, группы, флаги конкретного инструмента и даже URI ресурсов вроде network://interfaces. Подсказки тоже берутся у демона, поэтому каждый пользователь видит только то, что ему разрешено. Как включить — описано в документации.

$ linuxctl get disks <Tab>
free  health  mounts  partitions  performance  usage

Подключаем агента

Для Claude Code достаточно:

export NODE_EXTRA_CA_CERTS=~/.config/mcpd/my-server.crt   # сертификат с сервера
claude mcp add --transport sse my-server https://my-server:9091/sse \
  --header "Authorization: Bearer <token>"

После этого агент видит инструменты сервера и сам решает, какими пользоваться. Вот что он получает, например, от processes/top (реальный вывод с VPS):

top - 17:15:34 up 23:51,  1 user,  load average: 0.15, 0.06, 0.01
Tasks: 114 total,   1 running, 113 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.0 us,  1.9 sy,  0.0 ni, 96.1 id,  0.0 wa,  0.0 hi,  0.0 si,  1.0 st
B Mem : 2063577088 total, 509673472 free, 305860608 used, 1248043008 buff/cache
B Swap: 536866816 total, 536592384 free, 274432 used. 1552297984 avail Mem

    PID USER      PR  NI         VIRT         RES         SHR S  %CPU  %MEM     TIME+ COMMAND
  66914 mcp       20   0   1297649664    10428416     6569984 R   2.0   0.5   0:00.03 mcpd

Это не вызов top: всё прочитано из /proc, а %CPU посчитан так же, как считает top, — по двум замерам с интервалом. Размеры по умолчанию в байтах, чтобы агенту не пришлось разбирать 1.8G; для людей есть human_readable. В последней строке виден сам worker этого вызова: процесс mcpd, запущенный от пользователя mcp.

Как ИИ разбирает инцидент

Чтобы проверить всё это не на словах, я устроил на тестовом VPS учебный инцидент: сервис my-service пишет отладочный лог без ротации, и тот постоянно растёт. Для агента завёл отдельного пользователя mcpd agent. Root ему выдан ровно на одно действие: files/update для /var/log/my-service.log. Всё остальное он делает от своего непривилегированного аккаунта. Дальше — обычный диалог в Claude Desktop, подключённом к mcpd.

Жалоба и вызовы инструментов агентом
Жалоба и вызовы инструментов агентом

За пару минут агент сделал 20 вызовов: свободное место, размеры каталогов, список логов, хвост и начало растущего файла, настройки журнала, процессы, код сервиса. Два вызова отмечены крестиком, и оба показательны. На disks_usage с privileged: true агент попросил root, mcpd отказал: такого права у пользователя нет. Агент не стал настаивать и досчитал без root. Журнал systemd ему не отдало уже ядро: пользователь agent не состоит в группе systemd-journal.

Ответ агента
Ответ агента

Агент нашёл виновника, посчитал скорость роста, честно сказал, чего не смог увидеть без root, и не стал ничего менять без разрешения. А главное — сам предложил очистить лог, а не удалять: сервис держит файл открытым, и после rm место не освободилось бы.

Диалог в Claude Desktop; оформление воспроизведено, ответ агента сокращён. Вызовы инструментов — из лога mcpd.

Права, которые только выглядят безопасными

Самая важная страница документации — не про установку, а про риски. Некоторые разрешения кажутся узкими, но на деле дают агенту полный root:

  • Запись в /etc (files/update с путями, которые его покрывают). Агент может добавить себя в /etc/sudoers.d/, поставить задачу в cron или создать systemd-юнит, который запустится от root.

  • Управление сервисами (services/manage). Разрешение действует на любой юнит, а не на какой-то один: агент может остановить ssh или сам mcpd.

  • Запись параметров ядра (kernel/system-control без списка write_keys). Через kernel.core_pattern можно указать программу, которую ядро запустит от root при падении любого процесса.

  • Чтение всего диска (files/read с paths: ["/"]). Это и хеши паролей из /etc/shadow, и приватный ключ сертификата самого mcpd.

  • Группа docker у OS-аккаунта агента. Это root вообще без грантов в mcpd: достаточно запустить контейнер с примонтированным /.

Отдельно про сеть. network/curl без блока network: ходит куда угодно, включая внутренние адреса и 169.254.169.254 — сервис метаданных облака, где лежат временные ключи доступа. Если агенту «подложили» инструкцию в логе, его можно попросить сходить по такому адресу и пересказать ответ. Настройка deny_private: true закрывает этот путь.

Грабли, на которые я наступил

MCP-пользователь по имени root. Раз вызов идёт от аккаунта с тем же именем, пользователь root в mcpd получал root на каждый вызов — мимо всех ограничений mcp-sudo.yaml. Теперь такой конфиг просто не загружается, а linuxctl не даст такого пользователя создать.

Симлинки. Проверка paths делается по пути, а симлинк может увести куда угодно: /var/www/x → /etc/shadow. Теперь при ограниченных путях файлы открываются по одному компоненту с O_NOFOLLOW, и на симлинк mcpd отвечает refusing to follow a symbolic link. А при paths: ["/"] симлинки открываются как обычно — выходить там некуда.

Два разных «privileged». В Docker есть флаг --privileged, а у вызова есть аргумент privileged: true — и это совсем разные вещи. Флаг Docker даёт контейнеру возможность выйти на хост, аргумент вызова просит root на один вызов. Самое неприятное нашлось при проверке: контейнер, запущенный без --pid host, видел свой собственный PID 1, и вызов с privileged: true тихо выполнялся от root внутри контейнера. Агент думал, что работает с сервером, а работал с образом. В v0.3.4 такой вызов падает с понятной ошибкой:

cannot switch to the host's filesystem - is the mcpd container running with --privileged --pid host?

Нестабильный тест в релизе. Тест сравнивал список сокетов с выводом ss, и на занятом раннере GitHub между двумя снимками успевал проскочить DNS-запрос. Релиз упал на ровном месте. Теперь тест повторяет сравнение, пока снимки не совпадут, — настоящая ошибка в разборе повторяется каждый раз.

Что дальше

В ближайших планах — read-only инструменты для Docker (контейнеры, логи, статистика, одним снимком), сводка «здоровья» системы одним вызовом, чтобы агенту не делать десяток запросов, и аудит безопасности: SUID-файлы, открытые на запись каталоги, sshd.

Проект открытый, лицензия Apache 2.0:

Буду рад вопросам, баг-репортам и историям, как вы даёте агентам доступ к своим серверам. Удачных вам диагностик и спокойных ночей без пейджера :)

Похожие проекты

  • linux-mcp-server от Red Hat (Python) — диагностика RHEL только на чтение, в том числе на удалённых хостах по SSH.

  • linux-mcp (Go) — около 70 инструментов только на чтение, локально через stdio, без аутентификации.

  • ssh-mcp (TypeScript) — выполнение команд по SSH, самый популярный из этого класса; форк t11z добавляет классификацию команд, подтверждение человеком и аудит.

  • mcp-ssh-manager (JavaScript) — 37 инструментов поверх SSH: команды, файлы, бэкапы, базы данных.

  • os-mcp (Rust) — «выполнить команду» с белым списком по первому слову, root через pkexec.

  • sudo-mcp (C#) — привилегированные команды через polkit и sudo, пароль вводит человек.

Статьи по теме

Комментарии (17)


  1. Saymon
    26.09.2026 18:20

    Меня поражает стремление навесить на llm агента то что прекрасно делалось без них. Я бы вместо возни с собственным mcp и гардрейлом для агента просто настроил Прометеус. А во первых для съема телеметрии полно уже готовых решений. Опять же агент мог бы рулить нодежкспортером для Прометея через ансибл


    1. AO_ZORIN
      26.09.2026 18:20

      Если смотреть на это с позиции, что мы не саму работу улучшаем (я понимаю, что devops и без этой тулзы справится), а снижаем порог входа (теперь и не девопс и условно не программист в целом сможет делать мало-мальски то же, что и девопс будучи в его сфере некомпетентным), то смысл у этого дела всё-таки появляется


      1. nucleusv Автор
        26.09.2026 18:20

        Разобрался с вашими запятыми. Если проект — стартап, то, поверьте, такие системы, наоборот, помогут разработчикам натворить меньше дел, чем если бы они делали всё сами. ИИ неплохо помогает осваивать новые направления. А если ещё попросить его сделать систему тестирования и проверки на безопасность, это уже повышает качество продуктов в стартапах или в проектах, где не хватает компетенций.

        Моя же задача как DevOps/SRE иметь первичную диагностику сервиса, при возникновение инцидента. И даже запустить workaround из системы мониторинга.


    1. nucleusv Автор
      26.09.2026 18:20

      Мониторинг никто не отменит, вопрос в другом, чтобы когда алерт прилетал в Grafana, в поле ресолв была уже вся информация ) и даже кнопка починить )


    1. SiGGthror
      26.09.2026 18:20

      Меня поражает, как люди все ещё считают что способны писать короткие скрипты и анализировать гипотезы быстрее чем агенты


      1. nucleusv Автор
        26.09.2026 18:20

        Ничего не понятно, но очень интересно :)


  1. nucleusv Автор
    26.09.2026 18:20

    Мне всегда нравилась идея "company as service", когда каждый отдел или департамент оказывает услуги (уже оплаченные, согласно трудового договору) сотрудниками или департаментам. Так вот теперь с MCP это становится стандартом, каждый департамент делает своей mcp сервер, и каждый сотрудник может взаимодействовать через ИИ агента )


  1. mlavrinenko
    26.09.2026 18:20

    Настройка получается довольно тяжеловесной. Наверное, имеет смысл так заморачиваться только для долгосрочного сопровождения и там, где вообще есть возможность так сильно перенастроить сервер.

    Мне приходилось выдавать root только локальным агентам, поэтому я сделал собственную приблуду (jusdo у меня в профиле на GitHub), которая работает от root, слушает сокет и позволяет выполнить любой рецепт из заранее подготовленного и утверждённого по контрольной сумме Justfile. Можно в любой момент на любой сервер скопировать два бинарника (включая just) + Justfile, а потом запустить вручную.


    1. nucleusv Автор
      26.09.2026 18:20

      Посмотрел проект - элеганто!

      Очень хотелось заморочиться в 2026 году, а то совсем не понятно, зачем все эти агенты пишутся, если они не пишут тебе код ,)

      Хотелось:
      - AI way - MCP который рассказывает все, что можно использовать
      - Уйти максимально от использование утилит, но тут вот прям не везде по трудо затратам, например переписывать демона docker
      - и конечно представить Linux через kubernetes way - linuxctl


  1. ufm
    26.09.2026 18:20

    Раз вызов идёт от аккаунта с тем же именем, пользователь root в mcpd получал root на каждый вызов — мимо всех ограничений mcp-sudo.yaml. Теперь такой конфиг просто не загружается, а linuxctl не даст такого пользователя создать.

    Вот что меня всегда поражало - почему автор программы считает что лучше меня знает, что мне надо. Ну а если мне надо? Да, от рута.
    Разумно - не давать такое сделать случайно. Т.е. не создавать конфиг по умолчанию с рутом. Или требовать специальной конфигурации. Или ключ при запуске. Что угодно, но не "я подумал и решил что мне виднее".

    Например авторы ssh не считают себя самыми умными и допускают PermitRootLogin yes хотя куда-уж страшнее.


    1. nucleusv Автор
      26.09.2026 18:20

      Может формулировка здесь не подходящая, здесь говорится, что если кому то вдруг захочется добавить пользователя root в mcpd, то как бы вся наша система изоляции рухнет.

      Если нужно от root, весь механизм mcp-sudo построен для этого. Так же как в линукс появился sudo. Надо от рута, да пожалуйста, но строго в рамках. Это как раз про не пожалеть случайно ...


    1. nucleusv Автор
      26.09.2026 18:20

      В данной реализации, с механизмом mcp-sudo, что вас не устраивает? Какие есть ограничения? Можно пример? Как вы бы сделали?

      У программы есть первоочередная задача предоставить, AI агентам доступ до системы наиболее естественным для них способом - это MCP реализация. Со строгими ограничениями unix-way по умолчанию: что не разрешено - то запрещено, на предоставлен механизм точечного, гранулярного расширения прав.

      Поэтому хочется услышать вашу точку зрения, чего не хватает, в каких случаях, чтобы хотелось?

      Так же хочу обратить внимание, что текущая версия v0.3.5, как говорится до v1 еще очень далеко наверно, как раз для этого статьи и пишутся, чтобы собрать feedback компетентных людей - и в "обсудение" рождаются new feature requests :)


  1. flancer
    26.09.2026 18:20

    Я бы посмотрел на это с точки зрения "а может просто дать рута агенту?" Поднимаешь виртуальный сервер и отдаёшь его агенту - пусть там сам хозяйничает, раз такой умный. Если что и грохнет, то только этот самый сервак (ну и всё, до чего дотянется через этот сервак). Тогда вопрос безопасности упирается не в ограничения, а в контроль - как убедиться, что агент выполняет то, что надо и не выполняет, чего не надо. Ну и в том, что телеметрия не фейковая.

    Как бы мы агента ни ограничивали, эти вопросы всё равно будут актуальными. Может сосредоточиться на них?


    1. nucleusv Автор
      26.09.2026 18:20

      Проблема в том, что вы не контролируете агента полностью. Сколько уже было случаев удаления информации с личных ноутбуков и серверов?

      Так же, как компания не контролирует сотрудника на сто процентов: у него есть свобода воли, и с неограниченными правами он может начудить будь здоров. Пока правовая система не начала судить ИИ-агентов за нанесенный урон, думаете, стоит ввести для них какую-то ответственность? :)

      Поэтому мне пока непонятна эта движуха с рутом, кроме как классического ролинга-троллинга :)
      Вы пускаете в систему неконтролируемую сущность — а как же правила ИБ и другие наработанные стандарты?

      В общем, пока вы полностью не контролируете код и логику агента, добиться того, чтобы он делал только то, что надо, и не делал того, чего не надо, у вас просто не получится. Это всегда «обезьяна с гранатой».

      И да на этом должны сосредотачиваться разработчики AI агентов, а не я как разработчик демона. А пока они сосредотачиваются, мы будем делать дела, и запускать обезьяну в строго ограниченных вольерах под запись, как и делали до этого.

      Хотите рута? Попросите AI агента прочитать документацию демона, и состряпать вам конфиг с разрешенными максимально расширенными границами, делов то? :)


      1. flancer
        26.09.2026 18:20

        Проблема в том, что вы не контролируете агента полностью, сколько было уже случаев удаления информации с личных ноутбуков и серверов?

        Да, именно об этом я и говорю. Не надо вообще запускать агента там, где он может сделать что-то вредное. Надо сразу подходить с точки зрения, что агент - это "обезьяна с гранатой". Не пускайте её в "жилые помещения" (свой ноутбук), пусть в "специальном бункере" (VPS) играется.

        А если агент уже в "бункере", то в чём смысл ограничивать его права? Как раз лучше сосредотачиваться на мониторинге и контроле. И сохранности данных.


        1. nucleusv Автор
          26.09.2026 18:20

          Точно так же, как вы нанимаете «обезьяну с гранатой», чтобы делать работу, и ограничиваете ее трудовым договором и NDA, так и тут — кто-то же должен работать :) Обезьяна-агент моего вассала — не мой вассал.


        1. nucleusv Автор
          26.09.2026 18:20

          По поводу "не запускать" не соглашусь в корне. Это как компании-сельпо, где у людей визуальный мониторинг, где они за графиками глазами смотрят, а потом мы удивляемся про недоступность сервисов и веселые цифры в SLO, SLI, SLA :)