Skip to content

Commit 4540c4b

Browse files
committed
feat(mcp): sort tools and prompts
- 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 39cc3b9 commit 4540c4b

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
};
@@ -2385,24 +2386,43 @@ impl ChatContext {
23852386
style::Print("▔".repeat(terminal_width)),
23862387
)?;
23872388

2388-
self.conversation_state.tools.iter().for_each(|(origin, tools)| {
2389-
let to_display = tools
2389+
let mut origin_tools: Vec<_> = self.conversation_state.tools.iter().collect();
2390+
2391+
// Built in tools always appear first.
2392+
origin_tools.sort_by(|(origin_a, _), (origin_b, _)| match (origin_a, origin_b) {
2393+
(ToolOrigin::Native, _) => std::cmp::Ordering::Less,
2394+
(_, ToolOrigin::Native) => std::cmp::Ordering::Greater,
2395+
(ToolOrigin::McpServer(name_a), ToolOrigin::McpServer(name_b)) => name_a.cmp(name_b),
2396+
});
2397+
2398+
for (origin, tools) in origin_tools.iter() {
2399+
let mut sorted_tools: Vec<_> = tools
23902400
.iter()
23912401
.filter(|FigTool::ToolSpecification(spec)| spec.name != DUMMY_TOOL_NAME)
2392-
.fold(String::new(), |mut acc, FigTool::ToolSpecification(spec)| {
2393-
let width = longest - spec.name.len() + 4;
2394-
acc.push_str(
2395-
format!(
2396-
"- {}{:>width$}{}\n",
2397-
spec.name,
2398-
"",
2399-
self.tool_permissions.display_label(&spec.name),
2400-
width = width
2401-
)
2402-
.as_str(),
2403-
);
2404-
acc
2405-
});
2402+
.collect();
2403+
2404+
sorted_tools.sort_by_key(|t| match t {
2405+
FigTool::ToolSpecification(spec) => &spec.name,
2406+
});
2407+
2408+
let to_display =
2409+
sorted_tools
2410+
.iter()
2411+
.fold(String::new(), |mut acc, FigTool::ToolSpecification(spec)| {
2412+
let width = longest - spec.name.len() + 4;
2413+
acc.push_str(
2414+
format!(
2415+
"- {}{:>width$}{}\n",
2416+
spec.name,
2417+
"",
2418+
self.tool_permissions.display_label(&spec.name),
2419+
width = width
2420+
)
2421+
.as_str(),
2422+
);
2423+
acc
2424+
});
2425+
24062426
let _ = queue!(
24072427
self.output,
24082428
style::SetAttribute(Attribute::Bold),
@@ -2411,7 +2431,7 @@ impl ChatContext {
24112431
style::Print(to_display),
24122432
style::Print("\n")
24132433
);
2414-
});
2434+
}
24152435

24162436
let loading = self.conversation_state.tool_manager.pending_clients().await;
24172437
if !loading.is_empty() {
@@ -2430,15 +2450,15 @@ impl ChatContext {
24302450

24312451
queue!(
24322452
self.output,
2433-
style::Print("\nTrusted tools can be run without confirmation\n"),
2453+
style::Print("\nTrusted tools will run without confirmation."),
24342454
style::SetForegroundColor(Color::DarkGrey),
24352455
style::Print(format!("\n{}\n", "* Default settings")),
24362456
style::Print("\n💡 Use "),
24372457
style::SetForegroundColor(Color::Green),
24382458
style::Print("/tools help"),
24392459
style::SetForegroundColor(Color::Reset),
24402460
style::SetForegroundColor(Color::DarkGrey),
2441-
style::Print(" to edit permissions."),
2461+
style::Print(" to edit permissions.\n\n"),
24422462
style::SetForegroundColor(Color::Reset),
24432463
)?;
24442464
},
@@ -2580,23 +2600,31 @@ impl ChatContext {
25802600
style::Print("\n"),
25812601
style::Print(format!("{}\n", "▔".repeat(terminal_width))),
25822602
)?;
2583-
let prompts_by_server = prompts_wl.iter().fold(
2584-
HashMap::<&String, Vec<&PromptBundle>>::new(),
2585-
|mut acc, (prompt_name, bundles)| {
2586-
if prompt_name.contains(search_word.as_deref().unwrap_or("")) {
2587-
if prompt_name.len() > longest_name.len() {
2588-
longest_name = prompt_name.as_str();
2589-
}
2590-
for bundle in bundles {
2591-
acc.entry(&bundle.server_name)
2592-
.and_modify(|b| b.push(bundle))
2593-
.or_insert(vec![bundle]);
2603+
let mut prompts_by_server: Vec<_> = prompts_wl
2604+
.iter()
2605+
.fold(
2606+
HashMap::<&String, Vec<&PromptBundle>>::new(),
2607+
|mut acc, (prompt_name, bundles)| {
2608+
if prompt_name.contains(search_word.as_deref().unwrap_or("")) {
2609+
if prompt_name.len() > longest_name.len() {
2610+
longest_name = prompt_name.as_str();
2611+
}
2612+
for bundle in bundles {
2613+
acc.entry(&bundle.server_name)
2614+
.and_modify(|b| b.push(bundle))
2615+
.or_insert(vec![bundle]);
2616+
}
25942617
}
2595-
}
2596-
acc
2597-
},
2598-
);
2599-
for (i, (server_name, bundles)) in prompts_by_server.iter().enumerate() {
2618+
acc
2619+
},
2620+
)
2621+
.into_iter()
2622+
.collect();
2623+
prompts_by_server.sort_by_key(|(server_name, _)| server_name.as_str());
2624+
2625+
for (i, (server_name, bundles)) in prompts_by_server.iter_mut().enumerate() {
2626+
bundles.sort_by_key(|bundle| &bundle.prompt_get.name);
2627+
26002628
if i > 0 {
26012629
queue!(self.output, style::Print("\n"))?;
26022630
}

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)