code-puppy 0.0.135__py3-none-any.whl → 0.0.137__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/agent.py +15 -17
- code_puppy/agents/agent_manager.py +320 -9
- code_puppy/agents/base_agent.py +58 -2
- code_puppy/agents/runtime_manager.py +68 -42
- code_puppy/command_line/command_handler.py +82 -33
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/add_command.py +183 -0
- code_puppy/command_line/mcp/base.py +35 -0
- code_puppy/command_line/mcp/handler.py +133 -0
- code_puppy/command_line/mcp/help_command.py +146 -0
- code_puppy/command_line/mcp/install_command.py +176 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +126 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +92 -0
- code_puppy/command_line/mcp/search_command.py +117 -0
- code_puppy/command_line/mcp/start_all_command.py +126 -0
- code_puppy/command_line/mcp/start_command.py +98 -0
- code_puppy/command_line/mcp/status_command.py +185 -0
- code_puppy/command_line/mcp/stop_all_command.py +109 -0
- code_puppy/command_line/mcp/stop_command.py +79 -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 +259 -0
- code_puppy/command_line/model_picker_completion.py +21 -4
- code_puppy/command_line/prompt_toolkit_completion.py +9 -0
- code_puppy/config.py +5 -5
- code_puppy/main.py +23 -17
- code_puppy/mcp/__init__.py +42 -16
- code_puppy/mcp/async_lifecycle.py +51 -49
- code_puppy/mcp/blocking_startup.py +125 -113
- code_puppy/mcp/captured_stdio_server.py +63 -70
- code_puppy/mcp/circuit_breaker.py +63 -47
- code_puppy/mcp/config_wizard.py +169 -136
- code_puppy/mcp/dashboard.py +79 -71
- code_puppy/mcp/error_isolation.py +147 -100
- code_puppy/mcp/examples/retry_example.py +55 -42
- code_puppy/mcp/health_monitor.py +152 -141
- code_puppy/mcp/managed_server.py +100 -93
- code_puppy/mcp/manager.py +168 -156
- code_puppy/mcp/registry.py +148 -110
- code_puppy/mcp/retry_manager.py +63 -61
- code_puppy/mcp/server_registry_catalog.py +271 -225
- code_puppy/mcp/status_tracker.py +80 -80
- code_puppy/mcp/system_tools.py +47 -52
- code_puppy/messaging/message_queue.py +20 -13
- code_puppy/messaging/renderers.py +30 -15
- code_puppy/state_management.py +103 -0
- code_puppy/tui/app.py +64 -7
- code_puppy/tui/components/chat_view.py +3 -3
- code_puppy/tui/components/human_input_modal.py +12 -8
- code_puppy/tui/screens/__init__.py +2 -2
- code_puppy/tui/screens/mcp_install_wizard.py +208 -179
- code_puppy/tui/tests/test_agent_command.py +3 -3
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/RECORD +60 -42
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.137.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.137.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,10 +7,10 @@ within the same task, allowing servers to start and stay running.
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
-
from typing import Dict, Optional, Any, Union
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
from dataclasses import dataclass
|
|
13
10
|
from contextlib import AsyncExitStack
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Any, Dict, Optional, Union
|
|
14
14
|
|
|
15
15
|
from pydantic_ai.mcp import MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class ManagedServerContext:
|
|
22
22
|
"""Represents a managed MCP server with its async context."""
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
server_id: str
|
|
25
25
|
server: Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]
|
|
26
26
|
exit_stack: AsyncExitStack
|
|
@@ -31,32 +31,32 @@ class ManagedServerContext:
|
|
|
31
31
|
class AsyncServerLifecycleManager:
|
|
32
32
|
"""
|
|
33
33
|
Manages MCP server lifecycles asynchronously.
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
This properly maintains async contexts within the same task,
|
|
36
36
|
allowing servers to start and stay running independently of agents.
|
|
37
37
|
"""
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
def __init__(self):
|
|
40
40
|
"""Initialize the async lifecycle manager."""
|
|
41
41
|
self._servers: Dict[str, ManagedServerContext] = {}
|
|
42
42
|
self._lock = asyncio.Lock()
|
|
43
43
|
logger.info("AsyncServerLifecycleManager initialized")
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
async def start_server(
|
|
46
46
|
self,
|
|
47
47
|
server_id: str,
|
|
48
|
-
server: Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]
|
|
48
|
+
server: Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP],
|
|
49
49
|
) -> bool:
|
|
50
50
|
"""
|
|
51
51
|
Start an MCP server and maintain its context.
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
This creates a dedicated task that enters the server's context
|
|
54
54
|
and keeps it alive until explicitly stopped.
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
Args:
|
|
57
57
|
server_id: Unique identifier for the server
|
|
58
58
|
server: The pydantic-ai MCP server instance
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
Returns:
|
|
61
61
|
True if server started successfully, False otherwise
|
|
62
62
|
"""
|
|
@@ -68,18 +68,20 @@ class AsyncServerLifecycleManager:
|
|
|
68
68
|
return True
|
|
69
69
|
else:
|
|
70
70
|
# Server exists but not running, clean it up
|
|
71
|
-
logger.warning(
|
|
71
|
+
logger.warning(
|
|
72
|
+
f"Server {server_id} exists but not running, cleaning up"
|
|
73
|
+
)
|
|
72
74
|
await self._stop_server_internal(server_id)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
# Create a task that will manage this server's lifecycle
|
|
75
77
|
task = asyncio.create_task(
|
|
76
78
|
self._server_lifecycle_task(server_id, server),
|
|
77
|
-
name=f"mcp_server_{server_id}"
|
|
79
|
+
name=f"mcp_server_{server_id}",
|
|
78
80
|
)
|
|
79
|
-
|
|
81
|
+
|
|
80
82
|
# Wait briefly for the server to start
|
|
81
83
|
await asyncio.sleep(0.1)
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
# Check if task failed immediately
|
|
84
86
|
if task.done():
|
|
85
87
|
try:
|
|
@@ -87,29 +89,29 @@ class AsyncServerLifecycleManager:
|
|
|
87
89
|
except Exception as e:
|
|
88
90
|
logger.error(f"Failed to start server {server_id}: {e}")
|
|
89
91
|
return False
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
logger.info(f"Server {server_id} starting in background task")
|
|
92
94
|
return True
|
|
93
|
-
|
|
95
|
+
|
|
94
96
|
async def _server_lifecycle_task(
|
|
95
97
|
self,
|
|
96
98
|
server_id: str,
|
|
97
|
-
server: Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]
|
|
99
|
+
server: Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP],
|
|
98
100
|
) -> None:
|
|
99
101
|
"""
|
|
100
102
|
Task that manages a server's lifecycle.
|
|
101
|
-
|
|
103
|
+
|
|
102
104
|
This task enters the server's context and keeps it alive
|
|
103
105
|
until the server is stopped or an error occurs.
|
|
104
106
|
"""
|
|
105
107
|
exit_stack = AsyncExitStack()
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
try:
|
|
108
110
|
logger.info(f"Starting server lifecycle for {server_id}")
|
|
109
|
-
|
|
111
|
+
|
|
110
112
|
# Enter the server's context
|
|
111
113
|
await exit_stack.enter_async_context(server)
|
|
112
|
-
|
|
114
|
+
|
|
113
115
|
# Store the managed context
|
|
114
116
|
async with self._lock:
|
|
115
117
|
self._servers[server_id] = ManagedServerContext(
|
|
@@ -117,20 +119,20 @@ class AsyncServerLifecycleManager:
|
|
|
117
119
|
server=server,
|
|
118
120
|
exit_stack=exit_stack,
|
|
119
121
|
start_time=datetime.now(),
|
|
120
|
-
task=asyncio.current_task()
|
|
122
|
+
task=asyncio.current_task(),
|
|
121
123
|
)
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
logger.info(f"Server {server_id} started successfully")
|
|
124
|
-
|
|
126
|
+
|
|
125
127
|
# Keep the task alive until cancelled
|
|
126
128
|
while True:
|
|
127
129
|
await asyncio.sleep(1)
|
|
128
|
-
|
|
130
|
+
|
|
129
131
|
# Check if server is still running
|
|
130
132
|
if not server.is_running:
|
|
131
133
|
logger.warning(f"Server {server_id} stopped unexpectedly")
|
|
132
134
|
break
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
except asyncio.CancelledError:
|
|
135
137
|
logger.info(f"Server {server_id} lifecycle task cancelled")
|
|
136
138
|
raise
|
|
@@ -139,29 +141,29 @@ class AsyncServerLifecycleManager:
|
|
|
139
141
|
finally:
|
|
140
142
|
# Clean up the context
|
|
141
143
|
await exit_stack.aclose()
|
|
142
|
-
|
|
144
|
+
|
|
143
145
|
# Remove from managed servers
|
|
144
146
|
async with self._lock:
|
|
145
147
|
if server_id in self._servers:
|
|
146
148
|
del self._servers[server_id]
|
|
147
|
-
|
|
149
|
+
|
|
148
150
|
logger.info(f"Server {server_id} lifecycle ended")
|
|
149
|
-
|
|
151
|
+
|
|
150
152
|
async def stop_server(self, server_id: str) -> bool:
|
|
151
153
|
"""
|
|
152
154
|
Stop a running MCP server.
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
This cancels the lifecycle task, which properly exits the context.
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
Args:
|
|
157
159
|
server_id: ID of the server to stop
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
Returns:
|
|
160
162
|
True if server was stopped, False if not found
|
|
161
163
|
"""
|
|
162
164
|
async with self._lock:
|
|
163
165
|
return await self._stop_server_internal(server_id)
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
async def _stop_server_internal(self, server_id: str) -> bool:
|
|
166
168
|
"""
|
|
167
169
|
Internal method to stop a server (must be called with lock held).
|
|
@@ -169,38 +171,38 @@ class AsyncServerLifecycleManager:
|
|
|
169
171
|
if server_id not in self._servers:
|
|
170
172
|
logger.warning(f"Server {server_id} not found")
|
|
171
173
|
return False
|
|
172
|
-
|
|
174
|
+
|
|
173
175
|
context = self._servers[server_id]
|
|
174
|
-
|
|
176
|
+
|
|
175
177
|
# Cancel the lifecycle task
|
|
176
178
|
# This will cause the task to exit and clean up properly
|
|
177
179
|
context.task.cancel()
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
try:
|
|
180
182
|
await context.task
|
|
181
183
|
except asyncio.CancelledError:
|
|
182
184
|
pass # Expected
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
logger.info(f"Stopped server {server_id}")
|
|
185
187
|
return True
|
|
186
|
-
|
|
188
|
+
|
|
187
189
|
def is_running(self, server_id: str) -> bool:
|
|
188
190
|
"""
|
|
189
191
|
Check if a server is running.
|
|
190
|
-
|
|
192
|
+
|
|
191
193
|
Args:
|
|
192
194
|
server_id: ID of the server
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
Returns:
|
|
195
197
|
True if server is running, False otherwise
|
|
196
198
|
"""
|
|
197
199
|
context = self._servers.get(server_id)
|
|
198
200
|
return context.server.is_running if context else False
|
|
199
|
-
|
|
201
|
+
|
|
200
202
|
def list_servers(self) -> Dict[str, Dict[str, Any]]:
|
|
201
203
|
"""
|
|
202
204
|
List all running servers.
|
|
203
|
-
|
|
205
|
+
|
|
204
206
|
Returns:
|
|
205
207
|
Dictionary of server IDs to server info
|
|
206
208
|
"""
|
|
@@ -211,17 +213,17 @@ class AsyncServerLifecycleManager:
|
|
|
211
213
|
"type": context.server.__class__.__name__,
|
|
212
214
|
"is_running": context.server.is_running,
|
|
213
215
|
"uptime_seconds": uptime,
|
|
214
|
-
"start_time": context.start_time.isoformat()
|
|
216
|
+
"start_time": context.start_time.isoformat(),
|
|
215
217
|
}
|
|
216
218
|
return servers
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
async def stop_all(self) -> None:
|
|
219
221
|
"""Stop all running servers."""
|
|
220
222
|
server_ids = list(self._servers.keys())
|
|
221
|
-
|
|
223
|
+
|
|
222
224
|
for server_id in server_ids:
|
|
223
225
|
await self.stop_server(server_id)
|
|
224
|
-
|
|
226
|
+
|
|
225
227
|
logger.info("All MCP servers stopped")
|
|
226
228
|
|
|
227
229
|
|
|
@@ -234,4 +236,4 @@ def get_lifecycle_manager() -> AsyncServerLifecycleManager:
|
|
|
234
236
|
global _lifecycle_manager
|
|
235
237
|
if _lifecycle_manager is None:
|
|
236
238
|
_lifecycle_manager = AsyncServerLifecycleManager()
|
|
237
|
-
return _lifecycle_manager
|
|
239
|
+
return _lifecycle_manager
|