@olykov/node-red-contrib-mcp-server-readonly
@olykov/node-red-contrib-mcp-server-readonly
面向 Node-RED 的通用 Model Context Protocol(MCP)服务器节点:将任意流程作为 MCP 工具暴露在受 OAuth 保护的端点之后,并可选提供 Node-RED 管理流程的只读检查。不绑定家庭自动化或任何其他领域——这是一个纯粹的构建模块,用于将 Node-RED 流程变成 AI 助手(Claude、Codex 等)可以调用的 MCP 工具。
0.5.0 中的破坏性变更——仅支持公共客户端(PKCE)。 客户端密钥和节点端重定向 URI 白名单已被移除:开放的客户端注册端点会把任何已配置的密钥交给每个调用者,而重定向 URI 反正是在身份提供方的
/authorize处验证的。迁移: 将 IdP 客户端切换为带 PKCE 的公共客户端(仍然保密的客户端会因invalid_client而无法完成令牌交换),确保 MCP 客户端回调 URL 在 IdP 处被列入白名单,如果节点警告存在已存储的密钥,请打开其配置,点击完成,然后部署以删除它。升级前已连接的 MCP 客户端可能缓存了旧注册信息——如果登录行为异常,请在客户端中移除并重新添加服务器。
节点
mcp-server(配置节点)——在POST /mcp/<path>上托管独立的 MCP JSON-RPC 端点,提供 OAuth 2.0 受保护资源发现(RFC 9728)、授权服务器发现(RFC 8414,代理真实的 OIDC 身份提供方),以及动态客户端注册垫片,使支持 OAuth 的 MCP 客户端(例如 Claude.ai)能够自行注册并完成认证。多个mcp-server节点可以共存,每个节点有自己的path和独立的认证配置。mcp-in—— 定义一个 MCP 工具(名称、描述、JSON-Schema 参数,以及可选的按工具访问门)。当 MCP 客户端调用该工具时,节点会发出携带调用参数的消息;将流程的其余部分连接起来以完成实际工作。msg.payload中的参数是不可信的调用者输入——JSON schema 只是给模型的文档,而非验证——因此流程在使用这些参数于 shell 命令、文件路径、URL 或查询之前,必须对其进行验证和转义。mcp-out—— 解析一个挂起的工具调用。将流程的末端连接到这里,保持msg._mcpCallId完整(来自源头的mcp-in消息),并将msg.payload设置为结果。
一条 mcp-in → ... → mcp-out 链就是一个 MCP 工具。针对同一个 mcp-server 节点构建任意数量的链,即可暴露一整套工具集。
Related MCP server: nr-mcp
管理只读 API 工具
在 mcp-server 节点上启用 Admin read-only API tools,可额外暴露一个基于 Node-RED 自身 Admin HTTP API 的工具,由可配置的 JWT 声明(默认:groups 包含 admin)进行门控:
get_flow—— 列出所有流程标签页(id、label、节点数量),或在使用id调用时返回单个标签页的完整 JSON。
配置 mcp-server 节点
General:名称、
path(→ 注册POST /mcp/<path>)、此 Node-RED 实例可访问的公共Server URL、可选的服务器名称/指令(显示给模型),以及可选的主机名过滤器(见下文)。Auth:一个 OIDC
Identity provider签发者 URL(必填——端点从/.well-known/openid-configuration自动发现,并支持 PocketID 风格的备用路径;留空会产生一个带有相对路径端点且无有效认证的损坏 OAuth 发现文档,因此编辑器不会让你在缺少它的情况下部署)、客户端 ID(IdP 客户端必须是带 PKCE 的公共客户端——不再支持客户端密钥,重定向 URI 仅在 IdP 处配置和验证)、作用域、令牌受众、一个可选的本地调试令牌(完全绕过 IdP 用于本地测试——在 Identity provider 中放入任何占位 URL 并依赖调试令牌——当调试令牌匹配时永远不会联系 IdP;调试用户获得的groups声明是可配置的,因此访问门也可以本地测试),以及Access claim/Server access门(见下文)。Admin:启用/禁用管理只读 API 工具、管理令牌(用于 Node-RED Admin API)、管理 API 端口,以及额外限制只读管理工具的
Read-only access门。
访问控制
一个声明名,多个值列表。 Auth 标签页上的 Access claim(默认 groups)指定了每个门匹配的单个 JWT 声明。所有其他授权字段都是该声明值的逗号分隔任意匹配列表——media, ops 表示声明包含其中至少一个值即通过。空列表表示不施加任何限制。
嵌套声明使用点分路径寻址,适用于不将角色放在令牌顶层的提供方:realm_access.roles 读取 Keycloak 的领域角色,任意深度均可。字面上存在的键总是优先,因此真正包含点的声明名仍会解析为自身。只有字符串和字符串数组才能匹配——将声明指向容器对象不会授予任何权限,而不是意外匹配。
字段 | 位置 | 限制范围 |
| mcp-server,Auth 标签页 | 此服务器上的每个工具 |
| mcp-in | 该工具,额外限制 |
| mcp-server,Admin 标签页 |
|
这些列表以 AND 组合。 到达一个工具意味着同时通过服务器的列表 和 该工具自身的列表。管理只读 API 工具并非特例——它们的字段就是 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 会以 isError: true 和解释性消息的 MCP 工具结果被拒绝(而非原始 JSON-RPC 协议错误),因此原因会到达调用模型,而不是被折叠成通用的“工具执行失败”。
客户端轴:必需作用域
上述列表回答的是 此用户能做什么。Required scope 回答的是另一个问题——此客户端被授权代表用户做什么——两者以 AND 方式检查。
它们不可互换。组声明说明谁在键盘前;作用域说明该人的多少权限被委托给了持有令牌的软件。将它们合并为一个字段只会导致只检查其中一个:一个被授予只读作用域的客户端,由可能具有写权限的人驱动,就会执行写操作。客户端的授权必须约束用户的权利,而不是被忽略。
必需作用域会自动添加到 scopes_supported 中,因此无需在作用域字段中重复,并且会在 401 响应中的 WWW-Authenticate 挑战中命名。
作用域声明按照 OAuth 的定义读取(RFC 6749 §3.3):一个空格分隔的字符串,或者如果提供方发送数组则为数组。声明名不可配置,因为它是标准化的;scp 作为 Microsoft Entra 和 Okta 的备用读取。该字段本身是一个逗号分隔的任意匹配列表。空表示无约束,因此从未填写它的安装不受影响;令牌未携带的已配置作用域会被拒绝,包括令牌完全没有作用域声明的情况。
升级: 管理门不再有自己的声明名字段——它像其他所有字段一样匹配 Auth 标签页的
Access claim。如果你曾为管理工具设置了 不同 的声明名,请将该值移到 Auth 标签页或相应调整管理列表。字面上包含逗号的值现在被读取为列表而不是单个字面字符串。门字段也已重新标记(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 通道,也没有服务器发起的消息。这是当今支持 OAuth 的 MCP 客户端(例如 Claude)针对纯工具服务器实际使用的子集。广告的版本是故意固定的,而不是回显客户端的提议。
主机名过滤
默认关闭。启用 Only serve requests for this hostname 时,节点仅响应 Host 头与其 Server URL 中的主机名匹配的请求。这允许多个 mcp-server 节点在一个 Node-RED 实例上共享 相同 的 path,每个节点只响应自己的虚拟主机——在反向代理为单个 Node-RED 后端提供多个主机名时很有用。对于单个服务器,或当反向代理重写 Host 头时,请保持关闭。
反向代理
每个 mcp-server 节点都是自己的 OAuth 资源——与单个共享 MCP 端点不同,每个实例都注册自己的发现和注册路由,范围限定在其 path 下。对于 path: docker 和 Server URL: https://mcp.example.com 的节点,存在以下六条路由:
方法及路径 | 用途 |
| JSON-RPC MCP 端点(受 bearer 令牌保护) |
| 资源元数据(RFC 9728),路径插入形式 |
| 资源元数据(RFC 9728),RFC 8414 形式 |
| 授权服务器元数据(RFC 8414),路径插入形式 |
| 授权服务器元数据(RFC 8414),RFC 8414 形式 |
| 动态客户端注册垫片 |
客户端 ID 元数据文档(CIMD)。 MCP 2026-07-28 弃用动态客户端注册,转而支持 CIMD,其中客户端的 ID 是其自行托管的元数据文档的 HTTPS URL。此节点通过镜像 IdP 发现文档的内容来通告 client_id_metadata_document_supported——它永远不会在此处配置,因为解析客户端 ID 的是 IdP,而此服务器无法承诺 IdP 不提供的支持。发现信息只获取一次,并在节点生命周期内缓存,因此在 IdP 上启用或禁用 CIMD 会在下次 Node-RED 重启或部署时生效——而不是实时生效。
DCR 垫片默认关闭,应保持关闭。 它只适用于一种情况:一个无法使用 CIMD 的客户端,与一个自身无法执行 DCR 的 IdP 通信。当它开启时,此服务器会通告自身为授权服务器,以便注册端点可被发现——这也意味着您的 IdP 返回的 iss 将与客户端记录的签发者不匹配,而强制实施 RFC 9207(MCP 2026-07-28 要求)的客户端将拒绝完成流程。关闭时,客户端会被直接引导到 IdP,并且必须使用 CIMD 或预先注册的客户端 ID。在此开关存在之前配置的节点会保持垫片开启,因为这是它一直在做的事情。
这两种机制都特意保留了下来。客户端按规范中的顺序进行选择——先预注册,再 CIMD,最后 DCR——因此不支持 CIMD 的客户端会像以前一样继续使用注册填充层。每个客户端采用了哪种机制,都可以从日志中读取:重启后首次看到 CIMD 客户端时会记录 MCP CIMD client authenticated: <url>,而即使 IdP 通告了 CIMD 仍进行注册的客户端则会记录 MCP DCR fallback。这两行日志合在一起,涵盖了到达该服务器的每一个客户端。
来自 CIMD 客户端的令牌会将该文档 URL 作为其受众,而不是您预先注册的客户端 ID,并且只要 IdP 通告了 CIMD,这些令牌就会被接受。此节点自身不维护第二个允许列表,因此 IdP 的已接受元数据文档列表就是边界——列表上的任何 CIMD 客户端都可以访问此服务器,而声明门控是剩余的检查。
两种 well-known 形式都会被通告,因为不同的 MCP 客户端会探测不同的形式——请同时暴露两者。由于每个实例的路由都共享 /mcp/<path> 和 /.well-known/*/mcp/<path> 的形状,一组通配符规则即可覆盖当前和未来的所有 mcp-server 节点(只要它们都能通过同一域名/上游访问)——添加新的 path 时无需更改反向代理。例如,通过 caddy-docker-proxy 标签使用 Caddy:
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 的路径布局。使用提供程序 JWKS 上发布的密钥签名的 JWT 访问令牌(令牌在本地验证;不支持不透明/仅限 introspection 的访问令牌)。
一个公共客户端,支持 PKCE (S256)、授权类型
authorization_code+refresh_token,并将 MCP 客户端的重定向 URI 列入白名单(对于 Claude.ai:https://claude.ai/api/mcp/auth_callback)。重定向 URI 仅在身份提供程序处配置和验证——节点不再维护自己的允许列表,因此 IdP 的通配符支持(例如 PocketID 的)可以原样工作。不再支持客户端密钥:开放的客户端注册端点会将任何已配置的密钥交给每个调用者,因此它实际上永远无法保密。如果早期版本仍存储了密钥,它将被忽略并显示警告——请将 IdP 客户端切换为公共,然后打开节点配置,单击“完成”并部署以删除存储的密钥并清除警告。
已使用 Caddy(反向代理)+ PocketID(身份提供程序)+ Claude.ai 和 Hermes(MCP 客户端)进行测试。任何符合规范、签发 JWT 访问令牌的 OIDC 提供程序,只要位于转发上述路由的任何反向代理之后,都应该以相同方式工作。
示例
请参阅 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 deployed
Maintenance
Related MCP Connectors
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
MCP server for Argo RPG Platform — connects AI assistants to campaign data via OAuth2
Related MCP Servers
- AlicenseDqualityFmaintenanceModel Context Protocol (MCP) server for Node-RED — allows language models (like Claude, GPT) to interact with Node-RED through a standardized API.20954 npm39MIT
- AlicenseAqualityDmaintenanceLets AI assistants interact with Node-RED to read flows, search nodes, edit function code, deploy changes safely, and manage modules.1329 PyPI1MIT
- AlicenseBqualityDmaintenanceModel Context Protocol (MCP) server for Node-RED that allows language models to interact with Node-RED through a standardized API.279 npm4MIT
- AlicenseNot gradedqualityBmaintenanceExposes Node-RED flows as MCP tools for AI assistants, with OAuth protection and optional admin tools for flow management.21 npm1ISC