Skip to content

Commit 6e0987c

Browse files
hayemaxibrandonskiser
authored andcommitted
feat(mcp): sort tools and prompts (#1809)
- Tools and prompts are sorted by MCP server alphabetically. Built-in tools always come come first - Within each category (MCP), they are sorted by name alphabetically. - Slightly adjust /tools footer.
1 parent 89a08f8 commit 6e0987c

File tree

2 files changed

+63
-36
lines changed

2 files changed

+63
-36
lines changed

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ use tools::{
117117
OutputKind,
118118
QueuedTool,
119119
Tool,
120+
ToolOrigin,
120121
ToolPermissions,
121122
ToolSpec,
122123
};
@@ -2399,24 +2400,43 @@ impl ChatContext {
23992400
style::Print("▔".repeat(terminal_width)),
24002401
)?;
24012402

2402-
self.conversation_state.tools.iter().for_each(|(origin, tools)| {
2403-
let to_display = tools
2403+
let mut origin_tools: Vec<_> = self.conversation_state.tools.iter().collect();
2404+
2405+
// Built in tools always appear first.
2406+
origin_tools.sort_by(|(origin_a, _), (origin_b, _)| match (origin_a, origin_b) {
2407+
(ToolOrigin::Native, _) => std::cmp::Ordering::Less,
2408+
(_, ToolOrigin::Native) => std::cmp::Ordering::Greater,
2409+
(ToolOrigin::McpServer(name_a), ToolOrigin::McpServer(name_b)) => name_a.cmp(name_b),
2410+
});
2411+
2412+
for (origin, tools) in origin_tools.iter() {
2413+
let mut sorted_tools: Vec<_> = tools
24042414
.iter()
24052415
.filter(|FigTool::ToolSpecification(spec)| spec.name != DUMMY_TOOL_NAME)
2406-
.fold(String::new(), |mut acc, FigTool::ToolSpecification(spec)| {
2407-
let width = longest - spec.name.len() + 4;
2408-
acc.push_str(
2409-
format!(
2410-
"- {}{:>width$}{}\n",
2411-
spec.name,
2412-
"",
2413-
self.tool_permissions.display_label(&spec.name),
2414-
width = width
2415-
)
2416-
.as_str(),
2417-
);
2418-
acc
2419-
});
2416+
.collect();
2417+
2418+
sorted_tools.sort_by_key(|t| match t {
2419+
FigTool::ToolSpecification(spec) => &spec.name,
2420+
});
2421+
2422+
let to_display =
2423+
sorted_tools
2424+
.iter()
2425+
.fold(String::new(), |mut acc, FigTool::ToolSpecification(spec)| {
2426+
let width = longest - spec.name.len() + 4;
2427+
acc.push_str(
2428+
format!(
2429+
"- {}{:>width$}{}\n",
2430+
spec.name,
2431+
"",
2432+
self.tool_permissions.display_label(&spec.name),
2433+
width = width
2434+
)
2435+
.as_str(),
2436+
);
2437+
acc
2438+
});
2439+
24202440
let _ = queue!(
24212441
self.output,
24222442
style::SetAttribute(Attribute::Bold),
@@ -2425,7 +2445,7 @@ impl ChatContext {
24252445
style::Print(to_display),
24262446
style::Print("\n")
24272447
);
2428-
});
2448+
}
24292449

24302450
let loading = self.conversation_state.tool_manager.pending_clients().await;
24312451
if !loading.is_empty() {
@@ -2444,15 +2464,15 @@ impl ChatContext {
24442464

24452465
queue!(
24462466
self.output,
2447-
style::Print("\nTrusted tools can be run without confirmation\n"),
2467+
style::Print("\nTrusted tools will run without confirmation."),
24482468
style::SetForegroundColor(Color::DarkGrey),
24492469
style::Print(format!("\n{}\n", "* Default settings")),
24502470
style::Print("\n💡 Use "),
24512471
style::SetForegroundColor(Color::Green),
24522472
style::Print("/tools help"),
24532473
style::SetForegroundColor(Color::Reset),
24542474
style::SetForegroundColor(Color::DarkGrey),
2455-
style::Print(" to edit permissions."),
2475+
style::Print(" to edit permissions.\n\n"),
24562476
style::SetForegroundColor(Color::Reset),
24572477
)?;
24582478
},
@@ -2594,23 +2614,31 @@ impl ChatContext {
25942614
style::Print("\n"),
25952615
style::Print(format!("{}\n", "▔".repeat(terminal_width))),
25962616
)?;
2597-
let prompts_by_server = prompts_wl.iter().fold(
2598-
HashMap::<&String, Vec<&PromptBundle>>::new(),
2599-
|mut acc, (prompt_name, bundles)| {
2600-
if prompt_name.contains(search_word.as_deref().unwrap_or("")) {
2601-
if prompt_name.len() > longest_name.len() {
2602-
longest_name = prompt_name.as_str();
2603-
}
2604-
for bundle in bundles {
2605-
acc.entry(&bundle.server_name)
2606-
.and_modify(|b| b.push(bundle))
2607-
.or_insert(vec![bundle]);
2617+
let mut prompts_by_server: Vec<_> = prompts_wl
2618+
.iter()
2619+
.fold(
2620+
HashMap::<&String, Vec<&PromptBundle>>::new(),
2621+
|mut acc, (prompt_name, bundles)| {
2622+
if prompt_name.contains(search_word.as_deref().unwrap_or("")) {
2623+
if prompt_name.len() > longest_name.len() {
2624+
longest_name = prompt_name.as_str();
2625+
}
2626+
for bundle in bundles {
2627+
acc.entry(&bundle.server_name)
2628+
.and_modify(|b| b.push(bundle))
2629+
.or_insert(vec![bundle]);
2630+
}
26082631
}
2609-
}
2610-
acc
2611-
},
2612-
);
2613-
for (i, (server_name, bundles)) in prompts_by_server.iter().enumerate() {
2632+
acc
2633+
},
2634+
)
2635+
.into_iter()
2636+
.collect();
2637+
prompts_by_server.sort_by_key(|(server_name, _)| server_name.as_str());
2638+
2639+
for (i, (server_name, bundles)) in prompts_by_server.iter_mut().enumerate() {
2640+
bundles.sort_by_key(|bundle| &bundle.prompt_get.name);
2641+
26142642
if i > 0 {
26152643
queue!(self.output, style::Print("\n"))?;
26162644
}

crates/chat-cli/src/cli/chat/tools/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ impl ToolPermissions {
178178
}
179179

180180
/// Provide default permission labels for the built-in set of tools.
181-
/// Unknown tools are assumed to be "Per-request"
182181
// This "static" way avoids needing to construct a tool instance.
183182
fn default_permission_label(&self, tool_name: &str) -> String {
184183
let label = match tool_name {

0 commit comments

Comments
 (0)