hanzo-mcp 0.5.2__py3-none-any.whl → 0.6.2__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 (114) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +32 -0
  3. hanzo_mcp/dev_server.py +246 -0
  4. hanzo_mcp/prompts/__init__.py +1 -1
  5. hanzo_mcp/prompts/project_system.py +43 -7
  6. hanzo_mcp/server.py +5 -1
  7. hanzo_mcp/tools/__init__.py +66 -35
  8. hanzo_mcp/tools/agent/__init__.py +1 -1
  9. hanzo_mcp/tools/agent/agent.py +401 -0
  10. hanzo_mcp/tools/agent/agent_tool.py +3 -4
  11. hanzo_mcp/tools/common/__init__.py +1 -1
  12. hanzo_mcp/tools/common/base.py +2 -2
  13. hanzo_mcp/tools/common/batch_tool.py +3 -5
  14. hanzo_mcp/tools/common/config_tool.py +1 -1
  15. hanzo_mcp/tools/common/context.py +1 -1
  16. hanzo_mcp/tools/common/palette.py +344 -0
  17. hanzo_mcp/tools/common/palette_loader.py +108 -0
  18. hanzo_mcp/tools/common/stats.py +1 -1
  19. hanzo_mcp/tools/common/thinking_tool.py +3 -5
  20. hanzo_mcp/tools/common/tool_disable.py +1 -1
  21. hanzo_mcp/tools/common/tool_enable.py +1 -1
  22. hanzo_mcp/tools/common/tool_list.py +49 -52
  23. hanzo_mcp/tools/config/__init__.py +10 -0
  24. hanzo_mcp/tools/config/config_tool.py +212 -0
  25. hanzo_mcp/tools/config/index_config.py +176 -0
  26. hanzo_mcp/tools/config/palette_tool.py +166 -0
  27. hanzo_mcp/tools/database/__init__.py +1 -1
  28. hanzo_mcp/tools/database/graph.py +482 -0
  29. hanzo_mcp/tools/database/graph_add.py +1 -1
  30. hanzo_mcp/tools/database/graph_query.py +1 -1
  31. hanzo_mcp/tools/database/graph_remove.py +1 -1
  32. hanzo_mcp/tools/database/graph_search.py +1 -1
  33. hanzo_mcp/tools/database/graph_stats.py +1 -1
  34. hanzo_mcp/tools/database/sql.py +411 -0
  35. hanzo_mcp/tools/database/sql_query.py +1 -1
  36. hanzo_mcp/tools/database/sql_search.py +1 -1
  37. hanzo_mcp/tools/database/sql_stats.py +1 -1
  38. hanzo_mcp/tools/editor/neovim_command.py +1 -1
  39. hanzo_mcp/tools/editor/neovim_edit.py +1 -1
  40. hanzo_mcp/tools/editor/neovim_session.py +1 -1
  41. hanzo_mcp/tools/filesystem/__init__.py +42 -13
  42. hanzo_mcp/tools/filesystem/base.py +1 -1
  43. hanzo_mcp/tools/filesystem/batch_search.py +4 -4
  44. hanzo_mcp/tools/filesystem/content_replace.py +3 -5
  45. hanzo_mcp/tools/filesystem/diff.py +193 -0
  46. hanzo_mcp/tools/filesystem/directory_tree.py +3 -5
  47. hanzo_mcp/tools/filesystem/edit.py +3 -5
  48. hanzo_mcp/tools/filesystem/find.py +443 -0
  49. hanzo_mcp/tools/filesystem/find_files.py +1 -1
  50. hanzo_mcp/tools/filesystem/git_search.py +1 -1
  51. hanzo_mcp/tools/filesystem/grep.py +2 -2
  52. hanzo_mcp/tools/filesystem/multi_edit.py +3 -5
  53. hanzo_mcp/tools/filesystem/read.py +17 -5
  54. hanzo_mcp/tools/filesystem/{grep_ast_tool.py → symbols.py} +17 -27
  55. hanzo_mcp/tools/filesystem/symbols_unified.py +376 -0
  56. hanzo_mcp/tools/filesystem/tree.py +268 -0
  57. hanzo_mcp/tools/filesystem/unified_search.py +711 -0
  58. hanzo_mcp/tools/filesystem/unix_aliases.py +99 -0
  59. hanzo_mcp/tools/filesystem/watch.py +174 -0
  60. hanzo_mcp/tools/filesystem/write.py +3 -5
  61. hanzo_mcp/tools/jupyter/__init__.py +9 -12
  62. hanzo_mcp/tools/jupyter/base.py +1 -1
  63. hanzo_mcp/tools/jupyter/jupyter.py +326 -0
  64. hanzo_mcp/tools/jupyter/notebook_edit.py +3 -4
  65. hanzo_mcp/tools/jupyter/notebook_read.py +3 -5
  66. hanzo_mcp/tools/llm/__init__.py +4 -0
  67. hanzo_mcp/tools/llm/consensus_tool.py +1 -1
  68. hanzo_mcp/tools/llm/llm_manage.py +1 -1
  69. hanzo_mcp/tools/llm/llm_tool.py +1 -1
  70. hanzo_mcp/tools/llm/llm_unified.py +851 -0
  71. hanzo_mcp/tools/llm/provider_tools.py +1 -1
  72. hanzo_mcp/tools/mcp/__init__.py +4 -0
  73. hanzo_mcp/tools/mcp/mcp_add.py +1 -1
  74. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  75. hanzo_mcp/tools/mcp/mcp_stats.py +1 -1
  76. hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
  77. hanzo_mcp/tools/shell/__init__.py +20 -42
  78. hanzo_mcp/tools/shell/base.py +1 -1
  79. hanzo_mcp/tools/shell/base_process.py +303 -0
  80. hanzo_mcp/tools/shell/bash_unified.py +134 -0
  81. hanzo_mcp/tools/shell/logs.py +1 -1
  82. hanzo_mcp/tools/shell/npx.py +1 -1
  83. hanzo_mcp/tools/shell/npx_background.py +1 -1
  84. hanzo_mcp/tools/shell/npx_unified.py +101 -0
  85. hanzo_mcp/tools/shell/open.py +107 -0
  86. hanzo_mcp/tools/shell/pkill.py +1 -1
  87. hanzo_mcp/tools/shell/process_unified.py +131 -0
  88. hanzo_mcp/tools/shell/processes.py +1 -1
  89. hanzo_mcp/tools/shell/run_background.py +1 -1
  90. hanzo_mcp/tools/shell/run_command.py +3 -4
  91. hanzo_mcp/tools/shell/run_command_windows.py +3 -4
  92. hanzo_mcp/tools/shell/uvx.py +1 -1
  93. hanzo_mcp/tools/shell/uvx_background.py +1 -1
  94. hanzo_mcp/tools/shell/uvx_unified.py +101 -0
  95. hanzo_mcp/tools/todo/__init__.py +1 -1
  96. hanzo_mcp/tools/todo/base.py +1 -1
  97. hanzo_mcp/tools/todo/todo.py +265 -0
  98. hanzo_mcp/tools/todo/todo_read.py +3 -5
  99. hanzo_mcp/tools/todo/todo_write.py +3 -5
  100. hanzo_mcp/tools/vector/__init__.py +1 -1
  101. hanzo_mcp/tools/vector/index_tool.py +1 -1
  102. hanzo_mcp/tools/vector/project_manager.py +27 -5
  103. hanzo_mcp/tools/vector/vector.py +311 -0
  104. hanzo_mcp/tools/vector/vector_index.py +1 -1
  105. hanzo_mcp/tools/vector/vector_search.py +1 -1
  106. hanzo_mcp-0.6.2.dist-info/METADATA +336 -0
  107. hanzo_mcp-0.6.2.dist-info/RECORD +134 -0
  108. hanzo_mcp-0.6.2.dist-info/entry_points.txt +3 -0
  109. hanzo_mcp-0.5.2.dist-info/METADATA +0 -276
  110. hanzo_mcp-0.5.2.dist-info/RECORD +0 -106
  111. hanzo_mcp-0.5.2.dist-info/entry_points.txt +0 -2
  112. {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/WHEEL +0 -0
  113. {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/licenses/LICENSE +0 -0
  114. {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,482 @@
1
+ """Unified graph database tool."""
2
+
3
+ from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import Context as MCPContext
8
+ from pydantic import Field
9
+
10
+ from hanzo_mcp.tools.common.base import BaseTool
11
+ from hanzo_mcp.tools.common.permissions import PermissionManager
12
+ from hanzo_mcp.tools.database.database_manager import DatabaseManager
13
+
14
+
15
+ # Parameter types
16
+ Action = Annotated[
17
+ str,
18
+ Field(
19
+ description="Action: query (default), add, remove, search, stats",
20
+ default="query",
21
+ ),
22
+ ]
23
+
24
+ NodeId = Annotated[
25
+ Optional[str],
26
+ Field(
27
+ description="Node ID",
28
+ default=None,
29
+ ),
30
+ ]
31
+
32
+ NodeType = Annotated[
33
+ Optional[str],
34
+ Field(
35
+ description="Node type/label",
36
+ default=None,
37
+ ),
38
+ ]
39
+
40
+ EdgeType = Annotated[
41
+ Optional[str],
42
+ Field(
43
+ description="Edge type/relationship",
44
+ default=None,
45
+ ),
46
+ ]
47
+
48
+ FromNode = Annotated[
49
+ Optional[str],
50
+ Field(
51
+ description="Source node ID for edges",
52
+ default=None,
53
+ ),
54
+ ]
55
+
56
+ ToNode = Annotated[
57
+ Optional[str],
58
+ Field(
59
+ description="Target node ID for edges",
60
+ default=None,
61
+ ),
62
+ ]
63
+
64
+ Properties = Annotated[
65
+ Optional[Dict[str, Any]],
66
+ Field(
67
+ description="Node/edge properties as JSON",
68
+ default=None,
69
+ ),
70
+ ]
71
+
72
+ Pattern = Annotated[
73
+ Optional[str],
74
+ Field(
75
+ description="Search pattern for properties",
76
+ default=None,
77
+ ),
78
+ ]
79
+
80
+ Depth = Annotated[
81
+ int,
82
+ Field(
83
+ description="Max traversal depth for queries",
84
+ default=2,
85
+ ),
86
+ ]
87
+
88
+ Limit = Annotated[
89
+ int,
90
+ Field(
91
+ description="Maximum results to return",
92
+ default=50,
93
+ ),
94
+ ]
95
+
96
+
97
+ class GraphParams(TypedDict, total=False):
98
+ """Parameters for graph tool."""
99
+ action: str
100
+ node_id: Optional[str]
101
+ node_type: Optional[str]
102
+ edge_type: Optional[str]
103
+ from_node: Optional[str]
104
+ to_node: Optional[str]
105
+ properties: Optional[Dict[str, Any]]
106
+ pattern: Optional[str]
107
+ depth: int
108
+ limit: int
109
+
110
+
111
+ @final
112
+ class GraphTool(BaseTool):
113
+ """Unified graph database tool."""
114
+
115
+ def __init__(self, permission_manager: PermissionManager, db_manager: DatabaseManager):
116
+ """Initialize the graph tool."""
117
+ super().__init__(permission_manager)
118
+ self.db_manager = db_manager
119
+
120
+ @property
121
+ @override
122
+ def name(self) -> str:
123
+ """Get the tool name."""
124
+ return "graph"
125
+
126
+ @property
127
+ @override
128
+ def description(self) -> str:
129
+ """Get the tool description."""
130
+ return """Graph database. Actions: query (default), add, remove, search, stats.
131
+
132
+ Usage:
133
+ graph --node-id user123
134
+ graph --action add --node-id user123 --node-type User --properties '{"name": "John"}'
135
+ graph --action add --from-node user123 --to-node post456 --edge-type CREATED
136
+ graph --action query --node-id user123 --depth 3
137
+ graph --action search --pattern "John" --node-type User
138
+ """
139
+
140
+ @override
141
+ async def call(
142
+ self,
143
+ ctx: MCPContext,
144
+ **params: Unpack[GraphParams],
145
+ ) -> str:
146
+ """Execute graph operation."""
147
+ tool_ctx = self.create_tool_context(ctx)
148
+
149
+ # Get current project database
150
+ project_db = self.db_manager.get_current_project_db()
151
+ if not project_db:
152
+ return "Error: No project database found. Are you in a project directory?"
153
+
154
+ # Extract action
155
+ action = params.get("action", "query")
156
+
157
+ # Route to appropriate handler
158
+ if action == "query":
159
+ return await self._handle_query(project_db, params, tool_ctx)
160
+ elif action == "add":
161
+ return await self._handle_add(project_db, params, tool_ctx)
162
+ elif action == "remove":
163
+ return await self._handle_remove(project_db, params, tool_ctx)
164
+ elif action == "search":
165
+ return await self._handle_search(project_db, params, tool_ctx)
166
+ elif action == "stats":
167
+ return await self._handle_stats(project_db, tool_ctx)
168
+ else:
169
+ return f"Error: Unknown action '{action}'. Valid actions: query, add, remove, search, stats"
170
+
171
+ async def _handle_query(self, project_db, params: Dict[str, Any], tool_ctx) -> str:
172
+ """Query graph relationships."""
173
+ node_id = params.get("node_id")
174
+ node_type = params.get("node_type")
175
+ depth = params.get("depth", 2)
176
+ limit = params.get("limit", 50)
177
+
178
+ if not node_id and not node_type:
179
+ return "Error: node_id or node_type required for query"
180
+
181
+ try:
182
+ with project_db.get_graph_connection() as conn:
183
+ results = []
184
+
185
+ if node_id:
186
+ # Query specific node and its relationships
187
+ cursor = conn.execute("""
188
+ WITH RECURSIVE
189
+ node_tree(id, type, properties, depth, path) AS (
190
+ SELECT id, type, properties, 0, id
191
+ FROM nodes
192
+ WHERE id = ?
193
+
194
+ UNION ALL
195
+
196
+ SELECT n.id, n.type, n.properties, nt.depth + 1,
197
+ nt.path || ' -> ' || n.id
198
+ FROM nodes n
199
+ JOIN edges e ON (e.to_node = n.id OR e.from_node = n.id)
200
+ JOIN node_tree nt ON (
201
+ (e.from_node = nt.id AND e.to_node = n.id) OR
202
+ (e.to_node = nt.id AND e.from_node = n.id)
203
+ )
204
+ WHERE nt.depth < ?
205
+ )
206
+ SELECT DISTINCT * FROM node_tree
207
+ ORDER BY depth, id
208
+ LIMIT ?
209
+ """, (node_id, depth, limit))
210
+
211
+ nodes = cursor.fetchall()
212
+
213
+ # Get edges
214
+ cursor = conn.execute("""
215
+ SELECT from_node, to_node, type, properties
216
+ FROM edges
217
+ WHERE from_node IN (SELECT id FROM node_tree)
218
+ OR to_node IN (SELECT id FROM node_tree)
219
+ """)
220
+
221
+ edges = cursor.fetchall()
222
+
223
+ else:
224
+ # Query by type
225
+ cursor = conn.execute("""
226
+ SELECT id, type, properties
227
+ FROM nodes
228
+ WHERE type = ?
229
+ LIMIT ?
230
+ """, (node_type, limit))
231
+
232
+ nodes = cursor.fetchall()
233
+ edges = []
234
+
235
+ # Format results
236
+ output = ["=== Graph Query Results ==="]
237
+
238
+ if nodes:
239
+ output.append(f"\nNodes ({len(nodes)}):")
240
+ for node in nodes:
241
+ props = json.loads(node[2]) if node[2] else {}
242
+ output.append(f" {node[0]} [{node[1]}] {props}")
243
+
244
+ if edges:
245
+ output.append(f"\nEdges ({len(edges)}):")
246
+ for edge in edges:
247
+ props = json.loads(edge[3]) if edge[3] else {}
248
+ output.append(f" {edge[0]} --[{edge[2]}]--> {edge[1]} {props}")
249
+
250
+ if not nodes and not edges:
251
+ output.append("No results found")
252
+
253
+ return "\n".join(output)
254
+
255
+ except Exception as e:
256
+ await tool_ctx.error(f"Query failed: {str(e)}")
257
+ return f"Error during query: {str(e)}"
258
+
259
+ async def _handle_add(self, project_db, params: Dict[str, Any], tool_ctx) -> str:
260
+ """Add nodes or edges."""
261
+ node_id = params.get("node_id")
262
+ from_node = params.get("from_node")
263
+ to_node = params.get("to_node")
264
+
265
+ if node_id:
266
+ # Add node
267
+ node_type = params.get("node_type")
268
+ if not node_type:
269
+ return "Error: node_type required when adding node"
270
+
271
+ properties = params.get("properties", {})
272
+
273
+ try:
274
+ with project_db.get_graph_connection() as conn:
275
+ conn.execute("""
276
+ INSERT OR REPLACE INTO nodes (id, type, properties)
277
+ VALUES (?, ?, ?)
278
+ """, (node_id, node_type, json.dumps(properties)))
279
+ conn.commit()
280
+
281
+ await tool_ctx.info(f"Added node: {node_id}")
282
+ return f"Added node {node_id} [{node_type}]"
283
+
284
+ except Exception as e:
285
+ await tool_ctx.error(f"Failed to add node: {str(e)}")
286
+ return f"Error adding node: {str(e)}"
287
+
288
+ elif from_node and to_node:
289
+ # Add edge
290
+ edge_type = params.get("edge_type", "RELATED")
291
+ properties = params.get("properties", {})
292
+
293
+ try:
294
+ with project_db.get_graph_connection() as conn:
295
+ conn.execute("""
296
+ INSERT OR REPLACE INTO edges (from_node, to_node, type, properties)
297
+ VALUES (?, ?, ?, ?)
298
+ """, (from_node, to_node, edge_type, json.dumps(properties)))
299
+ conn.commit()
300
+
301
+ await tool_ctx.info(f"Added edge: {from_node} -> {to_node}")
302
+ return f"Added edge {from_node} --[{edge_type}]--> {to_node}"
303
+
304
+ except Exception as e:
305
+ await tool_ctx.error(f"Failed to add edge: {str(e)}")
306
+ return f"Error adding edge: {str(e)}"
307
+
308
+ else:
309
+ return "Error: Either node_id (for node) or from_node + to_node (for edge) required"
310
+
311
+ async def _handle_remove(self, project_db, params: Dict[str, Any], tool_ctx) -> str:
312
+ """Remove nodes or edges."""
313
+ node_id = params.get("node_id")
314
+ from_node = params.get("from_node")
315
+ to_node = params.get("to_node")
316
+
317
+ if node_id:
318
+ # Remove node and its edges
319
+ try:
320
+ with project_db.get_graph_connection() as conn:
321
+ # Delete edges first
322
+ cursor = conn.execute("""
323
+ DELETE FROM edges
324
+ WHERE from_node = ? OR to_node = ?
325
+ """, (node_id, node_id))
326
+
327
+ edges_deleted = cursor.rowcount
328
+
329
+ # Delete node
330
+ cursor = conn.execute("""
331
+ DELETE FROM nodes WHERE id = ?
332
+ """, (node_id,))
333
+
334
+ if cursor.rowcount == 0:
335
+ return f"Node {node_id} not found"
336
+
337
+ conn.commit()
338
+
339
+ msg = f"Removed node {node_id}"
340
+ if edges_deleted > 0:
341
+ msg += f" and {edges_deleted} connected edges"
342
+
343
+ await tool_ctx.info(msg)
344
+ return msg
345
+
346
+ except Exception as e:
347
+ await tool_ctx.error(f"Failed to remove node: {str(e)}")
348
+ return f"Error removing node: {str(e)}"
349
+
350
+ elif from_node and to_node:
351
+ # Remove specific edge
352
+ edge_type = params.get("edge_type")
353
+
354
+ try:
355
+ with project_db.get_graph_connection() as conn:
356
+ if edge_type:
357
+ cursor = conn.execute("""
358
+ DELETE FROM edges
359
+ WHERE from_node = ? AND to_node = ? AND type = ?
360
+ """, (from_node, to_node, edge_type))
361
+ else:
362
+ cursor = conn.execute("""
363
+ DELETE FROM edges
364
+ WHERE from_node = ? AND to_node = ?
365
+ """, (from_node, to_node))
366
+
367
+ if cursor.rowcount == 0:
368
+ return f"Edge not found"
369
+
370
+ conn.commit()
371
+
372
+ return f"Removed edge {from_node} --> {to_node}"
373
+
374
+ except Exception as e:
375
+ await tool_ctx.error(f"Failed to remove edge: {str(e)}")
376
+ return f"Error removing edge: {str(e)}"
377
+
378
+ else:
379
+ return "Error: Either node_id or from_node + to_node required for remove"
380
+
381
+ async def _handle_search(self, project_db, params: Dict[str, Any], tool_ctx) -> str:
382
+ """Search graph by pattern."""
383
+ pattern = params.get("pattern")
384
+ if not pattern:
385
+ return "Error: pattern required for search"
386
+
387
+ node_type = params.get("node_type")
388
+ limit = params.get("limit", 50)
389
+
390
+ try:
391
+ with project_db.get_graph_connection() as conn:
392
+ # Search in properties
393
+ if node_type:
394
+ cursor = conn.execute("""
395
+ SELECT id, type, properties
396
+ FROM nodes
397
+ WHERE type = ? AND properties LIKE ?
398
+ LIMIT ?
399
+ """, (node_type, f"%{pattern}%", limit))
400
+ else:
401
+ cursor = conn.execute("""
402
+ SELECT id, type, properties
403
+ FROM nodes
404
+ WHERE properties LIKE ?
405
+ LIMIT ?
406
+ """, (f"%{pattern}%", limit))
407
+
408
+ results = cursor.fetchall()
409
+
410
+ if not results:
411
+ return f"No nodes found matching '{pattern}'"
412
+
413
+ # Format results
414
+ output = [f"=== Graph Search Results for '{pattern}' ==="]
415
+ output.append(f"Found {len(results)} nodes\n")
416
+
417
+ for node in results:
418
+ props = json.loads(node[2]) if node[2] else {}
419
+ output.append(f"{node[0]} [{node[1]}] {props}")
420
+
421
+ return "\n".join(output)
422
+
423
+ except Exception as e:
424
+ await tool_ctx.error(f"Search failed: {str(e)}")
425
+ return f"Error during search: {str(e)}"
426
+
427
+ async def _handle_stats(self, project_db, tool_ctx) -> str:
428
+ """Get graph statistics."""
429
+ try:
430
+ with project_db.get_graph_connection() as conn:
431
+ # Node stats
432
+ cursor = conn.execute("""
433
+ SELECT type, COUNT(*) as count
434
+ FROM nodes
435
+ GROUP BY type
436
+ ORDER BY count DESC
437
+ """)
438
+
439
+ node_stats = cursor.fetchall()
440
+
441
+ # Edge stats
442
+ cursor = conn.execute("""
443
+ SELECT type, COUNT(*) as count
444
+ FROM edges
445
+ GROUP BY type
446
+ ORDER BY count DESC
447
+ """)
448
+
449
+ edge_stats = cursor.fetchall()
450
+
451
+ # Total counts
452
+ cursor = conn.execute("SELECT COUNT(*) FROM nodes")
453
+ total_nodes = cursor.fetchone()[0]
454
+
455
+ cursor = conn.execute("SELECT COUNT(*) FROM edges")
456
+ total_edges = cursor.fetchone()[0]
457
+
458
+ # Format output
459
+ output = [f"=== Graph Database Statistics ==="]
460
+ output.append(f"Project: {project_db.project_path}")
461
+ output.append(f"\nTotal nodes: {total_nodes}")
462
+ output.append(f"Total edges: {total_edges}")
463
+
464
+ if node_stats:
465
+ output.append("\nNodes by type:")
466
+ for node_type, count in node_stats:
467
+ output.append(f" {node_type}: {count}")
468
+
469
+ if edge_stats:
470
+ output.append("\nEdges by type:")
471
+ for edge_type, count in edge_stats:
472
+ output.append(f" {edge_type}: {count}")
473
+
474
+ return "\n".join(output)
475
+
476
+ except Exception as e:
477
+ await tool_ctx.error(f"Failed to get stats: {str(e)}")
478
+ return f"Error getting stats: {str(e)}"
479
+
480
+ def register(self, mcp_server) -> None:
481
+ """Register this tool with the MCP server."""
482
+ pass
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  from typing import Annotated, Optional, TypedDict, Unpack, final, override
5
5
 
6
- from fastmcp import Context as MCPContext
6
+ from mcp.server.fastmcp import Context as MCPContext
7
7
  from pydantic import Field
8
8
 
9
9
  from hanzo_mcp.tools.common.base import BaseTool
@@ -5,7 +5,7 @@ import sqlite3
5
5
  from typing import Annotated, Optional, TypedDict, Unpack, final, override, List, Dict, Any
6
6
  from collections import deque
7
7
 
8
- from fastmcp import Context as MCPContext
8
+ from mcp.server.fastmcp import Context as MCPContext
9
9
  from pydantic import Field
10
10
 
11
11
  from hanzo_mcp.tools.common.base import BaseTool
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  from typing import Annotated, Optional, TypedDict, Unpack, final, override
5
5
 
6
- from fastmcp import Context as MCPContext
6
+ from mcp.server.fastmcp import Context as MCPContext
7
7
  from pydantic import Field
8
8
 
9
9
  from hanzo_mcp.tools.common.base import BaseTool
@@ -4,7 +4,7 @@ import json
4
4
  import sqlite3
5
5
  from typing import Annotated, Optional, TypedDict, Unpack, final, override
6
6
 
7
- from fastmcp import Context as MCPContext
7
+ from mcp.server.fastmcp import Context as MCPContext
8
8
  from pydantic import Field
9
9
 
10
10
  from hanzo_mcp.tools.common.base import BaseTool
@@ -5,7 +5,7 @@ import sqlite3
5
5
  from typing import Annotated, Optional, TypedDict, Unpack, final, override
6
6
  from collections import Counter, defaultdict
7
7
 
8
- from fastmcp import Context as MCPContext
8
+ from mcp.server.fastmcp import Context as MCPContext
9
9
  from pydantic import Field
10
10
 
11
11
  from hanzo_mcp.tools.common.base import BaseTool