Establish MCP — транспорт до графа
Каждый другой скилл этой поставки предполагает, что MCP-тулы iskron_* уже в сессии. Этот скилл ставит транспорт — и чинит его, когда тулов нет или они есть по имени и отказывают в деле. Его работа кончается там, где начинается работа entry: сессией, чей первый вызов iskron_orient отвечает.
Сервер удалённый (streamable HTTP + OAuth); из него на машину пользователя не ставится ничего. Ставится эта поставка — и она несёт собственный транспорт: scripts/iskron.mjs, рядом с этим файлом — один файл, несущий мост, обоих сторожей стояния и диагностику doctor. Записи MCP-сервера в плагинах Claude Code и Codex — stdio-мост из самой поставки (путь к этому файлу от корня установленного плагина; ступень 0 ниже). В OpenCode мост поднимает плагин из поставки — scripts/opencode-plugin.js рядом, копией в каталоге плагинов OpenCode (шаг 1 ниже); в pi — расширение из каталога extensions/ поставки. Только архив claude.ai несёт нативную http-запись: локальный процесс он поднять не может. Где записи моста нет, мост ставишь ты, и ниже сказано как. Вход у моста двоякий: OAuth в браузере по умолчанию — или личный токен доступа (шаг 4), с которым в браузер он не ходит вовсе.
Сначала: которым путём
Ставь мост, и только если не выходит — нативную запись. Иди по лестнице вниз, остановись на первой подходящей строке. Браузера нет или OAuth не доходит — это не повод сходить с моста: у него второй вход, личный токен доступа (ISKRON_BRIDGE_TOKEN или файл ~/.iskron-bridge/token, шаг 4).
Мост стоит первым не по вкусу: у него две обязанности, которых у нативного MCP-клиента харнесса нет вовсе. Он держит грант без походов в браузер (обновление в простое, один флоу на всю машину, логин тратится последним) — и держит привязку стояния через пересборку сессии: заголовок стояния на открытии сессии плюс переигранный register, без которых записи агента уходят без автора и он узнаёт об этом по пометке на уже ушедшей записи. Третье помельче, но чинит вслепую: отказы моста всегда громкие, каждый называет iskron-bridge, сборку и вердикт.
- Плагин Claude Code, Codex, OpenCode или расширение pi уже поднял мост. В OpenCode мост поднимает плагин из каталога
~/.config/opencode/plugins/ (тулы под своими именами; запись mcp в конфиге OpenCode для этого не годится — она приписала бы каждому тулу имя сервера вторым префиксом), и его туда кладёшь ты шагом 1; в pi — расширение пакета. Плагинная запись — сам мост из поставки: claude mcp list показывает plugin:iskron:iskron stdio-записью, codex mcp list — iskron с command node и Auth: Unsupported (логинить нечем и незачем), первый вызов тула отвечает ошибкой с адресом авторизации, и после клика в браузере тулы отвечают. Копировать ничего не нужно, обновление приезжает с плагином (новая сессия или /reload-plugins). «Поднятие моста» ниже — для харнесов без плагинной записи и для второго инстанса; doctor тем же файлом говорит, какая сборка стоит и работает ли она.
- На машине есть Node ≥22, и конфиг харнесса берёт
command + args (stdio-сервер) → «Поднятие моста» ниже. Это путь по умолчанию, и единственный, который держит привязку стояния и сокет стояния.
- Мост не поднять, но у харнесса есть глагол авторизации — Node ≥22 нет или он не на
PATH того процесса, что запускает серверы; харнесс не запускает локальных процессов (веб-харнесс, claude.ai); конфиг принимает только удалённую запись, — а войти есть чем (/mcp, codex mcp login, opencode mcp auth, кнопка Authenticate, UI коннекторов) → нативная запись (url / httpUrl / serverUrl, type: "http" / "remote" / "streamableHttp") в пользовательском скоупе. Цена называется вслух, а не замалчивается: привязку стояния здесь не держит никто, и standing велит register перед каждой несущей записью.
- Мост не поднять, и глагола авторизации нет нигде — доки показывают ручные заголовки
Authorization → нативный OAuth-флоу не случится, а поднять нечего: скажи человеку прямо, что в этом харнессе графа не будет, и назови, чего не хватает — Node для моста или способа войти.
Два подключения к одному графу — обычное дело, и это не поломка. У плагинов Claude Code и Codex запись одна — мост; нативная http-запись едет только архивом claude.ai, и там харнесс, поднявший её, рядом с поднятым тобой мостом даёт в сессии два набора тулов iskron_* на один и тот же граф. Пишущие вызовы веди через мост: привязка стояния живёт в его процессе, и та же запись через нативный набор ляжет без автора. Различай их по имени набора в своём харнессе, а при сомнении — по ошибке любого вызова: мост называет себя и сборку.
Когда неясно — разрешай пробой, не чтением доков: подними мост и сделай один вызов. 401, за которым открылся браузер, — путь работает; молчание при живом на других поверхностях сервере — читай таблицу ошибок ниже.
Поднятие моста
Скопируй скрипт из поставки. Установленная копия этого скилла живёт по версионированному пути, меняющемуся с каждым релизом, — конфиг не должен указывать внутрь него никогда. Скопируй в устойчивый дом:
mkdir -p ~/.iskron-bridge
cp "$SKILL_DIR/scripts/iskron.mjs" ~/.iskron-bridge/iskron-bridge.mjs
В OpenCode — второй файл тем же ходом, плагин из поставки, в каталог, где OpenCode сам держит его единственную зависимость @opencode-ai/plugin:
mkdir -p ~/.config/opencode/plugins
cp "$SKILL_DIR/scripts/opencode-plugin.js" ~/.config/opencode/plugins/iskron.js
Шаг 3 для OpenCode после этого не нужен: плагин поднимает мост из домашней копии (или из ISKRON_BRIDGE_PATH) на старте OpenCode, регистрирует каждый тул под его именем и вкладывает кадры канала в сессию промптом; мост он гонит на Bun самого OpenCode, Node не нужен. Мост не ответил за двадцать секунд — плагин поднимает тулы из прошлого списка и говорит об этом; ни ответа, ни списка — тулов нет до перезапуска, и это сказано вслух.
($SKILL_DIR — базовая директория этого скилла, печатается при его загрузке. Имя копии, ~/.iskron-bridge/iskron-bridge.mjs, — контракт с конфигами харнесов, и с именем файла в поставке оно не меняется.) Повторять копирование после обновления поставки больше не нужно: дом выравнивает сам мост. Каждый долгоживущий запуск (мост, сторожа) сравнивает свою версию с домашней: своя новее — кладёт себя в дом (и плагин OpenCode рядом, если он стоит); домашняя новее — запускается ею, так что бежит новейшая копия, каким бы файлом ни запустил харнес. Раз в шесть часов мост, смотрящий на продовый инстанс, спрашивает релизы репозитория поставки: есть новее — скачивает мост, плагин OpenCode и SETUP.md в дом и говорит строкой ПОСТАВКА ОТСТАЛА в ближайшем ответе тула и уведомлением — эту строку агент передаёт человеку, скиллы обновляет канал харнеса. По требованию то же делает node ~/.iskron-bridge/iskron-bridge.mjs update (дверь iskron, маршрут «обнови»): кладёт свежее и печатает, что положено и что делать дальше. Выключатель для проб и CI — ISKRON_BRIDGE_NO_UPDATE=1; другой инстанс сервера обновлений с релизов не получает. Которая редакция стоит, отвечает сам мост: node ~/.iskron-bridge/iskron-bridge.mjs --version печатает сборку вида v3.4.4+a1b2c3d4 — версия равна версии плагина и датирует всю поставку разом, хеш различает файлы между релизами. Сверяй после каждого обновления: копия, отставшая от поставки, — самый частый источник «уже починенных» дефектов. Там, где поставку несёт расширение pi, эту сверку делает оно: сличает домашнюю копию со своей побайтово на старте сессии и говорит об этом вслух. Побайтово, а не по версии, нарочно — между релизами версия стоит на месте, пока файл меняется, и сверка по версии молчала бы ровно там, где копия отстала. Версия держит ограду в другую сторону: копию строго новее своей расширение не трогает. В остальных харнессах сверка твоя, и делается она этой же командой — или разом со всем остальным: node ~/.iskron-bridge/iskron-bridge.mjs doctor печатает сборку, сличает домашнюю копию с этим файлом, спрашивает сервер, читает грант и запись в конфиге харнеса, ничего не меняя.
Мост уже знает продовый инстанс. Без аргумента-URL он целит в https://mcp.iskron.ru. Передавай URL (или ISKRON_BRIDGE_URL) только когда пользователь работает с другим инстансом или форком — возьми его у него или из AGENTS.md репозитория; недефолтный никогда не угадывай.
Зарегистрируй мост обычным stdio-MCP-сервером — в ПОЛЬЗОВАТЕЛЬСКОМ скоупе, не проектном. Граф — собственный контур пользователя и следует за ним через каждый репозиторий; проектно-скоупная запись теряет его в первой же соседней директории. Потому регистрация идёт в пользовательский конфиг харнесса (файл в домашней директории), никогда не в пер-проектный — проектный скоуп только по явной просьбе пользователя. Общая форма:
{ "command": "node", "args": ["/home/USER/.iskron-bridge/iskron-bridge.mjs"] }
В Claude Code это
claude mcp add --scope user iskron -- node ~/.iskron-bridge/iskron-bridge.mjs
(скоуп по умолчанию — проектно-локальный: передай --scope user явно). Имя записи смотри по харнессу: где отгружаемая нативная запись неймспейсится плагином, столкновения нет и iskron свободно; где плагина нет и запись поднята плоской установкой, бери своё имя, иначе не различишь наборы тулов в сессии. URL сервера добавляй ещё одним аргументом только для недефолтного инстанса. В JSON-конфигах, не раскрывающих ~, пиши абсолютный домашний путь.
Первый вызов авторизует. На первый 401 мост отвечает открытием браузера для OAuth-флоу (discovery, регистрация клиента, PKCE — всё само). Безголовая сессия: поставь ISKRON_BRIDGE_NO_BROWSER=1 — мост напечатает authorize-URL на stderr, пользователь откроет где угодно. Токены ложатся в ~/.iskron-bridge/ (0600) и дальше обновляются сами — и в простое тоже, так что неиспользуемая сессия не деградирует в мёртвый грант.
Или — личный токен доступа вместо OAuth. Человек выпускает его на странице токенов графа и отдаёт тебе; ты кладёшь его мосту — файлом ~/.iskron-bridge/token (umask 077) либо переменной ISKRON_BRIDGE_TOKEN в окружении процесса моста (переменная старше файла). С токеном мост не ходит ни в discovery, ни в браузер, ни за обновлением, и хранилище OAuth-гранта не трогает; 401 при нём — «токен отвергнут», и ошибка называет источник токена и единственный ход: новый токен от человека. Никогда не выдумывай, не угадывай и не переиспользуй чужой; в конфиг харнеса и в URL токен не пишется. doctor называет режим и проверяет токен живым рукопожатием.
Проверяй вызовом, не конфигом. Перезапусти MCP-слой харнесса и прогони один настоящий вызов (iskron_realm(action="list") или orient). Мост подключён, когда вызов отвечает; всё меньшее — не сделано.
Что мост гарантирует — и как читать его ошибки
Мост никогда не отвечает харнессу тишиной: каждый запрос получает либо ответ сервера, либо JSON-RPC-ошибку, называющую iskron-bridge и причину. Долгоживущего upstream-соединения, способного умереть полуоткрытым, нет: каждый запрос — собственный POST с предельным сроком.
И каждая ошибка говорит, мог ли вызов подействовать. «Не удалось» и «неизвестно, удалось ли» — разные вести, и совет, стёртый до одного «повтори», опасен ровно на половине случаев: запрос, ушедший на провод и потерявший ответ, мог уже примениться, а слепой повтор пишет второй раз — молча там, где нет ограды версий. Поэтому мост отвечает одним из двух вердиктов. never reached the server … retry freely — сервер запроса не видел (отказ до отправки, отказ соединения, 4xx: сервер рассудил и отверг). THE OUTCOME IS UNKNOWN … re-read the target before retrying — запрос ушёл, ответа нет (таймаут, разрыв, 5xx, потерянный поток), и что с ним стало, мост честно не знает. И у безопасного повтора есть третья форма — «безопасно, но не сейчас». Отказ, который чинится ожиданием, а не починкой (придержанный nbf, досрочный отказ обновления, придержанный логин — и грация после первого отказа, и снуз после непройденного входа), несёт clears itself by waiting … wait out the interval named above: грант цел, ничего не применилось, но повтор сию секунду упрётся в ту же стену. Длину ожидания вердикт не повторяет — она уже названа в самой причине и посчитана по серверным часам; два числа на одном отказе кончаются тем, что вызывающий верит меньшему. Схлопнуть это в «не получилось» — послать агента чинить то, что чинится временем. Читай вердикт прежде текста причины: он говорит, что делать, а причина — почему. На повторах вердикт берётся худший из виденных: попытка, ушедшая с потерянным ответом, не отменяется более поздней, которая не ушла вовсе.
Много агентов, один грант, один флоу. Десятки локальных агентов могут держать каждый свой процесс моста; хранилище токенов у них одно, и обновление, доведённое любым из них, служит остальным. Само обновление потому и ведётся машинно-общим порядком: за раз его делает ровно один мост, прочие ждут и берут готовое с диска — одно истечение, одно обновление, сколько бы мостов ни стояло. Иначе ротация обращает соседей в повторителей: сервер, стерегущий повтор refresh-токена, читает такое как кражу гранта и гасит всю семью — и машина оказывается разлогинена целиком через минуты после исправного входа. Когда грант мёртв и нужен браузер, флоу ведёт ровно один инстанс; каждый вызов на каждом агенте тем временем отвечает сразу — и тем же самым authorize-URL: на какую бы поверхность человек ни смотрел, один клик лечит всю машину. Ни один вызов никогда не висит в ожидании человека.
Часы держит сервер, а не мост. Расписание берётся из самих токенов, когда они это говорят: exp access-токена — момент, когда его перестанут принимать (он старше объявленного expires_in, если те расходятся); nbf refresh-токена — момент, когда его начнут принимать, а сервер вправе придержать его до почти полного истечения access-токена; exp refresh-токена — час, после которого грант честно кончился и починит его только человек. Отсюда правило, и у него две половины. Обновление, которого никто не ждёт (access ещё работает), ждёт наступления nbf, а не тратит отказ впустую. Обновление, которого ждёт вызывающий, стучится — час есть довод подождать, но никогда не довод бездействовать: иначе «сервер, возможно, откажет» превращается в «ответа не будет полчаса», и обойти это вызывающему нечем; да и сам прочитанный nbf бывает устаревшим, если грант мгновением раньше ротировал сосед. Отказ на такой стук читается досрочным, а не смертью гранта; повторно мост стучит не чаще раза на короткий отрезок — не на каждый вызов, иначе один отвергнутый access-токен обернулся бы сотнями отказов от каждого моста машины. И первый досрочный отказ — не приговор следующему вызову: чтение часа может быть протухшим — грант мгновением раньше провернул сосед, — а отказ сервера не обязан пережить второе предъявление; наблюдалось в поле: тот же вызов проходил секунды спустя. Потому первый такой отказ прописывает повтор сейчас, и настоящим час считается только после повторного досрочного отказа — тот сам называет своё ожидание цифрой расписания токена, не длиной чьей-то глухоты. Токены непрозрачны — остаётся expires_in, но как запасной путь, а не первый. И читаются эти часы по серверным часам, а не по машинным: перекос локальных часов мост меряет по заголовку Date каждого ответа сервера, запоминает в хранилище и вносит в каждое суждение о часах — машина, отстающая на полчаса, иначе полчаса считала бы спетый access-токен свежим, а вступивший в силу refresh придержанным (наблюдалось в поле). Перекос крупнее полуминуты называется в grant.log прямым текстом — это диагноз, лечится NTP.
Логин тратится последним. Отказ в обновлении сам по себе не приговор: обновление, которое никому не было нужно (access ещё работает), и токен, не вступивший в силу, отвергаются теми же словами, что и мёртвый грант. Поэтому в браузер человека ведут, только когда отказ устоял — держится дольше короткой отсрочки, — и никогда дважды подряд: логин, предложенный и не доведённый, не предлагают снова следующим же вызовом. Одно исключение честно: refresh-токен, переживший собственный exp, — доказательство сам по себе, и логин по нему предлагается первым же вызовом, без отсрочки — грация здесь была бы двумя минутами чистого простоя. Что именно произошло с грантом, пишется в ~/.iskron-bridge/grant.log — короткую машинную летопись обновлений и отказов словами сервера (токенов там нет); она переживает сессию харнеса, чей stderr к моменту вопроса обычно уже потерян.
Кто ведёт флоу, решает петлевой callback-порт, а не заметка рядом с хранилищем токенов: флоу принадлежит инстансу, занявшему порт, а заметка лишь несёт его URL, чтобы остальные его показали. Потому мост, убитый посреди флоу — прибитый эфемерный процесс харнесса, падение, SIGKILL, — передаёт флоу чисто: следующий инстанс не находит слушателя, перенимает флоу и публикует собственный URL. Попрошенный уйти при незакрытом флоу, мост стоит до тех пор, пока клик не приземлится, — потому что клик неповторим. Отсюда следствие, о которое спотыкаются: SIGTERM (а значит и обычный pkill) такой мост не снимает, и порт остаётся занят — если он нужен тебе прямо сейчас, снимай kill -9, сознавая, что чей-то незавершённый логин при этом пропадёт. Если сомневаешься, жив ли ещё authorize-URL, вынь порт из его redirect_uri и поищи слушателя: lsof -iTCP:PORT -sTCP:LISTEN на macOS/Linux, netstat -ano | findstr :PORT на Windows. Открытый порт — и есть всё притязание; URL, опубликованный без него, стоит человеку логина впустую.
Каждая синтетическая ошибка моста и каждая строка grant.log несут сборку вида v3.4.4+a1b2c3d4: версия — версия плагина (датирует всю установленную поставку, включая скиллы), хеш — реально исполняемый файл. В любом репорте о проблеме первым делом цитируй её: она датирует код точнее любых слов, и «известный починенный дефект на старой редакции» отличается от «нового дефекта» одним взглядом.
| Видишь |
Значит |
Ход |
THE OUTCOME IS UNKNOWN в ошибке после ПИШУЩЕГО вызова |
запрос ушёл, ответ потерян; запись могла лечь |
перечитай цель и сверь версию, потом решай о повторе — слепой повтор задваивает |
never reached the server в ошибке |
сервер запроса не видел, ничего не применилось |
повторяй смело |
clears itself by waiting |
придержанный грант: ничего не применилось, но и повтор сейчас не пройдёт |
подожди срок, названный в самой причине, и не чини |
ошибка с upstream unreachable / no answer within |
сеть или сервер лежит; мост в порядке |
повтори; если держится — внимания требует серверная сторона, не мост |
| в ошибке сборка старше нынешней (или её нет вовсе) |
у пользователя устаревшая копия моста; дефект мог быть давно починен |
node ~/.iskron-bridge/iskron-bridge.mjs update, затем свежий SETUP.md рядом с грантом для скиллов; перезапусти сессии харнесса, чтобы не осталось старых процессов, — и только потом дебажь |
machine clock is … behind/ahead of the server в grant.log |
локальные часы врут; мост уже судит по серверным, но каждая ротация оплачивается лишним 401 |
почини NTP на машине |
| после пароля на странице входа — «в ссылке для входа не хватает обязательных параметров» |
в окне браузера адрес authorize без client_id: вкладку открыл мост прежней редакции на Windows, и оболочка обрезала ссылку на первом & |
открой ссылку из ответа агента руками, целиком; затем node ~/.iskron-bridge/iskron-bridge.mjs update — свежий мост открывает вкладку без оболочки |
ошибка с authorization failed |
отказал сам OAuth-флоу |
прочти stderr моста в MCP-логах харнесса; повтори вызов — флоу перезапустится; не проходит раз за разом или браузера нет — личный токен: ISKRON_BRIDGE_TOKEN или ~/.iskron-bridge/token (шаг 4) |
ошибка с personal access token … is refused |
мост идёт по токену, и сервер его отверг — отозван, истёк или без прав на граф |
новый токен от человека в тот источник, что назван в ошибке; убрать токен — мост вернётся к OAuth |
ошибка с session recovery failed |
сервер перезапустился и отверг ре-инициализацию |
перезапусти MCP-соединение харнесса |
ошибка с all candidate callback ports … are held |
заняты все запасные redirect-порты лесенки — мост сам пробует несколько выведенных из URL сервера портов, прежде чем сдаться |
найди занявших (lsof -iTCP:PORT, или netstat -ano | findstr :PORT) и освободи любой из названных портов |
| логин прошёл, но вызовы возвращают 401 |
токен выдан с чужим аудиенсом — сервер сверяет aud не с той формой адреса |
раскодируй aud из access_token в хранилище; разошлось — задай ISKRON_BRIDGE_RESOURCE строкой, которую ждёт сервер |
| вызовы висят вовсе без ошибки |
харнесс говорит не с этим мостом |
проверь, какой процесс конфиг реально запустил |
| все агенты разом просят перелогиниться |
грант погашен целиком — характерный след повтора ротированного refresh-токена |
посмотри, не остались ли процессы моста прежней редакции (ps ax | grep iskron-bridge): общий замок обновления они не берут. Обнови файл моста и перезапусти сессии харнесса, чтобы старых процессов не осталось |
| мост зовёт в браузер, хотя грант на вид жив |
обновление отвергнуто, и отказ устоял |
прочти ~/.iskron-bridge/grant.log — там причина словами сервера и время каждого обновления. not yet valid у refresh-токена означает, что сервер придерживает его до истечения access-токена, и ждать надо nbf, а не логина |
refresh token is not in force for another Ns — и так окнами по 25 минут |
access-токен умер раньше, чем refresh вступил в силу по своему nbf: в этом окне у моста нет ни чтения, ни записи, и вердикт «clears itself by waiting» точен, но означает не «подожди немного», а «полчаса без графа». Устройство ли это сервера или дефект расписания — открыто у держателя сервера |
жди срок из ошибки, не перезапускай вход: грант цел. В окне через мост не идёт ничего — и справка тула тоже; побудки соседям в это окно уходили прямым POST на входной адрес стояния с доски, он гранта не требует (наблюдено на сборке v4.0.0); записи в граф — только после окна |
| «not asking again for Ns» в ответе на вызов |
логин уже предлагали и не довели; мост не переспрашивает |
доведи прошлый вход или повтори вызов, когда отсрочка выйдет — она названа в самой ошибке |
Аудиенс — там, где логин проходит, а вызовы всё равно бьют 401. Мост шлёт resource (RFC 8707), и сервер кладёт его в aud выданного токена; MCP-эндпоинт потом сверяет aud со своим адресом посимвольно. Значение мост берёт из discovery — из поля resource в /.well-known/oauth-protected-resource. Если сервер валидирует форму, которую сам не печатает (характерный случай — со слешем на конце против без слеша), грант выйдет с чужим аудиенсом, и это не видно ни на форме логина, ни в ответе token-эндпоинта: сломается позже, на первом настоящем вызове. Задай ISKRON_BRIDGE_RESOURCE точной строкой, которую ждёт сервер — правильную знает только тот, кто держит деплой, поэтому спрашивай, а не подбирай. Проверяется это одним движением: возьми access_token из хранилища, раскодируй среднюю часть JWT и прочти aud — там должен стоять адрес MCP-сервера рядом с client_id.
Ручки, когда дефолты жмут: --timeout мс на запрос (по умолчанию 120000), --auth-dir, --no-browser, --debug (подробный stderr), env ISKRON_BRIDGE_CLIENT_ID (пропустить динамическую регистрацию), ISKRON_BRIDGE_SCOPE, ISKRON_BRIDGE_RESOURCE (переопределить resource/аудиенс), ISKRON_BRIDGE_NO_UPDATE (не выравнивать дом и не спрашивать релизы — пробы и CI).
Первая помощь, когда сессия на mcp-remote
Харнесс, всё ещё мостящийся через mcp-remote (npx), отказывает узнаваемой формой: каждый вызов iskron_* таймаутится, пока сервер жив на других своих поверхностях. Это полумёртвый мост: процесс отвечает харнессу по stdio, а его TCP к серверу пропал, и он не ошибается и не переподключается.
Диагностируй и вычисти:
ps aux | grep mcp-remote # кандидатные pid
lsof -nP -p PID | grep ESTABLISHED # у живого моста есть соединение к :443
kill PID # только те, что БЕЗ установленного TCP
Харнесс перезапустит свежие мосты, и следующий вызов пройдёт. Живые не трогай — среди трупов может стоять работающий. Затем предложи переход на iskron-bridge (его владеет и версионирует эта поставка, и его режим отказа — видимая ошибка вместо этой тишины).
1---2name: establish-mcp3description: Транспорт до графа Искрона. По умолчанию — iskron-bridge из поставки: stdio→streamable-HTTP MCP-мост (OAuth либо личный токен), держащий грант и привязку стояния через пересборку сессии и никогда не отвечающий харнессу тишиной; нативная http+OAuth регистрация — запасной путь, когда мост не поднять. Триггеры: «подключи граф», «подними мост», «тулы iskron недоступны», «mcp не подключается», «oauth не проходит», «войти по токену», «PAT вместо oauth», «подключи opencode», «какая версия моста», connect to the graph, raise the bridge, MCP tools missing. Композирует entry и iskronify.4---56# Establish MCP — транспорт до графа78Каждый другой скилл этой поставки предполагает, что MCP-тулы `iskron_*` уже в сессии. Этот скилл ставит транспорт — и чинит его, когда тулов нет или они есть по имени и отказывают в деле. Его работа кончается там, где начинается работа entry: сессией, чей первый вызов `iskron_orient` отвечает.910Сервер удалённый (streamable HTTP + OAuth); из него на машину пользователя не ставится ничего. Ставится эта поставка — и она несёт собственный транспорт: `scripts/iskron.mjs`, рядом с этим файлом — один файл, несущий мост, обоих сторожей стояния и диагностику `doctor`. Записи MCP-сервера в плагинах Claude Code и Codex — **stdio-мост из самой поставки** (путь к этому файлу от корня установленного плагина; ступень 0 ниже). В OpenCode мост поднимает **плагин из поставки** — `scripts/opencode-plugin.js` рядом, копией в каталоге плагинов OpenCode (шаг 1 ниже); в pi — расширение из каталога `extensions/` поставки. Только архив claude.ai несёт **нативную http-запись**: локальный процесс он поднять не может. Где записи моста нет, мост ставишь ты, и ниже сказано как. Вход у моста двоякий: OAuth в браузере по умолчанию — или **личный токен доступа** (шаг 4), с которым в браузер он не ходит вовсе.1112## Сначала: которым путём1314**Ставь мост, и только если не выходит — нативную запись.** Иди по лестнице вниз, остановись на первой подходящей строке. Браузера нет или OAuth не доходит — это не повод сходить с моста: у него второй вход, личный токен доступа (`ISKRON_BRIDGE_TOKEN` или файл `~/.iskron-bridge/token`, шаг 4).1516Мост стоит первым не по вкусу: у него две обязанности, которых у нативного MCP-клиента харнесса нет вовсе. Он держит грант без походов в браузер (обновление в простое, один флоу на всю машину, логин тратится последним) — и держит **привязку стояния** через пересборку сессии: заголовок стояния на открытии сессии плюс переигранный `register`, без которых записи агента уходят без автора и он узнаёт об этом по пометке на уже ушедшей записи. Третье помельче, но чинит вслепую: отказы моста всегда громкие, каждый называет `iskron-bridge`, сборку и вердикт.17180. **Плагин Claude Code, Codex, OpenCode или расширение pi уже поднял мост.** В OpenCode мост поднимает плагин из каталога `~/.config/opencode/plugins/` (тулы под своими именами; запись `mcp` в конфиге OpenCode для этого не годится — она приписала бы каждому тулу имя сервера вторым префиксом), и его туда кладёшь ты шагом 1; в pi — расширение пакета. Плагинная запись — сам мост из поставки: `claude mcp list` показывает `plugin:iskron:iskron` stdio-записью, `codex mcp list` — `iskron` с `command node` и `Auth: Unsupported` (логинить нечем и незачем), первый вызов тула отвечает ошибкой с адресом авторизации, и после клика в браузере тулы отвечают. Копировать ничего не нужно, обновление приезжает с плагином (новая сессия или `/reload-plugins`). «Поднятие моста» ниже — для харнесов без плагинной записи и для второго инстанса; `doctor` тем же файлом говорит, какая сборка стоит и работает ли она.191. **На машине есть Node ≥22, и конфиг харнесса берёт `command` + `args`** (stdio-сервер) → «Поднятие моста» ниже. Это путь по умолчанию, и единственный, который держит привязку стояния и сокет стояния.202. **Мост не поднять, но у харнесса есть глагол авторизации** — Node ≥22 нет или он не на `PATH` того процесса, что запускает серверы; харнесс не запускает локальных процессов (веб-харнесс, claude.ai); конфиг принимает только удалённую запись, — а войти есть чем (`/mcp`, `codex mcp login`, `opencode mcp auth`, кнопка Authenticate, UI коннекторов) → нативная запись (`url` / `httpUrl` / `serverUrl`, `type: "http"` / `"remote"` / `"streamableHttp"`) в пользовательском скоупе. Цена называется вслух, а не замалчивается: привязку стояния здесь не держит никто, и **standing** велит `register` перед каждой несущей записью.213. **Мост не поднять, и глагола авторизации нет нигде** — доки показывают ручные заголовки `Authorization` → нативный OAuth-флоу не случится, а поднять нечего: скажи человеку прямо, что в этом харнессе графа не будет, и назови, чего не хватает — Node для моста или способа войти.2223**Два подключения к одному графу — обычное дело, и это не поломка.** У плагинов Claude Code и Codex запись одна — мост; нативная http-запись едет только архивом claude.ai, и там харнесс, поднявший её, рядом с поднятым тобой мостом даёт в сессии два набора тулов `iskron_*` на один и тот же граф. Пишущие вызовы веди через **мост**: привязка стояния живёт в его процессе, и та же запись через нативный набор ляжет без автора. Различай их по имени набора в своём харнессе, а при сомнении — по ошибке любого вызова: мост называет себя и сборку.2425Когда неясно — разрешай пробой, не чтением доков: подними мост и сделай один вызов. `401`, за которым открылся браузер, — путь работает; молчание при живом на других поверхностях сервере — читай таблицу ошибок ниже.2627## Поднятие моста28291. **Скопируй скрипт из поставки.** Установленная копия этого скилла живёт по версионированному пути, меняющемуся с каждым релизом, — конфиг не должен указывать внутрь него никогда. Скопируй в устойчивый дом:3031 ```sh32 mkdir -p ~/.iskron-bridge33 cp "$SKILL_DIR/scripts/iskron.mjs" ~/.iskron-bridge/iskron-bridge.mjs34 ```3536 **В OpenCode — второй файл тем же ходом**, плагин из поставки, в каталог, где OpenCode сам держит его единственную зависимость `@opencode-ai/plugin`:3738 ```sh39 mkdir -p ~/.config/opencode/plugins40 cp "$SKILL_DIR/scripts/opencode-plugin.js" ~/.config/opencode/plugins/iskron.js41 ```4243 Шаг 3 для OpenCode после этого не нужен: плагин поднимает мост из домашней копии (или из `ISKRON_BRIDGE_PATH`) на старте OpenCode, регистрирует каждый тул под его именем и вкладывает кадры канала в сессию промптом; мост он гонит на Bun самого OpenCode, Node не нужен. Мост не ответил за двадцать секунд — плагин поднимает тулы из прошлого списка и говорит об этом; ни ответа, ни списка — тулов нет до перезапуска, и это сказано вслух.4445 (`$SKILL_DIR` — базовая директория этого скилла, печатается при его загрузке. Имя копии, `~/.iskron-bridge/iskron-bridge.mjs`, — контракт с конфигами харнесов, и с именем файла в поставке оно не меняется.) Повторять копирование после обновления поставки больше не нужно: **дом выравнивает сам мост.** Каждый долгоживущий запуск (мост, сторожа) сравнивает свою версию с домашней: своя новее — кладёт себя в дом (и плагин OpenCode рядом, если он стоит); домашняя новее — запускается ею, так что бежит новейшая копия, каким бы файлом ни запустил харнес. Раз в шесть часов мост, смотрящий на продовый инстанс, спрашивает релизы репозитория поставки: есть новее — скачивает мост, плагин OpenCode и `SETUP.md` в дом и говорит строкой `ПОСТАВКА ОТСТАЛА` в ближайшем ответе тула и уведомлением — эту строку агент передаёт человеку, скиллы обновляет канал харнеса. По требованию то же делает `node ~/.iskron-bridge/iskron-bridge.mjs update` (дверь `iskron`, маршрут «обнови»): кладёт свежее и печатает, что положено и что делать дальше. Выключатель для проб и CI — `ISKRON_BRIDGE_NO_UPDATE=1`; другой инстанс сервера обновлений с релизов не получает. Которая редакция стоит, отвечает сам мост: `node ~/.iskron-bridge/iskron-bridge.mjs --version` печатает сборку вида `v3.4.4+a1b2c3d4` — версия равна версии плагина и датирует всю поставку разом, хеш различает файлы между релизами. Сверяй после каждого обновления: копия, отставшая от поставки, — самый частый источник «уже починенных» дефектов. Там, где поставку несёт расширение pi, эту сверку делает оно: сличает домашнюю копию со своей **побайтово** на старте сессии и говорит об этом вслух. Побайтово, а не по версии, нарочно — между релизами версия стоит на месте, пока файл меняется, и сверка по версии молчала бы ровно там, где копия отстала. Версия держит ограду в другую сторону: копию **строго новее** своей расширение не трогает. В остальных харнессах сверка твоя, и делается она этой же командой — или разом со всем остальным: `node ~/.iskron-bridge/iskron-bridge.mjs doctor` печатает сборку, сличает домашнюю копию с этим файлом, спрашивает сервер, читает грант и запись в конфиге харнеса, ничего не меняя.46472. **Мост уже знает продовый инстанс.** Без аргумента-URL он целит в `https://mcp.iskron.ru`. Передавай URL (или `ISKRON_BRIDGE_URL`) только когда пользователь работает с другим инстансом или форком — возьми его у него или из AGENTS.md репозитория; недефолтный никогда не угадывай.48493. **Зарегистрируй мост обычным stdio-MCP-сервером — в ПОЛЬЗОВАТЕЛЬСКОМ скоупе, не проектном.** Граф — собственный контур пользователя и следует за ним через каждый репозиторий; проектно-скоупная запись теряет его в первой же соседней директории. Потому регистрация идёт в пользовательский конфиг харнесса (файл в домашней директории), никогда не в пер-проектный — проектный скоуп только по явной просьбе пользователя. Общая форма:5051 ```json52 { "command": "node", "args": ["/home/USER/.iskron-bridge/iskron-bridge.mjs"] }53 ```5455 В Claude Code это56 `claude mcp add --scope user iskron -- node ~/.iskron-bridge/iskron-bridge.mjs`57 (скоуп по умолчанию — проектно-локальный: передай `--scope user` явно). Имя записи смотри по харнессу: где отгружаемая нативная запись неймспейсится плагином, столкновения нет и `iskron` свободно; где плагина нет и запись поднята плоской установкой, бери своё имя, иначе не различишь наборы тулов в сессии. URL сервера добавляй ещё одним аргументом только для недефолтного инстанса. В JSON-конфигах, не раскрывающих `~`, пиши абсолютный домашний путь.58594. **Первый вызов авторизует.** На первый `401` мост отвечает открытием браузера для OAuth-флоу (discovery, регистрация клиента, PKCE — всё само). Безголовая сессия: поставь `ISKRON_BRIDGE_NO_BROWSER=1` — мост напечатает authorize-URL на stderr, пользователь откроет где угодно. Токены ложатся в `~/.iskron-bridge/` (0600) и дальше обновляются сами — и в простое тоже, так что неиспользуемая сессия не деградирует в мёртвый грант.6061 **Или — личный токен доступа вместо OAuth.** Человек выпускает его на странице токенов графа и отдаёт тебе; ты кладёшь его мосту — файлом `~/.iskron-bridge/token` (`umask 077`) либо переменной `ISKRON_BRIDGE_TOKEN` в окружении процесса моста (переменная старше файла). С токеном мост не ходит ни в discovery, ни в браузер, ни за обновлением, и хранилище OAuth-гранта не трогает; `401` при нём — «токен отвергнут», и ошибка называет источник токена и единственный ход: новый токен от человека. Никогда не выдумывай, не угадывай и не переиспользуй чужой; в конфиг харнеса и в URL токен не пишется. `doctor` называет режим и проверяет токен живым рукопожатием.62635. **Проверяй вызовом, не конфигом.** Перезапусти MCP-слой харнесса и прогони один настоящий вызов (`iskron_realm(action="list")` или orient). Мост подключён, когда вызов отвечает; всё меньшее — не сделано.6465## Что мост гарантирует — и как читать его ошибки6667Мост никогда не отвечает харнессу тишиной: каждый запрос получает либо ответ сервера, либо JSON-RPC-ошибку, называющую `iskron-bridge` и причину. Долгоживущего upstream-соединения, способного умереть полуоткрытым, нет: каждый запрос — собственный POST с предельным сроком.6869**И каждая ошибка говорит, мог ли вызов подействовать.** «Не удалось» и «неизвестно, удалось ли» — разные вести, и совет, стёртый до одного «повтори», опасен ровно на половине случаев: запрос, ушедший на провод и потерявший ответ, мог уже примениться, а слепой повтор пишет второй раз — молча там, где нет ограды версий. Поэтому мост отвечает одним из двух вердиктов. **`never reached the server … retry freely`** — сервер запроса не видел (отказ до отправки, отказ соединения, 4xx: сервер рассудил и отверг). **`THE OUTCOME IS UNKNOWN … re-read the target before retrying`** — запрос ушёл, ответа нет (таймаут, разрыв, 5xx, потерянный поток), и что с ним стало, мост честно не знает. **И у безопасного повтора есть третья форма — «безопасно, но не сейчас».** Отказ, который чинится ожиданием, а не починкой (придержанный `nbf`, досрочный отказ обновления, придержанный логин — и грация после первого отказа, и снуз после непройденного входа), несёт **`clears itself by waiting … wait out the interval named above`**: грант цел, ничего не применилось, но повтор сию секунду упрётся в ту же стену. Длину ожидания вердикт **не повторяет** — она уже названа в самой причине и посчитана по серверным часам; два числа на одном отказе кончаются тем, что вызывающий верит меньшему. Схлопнуть это в «не получилось» — послать агента чинить то, что чинится временем. Читай вердикт прежде текста причины: он говорит, что делать, а причина — почему. На повторах вердикт берётся худший из виденных: попытка, ушедшая с потерянным ответом, не отменяется более поздней, которая не ушла вовсе.7071**Много агентов, один грант, один флоу.** Десятки локальных агентов могут держать каждый свой процесс моста; хранилище токенов у них одно, и обновление, доведённое любым из них, служит остальным. Само обновление потому и ведётся машинно-общим порядком: за раз его делает ровно один мост, прочие ждут и берут готовое с диска — одно истечение, одно обновление, сколько бы мостов ни стояло. Иначе ротация обращает соседей в повторителей: сервер, стерегущий повтор refresh-токена, читает такое как кражу гранта и гасит всю семью — и машина оказывается разлогинена целиком через минуты после исправного входа. Когда грант мёртв и нужен браузер, флоу ведёт ровно один инстанс; каждый вызов на каждом агенте тем временем отвечает сразу — и тем же самым authorize-URL: на какую бы поверхность человек ни смотрел, один клик лечит всю машину. Ни один вызов никогда не висит в ожидании человека.7273**Часы держит сервер, а не мост.** Расписание берётся из самих токенов, когда они это говорят: `exp` access-токена — момент, когда его перестанут принимать (он старше объявленного `expires_in`, если те расходятся); `nbf` refresh-токена — момент, когда его *начнут* принимать, а сервер вправе придержать его до почти полного истечения access-токена; `exp` refresh-токена — час, после которого грант честно кончился и починит его только человек. Отсюда правило, и у него две половины. Обновление, которого никто не ждёт (access ещё работает), ждёт наступления `nbf`, а не тратит отказ впустую. Обновление, которого ждёт вызывающий, **стучится** — час есть довод подождать, но никогда не довод бездействовать: иначе «сервер, возможно, откажет» превращается в «ответа не будет полчаса», и обойти это вызывающему нечем; да и сам прочитанный `nbf` бывает устаревшим, если грант мгновением раньше ротировал сосед. Отказ на такой стук читается досрочным, а не смертью гранта; повторно мост стучит не чаще раза на короткий отрезок — не на каждый вызов, иначе один отвергнутый access-токен обернулся бы сотнями отказов от каждого моста машины. И первый досрочный отказ — не приговор следующему вызову: чтение часа может быть протухшим — грант мгновением раньше провернул сосед, — а отказ сервера не обязан пережить второе предъявление; наблюдалось в поле: тот же вызов проходил секунды спустя. Потому первый такой отказ прописывает повтор сейчас, и настоящим час считается только после повторного досрочного отказа — тот сам называет своё ожидание цифрой расписания токена, не длиной чьей-то глухоты. Токены непрозрачны — остаётся `expires_in`, но как запасной путь, а не первый. И читаются эти часы по серверным часам, а не по машинным: перекос локальных часов мост меряет по заголовку `Date` каждого ответа сервера, запоминает в хранилище и вносит в каждое суждение о часах — машина, отстающая на полчаса, иначе полчаса считала бы спетый access-токен свежим, а вступивший в силу refresh придержанным (наблюдалось в поле). Перекос крупнее полуминуты называется в `grant.log` прямым текстом — это диагноз, лечится NTP.7475**Логин тратится последним.** Отказ в обновлении сам по себе не приговор: обновление, которое никому не было нужно (access ещё работает), и токен, не вступивший в силу, отвергаются теми же словами, что и мёртвый грант. Поэтому в браузер человека ведут, только когда отказ **устоял** — держится дольше короткой отсрочки, — и никогда дважды подряд: логин, предложенный и не доведённый, не предлагают снова следующим же вызовом. Одно исключение честно: refresh-токен, переживший собственный `exp`, — доказательство сам по себе, и логин по нему предлагается первым же вызовом, без отсрочки — грация здесь была бы двумя минутами чистого простоя. Что именно произошло с грантом, пишется в `~/.iskron-bridge/grant.log` — короткую машинную летопись обновлений и отказов словами сервера (токенов там нет); она переживает сессию харнеса, чей stderr к моменту вопроса обычно уже потерян.7677Кто ведёт флоу, решает **петлевой callback-порт**, а не заметка рядом с хранилищем токенов: флоу принадлежит инстансу, занявшему порт, а заметка лишь несёт его URL, чтобы остальные его показали. Потому мост, убитый посреди флоу — прибитый эфемерный процесс харнесса, падение, `SIGKILL`, — передаёт флоу чисто: следующий инстанс не находит слушателя, перенимает флоу и публикует собственный URL. Попрошенный уйти при незакрытом флоу, мост стоит до тех пор, пока клик не приземлится, — потому что клик неповторим. Отсюда следствие, о которое спотыкаются: `SIGTERM` (а значит и обычный `pkill`) такой мост не снимает, и порт остаётся занят — если он нужен тебе прямо сейчас, снимай `kill -9`, сознавая, что чей-то незавершённый логин при этом пропадёт. Если сомневаешься, жив ли ещё authorize-URL, вынь порт из его `redirect_uri` и поищи слушателя: `lsof -iTCP:PORT -sTCP:LISTEN` на macOS/Linux, `netstat -ano | findstr :PORT` на Windows. Открытый порт — и есть всё притязание; URL, опубликованный без него, стоит человеку логина впустую.7879Каждая синтетическая ошибка моста и каждая строка `grant.log` несут сборку вида `v3.4.4+a1b2c3d4`: версия — версия плагина (датирует всю установленную поставку, включая скиллы), хеш — реально исполняемый файл. В любом репорте о проблеме первым делом цитируй её: она датирует код точнее любых слов, и «известный починенный дефект на старой редакции» отличается от «нового дефекта» одним взглядом.8081| Видишь | Значит | Ход |82|---|---|---|83| `THE OUTCOME IS UNKNOWN` в ошибке после ПИШУЩЕГО вызова | запрос ушёл, ответ потерян; запись могла лечь | перечитай цель и сверь версию, **потом** решай о повторе — слепой повтор задваивает |84| `never reached the server` в ошибке | сервер запроса не видел, ничего не применилось | повторяй смело |85| `clears itself by waiting` | придержанный грант: ничего не применилось, но и повтор сейчас не пройдёт | подожди срок, названный в самой причине, и не чини |86| ошибка с `upstream unreachable` / `no answer within` | сеть или сервер лежит; мост в порядке | повтори; если держится — внимания требует серверная сторона, не мост |87| в ошибке сборка старше нынешней (или её нет вовсе) | у пользователя устаревшая копия моста; дефект мог быть давно починен | `node ~/.iskron-bridge/iskron-bridge.mjs update`, затем свежий `SETUP.md` рядом с грантом для скиллов; перезапусти сессии харнесса, чтобы не осталось старых процессов, — и только потом дебажь |88| `machine clock is … behind/ahead of the server` в `grant.log` | локальные часы врут; мост уже судит по серверным, но каждая ротация оплачивается лишним 401 | почини NTP на машине |89| после пароля на странице входа — «в ссылке для входа не хватает обязательных параметров» | в окне браузера адрес authorize без `client_id`: вкладку открыл мост прежней редакции на Windows, и оболочка обрезала ссылку на первом `&` | открой ссылку из ответа агента руками, целиком; затем `node ~/.iskron-bridge/iskron-bridge.mjs update` — свежий мост открывает вкладку без оболочки |90| ошибка с `authorization failed` | отказал сам OAuth-флоу | прочти stderr моста в MCP-логах харнесса; повтори вызов — флоу перезапустится; не проходит раз за разом или браузера нет — личный токен: `ISKRON_BRIDGE_TOKEN` или `~/.iskron-bridge/token` (шаг 4) |91| ошибка с `personal access token … is refused` | мост идёт по токену, и сервер его отверг — отозван, истёк или без прав на граф | новый токен от человека в тот источник, что назван в ошибке; убрать токен — мост вернётся к OAuth |92| ошибка с `session recovery failed` | сервер перезапустился и отверг ре-инициализацию | перезапусти MCP-соединение харнесса |93| ошибка с `all candidate callback ports … are held` | заняты **все** запасные redirect-порты лесенки — мост сам пробует несколько выведенных из URL сервера портов, прежде чем сдаться | найди занявших (`lsof -iTCP:PORT`, или `netstat -ano \| findstr :PORT`) и освободи любой из названных портов |94| логин прошёл, но вызовы возвращают 401 | токен выдан с чужим аудиенсом — сервер сверяет `aud` не с той формой адреса | раскодируй `aud` из `access_token` в хранилище; разошлось — задай `ISKRON_BRIDGE_RESOURCE` строкой, которую ждёт сервер |95| вызовы висят вовсе без ошибки | харнесс говорит не с этим мостом | проверь, какой процесс конфиг реально запустил |96| все агенты разом просят перелогиниться | грант погашен целиком — характерный след повтора ротированного refresh-токена | посмотри, не остались ли процессы моста прежней редакции (`ps ax \| grep iskron-bridge`): общий замок обновления они не берут. Обнови файл моста и перезапусти сессии харнесса, чтобы старых процессов не осталось |97| мост зовёт в браузер, хотя грант на вид жив | обновление отвергнуто, и отказ устоял | прочти `~/.iskron-bridge/grant.log` — там причина словами сервера и время каждого обновления. `not yet valid` у refresh-токена означает, что сервер придерживает его до истечения access-токена, и ждать надо `nbf`, а не логина |98| `refresh token is not in force for another Ns` — и так окнами по 25 минут | access-токен умер раньше, чем refresh вступил в силу по своему `nbf`: в этом окне у моста нет ни чтения, ни записи, и вердикт «clears itself by waiting» точен, но означает не «подожди немного», а «полчаса без графа». Устройство ли это сервера или дефект расписания — открыто у держателя сервера | жди срок из ошибки, не перезапускай вход: грант цел. В окне через мост не идёт ничего — и справка тула тоже; побудки соседям в это окно уходили прямым POST на входной адрес стояния с доски, он гранта не требует (наблюдено на сборке v4.0.0); записи в граф — только после окна |99| «not asking again for Ns» в ответе на вызов | логин уже предлагали и не довели; мост не переспрашивает | доведи прошлый вход или повтори вызов, когда отсрочка выйдет — она названа в самой ошибке |100101**Аудиенс — там, где логин проходит, а вызовы всё равно бьют 401.** Мост шлёт `resource` (RFC 8707), и сервер кладёт его в `aud` выданного токена; MCP-эндпоинт потом сверяет `aud` со своим адресом посимвольно. Значение мост берёт из discovery — из поля `resource` в `/.well-known/oauth-protected-resource`. Если сервер валидирует форму, которую сам не печатает (характерный случай — со слешем на конце против без слеша), грант выйдет с чужим аудиенсом, и это не видно ни на форме логина, ни в ответе token-эндпоинта: сломается позже, на первом настоящем вызове. Задай `ISKRON_BRIDGE_RESOURCE` точной строкой, которую ждёт сервер — правильную знает только тот, кто держит деплой, поэтому спрашивай, а не подбирай. Проверяется это одним движением: возьми `access_token` из хранилища, раскодируй среднюю часть JWT и прочти `aud` — там должен стоять адрес MCP-сервера рядом с client_id.102103Ручки, когда дефолты жмут: `--timeout` мс на запрос (по умолчанию 120000), `--auth-dir`, `--no-browser`, `--debug` (подробный stderr), env `ISKRON_BRIDGE_CLIENT_ID` (пропустить динамическую регистрацию), `ISKRON_BRIDGE_SCOPE`, `ISKRON_BRIDGE_RESOURCE` (переопределить resource/аудиенс), `ISKRON_BRIDGE_NO_UPDATE` (не выравнивать дом и не спрашивать релизы — пробы и CI).104105## Первая помощь, когда сессия на mcp-remote106107Харнесс, всё ещё мостящийся через `mcp-remote` (npx), отказывает узнаваемой формой: каждый вызов `iskron_*` таймаутится, пока сервер жив на других своих поверхностях. Это полумёртвый мост: процесс отвечает харнессу по stdio, а его TCP к серверу пропал, и он не ошибается и не переподключается.108109Диагностируй и вычисти:110111```sh112ps aux | grep mcp-remote # кандидатные pid113lsof -nP -p PID | grep ESTABLISHED # у живого моста есть соединение к :443114kill PID # только те, что БЕЗ установленного TCP115```116117Харнесс перезапустит свежие мосты, и следующий вызов пройдёт. Живые не трогай — среди трупов может стоять работающий. Затем предложи переход на `iskron-bridge` (его владеет и версионирует эта поставка, и его режим отказа — видимая ошибка вместо этой тишины).