hanzo-mcp 0.7.6__py3-none-any.whl → 0.8.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +7 -1
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.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 pathlib import Path
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__(self, permission_manager: PermissionManager, db_manager: DatabaseManager):
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
- """, (node_id, depth, limit))
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
- """, (node_type, limit))
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
- """, (node_id, node_type, json.dumps(properties)))
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
- """, (from_node, to_node, edge_type, json.dumps(properties)))
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
- """, (node_id, node_id))
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
- """, (node_id,))
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
- """, (from_node, to_node, edge_type))
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
- """, (from_node, to_node))
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
- """, (node_type, f"%{pattern}%", limit))
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
- """, (f"%{pattern}%", limit))
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 Annotated, Optional, TypedDict, Unpack, final, override
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__(self, permission_manager: PermissionManager, db_manager: DatabaseManager):
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
- """, (node_id, node_type, properties_json))
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(f"Adding edge: {source} --[{relationship}]--> {target}")
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("SELECT id FROM nodes WHERE id IN (?, ?)", (source, target))
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
- """, (source, target, relationship, weight, properties_json))
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