From 1c243f86de60e606c592dc77467903cc9ff555b8 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:58:01 +0530 Subject: [PATCH] fix(tui_gateway): relay RFC 9207 iss through the oauth.callback RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The oauth.callback handler parsed `iss` but never passed it to deliver_callback_flow, and McpOauthCallbackParams (extra="forbid") had no `iss` field, so the desktop renderer sending `iss: null` was rejected with 4000 "unknown key" — breaking every Desktop→remote-gateway MCP OAuth login. Add the field, forward it, and regenerate the OpenRPC/TS contract artifacts via scripts/gen_gateway_contracts.py. Also update tests/hermes_cli/test_mcp_dashboard_oauth.py for the 3-tuple callback shape introduced by the cherry-picked commit (it was red on the stack). --- apps/shared/src/gateway-contract.generated.ts | 1 + apps/shared/src/gateway-contract.openrpc.json | 12 ++++++++++++ tests/hermes_cli/test_mcp_dashboard_oauth.py | 2 +- .../test_mcp_oauth_client_callback.py | 17 +++++++++++++++++ tui_gateway/contracts/tools_mcp_plugins.py | 2 ++ tui_gateway/methods_tools.py | 3 ++- 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/shared/src/gateway-contract.generated.ts b/apps/shared/src/gateway-contract.generated.ts index d301ccb748..e3b1baa0f9 100644 --- a/apps/shared/src/gateway-contract.generated.ts +++ b/apps/shared/src/gateway-contract.generated.ts @@ -3610,6 +3610,7 @@ export interface McpOauthCallbackParams { code?: string | null state?: string | null error?: string | null + iss?: string | null } export interface McpOauthCallbackResult { ok: boolean diff --git a/apps/shared/src/gateway-contract.openrpc.json b/apps/shared/src/gateway-contract.openrpc.json index c92821e7bc..76bad2cddc 100644 --- a/apps/shared/src/gateway-contract.openrpc.json +++ b/apps/shared/src/gateway-contract.openrpc.json @@ -13814,6 +13814,18 @@ ], "default": null, "title": "Error" + }, + "iss": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Iss" } }, "required": [ diff --git a/tests/hermes_cli/test_mcp_dashboard_oauth.py b/tests/hermes_cli/test_mcp_dashboard_oauth.py index 91f30ec4f0..c4df8eeeab 100644 --- a/tests/hermes_cli/test_mcp_dashboard_oauth.py +++ b/tests/hermes_cli/test_mcp_dashboard_oauth.py @@ -85,7 +85,7 @@ def test_hosted_callback_bypasses_gated_cookie_auth(monkeypatch): ) assert response.status_code == 200 - assert flow._callback == ("abc", "expected") + assert flow._callback == ("abc", "expected", None) def test_hosted_auth_allows_same_server_name_in_different_profiles(tmp_path, monkeypatch): diff --git a/tests/tui_gateway/test_mcp_oauth_client_callback.py b/tests/tui_gateway/test_mcp_oauth_client_callback.py index 6ca187ec10..17e99c13b3 100644 --- a/tests/tui_gateway/test_mcp_oauth_client_callback.py +++ b/tests/tui_gateway/test_mcp_oauth_client_callback.py @@ -201,6 +201,23 @@ def test_deliver_callback_forwards_iss(): assert flow._callback == ("abc", "s3cr3tstate", "https://as.example.com") +def test_oauth_callback_rpc_relays_iss(): + """The gateway ``mcp.servers.oauth.callback`` RPC accepts ``iss`` under the extra=forbid contract + and forwards it to the flow; the desktop renderer always sends the key (possibly null).""" + import tui_gateway.server as srv + from tui_gateway.contracts import registry as contracts + + flow = _make_session() + contract = contracts.METHODS["mcp.servers.oauth.callback"] + params = {"session_id": "sess-relay-1", "name": "hosp", "code": "abc", "state": "s3cr3tstate", + "iss": "https://as.example.com"} + params, problem = contracts.validate_params(contract, params) + assert problem is None + out = srv._methods["mcp.servers.oauth.callback"](1, params) + assert out["result"]["ok"] is True + assert flow._callback == ("abc", "s3cr3tstate", "https://as.example.com") + + def test_loopback_listener_forwards_iss(): """The gateway-hosted loopback listener parses ``iss`` off the redirect rather than dropping it.""" import urllib.request diff --git a/tui_gateway/contracts/tools_mcp_plugins.py b/tui_gateway/contracts/tools_mcp_plugins.py index e648867f89..7e6ef383e8 100644 --- a/tui_gateway/contracts/tools_mcp_plugins.py +++ b/tui_gateway/contracts/tools_mcp_plugins.py @@ -530,6 +530,8 @@ class McpOauthCallbackParams(McpOauthFlowParams): code: str | None = None state: str | None = None error: str | None = None + # RFC 9207 issuer; extra="forbid" would otherwise 4000 the desktop relay that always sends it. + iss: str | None = None class McpOauthCallbackResult(Result): diff --git a/tui_gateway/methods_tools.py b/tui_gateway/methods_tools.py index 044598c46f..80776f3a49 100644 --- a/tui_gateway/methods_tools.py +++ b/tui_gateway/methods_tools.py @@ -1369,7 +1369,8 @@ def _(rid, params: dict) -> dict: code, state, error, iss = (str(params.get(k) or "") or None for k in ("code", "state", "error", "iss")) deliver = _tools_mod("tui_gateway.mcp_oauth_sessions").deliver_callback_flow return _ok(rid, deliver( - _str_arg(params, "session_id"), _str_arg(params, "name"), code=code, state=state, error=error)) + _str_arg(params, "session_id"), _str_arg(params, "name"), code=code, state=state, error=error, + iss=iss)) # ─── Plugins ─────────────────────────────────────────────────────────────────