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.
Files changed (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. 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