hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.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.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""SQL search tool for text search in database."""
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
5
5
|
|
|
6
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
7
6
|
from pydantic import Field
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
10
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
11
11
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
12
|
from hanzo_mcp.tools.database.database_manager import DatabaseManager
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
SearchPattern = Annotated[
|
|
16
15
|
str,
|
|
17
16
|
Field(
|
|
@@ -67,7 +66,9 @@ class SqlSearchParams(TypedDict, total=False):
|
|
|
67
66
|
class SqlSearchTool(BaseTool):
|
|
68
67
|
"""Tool for searching text in SQLite database."""
|
|
69
68
|
|
|
70
|
-
def __init__(
|
|
69
|
+
def __init__(
|
|
70
|
+
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
71
|
+
):
|
|
71
72
|
"""Initialize the SQL search tool.
|
|
72
73
|
|
|
73
74
|
Args:
|
|
@@ -146,8 +147,9 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
146
147
|
project_db = self.db_manager.get_project_db(project_path)
|
|
147
148
|
else:
|
|
148
149
|
import os
|
|
150
|
+
|
|
149
151
|
project_db = self.db_manager.get_project_for_path(os.getcwd())
|
|
150
|
-
|
|
152
|
+
|
|
151
153
|
if not project_db:
|
|
152
154
|
return "Error: Could not find project database"
|
|
153
155
|
|
|
@@ -163,17 +165,17 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
163
165
|
try:
|
|
164
166
|
conn = project_db.get_sqlite_connection()
|
|
165
167
|
cursor = conn.cursor()
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
# Get searchable columns for the table
|
|
168
170
|
if column:
|
|
169
171
|
# Validate column exists
|
|
170
172
|
cursor.execute(f"PRAGMA table_info({table})")
|
|
171
173
|
columns_info = cursor.fetchall()
|
|
172
174
|
column_names = [col[1] for col in columns_info]
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
if column not in column_names:
|
|
175
177
|
return f"Error: Column '{column}' not found in table '{table}'. Available columns: {', '.join(column_names)}"
|
|
176
|
-
|
|
178
|
+
|
|
177
179
|
search_columns = [column]
|
|
178
180
|
else:
|
|
179
181
|
# Get all text columns
|
|
@@ -184,7 +186,7 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
184
186
|
# Build WHERE clause
|
|
185
187
|
where_conditions = [f"{col} LIKE ?" for col in search_columns]
|
|
186
188
|
where_clause = " OR ".join(where_conditions)
|
|
187
|
-
|
|
189
|
+
|
|
188
190
|
# Build query
|
|
189
191
|
if table == "files":
|
|
190
192
|
query = f"""
|
|
@@ -194,7 +196,7 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
194
196
|
LIMIT ?
|
|
195
197
|
"""
|
|
196
198
|
params_list = [pattern] * len(search_columns) + [max_results]
|
|
197
|
-
|
|
199
|
+
|
|
198
200
|
elif table == "symbols":
|
|
199
201
|
query = f"""
|
|
200
202
|
SELECT name, type, file_path, line_start, signature
|
|
@@ -204,7 +206,7 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
204
206
|
LIMIT ?
|
|
205
207
|
"""
|
|
206
208
|
params_list = [pattern] * len(search_columns) + [max_results]
|
|
207
|
-
|
|
209
|
+
|
|
208
210
|
else: # metadata
|
|
209
211
|
query = f"""
|
|
210
212
|
SELECT key, value, updated_at
|
|
@@ -213,17 +215,17 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
213
215
|
LIMIT ?
|
|
214
216
|
"""
|
|
215
217
|
params_list = [pattern] * len(search_columns) + [max_results]
|
|
216
|
-
|
|
218
|
+
|
|
217
219
|
# Execute search
|
|
218
220
|
cursor.execute(query, params_list)
|
|
219
221
|
results = cursor.fetchall()
|
|
220
|
-
|
|
222
|
+
|
|
221
223
|
if not results:
|
|
222
224
|
return f"No results found for pattern '{pattern}' in {table}"
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
# Format results
|
|
225
227
|
output = self._format_results(table, results, pattern, search_columns)
|
|
226
|
-
|
|
228
|
+
|
|
227
229
|
return f"Found {len(results)} result(s) in {table}:\n\n{output}"
|
|
228
230
|
|
|
229
231
|
except sqlite3.Error as e:
|
|
@@ -240,21 +242,23 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
240
242
|
"""Get text columns for a table."""
|
|
241
243
|
cursor.execute(f"PRAGMA table_info({table})")
|
|
242
244
|
columns_info = cursor.fetchall()
|
|
243
|
-
|
|
245
|
+
|
|
244
246
|
# Get TEXT columns
|
|
245
247
|
text_columns = []
|
|
246
248
|
for col in columns_info:
|
|
247
249
|
col_name = col[1]
|
|
248
250
|
col_type = col[2].upper()
|
|
249
|
-
if
|
|
251
|
+
if "TEXT" in col_type or "CHAR" in col_type or col_type == "":
|
|
250
252
|
text_columns.append(col_name)
|
|
251
|
-
|
|
253
|
+
|
|
252
254
|
return text_columns
|
|
253
255
|
|
|
254
|
-
def _format_results(
|
|
256
|
+
def _format_results(
|
|
257
|
+
self, table: str, results: list, pattern: str, search_columns: list[str]
|
|
258
|
+
) -> str:
|
|
255
259
|
"""Format search results based on table type."""
|
|
256
260
|
output = []
|
|
257
|
-
|
|
261
|
+
|
|
258
262
|
if table == "files":
|
|
259
263
|
output.append(f"Searched columns: {', '.join(search_columns)}\n")
|
|
260
264
|
for row in results:
|
|
@@ -264,12 +268,12 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
264
268
|
output.append(f"Modified: {modified}")
|
|
265
269
|
if snippet:
|
|
266
270
|
# Highlight pattern in snippet
|
|
267
|
-
snippet = snippet.replace(
|
|
271
|
+
snippet = snippet.replace("\n", " ")
|
|
268
272
|
if len(snippet) > 150:
|
|
269
273
|
snippet = snippet[:150] + "..."
|
|
270
274
|
output.append(f"Content: {snippet}")
|
|
271
275
|
output.append("-" * 60)
|
|
272
|
-
|
|
276
|
+
|
|
273
277
|
elif table == "symbols":
|
|
274
278
|
output.append(f"Searched columns: {', '.join(search_columns)}\n")
|
|
275
279
|
for row in results:
|
|
@@ -279,7 +283,7 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
279
283
|
if signature:
|
|
280
284
|
output.append(f"Signature: {signature}")
|
|
281
285
|
output.append("-" * 60)
|
|
282
|
-
|
|
286
|
+
|
|
283
287
|
else: # metadata
|
|
284
288
|
output.append(f"Searched columns: {', '.join(search_columns)}\n")
|
|
285
289
|
for row in results:
|
|
@@ -288,9 +292,9 @@ Use sql_query for complex queries with joins, conditions, etc."""
|
|
|
288
292
|
output.append(f"Value: {value}")
|
|
289
293
|
output.append(f"Updated: {updated}")
|
|
290
294
|
output.append("-" * 60)
|
|
291
|
-
|
|
295
|
+
|
|
292
296
|
return "\n".join(output)
|
|
293
297
|
|
|
294
298
|
def register(self, mcp_server) -> None:
|
|
295
299
|
"""Register this tool with the MCP server."""
|
|
296
|
-
pass
|
|
300
|
+
pass
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""SQL statistics tool for database insights."""
|
|
2
2
|
|
|
3
3
|
import sqlite3
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
5
5
|
|
|
6
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
7
6
|
from pydantic import Field
|
|
7
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
8
8
|
|
|
9
9
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
10
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
11
11
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
12
|
from hanzo_mcp.tools.database.database_manager import DatabaseManager
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
ProjectPath = Annotated[
|
|
16
15
|
Optional[str],
|
|
17
16
|
Field(
|
|
@@ -40,7 +39,9 @@ class SqlStatsParams(TypedDict, total=False):
|
|
|
40
39
|
class SqlStatsTool(BaseTool):
|
|
41
40
|
"""Tool for getting SQLite database statistics."""
|
|
42
41
|
|
|
43
|
-
def __init__(
|
|
42
|
+
def __init__(
|
|
43
|
+
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
44
|
+
):
|
|
44
45
|
"""Initialize the SQL stats tool.
|
|
45
46
|
|
|
46
47
|
Args:
|
|
@@ -103,8 +104,9 @@ Examples:
|
|
|
103
104
|
project_db = self.db_manager.get_project_db(project_path)
|
|
104
105
|
else:
|
|
105
106
|
import os
|
|
107
|
+
|
|
106
108
|
project_db = self.db_manager.get_project_for_path(os.getcwd())
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
if not project_db:
|
|
109
111
|
return "Error: Could not find project database"
|
|
110
112
|
|
|
@@ -113,49 +115,53 @@ Examples:
|
|
|
113
115
|
except Exception as e:
|
|
114
116
|
return f"Error accessing project database: {str(e)}"
|
|
115
117
|
|
|
116
|
-
await tool_ctx.info(
|
|
118
|
+
await tool_ctx.info(
|
|
119
|
+
f"Getting statistics for project: {project_db.project_path}"
|
|
120
|
+
)
|
|
117
121
|
|
|
118
122
|
# Collect statistics
|
|
119
123
|
conn = None
|
|
120
124
|
try:
|
|
121
125
|
conn = project_db.get_sqlite_connection()
|
|
122
126
|
cursor = conn.cursor()
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
output = []
|
|
125
129
|
output.append(f"=== SQLite Database Statistics ===")
|
|
126
130
|
output.append(f"Project: {project_db.project_path}")
|
|
127
131
|
output.append(f"Database: {project_db.sqlite_path}")
|
|
128
|
-
|
|
132
|
+
|
|
129
133
|
# Get database size
|
|
130
134
|
db_size = project_db.sqlite_path.stat().st_size
|
|
131
135
|
output.append(f"Database Size: {self._format_size(db_size)}")
|
|
132
136
|
output.append("")
|
|
133
|
-
|
|
137
|
+
|
|
134
138
|
# Get table statistics
|
|
135
|
-
cursor.execute(
|
|
139
|
+
cursor.execute(
|
|
140
|
+
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
141
|
+
)
|
|
136
142
|
tables = cursor.fetchall()
|
|
137
|
-
|
|
143
|
+
|
|
138
144
|
output.append("=== Tables ===")
|
|
139
145
|
total_rows = 0
|
|
140
|
-
|
|
146
|
+
|
|
141
147
|
for (table_name,) in tables:
|
|
142
|
-
if table_name.startswith(
|
|
148
|
+
if table_name.startswith("sqlite_"):
|
|
143
149
|
continue
|
|
144
|
-
|
|
150
|
+
|
|
145
151
|
# Get row count
|
|
146
152
|
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
|
|
147
153
|
row_count = cursor.fetchone()[0]
|
|
148
154
|
total_rows += row_count
|
|
149
|
-
|
|
155
|
+
|
|
150
156
|
# Get table info
|
|
151
157
|
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
152
158
|
columns = cursor.fetchall()
|
|
153
159
|
col_count = len(columns)
|
|
154
|
-
|
|
160
|
+
|
|
155
161
|
output.append(f"\n{table_name}:")
|
|
156
162
|
output.append(f" Rows: {row_count:,}")
|
|
157
163
|
output.append(f" Columns: {col_count}")
|
|
158
|
-
|
|
164
|
+
|
|
159
165
|
if detailed and row_count > 0:
|
|
160
166
|
# Show column details
|
|
161
167
|
output.append(" Columns:")
|
|
@@ -164,44 +170,50 @@ Examples:
|
|
|
164
170
|
col_type = col[2]
|
|
165
171
|
is_pk = col[5]
|
|
166
172
|
not_null = col[3]
|
|
167
|
-
|
|
173
|
+
|
|
168
174
|
flags = []
|
|
169
175
|
if is_pk:
|
|
170
176
|
flags.append("PRIMARY KEY")
|
|
171
177
|
if not_null:
|
|
172
178
|
flags.append("NOT NULL")
|
|
173
|
-
|
|
179
|
+
|
|
174
180
|
flag_str = f" ({', '.join(flags)})" if flags else ""
|
|
175
181
|
output.append(f" - {col_name}: {col_type}{flag_str}")
|
|
176
|
-
|
|
182
|
+
|
|
177
183
|
# Show sample data for specific tables
|
|
178
184
|
if table_name == "files" and row_count > 0:
|
|
179
|
-
cursor.execute(
|
|
185
|
+
cursor.execute(
|
|
186
|
+
f"SELECT COUNT(DISTINCT SUBSTR(path, -3)) as ext_count FROM {table_name}"
|
|
187
|
+
)
|
|
180
188
|
ext_count = cursor.fetchone()[0]
|
|
181
189
|
output.append(f" File types: ~{ext_count}")
|
|
182
|
-
|
|
190
|
+
|
|
183
191
|
elif table_name == "symbols" and row_count > 0:
|
|
184
|
-
cursor.execute(
|
|
192
|
+
cursor.execute(
|
|
193
|
+
f"SELECT type, COUNT(*) as count FROM {table_name} GROUP BY type ORDER BY count DESC LIMIT 5"
|
|
194
|
+
)
|
|
185
195
|
symbol_types = cursor.fetchall()
|
|
186
196
|
output.append(" Symbol types:")
|
|
187
197
|
for sym_type, count in symbol_types:
|
|
188
198
|
output.append(f" - {sym_type}: {count}")
|
|
189
|
-
|
|
199
|
+
|
|
190
200
|
# Get indexes
|
|
191
201
|
cursor.execute(f"PRAGMA index_list({table_name})")
|
|
192
202
|
indexes = cursor.fetchall()
|
|
193
203
|
if indexes:
|
|
194
204
|
output.append(f" Indexes: {len(indexes)}")
|
|
195
|
-
|
|
205
|
+
|
|
196
206
|
output.append(f"\nTotal Rows: {total_rows:,}")
|
|
197
|
-
|
|
207
|
+
|
|
198
208
|
# Get index statistics
|
|
199
|
-
cursor.execute(
|
|
209
|
+
cursor.execute(
|
|
210
|
+
"SELECT name FROM sqlite_master WHERE type='index' AND sql IS NOT NULL ORDER BY name"
|
|
211
|
+
)
|
|
200
212
|
indexes = cursor.fetchall()
|
|
201
213
|
if indexes:
|
|
202
214
|
output.append(f"\n=== Indexes ===")
|
|
203
215
|
output.append(f"Total Indexes: {len(indexes)}")
|
|
204
|
-
|
|
216
|
+
|
|
205
217
|
if detailed:
|
|
206
218
|
for (idx_name,) in indexes:
|
|
207
219
|
cursor.execute(f"PRAGMA index_info({idx_name})")
|
|
@@ -209,26 +221,26 @@ Examples:
|
|
|
209
221
|
if idx_info:
|
|
210
222
|
cols = [info[2] for info in idx_info]
|
|
211
223
|
output.append(f" {idx_name}: ({', '.join(cols)})")
|
|
212
|
-
|
|
224
|
+
|
|
213
225
|
# Database properties
|
|
214
226
|
if detailed:
|
|
215
227
|
output.append("\n=== Database Properties ===")
|
|
216
|
-
|
|
228
|
+
|
|
217
229
|
# Page size
|
|
218
230
|
cursor.execute("PRAGMA page_size")
|
|
219
231
|
page_size = cursor.fetchone()[0]
|
|
220
232
|
output.append(f"Page Size: {page_size:,} bytes")
|
|
221
|
-
|
|
233
|
+
|
|
222
234
|
# Page count
|
|
223
235
|
cursor.execute("PRAGMA page_count")
|
|
224
236
|
page_count = cursor.fetchone()[0]
|
|
225
237
|
output.append(f"Page Count: {page_count:,}")
|
|
226
|
-
|
|
238
|
+
|
|
227
239
|
# Cache size
|
|
228
240
|
cursor.execute("PRAGMA cache_size")
|
|
229
241
|
cache_size = cursor.fetchone()[0]
|
|
230
242
|
output.append(f"Cache Size: {abs(cache_size):,} pages")
|
|
231
|
-
|
|
243
|
+
|
|
232
244
|
return "\n".join(output)
|
|
233
245
|
|
|
234
246
|
except sqlite3.Error as e:
|
|
@@ -243,7 +255,7 @@ Examples:
|
|
|
243
255
|
|
|
244
256
|
def _format_size(self, size: int) -> str:
|
|
245
257
|
"""Format file size in human-readable format."""
|
|
246
|
-
for unit in [
|
|
258
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
247
259
|
if size < 1024.0:
|
|
248
260
|
return f"{size:.1f} {unit}"
|
|
249
261
|
size /= 1024.0
|
|
@@ -251,4 +263,4 @@ Examples:
|
|
|
251
263
|
|
|
252
264
|
def register(self, mcp_server) -> None:
|
|
253
265
|
"""Register this tool with the MCP server."""
|
|
254
|
-
pass
|
|
266
|
+
pass
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"""Execute Neovim commands and macros."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import subprocess
|
|
5
4
|
import shutil
|
|
6
5
|
import tempfile
|
|
7
|
-
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import List, Unpack, Optional, Annotated, TypedDict, final, override
|
|
8
8
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
9
|
from pydantic import Field
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
11
|
|
|
12
12
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
13
13
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
14
14
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
Command = Annotated[
|
|
18
17
|
Optional[str],
|
|
19
18
|
Field(
|
|
@@ -159,7 +158,9 @@ Note: Requires Neovim to be installed.
|
|
|
159
158
|
return "Error: Must provide either 'command', 'commands', or 'macro'"
|
|
160
159
|
|
|
161
160
|
if sum(bool(x) for x in [command, commands, macro]) > 1:
|
|
162
|
-
return
|
|
161
|
+
return (
|
|
162
|
+
"Error: Can only use one of 'command', 'commands', or 'macro' at a time"
|
|
163
|
+
)
|
|
163
164
|
|
|
164
165
|
# Check if Neovim is available
|
|
165
166
|
nvim_cmd = shutil.which("nvim")
|
|
@@ -168,7 +169,7 @@ Note: Requires Neovim to be installed.
|
|
|
168
169
|
|
|
169
170
|
# Prepare commands list
|
|
170
171
|
nvim_commands = []
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
if command:
|
|
173
174
|
nvim_commands.append(command)
|
|
174
175
|
elif commands:
|
|
@@ -178,65 +179,61 @@ Note: Requires Neovim to be installed.
|
|
|
178
179
|
# Escape special characters
|
|
179
180
|
escaped_macro = macro.replace('"', '\\"')
|
|
180
181
|
nvim_commands.append(f':normal "{escaped_macro}"')
|
|
181
|
-
|
|
182
|
+
|
|
182
183
|
# Add save command if requested
|
|
183
184
|
if save_after:
|
|
184
185
|
nvim_commands.append(":w")
|
|
185
|
-
|
|
186
|
+
|
|
186
187
|
# Always quit at the end
|
|
187
188
|
nvim_commands.append(":q")
|
|
188
189
|
|
|
189
190
|
# Build Neovim command line
|
|
190
191
|
cmd = [nvim_cmd, "-n", "-i", "NONE"] # No swap file, no shada file
|
|
191
|
-
|
|
192
|
+
|
|
192
193
|
# Add commands
|
|
193
194
|
for vim_cmd in nvim_commands:
|
|
194
195
|
cmd.extend(["-c", vim_cmd])
|
|
195
|
-
|
|
196
|
+
|
|
196
197
|
# Add file if specified
|
|
197
198
|
if file_path:
|
|
198
199
|
file_path = os.path.abspath(file_path)
|
|
199
|
-
|
|
200
|
+
|
|
200
201
|
# Check permissions
|
|
201
202
|
if not self.permission_manager.has_permission(file_path):
|
|
202
203
|
return f"Error: No permission to access {file_path}"
|
|
203
|
-
|
|
204
|
+
|
|
204
205
|
if not os.path.exists(file_path):
|
|
205
206
|
return f"Error: File not found: {file_path}"
|
|
206
|
-
|
|
207
|
+
|
|
207
208
|
cmd.append(file_path)
|
|
208
209
|
else:
|
|
209
210
|
# Create empty buffer
|
|
210
211
|
cmd.append("-")
|
|
211
|
-
|
|
212
|
+
|
|
212
213
|
await tool_ctx.info(f"Executing Neovim commands: {nvim_commands}")
|
|
213
|
-
|
|
214
|
+
|
|
214
215
|
try:
|
|
215
216
|
# Execute Neovim
|
|
216
217
|
if return_output:
|
|
217
218
|
# Capture output by redirecting messages
|
|
218
|
-
output_file = tempfile.NamedTemporaryFile(mode=
|
|
219
|
+
output_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
|
|
219
220
|
output_file.close()
|
|
220
|
-
|
|
221
|
+
|
|
221
222
|
# Add command to redirect messages
|
|
222
223
|
cmd.insert(3, "-c")
|
|
223
224
|
cmd.insert(4, f":redir! > {output_file.name}")
|
|
224
|
-
|
|
225
|
+
|
|
225
226
|
# Execute
|
|
226
|
-
result = subprocess.run(
|
|
227
|
-
|
|
228
|
-
capture_output=True,
|
|
229
|
-
text=True
|
|
230
|
-
)
|
|
231
|
-
|
|
227
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
228
|
+
|
|
232
229
|
# Read output
|
|
233
230
|
output_content = ""
|
|
234
231
|
try:
|
|
235
|
-
with open(output_file.name,
|
|
232
|
+
with open(output_file.name, "r") as f:
|
|
236
233
|
output_content = f.read().strip()
|
|
237
234
|
finally:
|
|
238
235
|
os.unlink(output_file.name)
|
|
239
|
-
|
|
236
|
+
|
|
240
237
|
if result.returncode == 0:
|
|
241
238
|
response = "Commands executed successfully"
|
|
242
239
|
if file_path:
|
|
@@ -254,7 +251,7 @@ Note: Requires Neovim to be installed.
|
|
|
254
251
|
else:
|
|
255
252
|
# Just execute without capturing output
|
|
256
253
|
result = subprocess.run(cmd)
|
|
257
|
-
|
|
254
|
+
|
|
258
255
|
if result.returncode == 0:
|
|
259
256
|
response = "Commands executed successfully"
|
|
260
257
|
if file_path:
|
|
@@ -262,7 +259,7 @@ Note: Requires Neovim to be installed.
|
|
|
262
259
|
return response
|
|
263
260
|
else:
|
|
264
261
|
return f"Neovim exited with code {result.returncode}"
|
|
265
|
-
|
|
262
|
+
|
|
266
263
|
except Exception as e:
|
|
267
264
|
await tool_ctx.error(f"Failed to execute Neovim commands: {str(e)}")
|
|
268
265
|
return f"Error executing Neovim commands: {str(e)}"
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
"""Open files in Neovim editor."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import subprocess
|
|
5
4
|
import shutil
|
|
6
|
-
|
|
7
|
-
from
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
8
7
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
8
|
from pydantic import Field
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
10
|
|
|
12
11
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
13
12
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
14
13
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
15
14
|
|
|
16
|
-
|
|
17
15
|
FilePath = Annotated[
|
|
18
16
|
str,
|
|
19
17
|
Field(
|
|
@@ -170,7 +168,7 @@ Note: Requires Neovim to be installed and available in PATH.
|
|
|
170
168
|
if os.path.exists(path):
|
|
171
169
|
nvim_cmd = path
|
|
172
170
|
break
|
|
173
|
-
|
|
171
|
+
|
|
174
172
|
if not nvim_cmd:
|
|
175
173
|
return """Error: Neovim (nvim) not found. Install it with:
|
|
176
174
|
|
|
@@ -187,18 +185,18 @@ Or visit: https://neovim.io/"""
|
|
|
187
185
|
|
|
188
186
|
# Convert to absolute path
|
|
189
187
|
file_path = os.path.abspath(file_path)
|
|
190
|
-
|
|
188
|
+
|
|
191
189
|
# Check permissions
|
|
192
190
|
if not self.permission_manager.has_permission(file_path):
|
|
193
191
|
return f"Error: No permission to access {file_path}"
|
|
194
192
|
|
|
195
193
|
# Build Neovim command
|
|
196
194
|
cmd = [nvim_cmd]
|
|
197
|
-
|
|
195
|
+
|
|
198
196
|
# Add read-only flag
|
|
199
197
|
if read_only:
|
|
200
198
|
cmd.append("-R")
|
|
201
|
-
|
|
199
|
+
|
|
202
200
|
# Add split mode
|
|
203
201
|
if split:
|
|
204
202
|
if split == "vsplit":
|
|
@@ -209,10 +207,10 @@ Or visit: https://neovim.io/"""
|
|
|
209
207
|
cmd.extend(["-c", "tabnew"])
|
|
210
208
|
else:
|
|
211
209
|
return f"Error: Invalid split mode '{split}'. Use 'vsplit', 'split', or 'tab'"
|
|
212
|
-
|
|
210
|
+
|
|
213
211
|
# Add file path
|
|
214
212
|
cmd.append(file_path)
|
|
215
|
-
|
|
213
|
+
|
|
216
214
|
# Add line/column positioning
|
|
217
215
|
if line_number:
|
|
218
216
|
if column_number:
|
|
@@ -221,9 +219,9 @@ Or visit: https://neovim.io/"""
|
|
|
221
219
|
else:
|
|
222
220
|
# Go to specific line
|
|
223
221
|
cmd.append(f"+{line_number}")
|
|
224
|
-
|
|
222
|
+
|
|
225
223
|
await tool_ctx.info(f"Opening {file_path} in Neovim")
|
|
226
|
-
|
|
224
|
+
|
|
227
225
|
try:
|
|
228
226
|
# Determine how to run Neovim
|
|
229
227
|
if in_terminal and not wait:
|
|
@@ -233,9 +231,9 @@ Or visit: https://neovim.io/"""
|
|
|
233
231
|
if shutil.which("osascript"):
|
|
234
232
|
# Build AppleScript to open in iTerm2 or Terminal
|
|
235
233
|
nvim_cmd_str = " ".join(f'"{arg}"' for arg in cmd)
|
|
236
|
-
|
|
234
|
+
|
|
237
235
|
# Try iTerm2 first
|
|
238
|
-
applescript = f
|
|
236
|
+
applescript = f"""tell application "System Events"
|
|
239
237
|
if exists application process "iTerm2" then
|
|
240
238
|
tell application "iTerm"
|
|
241
239
|
activate
|
|
@@ -252,35 +250,35 @@ Or visit: https://neovim.io/"""
|
|
|
252
250
|
do script "{nvim_cmd_str}"
|
|
253
251
|
end tell
|
|
254
252
|
end if
|
|
255
|
-
end tell
|
|
256
|
-
|
|
253
|
+
end tell"""
|
|
254
|
+
|
|
257
255
|
subprocess.run(["osascript", "-e", applescript])
|
|
258
256
|
return f"Opened {file_path} in Neovim (new terminal window)"
|
|
259
|
-
|
|
257
|
+
|
|
260
258
|
elif shutil.which("gnome-terminal"):
|
|
261
259
|
# Linux with GNOME
|
|
262
260
|
subprocess.Popen(["gnome-terminal", "--"] + cmd)
|
|
263
261
|
return f"Opened {file_path} in Neovim (new terminal window)"
|
|
264
|
-
|
|
262
|
+
|
|
265
263
|
elif shutil.which("xterm"):
|
|
266
264
|
# Fallback to xterm
|
|
267
265
|
subprocess.Popen(["xterm", "-e"] + cmd)
|
|
268
266
|
return f"Opened {file_path} in Neovim (new terminal window)"
|
|
269
|
-
|
|
267
|
+
|
|
270
268
|
else:
|
|
271
269
|
# Can't open in terminal, fall back to subprocess
|
|
272
270
|
subprocess.Popen(cmd)
|
|
273
271
|
return f"Opened {file_path} in Neovim (background process)"
|
|
274
|
-
|
|
272
|
+
|
|
275
273
|
else:
|
|
276
274
|
# Run and wait for completion
|
|
277
275
|
result = subprocess.run(cmd)
|
|
278
|
-
|
|
276
|
+
|
|
279
277
|
if result.returncode == 0:
|
|
280
278
|
return f"Successfully edited {file_path} in Neovim"
|
|
281
279
|
else:
|
|
282
280
|
return f"Neovim exited with code {result.returncode}"
|
|
283
|
-
|
|
281
|
+
|
|
284
282
|
except Exception as e:
|
|
285
283
|
await tool_ctx.error(f"Failed to open Neovim: {str(e)}")
|
|
286
284
|
return f"Error opening Neovim: {str(e)}"
|