hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +1 -3
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +1 -3
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +2 -6
- hanzo_mcp/tools/__init__.py +10 -24
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +5 -15
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +75 -74
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/swarm_tool.py +9 -29
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/permissions.py +3 -9
- hanzo_mcp/tools/common/personality.py +9 -34
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/filesystem/__init__.py +2 -3
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/base.py +4 -12
- hanzo_mcp/tools/filesystem/batch_search.py +35 -115
- hanzo_mcp/tools/filesystem/content_replace.py +4 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
- hanzo_mcp/tools/filesystem/edit.py +6 -18
- hanzo_mcp/tools/filesystem/find.py +3 -9
- hanzo_mcp/tools/filesystem/find_files.py +2 -6
- hanzo_mcp/tools/filesystem/git_search.py +9 -24
- hanzo_mcp/tools/filesystem/grep.py +9 -27
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
- hanzo_mcp/tools/filesystem/read.py +8 -26
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
- hanzo_mcp/tools/filesystem/search_tool.py +18 -62
- hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
- hanzo_mcp/tools/filesystem/tree.py +1 -3
- hanzo_mcp/tools/filesystem/watch.py +1 -3
- hanzo_mcp/tools/filesystem/write.py +1 -3
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
- hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +10 -32
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +2 -2
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +26 -79
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/todo/todo_read.py +3 -9
- hanzo_mcp/tools/todo/todo_write.py +6 -18
- hanzo_mcp/tools/vector/__init__.py +3 -9
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/METADATA +2 -2
- hanzo_mcp-0.8.13.dist-info/RECORD +193 -0
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.13.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/common/mode.py
CHANGED
|
@@ -94,11 +94,7 @@ def register_default_modes():
|
|
|
94
94
|
def get_mode_from_env() -> Optional[str]:
|
|
95
95
|
"""Get mode name from environment variables."""
|
|
96
96
|
# Check for HANZO_MODE, PERSONALITY, or MODE env vars
|
|
97
|
-
return (
|
|
98
|
-
os.environ.get("HANZO_MODE")
|
|
99
|
-
or os.environ.get("PERSONALITY")
|
|
100
|
-
or os.environ.get("MODE")
|
|
101
|
-
)
|
|
97
|
+
return os.environ.get("HANZO_MODE") or os.environ.get("PERSONALITY") or os.environ.get("MODE")
|
|
102
98
|
|
|
103
99
|
|
|
104
100
|
def activate_mode_from_env():
|
|
@@ -68,11 +68,7 @@ class PaginatedBaseTool(BaseTool):
|
|
|
68
68
|
if cursor:
|
|
69
69
|
# For continuation, check if we have cached results
|
|
70
70
|
cursor_data = CursorManager.parse_cursor(cursor)
|
|
71
|
-
if
|
|
72
|
-
cursor_data
|
|
73
|
-
and "tool" in cursor_data
|
|
74
|
-
and cursor_data["tool"] != self.name
|
|
75
|
-
):
|
|
71
|
+
if cursor_data and "tool" in cursor_data and cursor_data["tool"] != self.name:
|
|
76
72
|
return {"error": "Cursor is for a different tool", "code": -32602}
|
|
77
73
|
|
|
78
74
|
# Execute the tool
|
|
@@ -90,15 +86,11 @@ class PaginatedBaseTool(BaseTool):
|
|
|
90
86
|
if isinstance(paginated_result, dict) and "nextCursor" in paginated_result:
|
|
91
87
|
# Enhance the cursor with tool information
|
|
92
88
|
if "nextCursor" in paginated_result:
|
|
93
|
-
cursor_data = CursorManager.parse_cursor(
|
|
94
|
-
paginated_result["nextCursor"]
|
|
95
|
-
)
|
|
89
|
+
cursor_data = CursorManager.parse_cursor(paginated_result["nextCursor"])
|
|
96
90
|
if cursor_data:
|
|
97
91
|
cursor_data["tool"] = self.name
|
|
98
92
|
cursor_data["params"] = params # Store params for continuation
|
|
99
|
-
paginated_result["nextCursor"] = CursorManager.create_cursor(
|
|
100
|
-
cursor_data
|
|
101
|
-
)
|
|
93
|
+
paginated_result["nextCursor"] = CursorManager.create_cursor(cursor_data)
|
|
102
94
|
|
|
103
95
|
return paginated_result
|
|
104
96
|
else:
|
|
@@ -35,31 +35,21 @@ class AutoPaginatedResponse:
|
|
|
35
35
|
"""
|
|
36
36
|
# Handle different content types
|
|
37
37
|
if isinstance(content, str):
|
|
38
|
-
return AutoPaginatedResponse._handle_string_response(
|
|
39
|
-
content, cursor, max_tokens
|
|
40
|
-
)
|
|
38
|
+
return AutoPaginatedResponse._handle_string_response(content, cursor, max_tokens)
|
|
41
39
|
elif isinstance(content, list):
|
|
42
|
-
return AutoPaginatedResponse._handle_list_response(
|
|
43
|
-
content, cursor, max_tokens
|
|
44
|
-
)
|
|
40
|
+
return AutoPaginatedResponse._handle_list_response(content, cursor, max_tokens)
|
|
45
41
|
elif isinstance(content, dict):
|
|
46
42
|
# If dict already has pagination info, return as-is
|
|
47
43
|
if "nextCursor" in content or "cursor" in content:
|
|
48
44
|
return content
|
|
49
45
|
# Otherwise treat as single item
|
|
50
|
-
return AutoPaginatedResponse._handle_dict_response(
|
|
51
|
-
content, cursor, max_tokens
|
|
52
|
-
)
|
|
46
|
+
return AutoPaginatedResponse._handle_dict_response(content, cursor, max_tokens)
|
|
53
47
|
else:
|
|
54
48
|
# Convert to string for other types
|
|
55
|
-
return AutoPaginatedResponse._handle_string_response(
|
|
56
|
-
str(content), cursor, max_tokens
|
|
57
|
-
)
|
|
49
|
+
return AutoPaginatedResponse._handle_string_response(str(content), cursor, max_tokens)
|
|
58
50
|
|
|
59
51
|
@staticmethod
|
|
60
|
-
def _handle_string_response(
|
|
61
|
-
content: str, cursor: Optional[str], max_tokens: int
|
|
62
|
-
) -> Dict[str, Any]:
|
|
52
|
+
def _handle_string_response(content: str, cursor: Optional[str], max_tokens: int) -> Dict[str, Any]:
|
|
63
53
|
"""Handle pagination for string responses."""
|
|
64
54
|
# Parse cursor to get offset
|
|
65
55
|
offset = 0
|
|
@@ -124,9 +114,7 @@ class AutoPaginatedResponse:
|
|
|
124
114
|
return response
|
|
125
115
|
|
|
126
116
|
@staticmethod
|
|
127
|
-
def _handle_list_response(
|
|
128
|
-
items: List[Any], cursor: Optional[str], max_tokens: int
|
|
129
|
-
) -> Dict[str, Any]:
|
|
117
|
+
def _handle_list_response(items: List[Any], cursor: Optional[str], max_tokens: int) -> Dict[str, Any]:
|
|
130
118
|
"""Handle pagination for list responses."""
|
|
131
119
|
# Parse cursor to get offset
|
|
132
120
|
offset = 0
|
|
@@ -164,9 +152,7 @@ class AutoPaginatedResponse:
|
|
|
164
152
|
truncated = item[:5000] + "... [truncated]"
|
|
165
153
|
result_items.append(truncated)
|
|
166
154
|
else:
|
|
167
|
-
result_items.append(
|
|
168
|
-
{"error": "Item too large", "index": item_index}
|
|
169
|
-
)
|
|
155
|
+
result_items.append({"error": "Item too large", "index": item_index})
|
|
170
156
|
item_index += 1
|
|
171
157
|
break
|
|
172
158
|
|
|
@@ -199,9 +185,7 @@ class AutoPaginatedResponse:
|
|
|
199
185
|
return response
|
|
200
186
|
|
|
201
187
|
@staticmethod
|
|
202
|
-
def _handle_dict_response(
|
|
203
|
-
content: Dict[str, Any], cursor: Optional[str], max_tokens: int
|
|
204
|
-
) -> Dict[str, Any]:
|
|
188
|
+
def _handle_dict_response(content: Dict[str, Any], cursor: Optional[str], max_tokens: int) -> Dict[str, Any]:
|
|
205
189
|
"""Handle pagination for dict responses."""
|
|
206
190
|
# For dicts, check if it's too large as-is
|
|
207
191
|
content_str = json.dumps(content, indent=2)
|
|
@@ -280,17 +264,13 @@ def paginate_if_needed(
|
|
|
280
264
|
Original response if small enough, otherwise paginated dict
|
|
281
265
|
"""
|
|
282
266
|
# Quick check - if response is already paginated, return as-is
|
|
283
|
-
if isinstance(response, dict) and (
|
|
284
|
-
"nextCursor" in response or "pagination_info" in response
|
|
285
|
-
):
|
|
267
|
+
if isinstance(response, dict) and ("nextCursor" in response or "pagination_info" in response):
|
|
286
268
|
return response
|
|
287
269
|
|
|
288
270
|
# For small responses, don't paginate unless forced
|
|
289
271
|
if not force_pagination:
|
|
290
272
|
try:
|
|
291
|
-
response_str = (
|
|
292
|
-
json.dumps(response) if not isinstance(response, str) else response
|
|
293
|
-
)
|
|
273
|
+
response_str = json.dumps(response) if not isinstance(response, str) else response
|
|
294
274
|
if len(response_str) < 10000: # Quick heuristic
|
|
295
275
|
return response
|
|
296
276
|
except Exception:
|
|
@@ -154,9 +154,7 @@ class StreamPaginator(Generic[T]):
|
|
|
154
154
|
"""
|
|
155
155
|
self.page_size = page_size
|
|
156
156
|
|
|
157
|
-
def paginate_stream(
|
|
158
|
-
self, stream_generator, cursor: Optional[str] = None
|
|
159
|
-
) -> PaginatedResponse[T]:
|
|
157
|
+
def paginate_stream(self, stream_generator, cursor: Optional[str] = None) -> PaginatedResponse[T]:
|
|
160
158
|
"""Paginate results from a stream/generator.
|
|
161
159
|
|
|
162
160
|
Args:
|
|
@@ -185,18 +183,14 @@ class StreamPaginator(Generic[T]):
|
|
|
185
183
|
items.append(item)
|
|
186
184
|
if len(items) >= self.page_size:
|
|
187
185
|
# We have a full page, create cursor for next page
|
|
188
|
-
next_cursor = CursorManager.create_cursor(
|
|
189
|
-
{"skip": skip_count + len(items)}
|
|
190
|
-
)
|
|
186
|
+
next_cursor = CursorManager.create_cursor({"skip": skip_count + len(items)})
|
|
191
187
|
return PaginatedResponse(items=items, next_cursor=next_cursor)
|
|
192
188
|
|
|
193
189
|
# No more items
|
|
194
190
|
return PaginatedResponse(items=items, next_cursor=None)
|
|
195
191
|
|
|
196
192
|
|
|
197
|
-
def paginate_list(
|
|
198
|
-
items: List[T], cursor: Optional[str] = None, page_size: int = 100
|
|
199
|
-
) -> PaginatedResponse[T]:
|
|
193
|
+
def paginate_list(items: List[T], cursor: Optional[str] = None, page_size: int = 100) -> PaginatedResponse[T]:
|
|
200
194
|
"""Convenience function to paginate a list.
|
|
201
195
|
|
|
202
196
|
Args:
|
|
@@ -225,13 +225,9 @@ class PermissibleOperation:
|
|
|
225
225
|
"""
|
|
226
226
|
self.permission_manager: PermissionManager = permission_manager
|
|
227
227
|
self.operation: str = operation
|
|
228
|
-
self.get_path_fn: Callable[[list[Any], dict[str, Any]], str] | None =
|
|
229
|
-
get_path_fn
|
|
230
|
-
)
|
|
228
|
+
self.get_path_fn: Callable[[list[Any], dict[str, Any]], str] | None = get_path_fn
|
|
231
229
|
|
|
232
|
-
def __call__(
|
|
233
|
-
self, func: Callable[..., Awaitable[T]]
|
|
234
|
-
) -> Callable[..., Awaitable[T]]:
|
|
230
|
+
def __call__(self, func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
|
|
235
231
|
"""Decorate the function.
|
|
236
232
|
|
|
237
233
|
Args:
|
|
@@ -255,9 +251,7 @@ class PermissibleOperation:
|
|
|
255
251
|
|
|
256
252
|
# Check permission
|
|
257
253
|
if not self.permission_manager.is_path_allowed(path):
|
|
258
|
-
raise PermissionError(
|
|
259
|
-
f"Operation '{self.operation}' not allowed for path: {path}"
|
|
260
|
-
)
|
|
254
|
+
raise PermissionError(f"Operation '{self.operation}' not allowed for path: {path}")
|
|
261
255
|
|
|
262
256
|
# Call the function
|
|
263
257
|
return await func(*args, **kwargs)
|
|
@@ -88,10 +88,7 @@ personalities = [
|
|
|
88
88
|
programmer="Guido van Rossum",
|
|
89
89
|
description="Python's BDFL - readability counts",
|
|
90
90
|
philosophy="There should be one-- and preferably only one --obvious way to do it.",
|
|
91
|
-
tools=ESSENTIAL_TOOLS
|
|
92
|
-
+ ["uvx", "jupyter", "multi_edit", "symbols", "rules"]
|
|
93
|
-
+ AI_TOOLS
|
|
94
|
-
+ SEARCH_TOOLS,
|
|
91
|
+
tools=ESSENTIAL_TOOLS + ["uvx", "jupyter", "multi_edit", "symbols", "rules"] + AI_TOOLS + SEARCH_TOOLS,
|
|
95
92
|
environment={"PYTHONPATH": ".", "PYTEST_ARGS": "-xvs"},
|
|
96
93
|
),
|
|
97
94
|
ToolPersonality(
|
|
@@ -107,10 +104,7 @@ personalities = [
|
|
|
107
104
|
programmer="Brendan Eich",
|
|
108
105
|
description="JavaScript creator - dynamic and flexible",
|
|
109
106
|
philosophy="Always bet on JS.",
|
|
110
|
-
tools=ESSENTIAL_TOOLS
|
|
111
|
-
+ ["npx", "watch", "symbols", "todo", "rules"]
|
|
112
|
-
+ BUILD_TOOLS
|
|
113
|
-
+ SEARCH_TOOLS,
|
|
107
|
+
tools=ESSENTIAL_TOOLS + ["npx", "watch", "symbols", "todo", "rules"] + BUILD_TOOLS + SEARCH_TOOLS,
|
|
114
108
|
environment={"NODE_ENV": "development", "NPM_CONFIG_LOGLEVEL": "warn"},
|
|
115
109
|
),
|
|
116
110
|
ToolPersonality(
|
|
@@ -126,10 +120,7 @@ personalities = [
|
|
|
126
120
|
programmer="Bjarne Stroustrup",
|
|
127
121
|
description="C++ creator - zero-overhead abstractions",
|
|
128
122
|
philosophy="C++ is designed to allow you to express ideas.",
|
|
129
|
-
tools=ESSENTIAL_TOOLS
|
|
130
|
-
+ ["symbols", "multi_edit", "content_replace"]
|
|
131
|
-
+ UNIX_TOOLS
|
|
132
|
-
+ BUILD_TOOLS,
|
|
123
|
+
tools=ESSENTIAL_TOOLS + ["symbols", "multi_edit", "content_replace"] + UNIX_TOOLS + BUILD_TOOLS,
|
|
133
124
|
environment={"CXX": "g++", "CXXFLAGS": "-std=c++20 -Wall"},
|
|
134
125
|
),
|
|
135
126
|
ToolPersonality(
|
|
@@ -145,10 +136,7 @@ personalities = [
|
|
|
145
136
|
programmer="Anders Hejlsberg",
|
|
146
137
|
description="TypeScript/C# creator - type safety matters",
|
|
147
138
|
philosophy="TypeScript is JavaScript that scales.",
|
|
148
|
-
tools=ESSENTIAL_TOOLS
|
|
149
|
-
+ ["npx", "symbols", "watch", "rules"]
|
|
150
|
-
+ BUILD_TOOLS
|
|
151
|
-
+ SEARCH_TOOLS,
|
|
139
|
+
tools=ESSENTIAL_TOOLS + ["npx", "symbols", "watch", "rules"] + BUILD_TOOLS + SEARCH_TOOLS,
|
|
152
140
|
environment={"TYPESCRIPT_VERSION": "5.0"},
|
|
153
141
|
),
|
|
154
142
|
ToolPersonality(
|
|
@@ -181,9 +169,7 @@ personalities = [
|
|
|
181
169
|
programmer="Linus Torvalds",
|
|
182
170
|
description="Linux & Git creator - pragmatic excellence",
|
|
183
171
|
philosophy="Talk is cheap. Show me the code.",
|
|
184
|
-
tools=ESSENTIAL_TOOLS
|
|
185
|
-
+ ["git_search", "diff", "content_replace", "critic"]
|
|
186
|
-
+ UNIX_TOOLS,
|
|
172
|
+
tools=ESSENTIAL_TOOLS + ["git_search", "diff", "content_replace", "critic"] + UNIX_TOOLS,
|
|
187
173
|
environment={"KERNEL_VERSION": "6.0", "GIT_AUTHOR_NAME": "Linus Torvalds"},
|
|
188
174
|
),
|
|
189
175
|
ToolPersonality(
|
|
@@ -191,10 +177,7 @@ personalities = [
|
|
|
191
177
|
programmer="Rob Pike",
|
|
192
178
|
description="Go creator - simplicity and concurrency",
|
|
193
179
|
philosophy="A little copying is better than a little dependency.",
|
|
194
|
-
tools=ESSENTIAL_TOOLS
|
|
195
|
-
+ ["symbols", "batch", "process"]
|
|
196
|
-
+ UNIX_TOOLS
|
|
197
|
-
+ BUILD_TOOLS,
|
|
180
|
+
tools=ESSENTIAL_TOOLS + ["symbols", "batch", "process"] + UNIX_TOOLS + BUILD_TOOLS,
|
|
198
181
|
environment={"GOPATH": "~/go", "GO111MODULE": "on"},
|
|
199
182
|
),
|
|
200
183
|
ToolPersonality(
|
|
@@ -242,9 +225,7 @@ personalities = [
|
|
|
242
225
|
programmer="Graydon Hoare",
|
|
243
226
|
description="Rust creator - memory safety without GC",
|
|
244
227
|
philosophy="Memory safety without garbage collection, concurrency without data races.",
|
|
245
|
-
tools=ESSENTIAL_TOOLS
|
|
246
|
-
+ ["symbols", "multi_edit", "critic", "todo"]
|
|
247
|
-
+ BUILD_TOOLS,
|
|
228
|
+
tools=ESSENTIAL_TOOLS + ["symbols", "multi_edit", "critic", "todo"] + BUILD_TOOLS,
|
|
248
229
|
environment={"RUST_BACKTRACE": "1", "CARGO_HOME": "~/.cargo"},
|
|
249
230
|
),
|
|
250
231
|
ToolPersonality(
|
|
@@ -871,10 +852,7 @@ personalities = [
|
|
|
871
852
|
programmer="Data Scientist",
|
|
872
853
|
description="Analyze all the things",
|
|
873
854
|
philosophy="In God we trust. All others must bring data.",
|
|
874
|
-
tools=ESSENTIAL_TOOLS
|
|
875
|
-
+ ["jupyter", "sql_query", "stats"]
|
|
876
|
-
+ VECTOR_TOOLS
|
|
877
|
-
+ AI_TOOLS,
|
|
855
|
+
tools=ESSENTIAL_TOOLS + ["jupyter", "sql_query", "stats"] + VECTOR_TOOLS + AI_TOOLS,
|
|
878
856
|
environment={"JUPYTER_THEME": "dark"},
|
|
879
857
|
),
|
|
880
858
|
ToolPersonality(
|
|
@@ -906,10 +884,7 @@ personalities = [
|
|
|
906
884
|
programmer="Startup Founder",
|
|
907
885
|
description="Move fast and fix things",
|
|
908
886
|
philosophy="Done is better than perfect.",
|
|
909
|
-
tools=ESSENTIAL_TOOLS
|
|
910
|
-
+ ["todo", "agent", "consensus"]
|
|
911
|
-
+ BUILD_TOOLS
|
|
912
|
-
+ DATABASE_TOOLS,
|
|
887
|
+
tools=ESSENTIAL_TOOLS + ["todo", "agent", "consensus"] + BUILD_TOOLS + DATABASE_TOOLS,
|
|
913
888
|
environment={"STARTUP_MODE": "hustle"},
|
|
914
889
|
),
|
|
915
890
|
ToolPersonality(
|
|
@@ -88,12 +88,7 @@ class PluginLoader:
|
|
|
88
88
|
|
|
89
89
|
# Find tool classes
|
|
90
90
|
for _name, obj in inspect.getmembers(module):
|
|
91
|
-
if (
|
|
92
|
-
inspect.isclass(obj)
|
|
93
|
-
and issubclass(obj, BaseTool)
|
|
94
|
-
and obj != BaseTool
|
|
95
|
-
and hasattr(obj, "name")
|
|
96
|
-
):
|
|
91
|
+
if inspect.isclass(obj) and issubclass(obj, BaseTool) and obj != BaseTool and hasattr(obj, "name"):
|
|
97
92
|
# Load metadata if available
|
|
98
93
|
metadata = None
|
|
99
94
|
metadata_file = file_path.with_suffix(".json")
|
|
@@ -134,15 +129,8 @@ class PluginLoader:
|
|
|
134
129
|
else:
|
|
135
130
|
# Search for tool classes
|
|
136
131
|
for _name, obj in inspect.getmembers(module):
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
and issubclass(obj, BaseTool)
|
|
140
|
-
and obj != BaseTool
|
|
141
|
-
and hasattr(obj, "name")
|
|
142
|
-
):
|
|
143
|
-
plugin = ToolPlugin(
|
|
144
|
-
name=obj.name, tool_class=obj, source_path=package_dir
|
|
145
|
-
)
|
|
132
|
+
if inspect.isclass(obj) and issubclass(obj, BaseTool) and obj != BaseTool and hasattr(obj, "name"):
|
|
133
|
+
plugin = ToolPlugin(name=obj.name, tool_class=obj, source_path=package_dir)
|
|
146
134
|
self.plugins[obj.name] = plugin
|
|
147
135
|
finally:
|
|
148
136
|
# Remove from path
|
hanzo_mcp/tools/common/stats.py
CHANGED
|
@@ -97,9 +97,7 @@ Example:
|
|
|
97
97
|
memory_used_gb = memory.used / (1024**3)
|
|
98
98
|
memory_total_gb = memory.total / (1024**3)
|
|
99
99
|
memory_percent = memory.percent
|
|
100
|
-
output.append(
|
|
101
|
-
f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)"
|
|
102
|
-
)
|
|
100
|
+
output.append(f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)")
|
|
103
101
|
if memory_percent > 90:
|
|
104
102
|
warnings.append(f"⚠️ HIGH MEMORY USAGE: {memory_percent}%")
|
|
105
103
|
|
|
@@ -109,14 +107,10 @@ Example:
|
|
|
109
107
|
disk_total_gb = disk.total / (1024**3)
|
|
110
108
|
disk_percent = disk.percent
|
|
111
109
|
disk_free_gb = disk.free / (1024**3)
|
|
112
|
-
output.append(
|
|
113
|
-
f"Disk: {disk_used_gb:.1f}/{disk_total_gb:.1f} GB ({disk_percent}%)"
|
|
114
|
-
)
|
|
110
|
+
output.append(f"Disk: {disk_used_gb:.1f}/{disk_total_gb:.1f} GB ({disk_percent}%)")
|
|
115
111
|
output.append(f"Free Space: {disk_free_gb:.1f} GB")
|
|
116
112
|
if disk_percent > 90:
|
|
117
|
-
warnings.append(
|
|
118
|
-
f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100 - disk_percent:.1f}% remaining)"
|
|
119
|
-
)
|
|
113
|
+
warnings.append(f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100 - disk_percent:.1f}% remaining)")
|
|
120
114
|
|
|
121
115
|
output.append("")
|
|
122
116
|
|
|
@@ -173,9 +167,7 @@ Example:
|
|
|
173
167
|
size = db_file.stat().st_size
|
|
174
168
|
total_db_size += size
|
|
175
169
|
|
|
176
|
-
output.append(
|
|
177
|
-
f"Total Database Size: {total_db_size / (1024**2):.1f} MB"
|
|
178
|
-
)
|
|
170
|
+
output.append(f"Total Database Size: {total_db_size / (1024**2):.1f} MB")
|
|
179
171
|
output.append(f"Active Projects: {len(self.db_manager.projects)}")
|
|
180
172
|
|
|
181
173
|
# List largest databases
|
|
@@ -201,9 +193,7 @@ Example:
|
|
|
201
193
|
output.append("=== MCP Servers ===")
|
|
202
194
|
mcp_servers = McpAddTool.get_servers()
|
|
203
195
|
if mcp_servers:
|
|
204
|
-
running_mcp = sum(
|
|
205
|
-
1 for s in mcp_servers.values() if s.get("status") == "running"
|
|
206
|
-
)
|
|
196
|
+
running_mcp = sum(1 for s in mcp_servers.values() if s.get("status") == "running")
|
|
207
197
|
total_mcp_tools = sum(len(s.get("tools", [])) for s in mcp_servers.values())
|
|
208
198
|
|
|
209
199
|
output.append(f"Total Servers: {len(mcp_servers)}")
|
|
@@ -225,9 +215,7 @@ Example:
|
|
|
225
215
|
output.append(f"Log Files: {log_count} ({log_size / (1024**2):.1f} MB)")
|
|
226
216
|
|
|
227
217
|
if log_size > 100 * 1024**2: # > 100MB
|
|
228
|
-
warnings.append(
|
|
229
|
-
f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB"
|
|
230
|
-
)
|
|
218
|
+
warnings.append(f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB")
|
|
231
219
|
|
|
232
220
|
# Config directory
|
|
233
221
|
config_dir = Path.home() / ".hanzo" / "mcp"
|
|
@@ -115,9 +115,7 @@ Feature Implementation Planning
|
|
|
115
115
|
|
|
116
116
|
# Validate required thought parameter
|
|
117
117
|
if not thought:
|
|
118
|
-
await tool_ctx.error(
|
|
119
|
-
"Parameter 'thought' is required but was None or empty"
|
|
120
|
-
)
|
|
118
|
+
await tool_ctx.error("Parameter 'thought' is required but was None or empty")
|
|
121
119
|
return "Error: Parameter 'thought' is required but was None or empty"
|
|
122
120
|
|
|
123
121
|
if thought.strip() == "":
|
|
@@ -125,9 +125,7 @@ Use 'tool_enable' to re-enable disabled tools.
|
|
|
125
125
|
]
|
|
126
126
|
|
|
127
127
|
if not persist:
|
|
128
|
-
output.append(
|
|
129
|
-
"\nNote: This change is temporary and will be lost on restart."
|
|
130
|
-
)
|
|
128
|
+
output.append("\nNote: This change is temporary and will be lost on restart.")
|
|
131
129
|
|
|
132
130
|
# Warn about commonly used tools
|
|
133
131
|
common_tools = {"grep", "read", "write", "bash", "edit"}
|
|
@@ -137,9 +135,7 @@ Use 'tool_enable' to re-enable disabled tools.
|
|
|
137
135
|
)
|
|
138
136
|
|
|
139
137
|
# Count disabled tools
|
|
140
|
-
disabled_count = sum(
|
|
141
|
-
1 for enabled in ToolEnableTool._tool_states.values() if not enabled
|
|
142
|
-
)
|
|
138
|
+
disabled_count = sum(1 for enabled in ToolEnableTool._tool_states.values() if not enabled)
|
|
143
139
|
output.append(f"\nTotal disabled tools: {disabled_count}")
|
|
144
140
|
|
|
145
141
|
return "\n".join(output)
|
|
@@ -205,9 +205,7 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
205
205
|
|
|
206
206
|
# Iterate through categories
|
|
207
207
|
categories = (
|
|
208
|
-
[category_filter]
|
|
209
|
-
if category_filter and category_filter in self.TOOL_INFO
|
|
210
|
-
else self.TOOL_INFO.keys()
|
|
208
|
+
[category_filter] if category_filter and category_filter in self.TOOL_INFO else self.TOOL_INFO.keys()
|
|
211
209
|
)
|
|
212
210
|
|
|
213
211
|
for category in categories:
|
|
@@ -242,9 +240,7 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
242
240
|
max_name_len = max(len(name) for name, _, _ in category_shown)
|
|
243
241
|
|
|
244
242
|
for tool_name, description, status in category_shown:
|
|
245
|
-
output.append(
|
|
246
|
-
f"{status} {tool_name.ljust(max_name_len)} - {description}"
|
|
247
|
-
)
|
|
243
|
+
output.append(f"{status} {tool_name.ljust(max_name_len)} - {description}")
|
|
248
244
|
|
|
249
245
|
output.append("")
|
|
250
246
|
|
|
@@ -32,9 +32,7 @@ class ValidationResult:
|
|
|
32
32
|
return not self.is_valid
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def validate_path_parameter(
|
|
36
|
-
path: str | None, parameter_name: str = "path"
|
|
37
|
-
) -> ValidationResult:
|
|
35
|
+
def validate_path_parameter(path: str | None, parameter_name: str = "path") -> ValidationResult:
|
|
38
36
|
"""Validate a path parameter.
|
|
39
37
|
|
|
40
38
|
Args:
|
|
@@ -131,9 +131,7 @@ config --action toggle index.scope --path ./project"""
|
|
|
131
131
|
else:
|
|
132
132
|
return f"Error: Unknown action '{action}'. Valid actions: get, set, list, toggle"
|
|
133
133
|
|
|
134
|
-
async def _handle_get(
|
|
135
|
-
self, key: Optional[str], scope: str, path: Optional[str], tool_ctx
|
|
136
|
-
) -> str:
|
|
134
|
+
async def _handle_get(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
137
135
|
"""Get configuration value."""
|
|
138
136
|
if not key:
|
|
139
137
|
return "Error: key required for get action"
|
|
@@ -173,7 +171,9 @@ config --action toggle index.scope --path ./project"""
|
|
|
173
171
|
project_path = Path(project_dir)
|
|
174
172
|
project_path.mkdir(parents=True, exist_ok=True)
|
|
175
173
|
cfg = project_path / ".hanzo-mcp.json"
|
|
176
|
-
cfg.write_text(
|
|
174
|
+
cfg.write_text(
|
|
175
|
+
__import__("json").dumps(settings.__dict__ if hasattr(settings, "__dict__") else {}, indent=2)
|
|
176
|
+
)
|
|
177
177
|
return cfg
|
|
178
178
|
# Fallback to global handler
|
|
179
179
|
return save_settings(settings, global_config=True)
|
|
@@ -196,9 +196,7 @@ config --action toggle index.scope --path ./project"""
|
|
|
196
196
|
if key == "index.scope":
|
|
197
197
|
try:
|
|
198
198
|
new_scope = IndexScope(value)
|
|
199
|
-
self.index_config.set_scope(
|
|
200
|
-
new_scope, path if scope == "local" else None
|
|
201
|
-
)
|
|
199
|
+
self.index_config.set_scope(new_scope, path if scope == "local" else None)
|
|
202
200
|
return f"Set {key}={value} ({'project' if path else 'global'})"
|
|
203
201
|
except ValueError:
|
|
204
202
|
return f"Error: Invalid scope value '{value}'. Valid: project, global, auto"
|
|
@@ -281,18 +279,14 @@ config --action toggle index.scope --path ./project"""
|
|
|
281
279
|
|
|
282
280
|
return "\n".join(output)
|
|
283
281
|
|
|
284
|
-
async def _handle_toggle(
|
|
285
|
-
self, key: Optional[str], scope: str, path: Optional[str], tool_ctx
|
|
286
|
-
) -> str:
|
|
282
|
+
async def _handle_toggle(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
287
283
|
"""Toggle configuration value."""
|
|
288
284
|
if not key:
|
|
289
285
|
return "Error: key required for toggle action"
|
|
290
286
|
|
|
291
287
|
# Handle index scope toggle
|
|
292
288
|
if key == "index.scope":
|
|
293
|
-
new_scope = self.index_config.toggle_scope(
|
|
294
|
-
path if scope == "local" else None
|
|
295
|
-
)
|
|
289
|
+
new_scope = self.index_config.toggle_scope(path if scope == "local" else None)
|
|
296
290
|
return f"Toggled index.scope to {new_scope.value}"
|
|
297
291
|
|
|
298
292
|
# Handle execution tool enable/disable: tools.<name>.enabled or enabled_tools.<name>
|
|
@@ -99,9 +99,7 @@ class IndexConfig:
|
|
|
99
99
|
if project_root:
|
|
100
100
|
if str(project_root) not in self._config["project_configs"]:
|
|
101
101
|
self._config["project_configs"][str(project_root)] = {}
|
|
102
|
-
self._config["project_configs"][str(project_root)][
|
|
103
|
-
"scope"
|
|
104
|
-
] = scope.value
|
|
102
|
+
self._config["project_configs"][str(project_root)]["scope"] = scope.value
|
|
105
103
|
else:
|
|
106
104
|
# Set global default
|
|
107
105
|
self._config["default_scope"] = scope.value
|
|
@@ -187,12 +187,8 @@ mode --action current"""
|
|
|
187
187
|
for mode_name in mode_names:
|
|
188
188
|
mode = next((m for m in modes if m.name == mode_name), None)
|
|
189
189
|
if mode:
|
|
190
|
-
marker = (
|
|
191
|
-
|
|
192
|
-
)
|
|
193
|
-
output.append(
|
|
194
|
-
f" {mode.name}{marker}: {mode.programmer} - {mode.description}"
|
|
195
|
-
)
|
|
190
|
+
marker = " (active)" if active and active.name == mode.name else ""
|
|
191
|
+
output.append(f" {mode.name}{marker}: {mode.programmer} - {mode.description}")
|
|
196
192
|
|
|
197
193
|
output.append("\nUse 'mode --action activate <name>' to activate a mode")
|
|
198
194
|
|
|
@@ -256,9 +252,7 @@ mode --action current"""
|
|
|
256
252
|
for key, value in mode.environment.items():
|
|
257
253
|
output.append(f" {key}={value}")
|
|
258
254
|
|
|
259
|
-
output.append(
|
|
260
|
-
"\nNote: Restart MCP session for changes to take full effect"
|
|
261
|
-
)
|
|
255
|
+
output.append("\nNote: Restart MCP session for changes to take full effect")
|
|
262
256
|
|
|
263
257
|
return "\n".join(output)
|
|
264
258
|
|
|
@@ -312,17 +306,13 @@ mode --action current"""
|
|
|
312
306
|
tool_self = self
|
|
313
307
|
|
|
314
308
|
@server.tool(name=self.name, description=self.description)
|
|
315
|
-
async def mode_handler(
|
|
316
|
-
ctx: MCPContext, action: str = "list", name: Optional[str] = None
|
|
317
|
-
) -> str:
|
|
309
|
+
async def mode_handler(ctx: MCPContext, action: str = "list", name: Optional[str] = None) -> str:
|
|
318
310
|
"""Handle mode tool calls."""
|
|
319
311
|
return await tool_self.run(ctx, action=action, name=name)
|
|
320
312
|
|
|
321
313
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
322
314
|
"""Call the tool with arguments."""
|
|
323
|
-
return await self.run(
|
|
324
|
-
ctx, action=params.get("action", "list"), name=params.get("name")
|
|
325
|
-
)
|
|
315
|
+
return await self.run(ctx, action=params.get("action", "list"), name=params.get("name"))
|
|
326
316
|
|
|
327
317
|
|
|
328
318
|
# Create tool instance
|
|
@@ -127,9 +127,7 @@ class ProjectDatabase:
|
|
|
127
127
|
# Indexes for graph traversal
|
|
128
128
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source)")
|
|
129
129
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target)")
|
|
130
|
-
conn.execute(
|
|
131
|
-
"CREATE INDEX IF NOT EXISTS idx_edges_relationship ON edges(relationship)"
|
|
132
|
-
)
|
|
130
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_edges_relationship ON edges(relationship)")
|
|
133
131
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type)")
|
|
134
132
|
|
|
135
133
|
def _load_graph_from_disk(self):
|
|
@@ -138,15 +136,11 @@ class ProjectDatabase:
|
|
|
138
136
|
try:
|
|
139
137
|
# Copy nodes
|
|
140
138
|
nodes = disk_conn.execute("SELECT * FROM nodes").fetchall()
|
|
141
|
-
self.graph_conn.executemany(
|
|
142
|
-
"INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?)", nodes
|
|
143
|
-
)
|
|
139
|
+
self.graph_conn.executemany("INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?)", nodes)
|
|
144
140
|
|
|
145
141
|
# Copy edges
|
|
146
142
|
edges = disk_conn.execute("SELECT * FROM edges").fetchall()
|
|
147
|
-
self.graph_conn.executemany(
|
|
148
|
-
"INSERT OR REPLACE INTO edges VALUES (?, ?, ?, ?, ?, ?)", edges
|
|
149
|
-
)
|
|
143
|
+
self.graph_conn.executemany("INSERT OR REPLACE INTO edges VALUES (?, ?, ?, ?, ?, ?)", edges)
|
|
150
144
|
|
|
151
145
|
self.graph_conn.commit()
|
|
152
146
|
finally:
|
|
@@ -120,9 +120,7 @@ class GraphParams(TypedDict, total=False):
|
|
|
120
120
|
class GraphTool(BaseTool):
|
|
121
121
|
"""Unified graph database tool."""
|
|
122
122
|
|
|
123
|
-
def __init__(
|
|
124
|
-
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
125
|
-
):
|
|
123
|
+
def __init__(self, permission_manager: PermissionManager, db_manager: DatabaseManager):
|
|
126
124
|
"""Initialize the graph tool."""
|
|
127
125
|
super().__init__(permission_manager)
|
|
128
126
|
self.db_manager = db_manager
|
|
@@ -93,9 +93,7 @@ class GraphAddParams(TypedDict, total=False):
|
|
|
93
93
|
class GraphAddTool(BaseTool):
|
|
94
94
|
"""Tool for adding nodes and edges to graph database."""
|
|
95
95
|
|
|
96
|
-
def __init__(
|
|
97
|
-
self, permission_manager: PermissionManager, db_manager: DatabaseManager
|
|
98
|
-
):
|
|
96
|
+
def __init__(self, permission_manager: PermissionManager, db_manager: DatabaseManager):
|
|
99
97
|
"""Initialize the graph add tool.
|
|
100
98
|
|
|
101
99
|
Args:
|
|
@@ -225,15 +223,11 @@ Examples:
|
|
|
225
223
|
if not relationship:
|
|
226
224
|
return "Error: relationship is required when adding an edge"
|
|
227
225
|
|
|
228
|
-
await tool_ctx.info(
|
|
229
|
-
f"Adding edge: {source} --[{relationship}]--> {target}"
|
|
230
|
-
)
|
|
226
|
+
await tool_ctx.info(f"Adding edge: {source} --[{relationship}]--> {target}")
|
|
231
227
|
|
|
232
228
|
# Check if nodes exist
|
|
233
229
|
cursor = graph_conn.cursor()
|
|
234
|
-
cursor.execute(
|
|
235
|
-
"SELECT id FROM nodes WHERE id IN (?, ?)", (source, target)
|
|
236
|
-
)
|
|
230
|
+
cursor.execute("SELECT id FROM nodes WHERE id IN (?, ?)", (source, target))
|
|
237
231
|
existing = [row[0] for row in cursor.fetchall()]
|
|
238
232
|
|
|
239
233
|
if source not in existing:
|