fix: let an mcp server entry be named by the path the roster shows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-11 21:39:38 +02:00
co-authored by Claude Opus 5
parent 790d185e96
commit d21a11bd63
7 changed files with 34 additions and 15 deletions
+22 -8
View File
@@ -246,12 +246,6 @@ fn overlay_tool_inputs(
}
}
/// Write a linked step's own inputs over the brain its agent supplied.
///
/// Called only after the resource has been interpolated: these values are caller-controlled and
/// `build_args_map` has already resolved them, so passing them through it again would expand
/// contextual values — `$WM_TOKEN` in a user message would reach the model provider.
///
/// The roster a run advertises, given the tool names it enabled, plus the resource paths of the
/// MCP entries it named outright — those enable every tool of that server, which only
/// `load_mcp_tools` can enumerate.
@@ -282,9 +276,13 @@ fn narrow_roster(
.is_some_and(|s| enabled.iter().any(|n| n == s));
match &t.value {
ToolValue::Mcp(mcp) => {
// An MCP entry is added with no summary and the roster displays it by its
// resource path, so that path is the only name the form can offer for it. Match
// it too, or naming one tool would be the sole way to keep a whole server.
let path = mcp.resource_path.trim_start_matches("$res:");
let named = named || enabled.iter().any(|n| n == path);
if named {
enabled_mcp_paths
.insert(mcp.resource_path.trim_start_matches("$res:").to_string());
enabled_mcp_paths.insert(path.to_string());
}
named || names_a_server_tool
}
@@ -2056,6 +2054,22 @@ mod tests {
let (kept, paths) = narrow_roster(roster(), Some(&["mcp_github_create_issue".to_string()]));
assert_eq!(names(&kept), ["github"]);
assert!(paths.is_empty());
// An MCP entry is added with no summary, and the form offers it by the path the roster
// displays it as. Without this the only way to keep such a server would be naming one of
// the tools it has not been asked for yet.
let unnamed = || {
vec![AgentTool {
summary: None,
..mcp("c", "github", "$res:u/test/gh")
}]
};
let (kept, paths) = narrow_roster(unnamed(), Some(&["u/test/gh".to_string()]));
assert_eq!(kept.len(), 1);
assert_eq!(paths.into_iter().collect::<Vec<_>>(), ["u/test/gh"]);
let (dropped, paths) = narrow_roster(unnamed(), Some(&["u/test/other".to_string()]));
assert!(dropped.is_empty());
assert!(paths.is_empty());
}
/// The two sides of the server-entry match are different types, and getting it wrong advertises
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -4,7 +4,7 @@
anonymous usage-stats payload. It answers "does anyone use this, and which variant do they pick"
without any identifying data leaving the instance.
It currently carries 49 registered actions across eighteen features (`ai_session`, `ai_chat`,
It currently carries 50 registered actions across eighteen features (`ai_session`, `ai_chat`,
`ai_fix`, `ai_agent`, `ai_agent_eval`, `app_sandbox`, `datatable`, `flow_editor`, `flow_run`,
`flow_step`, `home`, `run_form`, `debugger`, `trigger`, `command_script`, `hub_script`,
`usage_meter`, `sso_groups_claim`). Nearly all of the
@@ -46,7 +46,7 @@
import { Plus, X } from 'lucide-svelte'
import type { PickableProperties } from '../previousResults'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import type { AgentTool } from '../agentToolUtils'
import { toolDisplayName, type AgentTool } from '../agentToolUtils'
import {
AGENT_FIELDS,
AGENT_FIELD_GROUPS,
@@ -168,7 +168,12 @@
// field's shape from; `flowInfers` hands every step its own copy, so this stays this step's.
// A linked step gets the resource's roster here, which is the one it narrows.
$effect(() => {
const names = tools.map((tool) => tool.summary).filter((name): name is string => !!name)
// By the name the roster shows, not the summary alone: an MCP entry is added without one and
// displays as its resource path, so keying on `summary` would leave a whole server with no
// name to pick. `narrow_roster` matches that path for the same reason.
const names = tools
.map((tool) => toolDisplayName(tool))
.filter((name): name is string => !!name)
const properties = schemaProperties
untrack(() => {
const list = properties['enabled_tools']?.oneOf?.find((variant) => variant.title === 'only')
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long