diff --git a/packages/bb-plugin-agent-plugins/README.md b/packages/bb-plugin-agent-plugins/README.md index 1fedf6b..650151e 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 4511e56..a79719c 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")) && (