From c7c8a2a567f6fe021803604edafca4015d2ac59b Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Wed, 8 Apr 2026 18:21:19 -0400 Subject: [PATCH 1/3] feat(desktop): add SPROUT_TOOLSETS configuration to agent setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toolsets like canvas, channel_admin, dms, and workflow_admin could only be enabled by setting SPROUT_TOOLSETS in the shell environment before launching the desktop app — with no way to configure them per-agent in the UI. This made multi-agent setups where different agents need different tool scopes impractical. Adds an mcp_toolsets field to ManagedAgentRecord, CreateManagedAgentRequest, and UpdateManagedAgentRequest. The runtime reads this field and forwards it as SPROUT_TOOLSETS to the spawned MCP subprocess, overriding (or removing) any ambient env var. A comma-separated text input appears in the Advanced Setup section of the create-agent dialog, placed next to the MCP command field, with a help line listing the available toolset names. --- desktop/scripts/check-file-sizes.mjs | 2 +- .../src-tauri/src/commands/agent_models.rs | 7 +++++++ desktop/src-tauri/src/commands/agents.rs | 6 ++++++ .../src-tauri/src/managed_agents/runtime.rs | 6 ++++++ desktop/src-tauri/src/managed_agents/types.rs | 8 +++++++ .../features/agents/ui/CreateAgentDialog.tsx | 5 +++++ .../agents/ui/CreateAgentDialogSections.tsx | 21 +++++++++++++++++++ desktop/src/shared/api/tauri.ts | 2 ++ desktop/src/shared/api/types.ts | 3 +++ 9 files changed, 59 insertions(+), 1 deletion(-) diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 2b57753f..1c101d2e 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -44,7 +44,7 @@ const overrides = new Map([ ["src/shared/api/tauri.ts", 1100], // remote agent provider API bindings + canvas API functions ["src-tauri/src/lib.rs", 550], // sprout-media:// proxy now forwards Range headers + propagates Content-Range/Accept-Ranges for video seeking ["src-tauri/src/commands/media.rs", 720], // ffmpeg video transcode + poster frame extraction + run_ffmpeg_with_timeout (find_ffmpeg, is_video_file, transcode_to_mp4, extract_poster_frame, transcode_and_extract_poster) + spawn_blocking wrappers + tests - ["src-tauri/src/commands/agents.rs", 849], // remote agent lifecycle routing (local + provider branches) + scope enforcement; rustfmt adds line breaks around long tuple/closure blocks + ["src-tauri/src/commands/agents.rs", 860], // remote agent lifecycle routing (local + provider branches) + scope enforcement + mcp_toolsets field; rustfmt adds line breaks around long tuple/closure blocks ["src-tauri/src/managed_agents/runtime.rs", 650], // KNOWN_AGENT_BINARIES const + process_belongs_to_us FFI (macOS proc_name + Linux /proc/comm) + terminate_process + start/stop/sync lifecycle ["src-tauri/src/managed_agents/backend.rs", 530], // provider IPC, validation, discovery, binary resolution + tests ["src/features/agents/hooks.ts", 520], // agent query/mutation surface now includes built-in persona library activation diff --git a/desktop/src-tauri/src/commands/agent_models.rs b/desktop/src-tauri/src/commands/agent_models.rs index c1174224..899bff48 100644 --- a/desktop/src-tauri/src/commands/agent_models.rs +++ b/desktop/src-tauri/src/commands/agent_models.rs @@ -130,6 +130,13 @@ pub fn update_managed_agent( if let Some(prompt_update) = input.system_prompt { record.system_prompt = prompt_update; } + if let Some(toolsets_update) = input.mcp_toolsets { + record.mcp_toolsets = toolsets_update + .as_deref() + .map(str::trim) + .filter(|v| !v.is_empty()) + .map(str::to_string); + } record.updated_at = now_iso(); save_managed_agents(&app, &records)?; diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index 6079aadd..e0974aac 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -385,6 +385,12 @@ pub async fn create_managed_agent( .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_string), + mcp_toolsets: input + .mcp_toolsets + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string), // Provider agents don't auto-start with the desktop — they're // managed externally. Force false to avoid persisting a flag the // app will never honor. diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 37ad9277..961a92c6 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -407,6 +407,7 @@ pub fn build_managed_agent_summary( parallelism: record.parallelism, system_prompt: record.system_prompt.clone(), model: record.model.clone(), + mcp_toolsets: record.mcp_toolsets.clone(), has_api_token: record.api_token.is_some(), backend: record.backend.clone(), backend_agent_id: record.backend_agent_id.clone(), @@ -526,6 +527,11 @@ pub fn start_managed_agent_process( } else { command.env_remove("SPROUT_ACP_MODEL"); } + if let Some(toolsets) = &record.mcp_toolsets { + command.env("SPROUT_TOOLSETS", toolsets); + } else { + command.env_remove("SPROUT_TOOLSETS"); + } command.env_remove("SPROUT_ACP_PRIVATE_KEY"); command.env_remove("SPROUT_ACP_API_TOKEN"); diff --git a/desktop/src-tauri/src/managed_agents/types.rs b/desktop/src-tauri/src/managed_agents/types.rs index 78dd9691..17a7a6e9 100644 --- a/desktop/src-tauri/src/managed_agents/types.rs +++ b/desktop/src-tauri/src/managed_agents/types.rs @@ -77,6 +77,10 @@ pub struct ManagedAgentRecord { /// creation by matching this ID against the fresh session/new response. #[serde(default)] pub model: Option, + /// Comma-separated toolset string forwarded as SPROUT_TOOLSETS to the MCP subprocess. + /// When None, the MCP server uses its own default ("default" toolset). + #[serde(default)] + pub mcp_toolsets: Option, #[serde(default = "default_start_on_app_launch")] pub start_on_app_launch: bool, #[serde(default)] @@ -117,6 +121,7 @@ pub struct ManagedAgentSummary { pub parallelism: u32, pub system_prompt: Option, pub model: Option, + pub mcp_toolsets: Option, pub has_api_token: bool, pub backend: BackendKind, pub backend_agent_id: Option, @@ -151,6 +156,7 @@ pub struct CreateManagedAgentRequest { pub system_prompt: Option, pub avatar_url: Option, pub model: Option, + pub mcp_toolsets: Option, #[serde(default)] pub mint_token: bool, #[serde(default)] @@ -271,6 +277,8 @@ pub struct UpdateManagedAgentRequest { pub model: Option>, #[serde(default)] pub system_prompt: Option>, + #[serde(default)] + pub mcp_toolsets: Option>, } /// Response from `get_agent_models` — normalized model info for the frontend. diff --git a/desktop/src/features/agents/ui/CreateAgentDialog.tsx b/desktop/src/features/agents/ui/CreateAgentDialog.tsx index fe2129c5..8845cf9e 100644 --- a/desktop/src/features/agents/ui/CreateAgentDialog.tsx +++ b/desktop/src/features/agents/ui/CreateAgentDialog.tsx @@ -53,6 +53,7 @@ export function CreateAgentDialog({ const [agentCommand, setAgentCommand] = React.useState("goose"); const [agentArgs, setAgentArgs] = React.useState("acp"); const [mcpCommand, setMcpCommand] = React.useState("sprout-mcp-server"); + const [mcpToolsets, setMcpToolsets] = React.useState(""); const prereqsQuery = useManagedAgentPrereqsQuery(acpCommand, mcpCommand); const [name, setName] = React.useState(""); const [relayUrl, setRelayUrl] = React.useState(""); @@ -213,6 +214,7 @@ export function CreateAgentDialog({ setAgentCommand("goose"); setAgentArgs("acp"); setMcpCommand("sprout-mcp-server"); + setMcpToolsets(""); setTurnTimeoutSeconds("320"); setParallelism("3"); setSystemPrompt(""); @@ -355,6 +357,7 @@ export function CreateAgentDialog({ .map((value) => value.trim()) .filter((value) => value.length > 0), mcpCommand: mcpCommand.trim() || undefined, + mcpToolsets: mcpToolsets.trim() || undefined, turnTimeoutSeconds: Number.parseInt(turnTimeoutSeconds, 10) > 0 ? Number.parseInt(turnTimeoutSeconds, 10) @@ -530,11 +533,13 @@ export function CreateAgentDialog({ agentArgs={agentArgs} agentCommand={agentCommand} mcpCommand={mcpCommand} + mcpToolsets={mcpToolsets} onParallelismChange={setParallelism} onAcpCommandChange={setAcpCommand} onAgentArgsChange={setAgentArgs} onAgentCommandChange={setAgentCommand} onMcpCommandChange={setMcpCommand} + onMcpToolsetsChange={setMcpToolsets} onRelayUrlChange={setRelayUrl} onSystemPromptChange={setSystemPrompt} onTurnTimeoutChange={setTurnTimeoutSeconds} diff --git a/desktop/src/features/agents/ui/CreateAgentDialogSections.tsx b/desktop/src/features/agents/ui/CreateAgentDialogSections.tsx index f066b083..12520452 100644 --- a/desktop/src/features/agents/ui/CreateAgentDialogSections.tsx +++ b/desktop/src/features/agents/ui/CreateAgentDialogSections.tsx @@ -100,6 +100,7 @@ export function CreateAgentRuntimeFields({ agentArgs, agentCommand, mcpCommand, + mcpToolsets, parallelism, relayUrl, selectedProviderId, @@ -109,6 +110,7 @@ export function CreateAgentRuntimeFields({ onAgentArgsChange, onAgentCommandChange, onMcpCommandChange, + onMcpToolsetsChange, onParallelismChange, onRelayUrlChange, onSystemPromptChange, @@ -118,6 +120,7 @@ export function CreateAgentRuntimeFields({ agentArgs: string; agentCommand: string; mcpCommand: string; + mcpToolsets: string; parallelism: string; relayUrl: string; selectedProviderId: string; @@ -127,6 +130,7 @@ export function CreateAgentRuntimeFields({ onAgentArgsChange: (value: string) => void; onAgentCommandChange: (value: string) => void; onMcpCommandChange: (value: string) => void; + onMcpToolsetsChange: (value: string) => void; onParallelismChange: (value: string) => void; onRelayUrlChange: (value: string) => void; onSystemPromptChange: (value: string) => void; @@ -280,6 +284,23 @@ export function CreateAgentRuntimeFields({ +
+ + onMcpToolsetsChange(event.target.value)} + placeholder="default" + value={mcpToolsets} + /> +

+ Comma-separated list of toolsets to expose via `SPROUT_TOOLSETS`. + Available: default, channel_admin, dms, canvas, workflow_admin, + identity, forums, social. Leave blank for the default toolset only. +

+
+