hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.0__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.
- hanzo_mcp/__init__.py +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
"""Git search tool for searching through git history."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import subprocess
|
|
5
4
|
import re
|
|
6
|
-
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
7
7
|
|
|
8
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
9
8
|
from pydantic import Field
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
10
10
|
|
|
11
11
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
12
12
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
13
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
Pattern = Annotated[
|
|
17
16
|
str,
|
|
18
17
|
Field(
|
|
@@ -188,7 +187,7 @@ Examples:
|
|
|
188
187
|
|
|
189
188
|
# Resolve absolute path
|
|
190
189
|
abs_path = os.path.abspath(path)
|
|
191
|
-
|
|
190
|
+
|
|
192
191
|
# Check permissions
|
|
193
192
|
if not self.permission_manager.has_permission(abs_path):
|
|
194
193
|
return f"Permission denied: {abs_path}"
|
|
@@ -210,23 +209,51 @@ Examples:
|
|
|
210
209
|
try:
|
|
211
210
|
if search_type == "content":
|
|
212
211
|
return await self._search_content(
|
|
213
|
-
abs_path,
|
|
214
|
-
|
|
212
|
+
abs_path,
|
|
213
|
+
pattern,
|
|
214
|
+
case_sensitive,
|
|
215
|
+
max_count,
|
|
216
|
+
branch,
|
|
217
|
+
file_pattern,
|
|
218
|
+
tool_ctx,
|
|
215
219
|
)
|
|
216
220
|
elif search_type == "commits":
|
|
217
221
|
return await self._search_commits(
|
|
218
|
-
abs_path,
|
|
219
|
-
|
|
222
|
+
abs_path,
|
|
223
|
+
pattern,
|
|
224
|
+
case_sensitive,
|
|
225
|
+
max_count,
|
|
226
|
+
branch,
|
|
227
|
+
author,
|
|
228
|
+
since,
|
|
229
|
+
until,
|
|
230
|
+
file_pattern,
|
|
231
|
+
tool_ctx,
|
|
220
232
|
)
|
|
221
233
|
elif search_type == "diff":
|
|
222
234
|
return await self._search_diff(
|
|
223
|
-
abs_path,
|
|
224
|
-
|
|
235
|
+
abs_path,
|
|
236
|
+
pattern,
|
|
237
|
+
case_sensitive,
|
|
238
|
+
max_count,
|
|
239
|
+
branch,
|
|
240
|
+
author,
|
|
241
|
+
since,
|
|
242
|
+
until,
|
|
243
|
+
file_pattern,
|
|
244
|
+
tool_ctx,
|
|
225
245
|
)
|
|
226
246
|
elif search_type == "log":
|
|
227
247
|
return await self._search_log(
|
|
228
|
-
abs_path,
|
|
229
|
-
|
|
248
|
+
abs_path,
|
|
249
|
+
pattern,
|
|
250
|
+
max_count,
|
|
251
|
+
branch,
|
|
252
|
+
author,
|
|
253
|
+
since,
|
|
254
|
+
until,
|
|
255
|
+
file_pattern,
|
|
256
|
+
tool_ctx,
|
|
230
257
|
)
|
|
231
258
|
elif search_type == "blame":
|
|
232
259
|
return await self._search_blame(
|
|
@@ -243,32 +270,35 @@ Examples:
|
|
|
243
270
|
return f"Error: {str(e)}"
|
|
244
271
|
|
|
245
272
|
async def _search_content(
|
|
246
|
-
self,
|
|
247
|
-
|
|
248
|
-
|
|
273
|
+
self,
|
|
274
|
+
repo_path: str,
|
|
275
|
+
pattern: str,
|
|
276
|
+
case_sensitive: bool,
|
|
277
|
+
max_count: int,
|
|
278
|
+
branch: str | None,
|
|
279
|
+
file_pattern: str | None,
|
|
280
|
+
tool_ctx,
|
|
249
281
|
) -> str:
|
|
250
282
|
"""Search file contents in git history."""
|
|
251
283
|
cmd = ["git", "grep", "-n", f"--max-count={max_count}"]
|
|
252
|
-
|
|
284
|
+
|
|
253
285
|
if not case_sensitive:
|
|
254
286
|
cmd.append("-i")
|
|
255
|
-
|
|
287
|
+
|
|
256
288
|
if branch:
|
|
257
289
|
cmd.append(branch)
|
|
258
290
|
else:
|
|
259
291
|
cmd.append("--all") # Search all branches
|
|
260
|
-
|
|
292
|
+
|
|
261
293
|
cmd.append(pattern)
|
|
262
|
-
|
|
294
|
+
|
|
263
295
|
if file_pattern:
|
|
264
296
|
cmd.extend(["--", file_pattern])
|
|
265
|
-
|
|
266
|
-
result = subprocess.run(
|
|
267
|
-
|
|
268
|
-
)
|
|
269
|
-
|
|
297
|
+
|
|
298
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
299
|
+
|
|
270
300
|
if result.returncode == 0:
|
|
271
|
-
lines = result.stdout.strip().split(
|
|
301
|
+
lines = result.stdout.strip().split("\n")
|
|
272
302
|
if lines and lines[0]:
|
|
273
303
|
await tool_ctx.info(f"Found {len(lines)} matches")
|
|
274
304
|
return self._format_grep_results(lines, pattern)
|
|
@@ -282,43 +312,51 @@ Examples:
|
|
|
282
312
|
)
|
|
283
313
|
|
|
284
314
|
async def _search_commits(
|
|
285
|
-
self,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
self,
|
|
316
|
+
repo_path: str,
|
|
317
|
+
pattern: str,
|
|
318
|
+
case_sensitive: bool,
|
|
319
|
+
max_count: int,
|
|
320
|
+
branch: str | None,
|
|
321
|
+
author: str | None,
|
|
322
|
+
since: str | None,
|
|
323
|
+
until: str | None,
|
|
324
|
+
file_pattern: str | None,
|
|
325
|
+
tool_ctx,
|
|
289
326
|
) -> str:
|
|
290
327
|
"""Search commit messages."""
|
|
291
328
|
cmd = ["git", "log", f"--max-count={max_count}", "--oneline"]
|
|
292
|
-
|
|
329
|
+
|
|
293
330
|
grep_flag = "--grep" if case_sensitive else "--grep-ignore-case"
|
|
294
331
|
cmd.extend([grep_flag, pattern])
|
|
295
|
-
|
|
332
|
+
|
|
296
333
|
if branch:
|
|
297
334
|
cmd.append(branch)
|
|
298
335
|
else:
|
|
299
336
|
cmd.append("--all")
|
|
300
|
-
|
|
337
|
+
|
|
301
338
|
if author:
|
|
302
339
|
cmd.extend(["--author", author])
|
|
303
|
-
|
|
340
|
+
|
|
304
341
|
if since:
|
|
305
342
|
cmd.extend(["--since", since])
|
|
306
|
-
|
|
343
|
+
|
|
307
344
|
if until:
|
|
308
345
|
cmd.extend(["--until", until])
|
|
309
|
-
|
|
346
|
+
|
|
310
347
|
if file_pattern:
|
|
311
348
|
cmd.extend(["--", file_pattern])
|
|
312
|
-
|
|
313
|
-
result = subprocess.run(
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
|
|
349
|
+
|
|
350
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
351
|
+
|
|
317
352
|
if result.returncode == 0:
|
|
318
|
-
lines = result.stdout.strip().split(
|
|
353
|
+
lines = result.stdout.strip().split("\n")
|
|
319
354
|
if lines and lines[0]:
|
|
320
355
|
await tool_ctx.info(f"Found {len(lines)} commits")
|
|
321
|
-
return
|
|
356
|
+
return (
|
|
357
|
+
f"Found {len(lines)} commits matching '{pattern}':\n\n"
|
|
358
|
+
+ result.stdout
|
|
359
|
+
)
|
|
322
360
|
else:
|
|
323
361
|
return f"No commits found matching: {pattern}"
|
|
324
362
|
else:
|
|
@@ -327,51 +365,59 @@ Examples:
|
|
|
327
365
|
)
|
|
328
366
|
|
|
329
367
|
async def _search_diff(
|
|
330
|
-
self,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
368
|
+
self,
|
|
369
|
+
repo_path: str,
|
|
370
|
+
pattern: str,
|
|
371
|
+
case_sensitive: bool,
|
|
372
|
+
max_count: int,
|
|
373
|
+
branch: str | None,
|
|
374
|
+
author: str | None,
|
|
375
|
+
since: str | None,
|
|
376
|
+
until: str | None,
|
|
377
|
+
file_pattern: str | None,
|
|
378
|
+
tool_ctx,
|
|
334
379
|
) -> str:
|
|
335
380
|
"""Search for pattern in diffs (when code was added/removed)."""
|
|
336
381
|
cmd = ["git", "log", f"--max-count={max_count}", "-p"]
|
|
337
|
-
|
|
382
|
+
|
|
338
383
|
# Use -G for diff search (shows commits that added/removed pattern)
|
|
339
384
|
search_flag = f"-G{pattern}"
|
|
340
385
|
if not case_sensitive:
|
|
341
386
|
# For case-insensitive, we need to use -G with regex
|
|
342
387
|
import re
|
|
388
|
+
|
|
343
389
|
case_insensitive_pattern = "".join(
|
|
344
390
|
f"[{c.upper()}{c.lower()}]" if c.isalpha() else re.escape(c)
|
|
345
391
|
for c in pattern
|
|
346
392
|
)
|
|
347
393
|
search_flag = f"-G{case_insensitive_pattern}"
|
|
348
|
-
|
|
394
|
+
|
|
349
395
|
cmd.append(search_flag)
|
|
350
|
-
|
|
396
|
+
|
|
351
397
|
if branch:
|
|
352
398
|
cmd.append(branch)
|
|
353
399
|
else:
|
|
354
400
|
cmd.append("--all")
|
|
355
|
-
|
|
401
|
+
|
|
356
402
|
if author:
|
|
357
403
|
cmd.extend(["--author", author])
|
|
358
|
-
|
|
404
|
+
|
|
359
405
|
if since:
|
|
360
406
|
cmd.extend(["--since", since])
|
|
361
|
-
|
|
407
|
+
|
|
362
408
|
if until:
|
|
363
409
|
cmd.extend(["--until", until])
|
|
364
|
-
|
|
410
|
+
|
|
365
411
|
if file_pattern:
|
|
366
412
|
cmd.extend(["--", file_pattern])
|
|
367
|
-
|
|
368
|
-
result = subprocess.run(
|
|
369
|
-
|
|
370
|
-
)
|
|
371
|
-
|
|
413
|
+
|
|
414
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
415
|
+
|
|
372
416
|
if result.returncode == 0 and result.stdout.strip():
|
|
373
417
|
# Parse and highlight matching lines
|
|
374
|
-
output = self._highlight_diff_matches(
|
|
418
|
+
output = self._highlight_diff_matches(
|
|
419
|
+
result.stdout, pattern, case_sensitive
|
|
420
|
+
)
|
|
375
421
|
matches = output.count("commit ")
|
|
376
422
|
await tool_ctx.info(f"Found {matches} commits with changes")
|
|
377
423
|
return f"Found {matches} commits with changes matching '{pattern}':\n\n{output}"
|
|
@@ -379,83 +425,90 @@ Examples:
|
|
|
379
425
|
return f"No changes found matching: {pattern}"
|
|
380
426
|
|
|
381
427
|
async def _search_log(
|
|
382
|
-
self,
|
|
383
|
-
|
|
384
|
-
|
|
428
|
+
self,
|
|
429
|
+
repo_path: str,
|
|
430
|
+
pattern: str | None,
|
|
431
|
+
max_count: int,
|
|
432
|
+
branch: str | None,
|
|
433
|
+
author: str | None,
|
|
434
|
+
since: str | None,
|
|
435
|
+
until: str | None,
|
|
436
|
+
file_pattern: str | None,
|
|
437
|
+
tool_ctx,
|
|
385
438
|
) -> str:
|
|
386
439
|
"""Search git log with filters."""
|
|
387
440
|
cmd = ["git", "log", f"--max-count={max_count}", "--oneline"]
|
|
388
|
-
|
|
441
|
+
|
|
389
442
|
if pattern:
|
|
390
443
|
# Search in commit message and changes
|
|
391
444
|
cmd.extend(["--grep", pattern, f"-G{pattern}"])
|
|
392
|
-
|
|
445
|
+
|
|
393
446
|
if branch:
|
|
394
447
|
cmd.append(branch)
|
|
395
448
|
else:
|
|
396
449
|
cmd.append("--all")
|
|
397
|
-
|
|
450
|
+
|
|
398
451
|
if author:
|
|
399
452
|
cmd.extend(["--author", author])
|
|
400
|
-
|
|
453
|
+
|
|
401
454
|
if since:
|
|
402
455
|
cmd.extend(["--since", since])
|
|
403
|
-
|
|
456
|
+
|
|
404
457
|
if until:
|
|
405
458
|
cmd.extend(["--until", until])
|
|
406
|
-
|
|
459
|
+
|
|
407
460
|
if file_pattern:
|
|
408
461
|
cmd.extend(["--", file_pattern])
|
|
409
|
-
|
|
410
|
-
result = subprocess.run(
|
|
411
|
-
|
|
412
|
-
)
|
|
413
|
-
|
|
462
|
+
|
|
463
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
464
|
+
|
|
414
465
|
if result.returncode == 0 and result.stdout.strip():
|
|
415
|
-
lines = result.stdout.strip().split(
|
|
466
|
+
lines = result.stdout.strip().split("\n")
|
|
416
467
|
await tool_ctx.info(f"Found {len(lines)} commits")
|
|
417
468
|
return f"Found {len(lines)} commits:\n\n" + result.stdout
|
|
418
469
|
else:
|
|
419
470
|
return "No commits found matching criteria"
|
|
420
471
|
|
|
421
472
|
async def _search_blame(
|
|
422
|
-
self,
|
|
423
|
-
|
|
473
|
+
self,
|
|
474
|
+
repo_path: str,
|
|
475
|
+
pattern: str,
|
|
476
|
+
case_sensitive: bool,
|
|
477
|
+
file_pattern: str | None,
|
|
478
|
+
tool_ctx,
|
|
424
479
|
) -> str:
|
|
425
480
|
"""Search using git blame to find who changed lines."""
|
|
426
481
|
if not file_pattern:
|
|
427
482
|
return "Error: file_pattern is required for blame search"
|
|
428
|
-
|
|
483
|
+
|
|
429
484
|
# First, find files matching the pattern
|
|
430
485
|
cmd = ["git", "ls-files", file_pattern]
|
|
431
|
-
result = subprocess.run(
|
|
432
|
-
|
|
433
|
-
)
|
|
434
|
-
|
|
486
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
487
|
+
|
|
435
488
|
if result.returncode != 0 or not result.stdout.strip():
|
|
436
489
|
return f"No files found matching: {file_pattern}"
|
|
437
|
-
|
|
438
|
-
files = result.stdout.strip().split(
|
|
490
|
+
|
|
491
|
+
files = result.stdout.strip().split("\n")
|
|
439
492
|
all_matches = []
|
|
440
|
-
|
|
493
|
+
|
|
441
494
|
for file_path in files[:10]: # Limit to 10 files
|
|
442
495
|
# Get blame for the file
|
|
443
496
|
cmd = ["git", "blame", "-l", file_path]
|
|
444
|
-
result = subprocess.run(
|
|
445
|
-
|
|
446
|
-
)
|
|
447
|
-
|
|
497
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True)
|
|
498
|
+
|
|
448
499
|
if result.returncode == 0:
|
|
449
500
|
# Search for pattern in blame output
|
|
450
501
|
flags = 0 if case_sensitive else re.IGNORECASE
|
|
451
|
-
for line in result.stdout.split(
|
|
502
|
+
for line in result.stdout.split("\n"):
|
|
452
503
|
if re.search(pattern, line, flags):
|
|
453
504
|
all_matches.append(f"{file_path}: {line}")
|
|
454
|
-
|
|
505
|
+
|
|
455
506
|
if all_matches:
|
|
456
507
|
await tool_ctx.info(f"Found {len(all_matches)} matching lines")
|
|
457
|
-
return
|
|
458
|
-
|
|
508
|
+
return (
|
|
509
|
+
f"Found {len(all_matches)} lines matching '{pattern}':\n\n"
|
|
510
|
+
+ "\n".join(all_matches[:50])
|
|
511
|
+
) # Limit output
|
|
459
512
|
else:
|
|
460
513
|
return f"No lines found matching: {pattern}"
|
|
461
514
|
|
|
@@ -463,43 +516,43 @@ Examples:
|
|
|
463
516
|
"""Format git grep results nicely."""
|
|
464
517
|
output = []
|
|
465
518
|
current_ref = None
|
|
466
|
-
|
|
519
|
+
|
|
467
520
|
for line in lines:
|
|
468
|
-
if
|
|
469
|
-
parts = line.split(
|
|
521
|
+
if ":" in line:
|
|
522
|
+
parts = line.split(":", 3)
|
|
470
523
|
if len(parts) >= 3:
|
|
471
524
|
ref = parts[0]
|
|
472
525
|
file_path = parts[1]
|
|
473
526
|
line_num = parts[2]
|
|
474
527
|
content = parts[3] if len(parts) > 3 else ""
|
|
475
|
-
|
|
528
|
+
|
|
476
529
|
if ref != current_ref:
|
|
477
530
|
current_ref = ref
|
|
478
531
|
output.append(f"\n=== {ref} ===")
|
|
479
|
-
|
|
532
|
+
|
|
480
533
|
output.append(f"{file_path}:{line_num}: {content}")
|
|
481
|
-
|
|
534
|
+
|
|
482
535
|
return f"Found matches for '{pattern}':\n" + "\n".join(output)
|
|
483
536
|
|
|
484
537
|
def _highlight_diff_matches(
|
|
485
538
|
self, diff_output: str, pattern: str, case_sensitive: bool
|
|
486
539
|
) -> str:
|
|
487
540
|
"""Highlight matching lines in diff output."""
|
|
488
|
-
lines = diff_output.split(
|
|
541
|
+
lines = diff_output.split("\n")
|
|
489
542
|
output = []
|
|
490
543
|
flags = 0 if case_sensitive else re.IGNORECASE
|
|
491
|
-
|
|
544
|
+
|
|
492
545
|
for line in lines:
|
|
493
|
-
if line.startswith((
|
|
546
|
+
if line.startswith(("+", "-")) and not line.startswith(("+++", "---")):
|
|
494
547
|
if re.search(pattern, line[1:], flags):
|
|
495
548
|
output.append(f">>> {line}") # Highlight matching lines
|
|
496
549
|
else:
|
|
497
550
|
output.append(line)
|
|
498
551
|
else:
|
|
499
552
|
output.append(line)
|
|
500
|
-
|
|
553
|
+
|
|
501
554
|
return "\n".join(output)
|
|
502
555
|
|
|
503
556
|
def register(self, mcp_server) -> None:
|
|
504
557
|
"""Register this tool with the MCP server."""
|
|
505
|
-
pass
|
|
558
|
+
pass
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
This module provides the Grep tool for finding text patterns in files using ripgrep.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import asyncio
|
|
7
|
-
import fnmatch
|
|
8
|
-
import json
|
|
9
6
|
import re
|
|
7
|
+
import json
|
|
10
8
|
import shlex
|
|
11
9
|
import shutil
|
|
10
|
+
import asyncio
|
|
11
|
+
import fnmatch
|
|
12
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
12
13
|
from pathlib import Path
|
|
13
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
14
14
|
|
|
15
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
16
|
-
from mcp.server import FastMCP
|
|
17
15
|
from pydantic import Field
|
|
16
|
+
from mcp.server import FastMCP
|
|
17
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
18
18
|
|
|
19
19
|
from hanzo_mcp.tools.common.context import ToolContext
|
|
20
20
|
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
@@ -426,7 +426,7 @@ When you are doing an open ended search that may require multiple rounds of glob
|
|
|
426
426
|
return truncate_response(
|
|
427
427
|
result,
|
|
428
428
|
max_tokens=25000,
|
|
429
|
-
truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]"
|
|
429
|
+
truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]",
|
|
430
430
|
)
|
|
431
431
|
else:
|
|
432
432
|
await tool_ctx.info(
|
|
@@ -436,7 +436,7 @@ When you are doing an open ended search that may require multiple rounds of glob
|
|
|
436
436
|
return truncate_response(
|
|
437
437
|
result,
|
|
438
438
|
max_tokens=25000,
|
|
439
|
-
truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]"
|
|
439
|
+
truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]",
|
|
440
440
|
)
|
|
441
441
|
except Exception as e:
|
|
442
442
|
await tool_ctx.error(f"Error in grep tool: {str(e)}")
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
This module provides the MultiEdit tool for making multiple precise text replacements in files.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
6
7
|
from difflib import unified_diff
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
9
9
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
10
|
from pydantic import Field
|
|
11
|
+
from mcp.server import FastMCP
|
|
12
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
15
|
|
|
@@ -348,11 +348,7 @@ If you want to create a new file, use:
|
|
|
348
348
|
tool_self = self # Create a reference to self for use in the closure
|
|
349
349
|
|
|
350
350
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
351
|
-
async def multi_edit(
|
|
352
|
-
file_path: FilePath,
|
|
353
|
-
edits: Edits,
|
|
354
|
-
ctx: MCPContext
|
|
355
|
-
) -> str:
|
|
351
|
+
async def multi_edit(file_path: FilePath, edits: Edits, ctx: MCPContext) -> str:
|
|
356
352
|
return await tool_self.call(
|
|
357
353
|
ctx,
|
|
358
354
|
file_path=file_path,
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
This module provides the ReadTool for reading the contents of files.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
8
8
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
-
from mcp.server import FastMCP
|
|
11
9
|
from pydantic import Field
|
|
10
|
+
from mcp.server import FastMCP
|
|
11
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
12
|
|
|
13
|
-
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
14
13
|
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
14
|
+
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
15
|
|
|
16
16
|
FilePath = Annotated[
|
|
17
17
|
str,
|
|
@@ -220,12 +220,12 @@ Usage:
|
|
|
220
220
|
result += f"\n... (output truncated, showing {limit} of {limit + truncated_lines}+ lines)"
|
|
221
221
|
|
|
222
222
|
await tool_ctx.info(f"Successfully read file: {file_path}")
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
# Apply token limit to prevent excessive output
|
|
225
225
|
return truncate_response(
|
|
226
226
|
result,
|
|
227
227
|
max_tokens=25000,
|
|
228
|
-
truncation_message="\n\n[File content truncated due to token limit. Use offset/limit parameters to read specific sections.]"
|
|
228
|
+
truncation_message="\n\n[File content truncated due to token limit. Use offset/limit parameters to read specific sections.]",
|
|
229
229
|
)
|
|
230
230
|
|
|
231
231
|
except Exception as e:
|
|
@@ -236,15 +236,17 @@ Usage:
|
|
|
236
236
|
await tool_ctx.error(f"Error reading file: {str(e)}")
|
|
237
237
|
return f"Error: {str(e)}"
|
|
238
238
|
|
|
239
|
-
async def run(
|
|
239
|
+
async def run(
|
|
240
|
+
self, ctx: MCPContext, file_path: str, offset: int = 0, limit: int = 2000
|
|
241
|
+
) -> str:
|
|
240
242
|
"""Run method for backwards compatibility with test scripts.
|
|
241
|
-
|
|
243
|
+
|
|
242
244
|
Args:
|
|
243
245
|
ctx: MCP context
|
|
244
246
|
file_path: Path to file to read
|
|
245
247
|
offset: Line offset to start reading
|
|
246
248
|
limit: Maximum lines to read
|
|
247
|
-
|
|
249
|
+
|
|
248
250
|
Returns:
|
|
249
251
|
File contents
|
|
250
252
|
"""
|
|
@@ -267,7 +269,7 @@ Usage:
|
|
|
267
269
|
ctx: MCPContext,
|
|
268
270
|
file_path: FilePath,
|
|
269
271
|
offset: Offset = 0,
|
|
270
|
-
limit: Limit = 2000
|
|
272
|
+
limit: Limit = 2000,
|
|
271
273
|
) -> str:
|
|
272
274
|
return await tool_self.call(
|
|
273
275
|
ctx, file_path=file_path, offset=offset, limit=limit
|