minion-code 0.1.0__py3-none-any.whl → 0.1.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.
- examples/cli_entrypoint.py +60 -0
- examples/{agent_with_todos.py → components/agent_with_todos.py} +58 -47
- examples/{message_response_children_demo.py → components/message_response_children_demo.py} +61 -55
- examples/components/messages_component.py +199 -0
- examples/file_freshness_example.py +22 -22
- examples/file_watching_example.py +32 -26
- examples/interruptible_tui.py +921 -3
- examples/repl_tui.py +129 -0
- examples/skills/example_usage.py +57 -0
- examples/start.py +173 -0
- minion_code/__init__.py +1 -1
- minion_code/acp_server/__init__.py +34 -0
- minion_code/acp_server/agent.py +539 -0
- minion_code/acp_server/hooks.py +354 -0
- minion_code/acp_server/main.py +194 -0
- minion_code/acp_server/permissions.py +142 -0
- minion_code/acp_server/test_client.py +104 -0
- minion_code/adapters/__init__.py +22 -0
- minion_code/adapters/output_adapter.py +207 -0
- minion_code/adapters/rich_adapter.py +169 -0
- minion_code/adapters/textual_adapter.py +254 -0
- minion_code/agents/__init__.py +2 -2
- minion_code/agents/code_agent.py +517 -104
- minion_code/agents/hooks.py +378 -0
- minion_code/cli.py +538 -429
- minion_code/cli_simple.py +665 -0
- minion_code/commands/__init__.py +136 -29
- minion_code/commands/clear_command.py +19 -46
- minion_code/commands/help_command.py +33 -49
- minion_code/commands/history_command.py +37 -55
- minion_code/commands/model_command.py +194 -0
- minion_code/commands/quit_command.py +9 -12
- minion_code/commands/resume_command.py +181 -0
- minion_code/commands/skill_command.py +89 -0
- minion_code/commands/status_command.py +48 -73
- minion_code/commands/tools_command.py +54 -52
- minion_code/commands/version_command.py +34 -69
- minion_code/components/ConfirmDialog.py +430 -0
- minion_code/components/Message.py +318 -97
- minion_code/components/MessageResponse.py +30 -29
- minion_code/components/Messages.py +351 -0
- minion_code/components/PromptInput.py +499 -245
- minion_code/components/__init__.py +24 -17
- minion_code/const.py +7 -0
- minion_code/screens/REPL.py +1453 -469
- minion_code/screens/__init__.py +1 -1
- minion_code/services/__init__.py +20 -20
- minion_code/services/event_system.py +19 -14
- minion_code/services/file_freshness_service.py +223 -170
- minion_code/skills/__init__.py +25 -0
- minion_code/skills/skill.py +128 -0
- minion_code/skills/skill_loader.py +198 -0
- minion_code/skills/skill_registry.py +177 -0
- minion_code/subagents/__init__.py +31 -0
- minion_code/subagents/builtin/__init__.py +30 -0
- minion_code/subagents/builtin/claude_code_guide.py +32 -0
- minion_code/subagents/builtin/explore.py +36 -0
- minion_code/subagents/builtin/general_purpose.py +19 -0
- minion_code/subagents/builtin/plan.py +61 -0
- minion_code/subagents/subagent.py +116 -0
- minion_code/subagents/subagent_loader.py +147 -0
- minion_code/subagents/subagent_registry.py +151 -0
- minion_code/tools/__init__.py +8 -2
- minion_code/tools/bash_tool.py +16 -3
- minion_code/tools/file_edit_tool.py +201 -104
- minion_code/tools/file_read_tool.py +183 -26
- minion_code/tools/file_write_tool.py +17 -3
- minion_code/tools/glob_tool.py +23 -2
- minion_code/tools/grep_tool.py +229 -21
- minion_code/tools/ls_tool.py +28 -3
- minion_code/tools/multi_edit_tool.py +89 -84
- minion_code/tools/python_interpreter_tool.py +9 -1
- minion_code/tools/skill_tool.py +210 -0
- minion_code/tools/task_tool.py +287 -0
- minion_code/tools/todo_read_tool.py +28 -24
- minion_code/tools/todo_write_tool.py +82 -65
- minion_code/{types.py → type_defs.py} +15 -2
- minion_code/utils/__init__.py +45 -17
- minion_code/utils/config.py +610 -0
- minion_code/utils/history.py +114 -0
- minion_code/utils/logs.py +53 -0
- minion_code/utils/mcp_loader.py +153 -55
- minion_code/utils/output_truncator.py +233 -0
- minion_code/utils/session_storage.py +369 -0
- minion_code/utils/todo_file_utils.py +26 -22
- minion_code/utils/todo_storage.py +43 -33
- minion_code/web/__init__.py +9 -0
- minion_code/web/adapters/__init__.py +5 -0
- minion_code/web/adapters/web_adapter.py +524 -0
- minion_code/web/api/__init__.py +7 -0
- minion_code/web/api/chat.py +277 -0
- minion_code/web/api/interactions.py +136 -0
- minion_code/web/api/sessions.py +135 -0
- minion_code/web/server.py +149 -0
- minion_code/web/services/__init__.py +5 -0
- minion_code/web/services/session_manager.py +420 -0
- minion_code-0.1.2.dist-info/METADATA +476 -0
- minion_code-0.1.2.dist-info/RECORD +111 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/WHEEL +1 -1
- minion_code-0.1.2.dist-info/entry_points.txt +6 -0
- tests/test_adapter.py +67 -0
- tests/test_adapter_simple.py +79 -0
- tests/test_file_read_tool.py +144 -0
- tests/test_readonly_tools.py +0 -2
- tests/test_skills.py +441 -0
- examples/advance_tui.py +0 -508
- examples/rich_example.py +0 -4
- examples/simple_file_watching.py +0 -57
- examples/simple_tui.py +0 -267
- examples/simple_usage.py +0 -69
- minion_code-0.1.0.dist-info/METADATA +0 -350
- minion_code-0.1.0.dist-info/RECORD +0 -59
- minion_code-0.1.0.dist-info/entry_points.txt +0 -4
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""History management utilities for minion-code.
|
|
2
|
+
|
|
3
|
+
This module provides command history functionality similar to the TypeScript
|
|
4
|
+
history.ts file, adapted for Python and the minion-code project structure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from .config import get_current_project_config, save_current_project_config
|
|
9
|
+
|
|
10
|
+
MAX_HISTORY_ITEMS = 100
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_history() -> List[str]:
|
|
14
|
+
"""Get command history for the current project.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
List of command history strings, with most recent first.
|
|
18
|
+
"""
|
|
19
|
+
project_config = get_current_project_config()
|
|
20
|
+
return project_config.history or []
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def add_to_history(command: str) -> None:
|
|
24
|
+
"""Add a command to the history.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
command: The command string to add to history.
|
|
28
|
+
|
|
29
|
+
Note:
|
|
30
|
+
- Commands are added to the beginning of the history list
|
|
31
|
+
- Duplicate consecutive commands are not added
|
|
32
|
+
- History is limited to MAX_HISTORY_ITEMS entries
|
|
33
|
+
"""
|
|
34
|
+
if not command or not command.strip():
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
command = command.strip()
|
|
38
|
+
project_config = get_current_project_config()
|
|
39
|
+
history = project_config.history or []
|
|
40
|
+
|
|
41
|
+
# Don't add if it's the same as the most recent command
|
|
42
|
+
if history and history[0] == command:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# Add to beginning and limit to MAX_HISTORY_ITEMS
|
|
46
|
+
history.insert(0, command)
|
|
47
|
+
project_config.history = history[:MAX_HISTORY_ITEMS]
|
|
48
|
+
|
|
49
|
+
save_current_project_config(project_config)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def clear_history() -> None:
|
|
53
|
+
"""Clear all command history for the current project."""
|
|
54
|
+
project_config = get_current_project_config()
|
|
55
|
+
project_config.history = []
|
|
56
|
+
save_current_project_config(project_config)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def remove_from_history(command: str) -> bool:
|
|
60
|
+
"""Remove a specific command from history.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
command: The command string to remove from history.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if the command was found and removed, False otherwise.
|
|
67
|
+
"""
|
|
68
|
+
if not command or not command.strip():
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
command = command.strip()
|
|
72
|
+
project_config = get_current_project_config()
|
|
73
|
+
history = project_config.history or []
|
|
74
|
+
|
|
75
|
+
if command in history:
|
|
76
|
+
history.remove(command)
|
|
77
|
+
project_config.history = history
|
|
78
|
+
save_current_project_config(project_config)
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_history_item(index: int) -> str:
|
|
85
|
+
"""Get a specific history item by index.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
index: The index of the history item (0 is most recent).
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The command string at the specified index, or empty string if not found.
|
|
92
|
+
"""
|
|
93
|
+
history = get_history()
|
|
94
|
+
if 0 <= index < len(history):
|
|
95
|
+
return history[index]
|
|
96
|
+
return ""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def search_history(query: str) -> List[str]:
|
|
100
|
+
"""Search history for commands containing the query string.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
query: The search query string.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of matching commands, with most recent first.
|
|
107
|
+
"""
|
|
108
|
+
if not query or not query.strip():
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
query = query.strip().lower()
|
|
112
|
+
history = get_history()
|
|
113
|
+
|
|
114
|
+
return [cmd for cmd in history if query in cmd.lower()]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from loguru import logger as _logger
|
|
9
|
+
from minion.const import MINION_ROOT
|
|
10
|
+
|
|
11
|
+
_print_level = "INFO"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def define_log_level(print_level="INFO", logfile_level="DEBUG", name: str = None):
|
|
15
|
+
"""Adjust the log level to above level"""
|
|
16
|
+
global _print_level
|
|
17
|
+
_print_level = print_level
|
|
18
|
+
|
|
19
|
+
current_date = datetime.now()
|
|
20
|
+
formatted_date = current_date.strftime("%Y%m%d")
|
|
21
|
+
log_name = (
|
|
22
|
+
f"{name}_{formatted_date}" if name else formatted_date
|
|
23
|
+
) # name a log with prefix name
|
|
24
|
+
|
|
25
|
+
_logger.remove()
|
|
26
|
+
_logger.add(sys.stdout, level=print_level)
|
|
27
|
+
_logger.add(MINION_ROOT / f"logs/{log_name}.txt", level=logfile_level)
|
|
28
|
+
return _logger
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_tui_logging():
|
|
32
|
+
"""Setup logging for TUI mode - removes console output to prevent UI interference"""
|
|
33
|
+
global _print_level
|
|
34
|
+
|
|
35
|
+
# Remove all existing handlers
|
|
36
|
+
_logger.remove()
|
|
37
|
+
|
|
38
|
+
# Only add file logging for TUI mode
|
|
39
|
+
current_date = datetime.now()
|
|
40
|
+
formatted_date = current_date.strftime("%Y%m%d")
|
|
41
|
+
log_name = f"{formatted_date}"
|
|
42
|
+
|
|
43
|
+
# Ensure logs directory exists
|
|
44
|
+
logs_dir = MINION_ROOT / "logs"
|
|
45
|
+
logs_dir.mkdir(exist_ok=True)
|
|
46
|
+
|
|
47
|
+
_logger.add(logs_dir / f"{log_name}.txt", level="DEBUG")
|
|
48
|
+
_print_level = "DEBUG" # Set to DEBUG for file logging
|
|
49
|
+
|
|
50
|
+
return _logger
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
logger = define_log_level()
|
minion_code/utils/mcp_loader.py
CHANGED
|
@@ -11,12 +11,14 @@ import json
|
|
|
11
11
|
import logging
|
|
12
12
|
import subprocess
|
|
13
13
|
import asyncio
|
|
14
|
+
import os
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Dict, List, Any, Optional
|
|
16
17
|
from dataclasses import dataclass
|
|
17
18
|
|
|
18
19
|
try:
|
|
19
20
|
from minion.tools.mcp.mcp_toolset import MCPToolset, StdioServerParameters
|
|
21
|
+
|
|
20
22
|
MCP_AVAILABLE = True
|
|
21
23
|
except ImportError:
|
|
22
24
|
MCP_AVAILABLE = False
|
|
@@ -26,16 +28,95 @@ except ImportError:
|
|
|
26
28
|
logger = logging.getLogger(__name__)
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def find_mcp_config(project_dir: Optional[Path] = None) -> Optional[Path]:
|
|
32
|
+
"""
|
|
33
|
+
Auto-discover MCP configuration file.
|
|
34
|
+
|
|
35
|
+
Searches in the following order (first found wins):
|
|
36
|
+
|
|
37
|
+
Project scope (current working directory or specified project_dir):
|
|
38
|
+
1. .mcp.json
|
|
39
|
+
2. .claude/mcp.json
|
|
40
|
+
3. .minion/mcp.json
|
|
41
|
+
|
|
42
|
+
User scope (home directory):
|
|
43
|
+
4. ~/.claude-code/mcp.json
|
|
44
|
+
5. ~/.minion-code/mcp.json
|
|
45
|
+
6. ~/.config/minion-code/mcp.json (XDG standard)
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
project_dir: Project directory to search in (defaults to cwd)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to config file if found, None otherwise
|
|
52
|
+
"""
|
|
53
|
+
# Project scope locations
|
|
54
|
+
project_root = project_dir or Path.cwd()
|
|
55
|
+
project_locations = [
|
|
56
|
+
project_root / ".mcp.json",
|
|
57
|
+
project_root / ".claude" / "mcp.json",
|
|
58
|
+
project_root / ".minion" / "mcp.json",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# User scope locations
|
|
62
|
+
home = Path.home()
|
|
63
|
+
user_locations = [
|
|
64
|
+
home / ".claude-code" / "mcp.json",
|
|
65
|
+
home / ".minion-code" / "mcp.json",
|
|
66
|
+
home / ".config" / "minion-code" / "mcp.json",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# Search project scope first
|
|
70
|
+
for config_path in project_locations:
|
|
71
|
+
if config_path.exists():
|
|
72
|
+
logger.info(f"Found MCP config at project scope: {config_path}")
|
|
73
|
+
return config_path
|
|
74
|
+
|
|
75
|
+
# Then search user scope
|
|
76
|
+
for config_path in user_locations:
|
|
77
|
+
if config_path.exists():
|
|
78
|
+
logger.info(f"Found MCP config at user scope: {config_path}")
|
|
79
|
+
return config_path
|
|
80
|
+
|
|
81
|
+
logger.debug("No MCP config file found in any standard location")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_mcp_config_locations() -> Dict[str, List[Path]]:
|
|
86
|
+
"""
|
|
87
|
+
Get all standard MCP config file locations.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dictionary with 'project' and 'user' scope locations
|
|
91
|
+
"""
|
|
92
|
+
project_root = Path.cwd()
|
|
93
|
+
home = Path.home()
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"project": [
|
|
97
|
+
project_root / ".mcp.json",
|
|
98
|
+
project_root / ".claude" / "mcp.json",
|
|
99
|
+
project_root / ".minion" / "mcp.json",
|
|
100
|
+
],
|
|
101
|
+
"user": [
|
|
102
|
+
home / ".claude-code" / "mcp.json",
|
|
103
|
+
home / ".minion-code" / "mcp.json",
|
|
104
|
+
home / ".config" / "minion-code" / "mcp.json",
|
|
105
|
+
],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
29
109
|
@dataclass
|
|
30
110
|
class MCPServerConfig:
|
|
31
111
|
"""Configuration for an MCP server."""
|
|
112
|
+
|
|
32
113
|
name: str
|
|
33
114
|
command: str
|
|
34
115
|
args: List[str]
|
|
35
116
|
env: Optional[Dict[str, str]] = None
|
|
36
117
|
disabled: bool = False
|
|
37
118
|
auto_approve: List[str] = None
|
|
38
|
-
|
|
119
|
+
|
|
39
120
|
def __post_init__(self):
|
|
40
121
|
if self.auto_approve is None:
|
|
41
122
|
self.auto_approve = []
|
|
@@ -43,154 +124,171 @@ class MCPServerConfig:
|
|
|
43
124
|
|
|
44
125
|
class MCPToolsLoader:
|
|
45
126
|
"""Loader for MCP tools from configuration files."""
|
|
46
|
-
|
|
47
|
-
def __init__(self, config_path: Optional[Path] = None):
|
|
127
|
+
|
|
128
|
+
def __init__(self, config_path: Optional[Path] = None, auto_discover: bool = True):
|
|
48
129
|
"""
|
|
49
130
|
Initialize MCP tools loader.
|
|
50
|
-
|
|
131
|
+
|
|
51
132
|
Args:
|
|
52
|
-
config_path: Path to MCP configuration file
|
|
133
|
+
config_path: Path to MCP configuration file. If None and auto_discover=True,
|
|
134
|
+
will search standard locations.
|
|
135
|
+
auto_discover: If True and config_path is None, automatically search for
|
|
136
|
+
config in standard locations (.mcp.json, .claude/, .minion/, etc.)
|
|
53
137
|
"""
|
|
54
|
-
|
|
138
|
+
if config_path:
|
|
139
|
+
self.config_path = config_path
|
|
140
|
+
elif auto_discover:
|
|
141
|
+
self.config_path = find_mcp_config()
|
|
142
|
+
else:
|
|
143
|
+
self.config_path = None
|
|
144
|
+
|
|
55
145
|
self.servers: Dict[str, MCPServerConfig] = {}
|
|
56
146
|
self.loaded_tools = []
|
|
57
147
|
self.toolsets: List[Any] = [] # Store MCPToolset instances for cleanup
|
|
58
|
-
|
|
59
|
-
def load_config(
|
|
148
|
+
|
|
149
|
+
def load_config(
|
|
150
|
+
self, config_path: Optional[Path] = None
|
|
151
|
+
) -> Dict[str, MCPServerConfig]:
|
|
60
152
|
"""
|
|
61
153
|
Load MCP configuration from JSON file.
|
|
62
|
-
|
|
154
|
+
|
|
63
155
|
Args:
|
|
64
156
|
config_path: Path to configuration file
|
|
65
|
-
|
|
157
|
+
|
|
66
158
|
Returns:
|
|
67
159
|
Dictionary of server configurations
|
|
68
160
|
"""
|
|
69
161
|
if config_path:
|
|
70
162
|
self.config_path = config_path
|
|
71
|
-
|
|
163
|
+
|
|
72
164
|
if not self.config_path or not self.config_path.exists():
|
|
73
165
|
logger.warning(f"MCP config file not found: {self.config_path}")
|
|
74
166
|
return {}
|
|
75
|
-
|
|
167
|
+
|
|
76
168
|
try:
|
|
77
|
-
with open(self.config_path,
|
|
169
|
+
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
78
170
|
config_data = json.load(f)
|
|
79
|
-
|
|
80
|
-
servers_config = config_data.get(
|
|
81
|
-
|
|
171
|
+
|
|
172
|
+
servers_config = config_data.get("mcpServers", {})
|
|
173
|
+
|
|
82
174
|
for server_name, server_data in servers_config.items():
|
|
83
175
|
self.servers[server_name] = MCPServerConfig(
|
|
84
176
|
name=server_name,
|
|
85
|
-
command=server_data.get(
|
|
86
|
-
args=server_data.get(
|
|
87
|
-
env=server_data.get(
|
|
88
|
-
disabled=server_data.get(
|
|
89
|
-
auto_approve=server_data.get(
|
|
177
|
+
command=server_data.get("command", ""),
|
|
178
|
+
args=server_data.get("args", []),
|
|
179
|
+
env=server_data.get("env", {}),
|
|
180
|
+
disabled=server_data.get("disabled", False),
|
|
181
|
+
auto_approve=server_data.get("autoApprove", []),
|
|
90
182
|
)
|
|
91
|
-
|
|
183
|
+
|
|
92
184
|
logger.info(f"Loaded {len(self.servers)} MCP server configurations")
|
|
93
185
|
return self.servers
|
|
94
|
-
|
|
186
|
+
|
|
95
187
|
except Exception as e:
|
|
96
188
|
logger.error(f"Failed to load MCP config from {self.config_path}: {e}")
|
|
97
189
|
return {}
|
|
98
|
-
|
|
190
|
+
|
|
99
191
|
async def load_tools_from_server(self, server_config: MCPServerConfig) -> List[Any]:
|
|
100
192
|
"""
|
|
101
193
|
Load tools from an MCP server.
|
|
102
|
-
|
|
194
|
+
|
|
103
195
|
Args:
|
|
104
196
|
server_config: Server configuration
|
|
105
|
-
|
|
197
|
+
|
|
106
198
|
Returns:
|
|
107
199
|
List of loaded tools
|
|
108
200
|
"""
|
|
109
201
|
if server_config.disabled:
|
|
110
202
|
logger.info(f"Skipping disabled MCP server: {server_config.name}")
|
|
111
203
|
return []
|
|
112
|
-
|
|
204
|
+
|
|
113
205
|
if not MCP_AVAILABLE:
|
|
114
206
|
logger.warning("MCP framework not available, skipping MCP server loading")
|
|
115
207
|
return []
|
|
116
|
-
|
|
208
|
+
|
|
117
209
|
try:
|
|
118
210
|
logger.info(f"Loading tools from MCP server: {server_config.name}")
|
|
119
|
-
logger.info(
|
|
120
|
-
|
|
211
|
+
logger.info(
|
|
212
|
+
f"Command: {server_config.command} {' '.join(server_config.args)}"
|
|
213
|
+
)
|
|
214
|
+
|
|
121
215
|
# Create MCPToolset with StdioServerParameters
|
|
122
216
|
toolset = await MCPToolset.create(
|
|
123
217
|
connection_params=StdioServerParameters(
|
|
124
218
|
command=server_config.command,
|
|
125
219
|
args=server_config.args,
|
|
126
|
-
env=server_config.env or {}
|
|
220
|
+
env=server_config.env or {},
|
|
127
221
|
),
|
|
128
222
|
name=server_config.name,
|
|
129
|
-
structured_output=False # Set to False as requested
|
|
223
|
+
structured_output=False, # Set to False as requested
|
|
130
224
|
)
|
|
131
|
-
|
|
225
|
+
|
|
132
226
|
# Store toolset for cleanup
|
|
133
227
|
self.toolsets.append(toolset)
|
|
134
|
-
|
|
228
|
+
|
|
135
229
|
# Get tools from the toolset
|
|
136
|
-
tools = toolset.tools if hasattr(toolset,
|
|
137
|
-
|
|
138
|
-
logger.info(
|
|
230
|
+
tools = toolset.tools if hasattr(toolset, "tools") else []
|
|
231
|
+
|
|
232
|
+
logger.info(
|
|
233
|
+
f"Successfully loaded {len(tools)} tools from {server_config.name}"
|
|
234
|
+
)
|
|
139
235
|
return tools
|
|
140
|
-
|
|
236
|
+
|
|
141
237
|
except Exception as e:
|
|
142
|
-
logger.error(
|
|
238
|
+
logger.error(
|
|
239
|
+
f"Failed to load tools from MCP server {server_config.name}: {e}"
|
|
240
|
+
)
|
|
143
241
|
return []
|
|
144
|
-
|
|
242
|
+
|
|
145
243
|
async def load_all_tools(self) -> List[Any]:
|
|
146
244
|
"""
|
|
147
245
|
Load tools from all configured MCP servers.
|
|
148
|
-
|
|
246
|
+
|
|
149
247
|
Returns:
|
|
150
248
|
List of all loaded MCP tools
|
|
151
249
|
"""
|
|
152
250
|
all_tools = []
|
|
153
|
-
|
|
251
|
+
|
|
154
252
|
for server_name, server_config in self.servers.items():
|
|
155
253
|
if not server_config.disabled:
|
|
156
254
|
tools = await self.load_tools_from_server(server_config)
|
|
157
255
|
all_tools.extend(tools)
|
|
158
256
|
logger.info(f"Loaded {len(tools)} tools from {server_name}")
|
|
159
|
-
|
|
257
|
+
|
|
160
258
|
self.loaded_tools = all_tools
|
|
161
259
|
logger.info(f"Total MCP tools loaded: {len(all_tools)}")
|
|
162
260
|
return all_tools
|
|
163
|
-
|
|
261
|
+
|
|
164
262
|
def get_server_info(self) -> Dict[str, Dict[str, Any]]:
|
|
165
263
|
"""
|
|
166
264
|
Get information about configured servers.
|
|
167
|
-
|
|
265
|
+
|
|
168
266
|
Returns:
|
|
169
267
|
Dictionary with server information
|
|
170
268
|
"""
|
|
171
269
|
info = {}
|
|
172
270
|
for name, config in self.servers.items():
|
|
173
271
|
info[name] = {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
272
|
+
"command": config.command,
|
|
273
|
+
"args": config.args,
|
|
274
|
+
"disabled": config.disabled,
|
|
275
|
+
"auto_approve_count": len(config.auto_approve),
|
|
178
276
|
}
|
|
179
277
|
return info
|
|
180
|
-
|
|
278
|
+
|
|
181
279
|
async def close(self):
|
|
182
280
|
"""
|
|
183
281
|
Close all MCP toolsets and clean up resources.
|
|
184
282
|
"""
|
|
185
283
|
logger.info(f"Closing {len(self.toolsets)} MCP toolsets...")
|
|
186
|
-
|
|
284
|
+
|
|
187
285
|
for toolset in self.toolsets:
|
|
188
286
|
try:
|
|
189
287
|
await toolset.close()
|
|
190
288
|
logger.debug(f"Closed toolset: {getattr(toolset, 'name', 'unknown')}")
|
|
191
289
|
except Exception as e:
|
|
192
290
|
logger.error(f"Error closing toolset: {e}")
|
|
193
|
-
|
|
291
|
+
|
|
194
292
|
self.toolsets.clear()
|
|
195
293
|
logger.info("All MCP toolsets closed")
|
|
196
294
|
|
|
@@ -199,13 +297,13 @@ class MCPToolsLoader:
|
|
|
199
297
|
async def load_mcp_tools(config_path: Path) -> List[Any]:
|
|
200
298
|
"""
|
|
201
299
|
Convenience function to load MCP tools from a configuration file.
|
|
202
|
-
|
|
300
|
+
|
|
203
301
|
Args:
|
|
204
302
|
config_path: Path to MCP configuration file
|
|
205
|
-
|
|
303
|
+
|
|
206
304
|
Returns:
|
|
207
305
|
List of loaded MCP tools
|
|
208
306
|
"""
|
|
209
307
|
loader = MCPToolsLoader(config_path)
|
|
210
308
|
loader.load_config()
|
|
211
|
-
return await loader.load_all_tools()
|
|
309
|
+
return await loader.load_all_tools()
|