From 890d3406ddc599d0dc9b1ccc2e7d4cce992945ae Mon Sep 17 00:00:00 2001 From: shivammittal274 <56757235+shivammittal274@users.noreply.github.com> Date: Tue, 24 Mar 2026 03:08:08 +0530 Subject: [PATCH] feat: promote BrowserOS as MCP with UI improvements (#541) - Add MCP promo banner on AI providers page with "New" badge and "66+ tools" highlight, linking to /settings/mcp - Add Quick Setup section on MCP settings page with copy-paste commands for Claude Code, Gemini CLI, Codex, Claude Desktop, OpenClaw - Consolidate MCP settings: move restart button inline with server URL, remove separate MCP Server Settings card - Add analytics event for promo banner clicks --- .../app/ai-settings/AISettingsPage.tsx | 3 + .../app/ai-settings/McpPromoBanner.tsx | 57 ++++++ .../app/mcp-settings/MCPServerHeader.tsx | 113 +++++++++--- .../app/mcp-settings/MCPSettingsPage.tsx | 11 +- .../app/mcp-settings/QuickSetupSection.tsx | 162 ++++++++++++++++++ .../agent/lib/constants/analyticsEvents.ts | 4 + 6 files changed, 319 insertions(+), 31 deletions(-) create mode 100644 packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/McpPromoBanner.tsx create mode 100644 packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/QuickSetupSection.tsx diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/AISettingsPage.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/AISettingsPage.tsx index 2a846bd0d..62813fed5 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/AISettingsPage.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/AISettingsPage.tsx @@ -45,6 +45,7 @@ import { import type { IncompleteProvider } from './IncompleteProviderCard' import { IncompleteProvidersList } from './IncompleteProvidersList' import { LlmProvidersHeader } from './LlmProvidersHeader' +import { McpPromoBanner } from './McpPromoBanner' import { NewProviderDialog } from './NewProviderDialog' import { ProviderTemplatesSection } from './ProviderTemplatesSection' @@ -347,6 +348,8 @@ export const AISettingsPage: FC = () => { onAddProvider={handleAddProvider} /> + + { + const [dismissed, setDismissed] = useState(false) + const navigate = useNavigate() + + if (dismissed) return null + + const handleClick = () => { + track(MCP_PROMO_BANNER_CLICKED_EVENT) + navigate('/settings/mcp') + } + + return ( +
+
+ +
+
+

+ Use BrowserOS with Claude Code, Cursor & more + + (66+ tools) + + + + New + +

+

+ Connect your favorite coding tools to BrowserOS as an MCP server +

+
+ + +
+ ) +} diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPServerHeader.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPServerHeader.tsx index 208f861b8..47dad5064 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPServerHeader.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPServerHeader.tsx @@ -1,31 +1,40 @@ -import { Check, Copy, ExternalLink, Globe, Server } from 'lucide-react' -import { type FC, useState } from 'react' +import { + Check, + Copy, + ExternalLink, + Loader2, + RefreshCw, + Server, +} from 'lucide-react' +import { type FC, useCallback, useState } from 'react' +import { toast } from 'sonner' import { Button } from '@/components/ui/button' +import { MCP_SERVER_RESTARTED_EVENT } from '@/lib/constants/analyticsEvents' +import { sendServerMessage } from '@/lib/messaging/server/serverMessages' +import { track } from '@/lib/metrics/track' interface MCPServerHeaderProps { serverUrl: string | null isLoading: boolean error: string | null - title?: string - description?: string - remoteAccessEnabled?: boolean + onServerRestart?: () => void } const DOCS_URL = 'https://docs.browseros.com/features/use-with-claude-code' +const HEALTH_CHECK_TIMEOUT_MS = 60_000 +const HEALTH_CHECK_INTERVAL_MS = 2_000 export const MCPServerHeader: FC = ({ serverUrl, isLoading, error, - title = 'BrowserOS MCP Server', - description = 'Connect BrowserOS to MCP clients like claude code, gemini and others.', - remoteAccessEnabled = false, + onServerRestart, }) => { const [isCopied, setIsCopied] = useState(false) + const [isRestarting, setIsRestarting] = useState(false) const handleCopy = async () => { if (!serverUrl) return - try { await navigator.clipboard.writeText(serverUrl) setIsCopied(true) @@ -35,6 +44,57 @@ export const MCPServerHeader: FC = ({ } } + const checkServerHealth = useCallback(async (): Promise => { + try { + const result = await sendServerMessage('checkHealth', undefined) + return result.healthy + } catch { + return false + } + }, []) + + const handleRestart = async () => { + setIsRestarting(true) + try { + const { getBrowserOSAdapter } = await import('@/lib/browseros/adapter') + const { BROWSEROS_PREFS } = await import('@/lib/browseros/prefs') + const adapter = getBrowserOSAdapter() + await adapter.setPref(BROWSEROS_PREFS.RESTART_SERVER, true) + + const startTime = Date.now() + const waitForHealth = (): Promise => + new Promise((resolve) => { + const check = async () => { + if (Date.now() - startTime >= HEALTH_CHECK_TIMEOUT_MS) { + resolve(false) + return + } + if (await checkServerHealth()) { + resolve(true) + return + } + setTimeout(check, HEALTH_CHECK_INTERVAL_MS) + } + setTimeout(check, HEALTH_CHECK_INTERVAL_MS) + }) + + const healthy = await waitForHealth() + if (healthy) { + track(MCP_SERVER_RESTARTED_EVENT) + toast.success('Server restarted successfully') + onServerRestart?.() + } else { + toast.error('Server did not respond. Try restarting the browser.') + } + } catch (err) { + toast.error( + err instanceof Error ? err.message : 'Failed to restart server', + ) + } finally { + setIsRestarting(false) + } + } + return (
@@ -43,18 +103,21 @@ export const MCPServerHeader: FC = ({
-

{title}

+

BrowserOS MCP Server

- Setup a client + Docs
-

{description}

+

+ Connect BrowserOS to MCP clients like Claude Code, Gemini CLI and + others. +

@@ -76,6 +139,7 @@ export const MCPServerHeader: FC = ({ onClick={handleCopy} disabled={!serverUrl || isLoading} className="shrink-0" + title="Copy URL" > {isCopied ? ( @@ -83,19 +147,22 @@ export const MCPServerHeader: FC = ({ )} +
- - {remoteAccessEnabled && serverUrl && !isLoading && ( -
- -

- External access is enabled. To connect from another device, - replace 127.0.0.1 with this - machine's IP address. -

-
- )}
diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPSettingsPage.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPSettingsPage.tsx index 2e0cd9748..3a09c0c76 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPSettingsPage.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/mcp-settings/MCPSettingsPage.tsx @@ -4,7 +4,7 @@ import type { McpTool } from '@/lib/mcp/client' import { sendServerMessage } from '@/lib/messaging/server/serverMessages' import { MCPServerHeader } from './MCPServerHeader' import { MCPToolsSection } from './MCPToolsSection' -import { ServerSettingsCard } from './ServerSettingsCard' +import { QuickSetupSection } from './QuickSetupSection' /** @public */ export const MCPSettingsPage: FC = () => { @@ -12,8 +12,6 @@ export const MCPSettingsPage: FC = () => { const [urlLoading, setUrlLoading] = useState(true) const [urlError, setUrlError] = useState(null) - const [remoteAccessEnabled, setRemoteAccessEnabled] = useState(false) - const [tools, setTools] = useState([]) const [toolsLoading, setToolsLoading] = useState(false) const [toolsError, setToolsError] = useState(null) @@ -82,13 +80,10 @@ export const MCPSettingsPage: FC = () => { serverUrl={serverUrl} isLoading={urlLoading} error={urlError} - remoteAccessEnabled={remoteAccessEnabled} + onServerRestart={loadServerUrlAndTools} /> - + string + fileName?: string +} + +const clients: ClientConfig[] = [ + { + id: 'claude-code', + name: 'Claude Code', + type: 'command', + getSnippet: (url) => + `claude mcp add --transport http browseros ${url} --scope user`, + }, + { + id: 'gemini-cli', + name: 'Gemini CLI', + type: 'command', + getSnippet: (url) => + `gemini mcp add local-server ${url} --transport http --scope user`, + }, + { + id: 'codex', + name: 'Codex', + type: 'command', + getSnippet: (url) => `codex mcp add browseros ${url}`, + }, + { + id: 'claude-desktop', + name: 'Claude Desktop', + type: 'json', + fileName: 'claude_desktop_config.json', + getSnippet: (url) => + JSON.stringify( + { + mcpServers: { + browserOS: { + command: 'npx', + args: ['mcp-remote', url], + }, + }, + }, + null, + 2, + ), + }, + { + id: 'openclaw', + name: 'OpenClaw', + type: 'json', + fileName: 'openclaw.json', + getSnippet: (url) => + JSON.stringify( + { + mcpServers: { + browseros: { url }, + }, + }, + null, + 2, + ), + }, +] + +const CopyButton: FC<{ text: string }> = ({ text }) => { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { + // Clipboard API failed + } + } + + return ( + + ) +} + +export const QuickSetupSection: FC = ({ + serverUrl, +}) => { + if (!serverUrl) return null + + return ( +
+
+
+ +
+
+

Quick Setup

+

+ Copy and run the command for your tool +

+ + + + {clients.map((client) => ( + + {client.name} + + ))} + + + {clients.map((client) => { + const snippet = client.getSnippet(serverUrl) + return ( + +
+ {client.fileName && ( +

+ Add to{' '} + + {client.fileName} + +

+ )} +
+
+                        {client.type === 'command' && (
+                          $
+                        )}
+                        {snippet}
+                      
+ +
+
+
+ ) + })} +
+
+
+
+ ) +} diff --git a/packages/browseros-agent/apps/agent/lib/constants/analyticsEvents.ts b/packages/browseros-agent/apps/agent/lib/constants/analyticsEvents.ts index da4712b9f..817746513 100644 --- a/packages/browseros-agent/apps/agent/lib/constants/analyticsEvents.ts +++ b/packages/browseros-agent/apps/agent/lib/constants/analyticsEvents.ts @@ -67,6 +67,10 @@ export const QWEN_CODE_OAUTH_DISCONNECTED_EVENT = /** @public */ export const HUB_PROVIDER_ADDED_EVENT = 'settings.hub_provider.added' +/** @public */ +export const MCP_PROMO_BANNER_CLICKED_EVENT = + 'settings.mcp_promo_banner.clicked' + /** @public */ export const MCP_EXTERNAL_ACCESS_ENABLED_EVENT = 'settings.mcp_external_access.enabled'