From fbf2398fb80260ed418317136b2da81edb54dd97 Mon Sep 17 00:00:00 2001 From: gustav-fff <286169375+gustav-fff@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:07:00 -0700 Subject: [PATCH] fix(mcp): declare readOnlyHint on all tools (#771) find_files, grep, and multi_grep only read the filesystem but declared no MCP tool annotations, so plan-mode / read-only clients blocked them. Add annotations(read_only_hint = true, destructive_hint = false, open_world_hint = false) to each #[tool]. Closes #771 --- crates/fff-mcp/src/server.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/fff-mcp/src/server.rs b/crates/fff-mcp/src/server.rs index c4affa334..3bef95ff8 100644 --- a/crates/fff-mcp/src/server.rs +++ b/crates/fff-mcp/src/server.rs @@ -421,7 +421,12 @@ impl FffServer { /// IMPORTANT: Keep queries SHORT — prefer 1-2 terms max. #[tool( name = "find_files", - description = "Fuzzy file search by name. Searches FILE NAMES, not file contents. Use it when you need to find a file, not a definition. Use grep instead for searching code content (definitions, usage patterns). Supports fuzzy matching, path prefixes ('src/'), and glob constraints ('name **/src/*.{ts,tsx} !test/'). IMPORTANT: Keep queries SHORT — prefer 1-2 terms max. Multiple words are a waterfall (each narrows results), NOT OR. If unsure, start broad with 1 term and refine." + description = "Fuzzy file search by name. Searches FILE NAMES, not file contents. Use it when you need to find a file, not a definition. Use grep instead for searching code content (definitions, usage patterns). Supports fuzzy matching, path prefixes ('src/'), and glob constraints ('name **/src/*.{ts,tsx} !test/'). IMPORTANT: Keep queries SHORT — prefer 1-2 terms max. Multiple words are a waterfall (each narrows results), NOT OR. If unsure, start broad with 1 term and refine.", + annotations( + read_only_hint = true, + destructive_hint = false, + open_world_hint = false + ) )] fn find_files( &self, @@ -536,7 +541,12 @@ impl FffServer { /// Prefer plain text over regex. Filter files with constraints. #[tool( name = "grep", - description = "Search file contents. Search for bare identifiers (e.g. 'InProgressQuote', 'ActorAuth'), NOT code syntax or regex. Filter files with constraints (e.g. '*.rs query', 'src/ query'). Use filename, directory (ending with /) or glob expressions to prefilter. See server instructions for constraint syntax and core rules." + description = "Search file contents. Search for bare identifiers (e.g. 'InProgressQuote', 'ActorAuth'), NOT code syntax or regex. Filter files with constraints (e.g. '*.rs query', 'src/ query'). Use filename, directory (ending with /) or glob expressions to prefilter. See server instructions for constraint syntax and core rules.", + annotations( + read_only_hint = true, + destructive_hint = false, + open_world_hint = false + ) )] fn grep( &self, @@ -573,7 +583,12 @@ impl FffServer { /// Patterns are literal text — NEVER escape special characters. #[tool( name = "multi_grep", - description = "Search file contents for lines matching ANY of multiple patterns (OR logic). IMPORTANT: This returns files where ANY query matches, NOT all patterns. Patterns are literal text — NEVER escape special characters (no \\( \\) \\. etc). Faster than regex alternation for literal text. See server instructions for constraint syntax." + description = "Search file contents for lines matching ANY of multiple patterns (OR logic). IMPORTANT: This returns files where ANY query matches, NOT all patterns. Patterns are literal text — NEVER escape special characters (no \\( \\) \\. etc). Faster than regex alternation for literal text. See server instructions for constraint syntax.", + annotations( + read_only_hint = true, + destructive_hint = false, + open_world_hint = false + ) )] fn multi_grep( &self,