mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-17 22:01:57 +08:00
feat: expose browser use to agents via MCP
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Download, ExternalLink, Globe, Loader2, MonitorPlay, Navigation, Pause, RefreshCw, Square } from 'lucide-react';
|
||||
import { Bot, Download, ExternalLink, Globe, Loader2, MonitorPlay, Navigation, Pause, RefreshCw, Share2, Square, X } from 'lucide-react';
|
||||
|
||||
import { Badge, Button } from '../../../shared/view/ui';
|
||||
import { authenticatedFetch } from '../../../utils/api';
|
||||
@@ -12,6 +12,7 @@ type BrowserUseStatus = {
|
||||
chromiumInstalled: boolean;
|
||||
installInProgress: boolean;
|
||||
sessionCount: number;
|
||||
agentToolsEnabled: boolean;
|
||||
mcpRecommended: boolean;
|
||||
message: string;
|
||||
};
|
||||
@@ -27,6 +28,9 @@ type BrowserUseSession = {
|
||||
updatedAt: string;
|
||||
lastAction: string | null;
|
||||
message: string | null;
|
||||
agentAccessEnabled: boolean;
|
||||
createdBy: 'user' | 'agent';
|
||||
profileName: string | null;
|
||||
};
|
||||
|
||||
type BrowserUsePanelProps = {
|
||||
@@ -112,6 +116,18 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
await readJson(response);
|
||||
});
|
||||
|
||||
const grantAgentAccess = () => runAction(async () => {
|
||||
if (!selectedSession) return;
|
||||
const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/agent-access/grant`, { method: 'POST' });
|
||||
await readJson(response);
|
||||
});
|
||||
|
||||
const revokeAgentAccess = () => runAction(async () => {
|
||||
if (!selectedSession) return;
|
||||
const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/agent-access/revoke`, { method: 'POST' });
|
||||
await readJson(response);
|
||||
});
|
||||
|
||||
const installRuntime = () => runAction(async () => {
|
||||
setIsInstalling(true);
|
||||
try {
|
||||
@@ -138,7 +154,7 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
Managed Playwright browser sessions with owner-scoped screenshots and navigation.
|
||||
Create browser sessions, watch agent activity, and decide which sessions agents may control.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -159,6 +175,11 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Runtime</div>
|
||||
<div className="mt-2 text-sm text-foreground">{status?.available ? 'Available' : 'Setup required'}</div>
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">{status?.message || 'Loading Browser Use status...'}</p>
|
||||
{status?.enabled && (
|
||||
<div className="mt-3 rounded-md border border-border/70 bg-background/60 px-2 py-2 text-xs text-muted-foreground">
|
||||
Agent tools: {status.agentToolsEnabled ? 'enabled' : 'disabled in settings'}
|
||||
</div>
|
||||
)}
|
||||
{canInstallRuntime && (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -178,6 +199,11 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
</div>
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="rounded-lg border border-border/70 bg-muted/30 p-3 text-xs leading-relaxed text-muted-foreground">
|
||||
Agents can create their own browser sessions when browser tools are enabled. Use
|
||||
<span className="font-medium text-foreground"> Give Agent Access </span>
|
||||
to let agents control a session you created, and revoke access whenever you want.
|
||||
</div>
|
||||
{sessions.map((session) => (
|
||||
<button
|
||||
key={session.id}
|
||||
@@ -192,6 +218,19 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
<span className="truncate font-medium">{session.title || session.url || 'Browser session'}</span>
|
||||
<Badge variant="outline" className="text-[10px]">{session.status}</Badge>
|
||||
</div>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{session.createdBy === 'agent' && (
|
||||
<span className="rounded border border-primary/30 px-1.5 py-0.5 text-[10px] text-primary">agent</span>
|
||||
)}
|
||||
{session.agentAccessEnabled && (
|
||||
<span className="rounded border border-emerald-500/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
|
||||
shared
|
||||
</span>
|
||||
)}
|
||||
{session.profileName && (
|
||||
<span className="rounded border border-border px-1.5 py-0.5 text-[10px]">profile: {session.profileName}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs">{session.url || session.message || session.id}</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -215,6 +254,17 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
<Navigation className="h-4 w-4" />
|
||||
Go
|
||||
</Button>
|
||||
{selectedSession?.agentAccessEnabled ? (
|
||||
<Button variant="outline" size="sm" onClick={revokeAgentAccess} disabled={isBusy || !selectedSession}>
|
||||
<X className="h-4 w-4" />
|
||||
Revoke Agent
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" size="sm" onClick={grantAgentAccess} disabled={isBusy || !selectedSession || !status?.agentToolsEnabled}>
|
||||
<Share2 className="h-4 w-4" />
|
||||
Give Agent Access
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" size="sm" disabled>
|
||||
<Pause className="h-4 w-4" />
|
||||
Pause
|
||||
@@ -236,6 +286,12 @@ export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) {
|
||||
<div className="flex items-center gap-2 border-b border-border/60 px-3 py-2 text-xs text-muted-foreground">
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
<span className="truncate">{selectedSession?.url || 'No page loaded'}</span>
|
||||
{selectedSession?.agentAccessEnabled && (
|
||||
<span className="ml-auto inline-flex items-center gap-1 rounded border border-emerald-500/30 px-2 py-0.5 text-emerald-600 dark:text-emerald-300">
|
||||
<Bot className="h-3.5 w-3.5" />
|
||||
Agent access active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex min-h-[360px] flex-1 items-center justify-center bg-neutral-950">
|
||||
{selectedSession?.screenshotDataUrl ? (
|
||||
|
||||
@@ -10,6 +10,7 @@ import SettingsToggle from '../../SettingsToggle';
|
||||
|
||||
type BrowserUseSettings = {
|
||||
enabled: boolean;
|
||||
agentToolsEnabled: boolean;
|
||||
};
|
||||
|
||||
type BrowserUseStatus = {
|
||||
@@ -19,6 +20,7 @@ type BrowserUseStatus = {
|
||||
playwrightInstalled: boolean;
|
||||
chromiumInstalled: boolean;
|
||||
installInProgress: boolean;
|
||||
agentToolsEnabled: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
@@ -31,7 +33,7 @@ async function readJson<T>(response: Response): Promise<T> {
|
||||
}
|
||||
|
||||
export default function BrowserUseSettingsTab() {
|
||||
const [settings, setSettings] = useState<BrowserUseSettings>({ enabled: false });
|
||||
const [settings, setSettings] = useState<BrowserUseSettings>({ enabled: false, agentToolsEnabled: false });
|
||||
const [status, setStatus] = useState<BrowserUseStatus | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -57,13 +59,13 @@ export default function BrowserUseSettingsTab() {
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [loadState]);
|
||||
|
||||
const updateEnabled = async (enabled: boolean) => {
|
||||
const updateSettings = async (nextSettings: Partial<BrowserUseSettings>) => {
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await authenticatedFetch('/api/browser-use/settings', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ enabled }),
|
||||
body: JSON.stringify(nextSettings),
|
||||
});
|
||||
const data = await readJson<{ data: { settings: BrowserUseSettings } }>(response);
|
||||
setSettings(data.data.settings);
|
||||
@@ -105,12 +107,24 @@ export default function BrowserUseSettingsTab() {
|
||||
>
|
||||
<SettingsToggle
|
||||
checked={settings.enabled}
|
||||
onChange={(value) => void updateEnabled(value)}
|
||||
onChange={(value) => void updateSettings({ enabled: value })}
|
||||
ariaLabel="Enable Browser Use"
|
||||
disabled={isLoading || isSaving}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
label="Enable Browser Tools for Agents"
|
||||
description="Register the Browser Use MCP server for all agent providers. Agents can create browser sessions and control sessions shared with agents."
|
||||
>
|
||||
<SettingsToggle
|
||||
checked={settings.agentToolsEnabled}
|
||||
onChange={(value) => void updateSettings({ agentToolsEnabled: value })}
|
||||
ariaLabel="Enable Browser Tools for Agents"
|
||||
disabled={isLoading || isSaving || !settings.enabled}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<div className="space-y-4 px-4 py-4">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0 space-y-1">
|
||||
|
||||
Reference in New Issue
Block a user