Блог • MCP

После обновления до Claude Code 2.1.305 stdio-сервер в managed-mcp.json, у которого переменная только в env, снова загружается. Как записать deniedMcpServers?

Заметка Claude Code 2.1.305 — одно предложение, и после обновления сервер может появиться сам: allowedMcpServers больше не фильтрует stdio-серверы в managed-mcp.json, у которых ${VAR} только в env. Если его не было в списке разрешённых, он загружается. Его держит выключенным deniedMcpServers, а не ещё одна копия списка разрешённых.

Кто отвечает за права MCP, написано в статье о безопасности MCP. Какой шаг журнала останавливает учётные данные на неверном сервере авторизации — в предыдущей статье про OAuth. Эта статья только об изменении 2.1.305: список разрешённых больше не останавливает stdio-сервер с переменной только в env, а JSON списка запрета должен точно совпасть с командой.

Решение: stdio-сервер, у которого ${VAR} только в env, после 2.1.305 списком разрешённых больше не останавливается. Запишите в список запрета serverCommand, совпадающий с command и args.

Какой случай списка разрешённых меняет 2.1.305?

Журнал изменений говорит: allowedMcpServers перестаёт фильтровать stdio-серверы в managed-mcp.json, у которых ${VAR} только в env. Тот, кого список разрешённых пропускал, загружается при обновлении. Чтобы он оставался выключенным, используйте deniedMcpServers. Журнал: журнал изменений Claude Code.

Руководство по MCP для организаций всё ещё описывает прежнее правило: серверы в managed-mcp.json пропускают список разрешённых, кроме определения с ${VAR}, которое всё ещё проверяется. До 2.1.259 каждый управляемый сервер должен был пройти список, если он задан. 2.1.305 убирает узкий кусок: stdio, и переменная только в env. Журнал не говорит, что ${VAR} в команде, аргументах, URL или headers тоже освобождён. Руководство: руководство по MCP для организаций.

Как увидеть, что переменная только в env?

Пример stdio в руководстве содержит type, command, args и env. Не кладите секрет в значение env. Учётные данные каждого пользователя используют ${VAR} и читаются из его окружения. Файл ниже оставляет переменную только в env. Команда и аргументы — литералы. Это случай, который называет 2.1.305. Путь команды — пример, а не пакет для установки.

Переменная только в env. После 2.1.305 список разрешённых больше не отфильтровывает этот stdio-сервер.
{
  "mcpServers": {
    "notes": {
      "type": "stdio",
      "command": "/usr/local/bin/notes-server",
      "args": ["--stdio"],
      "env": {
        "NOTES_TOKEN": "${NOTES_TOKEN}"
      }
    }
  }
}

Если ${VAR} стоит в command, args, URL или headers, журнал не включает их в это исключение. Считайте такой сервер по-прежнему подлежащим проверке списка разрешённых из руководства, пока журнал сам не скажет иначе. Пример списка запрета здесь покрывает только случай env.

С какой командой должен совпасть deniedMcpServers?

В руководстве serverCommand совпадает с командой и аргументами, которые запускают stdio-сервер, и совпадение должно быть точным. command выше плюс args — это тот массив. Убрать аргумент, добавить один или поменять порядок — и совпадения нет. serverName — только метка. Руководство пишет, что запись serverName в любом из списков не является средством безопасности, потому что пользователь может дать любое имя любому серверу.

Список запрета, который держит этот сервер выключенным. Массив тот же, что command и args выше.
{
  "deniedMcpServers": [
    {
      "serverCommand": ["/usr/local/bin/notes-server", "--stdio"]
    }
  ]
}

Список запрета действует и на управляемые серверы, и собственный deniedMcpServers пользователя тоже сливается, поэтому пользователь может заблокировать один управляемый сервер для себя. Когда список разрешённых — пустой массив, руководство говорит, что не разрешён ни один сервер, кроме тех, кто пропускает проверку. После 2.1.305 этот stdio-сервер с переменной только в env как раз из пропускающих, поэтому пустой список разрешённых его не удерживает.

Какая схема фиксирует форму этого списка запрета?

Используйте JSON Schema , чтобы зафиксировать типы. deniedMcpServers должен быть массивом. Каждый serverCommand — массивом строк. Пустой массив — верная форма. Она не доказывает, что этот сервер остановится. Его останавливает правило приложения: массив должен совпасть с command плюс args.

Форма списка запрета. Пустой массив всё ещё проходит.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["deniedMcpServers"],
  "properties": {
    "deniedMcpServers": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["serverCommand"],
        "properties": {
          "serverCommand": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      }
    }
  }
}
Что видно в записиСхемаЕсли этот сервер должен оставаться выключенным
serverCommand — это одна строкаНе проходит. Здесь нужен массив строк.Сначала разделите на команду и аргумент.
Аргумент — числоНе проходит. Каждый элемент массива — строка.Смените на ту же строку, что в args.
deniedMcpServers — пустой массив, форма вернаПроходит. Пустой массив — всё ещё массив.Добавьте serverCommand, совпадающий с command и args. Пустой список ничего не блокирует.

Какой из трёх шагов перед загрузкой его действительно останавливает?

Руководство записывает проверки перед загрузкой тремя шагами и повторяет их, когда пользователь заново подключает сервер или включает отключённый в /mcp. Внутрипроцессные серверы типа sdk пропускают все три. После совпадения со списком запрета его ничто не отменяет.

ШагЭтот сервер только с env
Слить спискиЗаписи списков разрешённых и запрета из каждой области настроек сливаются. Если allowManagedMcpServersOnly равен true, остаётся только управляемый список разрешённых. Список запрета всегда сливается из каждой области.
Сначала список запретаТочное совпадение serverCommand его останавливает. После этого шага список разрешённых не может вернуть его.
Затем список разрешённыхПосле 2.1.305 этот stdio-сервер с переменной только в env пропускает этот шаг. Отсутствие в списке разрешённых его больше не останавливает.

Как проверить этот список запрета в JSONNote?

  1. 1
    Выбрать нужную операцию

    Откройте JSON Schema. Переключите действовать на Проверьте с помощью Schema. Список запрета — в первое поле, схему выше — во второе. serverCommand должен быть массивом строк.

  2. 2
    Прогнать четыре плохих документа

    Поставьте serverCommand одной строкой, удалите deniedMcpServers, сделайте аргумент числом или превратите deniedMcpServers в объект. Все четыре не проходят. Список запрета выше проходит. Прохождение значит только верную форму.

  3. 3
    Сравнить пустой список и эту команду

    Откройте JSON Сравнить Diff. В первое поле — список запрета выше. Во втором смените deniedMcpServers на пустой массив. Оба проходят схему. Этот сервер останавливает тот serverCommand, а не пустой массив.

Вопросы

Заменит ли та же команда в списке разрешённых список запрета?

Нет. После 2.1.305 этот сервер пропускает список разрешённых. Есть ли команда в том списке, больше не решает, загрузится ли он. Чтобы остановить, нужна совпавшая запись запрета. После совпадения список разрешённых его не возвращает.

Достаточно ли serverName со значением notes?

Нет. Руководство пишет, что serverName — метка, которую задаёт пользователь, и подстановки не раскрываются. Пользователь может назвать любой сервер тем же именем. Чтобы остановить этот stdio-сервер, используйте serverCommand, совпадающий с command и args.

Если ${VAR} есть и в команде, он всё ещё загрузится?

Журнал не включает этот случай в исключение. Он называет только stdio и переменную только в env. Если ${VAR} есть и в команде, аргументах, URL или headers, по-прежнему считайте это проверкой списка разрешённых из руководства. Если такой сервер не совпадает со списком, не предполагайте, что 2.1.305 его загрузит.

Могут ли собственные настройки пользователя заблокировать сервер, который развернула организация?

Да. Руководство пишет, что deniedMcpServers действует и на серверы в managed-mcp.json, а собственный список запрета пользователя сливается. Пользователь может заблокировать один сервер для себя. Список разрешённых этот шаг не заменяет.

Нужно ли в блоке env хранить значение токена?

Нет. Руководство пишет, что любой на машине может прочитать managed-mcp.json, поэтому не кладите API-ключ или другие учётные данные в env. Учётные данные каждого пользователя используют ${VAR} и читаются из его окружения. В списке запрета тоже нет токена, только команда и аргументы.

Итог

2.1.305 освобождает один случай: stdio-сервер в managed-mcp.json, у которого ${VAR} только в env. Список разрешённых его больше не отфильтровывает. Журнал не освобождает вместе с ним переменную в команде, аргументах, URL или headers.

Решение: чтобы он оставался выключенным, запишите в deniedMcpServers serverCommand, совпадающий с command и args; пустой список разрешённых его не удерживает; serverName недостаточно; не храните значение токена в env.

Дальше: зафиксируйте массив списка запрета схемой, затем через Diff посмотрите пустой массив рядом с этим serverCommand.

Назад в блог