hanzo-mcp 0.8.8__py3-none-any.whl → 0.8.13__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 +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +4 -17
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +8 -17
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +2 -4
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +6 -7
- hanzo_mcp/tools/__init__.py +13 -29
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +6 -17
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +15 -42
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +76 -75
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/swarm_tool.py +16 -41
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/permissions.py +3 -9
- hanzo_mcp/tools/common/personality.py +9 -34
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +7 -19
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/filesystem/__init__.py +2 -3
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/base.py +4 -12
- hanzo_mcp/tools/filesystem/batch_search.py +35 -115
- hanzo_mcp/tools/filesystem/content_replace.py +4 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
- hanzo_mcp/tools/filesystem/edit.py +6 -18
- hanzo_mcp/tools/filesystem/find.py +3 -9
- hanzo_mcp/tools/filesystem/find_files.py +2 -6
- hanzo_mcp/tools/filesystem/git_search.py +9 -24
- hanzo_mcp/tools/filesystem/grep.py +9 -27
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
- hanzo_mcp/tools/filesystem/read.py +8 -26
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
- hanzo_mcp/tools/filesystem/search_tool.py +18 -62
- hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
- hanzo_mcp/tools/filesystem/tree.py +1 -3
- hanzo_mcp/tools/filesystem/watch.py +1 -3
- hanzo_mcp/tools/filesystem/write.py +1 -3
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
- hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
- hanzo_mcp/tools/mcp/mcp_add.py +3 -5
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +33 -40
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +7 -19
- hanzo_mcp/tools/search/find_tool.py +10 -32
- hanzo_mcp/tools/search/unified_search.py +27 -81
- hanzo_mcp/tools/shell/__init__.py +2 -2
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/todo/todo_read.py +3 -9
- hanzo_mcp/tools/todo/todo_write.py +6 -18
- hanzo_mcp/tools/vector/__init__.py +3 -9
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +11 -30
- hanzo_mcp/tools/vector/mock_infinity.py +159 -0
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/METADATA +2 -2
- hanzo_mcp-0.8.13.dist-info/RECORD +193 -0
- hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.8.13.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/shell/uvx.py
CHANGED
|
@@ -135,9 +135,7 @@ For long-running servers, use uvx_background instead.
|
|
|
135
135
|
|
|
136
136
|
try:
|
|
137
137
|
# Run installation
|
|
138
|
-
install_result = subprocess.run(
|
|
139
|
-
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
140
|
-
)
|
|
138
|
+
install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
141
139
|
|
|
142
140
|
if install_result.returncode == 0:
|
|
143
141
|
await tool_ctx.info("uvx installed successfully!")
|
|
@@ -146,9 +144,7 @@ For long-running servers, use uvx_background instead.
|
|
|
146
144
|
import os
|
|
147
145
|
|
|
148
146
|
home = os.path.expanduser("~")
|
|
149
|
-
os.environ["PATH"] = (
|
|
150
|
-
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
151
|
-
)
|
|
147
|
+
os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
152
148
|
|
|
153
149
|
# Check again
|
|
154
150
|
if not shutil.which("uvx"):
|
|
@@ -197,9 +193,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
197
193
|
|
|
198
194
|
try:
|
|
199
195
|
# Execute command
|
|
200
|
-
result = subprocess.run(
|
|
201
|
-
cmd, capture_output=True, text=True, timeout=timeout, check=True
|
|
202
|
-
)
|
|
196
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=True)
|
|
203
197
|
|
|
204
198
|
output = []
|
|
205
199
|
if result.stdout:
|
|
@@ -207,11 +201,7 @@ curl -LsSf https://astral.sh/uv/install.sh | sh"""
|
|
|
207
201
|
if result.stderr:
|
|
208
202
|
output.append(f"\nSTDERR:\n{result.stderr}")
|
|
209
203
|
|
|
210
|
-
return (
|
|
211
|
-
"\n".join(output)
|
|
212
|
-
if output
|
|
213
|
-
else "Command completed successfully with no output."
|
|
214
|
-
)
|
|
204
|
+
return "\n".join(output) if output else "Command completed successfully with no output."
|
|
215
205
|
|
|
216
206
|
except subprocess.TimeoutExpired:
|
|
217
207
|
return f"Error: Command timed out after {timeout} seconds. Use uvx_background for long-running processes."
|
|
@@ -156,9 +156,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
|
|
|
156
156
|
|
|
157
157
|
try:
|
|
158
158
|
# Run installation
|
|
159
|
-
install_result = subprocess.run(
|
|
160
|
-
install_cmd, shell=True, capture_output=True, text=True, timeout=60
|
|
161
|
-
)
|
|
159
|
+
install_result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
162
160
|
|
|
163
161
|
if install_result.returncode == 0:
|
|
164
162
|
await tool_ctx.info("uvx installed successfully!")
|
|
@@ -167,9 +165,7 @@ Use 'processes' to list running processes and 'pkill' to stop them.
|
|
|
167
165
|
import os
|
|
168
166
|
|
|
169
167
|
home = os.path.expanduser("~")
|
|
170
|
-
os.environ["PATH"] = (
|
|
171
|
-
f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
172
|
-
)
|
|
168
|
+
os.environ["PATH"] = f"{home}/.cargo/bin:{os.environ.get('PATH', '')}"
|
|
173
169
|
|
|
174
170
|
# Check again
|
|
175
171
|
if not shutil.which("uvx"):
|
|
@@ -86,9 +86,7 @@ uvx jupyter lab --port 8888 # Auto-backgrounds if needed"""
|
|
|
86
86
|
cwd: Optional[str] = None,
|
|
87
87
|
python: Optional[str] = None,
|
|
88
88
|
) -> str:
|
|
89
|
-
return await tool_self.run(
|
|
90
|
-
ctx, package=package, args=args, cwd=cwd, python=python
|
|
91
|
-
)
|
|
89
|
+
return await tool_self.run(ctx, package=package, args=args, cwd=cwd, python=python)
|
|
92
90
|
|
|
93
91
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
94
92
|
"""Call the tool with arguments."""
|
|
@@ -29,9 +29,7 @@ class ZshTool(BaseScriptTool):
|
|
|
29
29
|
env: Optional[dict[str, str]] = None,
|
|
30
30
|
timeout: Optional[int] = None,
|
|
31
31
|
) -> str:
|
|
32
|
-
return await tool_self.run(
|
|
33
|
-
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
34
|
-
)
|
|
32
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
35
33
|
|
|
36
34
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
37
35
|
"""Call the tool with arguments."""
|
|
@@ -79,12 +77,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
79
77
|
return path
|
|
80
78
|
# Fall back to bash if no zsh found
|
|
81
79
|
return "bash"
|
|
82
|
-
|
|
80
|
+
|
|
83
81
|
# On Unix-like systems, check for zsh
|
|
84
82
|
zsh_path = shutil.which("zsh")
|
|
85
83
|
if zsh_path:
|
|
86
84
|
return zsh_path
|
|
87
|
-
|
|
85
|
+
|
|
88
86
|
# Fall back to bash if zsh not found
|
|
89
87
|
return "bash"
|
|
90
88
|
|
|
@@ -124,14 +122,12 @@ zsh "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
124
122
|
# Check if zsh is available
|
|
125
123
|
if not shutil.which("zsh") and platform.system() != "Windows":
|
|
126
124
|
return "Error: Zsh is not installed. Please install zsh first."
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
# Prepare working directory
|
|
129
127
|
work_dir = Path(cwd).resolve() if cwd else Path.cwd()
|
|
130
128
|
|
|
131
129
|
# Use execute_sync which has auto-backgrounding
|
|
132
|
-
output = await self.execute_sync(
|
|
133
|
-
command, cwd=work_dir, env=env, timeout=timeout
|
|
134
|
-
)
|
|
130
|
+
output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
|
|
135
131
|
return output if output else "Command completed successfully (no output)"
|
|
136
132
|
|
|
137
133
|
|
|
@@ -152,12 +148,12 @@ class ShellTool(BaseScriptTool):
|
|
|
152
148
|
# Also check if .zshrc exists
|
|
153
149
|
if (Path.home() / ".zshrc").exists():
|
|
154
150
|
return "zsh"
|
|
155
|
-
|
|
151
|
+
|
|
156
152
|
# Check for user's preferred shell
|
|
157
153
|
user_shell = os.environ.get("SHELL", "")
|
|
158
154
|
if user_shell and Path(user_shell).exists():
|
|
159
155
|
return user_shell
|
|
160
|
-
|
|
156
|
+
|
|
161
157
|
# Default to bash
|
|
162
158
|
return "bash"
|
|
163
159
|
|
|
@@ -173,9 +169,7 @@ class ShellTool(BaseScriptTool):
|
|
|
173
169
|
env: Optional[dict[str, str]] = None,
|
|
174
170
|
timeout: Optional[int] = None,
|
|
175
171
|
) -> str:
|
|
176
|
-
return await tool_self.run(
|
|
177
|
-
ctx, command=command, cwd=cwd, env=env, timeout=timeout
|
|
178
|
-
)
|
|
172
|
+
return await tool_self.run(ctx, command=command, cwd=cwd, env=env, timeout=timeout)
|
|
179
173
|
|
|
180
174
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
181
175
|
"""Call the tool with arguments."""
|
|
@@ -249,12 +243,10 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
249
243
|
|
|
250
244
|
# Add shell info to output if verbose
|
|
251
245
|
shell_name = os.path.basename(self._best_shell)
|
|
252
|
-
|
|
246
|
+
|
|
253
247
|
# Use execute_sync which has auto-backgrounding
|
|
254
|
-
output = await self.execute_sync(
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
248
|
+
output = await self.execute_sync(command, cwd=work_dir, env=env, timeout=timeout)
|
|
249
|
+
|
|
258
250
|
if output:
|
|
259
251
|
return output
|
|
260
252
|
else:
|
|
@@ -263,4 +255,4 @@ shell "npm run dev" --cwd ./frontend # Auto-backgrounds if needed"""
|
|
|
263
255
|
|
|
264
256
|
# Create tool instances
|
|
265
257
|
zsh_tool = ZshTool()
|
|
266
|
-
shell_tool = ShellTool() # Smart shell that prefers zsh
|
|
258
|
+
shell_tool = ShellTool() # Smart shell that prefers zsh
|
hanzo_mcp/tools/todo/todo.py
CHANGED
|
@@ -161,9 +161,7 @@ todo --filter in_progress
|
|
|
161
161
|
priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(
|
|
162
162
|
todo.get("priority", "medium"), "⚪"
|
|
163
163
|
)
|
|
164
|
-
output.append(
|
|
165
|
-
f"{priority_icon} [{todo['id'][:8]}] {todo['content']}"
|
|
166
|
-
)
|
|
164
|
+
output.append(f"{priority_icon} [{todo['id'][:8]}] {todo['content']}")
|
|
167
165
|
|
|
168
166
|
# Summary
|
|
169
167
|
output.append(
|
|
@@ -14,9 +14,7 @@ from hanzo_mcp.tools.todo.base import TodoStorage, TodoBaseTool
|
|
|
14
14
|
|
|
15
15
|
SessionId = Annotated[
|
|
16
16
|
str | int | float,
|
|
17
|
-
Field(
|
|
18
|
-
description="Unique identifier for the Claude Desktop session (generate using timestamp command)"
|
|
19
|
-
),
|
|
17
|
+
Field(description="Unique identifier for the Claude Desktop session (generate using timestamp command)"),
|
|
20
18
|
]
|
|
21
19
|
|
|
22
20
|
|
|
@@ -109,13 +107,9 @@ Usage:
|
|
|
109
107
|
|
|
110
108
|
# Log status
|
|
111
109
|
if todos:
|
|
112
|
-
await tool_ctx.info(
|
|
113
|
-
f"Found {len(todos)} todos for session {session_id}"
|
|
114
|
-
)
|
|
110
|
+
await tool_ctx.info(f"Found {len(todos)} todos for session {session_id}")
|
|
115
111
|
else:
|
|
116
|
-
await tool_ctx.info(
|
|
117
|
-
f"No todos found for session {session_id} (returning empty list)"
|
|
118
|
-
)
|
|
112
|
+
await tool_ctx.info(f"No todos found for session {session_id} (returning empty list)")
|
|
119
113
|
|
|
120
114
|
# Return todos as JSON string
|
|
121
115
|
result = json.dumps(todos, indent=2)
|
|
@@ -309,9 +309,7 @@ When in doubt, use this tool. Being proactive with task management demonstrates
|
|
|
309
309
|
|
|
310
310
|
# Log storage stats
|
|
311
311
|
session_count = TodoStorage.get_session_count()
|
|
312
|
-
await tool_ctx.info(
|
|
313
|
-
f"Successfully stored todos. Total active sessions: {session_count}"
|
|
314
|
-
)
|
|
312
|
+
await tool_ctx.info(f"Successfully stored todos. Total active sessions: {session_count}")
|
|
315
313
|
|
|
316
314
|
# Provide feedback about the todos
|
|
317
315
|
if todos:
|
|
@@ -328,23 +326,15 @@ When in doubt, use this tool. Being proactive with task management demonstrates
|
|
|
328
326
|
# Create summary
|
|
329
327
|
summary_parts = []
|
|
330
328
|
if status_counts:
|
|
331
|
-
status_summary = ", ".join(
|
|
332
|
-
[f"{count} {status}" for status, count in status_counts.items()]
|
|
333
|
-
)
|
|
329
|
+
status_summary = ", ".join([f"{count} {status}" for status, count in status_counts.items()])
|
|
334
330
|
summary_parts.append(f"Status: {status_summary}")
|
|
335
331
|
|
|
336
332
|
if priority_counts:
|
|
337
|
-
priority_summary = ", ".join(
|
|
338
|
-
[
|
|
339
|
-
f"{count} {priority}"
|
|
340
|
-
for priority, count in priority_counts.items()
|
|
341
|
-
]
|
|
342
|
-
)
|
|
333
|
+
priority_summary = ", ".join([f"{count} {priority}" for priority, count in priority_counts.items()])
|
|
343
334
|
summary_parts.append(f"Priority: {priority_summary}")
|
|
344
335
|
|
|
345
|
-
summary = (
|
|
346
|
-
|
|
347
|
-
+ "; ".join(summary_parts)
|
|
336
|
+
summary = f"Successfully stored {len(todos)} todos for session {session_id}.\n" + "; ".join(
|
|
337
|
+
summary_parts
|
|
348
338
|
)
|
|
349
339
|
|
|
350
340
|
return summary
|
|
@@ -368,7 +358,5 @@ When in doubt, use this tool. Being proactive with task management demonstrates
|
|
|
368
358
|
tool_self = self # Create a reference to self for use in the closure
|
|
369
359
|
|
|
370
360
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
371
|
-
async def todo_write(
|
|
372
|
-
session_id: SessionId, todos: Todos, ctx: MCPContext
|
|
373
|
-
) -> str:
|
|
361
|
+
async def todo_write(session_id: SessionId, todos: Todos, ctx: MCPContext) -> str:
|
|
374
362
|
return await tool_self.call(ctx, session_id=session_id, todos=todos)
|
|
@@ -56,9 +56,7 @@ try:
|
|
|
56
56
|
store_config = vector_config.copy()
|
|
57
57
|
project_manager = ProjectVectorManager(
|
|
58
58
|
global_db_path=store_config.get("data_path"),
|
|
59
|
-
embedding_model=store_config.get(
|
|
60
|
-
"embedding_model", "text-embedding-3-small"
|
|
61
|
-
),
|
|
59
|
+
embedding_model=store_config.get("embedding_model", "text-embedding-3-small"),
|
|
62
60
|
dimension=store_config.get("dimension", 1536),
|
|
63
61
|
)
|
|
64
62
|
|
|
@@ -68,9 +66,7 @@ try:
|
|
|
68
66
|
import logging
|
|
69
67
|
|
|
70
68
|
logger = logging.getLogger(__name__)
|
|
71
|
-
logger.info(
|
|
72
|
-
f"Detected {len(detected_projects)} projects with LLM.md files"
|
|
73
|
-
)
|
|
69
|
+
logger.info(f"Detected {len(detected_projects)} projects with LLM.md files")
|
|
74
70
|
|
|
75
71
|
# Register individual tools if enabled
|
|
76
72
|
if tool_enabled.get("index", True):
|
|
@@ -97,9 +93,7 @@ except ImportError:
|
|
|
97
93
|
import logging
|
|
98
94
|
|
|
99
95
|
logger = logging.getLogger(__name__)
|
|
100
|
-
logger.warning(
|
|
101
|
-
"Vector tools not available. Install infinity-embedded: pip install infinity-embedded"
|
|
102
|
-
)
|
|
96
|
+
logger.warning("Vector tools not available. Install infinity-embedded: pip install infinity-embedded")
|
|
103
97
|
return []
|
|
104
98
|
|
|
105
99
|
|
|
@@ -130,9 +130,7 @@ class ASTAnalyzer:
|
|
|
130
130
|
return self._analyze_python_file(file_path, content, file_hash)
|
|
131
131
|
else:
|
|
132
132
|
# Generic analysis for other languages
|
|
133
|
-
return self._analyze_generic_file(
|
|
134
|
-
file_path, content, file_hash, language
|
|
135
|
-
)
|
|
133
|
+
return self._analyze_generic_file(file_path, content, file_hash, language)
|
|
136
134
|
|
|
137
135
|
except Exception as e:
|
|
138
136
|
import logging
|
|
@@ -177,9 +175,7 @@ class ASTAnalyzer:
|
|
|
177
175
|
|
|
178
176
|
return language_map.get(extension)
|
|
179
177
|
|
|
180
|
-
def _analyze_python_file(
|
|
181
|
-
self, file_path: str, content: str, file_hash: str
|
|
182
|
-
) -> FileAST:
|
|
178
|
+
def _analyze_python_file(self, file_path: str, content: str, file_hash: str) -> FileAST:
|
|
183
179
|
"""Analyze Python file using both AST and tree-sitter."""
|
|
184
180
|
symbols = []
|
|
185
181
|
ast_nodes = []
|
|
@@ -228,9 +224,7 @@ class ASTAnalyzer:
|
|
|
228
224
|
dependencies=dependencies,
|
|
229
225
|
)
|
|
230
226
|
|
|
231
|
-
def _analyze_generic_file(
|
|
232
|
-
self, file_path: str, content: str, file_hash: str, language: str
|
|
233
|
-
) -> FileAST:
|
|
227
|
+
def _analyze_generic_file(self, file_path: str, content: str, file_hash: str, language: str) -> FileAST:
|
|
234
228
|
"""Generic analysis for non-Python files."""
|
|
235
229
|
# For now, just basic line-based analysis
|
|
236
230
|
# Could be enhanced with language-specific parsers
|
|
@@ -248,11 +242,7 @@ class ASTAnalyzer:
|
|
|
248
242
|
|
|
249
243
|
# Basic function detection (works for many C-style languages)
|
|
250
244
|
if language in ["javascript", "typescript", "java", "cpp", "c"]:
|
|
251
|
-
if (
|
|
252
|
-
"function " in line
|
|
253
|
-
or line.startswith("def ")
|
|
254
|
-
or " function(" in line
|
|
255
|
-
):
|
|
245
|
+
if "function " in line or line.startswith("def ") or " function(" in line:
|
|
256
246
|
# Extract function name
|
|
257
247
|
parts = line.split()
|
|
258
248
|
for j, part in enumerate(parts):
|
|
@@ -399,9 +389,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
|
|
|
399
389
|
|
|
400
390
|
# Extract base classes
|
|
401
391
|
bases = [self._get_name(base) for base in node.bases]
|
|
402
|
-
signature = (
|
|
403
|
-
f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
404
|
-
)
|
|
392
|
+
signature = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
405
393
|
|
|
406
394
|
symbol = Symbol(
|
|
407
395
|
name=node.name,
|
|
@@ -441,9 +429,7 @@ class PythonSymbolExtractor(ast.NodeVisitor):
|
|
|
441
429
|
|
|
442
430
|
for alias in node.names:
|
|
443
431
|
if alias.name != "*":
|
|
444
|
-
import_item =
|
|
445
|
-
f"{node.module}.{alias.name}" if node.module else alias.name
|
|
446
|
-
)
|
|
432
|
+
import_item = f"{node.module}.{alias.name}" if node.module else alias.name
|
|
447
433
|
self.imports.append(import_item)
|
|
448
434
|
|
|
449
435
|
def visit_Assign(self, node):
|
|
@@ -153,16 +153,12 @@ class GitIngester:
|
|
|
153
153
|
)
|
|
154
154
|
return result.stdout.strip()
|
|
155
155
|
|
|
156
|
-
def _get_repository_files(
|
|
157
|
-
self, repo_path: Path, patterns: Optional[List[str]] = None
|
|
158
|
-
) -> List[Path]:
|
|
156
|
+
def _get_repository_files(self, repo_path: Path, patterns: Optional[List[str]] = None) -> List[Path]:
|
|
159
157
|
"""Get list of files in repository matching patterns."""
|
|
160
158
|
# Use git ls-files to respect .gitignore
|
|
161
159
|
cmd = ["git", "ls-files"]
|
|
162
160
|
|
|
163
|
-
result = subprocess.run(
|
|
164
|
-
cmd, cwd=repo_path, capture_output=True, text=True, check=True
|
|
165
|
-
)
|
|
161
|
+
result = subprocess.run(cmd, cwd=repo_path, capture_output=True, text=True, check=True)
|
|
166
162
|
|
|
167
163
|
files = []
|
|
168
164
|
for line in result.stdout.strip().split("\n"):
|
|
@@ -178,9 +174,7 @@ class GitIngester:
|
|
|
178
174
|
|
|
179
175
|
return files
|
|
180
176
|
|
|
181
|
-
def _get_commit_history(
|
|
182
|
-
self, repo_path: Path, branch: str = "HEAD", max_commits: int = 1000
|
|
183
|
-
) -> List[GitCommit]:
|
|
177
|
+
def _get_commit_history(self, repo_path: Path, branch: str = "HEAD", max_commits: int = 1000) -> List[GitCommit]:
|
|
184
178
|
"""Get commit history for the repository."""
|
|
185
179
|
# Get commit list with basic info
|
|
186
180
|
result = subprocess.run(
|
|
@@ -222,9 +216,7 @@ class GitIngester:
|
|
|
222
216
|
|
|
223
217
|
return commits
|
|
224
218
|
|
|
225
|
-
def _get_commit_files(
|
|
226
|
-
self, repo_path: Path, commit_hash: str
|
|
227
|
-
) -> List[Dict[str, str]]:
|
|
219
|
+
def _get_commit_files(self, repo_path: Path, commit_hash: str) -> List[Dict[str, str]]:
|
|
228
220
|
"""Get list of files changed in a commit."""
|
|
229
221
|
result = subprocess.run(
|
|
230
222
|
["git", "show", "--name-status", "--format=", commit_hash],
|
|
@@ -275,21 +267,15 @@ class GitIngester:
|
|
|
275
267
|
if history:
|
|
276
268
|
metadata["first_commit"] = history[-1]["hash"]
|
|
277
269
|
metadata["last_commit"] = history[0]["hash"]
|
|
278
|
-
metadata["last_modified"] = datetime.fromtimestamp(
|
|
279
|
-
history[0]["timestamp"]
|
|
280
|
-
).isoformat()
|
|
270
|
+
metadata["last_modified"] = datetime.fromtimestamp(history[0]["timestamp"]).isoformat()
|
|
281
271
|
|
|
282
272
|
# Add blame information if requested
|
|
283
273
|
if include_blame:
|
|
284
274
|
blame_data = self._get_file_blame(repo_path, relative_path)
|
|
285
|
-
metadata["unique_authors"] = len(
|
|
286
|
-
set(b["author"] for b in blame_data.values())
|
|
287
|
-
)
|
|
275
|
+
metadata["unique_authors"] = len(set(b["author"] for b in blame_data.values()))
|
|
288
276
|
|
|
289
277
|
# Index the file content
|
|
290
|
-
doc_ids = self.vector_store.add_file(
|
|
291
|
-
str(file_path), chunk_size=1000, chunk_overlap=200, metadata=metadata
|
|
292
|
-
)
|
|
278
|
+
doc_ids = self.vector_store.add_file(str(file_path), chunk_size=1000, chunk_overlap=200, metadata=metadata)
|
|
293
279
|
results["files_indexed"] += 1
|
|
294
280
|
|
|
295
281
|
# Perform AST analysis for supported languages
|
|
@@ -309,9 +295,7 @@ class GitIngester:
|
|
|
309
295
|
except Exception as e:
|
|
310
296
|
logger.warning(f"AST analysis failed for {file_path}: {e}")
|
|
311
297
|
|
|
312
|
-
def _get_file_history(
|
|
313
|
-
self, repo_path: Path, file_path: Path
|
|
314
|
-
) -> List[Dict[str, Any]]:
|
|
298
|
+
def _get_file_history(self, repo_path: Path, file_path: Path) -> List[Dict[str, Any]]:
|
|
315
299
|
"""Get commit history for a specific file."""
|
|
316
300
|
result = subprocess.run(
|
|
317
301
|
[
|
|
@@ -346,9 +330,7 @@ class GitIngester:
|
|
|
346
330
|
|
|
347
331
|
return history
|
|
348
332
|
|
|
349
|
-
def _get_file_blame(
|
|
350
|
-
self, repo_path: Path, file_path: Path
|
|
351
|
-
) -> Dict[int, Dict[str, Any]]:
|
|
333
|
+
def _get_file_blame(self, repo_path: Path, file_path: Path) -> Dict[int, Dict[str, Any]]:
|
|
352
334
|
"""Get blame information for a file."""
|
|
353
335
|
result = subprocess.run(
|
|
354
336
|
["git", "blame", "--line-porcelain", "--", str(file_path)],
|
|
@@ -448,9 +430,7 @@ Message: {commit.message}
|
|
|
448
430
|
text=True,
|
|
449
431
|
)
|
|
450
432
|
|
|
451
|
-
remote_url = (
|
|
452
|
-
remote_result.stdout.strip() if remote_result.returncode == 0 else None
|
|
453
|
-
)
|
|
433
|
+
remote_url = remote_result.stdout.strip() if remote_result.returncode == 0 else None
|
|
454
434
|
|
|
455
435
|
# Create repository summary document
|
|
456
436
|
repo_doc = f"""Repository: {repo_path.name}
|
|
@@ -151,13 +151,9 @@ Usage:
|
|
|
151
151
|
if not force:
|
|
152
152
|
stats = await vector_store.get_stats()
|
|
153
153
|
if stats and stats.get("document_count", 0) > 0:
|
|
154
|
-
await tool_ctx.info(
|
|
155
|
-
"Project already indexed, use --force to re-index"
|
|
156
|
-
)
|
|
154
|
+
await tool_ctx.info("Project already indexed, use --force to re-index")
|
|
157
155
|
if show_stats:
|
|
158
|
-
return self._format_stats(
|
|
159
|
-
stats, abs_path, time.time() - start_time
|
|
160
|
-
)
|
|
156
|
+
return self._format_stats(stats, abs_path, time.time() - start_time)
|
|
161
157
|
return "Project is already indexed. Use --force to re-index."
|
|
162
158
|
|
|
163
159
|
# Prepare file patterns
|
|
@@ -268,9 +264,7 @@ Usage:
|
|
|
268
264
|
except Exception as e:
|
|
269
265
|
errors.append(f"{file_path}: {str(e)}")
|
|
270
266
|
|
|
271
|
-
await tool_ctx.info(
|
|
272
|
-
f"Indexed {indexed_files} files ({total_size / 1024 / 1024:.1f} MB)"
|
|
273
|
-
)
|
|
267
|
+
await tool_ctx.info(f"Indexed {indexed_files} files ({total_size / 1024 / 1024:.1f} MB)")
|
|
274
268
|
|
|
275
269
|
# Index git history if requested
|
|
276
270
|
git_stats = {}
|
|
@@ -11,9 +11,10 @@ try:
|
|
|
11
11
|
|
|
12
12
|
INFINITY_AVAILABLE = True
|
|
13
13
|
except ImportError:
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
# Use mock implementation when infinity_embedded is not available
|
|
15
|
+
from . import mock_infinity as infinity_embedded
|
|
16
|
+
|
|
17
|
+
INFINITY_AVAILABLE = True # Mock is always available
|
|
17
18
|
|
|
18
19
|
from .ast_analyzer import Symbol, FileAST, ASTAnalyzer, create_symbol_embedding_text
|
|
19
20
|
|
|
@@ -78,9 +79,7 @@ class InfinityVectorStore:
|
|
|
78
79
|
dimension: Vector dimension (must match embedding model)
|
|
79
80
|
"""
|
|
80
81
|
if not INFINITY_AVAILABLE:
|
|
81
|
-
raise ImportError(
|
|
82
|
-
"infinity_embedded is required for vector store functionality"
|
|
83
|
-
)
|
|
82
|
+
raise ImportError("infinity_embedded is required for vector store functionality")
|
|
84
83
|
|
|
85
84
|
# Set up data path
|
|
86
85
|
if data_path:
|
|
@@ -171,17 +170,13 @@ class InfinityVectorStore:
|
|
|
171
170
|
"source_file": {"type": "varchar"},
|
|
172
171
|
"target_file": {"type": "varchar"},
|
|
173
172
|
"symbol_name": {"type": "varchar"},
|
|
174
|
-
"reference_type": {
|
|
175
|
-
"type": "varchar"
|
|
176
|
-
}, # import, call, inheritance, etc.
|
|
173
|
+
"reference_type": {"type": "varchar"}, # import, call, inheritance, etc.
|
|
177
174
|
"line_number": {"type": "integer"},
|
|
178
175
|
"metadata": {"type": "varchar"}, # JSON string
|
|
179
176
|
},
|
|
180
177
|
)
|
|
181
178
|
|
|
182
|
-
def _generate_doc_id(
|
|
183
|
-
self, content: str, file_path: str = "", chunk_index: int = 0
|
|
184
|
-
) -> str:
|
|
179
|
+
def _generate_doc_id(self, content: str, file_path: str = "", chunk_index: int = 0) -> str:
|
|
185
180
|
"""Generate a unique document ID."""
|
|
186
181
|
content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
187
182
|
path_hash = hashlib.sha256(file_path.encode()).hexdigest()[:8]
|
|
@@ -533,11 +528,7 @@ class InfinityVectorStore:
|
|
|
533
528
|
FileAST object if file found, None otherwise
|
|
534
529
|
"""
|
|
535
530
|
try:
|
|
536
|
-
results = (
|
|
537
|
-
self.ast_table.output(["*"])
|
|
538
|
-
.filter(f"file_path = '{file_path}'")
|
|
539
|
-
.to_pl()
|
|
540
|
-
)
|
|
531
|
+
results = self.ast_table.output(["*"]).filter(f"file_path = '{file_path}'").to_pl()
|
|
541
532
|
|
|
542
533
|
if len(results) == 0:
|
|
543
534
|
return None
|
|
@@ -576,11 +567,7 @@ class InfinityVectorStore:
|
|
|
576
567
|
List of reference information
|
|
577
568
|
"""
|
|
578
569
|
try:
|
|
579
|
-
results = (
|
|
580
|
-
self.references_table.output(["*"])
|
|
581
|
-
.filter(f"target_file = '{file_path}'")
|
|
582
|
-
.to_pl()
|
|
583
|
-
)
|
|
570
|
+
results = self.references_table.output(["*"]).filter(f"target_file = '{file_path}'").to_pl()
|
|
584
571
|
|
|
585
572
|
references = []
|
|
586
573
|
for row in results.iter_rows(named=True):
|
|
@@ -695,11 +682,7 @@ class InfinityVectorStore:
|
|
|
695
682
|
"""
|
|
696
683
|
try:
|
|
697
684
|
# Get count first
|
|
698
|
-
results = (
|
|
699
|
-
self.documents_table.output(["id"])
|
|
700
|
-
.filter(f"file_path = '{file_path}'")
|
|
701
|
-
.to_pl()
|
|
702
|
-
)
|
|
685
|
+
results = self.documents_table.output(["id"]).filter(f"file_path = '{file_path}'").to_pl()
|
|
703
686
|
count = len(results)
|
|
704
687
|
|
|
705
688
|
# Delete all documents for this file
|
|
@@ -725,9 +708,7 @@ class InfinityVectorStore:
|
|
|
725
708
|
metadata = json.loads(row["metadata"])
|
|
726
709
|
files[file_path] = {
|
|
727
710
|
"file_path": file_path,
|
|
728
|
-
"file_name": metadata.get(
|
|
729
|
-
"file_name", Path(file_path).name
|
|
730
|
-
),
|
|
711
|
+
"file_name": metadata.get("file_name", Path(file_path).name),
|
|
731
712
|
"file_size": metadata.get("file_size", 0),
|
|
732
713
|
"total_chunks": metadata.get("total_chunks", 1),
|
|
733
714
|
}
|