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,428 @@
1
+ """
2
+ ManagedMCPServer wrapper class implementation.
3
+
4
+ This module provides a managed wrapper around pydantic-ai MCP server classes
5
+ that adds management capabilities while maintaining 100% compatibility.
6
+ """
7
+
8
+ import os
9
+ import uuid
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timedelta
12
+ from enum import Enum
13
+ from typing import Any, Dict, Optional, Union
14
+
15
+ import httpx
16
+ from pydantic_ai import RunContext
17
+ from pydantic_ai.mcp import (
18
+ CallToolFunc,
19
+ MCPServerSSE,
20
+ MCPServerStdio,
21
+ MCPServerStreamableHTTP,
22
+ ToolResult,
23
+ )
24
+
25
+ from code_puppy.http_utils import create_async_client
26
+ from code_puppy.mcp_.blocking_startup import BlockingMCPServerStdio
27
+
28
+
29
+ def _expand_env_vars(value: Any) -> Any:
30
+ """
31
+ Recursively expand environment variables in config values.
32
+
33
+ Supports $VAR and ${VAR} syntax. Works with:
34
+ - Strings: expands env vars
35
+ - Dicts: recursively expands all string values
36
+ - Lists: recursively expands all string elements
37
+ - Other types: returned as-is
38
+
39
+ Args:
40
+ value: The value to expand env vars in
41
+
42
+ Returns:
43
+ The value with env vars expanded
44
+ """
45
+ if isinstance(value, str):
46
+ return os.path.expandvars(value)
47
+ elif isinstance(value, dict):
48
+ return {k: _expand_env_vars(v) for k, v in value.items()}
49
+ elif isinstance(value, list):
50
+ return [_expand_env_vars(item) for item in value]
51
+ return value
52
+
53
+
54
+ class ServerState(Enum):
55
+ """Enumeration of possible server states."""
56
+
57
+ STOPPED = "stopped"
58
+ STARTING = "starting"
59
+ RUNNING = "running"
60
+ STOPPING = "stopping"
61
+ ERROR = "error"
62
+ QUARANTINED = "quarantined"
63
+
64
+
65
+ @dataclass
66
+ class ServerConfig:
67
+ """Configuration for an MCP server."""
68
+
69
+ id: str
70
+ name: str
71
+ type: str # "sse", "stdio", or "http"
72
+ enabled: bool = True
73
+ config: Dict = field(default_factory=dict) # Raw config from JSON
74
+
75
+
76
+ async def process_tool_call(
77
+ ctx: RunContext[Any],
78
+ call_tool: CallToolFunc,
79
+ name: str,
80
+ tool_args: dict[str, Any],
81
+ ) -> ToolResult:
82
+ """A tool call processor that passes along the deps."""
83
+ from rich.console import Console
84
+
85
+ from code_puppy.config import get_banner_color
86
+
87
+ console = Console()
88
+ color = get_banner_color("mcp_tool_call")
89
+ banner = f"[bold white on {color}] MCP TOOL CALL [/bold white on {color}]"
90
+ console.print(f"\n{banner} 🔧 [bold cyan]{name}[/bold cyan]")
91
+ return await call_tool(name, tool_args, {"deps": ctx.deps})
92
+
93
+
94
+ class ManagedMCPServer:
95
+ """
96
+ Managed wrapper around pydantic-ai MCP server classes.
97
+
98
+ This class provides management capabilities like enable/disable,
99
+ quarantine, and status tracking while maintaining 100% compatibility
100
+ with the existing Agent interface through get_pydantic_server().
101
+
102
+ Example usage:
103
+ config = ServerConfig(
104
+ id="123",
105
+ name="test",
106
+ type="sse",
107
+ config={"url": "http://localhost:8080"}
108
+ )
109
+ managed = ManagedMCPServer(config)
110
+ pydantic_server = managed.get_pydantic_server() # Returns actual MCPServerSSE
111
+ """
112
+
113
+ def __init__(self, server_config: ServerConfig):
114
+ """
115
+ Initialize managed server with configuration.
116
+
117
+ Args:
118
+ server_config: Server configuration containing type, connection details, etc.
119
+ """
120
+ self.config = server_config
121
+ self._pydantic_server: Optional[
122
+ Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]
123
+ ] = None
124
+ self._state = ServerState.STOPPED
125
+ # Always start disabled - servers must be explicitly started with /mcp start
126
+ self._enabled = False
127
+ self._quarantine_until: Optional[datetime] = None
128
+ self._start_time: Optional[datetime] = None
129
+ self._stop_time: Optional[datetime] = None
130
+ self._error_message: Optional[str] = None
131
+
132
+ # Initialize the pydantic server
133
+ try:
134
+ self._create_server()
135
+ # Always start as STOPPED - servers must be explicitly started
136
+ self._state = ServerState.STOPPED
137
+ except Exception as e:
138
+ self._state = ServerState.ERROR
139
+ self._error_message = str(e)
140
+
141
+ def get_pydantic_server(
142
+ self,
143
+ ) -> Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]:
144
+ """
145
+ Get the actual pydantic-ai server instance.
146
+
147
+ This method returns the real pydantic-ai MCP server objects for 100% compatibility
148
+ with the existing Agent interface. Do not return custom classes or proxies.
149
+
150
+ Returns:
151
+ Actual pydantic-ai MCP server instance (MCPServerSSE, MCPServerStdio, or MCPServerStreamableHTTP)
152
+
153
+ Raises:
154
+ RuntimeError: If server creation failed or server is not available
155
+ """
156
+ if self._pydantic_server is None:
157
+ raise RuntimeError(f"Server {self.config.name} is not available")
158
+
159
+ if not self.is_enabled() or self.is_quarantined():
160
+ raise RuntimeError(f"Server {self.config.name} is disabled or quarantined")
161
+
162
+ return self._pydantic_server
163
+
164
+ def _create_server(self) -> None:
165
+ """
166
+ Create appropriate pydantic-ai server based on config type.
167
+
168
+ Raises:
169
+ ValueError: If server type is unsupported or config is invalid
170
+ Exception: If server creation fails
171
+ """
172
+ server_type = self.config.type.lower()
173
+ config = self.config.config
174
+
175
+ try:
176
+ if server_type == "sse":
177
+ if "url" not in config:
178
+ raise ValueError("SSE server requires 'url' in config")
179
+
180
+ # Prepare arguments for MCPServerSSE (expand env vars in URL)
181
+ sse_kwargs = {
182
+ "url": _expand_env_vars(config["url"]),
183
+ }
184
+
185
+ # Add optional parameters if provided
186
+ if "timeout" in config:
187
+ sse_kwargs["timeout"] = config["timeout"]
188
+ if "read_timeout" in config:
189
+ sse_kwargs["read_timeout"] = config["read_timeout"]
190
+ if "http_client" in config:
191
+ sse_kwargs["http_client"] = config["http_client"]
192
+ elif config.get("headers"):
193
+ # Create HTTP client if headers are provided but no client specified
194
+ sse_kwargs["http_client"] = self._get_http_client()
195
+
196
+ self._pydantic_server = MCPServerSSE(
197
+ **sse_kwargs, process_tool_call=process_tool_call
198
+ )
199
+
200
+ elif server_type == "stdio":
201
+ if "command" not in config:
202
+ raise ValueError("Stdio server requires 'command' in config")
203
+
204
+ # Handle command and arguments (expand env vars)
205
+ command = _expand_env_vars(config["command"])
206
+ args = config.get("args", [])
207
+ if isinstance(args, str):
208
+ # If args is a string, split it then expand
209
+ args = [_expand_env_vars(a) for a in args.split()]
210
+ else:
211
+ args = _expand_env_vars(args)
212
+
213
+ # Prepare arguments for MCPServerStdio
214
+ stdio_kwargs = {"command": command, "args": list(args) if args else []}
215
+
216
+ # Add optional parameters if provided (expand env vars in env and cwd)
217
+ if "env" in config:
218
+ stdio_kwargs["env"] = _expand_env_vars(config["env"])
219
+ if "cwd" in config:
220
+ stdio_kwargs["cwd"] = _expand_env_vars(config["cwd"])
221
+ # Default timeout of 60s for stdio servers - some servers like Serena take a while to start
222
+ # Users can override this in their config
223
+ stdio_kwargs["timeout"] = config.get("timeout", 60)
224
+ if "read_timeout" in config:
225
+ stdio_kwargs["read_timeout"] = config["read_timeout"]
226
+
227
+ # Use BlockingMCPServerStdio for proper initialization blocking and stderr capture
228
+ # Create a unique message group for this server
229
+ message_group = uuid.uuid4()
230
+ self._pydantic_server = BlockingMCPServerStdio(
231
+ **stdio_kwargs,
232
+ process_tool_call=process_tool_call,
233
+ tool_prefix=self.config.name,
234
+ emit_stderr=False, # Logs go to file, not console (use /mcp logs to view)
235
+ message_group=message_group,
236
+ )
237
+
238
+ elif server_type == "http":
239
+ if "url" not in config:
240
+ raise ValueError("HTTP server requires 'url' in config")
241
+
242
+ # Prepare arguments for MCPServerStreamableHTTP (expand env vars in URL)
243
+ http_kwargs = {
244
+ "url": _expand_env_vars(config["url"]),
245
+ }
246
+
247
+ # Add optional parameters if provided
248
+ if "timeout" in config:
249
+ http_kwargs["timeout"] = config["timeout"]
250
+ if "read_timeout" in config:
251
+ http_kwargs["read_timeout"] = config["read_timeout"]
252
+
253
+ # Pass headers directly instead of creating http_client
254
+ # Note: There's a bug in MCP 1.25.0 where passing http_client
255
+ # causes "'_AsyncGeneratorContextManager' object has no attribute 'stream'"
256
+ # The workaround is to pass headers directly and let pydantic-ai
257
+ # create the http_client internally.
258
+ if config.get("headers"):
259
+ # Expand environment variables in headers
260
+ http_kwargs["headers"] = _expand_env_vars(config["headers"])
261
+
262
+ self._pydantic_server = MCPServerStreamableHTTP(
263
+ **http_kwargs, process_tool_call=process_tool_call
264
+ )
265
+
266
+ else:
267
+ raise ValueError(f"Unsupported server type: {server_type}")
268
+
269
+ except Exception:
270
+ raise
271
+
272
+ def _get_http_client(self) -> httpx.AsyncClient:
273
+ """
274
+ Create httpx.AsyncClient with headers from config.
275
+
276
+ Returns:
277
+ Configured async HTTP client with custom headers
278
+ """
279
+ headers = self.config.config.get("headers", {})
280
+
281
+ # Expand environment variables in headers
282
+ resolved_headers = {}
283
+ if isinstance(headers, dict):
284
+ for k, v in headers.items():
285
+ if isinstance(v, str):
286
+ resolved_headers[k] = os.path.expandvars(v)
287
+ else:
288
+ resolved_headers[k] = v
289
+
290
+ timeout = self.config.config.get("timeout", 30)
291
+ client = create_async_client(headers=resolved_headers, timeout=timeout)
292
+ return client
293
+
294
+ def enable(self) -> None:
295
+ """Enable server availability."""
296
+ self._enabled = True
297
+ if self._state == ServerState.STOPPED and self._pydantic_server is not None:
298
+ self._state = ServerState.RUNNING
299
+ self._start_time = datetime.now()
300
+
301
+ def disable(self) -> None:
302
+ """Disable server availability."""
303
+ self._enabled = False
304
+ if self._state == ServerState.RUNNING:
305
+ self._state = ServerState.STOPPED
306
+ self._stop_time = datetime.now()
307
+
308
+ def is_enabled(self) -> bool:
309
+ """
310
+ Check if server is enabled.
311
+
312
+ Returns:
313
+ True if server is enabled, False otherwise
314
+ """
315
+ return self._enabled
316
+
317
+ def quarantine(self, duration: int) -> None:
318
+ """
319
+ Temporarily disable server for specified duration.
320
+
321
+ Args:
322
+ duration: Quarantine duration in seconds
323
+ """
324
+ self._quarantine_until = datetime.now() + timedelta(seconds=duration)
325
+ self._state = ServerState.QUARANTINED
326
+
327
+ def is_quarantined(self) -> bool:
328
+ """
329
+ Check if server is currently quarantined.
330
+
331
+ Returns:
332
+ True if server is quarantined, False otherwise
333
+ """
334
+ if self._quarantine_until is None:
335
+ return False
336
+
337
+ if datetime.now() >= self._quarantine_until:
338
+ # Quarantine period has expired
339
+ self._quarantine_until = None
340
+ if self._state == ServerState.QUARANTINED:
341
+ # Restore to running state if enabled
342
+ self._state = (
343
+ ServerState.RUNNING if self._enabled else ServerState.STOPPED
344
+ )
345
+ return False
346
+
347
+ return True
348
+
349
+ def get_captured_stderr(self) -> list[str]:
350
+ """
351
+ Get captured stderr output if this is a stdio server.
352
+
353
+ Returns:
354
+ List of captured stderr lines, or empty list if not applicable
355
+ """
356
+ if isinstance(self._pydantic_server, BlockingMCPServerStdio):
357
+ return self._pydantic_server.get_captured_stderr()
358
+ return []
359
+
360
+ async def wait_until_ready(self, timeout: float = 30.0) -> bool:
361
+ """
362
+ Wait until the server is ready.
363
+
364
+ Args:
365
+ timeout: Maximum time to wait in seconds
366
+
367
+ Returns:
368
+ True if server is ready, False otherwise
369
+ """
370
+ if isinstance(self._pydantic_server, BlockingMCPServerStdio):
371
+ try:
372
+ await self._pydantic_server.wait_until_ready(timeout)
373
+ return True
374
+ except Exception:
375
+ return False
376
+ # Non-stdio servers are considered ready immediately
377
+ return True
378
+
379
+ async def ensure_ready(self, timeout: float = 30.0):
380
+ """
381
+ Ensure server is ready, raising exception if not.
382
+
383
+ Args:
384
+ timeout: Maximum time to wait in seconds
385
+
386
+ Raises:
387
+ TimeoutError: If server doesn't initialize within timeout
388
+ Exception: If server initialization failed
389
+ """
390
+ if isinstance(self._pydantic_server, BlockingMCPServerStdio):
391
+ await self._pydantic_server.ensure_ready(timeout)
392
+
393
+ def get_status(self) -> Dict[str, Any]:
394
+ """
395
+ Return current status information.
396
+
397
+ Returns:
398
+ Dictionary containing comprehensive status information
399
+ """
400
+ now = datetime.now()
401
+ uptime = None
402
+ if self._start_time and self._state == ServerState.RUNNING:
403
+ uptime = (now - self._start_time).total_seconds()
404
+
405
+ quarantine_remaining = None
406
+ if self.is_quarantined():
407
+ quarantine_remaining = (self._quarantine_until - now).total_seconds()
408
+
409
+ return {
410
+ "id": self.config.id,
411
+ "name": self.config.name,
412
+ "type": self.config.type,
413
+ "state": self._state.value,
414
+ "enabled": self._enabled,
415
+ "quarantined": self.is_quarantined(),
416
+ "quarantine_remaining_seconds": quarantine_remaining,
417
+ "uptime_seconds": uptime,
418
+ "start_time": self._start_time.isoformat() if self._start_time else None,
419
+ "stop_time": self._stop_time.isoformat() if self._stop_time else None,
420
+ "error_message": self._error_message,
421
+ "config": self.config.config.copy(), # Copy to prevent modification
422
+ "server_available": (
423
+ self._pydantic_server is not None
424
+ and self._enabled
425
+ and not self.is_quarantined()
426
+ and self._state == ServerState.RUNNING
427
+ ),
428
+ }