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.
- hanzo_mcp/__init__.py +7 -1
- 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.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -5,19 +5,18 @@ parallel or serial depending on their characteristics.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any, Unpack, Annotated, TypedDict, final, override
|
|
9
9
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
10
|
from pydantic import Field
|
|
11
|
+
from mcp.server import FastMCP
|
|
12
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
15
15
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
16
|
-
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
16
|
+
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
17
17
|
from hanzo_mcp.tools.common.fastmcp_pagination import (
|
|
18
|
-
create_paginated_response,
|
|
19
18
|
CursorData,
|
|
20
|
-
|
|
19
|
+
create_paginated_response,
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -197,9 +196,9 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
197
196
|
|
|
198
197
|
# Execute all tool invocations in parallel
|
|
199
198
|
tasks: list[asyncio.Future[dict[str, Any]]] = []
|
|
200
|
-
invocation_map: dict[
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
invocation_map: dict[asyncio.Future[dict[str, Any]], dict[str, Any]] = (
|
|
200
|
+
{}
|
|
201
|
+
) # Map task Future to invocation
|
|
203
202
|
|
|
204
203
|
for i, invocation in enumerate(invocations):
|
|
205
204
|
# Extract tool name and input from invocation
|
|
@@ -290,7 +289,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
290
289
|
# Extract cursor if provided
|
|
291
290
|
cursor = params.get("cursor")
|
|
292
291
|
cursor_offset = 0
|
|
293
|
-
|
|
292
|
+
|
|
294
293
|
# If cursor provided, we need to resume from where we left off
|
|
295
294
|
if cursor:
|
|
296
295
|
cursor_data = CursorData.from_cursor(cursor)
|
|
@@ -298,63 +297,71 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
298
297
|
# Skip already returned results
|
|
299
298
|
cursor_offset = cursor_data.offset
|
|
300
299
|
results = results[cursor_offset:]
|
|
301
|
-
|
|
300
|
+
|
|
302
301
|
# Format results
|
|
303
302
|
formatted_results = []
|
|
304
303
|
for i, result in enumerate(results):
|
|
305
304
|
invocation = result["invocation"]
|
|
306
305
|
tool_name = invocation.get("tool_name", "unknown")
|
|
307
|
-
formatted_results.append(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
306
|
+
formatted_results.append(
|
|
307
|
+
{
|
|
308
|
+
"tool": tool_name,
|
|
309
|
+
"result": result["result"],
|
|
310
|
+
"index": i + cursor_offset,
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
|
|
313
314
|
# Create paginated response with token awareness
|
|
314
315
|
paginated_response = create_paginated_response(
|
|
315
|
-
formatted_results,
|
|
316
|
-
cursor=cursor,
|
|
317
|
-
use_token_limit=True
|
|
316
|
+
formatted_results, cursor=cursor, use_token_limit=True
|
|
318
317
|
)
|
|
319
|
-
|
|
318
|
+
|
|
320
319
|
# Convert paginated response to string format for MCP
|
|
321
320
|
if isinstance(paginated_response, dict) and "items" in paginated_response:
|
|
322
321
|
# Format the items as a readable string
|
|
323
322
|
result_parts = []
|
|
324
|
-
|
|
323
|
+
|
|
325
324
|
# Add header
|
|
326
325
|
result_parts.append(f"=== Batch operation: {description} ===")
|
|
327
326
|
result_parts.append(f"Total invocations: {len(invocations)}")
|
|
328
|
-
result_parts.append(
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
result_parts.append(
|
|
328
|
+
f"Showing results: {len(paginated_response['items'])} of {len(results)}"
|
|
329
|
+
)
|
|
330
|
+
if paginated_response.get("hasMore"):
|
|
331
|
+
result_parts.append(
|
|
332
|
+
f"More results available - use cursor: {paginated_response.get('nextCursor')}"
|
|
333
|
+
)
|
|
331
334
|
result_parts.append("")
|
|
332
|
-
|
|
335
|
+
|
|
333
336
|
# Format each result
|
|
334
|
-
for item in paginated_response[
|
|
337
|
+
for item in paginated_response["items"]:
|
|
335
338
|
result_parts.append(f"### Result {item['index'] + 1}: {item['tool']}")
|
|
336
|
-
result_content = item[
|
|
337
|
-
|
|
339
|
+
result_content = item["result"]
|
|
340
|
+
|
|
338
341
|
# Add the result content - use multi-line code blocks for code outputs
|
|
339
342
|
if isinstance(result_content, str) and "\n" in result_content:
|
|
340
343
|
result_parts.append(f"```\n{result_content}\n```")
|
|
341
344
|
else:
|
|
342
345
|
result_parts.append(str(result_content))
|
|
343
346
|
result_parts.append("")
|
|
344
|
-
|
|
347
|
+
|
|
345
348
|
# Join all parts
|
|
346
349
|
formatted_output = "\n".join(result_parts)
|
|
347
|
-
|
|
350
|
+
|
|
348
351
|
# If there's a next cursor, we need to preserve it in the response
|
|
349
352
|
# For now, append it as a note at the end
|
|
350
|
-
if paginated_response.get(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
+
if paginated_response.get("hasMore") and paginated_response.get(
|
|
354
|
+
"nextCursor"
|
|
355
|
+
):
|
|
356
|
+
formatted_output += (
|
|
357
|
+
f"\n\n[To continue, use cursor: {paginated_response['nextCursor']}]"
|
|
358
|
+
)
|
|
359
|
+
|
|
353
360
|
await tool_ctx.info(
|
|
354
361
|
f"Batch operation '{description}' completed with {len(paginated_response['items'])} results"
|
|
355
362
|
f"{' (more available)' if paginated_response.get('hasMore') else ''}"
|
|
356
363
|
)
|
|
357
|
-
|
|
364
|
+
|
|
358
365
|
return formatted_output
|
|
359
366
|
else:
|
|
360
367
|
# Fallback if pagination didn't work as expected
|
|
@@ -376,16 +383,16 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
376
383
|
|
|
377
384
|
# Add the result header
|
|
378
385
|
formatted_parts.append(f"### Result {i + 1}: {tool_name}")
|
|
379
|
-
|
|
386
|
+
|
|
380
387
|
# Truncate individual results if they're too large
|
|
381
388
|
result_content = result["result"]
|
|
382
389
|
if len(result_content) > 50000: # If individual result > 50k chars
|
|
383
390
|
result_content = truncate_response(
|
|
384
391
|
result_content,
|
|
385
392
|
max_tokens=5000, # Limit individual results to ~5k tokens
|
|
386
|
-
truncation_message=f"\n\n[Result from {tool_name} truncated. Use the tool directly with pagination/filtering for full output.]"
|
|
393
|
+
truncation_message=f"\n\n[Result from {tool_name} truncated. Use the tool directly with pagination/filtering for full output.]",
|
|
387
394
|
)
|
|
388
|
-
|
|
395
|
+
|
|
389
396
|
# Add the result content - use multi-line code blocks for code outputs
|
|
390
397
|
if "\n" in result_content:
|
|
391
398
|
formatted_parts.append(f"```\n{result_content}\n```")
|
|
@@ -413,7 +420,7 @@ Not available: think,write,edit,multi_edit,notebook_edit
|
|
|
413
420
|
description: Description,
|
|
414
421
|
invocations: Invocations,
|
|
415
422
|
cursor: Cursor,
|
|
416
|
-
ctx: MCPContext
|
|
423
|
+
ctx: MCPContext,
|
|
417
424
|
) -> str:
|
|
418
425
|
return await tool_self.call(
|
|
419
426
|
ctx, description=description, invocations=invocations, cursor=cursor
|
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
"""Configuration management tool for dynamic settings updates."""
|
|
2
2
|
|
|
3
|
-
from typing import Dict, List, Optional, TypedDict, Unpack, Any, final
|
|
4
3
|
import json
|
|
5
|
-
from
|
|
4
|
+
from typing import Any, Dict, Unpack, Optional, TypedDict, final
|
|
6
5
|
|
|
7
6
|
from mcp.server.fastmcp import Context as MCPContext
|
|
8
7
|
|
|
9
|
-
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
|
-
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
11
8
|
from hanzo_mcp.config.settings import (
|
|
12
|
-
|
|
13
|
-
MCPServerConfig,
|
|
14
|
-
|
|
15
|
-
load_settings,
|
|
9
|
+
ProjectConfig,
|
|
10
|
+
MCPServerConfig,
|
|
11
|
+
load_settings,
|
|
16
12
|
save_settings,
|
|
17
13
|
detect_project_from_path,
|
|
18
|
-
ensure_project_hanzo_dir
|
|
19
14
|
)
|
|
15
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
16
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class ConfigToolParams(TypedDict, total=False):
|
|
23
20
|
"""Parameters for configuration management operations."""
|
|
24
|
-
|
|
21
|
+
|
|
25
22
|
action: str # get, set, add_server, remove_server, add_project, etc.
|
|
26
23
|
scope: Optional[str] # global, project, current
|
|
27
|
-
setting_path: Optional[str] # dot-notation path like "agent.enabled"
|
|
24
|
+
setting_path: Optional[str] # dot-notation path like "agent.enabled"
|
|
28
25
|
value: Optional[Any] # new value to set
|
|
29
26
|
server_name: Optional[str] # for MCP server operations
|
|
30
27
|
server_config: Optional[Dict[str, Any]] # for adding servers
|
|
@@ -35,20 +32,20 @@ class ConfigToolParams(TypedDict, total=False):
|
|
|
35
32
|
@final
|
|
36
33
|
class ConfigTool(BaseTool):
|
|
37
34
|
"""Tool for managing Hanzo AI configuration dynamically."""
|
|
38
|
-
|
|
35
|
+
|
|
39
36
|
def __init__(self, permission_manager: PermissionManager):
|
|
40
37
|
"""Initialize the configuration tool.
|
|
41
|
-
|
|
38
|
+
|
|
42
39
|
Args:
|
|
43
40
|
permission_manager: Permission manager for access control
|
|
44
41
|
"""
|
|
45
42
|
self.permission_manager = permission_manager
|
|
46
|
-
|
|
43
|
+
|
|
47
44
|
@property
|
|
48
45
|
def name(self) -> str:
|
|
49
46
|
"""Get the tool name."""
|
|
50
47
|
return "config"
|
|
51
|
-
|
|
48
|
+
|
|
52
49
|
@property
|
|
53
50
|
def description(self) -> str:
|
|
54
51
|
"""Get the tool description."""
|
|
@@ -64,18 +61,18 @@ Perfect for AI-driven configuration where users can say things like:
|
|
|
64
61
|
- "Show me the current project configuration"
|
|
65
62
|
|
|
66
63
|
Automatically detects projects based on LLM.md files and manages .hanzo/ directories."""
|
|
67
|
-
|
|
64
|
+
|
|
68
65
|
async def call(
|
|
69
66
|
self,
|
|
70
67
|
ctx: MCPContext,
|
|
71
68
|
**params: Unpack[ConfigToolParams],
|
|
72
69
|
) -> str:
|
|
73
70
|
"""Manage configuration settings.
|
|
74
|
-
|
|
71
|
+
|
|
75
72
|
Args:
|
|
76
73
|
ctx: MCP context
|
|
77
74
|
**params: Tool parameters
|
|
78
|
-
|
|
75
|
+
|
|
79
76
|
Returns:
|
|
80
77
|
Configuration operation result
|
|
81
78
|
"""
|
|
@@ -87,14 +84,20 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
87
84
|
server_config = params.get("server_config")
|
|
88
85
|
project_name = params.get("project_name")
|
|
89
86
|
project_path = params.get("project_path")
|
|
90
|
-
|
|
87
|
+
|
|
91
88
|
try:
|
|
92
89
|
if action == "get":
|
|
93
|
-
return await self._get_config(
|
|
90
|
+
return await self._get_config(
|
|
91
|
+
scope, setting_path, project_name, project_path
|
|
92
|
+
)
|
|
94
93
|
elif action == "set":
|
|
95
|
-
return await self._set_config(
|
|
94
|
+
return await self._set_config(
|
|
95
|
+
scope, setting_path, value, project_name, project_path
|
|
96
|
+
)
|
|
96
97
|
elif action == "add_server":
|
|
97
|
-
return await self._add_mcp_server(
|
|
98
|
+
return await self._add_mcp_server(
|
|
99
|
+
server_name, server_config, scope, project_name
|
|
100
|
+
)
|
|
98
101
|
elif action == "remove_server":
|
|
99
102
|
return await self._remove_mcp_server(server_name, scope, project_name)
|
|
100
103
|
elif action == "enable_server":
|
|
@@ -115,11 +118,17 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
115
118
|
return await self._detect_project(project_path)
|
|
116
119
|
else:
|
|
117
120
|
return f"Error: Unknown action '{action}'. Available actions: get, set, add_server, remove_server, enable_server, disable_server, trust_server, add_project, set_current_project, list_servers, list_projects, detect_project"
|
|
118
|
-
|
|
121
|
+
|
|
119
122
|
except Exception as e:
|
|
120
123
|
return f"Error managing configuration: {str(e)}"
|
|
121
|
-
|
|
122
|
-
async def _get_config(
|
|
124
|
+
|
|
125
|
+
async def _get_config(
|
|
126
|
+
self,
|
|
127
|
+
scope: str,
|
|
128
|
+
setting_path: Optional[str],
|
|
129
|
+
project_name: Optional[str],
|
|
130
|
+
project_path: Optional[str],
|
|
131
|
+
) -> str:
|
|
123
132
|
"""Get configuration value(s)."""
|
|
124
133
|
# Load appropriate settings
|
|
125
134
|
if scope == "project" or project_name or project_path:
|
|
@@ -133,14 +142,14 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
133
142
|
settings = load_settings()
|
|
134
143
|
else:
|
|
135
144
|
settings = load_settings()
|
|
136
|
-
|
|
145
|
+
|
|
137
146
|
if not setting_path:
|
|
138
147
|
# Return full config summary
|
|
139
148
|
if scope == "project" and settings.current_project:
|
|
140
149
|
project = settings.get_current_project()
|
|
141
150
|
if project:
|
|
142
151
|
return f"Current Project Configuration ({project.name}):\\n{json.dumps(project.__dict__, indent=2)}"
|
|
143
|
-
|
|
152
|
+
|
|
144
153
|
# Return global config summary
|
|
145
154
|
summary = {
|
|
146
155
|
"server": settings.server.__dict__,
|
|
@@ -149,24 +158,34 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
149
158
|
"agent": settings.agent.__dict__,
|
|
150
159
|
"vector_store": settings.vector_store.__dict__,
|
|
151
160
|
"hub_enabled": settings.hub_enabled,
|
|
152
|
-
"mcp_servers": {
|
|
161
|
+
"mcp_servers": {
|
|
162
|
+
name: server.__dict__
|
|
163
|
+
for name, server in settings.mcp_servers.items()
|
|
164
|
+
},
|
|
153
165
|
"current_project": settings.current_project,
|
|
154
166
|
"projects": list(settings.projects.keys()),
|
|
155
167
|
}
|
|
156
168
|
return f"Configuration Summary:\\n{json.dumps(summary, indent=2)}"
|
|
157
|
-
|
|
169
|
+
|
|
158
170
|
# Get specific setting
|
|
159
171
|
value = self._get_nested_value(settings.__dict__, setting_path)
|
|
160
172
|
if value is not None:
|
|
161
173
|
return f"{setting_path}: {json.dumps(value, indent=2)}"
|
|
162
174
|
else:
|
|
163
175
|
return f"Setting '{setting_path}' not found"
|
|
164
|
-
|
|
165
|
-
async def _set_config(
|
|
176
|
+
|
|
177
|
+
async def _set_config(
|
|
178
|
+
self,
|
|
179
|
+
scope: str,
|
|
180
|
+
setting_path: Optional[str],
|
|
181
|
+
value: Any,
|
|
182
|
+
project_name: Optional[str],
|
|
183
|
+
project_path: Optional[str],
|
|
184
|
+
) -> str:
|
|
166
185
|
"""Set configuration value."""
|
|
167
186
|
if not setting_path:
|
|
168
187
|
return "Error: setting_path is required for set action"
|
|
169
|
-
|
|
188
|
+
|
|
170
189
|
# Load settings
|
|
171
190
|
project_dir = None
|
|
172
191
|
if scope == "project" or project_name or project_path:
|
|
@@ -176,9 +195,9 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
176
195
|
project_dir = project_info["root_path"]
|
|
177
196
|
else:
|
|
178
197
|
return f"No project detected at path: {project_path}"
|
|
179
|
-
|
|
198
|
+
|
|
180
199
|
settings = load_settings(project_dir)
|
|
181
|
-
|
|
200
|
+
|
|
182
201
|
# Set the value
|
|
183
202
|
if self._set_nested_value(settings.__dict__, setting_path, value):
|
|
184
203
|
# Save settings
|
|
@@ -189,14 +208,20 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
189
208
|
return f"Successfully set {setting_path} = {json.dumps(value)}"
|
|
190
209
|
else:
|
|
191
210
|
return f"Error: Could not set '{setting_path}'"
|
|
192
|
-
|
|
193
|
-
async def _add_mcp_server(
|
|
211
|
+
|
|
212
|
+
async def _add_mcp_server(
|
|
213
|
+
self,
|
|
214
|
+
server_name: Optional[str],
|
|
215
|
+
server_config: Optional[Dict[str, Any]],
|
|
216
|
+
scope: str,
|
|
217
|
+
project_name: Optional[str],
|
|
218
|
+
) -> str:
|
|
194
219
|
"""Add a new MCP server."""
|
|
195
220
|
if not server_name or not server_config:
|
|
196
221
|
return "Error: server_name and server_config are required"
|
|
197
|
-
|
|
222
|
+
|
|
198
223
|
settings = load_settings()
|
|
199
|
-
|
|
224
|
+
|
|
200
225
|
# Create server config
|
|
201
226
|
mcp_server = MCPServerConfig(
|
|
202
227
|
name=server_name,
|
|
@@ -207,95 +232,105 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
207
232
|
description=server_config.get("description", ""),
|
|
208
233
|
capabilities=server_config.get("capabilities", []),
|
|
209
234
|
)
|
|
210
|
-
|
|
235
|
+
|
|
211
236
|
if settings.add_mcp_server(mcp_server):
|
|
212
237
|
save_settings(settings)
|
|
213
238
|
return f"Successfully added MCP server '{server_name}'"
|
|
214
239
|
else:
|
|
215
240
|
return f"Error: MCP server '{server_name}' already exists"
|
|
216
|
-
|
|
217
|
-
async def _remove_mcp_server(
|
|
241
|
+
|
|
242
|
+
async def _remove_mcp_server(
|
|
243
|
+
self, server_name: Optional[str], scope: str, project_name: Optional[str]
|
|
244
|
+
) -> str:
|
|
218
245
|
"""Remove an MCP server."""
|
|
219
246
|
if not server_name:
|
|
220
247
|
return "Error: server_name is required"
|
|
221
|
-
|
|
248
|
+
|
|
222
249
|
settings = load_settings()
|
|
223
|
-
|
|
250
|
+
|
|
224
251
|
if settings.remove_mcp_server(server_name):
|
|
225
252
|
save_settings(settings)
|
|
226
253
|
return f"Successfully removed MCP server '{server_name}'"
|
|
227
254
|
else:
|
|
228
255
|
return f"Error: MCP server '{server_name}' not found"
|
|
229
|
-
|
|
230
|
-
async def _enable_mcp_server(
|
|
256
|
+
|
|
257
|
+
async def _enable_mcp_server(
|
|
258
|
+
self, server_name: Optional[str], scope: str, project_name: Optional[str]
|
|
259
|
+
) -> str:
|
|
231
260
|
"""Enable an MCP server."""
|
|
232
261
|
if not server_name:
|
|
233
262
|
return "Error: server_name is required"
|
|
234
|
-
|
|
263
|
+
|
|
235
264
|
settings = load_settings()
|
|
236
|
-
|
|
265
|
+
|
|
237
266
|
if settings.enable_mcp_server(server_name):
|
|
238
267
|
save_settings(settings)
|
|
239
268
|
return f"Successfully enabled MCP server '{server_name}'"
|
|
240
269
|
else:
|
|
241
270
|
return f"Error: MCP server '{server_name}' not found"
|
|
242
|
-
|
|
243
|
-
async def _disable_mcp_server(
|
|
271
|
+
|
|
272
|
+
async def _disable_mcp_server(
|
|
273
|
+
self, server_name: Optional[str], scope: str, project_name: Optional[str]
|
|
274
|
+
) -> str:
|
|
244
275
|
"""Disable an MCP server."""
|
|
245
276
|
if not server_name:
|
|
246
277
|
return "Error: server_name is required"
|
|
247
|
-
|
|
278
|
+
|
|
248
279
|
settings = load_settings()
|
|
249
|
-
|
|
280
|
+
|
|
250
281
|
if settings.disable_mcp_server(server_name):
|
|
251
282
|
save_settings(settings)
|
|
252
283
|
return f"Successfully disabled MCP server '{server_name}'"
|
|
253
284
|
else:
|
|
254
285
|
return f"Error: MCP server '{server_name}' not found"
|
|
255
|
-
|
|
286
|
+
|
|
256
287
|
async def _trust_mcp_server(self, server_name: Optional[str]) -> str:
|
|
257
288
|
"""Trust an MCP server."""
|
|
258
289
|
if not server_name:
|
|
259
290
|
return "Error: server_name is required"
|
|
260
|
-
|
|
291
|
+
|
|
261
292
|
settings = load_settings()
|
|
262
|
-
|
|
293
|
+
|
|
263
294
|
if settings.trust_mcp_server(server_name):
|
|
264
295
|
save_settings(settings)
|
|
265
296
|
return f"Successfully trusted MCP server '{server_name}'"
|
|
266
297
|
else:
|
|
267
298
|
return f"Error: MCP server '{server_name}' not found"
|
|
268
|
-
|
|
269
|
-
async def _add_project(
|
|
299
|
+
|
|
300
|
+
async def _add_project(
|
|
301
|
+
self, project_name: Optional[str], project_path: Optional[str]
|
|
302
|
+
) -> str:
|
|
270
303
|
"""Add a project configuration."""
|
|
271
304
|
if not project_path:
|
|
272
305
|
return "Error: project_path is required"
|
|
273
|
-
|
|
306
|
+
|
|
274
307
|
# Detect or create project
|
|
275
308
|
project_info = detect_project_from_path(project_path)
|
|
276
309
|
if not project_info:
|
|
277
310
|
return f"No LLM.md found in project path: {project_path}"
|
|
278
|
-
|
|
311
|
+
|
|
279
312
|
if not project_name:
|
|
280
313
|
project_name = project_info["name"]
|
|
281
|
-
|
|
314
|
+
|
|
282
315
|
settings = load_settings()
|
|
283
|
-
|
|
316
|
+
|
|
284
317
|
project_config = ProjectConfig(
|
|
285
318
|
name=project_name,
|
|
286
319
|
root_path=project_info["root_path"],
|
|
287
320
|
)
|
|
288
|
-
|
|
321
|
+
|
|
289
322
|
if settings.add_project(project_config):
|
|
290
323
|
save_settings(settings)
|
|
291
324
|
return f"Successfully added project '{project_name}' at {project_info['root_path']}"
|
|
292
325
|
else:
|
|
293
326
|
return f"Error: Project '{project_name}' already exists"
|
|
294
|
-
|
|
295
|
-
async def _set_current_project(
|
|
327
|
+
|
|
328
|
+
async def _set_current_project(
|
|
329
|
+
self, project_name: Optional[str], project_path: Optional[str]
|
|
330
|
+
) -> str:
|
|
296
331
|
"""Set the current active project."""
|
|
297
332
|
settings = load_settings()
|
|
298
|
-
|
|
333
|
+
|
|
299
334
|
if project_path:
|
|
300
335
|
project_info = detect_project_from_path(project_path)
|
|
301
336
|
if project_info:
|
|
@@ -304,62 +339,63 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
304
339
|
if project_name not in settings.projects:
|
|
305
340
|
await self._add_project(project_name, project_path)
|
|
306
341
|
settings = load_settings() # Reload after adding
|
|
307
|
-
|
|
342
|
+
|
|
308
343
|
if not project_name:
|
|
309
344
|
return "Error: project_name or project_path is required"
|
|
310
|
-
|
|
345
|
+
|
|
311
346
|
if settings.set_current_project(project_name):
|
|
312
347
|
save_settings(settings)
|
|
313
348
|
return f"Successfully set current project to '{project_name}'"
|
|
314
349
|
else:
|
|
315
350
|
return f"Error: Project '{project_name}' not found"
|
|
316
|
-
|
|
351
|
+
|
|
317
352
|
async def _list_mcp_servers(self, scope: str, project_name: Optional[str]) -> str:
|
|
318
353
|
"""List MCP servers."""
|
|
319
354
|
settings = load_settings()
|
|
320
|
-
|
|
355
|
+
|
|
321
356
|
if not settings.mcp_servers:
|
|
322
357
|
return "No MCP servers configured"
|
|
323
|
-
|
|
358
|
+
|
|
324
359
|
servers_info = []
|
|
325
360
|
for name, server in settings.mcp_servers.items():
|
|
326
361
|
status = "enabled" if server.enabled else "disabled"
|
|
327
362
|
trust = "trusted" if server.trusted else "untrusted"
|
|
328
363
|
servers_info.append(f"- {name}: {server.command} ({status}, {trust})")
|
|
329
|
-
|
|
364
|
+
|
|
330
365
|
return f"MCP Servers:\\n" + "\\n".join(servers_info)
|
|
331
|
-
|
|
366
|
+
|
|
332
367
|
async def _list_projects(self) -> str:
|
|
333
368
|
"""List projects."""
|
|
334
369
|
settings = load_settings()
|
|
335
|
-
|
|
370
|
+
|
|
336
371
|
if not settings.projects:
|
|
337
372
|
return "No projects configured"
|
|
338
|
-
|
|
373
|
+
|
|
339
374
|
projects_info = []
|
|
340
375
|
for name, project in settings.projects.items():
|
|
341
376
|
current = " (current)" if name == settings.current_project else ""
|
|
342
377
|
projects_info.append(f"- {name}: {project.root_path}{current}")
|
|
343
|
-
|
|
378
|
+
|
|
344
379
|
return f"Projects:\\n" + "\\n".join(projects_info)
|
|
345
|
-
|
|
380
|
+
|
|
346
381
|
async def _detect_project(self, project_path: Optional[str]) -> str:
|
|
347
382
|
"""Detect project from path."""
|
|
348
383
|
if not project_path:
|
|
349
384
|
import os
|
|
385
|
+
|
|
350
386
|
project_path = os.getcwd()
|
|
351
|
-
|
|
387
|
+
|
|
352
388
|
project_info = detect_project_from_path(project_path)
|
|
353
389
|
if project_info:
|
|
354
390
|
return f"Project detected:\\n{json.dumps(project_info, indent=2)}"
|
|
355
391
|
else:
|
|
356
392
|
return f"No project detected at path: {project_path}"
|
|
357
|
-
|
|
393
|
+
|
|
358
394
|
def _get_nested_value(self, obj: Dict[str, Any], path: str) -> Any:
|
|
359
395
|
"""Get nested value using dot notation."""
|
|
360
396
|
keys = path.split(".")
|
|
361
397
|
current = obj
|
|
362
|
-
|
|
398
|
+
|
|
363
399
|
for key in keys:
|
|
364
400
|
if isinstance(current, dict) and key in current:
|
|
365
401
|
current = current[key]
|
|
@@ -367,14 +403,14 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
367
403
|
current = getattr(current, key)
|
|
368
404
|
else:
|
|
369
405
|
return None
|
|
370
|
-
|
|
406
|
+
|
|
371
407
|
return current
|
|
372
|
-
|
|
408
|
+
|
|
373
409
|
def _set_nested_value(self, obj: Dict[str, Any], path: str, value: Any) -> bool:
|
|
374
410
|
"""Set nested value using dot notation."""
|
|
375
411
|
keys = path.split(".")
|
|
376
412
|
current = obj
|
|
377
|
-
|
|
413
|
+
|
|
378
414
|
# Navigate to parent
|
|
379
415
|
for key in keys[:-1]:
|
|
380
416
|
if isinstance(current, dict) and key in current:
|
|
@@ -383,7 +419,7 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
383
419
|
current = getattr(current, key)
|
|
384
420
|
else:
|
|
385
421
|
return False
|
|
386
|
-
|
|
422
|
+
|
|
387
423
|
# Set final value
|
|
388
424
|
final_key = keys[-1]
|
|
389
425
|
if isinstance(current, dict):
|
|
@@ -392,5 +428,5 @@ Automatically detects projects based on LLM.md files and manages .hanzo/ directo
|
|
|
392
428
|
elif hasattr(current, final_key):
|
|
393
429
|
setattr(current, final_key, value)
|
|
394
430
|
return True
|
|
395
|
-
|
|
396
|
-
return False
|
|
431
|
+
|
|
432
|
+
return False
|
|
@@ -4,12 +4,8 @@ This module provides an enhanced Context class that wraps the MCP Context
|
|
|
4
4
|
and adds additional functionality specific to Hanzo tools.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import os
|
|
9
|
-
import time
|
|
7
|
+
from typing import ClassVar, final
|
|
10
8
|
from collections.abc import Iterable
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, ClassVar, final
|
|
13
9
|
|
|
14
10
|
from mcp.server.fastmcp import Context as MCPContext
|
|
15
11
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
@@ -9,18 +9,20 @@ DEPRECATED: Use hanzo_mcp.tools.common.decorators directly.
|
|
|
9
9
|
# Re-export for backward compatibility
|
|
10
10
|
from hanzo_mcp.tools.common.decorators import (
|
|
11
11
|
MockContext,
|
|
12
|
-
with_context_normalization,
|
|
13
12
|
_is_valid_context as is_valid_context,
|
|
13
|
+
with_context_normalization,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
# Backward compatibility function
|
|
17
18
|
def normalize_context(ctx):
|
|
18
19
|
"""Normalize context - backward compatibility wrapper.
|
|
19
|
-
|
|
20
|
+
|
|
20
21
|
DEPRECATED: Use decorators.with_context_normalization instead.
|
|
21
22
|
"""
|
|
22
23
|
if is_valid_context(ctx):
|
|
23
24
|
return ctx
|
|
24
25
|
return MockContext()
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
__all__ = ["MockContext", "normalize_context", "with_context_normalization"]
|