hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (154) hide show
  1. hanzo_mcp/__init__.py +2 -4
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +13 -6
  17. hanzo_mcp/tools/__init__.py +10 -24
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +5 -15
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
  22. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  23. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  24. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  25. hanzo_mcp/tools/agent/code_auth.py +1 -3
  26. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  27. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  28. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  29. hanzo_mcp/tools/agent/network_tool.py +7 -18
  30. hanzo_mcp/tools/agent/prompt.py +1 -5
  31. hanzo_mcp/tools/agent/review_tool.py +10 -25
  32. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  33. hanzo_mcp/tools/agent/swarm_tool.py +9 -29
  34. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
  35. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  36. hanzo_mcp/tools/common/batch_tool.py +15 -45
  37. hanzo_mcp/tools/common/config_tool.py +9 -28
  38. hanzo_mcp/tools/common/context.py +1 -3
  39. hanzo_mcp/tools/common/critic_tool.py +1 -3
  40. hanzo_mcp/tools/common/decorators.py +2 -6
  41. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  42. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  43. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  44. hanzo_mcp/tools/common/mode.py +1 -5
  45. hanzo_mcp/tools/common/paginated_base.py +3 -11
  46. hanzo_mcp/tools/common/paginated_response.py +10 -30
  47. hanzo_mcp/tools/common/pagination.py +3 -9
  48. hanzo_mcp/tools/common/permissions.py +38 -11
  49. hanzo_mcp/tools/common/personality.py +9 -34
  50. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  51. hanzo_mcp/tools/common/stats.py +6 -18
  52. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  53. hanzo_mcp/tools/common/tool_disable.py +2 -6
  54. hanzo_mcp/tools/common/tool_list.py +2 -6
  55. hanzo_mcp/tools/common/validation.py +1 -3
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_edit.py +2 -2
  72. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  73. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  74. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  75. hanzo_mcp/tools/filesystem/base.py +4 -12
  76. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  77. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  78. hanzo_mcp/tools/filesystem/diff.py +2 -10
  79. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  80. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  81. hanzo_mcp/tools/filesystem/edit.py +6 -18
  82. hanzo_mcp/tools/filesystem/find.py +3 -9
  83. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  84. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  85. hanzo_mcp/tools/filesystem/grep.py +9 -27
  86. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  87. hanzo_mcp/tools/filesystem/read.py +8 -26
  88. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  89. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  90. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  91. hanzo_mcp/tools/filesystem/tree.py +1 -3
  92. hanzo_mcp/tools/filesystem/watch.py +1 -3
  93. hanzo_mcp/tools/filesystem/write.py +1 -3
  94. hanzo_mcp/tools/jupyter/base.py +6 -20
  95. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  96. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  97. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  98. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  99. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  100. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  101. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  102. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  103. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  104. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  105. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  106. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  107. hanzo_mcp/tools/memory/__init__.py +10 -27
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +24 -78
  112. hanzo_mcp/tools/shell/__init__.py +2 -2
  113. hanzo_mcp/tools/shell/auto_background.py +2 -6
  114. hanzo_mcp/tools/shell/base.py +1 -5
  115. hanzo_mcp/tools/shell/base_process.py +5 -7
  116. hanzo_mcp/tools/shell/bash_session.py +7 -24
  117. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  118. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  119. hanzo_mcp/tools/shell/command_executor.py +33 -86
  120. hanzo_mcp/tools/shell/logs.py +4 -16
  121. hanzo_mcp/tools/shell/npx.py +2 -8
  122. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  123. hanzo_mcp/tools/shell/pkill.py +4 -12
  124. hanzo_mcp/tools/shell/process_tool.py +2 -8
  125. hanzo_mcp/tools/shell/processes.py +5 -17
  126. hanzo_mcp/tools/shell/run_background.py +1 -3
  127. hanzo_mcp/tools/shell/run_command.py +1 -3
  128. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  129. hanzo_mcp/tools/shell/session_manager.py +2 -6
  130. hanzo_mcp/tools/shell/session_storage.py +2 -6
  131. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  132. hanzo_mcp/tools/shell/uvx.py +4 -14
  133. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  134. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  135. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  136. hanzo_mcp/tools/todo/todo.py +1 -3
  137. hanzo_mcp/tools/todo/todo_read.py +3 -9
  138. hanzo_mcp/tools/todo/todo_write.py +6 -18
  139. hanzo_mcp/tools/vector/__init__.py +3 -9
  140. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  141. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  142. hanzo_mcp/tools/vector/index_tool.py +3 -9
  143. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  144. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  145. hanzo_mcp/tools/vector/project_manager.py +4 -12
  146. hanzo_mcp/tools/vector/vector.py +2 -6
  147. hanzo_mcp/tools/vector/vector_index.py +8 -8
  148. hanzo_mcp/tools/vector/vector_search.py +7 -21
  149. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  152. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
  154. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/top_level.txt +0 -0
@@ -152,9 +152,7 @@ Returns nextCursor if more entries are available."""
152
152
  await tool_ctx.error(path_validation.error_message)
153
153
  return {"error": path_validation.error_message}
154
154
 
155
- await tool_ctx.info(
156
- f"Getting paginated directory tree: {path} (depth: {depth}, page_size: {page_size})"
157
- )
155
+ await tool_ctx.info(f"Getting paginated directory tree: {path} (depth: {depth}, page_size: {page_size})")
158
156
 
159
157
  # Check if path is allowed
160
158
  allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
@@ -196,35 +194,27 @@ Returns nextCursor if more entries are available."""
196
194
  def should_filter(current_path: Path) -> bool:
197
195
  if str(current_path.absolute()) == str(dir_path.absolute()):
198
196
  return False
199
- return (
200
- current_path.name in FILTERED_DIRECTORIES and not include_filtered
201
- )
197
+ return current_path.name in FILTERED_DIRECTORIES and not include_filtered
202
198
 
203
199
  # Collect all entries in a flat list for pagination
204
200
  all_entries: List[Dict[str, Any]] = []
205
201
 
206
202
  # Build the tree and collect entries
207
- def collect_entries(
208
- current_path: Path, current_depth: int = 0, parent_path: str = ""
209
- ) -> None:
203
+ def collect_entries(current_path: Path, current_depth: int = 0, parent_path: str = "") -> None:
210
204
  """Collect entries in a flat list for pagination."""
211
205
  if not self.is_path_allowed(str(current_path)):
212
206
  return
213
207
 
214
208
  try:
215
209
  # Sort entries: directories first, then files alphabetically
216
- entries = sorted(
217
- current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
218
- )
210
+ entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
219
211
 
220
212
  for entry in entries:
221
213
  if not self.is_path_allowed(str(entry)):
222
214
  continue
223
215
 
224
216
  # Calculate relative path for display
225
- relative_path = (
226
- f"{parent_path}/{entry.name}" if parent_path else entry.name
227
- )
217
+ relative_path = f"{parent_path}/{entry.name}" if parent_path else entry.name
228
218
 
229
219
  if entry.is_dir():
230
220
  entry_data: Dict[str, Any] = {
@@ -121,9 +121,7 @@ Usage:
121
121
  # Empty old_string is valid when creating a new file
122
122
  file_exists = Path(file_path).exists()
123
123
  if file_exists and old_string.strip() == "":
124
- await tool_ctx.error(
125
- "Parameter 'old_string' cannot be empty for existing files"
126
- )
124
+ await tool_ctx.error("Parameter 'old_string' cannot be empty for existing files")
127
125
  return "Error: Parameter 'old_string' cannot be empty for existing files"
128
126
 
129
127
  if (
@@ -131,12 +129,8 @@ Usage:
131
129
  or not isinstance(expected_replacements, (int, float))
132
130
  or expected_replacements < 0
133
131
  ):
134
- await tool_ctx.error(
135
- "Parameter 'expected_replacements' must be a non-negative number"
136
- )
137
- return (
138
- "Error: Parameter 'expected_replacements' must be a non-negative number"
139
- )
132
+ await tool_ctx.error("Parameter 'expected_replacements' must be a non-negative number")
133
+ return "Error: Parameter 'expected_replacements' must be a non-negative number"
140
134
 
141
135
  await tool_ctx.info(f"Editing file: {file_path}")
142
136
 
@@ -164,9 +158,7 @@ Usage:
164
158
  f.write(new_string)
165
159
 
166
160
  await tool_ctx.info(f"Successfully created file: {file_path}")
167
- return (
168
- f"Successfully created file: {file_path} ({len(new_string)} bytes)"
169
- )
161
+ return f"Successfully created file: {file_path} ({len(new_string)} bytes)"
170
162
 
171
163
  # Check file exists for non-creation operations
172
164
  exists, error_msg = await self.check_path_exists(file_path, tool_ctx)
@@ -199,9 +191,7 @@ Usage:
199
191
  modified_content = original_content.replace(old_string, new_string)
200
192
  else:
201
193
  # If we can't find the exact string, report an error
202
- await tool_ctx.error(
203
- "The specified old_string was not found in the file content"
204
- )
194
+ await tool_ctx.error("The specified old_string was not found in the file content")
205
195
  return "Error: The specified old_string was not found in the file content. Please check that it matches exactly, including all whitespace and indentation."
206
196
 
207
197
  # Generate diff
@@ -226,9 +216,7 @@ Usage:
226
216
  num_backticks += 1
227
217
 
228
218
  # Format diff with appropriate number of backticks
229
- formatted_diff = (
230
- f"```{num_backticks}diff\n{diff_text}```{num_backticks}\n"
231
- )
219
+ formatted_diff = f"```{num_backticks}diff\n{diff_text}```{num_backticks}\n"
232
220
 
233
221
  # Write the file if there are changes
234
222
  if diff_text:
@@ -199,9 +199,7 @@ Fast, intuitive file content search."""
199
199
  # Fallback
200
200
  selected_backend = "grep"
201
201
 
202
- await tool_ctx.info(
203
- f"Using {selected_backend} to search for '{pattern}' in {path}"
204
- )
202
+ await tool_ctx.info(f"Using {selected_backend} to search for '{pattern}' in {path}")
205
203
 
206
204
  # Execute search
207
205
  if selected_backend == "rg":
@@ -459,9 +457,7 @@ Fast, intuitive file content search."""
459
457
  context_lines = []
460
458
  for j in range(start, end):
461
459
  prefix = ":" if j + 1 == i else "-"
462
- context_lines.append(
463
- f"{file_path}:{j + 1}{prefix}{lines[j].rstrip()}"
464
- )
460
+ context_lines.append(f"{file_path}:{j + 1}{prefix}{lines[j].rstrip()}")
465
461
  results.extend(context_lines)
466
462
  results.append("") # Separator
467
463
  else:
@@ -482,9 +478,7 @@ Fast, intuitive file content search."""
482
478
  await tool_ctx.error(f"Error in fallback grep: {str(e)}")
483
479
  return f"Error in fallback grep: {str(e)}"
484
480
 
485
- def _match_file_pattern(
486
- self, filename: str, include: Optional[str], exclude: Optional[str]
487
- ) -> bool:
481
+ def _match_file_pattern(self, filename: str, include: Optional[str], exclude: Optional[str]) -> bool:
488
482
  """Check if filename matches include/exclude patterns."""
489
483
  if include and not fnmatch.fnmatch(filename, include):
490
484
  return False
@@ -335,9 +335,7 @@ For database search, use 'sql_search' or 'vector_search'.
335
335
  return f"No files found matching '{pattern}' (using fallback search)"
336
336
 
337
337
  # Format output
338
- output = [
339
- f"Found {len(results)} file(s) matching '{pattern}' (using fallback search):"
340
- ]
338
+ output = [f"Found {len(results)} file(s) matching '{pattern}' (using fallback search):"]
341
339
  output.append("")
342
340
 
343
341
  for filepath in sorted(results):
@@ -346,9 +344,7 @@ For database search, use 'sql_search' or 'vector_search'.
346
344
  if count >= max_results:
347
345
  output.append(f"\n... (showing first {max_results} results)")
348
346
 
349
- output.append(
350
- "\nNote: Install 'ffind' for faster searching: pip install ffind"
351
- )
347
+ output.append("\nNote: Install 'ffind' for faster searching: pip install ffind")
352
348
 
353
349
  return "\n".join(output)
354
350
 
@@ -256,9 +256,7 @@ Examples:
256
256
  tool_ctx,
257
257
  )
258
258
  elif search_type == "blame":
259
- return await self._search_blame(
260
- abs_path, pattern, case_sensitive, file_pattern, tool_ctx
261
- )
259
+ return await self._search_blame(abs_path, pattern, case_sensitive, file_pattern, tool_ctx)
262
260
  else:
263
261
  return f"Unknown search type: {search_type}"
264
262
 
@@ -307,9 +305,7 @@ Examples:
307
305
  elif result.returncode == 1:
308
306
  return f"No matches found for pattern: {pattern}"
309
307
  else:
310
- raise subprocess.CalledProcessError(
311
- result.returncode, cmd, result.stdout, result.stderr
312
- )
308
+ raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
313
309
 
314
310
  async def _search_commits(
315
311
  self,
@@ -353,16 +349,11 @@ Examples:
353
349
  lines = result.stdout.strip().split("\n")
354
350
  if lines and lines[0]:
355
351
  await tool_ctx.info(f"Found {len(lines)} commits")
356
- return (
357
- f"Found {len(lines)} commits matching '{pattern}':\n\n"
358
- + result.stdout
359
- )
352
+ return f"Found {len(lines)} commits matching '{pattern}':\n\n" + result.stdout
360
353
  else:
361
354
  return f"No commits found matching: {pattern}"
362
355
  else:
363
- raise subprocess.CalledProcessError(
364
- result.returncode, cmd, result.stdout, result.stderr
365
- )
356
+ raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
366
357
 
367
358
  async def _search_diff(
368
359
  self,
@@ -387,8 +378,7 @@ Examples:
387
378
  import re
388
379
 
389
380
  case_insensitive_pattern = "".join(
390
- f"[{c.upper()}{c.lower()}]" if c.isalpha() else re.escape(c)
391
- for c in pattern
381
+ f"[{c.upper()}{c.lower()}]" if c.isalpha() else re.escape(c) for c in pattern
392
382
  )
393
383
  search_flag = f"-G{case_insensitive_pattern}"
394
384
 
@@ -415,9 +405,7 @@ Examples:
415
405
 
416
406
  if result.returncode == 0 and result.stdout.strip():
417
407
  # Parse and highlight matching lines
418
- output = self._highlight_diff_matches(
419
- result.stdout, pattern, case_sensitive
420
- )
408
+ output = self._highlight_diff_matches(result.stdout, pattern, case_sensitive)
421
409
  matches = output.count("commit ")
422
410
  await tool_ctx.info(f"Found {matches} commits with changes")
423
411
  return f"Found {matches} commits with changes matching '{pattern}':\n\n{output}"
@@ -505,9 +493,8 @@ Examples:
505
493
 
506
494
  if all_matches:
507
495
  await tool_ctx.info(f"Found {len(all_matches)} matching lines")
508
- return (
509
- f"Found {len(all_matches)} lines matching '{pattern}':\n\n"
510
- + "\n".join(all_matches[:50])
496
+ return f"Found {len(all_matches)} lines matching '{pattern}':\n\n" + "\n".join(
497
+ all_matches[:50]
511
498
  ) # Limit output
512
499
  else:
513
500
  return f"No lines found matching: {pattern}"
@@ -534,9 +521,7 @@ Examples:
534
521
 
535
522
  return f"Found matches for '{pattern}':\n" + "\n".join(output)
536
523
 
537
- def _highlight_diff_matches(
538
- self, diff_output: str, pattern: str, case_sensitive: bool
539
- ) -> str:
524
+ def _highlight_diff_matches(self, diff_output: str, pattern: str, case_sensitive: bool) -> str:
540
525
  """Highlight matching lines in diff output."""
541
526
  lines = diff_output.split("\n")
542
527
  output = []
@@ -118,9 +118,7 @@ When you are doing an open ended search that may require multiple rounds of glob
118
118
  # Special case for tests: direct file path with include pattern that doesn't match
119
119
  if Path(path).is_file() and include_pattern and include_pattern != "*":
120
120
  if not fnmatch.fnmatch(Path(path).name, include_pattern):
121
- await tool_ctx.info(
122
- f"File does not match pattern '{include_pattern}': {path}"
123
- )
121
+ await tool_ctx.info(f"File does not match pattern '{include_pattern}': {path}")
124
122
  return f"File does not match pattern '{include_pattern}': {path}"
125
123
 
126
124
  cmd = ["rg", "--json", pattern]
@@ -144,9 +142,7 @@ When you are doing an open ended search that may require multiple rounds of glob
144
142
 
145
143
  if process.returncode != 0 and process.returncode != 1:
146
144
  # rg returns 1 when no matches are found, which is not an error
147
- await tool_ctx.error(
148
- f"ripgrep failed with exit code {process.returncode}: {stderr.decode()}"
149
- )
145
+ await tool_ctx.error(f"ripgrep failed with exit code {process.returncode}: {stderr.decode()}")
150
146
  return f"Error executing ripgrep: {stderr.decode()}"
151
147
 
152
148
  # Parse the JSON output
@@ -182,9 +178,7 @@ When you are doing an open ended search that may require multiple rounds of glob
182
178
  if data.get("type") == "match":
183
179
  path = data.get("data", {}).get("path", {}).get("text", "")
184
180
  line_number = data.get("data", {}).get("line_number", 0)
185
- line_text = (
186
- data.get("data", {}).get("lines", {}).get("text", "").rstrip()
187
- )
181
+ line_text = data.get("data", {}).get("lines", {}).get("text", "").rstrip()
188
182
 
189
183
  if path not in file_results:
190
184
  file_results[path] = []
@@ -251,9 +245,7 @@ When you are doing an open ended search that may require multiple rounds of glob
251
245
  await tool_ctx.info(f"Searching single file: {path}")
252
246
  else:
253
247
  # File doesn't match the pattern, return immediately
254
- await tool_ctx.info(
255
- f"File does not match pattern '{include_pattern}': {path}"
256
- )
248
+ await tool_ctx.info(f"File does not match pattern '{include_pattern}': {path}")
257
249
  return f"File does not match pattern '{include_pattern}': {path}"
258
250
  elif input_path.is_dir():
259
251
  # Directory search - find all files
@@ -290,9 +282,7 @@ When you are doing an open ended search that may require multiple rounds of glob
290
282
  if input_path.is_file():
291
283
  await tool_ctx.info(f"Searching file: {path}")
292
284
  else:
293
- await tool_ctx.info(
294
- f"Searching through {total_files} files in directory"
295
- )
285
+ await tool_ctx.info(f"Searching through {total_files} files in directory")
296
286
 
297
287
  # Set up for parallel processing
298
288
  results: list[str] = []
@@ -314,18 +304,14 @@ When you are doing an open ended search that may require multiple rounds of glob
314
304
  with open(file_path, "r", encoding="utf-8") as f:
315
305
  for line_num, line in enumerate(f, 1):
316
306
  if re.search(pattern, line):
317
- file_results.append(
318
- f"{file_path}:{line_num}: {line.rstrip()}"
319
- )
307
+ file_results.append(f"{file_path}:{line_num}: {line.rstrip()}")
320
308
  matches_found += 1
321
309
  files_processed += 1
322
310
  except UnicodeDecodeError:
323
311
  # Skip binary files
324
312
  files_processed += 1
325
313
  except Exception as e:
326
- await tool_ctx.warning(
327
- f"Error reading {file_path}: {str(e)}"
328
- )
314
+ await tool_ctx.warning(f"Error reading {file_path}: {str(e)}")
329
315
  except Exception as e:
330
316
  await tool_ctx.warning(f"Error processing {file_path}: {str(e)}")
331
317
 
@@ -429,9 +415,7 @@ When you are doing an open ended search that may require multiple rounds of glob
429
415
  truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]",
430
416
  )
431
417
  else:
432
- await tool_ctx.info(
433
- "ripgrep is not installed, using fallback implementation"
434
- )
418
+ await tool_ctx.info("ripgrep is not installed, using fallback implementation")
435
419
  result = await self.fallback_grep(pattern, path, tool_ctx, include)
436
420
  return truncate_response(
437
421
  result,
@@ -462,6 +446,4 @@ When you are doing an open ended search that may require multiple rounds of glob
462
446
  include: Include,
463
447
  ) -> str:
464
448
  # Use 'include' parameter if provided, otherwise fall back to 'file_pattern'
465
- return await tool_self.call(
466
- ctx, pattern=pattern, path=path, include=include
467
- )
449
+ return await tool_self.call(ctx, pattern=pattern, path=path, include=include)
@@ -170,15 +170,11 @@ If you want to create a new file, use:
170
170
  expected_replacements = edit.get("expected_replacements", 1)
171
171
 
172
172
  if old_string is None:
173
- await tool_ctx.error(
174
- f"Parameter 'old_string' in edit at index {i} is required but was None"
175
- )
173
+ await tool_ctx.error(f"Parameter 'old_string' in edit at index {i} is required but was None")
176
174
  return f"Error: Parameter 'old_string' in edit at index {i} is required but was None"
177
175
 
178
176
  if new_string is None:
179
- await tool_ctx.error(
180
- f"Parameter 'new_string' in edit at index {i} is required but was None"
181
- )
177
+ await tool_ctx.error(f"Parameter 'new_string' in edit at index {i} is required but was None")
182
178
  return f"Error: Parameter 'new_string' in edit at index {i} is required but was None"
183
179
 
184
180
  if (
@@ -192,12 +188,8 @@ If you want to create a new file, use:
192
188
  return f"Error: Parameter 'expected_replacements' in edit at index {i} must be a non-negative number"
193
189
 
194
190
  if old_string == new_string:
195
- await tool_ctx.error(
196
- f"Edit at index {i}: old_string and new_string are identical"
197
- )
198
- return (
199
- f"Error: Edit at index {i}: old_string and new_string are identical"
200
- )
191
+ await tool_ctx.error(f"Edit at index {i}: old_string and new_string are identical")
192
+ return f"Error: Edit at index {i}: old_string and new_string are identical"
201
193
 
202
194
  await tool_ctx.info(f"Applying {len(edits)} edits to file: {file_path}")
203
195
 
@@ -261,9 +253,7 @@ If you want to create a new file, use:
261
253
 
262
254
  # Check if old_string exists in current content
263
255
  if old_string not in current_content:
264
- edit_index = (
265
- i + 1 if not creation_mode else i + 2
266
- ) # Adjust for display
256
+ edit_index = i + 1 if not creation_mode else i + 2 # Adjust for display
267
257
  await tool_ctx.error(
268
258
  f"Edit {edit_index}: The specified old_string was not found in the file content"
269
259
  )
@@ -274,9 +264,7 @@ If you want to create a new file, use:
274
264
 
275
265
  # Check if the number of occurrences matches expected_replacements
276
266
  if occurrences != expected_replacements:
277
- edit_index = (
278
- i + 1 if not creation_mode else i + 2
279
- ) # Adjust for display
267
+ edit_index = i + 1 if not creation_mode else i + 2 # Adjust for display
280
268
  await tool_ctx.error(
281
269
  f"Edit {edit_index}: Found {occurrences} occurrences of the specified old_string, but expected {expected_replacements}"
282
270
  )
@@ -118,18 +118,12 @@ Usage:
118
118
  await tool_ctx.error("Parameter 'file_path' is required but was None")
119
119
  return "Error: Parameter 'file_path' is required but was None"
120
120
 
121
- await tool_ctx.info(
122
- f"Reading file: {file_path} (offset: {offset}, limit: {limit})"
123
- )
121
+ await tool_ctx.info(f"Reading file: {file_path} (offset: {offset}, limit: {limit})")
124
122
 
125
123
  # Check if path is allowed
126
124
  if not self.is_path_allowed(file_path):
127
- await tool_ctx.error(
128
- f"Access denied - path outside allowed directories: {file_path}"
129
- )
130
- return (
131
- f"Error: Access denied - path outside allowed directories: {file_path}"
132
- )
125
+ await tool_ctx.error(f"Access denied - path outside allowed directories: {file_path}")
126
+ return f"Error: Access denied - path outside allowed directories: {file_path}"
133
127
 
134
128
  try:
135
129
  file_path_obj = Path(file_path)
@@ -166,10 +160,7 @@ Usage:
166
160
 
167
161
  # Truncate long lines
168
162
  if len(line) > self.MAX_LINE_LENGTH:
169
- line = (
170
- line[: self.MAX_LINE_LENGTH]
171
- + self.LINE_TRUNCATION_INDICATOR
172
- )
163
+ line = line[: self.MAX_LINE_LENGTH] + self.LINE_TRUNCATION_INDICATOR
173
164
 
174
165
  # Add line with line number (1-based)
175
166
  lines.append(f"{i + 1:6d} {line.rstrip()}")
@@ -196,17 +187,12 @@ Usage:
196
187
 
197
188
  # Truncate long lines
198
189
  if len(line) > self.MAX_LINE_LENGTH:
199
- line = (
200
- line[: self.MAX_LINE_LENGTH]
201
- + self.LINE_TRUNCATION_INDICATOR
202
- )
190
+ line = line[: self.MAX_LINE_LENGTH] + self.LINE_TRUNCATION_INDICATOR
203
191
 
204
192
  # Add line with line number (1-based)
205
193
  lines.append(f"{i + 1:6d} {line.rstrip()}")
206
194
 
207
- await tool_ctx.warning(
208
- f"File read with latin-1 encoding: {file_path}"
209
- )
195
+ await tool_ctx.warning(f"File read with latin-1 encoding: {file_path}")
210
196
 
211
197
  except Exception:
212
198
  await tool_ctx.error(f"Cannot read binary file: {file_path}")
@@ -236,9 +222,7 @@ Usage:
236
222
  await tool_ctx.error(f"Error reading file: {str(e)}")
237
223
  return f"Error: {str(e)}"
238
224
 
239
- async def run(
240
- self, ctx: MCPContext, file_path: str, offset: int = 0, limit: int = 2000
241
- ) -> str:
225
+ async def run(self, ctx: MCPContext, file_path: str, offset: int = 0, limit: int = 2000) -> str:
242
226
  """Run method for backwards compatibility with test scripts.
243
227
 
244
228
  Args:
@@ -271,6 +255,4 @@ Usage:
271
255
  offset: Offset = 0,
272
256
  limit: Limit = 2000,
273
257
  ) -> str:
274
- return await tool_self.call(
275
- ctx, file_path=file_path, offset=offset, limit=limit
276
- )
258
+ return await tool_self.call(ctx, file_path=file_path, offset=offset, limit=limit)
@@ -142,9 +142,7 @@ understand project-specific requirements and preferences."""
142
142
  found_configs.append(
143
143
  {
144
144
  "path": str(config_path),
145
- "relative_path": str(
146
- config_path.relative_to(start_path)
147
- ),
145
+ "relative_path": str(config_path.relative_to(start_path)),
148
146
  "content": content,
149
147
  "size": len(content),
150
148
  }
@@ -152,9 +150,7 @@ understand project-specific requirements and preferences."""
152
150
 
153
151
  await tool_ctx.info(f"Found configuration: {config_path}")
154
152
  except Exception as e:
155
- await tool_ctx.warning(
156
- f"Could not read {config_path}: {str(e)}"
157
- )
153
+ await tool_ctx.warning(f"Could not read {config_path}: {str(e)}")
158
154
 
159
155
  # Check if we've reached the root or a git repository root
160
156
  if current_path.parent == current_path:
@@ -169,8 +165,7 @@ understand project-specific requirements and preferences."""
169
165
  if (
170
166
  config_path.exists()
171
167
  and config_path.is_file()
172
- and str(config_path)
173
- not in [c["path"] for c in found_configs]
168
+ and str(config_path) not in [c["path"] for c in found_configs]
174
169
  ):
175
170
  try:
176
171
  if self.is_path_allowed(str(config_path)):
@@ -180,21 +175,15 @@ understand project-specific requirements and preferences."""
180
175
  found_configs.append(
181
176
  {
182
177
  "path": str(config_path),
183
- "relative_path": str(
184
- config_path.relative_to(start_path)
185
- ),
178
+ "relative_path": str(config_path.relative_to(start_path)),
186
179
  "content": content,
187
180
  "size": len(content),
188
181
  }
189
182
  )
190
183
 
191
- await tool_ctx.info(
192
- f"Found configuration: {config_path}"
193
- )
184
+ await tool_ctx.info(f"Found configuration: {config_path}")
194
185
  except Exception as e:
195
- await tool_ctx.warning(
196
- f"Could not read {config_path}: {str(e)}"
197
- )
186
+ await tool_ctx.warning(f"Could not read {config_path}: {str(e)}")
198
187
  break
199
188
 
200
189
  # Move to parent directory