Add per-agent MCP toolset configuration to agent setup (#279)

This commit is contained in:
Will Pfleger
2026-04-10 13:42:04 -04:00
committed by GitHub
parent 41d343bb35
commit d0970d64e6
9 changed files with 61 additions and 2 deletions
+2 -2
View File
@@ -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
@@ -54,7 +54,7 @@ const overrides = new Map([
["src/features/agents/ui/useTeamActions.ts", 510], // team CRUD + export + import + import-update orchestration with query invalidation
["src/features/agents/ui/CreateAgentDialog.tsx", 685], // provider selector + config form + schema-typed config coercion + required field validation + locked scopes
["src/features/channels/ui/AddChannelBotDialog.tsx", 640], // provider mode: Run on selector, trust warning, probe effect, single-agent enforcement, provider warnings display
["src/shared/api/types.ts", 530], // persona provider/model fields + forum types + workflow type re-exports + ephemeral channel TTL fields
["src/shared/api/types.ts", 535], // persona provider/model fields + forum types + workflow type re-exports + ephemeral channel TTL fields + mcpToolsets
]);
async function walkFiles(directory) {
@@ -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)?;
+6
View File
@@ -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.
@@ -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");
@@ -77,6 +77,10 @@ pub struct ManagedAgentRecord {
/// creation by matching this ID against the fresh session/new response.
#[serde(default)]
pub model: Option<String>,
/// 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<String>,
#[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<String>,
pub model: Option<String>,
pub mcp_toolsets: Option<String>,
pub has_api_token: bool,
pub backend: BackendKind,
pub backend_agent_id: Option<String>,
@@ -151,6 +156,7 @@ pub struct CreateManagedAgentRequest {
pub system_prompt: Option<String>,
pub avatar_url: Option<String>,
pub model: Option<String>,
pub mcp_toolsets: Option<String>,
#[serde(default)]
pub mint_token: bool,
#[serde(default)]
@@ -271,6 +277,8 @@ pub struct UpdateManagedAgentRequest {
pub model: Option<Option<String>>,
#[serde(default)]
pub system_prompt: Option<Option<String>>,
#[serde(default)]
pub mcp_toolsets: Option<Option<String>>,
}
/// Response from `get_agent_models` — normalized model info for the frontend.
@@ -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}
@@ -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({
</div>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium" htmlFor="agent-mcp-toolsets">
MCP toolsets
</label>
<Input
id="agent-mcp-toolsets"
onChange={(event) => onMcpToolsetsChange(event.target.value)}
placeholder="default"
value={mcpToolsets}
/>
<p className="text-xs text-muted-foreground">
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.
</p>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium" htmlFor="agent-system-prompt">
System prompt override
+3
View File
@@ -240,6 +240,7 @@ export type RawManagedAgent = {
parallelism: number;
system_prompt: string | null;
model: string | null;
mcp_toolsets: string | null;
has_api_token: boolean;
status: ManagedAgent["status"];
pid: number | null;
@@ -831,6 +832,7 @@ export function fromRawManagedAgent(agent: RawManagedAgent): ManagedAgent {
parallelism: agent.parallelism,
systemPrompt: agent.system_prompt,
model: agent.model,
mcpToolsets: agent.mcp_toolsets,
hasApiToken: agent.has_api_token,
status: agent.status,
pid: agent.pid,
@@ -927,6 +929,7 @@ export async function createManagedAgent(input: CreateManagedAgentInput) {
agentCommand: input.agentCommand,
agentArgs: input.agentArgs,
mcpCommand: input.mcpCommand,
mcpToolsets: input.mcpToolsets,
turnTimeoutSeconds: input.turnTimeoutSeconds,
idleTimeoutSeconds: input.idleTimeoutSeconds,
maxTurnDurationSeconds: input.maxTurnDurationSeconds,
+3
View File
@@ -294,6 +294,7 @@ export type ManagedAgent = {
parallelism: number;
systemPrompt: string | null;
model: string | null;
mcpToolsets: string | null;
hasApiToken: boolean;
status: "running" | "stopped" | "deployed" | "not_deployed";
pid: number | null;
@@ -337,6 +338,7 @@ export type CreateManagedAgentInput = {
systemPrompt?: string;
avatarUrl?: string;
model?: string;
mcpToolsets?: string;
mintToken?: boolean;
tokenScopes?: TokenScope[];
tokenName?: string;
@@ -407,6 +409,7 @@ export type UpdateManagedAgentInput = {
name?: string;
model?: string | null;
systemPrompt?: string | null;
mcpToolsets?: string | null;
};
export type AgentPersona = {
id: string;