Представьте пост на доске обратной связи, который за ночь переехал в колонку Declined. В истории написано, что это сделал проект.
Никто в команде этого не помнит. Это сделал чей-то ассистент на MCP-ключе, который все скопировали с одной и той же страницы настроек, и чтобы выяснить чей, придётся опросить всех по очереди.
Так сейчас устроено большинство MCP-серверов: они позаимствовали модель API-ключей. В Kookee я только что заменил её личными токенами. Ниже — почему общий ключ не годится, что я сделал вместо него и какие детали оказались важными.
Общий ключ — привычка из мира API-ключей
API-ключ идентифицирует приложение. Одно приложение — один ключ, и ключ умеет только то, что нужно приложению: в публичном API Kookee — читать опубликованный контент. Для приложений такая модель годится.
MCP-сервер подключает другое — ассистента конкретного человека, который по его указаниям что-то меняет. Дайте ему ключ проекта, и сломаются сразу три вещи.
Никто этого не делал. Каждое изменение записывается на проект. Когда посту меняют категорию, назначают ответственного или отвечают на него, не понять, чей ассистент это сделал и не сделал ли это человек.
Все могут всё. Один ключ — один уровень доступа. Коллега, который хотел только, чтобы ассистент пересказывал ему обратную связь, держит ключ, которым можно публиковать.
Ключ не отозвать у одного человека. Когда кто-то уходит из команды, отключить его ассистента можно, только заменив ключ, а это ломает настройку у всех остальных. Поэтому на практике ключ никто не меняет.
А ключ, который копируют все, оказывается везде: в дотфайлах, в закоммиченном .mcp.json, на скриншоте в переписке с поддержкой.
Что я сделал: один токен на человека и проект
Теперь каждый участник создаёт собственный токен на странице MCP-сервера проекта. Токен действует от имени этого участника — с его ролью и правами, которые проверяются при каждом запросе, — и каждое изменение записывается под его именем.

По умолчанию выбрано «Только чтение». Доступ на запись — отдельный осознанный клик.
Шесть деталей, от которых зависит, сработает ли это
1. «Только чтение» значит, что инструментов записи нет
Очевидная реализация отказывает в записи в момент вызова. Но инструмент всё равно на виду: модель видит update_feedback в списке инструментов, строит вокруг него целый разбор обратной связи и на последнем шаге получает ошибку, которую потом пытается обойти.
Поэтому в сессии только для чтения инструменты записи вообще не регистрируются, а в инструкциях сервера нет ни одного сценария записи — вместо них сказано, как получить доступ на запись. В общих чертах:
registerReadTools(server, auth);
if (auth.token.canWrite) {
registerWriteTools(server, auth);
}
Тогда ассистент прямо говорит пользователю, что его токен только для чтения, а не импровизирует.
2. Проверять человека за токеном при каждом запросе
Токен живёт дольше, чем причина, по которой его выдали. Поэтому проверка не заканчивается на «хеш совпал»: сервер загружает участника, которому принадлежит токен, и проверяет, что его аккаунт активен, проект не заблокирован, а сам он всё ещё в проекте.
if (!plain.startsWith('kkm_')) return fail('not_mcp_token');
const token = await findTokenByHash(sha256(plain));
if (!token) return fail('not_found');
if (!token.user.isActive) return fail('inactive');
if (token.project.bannedAt) return fail('banned');
if (!(await isMember(token.projectId, token.userId))) return fail('not_member');
При удалении или блокировке участника его токены тоже удаляются — в той же транзакции, что и изменение его участия в проекте. Доступ ему закрывает уже проверка на каждом запросе, а удаление строк нужно, чтобы список токенов не врал.
3. Хранить хеш, показывать один раз, давать префикс
Хранится только SHA-256-хеш токена. Сам токен показывается один раз, в диалоге создания, а дальше в списке видны только последние четыре символа.
У префикса две задачи. kkm_ отличается от kk_ у API-ключа, так что сканер секретов или человек сразу видит, что именно утекло. И по префиксу сервер может вернуть полезную ошибку: на API-ключ, отправленный на MCP-эндпоинт, он отвечает MCP_PERSONAL_TOKEN_REQUIRED, и человеку понятно, что делать. На любую другую ошибку приходит один общий MCP_TOKEN_INVALID, чтобы перебором нельзя было выяснить, почему токен отклонён; точная причина попадает только в лог сервера.
4. Показывать авторство там, куда смотрят люди
Журнал аудита — место, куда ответственность отправляют, чтобы о ней забыть. Я пишу туда каждое изменение через MCP, но важная часть на виду: каждая операция записи сохраняет id токена вместе с событием в истории или комментарием, который она создаёт, и панель управления помечает их через MCP.

Инструмент истории, который читает ассистент, отдаёт тот же признак, так что на вопрос «кто перенёс это в Planned и был ли это ассистент?» есть ответ с обеих сторон. Публичный API комментариев этот признак убирает: посетители видят ответ команды, а не id токена.
5. Ограничивать частоту записи по человеку и инструменту
Агент, который вышел из-под контроля и повторяет неудачную правку, может делать сотни вызовов в минуту. Лимит записи на весь проект заблокировал бы коллег, которые ничего не нарушали, а лимит на токен обходится созданием второго токена.
Поэтому инструменты записи ограничены для каждого участника и каждого инструмента отдельно: 60 вызовов в минуту к update_feedback и отдельные 60 к create_comment. Общее окно лимита MCP для проекта тоже отделено от окна публичного API, так что агент, который рассылает запросы веером, не исчерпает лимит вашего сайта для клиентов.
6. Не хранить секреты в общем конфиге
Команде нужен один закоммиченный .mcp.json, чтобы у всех была одинаковая настройка. Но раз токены личные, ни одного из них в этом файле быть не должно. Вместо этого файл берёт токен из окружения каждого человека:
{
"mcpServers": {
"kookee": {
"type": "http",
"url": "https://api.kookee.dev/api/mcp",
"headers": { "Authorization": "Bearer ${KOOKEE_MCP_TOKEN}" }
}
}
}
Каждый участник записывает в KOOKEE_MCP_TOKEN свой токен. Команда для Claude Code, которую показывает панель управления, делает то же через $KOOKEE_MCP_TOKEN — кроме момента сразу после создания токена: тогда она подставляет его за вас.
Если у вас свой MCP-сервер
Выдавайте доступы на человека и на рабочее пространство. MCP-клиент — это агент конкретного человека, а не приложение.
Делайте «только чтение» режимом по умолчанию и не регистрируйте инструменты, которые токену недоступны.
Перепроверяйте человека за токеном при каждом запросе: аккаунт, участие, блокировку.
Храните хеши, показывайте токены один раз и давайте им собственный префикс.
Отличайте «не тот тип доступа» от «недействителен» и больше ничего не сообщайте.
Записывайте токен с каждым изменением и показывайте его там, где люди читают историю.
Ограничивайте частоту записи по человеку и инструменту, отдельно от публичного API.
Не держите секреты в общем конфиге клиента: берите их из переменных окружения.
MCP-сервер Kookee теперь работает на личных токенах, а ассистенты умеют читать и разбирать доску обратной связи. Настройка описана в статье Управление контентом из ИИ-ассистента (MCP), новые инструменты — в статье Разбор обратной связи из ИИ-ассистента.