@olykov/node-red-contrib-mcp-server-readonly
@olykov/node-red-contrib-mcp-server-readonly
Универсальные узлы сервера Model Context Protocol (MCP) для Node-RED: выставляйте любой поток как MCP-инструмент за конечной точкой, защищённой OAuth, с возможностью только просмотра потоков Node-RED через административный API. Никакой привязки к умному дому или другим предметным областям — это «голый» строительный блок для превращения потоков Node-RED в инструменты MCP, которые могут вызывать ИИ-ассистенты (Claude, Codex и т. п.).
Критическое изменение в версии 0.5.0 — только публичный клиент (PKCE). Секреты клиента и белый список redirect URI на стороне узла удалены: открытая конечная точка регистрации клиентов выдавала любой настроенный секрет любому вызывающему, а redirect URI в любом случае проверяет сам identity provider при обращении к
/authorize. Миграция: переведите клиента IdP в режим публичного клиента с PKCE (конфиденциальный клиент при обмене токена получитinvalid_client), убедитесь, что callback-URL MCP-клиента внесены в белый список IdP, а если узел предупреждает о сохранённом секрете — откройте его конфигурацию, нажмите Done и выполните деплой, чтобы секрет удалился. MCP-клиенты, подключённые до обновления, могли закэшировать старую регистрацию — если вход в систему ведёт себя некорректно, удалите сервер в клиенте и добавьте его заново.
Nodes
mcp-server(конфигурационный узел) — размещает автономную конечную точку MCP JSON-RPC наPOST /mcp/<path>, discovery защищённого ресурса OAuth 2.0 (RFC 9728), discovery сервера авторизации (RFC 8414), проксирующего реальный OIDC identity provider, и заглушку динамической регистрации клиентов, так что MCP-клиенты с поддержкой OAuth (например, Claude.ai) могут регистрироваться и аутентифицироваться самостоятельно. Несколько узловmcp-serverмогут работать одновременно, у каждого свойpathи своя независимая конфигурация авторизации.mcp-in— определяет один MCP-инструмент (имя, описание, параметры JSON Schema и необязательный гейт доступа для отдельного инструмента). Когда MCP-клиент вызывает инструмент, узел отправляет сообщение с аргументами вызова. Остальную работу выполняет остальная часть потока. Аргументы вmsg.payload— недоверенный ввод вызывающего: JSON-схема — это документация для модели, а не валидация, поэтому поток должен проверять и экранировать их перед использованием в командах оболочки, путях к файлам, URL или запросах.mcp-out— завершает ожидающий вызов инструмента. Подведите конец потока сюда, сохранивmsg._mcpCallId(из исходного сообщенияmcp-in) и установивmsg.payloadв результат.
Цепочка mcp-in → ... → mcp-out — это один MCP-инструмент. Стройте столько цепочек, сколько нужно и хвата ватьяue with the same mcp-server node, чтобы получить целый набор инструментов.
Админ-инструменты read-only API
Enable Admin read-only API tools on a mcp-server node to opt into one extra tool that works with Node-RED’s own admin HTTP API — restricted by configurable JWT claim (default: groups includes admin):
get_flow— перечисляет все вкладки потоков (id, label, node count) или возвращает полный JSON отдельной вкладки при вызове сid.
Настройка узла mcp-server
General: name,
path(→ registersPOST /mcp/<path>), publicServer URL, по которому доступен этот экземпляр Node-RED, optional имя/инструкции сервера, видимые модели, and and the optional hostname filter (see below).Auth: OIDC
Identity providerissuer URL (обязательно — конечные точки определяются автоматически из/.well-known/openid-configuration, с запасными путями в стиле PocketID; если оставить пусто, документ OAuth discovery окажется нерабочим: эндпоинты будут относительными, авторизация не заработает, и редактор не даст развернуть узел), client ID (клиент IdP должен быть public with PKCE — секреты клиента больше не поддерживаются, а redirect URI настраиваются и проверяются только в IdP), scopes, audience токена, необязательный локальный отладочный токен, который полностью обходит IdP при локальном тестировании (впишите вIdentity providerлюбой URL-заглушку и полагайтесь на отладочный токен — IdP никогда не опрашивается движком при совпадении отладочного токена;groupsнастраивается, так что claim доступ также тестировать локально), a также гейтAccess claim/Server excess(см. ниже).Admin: включать/выключать админ-read-only API инструменты, admin token (для Node-RED Admin API), порт Admin API, and and раw с `Need to check original: "admin token, the administered API port"** Actually: "admin token (for the Node-RED Admin API) and admin API port".
Let me re-parse:
Admin: enable/disable admin read-only API tools, admin token (for the Node-RED Admin API), admin API port, and the
Read-only accessgate that additionally restricts just the alternately API tools.
Ok:
Admin: включить/отключить административные read-only API инструменты, админ-токен (для Node-RED Admin API), порт для административного API и гейт
Read-only access, который отдельно ограничивает именно read-only админ-инструменты.
Access control
Одно имя claim, много списков значений. Access claim на вкладке Auth (по умолчанию groups) — это единственное имя JWT claim, с которым сравниваются все гейты. Каждое другое поле авторизации — разделяемый запятыми список any-of значений этого claim: media, ops пройдет, если claim содержит хотя бы одно из них. Пустой список означает отсутствие ограничений.
Nested claims address in account — для провайдеров, которые не помещают роли на верхний уровень токена: realm_access.roles читает based realm-роли Keycloak, and on any depth works. A key that literally exists wins, therefore italic claim, in "нама.Token" that has a dot in the middle, is searched against. This literally WINS — так claim с точкой в имени разрешается сам в себя.
Толко strings are subscripted to the exact match, resulting in an exact scalar; exact locations are never computed. The claim on the container-object it yields nothing rather than because of a random lead.
Одно имя claim, множества списков значений. Access claim на вкладке Auth (по умолчанию groups) задаёт единственный JWT-claim, с которым сверяются все гейты. Каждое остальное поле авторизации — это разделённый запятыми список любой-из (any-of) значений этого claim: media, ops проходит, если claim содержит хотя бы одно из них. Пустой список не задаёт никаких ограничений.
Вложенные claims адресуются точечным путём — для провайдеров, которые не кладут роли на верхний уровень токена: realm_access.roles читает роли Keycloak, поддерживается любая глубина. Ключ, который существует буквально, всегда побеждает: claim, в имени которого действительно есть точка, по-прежнему разрешается сам в себя. Совпадение дают только строки и массивы строк; если указать на объект-контейнер, доступа не выдаётся — и это не случайное совпадение.
Таблица:
Поле | Где | Ограничивает |
| mcp-server, вкладка Auth | все инструменты это сервера |
| mcp-in | тот инструмент, уже дополнительно |
| mcp-server, вкладка Admin |
|
Списки комбинируются по AND. Чтобы добраться до инструмента, нужно пройти и серверный список, и список инструментаК. Не admin read-only инструменты — не особый случай; их поле — это и есть список инструментов для get_flow.
Access claim: groups Server access: staff
tool A: (empty) tool B: media Admin access: admin
groups=[staff] → A
groups=[staff, media] → A, B
groups=[staff, admin] → A + get_flow
groups=[media] → nothing (server list not cleared)
groups=[guest] → nothing
Server access empty:
groups=[media] → A, B
groups=[guest] → AПодключение остаётся доступно каждому, у кого валидный токен — initialize всегда проходит успешно, но инструменты, которые вызывающий не может полнед, не появляются в tools/list и в инструкции initialize. Прямой вызов tools/call на таком инструменте вернёт результат MCP с isError: true и поясняющим сообщением (а не низк-JSON-RPC протокольную ошибку), чтобы причина дошла до вызывающей модели, а не слилась в общее «выполнение инструмента не удалось».
Ос клиента: обязательный scope
Списки выше отвечают на вопрос что этому пользователю можно. Required scope отвечает на другой вопрос — а что выданное программными средствами авторизовано делать от имени пользователя, — и эти две проверки склеиваются по AND.
Они не взаимозаменяемы. Группа говорит, кто кидает клавиатуру; scope — сколько из прав конкретного человека делегировано программе, хранящей токен. Свчей в одно поле — и учитывается только одно из них: клиент, который разрешение иметь read-only scope, и под управлением пользователя, который имеет право писать, смог бы писать. Выданные грант должен ограничивать права пользователя, а не голосем их игнорировать.
Требуемый scope автоматически добавляется в scopes_supported — в поле scopes его не нужно повторять, — и может перечисляться в challenge WWW-Authenticate при ответе 401.
Собственно claim scope читается так, как описано в OAuth (RFC 6749 §3.3): строка, разделённая пробелами, либо массив, если провайдер его присылает. Имя claim не настраивается — это стандартный scope; для Microsoft Entra и Okta в качестве запасного варианта читается scp. Ввод самого поля — опечатанный через стропило список any-of; пустое означает «не ограничено», поэтому конфигурация без этого поля прежнего образа; настроенный scope, отсутствующий в токене, будет отклоняться, включая ваш токен вообще не в один pointпроц.
Обновление: admin gate больше не имеет отдельного поля имени claim — теперь он в этом соответствует заявок из вкладке Auth. Если вы ставили другое имя claim для админ-инструментов, перенесите его значение на вкладку Auth или правсоответственно правьте админ-список. Значение, которое буквально содержит запятую, теперь читается за список, а не literально одну строку. Поля гейта тем самым переименованы (
Required claim/Required value→Access claim/Server access/Admin access), но базовые настройки не из их поведения, поэтому действующие потоки продолжат работать без изменений.
Протокол
Конечная точка говорит по MCP-протоколу версии 2024-11-05 через простой HTTP POST — один запрос = одно JSON-RPC-сообщение, один ответ = одно JSON-ОТЕЛ. Поддерживаются initialize, tools/list, tools/call и ping; нет канала SSE/потоковый GET и никаких сообщений, инициатив которых со стороны сервера. Это вполановольческое подмножество, которое реально используется сегодняшними MCP-клиентами с OAuth (например, Claude) против сервера, только обслужива vehicles инструменты. Ученая объявляемая версия принудительно закрепляется, она не повторяет предложенную клиентом version.
Hostname filtering
Отключено по умолчанию. Когда включён параметр Only serve requests for this hostname, узек отвечает только на тот запрос, у которого заголовок Host соответствует hostname из Server URL. Благодаря этому несколько узлов mcp-server могут делить один и тот же path на одном экземпляре Node-RED — каждый отвечает только на собственный, виртуальный хэндлер — этот заменатель в зе ба которствует reverse proxy, указывающий несколько hostnames на одну базу Node-RED. Должен быть off для одного пальца, либо когдазерні реверсив — оставляет если reverse proxy rewrites Host.
Обратный прокси
Каждый узел mcp-server порождает отдельный ресурс OAuth: в отличие от единого общегоendpoint MCP, каждый интстант реigстрриирует лесобственные маршруты discovery и реigстрирование в базе собственного path. Для узла path: docker/Server URL: https://mcp.example.com это делает, поминует появиные:
Метод и путь | Назначение |
| JSON-RPC MCP-эндпоинт (защита bearer-токеном) |
| Метаданные ресурса (RFC 9728), форма с внутренним путём |
| Метаданные ресурса (RFC 9728), форма RFC 8414 |
| Метаданные сервера авторизации (RFC 8414), форма с внутренним путём |
`GET /.well-known/oauth-authorization-server/m |
Оба механизма намеренно остаются доступными. Клиенты выбирают в порядке, указанном в спецификации: сначала предварительно зарегистрированные, затем CIMD, затем DCR — поэтому клиент без поддержки CIMD продолжает использовать регистрационный шим точно так же, как и раньше. Какой механизм выбрал каждый клиент, видно из журнала: MCP CIMD client authenticated: <url> при первом обнаружении CIMD-клиента после перезапуска и MCP DCR fallback для клиента, который зарегистрировался, даже если IdP рекламирует CIMD. Вместе эти две строки охватывают каждого клиента, который обращается к серверу.
Токены от CIMD-клиента содержат URL этого документа в качестве аудитории, а не ваш предварительно зарегистрированный client id, и они принимаются всякий раз, когда IdP рекламирует CIMD. Этот узел не ведёт собственный второй список разрешений, поэтому список принятых метаданных документов IdP является границей — любой CIMD-клиент из этого списка может получить доступ к этому серверу, а проверка claims остаётся последним барьером.
Обе well-known формы рекламируются, потому что разные MCP-клиенты проверяют разные — публикуйте обе.
Поскольку маршруты каждого экземпляра используют формы /mcp/<path> и /.well-known/*/mcp/<path>,
один набор правил с подстановочными знаками покрывает все текущие и будущие узлы mcp-server (при условии,
что все они доступны через один и тот же домен/upstream) — изменение reverse-proxy не требуется при
добавлении нового path. Пример с использованием
Caddy через метки caddy-docker-proxy:
labels:
caddy_1: mcp.example.com
caddy_1.reverse_proxy_0: /mcp/* "{{upstreams 1880}}"
caddy_1.reverse_proxy_1: /.well-known/oauth-protected-resource/mcp/* "{{upstreams 1880}}"
caddy_1.reverse_proxy_2: /.well-known/oauth-authorization-server/mcp/* "{{upstreams 1880}}"Сам Node-RED возвращает 404 для любого пути, который не является фактически зарегистрированным маршрутом, поэтому подстановочный знак не открывает ничего сверх того, что уже регистрирует каждый развёрнутый узел mcp-server. Если path должен быть доступен на другом домене, чем остальные, дайте ему собственный блок сайта caddy_N (или комбинируйте с фильтрацией по имени хоста выше).
Что должен поддерживать поставщик удостоверений (те же требования, что и lib/mcp-auth.js):
OIDC-провайдер с обнаружением — конечные точки считываются из
‹issuerUrl›/.well-known/openid-configuration, с возвратом к структуре путей PocketID, если обнаружение недоступно.JWT access-токены, подписанные ключом, опубликованным в JWKS провайдера (токены проверяются локально; непрозрачные токены, поддерживающие только introspection, не поддерживаются).
Публичный клиент с PKCE (S256), типами предоставления
authorization_code+refresh_token, и redirect URI MCP-клиента в белом списке (для Claude.ai:https://claude.ai/api/mcp/auth_callback). Redirect URI настраиваются и проверяются только у поставщика удостоверений — узел больше не ведёт собственный список разрешений, поэтому поддержка подстановочных знаков IdP (например, у PocketID) работает как есть. Секреты клиента больше не поддерживаются: открытая конечная точка регистрации клиента передавала любой настроенный секрет каждому вызывающему, поэтому он никогда не мог быть действительно секретным. Если секрет всё ещё сохранён с более ранней версии, он игнорируется с предупреждением — переключите клиент IdP на публичный, затем откройте конфигурацию узла, нажмите Done и выполните развёртывание, чтобы удалить сохранённый секрет и снять предупреждение.
Протестировано с Caddy (reverse proxy) + PocketID (поставщик удостоверений) + Claude.ai и Hermes (MCP-клиенты). Любой соответствующий спецификации OIDC-провайдер, выдающий JWT access-токены, за любым reverse proxy, который пересылает указанные выше маршруты, должен работать аналогично.
Примеры
См. examples/ — девять готовых к импорту потоков (Jellyfin, Calibre, Docker,
Music Assistant, Radarr, iRobot/rest980, Overseerr, Sonarr, Spotify), каждый со своим
узлом mcp-server (описание сервера предзаполнено, Server URL/Identity provider
оставлены пустыми для заполнения) и инструментами mcp-in/mcp-out — хороший справочник
для подключения собственных инструментов.
Разработка
npm install
npm testЛицензия
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server for Argo RPG Platform — connects AI assistants to campaign data via OAuth2
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
MCP Hub: AI service discovery, per-user OAuth, and multi-service workflow orchestration
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/olykov/node-red-contrib-mcp-server-readonly'
If you have feedback or need assistance with the MCP directory API, please join our Discord server