codepp 0.0.437__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.
- code_puppy/__init__.py +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +117 -0
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +638 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +124 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +385 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +169 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_scheduler.py +121 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +2156 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +327 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +453 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +692 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +672 -0
- code_puppy/cli_runner.py +1073 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +532 -0
- code_puppy/command_line/command_handler.py +293 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +867 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +96 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/shell_passthrough.py +145 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1770 -0
- code_puppy/error_logging.py +134 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +21 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +221 -0
- code_puppy/hook_engine/executor.py +296 -0
- code_puppy/hook_engine/matcher.py +156 -0
- code_puppy/hook_engine/models.py +240 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +144 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1158 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +95 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +174 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +137 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +640 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +290 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +356 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +501 -0
- code_puppy/tools/agent_tools.py +603 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +26 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +378 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +886 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +244 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- codepp-0.0.437.dist-info/METADATA +766 -0
- codepp-0.0.437.dist-info/RECORD +288 -0
- codepp-0.0.437.dist-info/WHEEL +4 -0
- codepp-0.0.437.dist-info/entry_points.txt +3 -0
- codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Configuration Wizard - Interactive setup for MCP servers.
|
|
3
|
+
|
|
4
|
+
Note: This module imports ServerConfig and get_mcp_manager directly from
|
|
5
|
+
.code_puppy.mcp.manager to avoid circular imports with the package __init__.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
from code_puppy.mcp_.manager import ServerConfig, get_mcp_manager
|
|
16
|
+
from code_puppy.messaging import (
|
|
17
|
+
emit_error,
|
|
18
|
+
emit_info,
|
|
19
|
+
emit_prompt,
|
|
20
|
+
emit_success,
|
|
21
|
+
emit_warning,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def prompt_ask(
|
|
26
|
+
prompt_text: str, default: Optional[str] = None, choices: Optional[list] = None
|
|
27
|
+
) -> Optional[str]:
|
|
28
|
+
"""Helper function to replace rich.prompt.Prompt.ask with emit_prompt."""
|
|
29
|
+
try:
|
|
30
|
+
if default:
|
|
31
|
+
full_prompt = f"{prompt_text} [{default}]"
|
|
32
|
+
else:
|
|
33
|
+
full_prompt = prompt_text
|
|
34
|
+
|
|
35
|
+
if choices:
|
|
36
|
+
full_prompt += f" ({'/'.join(choices)})"
|
|
37
|
+
|
|
38
|
+
response = emit_prompt(full_prompt + ": ")
|
|
39
|
+
|
|
40
|
+
# Handle default value
|
|
41
|
+
if not response.strip() and default:
|
|
42
|
+
return default
|
|
43
|
+
|
|
44
|
+
# Handle choices validation
|
|
45
|
+
if choices and response.strip() and response.strip() not in choices:
|
|
46
|
+
emit_error(f"Invalid choice. Must be one of: {', '.join(choices)}")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
return response.strip() if response.strip() else None
|
|
50
|
+
except Exception as e:
|
|
51
|
+
emit_error(f"Input error: {e}")
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def confirm_ask(prompt_text: str, default: bool = True) -> bool:
|
|
56
|
+
"""Helper function to replace rich.prompt.Confirm.ask with emit_prompt."""
|
|
57
|
+
try:
|
|
58
|
+
default_text = "[Y/n]" if default else "[y/N]"
|
|
59
|
+
response = emit_prompt(f"{prompt_text} {default_text}: ")
|
|
60
|
+
|
|
61
|
+
if not response.strip():
|
|
62
|
+
return default
|
|
63
|
+
|
|
64
|
+
response_lower = response.strip().lower()
|
|
65
|
+
if response_lower in ["y", "yes", "true", "1"]:
|
|
66
|
+
return True
|
|
67
|
+
elif response_lower in ["n", "no", "false", "0"]:
|
|
68
|
+
return False
|
|
69
|
+
else:
|
|
70
|
+
return default
|
|
71
|
+
except Exception as e:
|
|
72
|
+
emit_error(f"Input error: {e}")
|
|
73
|
+
return default
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MCPConfigWizard:
|
|
77
|
+
"""Interactive wizard for configuring MCP servers."""
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self.manager = get_mcp_manager()
|
|
81
|
+
|
|
82
|
+
def run_wizard(self, group_id: str = None) -> Optional[ServerConfig]:
|
|
83
|
+
"""
|
|
84
|
+
Run the interactive configuration wizard.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
group_id: Optional message group ID for grouping related messages
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
ServerConfig if successful, None if cancelled
|
|
91
|
+
"""
|
|
92
|
+
if group_id is None:
|
|
93
|
+
import uuid
|
|
94
|
+
|
|
95
|
+
group_id = str(uuid.uuid4())
|
|
96
|
+
|
|
97
|
+
emit_info("🧙 MCP Server Configuration Wizard", message_group=group_id)
|
|
98
|
+
|
|
99
|
+
# Step 1: Server name
|
|
100
|
+
name = self.prompt_server_name(group_id)
|
|
101
|
+
if not name:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
# Step 2: Server type
|
|
105
|
+
server_type = self.prompt_server_type(group_id)
|
|
106
|
+
if not server_type:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
# Step 3: Type-specific configuration
|
|
110
|
+
config = {}
|
|
111
|
+
if server_type == "sse":
|
|
112
|
+
config = self.prompt_sse_config(group_id)
|
|
113
|
+
elif server_type == "http":
|
|
114
|
+
config = self.prompt_http_config(group_id)
|
|
115
|
+
elif server_type == "stdio":
|
|
116
|
+
config = self.prompt_stdio_config(group_id)
|
|
117
|
+
|
|
118
|
+
if not config:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
# Step 4: Create ServerConfig
|
|
122
|
+
server_config = ServerConfig(
|
|
123
|
+
id=f"{name}_{hash(name)}",
|
|
124
|
+
name=name,
|
|
125
|
+
type=server_type,
|
|
126
|
+
enabled=True,
|
|
127
|
+
config=config,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Step 5: Show summary and confirm
|
|
131
|
+
if self.prompt_confirmation(server_config, group_id):
|
|
132
|
+
return server_config
|
|
133
|
+
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def prompt_server_name(self, group_id: str = None) -> Optional[str]:
|
|
137
|
+
"""Prompt for server name with validation."""
|
|
138
|
+
while True:
|
|
139
|
+
name = prompt_ask("Enter server name", default=None)
|
|
140
|
+
|
|
141
|
+
if not name:
|
|
142
|
+
if not confirm_ask("Cancel configuration?", default=False):
|
|
143
|
+
continue
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
# Validate name
|
|
147
|
+
if not self.validate_name(name):
|
|
148
|
+
emit_error(
|
|
149
|
+
"Name must be alphanumeric with hyphens/underscores only",
|
|
150
|
+
message_group=group_id,
|
|
151
|
+
)
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
# Check uniqueness
|
|
155
|
+
existing = self.manager.registry.get_by_name(name)
|
|
156
|
+
if existing:
|
|
157
|
+
emit_error(f"Server '{name}' already exists", message_group=group_id)
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
return name
|
|
161
|
+
|
|
162
|
+
def prompt_server_type(self, group_id: str = None) -> Optional[str]:
|
|
163
|
+
"""Prompt for server type."""
|
|
164
|
+
emit_info("\nServer types:", message_group=group_id)
|
|
165
|
+
emit_info(
|
|
166
|
+
" sse - Server-Sent Events (HTTP streaming)", message_group=group_id
|
|
167
|
+
)
|
|
168
|
+
emit_info(" http - HTTP/REST API", message_group=group_id)
|
|
169
|
+
emit_info(" stdio - Local command (subprocess)", message_group=group_id)
|
|
170
|
+
|
|
171
|
+
while True:
|
|
172
|
+
server_type = prompt_ask(
|
|
173
|
+
"Select server type", choices=["sse", "http", "stdio"], default="stdio"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if server_type in ["sse", "http", "stdio"]:
|
|
177
|
+
return server_type
|
|
178
|
+
|
|
179
|
+
emit_error(
|
|
180
|
+
"Invalid type. Choose: sse, http, or stdio", message_group=group_id
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def prompt_sse_config(self, group_id: str = None) -> Optional[Dict]:
|
|
184
|
+
"""Prompt for SSE server configuration."""
|
|
185
|
+
emit_info("Configuring SSE server", message_group=group_id)
|
|
186
|
+
|
|
187
|
+
# URL
|
|
188
|
+
url = self.prompt_url("SSE", group_id)
|
|
189
|
+
if not url:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
config = {"type": "sse", "url": url, "timeout": 30}
|
|
193
|
+
|
|
194
|
+
# Headers (optional)
|
|
195
|
+
if confirm_ask("Add custom headers?", default=False):
|
|
196
|
+
headers = self.prompt_headers(group_id)
|
|
197
|
+
if headers:
|
|
198
|
+
config["headers"] = headers
|
|
199
|
+
|
|
200
|
+
# Timeout
|
|
201
|
+
timeout_str = prompt_ask("Connection timeout (seconds)", default="30")
|
|
202
|
+
try:
|
|
203
|
+
config["timeout"] = int(timeout_str)
|
|
204
|
+
except ValueError:
|
|
205
|
+
config["timeout"] = 30
|
|
206
|
+
|
|
207
|
+
return config
|
|
208
|
+
|
|
209
|
+
def prompt_http_config(self, group_id: str = None) -> Optional[Dict]:
|
|
210
|
+
"""Prompt for HTTP server configuration."""
|
|
211
|
+
emit_info("Configuring HTTP server", message_group=group_id)
|
|
212
|
+
|
|
213
|
+
# URL
|
|
214
|
+
url = self.prompt_url("HTTP", group_id)
|
|
215
|
+
if not url:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
config = {"type": "http", "url": url, "timeout": 30}
|
|
219
|
+
|
|
220
|
+
# Headers (optional)
|
|
221
|
+
if confirm_ask("Add custom headers?", default=False):
|
|
222
|
+
headers = self.prompt_headers(group_id)
|
|
223
|
+
if headers:
|
|
224
|
+
config["headers"] = headers
|
|
225
|
+
|
|
226
|
+
# Timeout
|
|
227
|
+
timeout_str = prompt_ask("Request timeout (seconds)", default="30")
|
|
228
|
+
try:
|
|
229
|
+
config["timeout"] = int(timeout_str)
|
|
230
|
+
except ValueError:
|
|
231
|
+
config["timeout"] = 30
|
|
232
|
+
|
|
233
|
+
return config
|
|
234
|
+
|
|
235
|
+
def prompt_stdio_config(self, group_id: str = None) -> Optional[Dict]:
|
|
236
|
+
"""Prompt for Stdio server configuration."""
|
|
237
|
+
emit_info("Configuring Stdio server", message_group=group_id)
|
|
238
|
+
emit_info("Examples:", message_group=group_id)
|
|
239
|
+
emit_info(
|
|
240
|
+
" • npx -y @modelcontextprotocol/server-filesystem /path",
|
|
241
|
+
message_group=group_id,
|
|
242
|
+
)
|
|
243
|
+
emit_info(" • python mcp_server.py", message_group=group_id)
|
|
244
|
+
emit_info(" • node server.js", message_group=group_id)
|
|
245
|
+
|
|
246
|
+
# Command
|
|
247
|
+
command = prompt_ask("Enter command", default=None)
|
|
248
|
+
|
|
249
|
+
if not command:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
config = {"type": "stdio", "command": command, "args": [], "timeout": 30}
|
|
253
|
+
|
|
254
|
+
# Arguments
|
|
255
|
+
args_str = prompt_ask("Enter arguments (space-separated)", default="")
|
|
256
|
+
if args_str:
|
|
257
|
+
# Simple argument parsing (handles quoted strings)
|
|
258
|
+
import shlex
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
config["args"] = shlex.split(args_str)
|
|
262
|
+
except ValueError:
|
|
263
|
+
config["args"] = args_str.split()
|
|
264
|
+
|
|
265
|
+
# Working directory (optional)
|
|
266
|
+
cwd = prompt_ask("Working directory (optional)", default="")
|
|
267
|
+
if cwd:
|
|
268
|
+
import os
|
|
269
|
+
|
|
270
|
+
if os.path.isdir(os.path.expanduser(cwd)):
|
|
271
|
+
config["cwd"] = os.path.expanduser(cwd)
|
|
272
|
+
else:
|
|
273
|
+
emit_warning(
|
|
274
|
+
f"Directory '{cwd}' not found, ignoring", message_group=group_id
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Environment variables (optional)
|
|
278
|
+
if confirm_ask("Add environment variables?", default=False):
|
|
279
|
+
env = self.prompt_env_vars(group_id)
|
|
280
|
+
if env:
|
|
281
|
+
config["env"] = env
|
|
282
|
+
|
|
283
|
+
# Timeout
|
|
284
|
+
timeout_str = prompt_ask("Startup timeout (seconds)", default="30")
|
|
285
|
+
try:
|
|
286
|
+
config["timeout"] = int(timeout_str)
|
|
287
|
+
except ValueError:
|
|
288
|
+
config["timeout"] = 30
|
|
289
|
+
|
|
290
|
+
return config
|
|
291
|
+
|
|
292
|
+
def prompt_url(self, server_type: str, group_id: str = None) -> Optional[str]:
|
|
293
|
+
"""Prompt for and validate URL."""
|
|
294
|
+
while True:
|
|
295
|
+
url = prompt_ask(f"Enter {server_type} server URL", default=None)
|
|
296
|
+
|
|
297
|
+
if not url:
|
|
298
|
+
if confirm_ask("Cancel configuration?", default=False):
|
|
299
|
+
return None
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
if self.validate_url(url):
|
|
303
|
+
return url
|
|
304
|
+
|
|
305
|
+
emit_error(
|
|
306
|
+
"Invalid URL. Must be http:// or https://", message_group=group_id
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def prompt_headers(self, group_id: str = None) -> Dict[str, str]:
|
|
310
|
+
"""Prompt for HTTP headers."""
|
|
311
|
+
headers = {}
|
|
312
|
+
emit_info("Enter headers (format: Name: Value)", message_group=group_id)
|
|
313
|
+
emit_info("Press Enter with empty name to finish", message_group=group_id)
|
|
314
|
+
|
|
315
|
+
while True:
|
|
316
|
+
name = prompt_ask("Header name", default="")
|
|
317
|
+
if not name:
|
|
318
|
+
break
|
|
319
|
+
|
|
320
|
+
value = prompt_ask(f"Value for '{name}'", default="")
|
|
321
|
+
headers[name] = value
|
|
322
|
+
|
|
323
|
+
if not confirm_ask("Add another header?", default=True):
|
|
324
|
+
break
|
|
325
|
+
|
|
326
|
+
return headers
|
|
327
|
+
|
|
328
|
+
def prompt_env_vars(self, group_id: str = None) -> Dict[str, str]:
|
|
329
|
+
"""Prompt for environment variables."""
|
|
330
|
+
env = {}
|
|
331
|
+
emit_info("Enter environment variables", message_group=group_id)
|
|
332
|
+
emit_info("Press Enter with empty name to finish", message_group=group_id)
|
|
333
|
+
|
|
334
|
+
while True:
|
|
335
|
+
name = prompt_ask("Variable name", default="")
|
|
336
|
+
if not name:
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
value = prompt_ask(f"Value for '{name}'", default="")
|
|
340
|
+
env[name] = value
|
|
341
|
+
|
|
342
|
+
if not confirm_ask("Add another variable?", default=True):
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
return env
|
|
346
|
+
|
|
347
|
+
def validate_name(self, name: str) -> bool:
|
|
348
|
+
"""Validate server name."""
|
|
349
|
+
# Allow alphanumeric, hyphens, and underscores
|
|
350
|
+
return bool(re.match(r"^[a-zA-Z0-9_-]+$", name))
|
|
351
|
+
|
|
352
|
+
def validate_url(self, url: str) -> bool:
|
|
353
|
+
"""Validate URL format."""
|
|
354
|
+
try:
|
|
355
|
+
result = urlparse(url)
|
|
356
|
+
return result.scheme in ("http", "https") and bool(result.netloc)
|
|
357
|
+
except Exception:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
def validate_command(self, command: str) -> bool:
|
|
361
|
+
"""Check if command exists (basic check)."""
|
|
362
|
+
import os
|
|
363
|
+
import shutil
|
|
364
|
+
|
|
365
|
+
# If it's a path, check if file exists
|
|
366
|
+
if "/" in command or "\\" in command:
|
|
367
|
+
return os.path.isfile(command)
|
|
368
|
+
|
|
369
|
+
# Otherwise check if it's in PATH
|
|
370
|
+
return shutil.which(command) is not None
|
|
371
|
+
|
|
372
|
+
def test_connection(self, config: ServerConfig, group_id: str = None) -> bool:
|
|
373
|
+
"""
|
|
374
|
+
Test connection to the configured server.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
config: Server configuration to test
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
True if connection successful, False otherwise
|
|
381
|
+
"""
|
|
382
|
+
emit_info("Testing connection...", message_group=group_id)
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
# Try to create the server instance
|
|
386
|
+
managed = self.manager.get_server(config.id)
|
|
387
|
+
if not managed:
|
|
388
|
+
# Temporarily register to test
|
|
389
|
+
self.manager.register_server(config)
|
|
390
|
+
managed = self.manager.get_server(config.id)
|
|
391
|
+
|
|
392
|
+
if managed:
|
|
393
|
+
# Try to get the pydantic server (this validates config)
|
|
394
|
+
server = managed.get_pydantic_server()
|
|
395
|
+
if server:
|
|
396
|
+
emit_success("✓ Configuration valid", message_group=group_id)
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
emit_error("✗ Failed to create server instance", message_group=group_id)
|
|
400
|
+
return False
|
|
401
|
+
|
|
402
|
+
except Exception as e:
|
|
403
|
+
emit_error(f"✗ Configuration error: {e}", message_group=group_id)
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
def prompt_confirmation(self, config: ServerConfig, group_id: str = None) -> bool:
|
|
407
|
+
"""Show summary and ask for confirmation."""
|
|
408
|
+
emit_info("Configuration Summary:", message_group=group_id)
|
|
409
|
+
emit_info(f" Name: {config.name}", message_group=group_id)
|
|
410
|
+
emit_info(f" Type: {config.type}", message_group=group_id)
|
|
411
|
+
|
|
412
|
+
if config.type in ["sse", "http"]:
|
|
413
|
+
emit_info(f" URL: {config.config.get('url')}", message_group=group_id)
|
|
414
|
+
elif config.type == "stdio":
|
|
415
|
+
emit_info(
|
|
416
|
+
f" Command: {config.config.get('command')}", message_group=group_id
|
|
417
|
+
)
|
|
418
|
+
args = config.config.get("args", [])
|
|
419
|
+
if args:
|
|
420
|
+
emit_info(f" Arguments: {' '.join(args)}", message_group=group_id)
|
|
421
|
+
|
|
422
|
+
emit_info(
|
|
423
|
+
f" Timeout: {config.config.get('timeout', 30)}s", message_group=group_id
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Test connection if requested
|
|
427
|
+
if confirm_ask("Test connection?", default=True):
|
|
428
|
+
if not self.test_connection(config, group_id):
|
|
429
|
+
if not confirm_ask("Continue anyway?", default=False):
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
return confirm_ask("Save this configuration?", default=True)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def run_add_wizard(group_id: str = None) -> bool:
|
|
436
|
+
"""
|
|
437
|
+
Run the MCP add wizard and register the server.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
group_id: Optional message group ID for grouping related messages
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
True if server was added, False otherwise
|
|
444
|
+
"""
|
|
445
|
+
if group_id is None:
|
|
446
|
+
import uuid
|
|
447
|
+
|
|
448
|
+
group_id = str(uuid.uuid4())
|
|
449
|
+
|
|
450
|
+
wizard = MCPConfigWizard()
|
|
451
|
+
config = wizard.run_wizard(group_id)
|
|
452
|
+
|
|
453
|
+
if config:
|
|
454
|
+
try:
|
|
455
|
+
manager = get_mcp_manager()
|
|
456
|
+
server_id = manager.register_server(config)
|
|
457
|
+
|
|
458
|
+
emit_success(
|
|
459
|
+
f"\n✅ Server '{config.name}' added successfully!",
|
|
460
|
+
message_group=group_id,
|
|
461
|
+
)
|
|
462
|
+
emit_info(f"Server ID: {server_id}", message_group=group_id)
|
|
463
|
+
emit_info("Use '/mcp list' to see all servers", message_group=group_id)
|
|
464
|
+
emit_info(
|
|
465
|
+
f"Use '/mcp start {config.name}' to start the server",
|
|
466
|
+
message_group=group_id,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# Also save to mcp_servers.json for persistence
|
|
470
|
+
import json
|
|
471
|
+
import os
|
|
472
|
+
|
|
473
|
+
from code_puppy.config import MCP_SERVERS_FILE
|
|
474
|
+
|
|
475
|
+
# Load existing configs
|
|
476
|
+
if os.path.exists(MCP_SERVERS_FILE):
|
|
477
|
+
with open(MCP_SERVERS_FILE, "r") as f:
|
|
478
|
+
data = json.load(f)
|
|
479
|
+
servers = data.get("mcp_servers", {})
|
|
480
|
+
else:
|
|
481
|
+
servers = {}
|
|
482
|
+
data = {"mcp_servers": servers}
|
|
483
|
+
|
|
484
|
+
# Add new server
|
|
485
|
+
servers[config.name] = config.config
|
|
486
|
+
|
|
487
|
+
# Save back
|
|
488
|
+
os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
|
|
489
|
+
temp_path = Path(MCP_SERVERS_FILE).with_suffix(".tmp")
|
|
490
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
491
|
+
json.dump(data, f, indent=2)
|
|
492
|
+
temp_path.replace(MCP_SERVERS_FILE)
|
|
493
|
+
|
|
494
|
+
emit_info(
|
|
495
|
+
Text.from_markup(
|
|
496
|
+
f"[dim]Configuration saved to {MCP_SERVERS_FILE}[/dim]"
|
|
497
|
+
),
|
|
498
|
+
message_group=group_id,
|
|
499
|
+
)
|
|
500
|
+
return True
|
|
501
|
+
|
|
502
|
+
except Exception as e:
|
|
503
|
+
emit_error(f"Failed to add server: {e}", message_group=group_id)
|
|
504
|
+
return False
|
|
505
|
+
else:
|
|
506
|
+
emit_warning("Configuration cancelled", message_group=group_id)
|
|
507
|
+
return False
|