code-puppy 0.0.135__py3-none-any.whl → 0.0.136__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/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.136.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
code_puppy/mcp/managed_server.py
CHANGED
|
@@ -5,22 +5,27 @@ This module provides a managed wrapper around pydantic-ai MCP server classes
|
|
|
5
5
|
that adds management capabilities while maintaining 100% compatibility.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import asyncio
|
|
9
8
|
import json
|
|
10
9
|
import logging
|
|
11
10
|
import uuid
|
|
12
11
|
from dataclasses import dataclass, field
|
|
13
12
|
from datetime import datetime, timedelta
|
|
14
13
|
from enum import Enum
|
|
15
|
-
from typing import
|
|
14
|
+
from typing import Any, Dict, Optional, Union
|
|
15
|
+
|
|
16
16
|
import httpx
|
|
17
17
|
from pydantic_ai import RunContext
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
from pydantic_ai.mcp import (
|
|
19
|
+
CallToolFunc,
|
|
20
|
+
MCPServerSSE,
|
|
21
|
+
MCPServerStdio,
|
|
22
|
+
MCPServerStreamableHTTP,
|
|
23
|
+
ToolResult,
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
from code_puppy.http_utils import create_async_client
|
|
22
|
-
from code_puppy.messaging import emit_info
|
|
23
27
|
from code_puppy.mcp.blocking_startup import BlockingMCPServerStdio
|
|
28
|
+
from code_puppy.messaging import emit_info
|
|
24
29
|
|
|
25
30
|
# Configure logging
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
@@ -28,6 +33,7 @@ logger = logging.getLogger(__name__)
|
|
|
28
33
|
|
|
29
34
|
class ServerState(Enum):
|
|
30
35
|
"""Enumeration of possible server states."""
|
|
36
|
+
|
|
31
37
|
STOPPED = "stopped"
|
|
32
38
|
STARTING = "starting"
|
|
33
39
|
RUNNING = "running"
|
|
@@ -39,6 +45,7 @@ class ServerState(Enum):
|
|
|
39
45
|
@dataclass
|
|
40
46
|
class ServerConfig:
|
|
41
47
|
"""Configuration for an MCP server."""
|
|
48
|
+
|
|
42
49
|
id: str
|
|
43
50
|
name: str
|
|
44
51
|
type: str # "sse", "stdio", or "http"
|
|
@@ -58,45 +65,41 @@ async def process_tool_call(
|
|
|
58
65
|
f"\n[bold white on purple] MCP Tool Call - {name}[/bold white on purple]",
|
|
59
66
|
message_group=group_id,
|
|
60
67
|
)
|
|
61
|
-
emit_info(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
emit_info(
|
|
66
|
-
json.dumps(tool_args, indent=2),
|
|
67
|
-
message_group=group_id
|
|
68
|
-
)
|
|
69
|
-
return await call_tool(name, tool_args, {'deps': ctx.deps})
|
|
68
|
+
emit_info("\nArgs:", message_group=group_id)
|
|
69
|
+
emit_info(json.dumps(tool_args, indent=2), message_group=group_id)
|
|
70
|
+
return await call_tool(name, tool_args, {"deps": ctx.deps})
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
class ManagedMCPServer:
|
|
73
74
|
"""
|
|
74
75
|
Managed wrapper around pydantic-ai MCP server classes.
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
This class provides management capabilities like enable/disable,
|
|
77
78
|
quarantine, and status tracking while maintaining 100% compatibility
|
|
78
79
|
with the existing Agent interface through get_pydantic_server().
|
|
79
|
-
|
|
80
|
+
|
|
80
81
|
Example usage:
|
|
81
82
|
config = ServerConfig(
|
|
82
|
-
id="123",
|
|
83
|
-
name="test",
|
|
84
|
-
type="sse",
|
|
83
|
+
id="123",
|
|
84
|
+
name="test",
|
|
85
|
+
type="sse",
|
|
85
86
|
config={"url": "http://localhost:8080"}
|
|
86
87
|
)
|
|
87
88
|
managed = ManagedMCPServer(config)
|
|
88
89
|
pydantic_server = managed.get_pydantic_server() # Returns actual MCPServerSSE
|
|
89
90
|
"""
|
|
90
|
-
|
|
91
|
+
|
|
91
92
|
def __init__(self, server_config: ServerConfig):
|
|
92
93
|
"""
|
|
93
94
|
Initialize managed server with configuration.
|
|
94
|
-
|
|
95
|
+
|
|
95
96
|
Args:
|
|
96
97
|
server_config: Server configuration containing type, connection details, etc.
|
|
97
98
|
"""
|
|
98
99
|
self.config = server_config
|
|
99
|
-
self._pydantic_server: Optional[
|
|
100
|
+
self._pydantic_server: Optional[
|
|
101
|
+
Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]
|
|
102
|
+
] = None
|
|
100
103
|
self._state = ServerState.STOPPED
|
|
101
104
|
# Always start disabled - servers must be explicitly started with /mcp start
|
|
102
105
|
self._enabled = False
|
|
@@ -104,7 +107,7 @@ class ManagedMCPServer:
|
|
|
104
107
|
self._start_time: Optional[datetime] = None
|
|
105
108
|
self._stop_time: Optional[datetime] = None
|
|
106
109
|
self._error_message: Optional[str] = None
|
|
107
|
-
|
|
110
|
+
|
|
108
111
|
# Initialize the pydantic server
|
|
109
112
|
try:
|
|
110
113
|
self._create_server()
|
|
@@ -114,49 +117,51 @@ class ManagedMCPServer:
|
|
|
114
117
|
logger.error(f"Failed to create server {self.config.name}: {e}")
|
|
115
118
|
self._state = ServerState.ERROR
|
|
116
119
|
self._error_message = str(e)
|
|
117
|
-
|
|
118
|
-
def get_pydantic_server(
|
|
120
|
+
|
|
121
|
+
def get_pydantic_server(
|
|
122
|
+
self,
|
|
123
|
+
) -> Union[MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP]:
|
|
119
124
|
"""
|
|
120
125
|
Get the actual pydantic-ai server instance.
|
|
121
|
-
|
|
126
|
+
|
|
122
127
|
This method returns the real pydantic-ai MCP server objects for 100% compatibility
|
|
123
128
|
with the existing Agent interface. Do not return custom classes or proxies.
|
|
124
|
-
|
|
129
|
+
|
|
125
130
|
Returns:
|
|
126
131
|
Actual pydantic-ai MCP server instance (MCPServerSSE, MCPServerStdio, or MCPServerStreamableHTTP)
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
Raises:
|
|
129
134
|
RuntimeError: If server creation failed or server is not available
|
|
130
135
|
"""
|
|
131
136
|
if self._pydantic_server is None:
|
|
132
137
|
raise RuntimeError(f"Server {self.config.name} is not available")
|
|
133
|
-
|
|
138
|
+
|
|
134
139
|
if not self.is_enabled() or self.is_quarantined():
|
|
135
140
|
raise RuntimeError(f"Server {self.config.name} is disabled or quarantined")
|
|
136
|
-
|
|
141
|
+
|
|
137
142
|
return self._pydantic_server
|
|
138
|
-
|
|
143
|
+
|
|
139
144
|
def _create_server(self) -> None:
|
|
140
145
|
"""
|
|
141
146
|
Create appropriate pydantic-ai server based on config type.
|
|
142
|
-
|
|
147
|
+
|
|
143
148
|
Raises:
|
|
144
149
|
ValueError: If server type is unsupported or config is invalid
|
|
145
150
|
Exception: If server creation fails
|
|
146
151
|
"""
|
|
147
152
|
server_type = self.config.type.lower()
|
|
148
153
|
config = self.config.config
|
|
149
|
-
|
|
154
|
+
|
|
150
155
|
try:
|
|
151
156
|
if server_type == "sse":
|
|
152
157
|
if "url" not in config:
|
|
153
158
|
raise ValueError("SSE server requires 'url' in config")
|
|
154
|
-
|
|
159
|
+
|
|
155
160
|
# Prepare arguments for MCPServerSSE
|
|
156
161
|
sse_kwargs = {
|
|
157
162
|
"url": config["url"],
|
|
158
163
|
}
|
|
159
|
-
|
|
164
|
+
|
|
160
165
|
# Add optional parameters if provided
|
|
161
166
|
if "timeout" in config:
|
|
162
167
|
sse_kwargs["timeout"] = config["timeout"]
|
|
@@ -167,26 +172,25 @@ class ManagedMCPServer:
|
|
|
167
172
|
elif config.get("headers"):
|
|
168
173
|
# Create HTTP client if headers are provided but no client specified
|
|
169
174
|
sse_kwargs["http_client"] = self._get_http_client()
|
|
170
|
-
|
|
171
|
-
self._pydantic_server = MCPServerSSE(
|
|
172
|
-
|
|
175
|
+
|
|
176
|
+
self._pydantic_server = MCPServerSSE(
|
|
177
|
+
**sse_kwargs, process_tool_call=process_tool_call
|
|
178
|
+
)
|
|
179
|
+
|
|
173
180
|
elif server_type == "stdio":
|
|
174
181
|
if "command" not in config:
|
|
175
182
|
raise ValueError("Stdio server requires 'command' in config")
|
|
176
|
-
|
|
183
|
+
|
|
177
184
|
# Handle command and arguments
|
|
178
185
|
command = config["command"]
|
|
179
186
|
args = config.get("args", [])
|
|
180
187
|
if isinstance(args, str):
|
|
181
188
|
# If args is a string, split it
|
|
182
189
|
args = args.split()
|
|
183
|
-
|
|
190
|
+
|
|
184
191
|
# Prepare arguments for MCPServerStdio
|
|
185
|
-
stdio_kwargs = {
|
|
186
|
-
|
|
187
|
-
"args": list(args) if args else []
|
|
188
|
-
}
|
|
189
|
-
|
|
192
|
+
stdio_kwargs = {"command": command, "args": list(args) if args else []}
|
|
193
|
+
|
|
190
194
|
# Add optional parameters if provided
|
|
191
195
|
if "env" in config:
|
|
192
196
|
stdio_kwargs["env"] = config["env"]
|
|
@@ -196,27 +200,27 @@ class ManagedMCPServer:
|
|
|
196
200
|
stdio_kwargs["timeout"] = config["timeout"]
|
|
197
201
|
if "read_timeout" in config:
|
|
198
202
|
stdio_kwargs["read_timeout"] = config["read_timeout"]
|
|
199
|
-
|
|
203
|
+
|
|
200
204
|
# Use BlockingMCPServerStdio for proper initialization blocking and stderr capture
|
|
201
205
|
# Create a unique message group for this server
|
|
202
206
|
message_group = uuid.uuid4()
|
|
203
207
|
self._pydantic_server = BlockingMCPServerStdio(
|
|
204
|
-
**stdio_kwargs,
|
|
205
|
-
process_tool_call=process_tool_call,
|
|
208
|
+
**stdio_kwargs,
|
|
209
|
+
process_tool_call=process_tool_call,
|
|
206
210
|
tool_prefix=config["name"],
|
|
207
211
|
emit_stderr=True, # Always emit stderr for now
|
|
208
|
-
message_group=message_group
|
|
212
|
+
message_group=message_group,
|
|
209
213
|
)
|
|
210
|
-
|
|
214
|
+
|
|
211
215
|
elif server_type == "http":
|
|
212
216
|
if "url" not in config:
|
|
213
217
|
raise ValueError("HTTP server requires 'url' in config")
|
|
214
|
-
|
|
218
|
+
|
|
215
219
|
# Prepare arguments for MCPServerStreamableHTTP
|
|
216
220
|
http_kwargs = {
|
|
217
221
|
"url": config["url"],
|
|
218
222
|
}
|
|
219
|
-
|
|
223
|
+
|
|
220
224
|
# Add optional parameters if provided
|
|
221
225
|
if "timeout" in config:
|
|
222
226
|
http_kwargs["timeout"] = config["timeout"]
|
|
@@ -227,33 +231,34 @@ class ManagedMCPServer:
|
|
|
227
231
|
elif config.get("headers"):
|
|
228
232
|
# Create HTTP client if headers are provided but no client specified
|
|
229
233
|
http_kwargs["http_client"] = self._get_http_client()
|
|
230
|
-
|
|
231
|
-
self._pydantic_server = MCPServerStreamableHTTP(
|
|
232
|
-
|
|
234
|
+
|
|
235
|
+
self._pydantic_server = MCPServerStreamableHTTP(
|
|
236
|
+
**http_kwargs, process_tool_call=process_tool_call
|
|
237
|
+
)
|
|
238
|
+
|
|
233
239
|
else:
|
|
234
240
|
raise ValueError(f"Unsupported server type: {server_type}")
|
|
235
|
-
|
|
241
|
+
|
|
236
242
|
logger.info(f"Created {server_type} server: {self.config.name}")
|
|
237
|
-
|
|
243
|
+
|
|
238
244
|
except Exception as e:
|
|
239
|
-
logger.error(
|
|
245
|
+
logger.error(
|
|
246
|
+
f"Failed to create {server_type} server {self.config.name}: {e}"
|
|
247
|
+
)
|
|
240
248
|
raise
|
|
241
|
-
|
|
249
|
+
|
|
242
250
|
def _get_http_client(self) -> httpx.AsyncClient:
|
|
243
251
|
"""
|
|
244
252
|
Create httpx.AsyncClient with headers from config.
|
|
245
|
-
|
|
253
|
+
|
|
246
254
|
Returns:
|
|
247
255
|
Configured async HTTP client with custom headers
|
|
248
256
|
"""
|
|
249
257
|
headers = self.config.config.get("headers", {})
|
|
250
258
|
timeout = self.config.config.get("timeout", 30)
|
|
251
|
-
client = create_async_client(
|
|
252
|
-
headers=headers,
|
|
253
|
-
timeout=timeout
|
|
254
|
-
)
|
|
259
|
+
client = create_async_client(headers=headers, timeout=timeout)
|
|
255
260
|
return client
|
|
256
|
-
|
|
261
|
+
|
|
257
262
|
def enable(self) -> None:
|
|
258
263
|
"""Enable server availability."""
|
|
259
264
|
self._enabled = True
|
|
@@ -261,7 +266,7 @@ class ManagedMCPServer:
|
|
|
261
266
|
self._state = ServerState.RUNNING
|
|
262
267
|
self._start_time = datetime.now()
|
|
263
268
|
logger.info(f"Enabled server: {self.config.name}")
|
|
264
|
-
|
|
269
|
+
|
|
265
270
|
def disable(self) -> None:
|
|
266
271
|
"""Disable server availability."""
|
|
267
272
|
self._enabled = False
|
|
@@ -269,20 +274,20 @@ class ManagedMCPServer:
|
|
|
269
274
|
self._state = ServerState.STOPPED
|
|
270
275
|
self._stop_time = datetime.now()
|
|
271
276
|
logger.info(f"Disabled server: {self.config.name}")
|
|
272
|
-
|
|
277
|
+
|
|
273
278
|
def is_enabled(self) -> bool:
|
|
274
279
|
"""
|
|
275
280
|
Check if server is enabled.
|
|
276
|
-
|
|
281
|
+
|
|
277
282
|
Returns:
|
|
278
283
|
True if server is enabled, False otherwise
|
|
279
284
|
"""
|
|
280
285
|
return self._enabled
|
|
281
|
-
|
|
286
|
+
|
|
282
287
|
def quarantine(self, duration: int) -> None:
|
|
283
288
|
"""
|
|
284
289
|
Temporarily disable server for specified duration.
|
|
285
|
-
|
|
290
|
+
|
|
286
291
|
Args:
|
|
287
292
|
duration: Quarantine duration in seconds
|
|
288
293
|
"""
|
|
@@ -293,46 +298,48 @@ class ManagedMCPServer:
|
|
|
293
298
|
f"Quarantined server {self.config.name} for {duration} seconds "
|
|
294
299
|
f"(was {previous_state.value})"
|
|
295
300
|
)
|
|
296
|
-
|
|
301
|
+
|
|
297
302
|
def is_quarantined(self) -> bool:
|
|
298
303
|
"""
|
|
299
304
|
Check if server is currently quarantined.
|
|
300
|
-
|
|
305
|
+
|
|
301
306
|
Returns:
|
|
302
307
|
True if server is quarantined, False otherwise
|
|
303
308
|
"""
|
|
304
309
|
if self._quarantine_until is None:
|
|
305
310
|
return False
|
|
306
|
-
|
|
311
|
+
|
|
307
312
|
if datetime.now() >= self._quarantine_until:
|
|
308
313
|
# Quarantine period has expired
|
|
309
314
|
self._quarantine_until = None
|
|
310
315
|
if self._state == ServerState.QUARANTINED:
|
|
311
316
|
# Restore to running state if enabled
|
|
312
|
-
self._state =
|
|
317
|
+
self._state = (
|
|
318
|
+
ServerState.RUNNING if self._enabled else ServerState.STOPPED
|
|
319
|
+
)
|
|
313
320
|
logger.info(f"Released quarantine for server: {self.config.name}")
|
|
314
321
|
return False
|
|
315
|
-
|
|
322
|
+
|
|
316
323
|
return True
|
|
317
|
-
|
|
324
|
+
|
|
318
325
|
def get_captured_stderr(self) -> list[str]:
|
|
319
326
|
"""
|
|
320
327
|
Get captured stderr output if this is a stdio server.
|
|
321
|
-
|
|
328
|
+
|
|
322
329
|
Returns:
|
|
323
330
|
List of captured stderr lines, or empty list if not applicable
|
|
324
331
|
"""
|
|
325
332
|
if isinstance(self._pydantic_server, BlockingMCPServerStdio):
|
|
326
333
|
return self._pydantic_server.get_captured_stderr()
|
|
327
334
|
return []
|
|
328
|
-
|
|
335
|
+
|
|
329
336
|
async def wait_until_ready(self, timeout: float = 30.0) -> bool:
|
|
330
337
|
"""
|
|
331
338
|
Wait until the server is ready.
|
|
332
|
-
|
|
339
|
+
|
|
333
340
|
Args:
|
|
334
341
|
timeout: Maximum time to wait in seconds
|
|
335
|
-
|
|
342
|
+
|
|
336
343
|
Returns:
|
|
337
344
|
True if server is ready, False otherwise
|
|
338
345
|
"""
|
|
@@ -344,25 +351,25 @@ class ManagedMCPServer:
|
|
|
344
351
|
return False
|
|
345
352
|
# Non-stdio servers are considered ready immediately
|
|
346
353
|
return True
|
|
347
|
-
|
|
354
|
+
|
|
348
355
|
async def ensure_ready(self, timeout: float = 30.0):
|
|
349
356
|
"""
|
|
350
357
|
Ensure server is ready, raising exception if not.
|
|
351
|
-
|
|
358
|
+
|
|
352
359
|
Args:
|
|
353
360
|
timeout: Maximum time to wait in seconds
|
|
354
|
-
|
|
361
|
+
|
|
355
362
|
Raises:
|
|
356
363
|
TimeoutError: If server doesn't initialize within timeout
|
|
357
364
|
Exception: If server initialization failed
|
|
358
365
|
"""
|
|
359
366
|
if isinstance(self._pydantic_server, BlockingMCPServerStdio):
|
|
360
367
|
await self._pydantic_server.ensure_ready(timeout)
|
|
361
|
-
|
|
368
|
+
|
|
362
369
|
def get_status(self) -> Dict[str, Any]:
|
|
363
370
|
"""
|
|
364
371
|
Return current status information.
|
|
365
|
-
|
|
372
|
+
|
|
366
373
|
Returns:
|
|
367
374
|
Dictionary containing comprehensive status information
|
|
368
375
|
"""
|
|
@@ -370,11 +377,11 @@ class ManagedMCPServer:
|
|
|
370
377
|
uptime = None
|
|
371
378
|
if self._start_time and self._state == ServerState.RUNNING:
|
|
372
379
|
uptime = (now - self._start_time).total_seconds()
|
|
373
|
-
|
|
380
|
+
|
|
374
381
|
quarantine_remaining = None
|
|
375
382
|
if self.is_quarantined():
|
|
376
383
|
quarantine_remaining = (self._quarantine_until - now).total_seconds()
|
|
377
|
-
|
|
384
|
+
|
|
378
385
|
return {
|
|
379
386
|
"id": self.config.id,
|
|
380
387
|
"name": self.config.name,
|
|
@@ -389,9 +396,9 @@ class ManagedMCPServer:
|
|
|
389
396
|
"error_message": self._error_message,
|
|
390
397
|
"config": self.config.config.copy(), # Copy to prevent modification
|
|
391
398
|
"server_available": (
|
|
392
|
-
self._pydantic_server is not None
|
|
393
|
-
self._enabled
|
|
394
|
-
not self.is_quarantined()
|
|
395
|
-
self._state == ServerState.RUNNING
|
|
396
|
-
)
|
|
397
|
-
}
|
|
399
|
+
self._pydantic_server is not None
|
|
400
|
+
and self._enabled
|
|
401
|
+
and not self.is_quarantined()
|
|
402
|
+
and self._state == ServerState.RUNNING
|
|
403
|
+
),
|
|
404
|
+
}
|