outsystems-mcp-relay

outsystems-mcp-relay

Enables MCP clients to connect to remote servers that have OAuth issuer mismatches (e.g., OutSystems) by relaying stdio and handling OAuth flows with optional issuer override.

Category
访问服务器

README

outsystems-mcp-relay

A lightweight, generic stdio → remote MCP relay with OAuth, plus an RFC 9207 issuer override for remote servers whose published OAuth metadata doesn't match the authorization response. Zero runtime dependencies. One file.

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

Why this exists

Some remote MCP deployments are a reverse proxy in front of Keycloak (the OutSystems Developer Cloud MCP gateway is one). They publish OAuth metadata whose issuer is the proxy URL (e.g. https://<tenant>/mcp), but the authorization server stamps its real issuer in the authorization-response iss parameter (e.g. https://<tenant>/auth/realms/<realm>).

RFC 9207-compliant clients must reject that mismatch, so OAuth login fails on every harness — Claude Code, pi, Cursor, Codex, you name it. This relay lets you validate iss against the true backend issuer while keeping every other OAuth check strict. For normal servers it behaves like a plain relay.

When to use this

Try the official direct connection first — point your harness straight at the remote MCP URL, no relay in between. Only reach for this relay if that fails with the RFC 9207 issuer-mismatch error above.

This exists solely to work around that one server-side bug. It doesn't do anything better than the official path once the bug isn't there — so if OutSystems fixes it tenant-wide, or your tenant never hit it in the first place, drop the relay and connect directly. The relay tells you when that's the case: on a successful login it checks whether any issuer correction was actually needed, and if not, prints a note to stderr saying so. Don't wait for a "does it still need it" review — if you see that note, switch back to the official direct connection right away.

Install

Requires Node.js ≥ 20. No dependencies — just the file.

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

You do not need to clone this repo to use the relay. Install from npm (or use npx) and you're done. Clone it only to audit the source (a single ~500-line file) or contribute.

Usage

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

Generic example (normal remote server)

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

OutSystems example (issuer mismatch)

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

No --expected-issuer needed — the relay auto-detects the real authorization-server issuer from the authorize redirect (Keycloak realm setups). Only add --expected-issuer https://<tenant>.outsystems.dev/auth/realms/<realm-id> if auto-detection fails (a non-Keycloak server): the OAuth error message prints the realm — its received value is the value to use. Auto-detection only adopts a cross-origin candidate when its issuer is confirmed by the realm's own metadata (reachable through the origin allowlist); for a genuinely separate IdP host, pass --expected-issuer (or --as-metadata-url to pre-allowlist it).

On the first call the relay opens a browser for sign-in (or prints the URL with --no-open). Tokens are cached and refreshed automatically.

Install via an AI assistant (paste this)

Paste into Claude Code, pi, Cursor, or any AI harness:

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 quickstart (OutSystems issuer mismatch)

The error you're seeing looks like this:

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

In a terminal (not inside 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

Then restart Claude Code. On the first OutSystems tool call the relay opens a browser for sign-in (add --no-open if you'd rather paste the URL). Tokens are cached, so later sessions skip sign-in. Verify with /mcp (server should be connected) and a simple "list my environments".

You don't need to hunt for the realm issuer. The relay auto-detects it from the authorize redirect. If auto-detection can't (a non-Keycloak server), the error message prints it: the received value in the error IS the --expected-issuer value.

How it works

  • Protocol-agnostic passthrough: reads newline-delimited JSON-RPC from stdin, POSTs each frame verbatim to the remote server, writes the JSON-RPC response back to stdout. No tool semantics live here — works for tools, resources, prompts, anything.
  • Handles Streamable HTTP details: Mcp-Session-Id echo, direct-JSON responses, and 202/text/event-stream responses (SSE reassembly).
  • OAuth: discovers the authorization-server metadata, dynamically registers a public client (PKCE S256), opens the browser, validates state and iss, exchanges the code, refreshes tokens on 401. --expected-issuer sets the issuer iss is validated against — the fix for proxy/Keycloak mismatches.
  • Requests are serialized (no interleaved responses on stdout).

Security

  • RFC 9207 enforced: iss is validated only when the authorization server actually sends it (absent = the AS doesn't implement RFC 9207, no check; present = strict string match against the expected issuer). --expected-issuer opts into a different expected value — it never disables validation.
  • Origin allowlist: the relay only contacts the configured remote origin (and an explicitly provided --as-metadata-url). Redirects are walked manually and every hop is allowlisted (307/308 preserve the request body; 301/302/303 downgrade to GET per HTTP semantics), and Authorization/Cookie are stripped when a redirect changes origin (matching native fetch). No SSRF.
  • PKCE S256 + random state (validated) + localhost-only callback server on an ephemeral port.
  • Never logs secrets: tokens and authorization codes never appear in output (all diagnostics go to stderr; stdout carries protocol messages only).
  • Tokens are stored at ~/.mcp-auth/outsystems-mcp-relay-<sha1(url)>.json with 0600 permissions — the ecosystem convention (same store shape as mcp-remote). OS-keychain storage is a planned enhancement; see Non-goals.

Testing

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

Troubleshooting

Symptom Fix
Issuer mismatch ... expected "…/mcp", received "…/auth/realms/…" Normally auto-detection handles this with zero flags. If it can't, pass the received URL as --expected-issuer — the error prints it for you
authentication failed after a long idle The cached token expired and refresh failed. Re-run with --force (or delete the relay's file in ~/.mcp-auth/) to re-authenticate
Browser never opens Add --no-open — the relay prints the authorization URL to paste into a browser
Dynamic client registration fails The server's registration endpoint is restricted (e.g. Keycloak's Trusted-Hosts policy). If it's the OutSystems proxy this shouldn't happen; otherwise register a client yourself and pass --client-id
Something else Open an issue with the full error text (all diagnostics go to stderr — redact any tokens)

Non-goals (v1)

  • OS-keychain token storage (file with 0600 perms for now)
  • Multi-server aggregation / management (use a gateway for that)
  • Server-initiated notification streaming beyond passthrough
  • Custom CA flags

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选