Skip to content

Commit 15686f6

Browse files
github-actions[bot]CopilotCopilotjamesadevine
authored
refactor(engine): reduce complexity of collect_allowed_tools (#1454)
Extract collect_extension_tools, mcp_server_has_backing_server, add_mcp_server_tools, and build_bash_command_list from the monolithic collect_allowed_tools function. Before: single ~90-line function with three nested loops and two in-line match arms (estimated cognitive complexity ~20). After: collect_allowed_tools is a 25-line coordinator; each concern is named and independently testable. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 137b014 commit 15686f6

1 file changed

Lines changed: 57 additions & 52 deletions

File tree

src/engine.rs

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -469,11 +469,48 @@ fn collect_allowed_tools(
469469
edit_enabled: bool,
470470
) -> Result<Vec<String>> {
471471
let mut allowed_tools = collect_extension_allowed_tools(extension_declarations);
472+
add_mcp_server_tools(front_matter, &mut allowed_tools);
472473

473-
// Tools from user-defined MCP servers (sorted for deterministic output).
474-
// Only add --allow-tool for MCPs that will actually produce an MCPG entry (i.e.,
475-
// WithOptions that have a container or url). McpConfig::Enabled(true) has no backing
476-
// server in MCPG, so granting the permission would cause confusing runtime errors.
474+
// Intentional: with restricted bash, both --allow-tool write (tool identity)
475+
// and --allow-all-paths (path scope) are emitted. --allow-all-tools subsumes
476+
// --allow-tool write, so only --allow-all-paths is needed on that path.
477+
if edit_enabled {
478+
allowed_tools.push("write".to_string());
479+
}
480+
481+
let bash_commands = build_bash_command_list(front_matter, extension_declarations);
482+
for cmd in bash_commands {
483+
// Reject single quotes — copilot_params are embedded inside a single-quoted
484+
// bash string in the AWF command.
485+
if cmd.contains('\'') {
486+
anyhow::bail!(
487+
"Bash command '{}' contains a single quote, which is not allowed \
488+
(would break AWF shell quoting).",
489+
cmd
490+
);
491+
}
492+
allowed_tools.push(format!("shell({})", cmd));
493+
}
494+
495+
Ok(allowed_tools)
496+
}
497+
498+
/// Returns `true` when an MCP server config has a real backing server (container
499+
/// or URL). `McpConfig::Enabled(true)` has no backing server in MCPG, so granting
500+
/// its permission would cause confusing runtime errors.
501+
fn mcp_server_has_backing_server(config: &McpConfig) -> bool {
502+
match config {
503+
McpConfig::Enabled(_) => false,
504+
McpConfig::WithOptions(opts) => {
505+
opts.enabled.unwrap_or(true) && (opts.container.is_some() || opts.url.is_some())
506+
}
507+
}
508+
}
509+
510+
/// Appends user-defined MCP server names to `allowed_tools` (sorted for
511+
/// deterministic output), skipping any server already provided by an extension
512+
/// and any server without a backing MCPG entry.
513+
fn add_mcp_server_tools(front_matter: &FrontMatter, allowed_tools: &mut Vec<String>) {
477514
let mut sorted_mcps: Vec<_> = front_matter.mcp_servers.iter().collect();
478515
sorted_mcps.sort_by_key(|(a, _)| *a);
479516
for (name, config) in sorted_mcps {
@@ -482,68 +519,36 @@ fn collect_allowed_tools(
482519
if allowed_tools.iter().any(|t| t.eq_ignore_ascii_case(name)) {
483520
continue;
484521
}
485-
// Only add MCPs that have a backing server (container or url)
486-
let has_backing_server = match config {
487-
McpConfig::Enabled(_) => false,
488-
McpConfig::WithOptions(opts) => {
489-
opts.enabled.unwrap_or(true) && (opts.container.is_some() || opts.url.is_some())
490-
}
491-
};
492-
if has_backing_server {
522+
if mcp_server_has_backing_server(config) {
493523
allowed_tools.push(name.clone());
494524
}
495525
}
526+
}
496527

497-
// Intentional: with restricted bash, both --allow-tool write (tool identity)
498-
// and --allow-all-paths (path scope) are emitted. --allow-all-tools subsumes
499-
// --allow-tool write, so only --allow-all-paths is needed on that path.
500-
if edit_enabled {
501-
allowed_tools.push("write".to_string());
502-
}
503-
504-
// Bash tool: use the explicitly configured list.
505-
// When bash is None (not specified), use_allow_all_tools is true and this
506-
// function is not called — that invariant is upheld by the caller.
528+
/// Builds the deduplicated bash command list from the front-matter configuration
529+
/// augmented with any extension-declared commands (runtimes + first-party tools).
530+
///
531+
/// When bash is `None`, `use_allow_all_tools` is `true` and this function is never
532+
/// called — the caller upholds that invariant.
533+
fn build_bash_command_list(
534+
front_matter: &FrontMatter,
535+
extension_declarations: &[Declarations],
536+
) -> Vec<String> {
507537
let mut bash_commands: Vec<String> =
508538
match front_matter.tools.as_ref().and_then(|t| t.bash.as_ref()) {
509-
Some(cmds) if cmds.is_empty() => {
510-
// Explicitly disabled: no bash commands
511-
vec![]
512-
}
513-
Some(cmds) => {
514-
// Explicit list of commands
515-
cmds.clone()
516-
}
517-
None => {
518-
// Invariant: bash=None → use_allow_all_tools=true → this function is
519-
// not called. Panic if the invariant is ever broken.
520-
unreachable!("bash=None should imply use_allow_all_tools=true")
521-
}
539+
Some(cmds) if cmds.is_empty() => vec![],
540+
Some(cmds) => cmds.clone(),
541+
// Invariant: bash=None → use_allow_all_tools=true → not called.
542+
None => unreachable!("bash=None should imply use_allow_all_tools=true"),
522543
};
523-
524-
// Auto-add extension-declared bash commands (runtimes + first-party tools)
525544
for decl in extension_declarations {
526545
for cmd in &decl.bash_commands {
527546
if !bash_commands.contains(cmd) {
528547
bash_commands.push(cmd.clone());
529548
}
530549
}
531550
}
532-
533-
for cmd in &bash_commands {
534-
// Reject single quotes in bash commands — copilot_params are embedded inside
535-
// a single-quoted bash string in the AWF command.
536-
if cmd.contains('\'') {
537-
anyhow::bail!(
538-
"Bash command '{}' contains a single quote, which is not allowed \
539-
(would break AWF shell quoting).",
540-
cmd
541-
);
542-
}
543-
allowed_tools.push(format!("shell({})", cmd));
544-
}
545-
546-
Ok(allowed_tools)
551+
bash_commands
547552
}
548553

549554
/// Validates a single `engine.args` entry.

0 commit comments

Comments
 (0)