From 16d6dce2a2a87ce243c703c21a41531030f683ef Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Mon, 5 Oct 2026 10:47:44 -0700 Subject: [PATCH] Support pasted MCP OAuth callbacks for remote BB --- packages/bb-plugin-agent-plugins/README.md | 2 + packages/bb-plugin-agent-plugins/app.tsx | 51 +++++++++++++++++++ packages/bb-plugin-agent-plugins/server.ts | 13 +++-- .../bb-plugin-agent-plugins/src/app.test.tsx | 38 ++++++++++++++ .../src/callback-url.test.ts | 33 ++++++++++++ .../src/callback-url.ts | 31 +++++++++++ .../src/oauth.integration.test.ts | 4 ++ .../src/server.test.ts | 14 +++++ 8 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 packages/bb-plugin-agent-plugins/src/callback-url.test.ts create mode 100644 packages/bb-plugin-agent-plugins/src/callback-url.ts diff --git a/packages/bb-plugin-agent-plugins/README.md b/packages/bb-plugin-agent-plugins/README.md index 1fedf6ba..650151e0 100644 --- a/packages/bb-plugin-agent-plugins/README.md +++ b/packages/bb-plugin-agent-plugins/README.md @@ -38,6 +38,8 @@ bb plugin install git:https://github.com/patleeman/bb-plugins.git --subdirectory The MCP client delegates authentication to the official SDK, including protected-resource/authorization-server discovery, dynamic client registration, PKCE, refresh, `WWW-Authenticate`, insufficient-scope step-up, and OAuth callback completion. Tokens and PKCE state are stored in a BB secret setting; they are never placed in the database snapshot or frontend payload. +For OAuth-protected MCP servers such as Fastmail, the registered redirect uses `http://localhost` on BB's port. If BB runs on another machine, sign in with your normal browser, then copy the **full** localhost URL from its address bar after the connection fails and paste it into the matching server's **Finish sign-in** field in BB. The URL contains a short-lived, one-use authorization code: paste it only into BB, never into chat or logs. BB verifies the callback URL, state and PKCE grant before exchanging the code. If the browser runs on the BB host or you have a port forward, the callback completes normally without pasting. Fastmail's MCP dynamic registration does not accept arbitrary public HTTPS redirect URIs; setting `BB_APP_URL` does not change this callback. + This v0 release installs one package instance at a time. Remove an existing plugin before installing the same package name again; removal keeps its data unless you choose to purge it. diff --git a/packages/bb-plugin-agent-plugins/app.tsx b/packages/bb-plugin-agent-plugins/app.tsx index 4511e56b..a79719cb 100644 --- a/packages/bb-plugin-agent-plugins/app.tsx +++ b/packages/bb-plugin-agent-plugins/app.tsx @@ -277,6 +277,32 @@ function InstallBar({ onDone }: { onDone: () => void }) { ); } +function CallbackPasteForm({ + pluginId, serverId, busy, onFinish, +}: { + pluginId: string; + serverId: string; + busy: boolean; + onFinish: (url: string) => Promise; +}) { + const [callbackUrl, setCallbackUrl] = useState(""); + return ( +
{ + event.preventDefault(); + if (!callbackUrl.trim()) return; + void onFinish(callbackUrl.trim()).then((completed) => { if (completed) setCallbackUrl(""); }); + }}> + +
+ setCallbackUrl(event.target.value)} autoComplete="off" spellCheck={false} disabled={busy} /> + +
+
+ ); +} + function PluginRow({ plugin, update, @@ -288,6 +314,7 @@ function PluginRow({ onReconnect, onReauthorize, onDisconnect, + onFinishAuthentication, onSkillEnabledChange, onMcpEnabledChange, onUpdate, @@ -303,6 +330,7 @@ function PluginRow({ onReconnect: (p: string, s: string) => void; onReauthorize: (p: string, s: string) => void; onDisconnect: (p: string, s: string) => void; + onFinishAuthentication: (p: string, s: string, url: string) => Promise; onSkillEnabledChange: (pluginId: string, skillName: string, enabled: boolean) => void; onMcpEnabledChange: (pluginId: string, serverId: string, enabled: boolean) => void; onUpdate: (pluginId: string) => void; @@ -476,6 +504,9 @@ function PluginRow({ {authStatus === "authorizing" ? "Continue authentication" : "Authenticate"} {srv.serverId} )} + {canManageAuth && srv.enabled && authStatus === "authorizing" && ( + onFinishAuthentication(plugin.id, srv.serverId, url)} /> + )} {canManageAuth && srv.enabled && (srv.status === "ready" || srv.status === "error" || (srv.status === "idle" && authStatus === "authenticated")) && (