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,24 @@
|
|
|
1
1
|
"""Unified graph database tool."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
|
|
4
3
|
import json
|
|
5
|
-
from
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
Unpack,
|
|
8
|
+
Optional,
|
|
9
|
+
Annotated,
|
|
10
|
+
TypedDict,
|
|
11
|
+
final,
|
|
12
|
+
override,
|
|
13
|
+
)
|
|
6
14
|
|
|
7
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
8
15
|
from pydantic import Field
|
|
16
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
9
17
|
|
|
10
18
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
11
19
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
20
|
from hanzo_mcp.tools.database.database_manager import DatabaseManager
|
|
13
21
|
|
|
14
|
-
|
|
15
22
|
# Parameter types
|
|
16
23
|
Action = Annotated[
|
|
17
24
|
str,
|
|
@@ -96,6 +103,7 @@ Limit = Annotated[
|
|
|
96
103
|
|
|
97
104
|
class GraphParams(TypedDict, total=False):
|
|
98
105
|
"""Parameters for graph tool."""
|
|
106
|
+
|
|
99
107
|
action: str
|
|
100
108
|
node_id: Optional[str]
|
|
101
109
|
node_type: Optional[str]
|
|
@@ -111,8 +119,10 @@ class GraphParams(TypedDict, total=False):
|
|
|
111
119
|
@final
|
|
112
120
|
class GraphTool(BaseTool):
|
|
113
121
|
"""Unified graph database tool."""
|
|
114
|
-
|
|
115
|
-
def __init__(
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
125
|
+
):
|
|
116
126
|
"""Initialize the graph tool."""
|
|
117
127
|
super().__init__(permission_manager)
|
|
118
128
|
self.db_manager = db_manager
|
|
@@ -153,7 +163,7 @@ graph --action search --pattern "John" --node-type User
|
|
|
153
163
|
|
|
154
164
|
# Extract action
|
|
155
165
|
action = params.get("action", "query")
|
|
156
|
-
|
|
166
|
+
|
|
157
167
|
# Route to appropriate handler
|
|
158
168
|
if action == "query":
|
|
159
169
|
return await self._handle_query(project_db, params, tool_ctx)
|
|
@@ -174,17 +184,18 @@ graph --action search --pattern "John" --node-type User
|
|
|
174
184
|
node_type = params.get("node_type")
|
|
175
185
|
depth = params.get("depth", 2)
|
|
176
186
|
limit = params.get("limit", 50)
|
|
177
|
-
|
|
187
|
+
|
|
178
188
|
if not node_id and not node_type:
|
|
179
189
|
return "Error: node_id or node_type required for query"
|
|
180
|
-
|
|
190
|
+
|
|
181
191
|
try:
|
|
182
192
|
with project_db.get_graph_connection() as conn:
|
|
183
193
|
results = []
|
|
184
|
-
|
|
194
|
+
|
|
185
195
|
if node_id:
|
|
186
196
|
# Query specific node and its relationships
|
|
187
|
-
cursor = conn.execute(
|
|
197
|
+
cursor = conn.execute(
|
|
198
|
+
"""
|
|
188
199
|
WITH RECURSIVE
|
|
189
200
|
node_tree(id, type, properties, depth, path) AS (
|
|
190
201
|
SELECT id, type, properties, 0, id
|
|
@@ -206,52 +217,59 @@ graph --action search --pattern "John" --node-type User
|
|
|
206
217
|
SELECT DISTINCT * FROM node_tree
|
|
207
218
|
ORDER BY depth, id
|
|
208
219
|
LIMIT ?
|
|
209
|
-
""",
|
|
210
|
-
|
|
220
|
+
""",
|
|
221
|
+
(node_id, depth, limit),
|
|
222
|
+
)
|
|
223
|
+
|
|
211
224
|
nodes = cursor.fetchall()
|
|
212
|
-
|
|
225
|
+
|
|
213
226
|
# Get edges
|
|
214
|
-
cursor = conn.execute(
|
|
227
|
+
cursor = conn.execute(
|
|
228
|
+
"""
|
|
215
229
|
SELECT from_node, to_node, type, properties
|
|
216
230
|
FROM edges
|
|
217
231
|
WHERE from_node IN (SELECT id FROM node_tree)
|
|
218
232
|
OR to_node IN (SELECT id FROM node_tree)
|
|
219
|
-
"""
|
|
220
|
-
|
|
233
|
+
"""
|
|
234
|
+
)
|
|
235
|
+
|
|
221
236
|
edges = cursor.fetchall()
|
|
222
|
-
|
|
237
|
+
|
|
223
238
|
else:
|
|
224
239
|
# Query by type
|
|
225
|
-
cursor = conn.execute(
|
|
240
|
+
cursor = conn.execute(
|
|
241
|
+
"""
|
|
226
242
|
SELECT id, type, properties
|
|
227
243
|
FROM nodes
|
|
228
244
|
WHERE type = ?
|
|
229
245
|
LIMIT ?
|
|
230
|
-
""",
|
|
231
|
-
|
|
246
|
+
""",
|
|
247
|
+
(node_type, limit),
|
|
248
|
+
)
|
|
249
|
+
|
|
232
250
|
nodes = cursor.fetchall()
|
|
233
251
|
edges = []
|
|
234
|
-
|
|
252
|
+
|
|
235
253
|
# Format results
|
|
236
254
|
output = ["=== Graph Query Results ==="]
|
|
237
|
-
|
|
255
|
+
|
|
238
256
|
if nodes:
|
|
239
257
|
output.append(f"\nNodes ({len(nodes)}):")
|
|
240
258
|
for node in nodes:
|
|
241
259
|
props = json.loads(node[2]) if node[2] else {}
|
|
242
260
|
output.append(f" {node[0]} [{node[1]}] {props}")
|
|
243
|
-
|
|
261
|
+
|
|
244
262
|
if edges:
|
|
245
263
|
output.append(f"\nEdges ({len(edges)}):")
|
|
246
264
|
for edge in edges:
|
|
247
265
|
props = json.loads(edge[3]) if edge[3] else {}
|
|
248
266
|
output.append(f" {edge[0]} --[{edge[2]}]--> {edge[1]} {props}")
|
|
249
|
-
|
|
267
|
+
|
|
250
268
|
if not nodes and not edges:
|
|
251
269
|
output.append("No results found")
|
|
252
|
-
|
|
270
|
+
|
|
253
271
|
return "\n".join(output)
|
|
254
|
-
|
|
272
|
+
|
|
255
273
|
except Exception as e:
|
|
256
274
|
await tool_ctx.error(f"Query failed: {str(e)}")
|
|
257
275
|
return f"Error during query: {str(e)}"
|
|
@@ -261,50 +279,56 @@ graph --action search --pattern "John" --node-type User
|
|
|
261
279
|
node_id = params.get("node_id")
|
|
262
280
|
from_node = params.get("from_node")
|
|
263
281
|
to_node = params.get("to_node")
|
|
264
|
-
|
|
282
|
+
|
|
265
283
|
if node_id:
|
|
266
284
|
# Add node
|
|
267
285
|
node_type = params.get("node_type")
|
|
268
286
|
if not node_type:
|
|
269
287
|
return "Error: node_type required when adding node"
|
|
270
|
-
|
|
288
|
+
|
|
271
289
|
properties = params.get("properties", {})
|
|
272
|
-
|
|
290
|
+
|
|
273
291
|
try:
|
|
274
292
|
with project_db.get_graph_connection() as conn:
|
|
275
|
-
conn.execute(
|
|
293
|
+
conn.execute(
|
|
294
|
+
"""
|
|
276
295
|
INSERT OR REPLACE INTO nodes (id, type, properties)
|
|
277
296
|
VALUES (?, ?, ?)
|
|
278
|
-
""",
|
|
297
|
+
""",
|
|
298
|
+
(node_id, node_type, json.dumps(properties)),
|
|
299
|
+
)
|
|
279
300
|
conn.commit()
|
|
280
|
-
|
|
301
|
+
|
|
281
302
|
await tool_ctx.info(f"Added node: {node_id}")
|
|
282
303
|
return f"Added node {node_id} [{node_type}]"
|
|
283
|
-
|
|
304
|
+
|
|
284
305
|
except Exception as e:
|
|
285
306
|
await tool_ctx.error(f"Failed to add node: {str(e)}")
|
|
286
307
|
return f"Error adding node: {str(e)}"
|
|
287
|
-
|
|
308
|
+
|
|
288
309
|
elif from_node and to_node:
|
|
289
310
|
# Add edge
|
|
290
311
|
edge_type = params.get("edge_type", "RELATED")
|
|
291
312
|
properties = params.get("properties", {})
|
|
292
|
-
|
|
313
|
+
|
|
293
314
|
try:
|
|
294
315
|
with project_db.get_graph_connection() as conn:
|
|
295
|
-
conn.execute(
|
|
316
|
+
conn.execute(
|
|
317
|
+
"""
|
|
296
318
|
INSERT OR REPLACE INTO edges (from_node, to_node, type, properties)
|
|
297
319
|
VALUES (?, ?, ?, ?)
|
|
298
|
-
""",
|
|
320
|
+
""",
|
|
321
|
+
(from_node, to_node, edge_type, json.dumps(properties)),
|
|
322
|
+
)
|
|
299
323
|
conn.commit()
|
|
300
|
-
|
|
324
|
+
|
|
301
325
|
await tool_ctx.info(f"Added edge: {from_node} -> {to_node}")
|
|
302
326
|
return f"Added edge {from_node} --[{edge_type}]--> {to_node}"
|
|
303
|
-
|
|
327
|
+
|
|
304
328
|
except Exception as e:
|
|
305
329
|
await tool_ctx.error(f"Failed to add edge: {str(e)}")
|
|
306
330
|
return f"Error adding edge: {str(e)}"
|
|
307
|
-
|
|
331
|
+
|
|
308
332
|
else:
|
|
309
333
|
return "Error: Either node_id (for node) or from_node + to_node (for edge) required"
|
|
310
334
|
|
|
@@ -313,68 +337,80 @@ graph --action search --pattern "John" --node-type User
|
|
|
313
337
|
node_id = params.get("node_id")
|
|
314
338
|
from_node = params.get("from_node")
|
|
315
339
|
to_node = params.get("to_node")
|
|
316
|
-
|
|
340
|
+
|
|
317
341
|
if node_id:
|
|
318
342
|
# Remove node and its edges
|
|
319
343
|
try:
|
|
320
344
|
with project_db.get_graph_connection() as conn:
|
|
321
345
|
# Delete edges first
|
|
322
|
-
cursor = conn.execute(
|
|
346
|
+
cursor = conn.execute(
|
|
347
|
+
"""
|
|
323
348
|
DELETE FROM edges
|
|
324
349
|
WHERE from_node = ? OR to_node = ?
|
|
325
|
-
""",
|
|
326
|
-
|
|
350
|
+
""",
|
|
351
|
+
(node_id, node_id),
|
|
352
|
+
)
|
|
353
|
+
|
|
327
354
|
edges_deleted = cursor.rowcount
|
|
328
|
-
|
|
355
|
+
|
|
329
356
|
# Delete node
|
|
330
|
-
cursor = conn.execute(
|
|
357
|
+
cursor = conn.execute(
|
|
358
|
+
"""
|
|
331
359
|
DELETE FROM nodes WHERE id = ?
|
|
332
|
-
""",
|
|
333
|
-
|
|
360
|
+
""",
|
|
361
|
+
(node_id,),
|
|
362
|
+
)
|
|
363
|
+
|
|
334
364
|
if cursor.rowcount == 0:
|
|
335
365
|
return f"Node {node_id} not found"
|
|
336
|
-
|
|
366
|
+
|
|
337
367
|
conn.commit()
|
|
338
|
-
|
|
368
|
+
|
|
339
369
|
msg = f"Removed node {node_id}"
|
|
340
370
|
if edges_deleted > 0:
|
|
341
371
|
msg += f" and {edges_deleted} connected edges"
|
|
342
|
-
|
|
372
|
+
|
|
343
373
|
await tool_ctx.info(msg)
|
|
344
374
|
return msg
|
|
345
|
-
|
|
375
|
+
|
|
346
376
|
except Exception as e:
|
|
347
377
|
await tool_ctx.error(f"Failed to remove node: {str(e)}")
|
|
348
378
|
return f"Error removing node: {str(e)}"
|
|
349
|
-
|
|
379
|
+
|
|
350
380
|
elif from_node and to_node:
|
|
351
381
|
# Remove specific edge
|
|
352
382
|
edge_type = params.get("edge_type")
|
|
353
|
-
|
|
383
|
+
|
|
354
384
|
try:
|
|
355
385
|
with project_db.get_graph_connection() as conn:
|
|
356
386
|
if edge_type:
|
|
357
|
-
cursor = conn.execute(
|
|
387
|
+
cursor = conn.execute(
|
|
388
|
+
"""
|
|
358
389
|
DELETE FROM edges
|
|
359
390
|
WHERE from_node = ? AND to_node = ? AND type = ?
|
|
360
|
-
""",
|
|
391
|
+
""",
|
|
392
|
+
(from_node, to_node, edge_type),
|
|
393
|
+
)
|
|
361
394
|
else:
|
|
362
|
-
cursor = conn.execute(
|
|
395
|
+
cursor = conn.execute(
|
|
396
|
+
"""
|
|
363
397
|
DELETE FROM edges
|
|
364
398
|
WHERE from_node = ? AND to_node = ?
|
|
365
|
-
""",
|
|
366
|
-
|
|
399
|
+
""",
|
|
400
|
+
(from_node, to_node),
|
|
401
|
+
)
|
|
402
|
+
|
|
367
403
|
if cursor.rowcount == 0:
|
|
368
404
|
return f"Edge not found"
|
|
369
|
-
|
|
405
|
+
|
|
370
406
|
conn.commit()
|
|
371
|
-
|
|
407
|
+
|
|
372
408
|
return f"Removed edge {from_node} --> {to_node}"
|
|
373
|
-
|
|
409
|
+
|
|
374
410
|
except Exception as e:
|
|
375
411
|
await tool_ctx.error(f"Failed to remove edge: {str(e)}")
|
|
376
412
|
return f"Error removing edge: {str(e)}"
|
|
377
|
-
|
|
413
|
+
|
|
378
414
|
else:
|
|
379
415
|
return "Error: Either node_id or from_node + to_node required for remove"
|
|
380
416
|
|
|
@@ -383,43 +419,49 @@ graph --action search --pattern "John" --node-type User
|
|
|
383
419
|
pattern = params.get("pattern")
|
|
384
420
|
if not pattern:
|
|
385
421
|
return "Error: pattern required for search"
|
|
386
|
-
|
|
422
|
+
|
|
387
423
|
node_type = params.get("node_type")
|
|
388
424
|
limit = params.get("limit", 50)
|
|
389
|
-
|
|
425
|
+
|
|
390
426
|
try:
|
|
391
427
|
with project_db.get_graph_connection() as conn:
|
|
392
428
|
# Search in properties
|
|
393
429
|
if node_type:
|
|
394
|
-
cursor = conn.execute(
|
|
430
|
+
cursor = conn.execute(
|
|
431
|
+
"""
|
|
395
432
|
SELECT id, type, properties
|
|
396
433
|
FROM nodes
|
|
397
434
|
WHERE type = ? AND properties LIKE ?
|
|
398
435
|
LIMIT ?
|
|
399
|
-
""",
|
|
436
|
+
""",
|
|
437
|
+
(node_type, f"%{pattern}%", limit),
|
|
438
|
+
)
|
|
400
439
|
else:
|
|
401
|
-
cursor = conn.execute(
|
|
440
|
+
cursor = conn.execute(
|
|
441
|
+
"""
|
|
402
442
|
SELECT id, type, properties
|
|
403
443
|
FROM nodes
|
|
404
444
|
WHERE properties LIKE ?
|
|
405
445
|
LIMIT ?
|
|
406
|
-
""",
|
|
407
|
-
|
|
446
|
+
""",
|
|
447
|
+
(f"%{pattern}%", limit),
|
|
448
|
+
)
|
|
449
|
+
|
|
408
450
|
results = cursor.fetchall()
|
|
409
|
-
|
|
451
|
+
|
|
410
452
|
if not results:
|
|
411
453
|
return f"No nodes found matching '{pattern}'"
|
|
412
|
-
|
|
454
|
+
|
|
413
455
|
# Format results
|
|
414
456
|
output = [f"=== Graph Search Results for '{pattern}' ==="]
|
|
415
457
|
output.append(f"Found {len(results)} nodes\n")
|
|
416
|
-
|
|
458
|
+
|
|
417
459
|
for node in results:
|
|
418
460
|
props = json.loads(node[2]) if node[2] else {}
|
|
419
461
|
output.append(f"{node[0]} [{node[1]}] {props}")
|
|
420
|
-
|
|
462
|
+
|
|
421
463
|
return "\n".join(output)
|
|
422
|
-
|
|
464
|
+
|
|
423
465
|
except Exception as e:
|
|
424
466
|
await tool_ctx.error(f"Search failed: {str(e)}")
|
|
425
467
|
return f"Error during search: {str(e)}"
|
|
@@ -429,54 +471,58 @@ graph --action search --pattern "John" --node-type User
|
|
|
429
471
|
try:
|
|
430
472
|
with project_db.get_graph_connection() as conn:
|
|
431
473
|
# Node stats
|
|
432
|
-
cursor = conn.execute(
|
|
474
|
+
cursor = conn.execute(
|
|
475
|
+
"""
|
|
433
476
|
SELECT type, COUNT(*) as count
|
|
434
477
|
FROM nodes
|
|
435
478
|
GROUP BY type
|
|
436
479
|
ORDER BY count DESC
|
|
437
|
-
"""
|
|
438
|
-
|
|
480
|
+
"""
|
|
481
|
+
)
|
|
482
|
+
|
|
439
483
|
node_stats = cursor.fetchall()
|
|
440
|
-
|
|
484
|
+
|
|
441
485
|
# Edge stats
|
|
442
|
-
cursor = conn.execute(
|
|
486
|
+
cursor = conn.execute(
|
|
487
|
+
"""
|
|
443
488
|
SELECT type, COUNT(*) as count
|
|
444
489
|
FROM edges
|
|
445
490
|
GROUP BY type
|
|
446
491
|
ORDER BY count DESC
|
|
447
|
-
"""
|
|
448
|
-
|
|
492
|
+
"""
|
|
493
|
+
)
|
|
494
|
+
|
|
449
495
|
edge_stats = cursor.fetchall()
|
|
450
|
-
|
|
496
|
+
|
|
451
497
|
# Total counts
|
|
452
498
|
cursor = conn.execute("SELECT COUNT(*) FROM nodes")
|
|
453
499
|
total_nodes = cursor.fetchone()[0]
|
|
454
|
-
|
|
500
|
+
|
|
455
501
|
cursor = conn.execute("SELECT COUNT(*) FROM edges")
|
|
456
502
|
total_edges = cursor.fetchone()[0]
|
|
457
|
-
|
|
503
|
+
|
|
458
504
|
# Format output
|
|
459
505
|
output = [f"=== Graph Database Statistics ==="]
|
|
460
506
|
output.append(f"Project: {project_db.project_path}")
|
|
461
507
|
output.append(f"\nTotal nodes: {total_nodes}")
|
|
462
508
|
output.append(f"Total edges: {total_edges}")
|
|
463
|
-
|
|
509
|
+
|
|
464
510
|
if node_stats:
|
|
465
511
|
output.append("\nNodes by type:")
|
|
466
512
|
for node_type, count in node_stats:
|
|
467
513
|
output.append(f" {node_type}: {count}")
|
|
468
|
-
|
|
514
|
+
|
|
469
515
|
if edge_stats:
|
|
470
516
|
output.append("\nEdges by type:")
|
|
471
517
|
for edge_type, count in edge_stats:
|
|
472
518
|
output.append(f" {edge_type}: {count}")
|
|
473
|
-
|
|
519
|
+
|
|
474
520
|
return "\n".join(output)
|
|
475
|
-
|
|
521
|
+
|
|
476
522
|
except Exception as e:
|
|
477
523
|
await tool_ctx.error(f"Failed to get stats: {str(e)}")
|
|
478
524
|
return f"Error getting stats: {str(e)}"
|
|
479
525
|
|
|
480
526
|
def register(self, mcp_server) -> None:
|
|
481
527
|
"""Register this tool with the MCP server."""
|
|
482
|
-
pass
|
|
528
|
+
pass
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Graph add tool for adding nodes and edges to the graph database."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
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
|
NodeId = Annotated[
|
|
16
15
|
Optional[str],
|
|
17
16
|
Field(
|
|
@@ -94,7 +93,9 @@ class GraphAddParams(TypedDict, total=False):
|
|
|
94
93
|
class GraphAddTool(BaseTool):
|
|
95
94
|
"""Tool for adding nodes and edges to graph database."""
|
|
96
95
|
|
|
97
|
-
def __init__(
|
|
96
|
+
def __init__(
|
|
97
|
+
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
98
|
+
):
|
|
98
99
|
"""Initialize the graph add tool.
|
|
99
100
|
|
|
100
101
|
Args:
|
|
@@ -181,8 +182,9 @@ Examples:
|
|
|
181
182
|
project_db = self.db_manager.get_project_db(project_path)
|
|
182
183
|
else:
|
|
183
184
|
import os
|
|
185
|
+
|
|
184
186
|
project_db = self.db_manager.get_project_for_path(os.getcwd())
|
|
185
|
-
|
|
187
|
+
|
|
186
188
|
if not project_db:
|
|
187
189
|
return "Error: Could not find project database"
|
|
188
190
|
|
|
@@ -206,13 +208,16 @@ Examples:
|
|
|
206
208
|
properties_json = json.dumps(properties) if properties else None
|
|
207
209
|
|
|
208
210
|
# Insert or update node
|
|
209
|
-
graph_conn.execute(
|
|
211
|
+
graph_conn.execute(
|
|
212
|
+
"""
|
|
210
213
|
INSERT OR REPLACE INTO nodes (id, type, properties)
|
|
211
214
|
VALUES (?, ?, ?)
|
|
212
|
-
""",
|
|
213
|
-
|
|
215
|
+
""",
|
|
216
|
+
(node_id, node_type, properties_json),
|
|
217
|
+
)
|
|
218
|
+
|
|
214
219
|
graph_conn.commit()
|
|
215
|
-
|
|
220
|
+
|
|
216
221
|
return f"Successfully added node '{node_id}' of type '{node_type}'"
|
|
217
222
|
|
|
218
223
|
else:
|
|
@@ -220,13 +225,17 @@ Examples:
|
|
|
220
225
|
if not relationship:
|
|
221
226
|
return "Error: relationship is required when adding an edge"
|
|
222
227
|
|
|
223
|
-
await tool_ctx.info(
|
|
228
|
+
await tool_ctx.info(
|
|
229
|
+
f"Adding edge: {source} --[{relationship}]--> {target}"
|
|
230
|
+
)
|
|
224
231
|
|
|
225
232
|
# Check if nodes exist
|
|
226
233
|
cursor = graph_conn.cursor()
|
|
227
|
-
cursor.execute(
|
|
234
|
+
cursor.execute(
|
|
235
|
+
"SELECT id FROM nodes WHERE id IN (?, ?)", (source, target)
|
|
236
|
+
)
|
|
228
237
|
existing = [row[0] for row in cursor.fetchall()]
|
|
229
|
-
|
|
238
|
+
|
|
230
239
|
if source not in existing:
|
|
231
240
|
return f"Error: Source node '{source}' does not exist"
|
|
232
241
|
if target not in existing:
|
|
@@ -236,16 +245,19 @@ Examples:
|
|
|
236
245
|
properties_json = json.dumps(properties) if properties else None
|
|
237
246
|
|
|
238
247
|
# Insert or update edge
|
|
239
|
-
graph_conn.execute(
|
|
248
|
+
graph_conn.execute(
|
|
249
|
+
"""
|
|
240
250
|
INSERT OR REPLACE INTO edges (source, target, relationship, weight, properties)
|
|
241
251
|
VALUES (?, ?, ?, ?, ?)
|
|
242
|
-
""",
|
|
243
|
-
|
|
252
|
+
""",
|
|
253
|
+
(source, target, relationship, weight, properties_json),
|
|
254
|
+
)
|
|
255
|
+
|
|
244
256
|
graph_conn.commit()
|
|
245
|
-
|
|
257
|
+
|
|
246
258
|
# Save to disk
|
|
247
259
|
project_db._save_graph_to_disk()
|
|
248
|
-
|
|
260
|
+
|
|
249
261
|
return f"Successfully added edge: {source} --[{relationship}]--> {target} (weight: {weight})"
|
|
250
262
|
|
|
251
263
|
except Exception as e:
|
|
@@ -254,4 +266,4 @@ Examples:
|
|
|
254
266
|
|
|
255
267
|
def register(self, mcp_server) -> None:
|
|
256
268
|
"""Register this tool with the MCP server."""
|
|
257
|
-
pass
|
|
269
|
+
pass
|