Skip to main content
Glama
izambasiron

outsystems-mcp-relay

by izambasiron

outsystems-mcp-relay

一个轻量级、通用的 stdio → 远程 MCP 中继,支持 OAuth,并带有 RFC 9207 签发者覆盖功能,用于远程服务器的已发布 OAuth 元数据与授权响应不匹配的情况。零运行时依赖。单文件。

stdio (your MCP client)  ⇄  outsystems-mcp-relay  ⇄  remote MCP server (Streamable HTTP)

为什么需要这个

某些远程 MCP 部署是 Keycloak 前面的反向代理(OutSystems Developer Cloud MCP 网关就是其中之一)。它们发布的 OAuth 元数据中 issuer 是代理 URL(例如 https://<tenant>/mcp),但授权服务器在授权响应的 iss 参数中盖的是其真实签发者(例如 https://<tenant>/auth/realms/<realm>)。

符合 RFC 9207 的客户端必须拒绝这种不匹配,因此 OAuth 登录在几乎所有工具链上都会失败——Claude Code、pi、Cursor、Codex,无一例外。这个中继让你可以针对真实后端签发者验证 iss,同时保持所有其他 OAuth 检查严格不变。对于正常服务器,它的行为就像一个普通中继。

Related MCP server: mcp-auth-proxy

何时使用

先尝试官方直连方式——将你的工具链直接指向远程 MCP URL,中间不经过中继。只有当直连因上述 RFC 9207 签发者不匹配错误而失败时,才考虑使用这个中继。

它的存在仅仅是为了绕开那一个服务端 bug。一旦该 bug 不存在,它并不会比官方路径做得更好——所以如果 OutSystems 在租户范围内修复了它,或者你的租户从一开始就没遇到这个问题,就放弃中继,直接连接。中继会告诉你何时属于这种情况:在成功登录时,它会检查是否确实需要签发者修正,如果不需要,会向 stderr 打印一条说明。不要等着做"是否还需要它"的复查——如果你看到那条说明,立即切回官方直连。

安装

需要 Node.js ≥ 20。无依赖——只有这一个文件。

npm install -g outsystems-mcp-relay      # recommended
# or, without a global install:
npx outsystems-mcp-relay <remote-url> ...

使用中继不需要克隆此仓库。 从 npm 安装(或使用 npx)即可。克隆仅用于审计源码(一个约 500 行的单文件)或贡献代码。

用法

outsystems-mcp-relay <remote-url> [options]

  --as-metadata-url <url>   OAuth AS metadata URL (default: discover from remote-url)
  --expected-issuer <url>   Override the RFC 9207 expected issuer (the proxy fix)
  --client-id <id>          Pre-registered client id (skips dynamic registration)
  --bearer <token>          Static bearer token mode (skips OAuth entirely)
  --force                   Ignore cached tokens and re-authenticate
  --no-open                 Print the authorization URL instead of opening a browser
  --help                    Show help

通用示例(普通远程服务器)

// mcp.json
{
  "mcpServers": {
    "my-remote": {
      "command": "outsystems-mcp-relay",
      "args": ["https://api.example.com/mcp"]
    }
  }
}

OutSystems 示例(签发者不匹配)

{
  "mcpServers": {
    "outsystems": {
      "command": "outsystems-mcp-relay",
      "args": ["https://<tenant>.outsystems.dev/mcp"]
    }
  }
}

无需 --expected-issuer——中继会从授权重定向中自动检测真实的授权服务器签发者(Keycloak realm 场景)。只有当自动检测失败时(非 Keycloak 服务器),才添加 --expected-issuer https://<tenant>.outsystems.dev/auth/realms/<realm-id>:OAuth 错误消息会打印 realm——其收到的值就是要使用的值。自动检测只会在跨源候选者的签发者被 realm 自身的元数据(可通过源允许列表访问)确认后才采用;对于真正独立的 IdP 主机,请传入 --expected-issuer(或 --as-metadata-url 以预先加入允许列表)。

首次调用时,中继会打开浏览器进行登录(或使用 --no-open 打印 URL)。令牌会被缓存并自动刷新。

通过 AI 助手安装(粘贴此内容)

粘贴到 Claude Code、pi、Cursor 或任何 AI 工具链中:

Install the OutSystems MCP fix (outsystems-mcp-relay). The official OutSystems
MCP fails OAuth on every AI harness with an RFC 9207 issuer mismatch — a
server-side bug in the gateway's Keycloak setup.
Step 1: run `npm install -g outsystems-mcp-relay`.
Step 2: ask me for my OutSystems tenant hostname (something like
`mycompany.outsystems.dev`).
Step 3: when I tell you, replace the broken server:
  `claude mcp remove outsystems`
  `claude mcp add -s user --transport stdio outsystems -- outsystems-mcp-relay https://<my-tenant>/mcp`
  (substitute my actual tenant. No other flags needed — the relay auto-detects
  the real authorization-server issuer; if auto-detection can't, ask me to paste
  the OAuth error and use its "received" URL as `--expected-issuer`.)
Step 4: tell me to restart Claude Code, then ask anything OutSystems-related.
The first tool call opens a browser for sign-in (or prints the URL with
`--no-open`).

Claude Code 快速入门(OutSystems 签发者不匹配)

你看到的错误是这样的:

Issuer mismatch in authorization response (RFC 9207):
expected "https://<tenant>.outsystems.dev/mcp",
received "https://<tenant>.outsystems.dev/auth/realms/<realm-id>"

在终端中(不是在 Claude Code 内部):

npm install -g outsystems-mcp-relay

# 1. remove the broken HTTP entry
claude mcp remove outsystems

# 2. add the relay as a local stdio server — no other flags needed: it
#    auto-detects the real authorization-server issuer
claude mcp add -s user --transport stdio outsystems -- \
  outsystems-mcp-relay \
  https://<tenant>.outsystems.dev/mcp

然后重启 Claude Code。在首次调用 OutSystems 工具时,中继会打开浏览器进行登录(如果更愿意粘贴 URL,可添加 --no-open)。令牌会被缓存,因此后续会话会跳过登录。用 /mcp 验证(服务器应显示已连接),并执行一个简单的"列出我的环境"。

你不需要去寻找 realm 签发者。 中继会从授权重定向中自动检测它。如果自动检测无法完成(非 Keycloak 服务器),错误消息会打印出来:错误中的收到的值就是 --expected-issuer 的值。

工作原理

  • 协议无关的透传:从 stdin 读取换行分隔的 JSON-RPC,将每个帧原样 POST 到远程服务器,将 JSON-RPC 响应写回 stdout。这里不包含任何工具语义——适用于工具、资源、提示词等一切内容。

  • 处理 Streamable HTTP 细节:Mcp-Session-Id 回显、直接 JSON 响应,以及 202/text/event-stream 响应(SSE 重组)。

  • OAuth:发现授权服务器元数据,动态注册公共客户端(PKCE S256),打开浏览器,验证 state 和 iss,交换授权码,在 401 时刷新令牌。--expected-issuer 设置 iss 的验证目标——这是针对代理/Keycloak 不匹配的修复方案。

  • 请求被串行化(stdout 上不会出现交错响应)。

安全性

  • 强制执行 RFC 9207:仅当授权服务器实际发送 iss 时才进行验证(缺失 = AS 未实现 RFC 9207,不检查;存在 = 与预期签发者进行严格字符串匹配)。 --expected-issuer 选择不同的预期值——它绝不会禁用验证。

  • 源允许列表:中继只联系配置的远程源(以及显式提供的 --as-metadata-url)。重定向被手动遍历,每一跳都在允许列表中(307/308 保留请求体;301/302/303 按 HTTP 语义降级为 GET),并且当重定向改变源时,Authorization/Cookie 会被剥离(与原生 fetch 一致)。无 SSRF。

  • PKCE S256 + 随机 state(已验证)+ 仅限 localhost 的回调服务器,使用临时端口。

  • 绝不记录机密:令牌和授权码永远不会出现在输出中(所有诊断信息都发送到 stderr;stdout 只承载协议消息)。

  • 令牌存储在 ~/.mcp-auth/outsystems-mcp-relay-<sha1(url)>.json,权限为 0600—— 这是生态系统的惯例(与 mcp-remote 的存储格式相同)。操作系统钥匙串存储是计划中的增强功能;参见非目标。

测试

npm test                # mock-server protocol test (passthrough, session-id, SSE, 401)
npm run test:e2e -- <remote-url> --expected-issuer <issuer>   # real-tenant round trip

故障排查

症状

修复

Issuer mismatch ... expected "…/mcp", received "…/auth/realms/…"

通常自动检测无需任何标志即可处理。如果无法处理,请将收到的 URL 作为 --expected-issuer 传入——错误消息会为你打印出来

长时间空闲后 authentication failed

缓存的令牌已过期且刷新失败。使用 --force 重新运行(或删除 ~/.mcp-auth/ 中中继的文件)以重新认证

浏览器从未打开

添加 --no-open——中继会打印授权 URL,供你粘贴到浏览器中

动态客户端注册失败

服务器的注册端点受限(例如 Keycloak 的 Trusted-Hosts 策略)。如果是 OutSystems 代理,这不应发生;否则请自行注册客户端并传入 --client-id

其他问题

提交 issue,附上完整错误文本(所有诊断信息都发送到 stderr——请对任何令牌进行脱敏)

非目标(v1)

  • 操作系统钥匙串令牌存储(目前使用 0600 权限的文件)

  • 多服务器聚合/管理(请使用网关)

  • 超出透传范围的服务器发起通知流

  • 自定义 CA 标志

许可证

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Local stdio proxy for Uno MCP Gateway that enables MCP clients without OAuth support to securely connect to authenticated remote servers.
    8
    21 PyPI
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Bridges stdio-based LLM harnesses to OAuth-protected remote MCP servers via Streamable HTTP, handling PKCE browser login and token refresh automatically.
    5 npm
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Bridge that lets stdio-only MCP clients connect to remote MCP servers with OAuth and other auth support, enabling local clients to use remote, authorized MCP servers.
    19 npm
    54
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables local MCP clients to access tools from a remote FastMCP server over stdio, handling OAuth authentication and Streamable HTTP communication.
    -