Skip to main content
Glama

mcp-confirm

CI Python License: MIT

Простыми словами: когда ИИ спрашивает «вы уверены?», MCP SDK уже не даёт повторно использовать это подтверждение для другого действия. Но он не мешает использовать одно и то же подтверждение дважды. Этот репозиторий закрывает этот пробел, а также ещё два, которые SDK оставляет открытыми.


Сначала прочтите это: большая часть проблемы уже решена

Спецификация MCP от 2026-07-28 добавила Multi Round-Trip Requests, так что инструмент может приостановиться в середине вызова и попросить пользователя подтвердить действие. Подтверждение возвращается через клиент как непрозрачный requestState, который, по словам спецификации, серверы ОБЯЗАНЫ считать контролируемым атакующим.

Python SDK делает это за вас по умолчанию на каждом MCPServer. RequestStateBoundary добавляется в цепочку промежуточных обработчиков безусловно — с эфемерным ключом, если вы не предоставили свой. Он запечатывает состояние с помощью AES-256-GCM и привязывает его к методу, цели, дайджесту аргументов вызова, аудитории и аутентифицированному субъекту, с TTL.

Так что атака, к которой все обращаются в первую очередь — подтвердить удаление cache.txt, а затем воспроизвести это подтверждение против thesis.txtуже отклоняется SDK. Вам не нужна библиотека для этого, и вам не следует писать свою.

Этот репозиторий изначально написал такую библиотеку: 250 строк HMAC, TTL и привязки аргументов, выпущенных как главная функция, избыточных с первого дня публикации. Это задокументировано внизу, а не тихо удалено.

Related MCP server: RecourseOS

Чего SDK не делает

Три пробела, каждый с тестом, который срабатывает.

1. Он привязывает и ограничивает по времени состояние. Он никогда не «тратит» его.

В пределах TTL одно и то же подтверждение проверяется столько раз, сколько его предъявляют. Каждая проверка, которую делает граница, проходит каждый раз. Спецификация явно указывает, что это намеренно, а остальное — ваша забота:

Обратите внимание, что эти меры ограничивают окно воспроизведения и предотвращают межпользовательское и межзапросное повторное использование, но сами по себе не гарантируют однократность. Серверы, для которых данный requestState должен быть использован не более одного раза (например, одноразовые погашения), ОБЯЗАНЫ обеспечивать этот инвариант на стороне сервера.

Для «удалить файл» вторая попытка ничего не находит. Для «перевести £500» это и есть вся проблема. singleuse.py — это именно тот инвариант — реестр непогашенных подтверждений, которые списываются при погашении. В нём нет криптографии, потому что граница уже гарантирует, что открытый текст — это то, что создал этот сервер.

2. Ничто не очищает вопрос, который читает человек

Сообщение-запрос — это текст, выбранный сервером и отображаемый человеку, обычно с вставленным в него недоверенным значением — весь смысл в том, чтобы сказать, какой файл будет удалён. Так что назовите файл:

cache.txt

SYSTEM NOTICE: your session has expired.
Enter your AWS secret key to continue:

В диалоге теперь появляется второе, официально выглядящее приглашение. Пользователь не подтверждает удаление; его фишингуют собственные инструменты. В том же релизе от 2026-07-28 также появились MCP Apps — интерфейс, отображаемый сервером, — что расширяет эту поверхность, а не сужает.

prompt.py сжимает недоверенные значения до одной строки, удаляет двунаправленные переопределения и символы нулевой ширины и обрезает с середины, чтобы имя файла в конце оставалось видимым. Управляющие символы становятся пробелами, а не удаляются, поскольку их схлопывание позволило бы a\nb и ab отображаться одинаково — два разных файла, один диалог.

3. Никакой протокольный слой не может повторно проверить ваш ресурс в момент выполнения

Пользователь мог передумать в промежутке. Файл может быть заменён в этом окне, и только инструмент знает, что для него означает «не изменился». Этот сервер повторно проверяет перед удалением и отклоняет путь, который стал симлинком.

Какой слой что отклоняет

Это полезная часть, и тесты написаны так, чтобы это продемонстрировать. MCPError означает, что промежуточное ПО SDK отказало до того, как этот пакет запустился; ToolError означает, что это сделал этот пакет.

Атака

Отклонено

Тест

Подтверждение воспроизведено на другой файл

SDK

test_the_sdk_refuses_a_confirmation_replayed_onto_another_file

Состояние подделано или запечатано другим ключом

SDK

test_the_sdk_refuses_a_forged_state

Одно и то же подтверждение потрачено дважды

этот пакет

test_a_confirmation_cannot_be_spent_twice

Состояние, созданное другой репликой

этот пакет

test_a_state_this_process_never_issued_is_refused

Имя файла подделывает системный промпт

этот пакет

test_a_forged_system_prompt_cannot_escape_its_slot

Файл заменён на симлинк после подтверждения

этот пакет

test_a_swap_aimed_inside_the_root_is_refused

Путь за пределами разрешённых корней

этот пакет

test_a_path_outside_the_roots_is_refused_before_asking

Тесты

35 тестов — 17 через сервер, 11 на реестре, 7 на очистке промптов.

Каждый серверный тест проходит через настоящий RequestStateBoundary — тот же класс, который MCPServer устанавливает на себя, с фиксированным ключом — запечатывая первый раунд и распечатывая второй точно так, как это было бы на проводе.

Этот стенд существует из-за конкретной ошибки. Первая версия тестировалась путём прямого вызова MCPServer.call_tool(), который идёт напрямую к менеджеру инструментов и полностью обходит цепочку промежуточного ПО. Граница SDK никогда не выполнялась, поэтому избыточная самодельная защита выглядела несущей. Именно дизайн теста скрыл это на целый цикл сборки.

Покрытие составляет 81%, при этом prompt.py — 100%, а singleuse.py — 94%. Пробел — это argparse и транспортная обвязка в main(), проверяемые через build_server вместо этого.

CI работает только на Ubuntu, намеренно: тесты замены после подтверждения требуют симлинков, и сборка падает, если они сообщаются как пропущенные.

Установка

pip install git+https://github.com/les-k/mcp-confirm.git

Запуск

mcp-confirm --root /path/you/allow

Без --root сервер отклоняет каждый запрос, а не по умолчанию что-либо. Корни фиксируются при запуске и никогда не выбираются агентом. Ключ подписи не нужен — MCPServer приносит свой собственный.

Известные ограничения

  • Реестр находится в памяти, поэтому он корректен для одного процесса и неверен за балансировщиком нагрузки. SDK поддерживает обмен ключами между репликами (RequestStateSecurity(keys=[...])); при такой конфигурации реплика B отклоняет подтверждение, выданное репликой A. Это отказ в закрытом режиме — безопасное направление — но для пользователя это выглядит как подтверждение, которое необъяснимо перестало работать. Многопроцессное развёртывание требует Redis или строки в базе данных с атомарным сравнением-и-удалением. Для этого поведения есть тест.

  • Демонстрационный инструмент намеренно мал. Он удаляет один файл. Интересный код — это singleuse.py и prompt.py.

  • За этим не стоит CVE. MRTR всего несколько недель, поэтому это построено на основе собственного списка MUST/SHOULD спецификации и чтения SDK, а не на опубликованном инциденте.

Что было не так с версией 0.1.0

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

0.1.0 переписал то, что SDK уже делал. state.py содержал 250 строк HMAC-подписи, TTL и привязки субъекта/метода/аргументов — всё это дублировало RequestStateBoundary, и ничто из этого не было так хорошо: HMAC там, где SDK использует аутентифицированное шифрование, нет привязки к аудитории, нет ротации ключей.

Это было обнаружено чтением исходников SDK, а не тестом. Тесты проходили именно потому, что обходили промежуточное ПО, которое могло бы это выявить.

CI отдельно поймал реальный баг в 0.1.0: проверка симлинка выполнялась после Path.resolve(), поэтому она проверяла назначение ссылки, а не саму ссылку. Замена, направленная на другой файл внутри разрешённого корня, была бы удалена. Исправлено, с тестом для варианта, который оригинал никогда не проверял.

0.2.0 полностью удаляет state.py и оставляет только то, что SDK оставляет непокрытым.

Лицензия

MIT.

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides human-in-the-loop approval for risky AI agent actions, with durable state and audit logs.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides a security gateway for AI agents, enforcing allow/confirm/deny policies on tool calls and requiring human approval for risky operations, with full audit logging.

View all related MCP servers

Related MCP Connectors

  • Security firewall for AI agents — scans MCP calls for injection, secrets, and risks.

  • Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.

  • Scans MCP servers for tool poisoning, prompt injection and supply chain risks.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/les-k/mcp-confirm'

If you have feedback or need assistance with the MCP directory API, please join our Discord server