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
|
@@ -8,12 +8,10 @@ logic with exponential backoff for failed servers.
|
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import logging
|
|
11
|
-
from datetime import datetime, timedelta
|
|
12
11
|
from dataclasses import dataclass, field
|
|
13
|
-
from
|
|
12
|
+
from datetime import datetime, timedelta
|
|
14
13
|
from enum import Enum
|
|
15
|
-
import
|
|
16
|
-
|
|
14
|
+
from typing import Any, Callable, Dict, Optional
|
|
17
15
|
|
|
18
16
|
logger = logging.getLogger(__name__)
|
|
19
17
|
|
|
@@ -21,6 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
21
19
|
@dataclass
|
|
22
20
|
class ErrorStats:
|
|
23
21
|
"""Statistics for MCP server errors and quarantine status."""
|
|
22
|
+
|
|
24
23
|
total_errors: int = 0
|
|
25
24
|
consecutive_errors: int = 0
|
|
26
25
|
last_error: Optional[datetime] = None
|
|
@@ -31,6 +30,7 @@ class ErrorStats:
|
|
|
31
30
|
|
|
32
31
|
class ErrorCategory(Enum):
|
|
33
32
|
"""Categories of errors that can be isolated."""
|
|
33
|
+
|
|
34
34
|
NETWORK = "network"
|
|
35
35
|
PROTOCOL = "protocol"
|
|
36
36
|
SERVER = "server"
|
|
@@ -42,18 +42,18 @@ class ErrorCategory(Enum):
|
|
|
42
42
|
class MCPErrorIsolator:
|
|
43
43
|
"""
|
|
44
44
|
Isolates MCP server errors to prevent application crashes.
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
Features:
|
|
47
47
|
- Quarantine servers after consecutive failures
|
|
48
48
|
- Exponential backoff for quarantine duration
|
|
49
49
|
- Error categorization and tracking
|
|
50
50
|
- Automatic recovery after successful calls
|
|
51
51
|
"""
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def __init__(self, quarantine_threshold: int = 5, max_quarantine_minutes: int = 30):
|
|
54
54
|
"""
|
|
55
55
|
Initialize the error isolator.
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
Args:
|
|
58
58
|
quarantine_threshold: Number of consecutive errors to trigger quarantine
|
|
59
59
|
max_quarantine_minutes: Maximum quarantine duration in minutes
|
|
@@ -62,25 +62,27 @@ class MCPErrorIsolator:
|
|
|
62
62
|
self.max_quarantine_duration = timedelta(minutes=max_quarantine_minutes)
|
|
63
63
|
self.server_stats: Dict[str, ErrorStats] = {}
|
|
64
64
|
self._lock = asyncio.Lock()
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
logger.info(
|
|
67
67
|
f"MCPErrorIsolator initialized with threshold={quarantine_threshold}, "
|
|
68
68
|
f"max_quarantine={max_quarantine_minutes}min"
|
|
69
69
|
)
|
|
70
|
-
|
|
71
|
-
async def isolated_call(
|
|
70
|
+
|
|
71
|
+
async def isolated_call(
|
|
72
|
+
self, server_id: str, func: Callable, *args, **kwargs
|
|
73
|
+
) -> Any:
|
|
72
74
|
"""
|
|
73
75
|
Execute a function call with error isolation.
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
Args:
|
|
76
78
|
server_id: ID of the MCP server making the call
|
|
77
79
|
func: Function to execute
|
|
78
80
|
*args: Arguments for the function
|
|
79
81
|
**kwargs: Keyword arguments for the function
|
|
80
|
-
|
|
82
|
+
|
|
81
83
|
Returns:
|
|
82
84
|
Result of the function call
|
|
83
|
-
|
|
85
|
+
|
|
84
86
|
Raises:
|
|
85
87
|
Exception: If the server is quarantined or the call fails
|
|
86
88
|
"""
|
|
@@ -91,32 +93,32 @@ class MCPErrorIsolator:
|
|
|
91
93
|
raise QuarantinedServerError(
|
|
92
94
|
f"Server {server_id} is quarantined until {quarantine_until}"
|
|
93
95
|
)
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
try:
|
|
96
98
|
# Execute the function
|
|
97
99
|
if asyncio.iscoroutinefunction(func):
|
|
98
100
|
result = await func(*args, **kwargs)
|
|
99
101
|
else:
|
|
100
102
|
result = func(*args, **kwargs)
|
|
101
|
-
|
|
103
|
+
|
|
102
104
|
# Record success
|
|
103
105
|
async with self._lock:
|
|
104
106
|
await self._record_success(server_id)
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
return result
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
except Exception as error:
|
|
109
111
|
# Record and categorize the error
|
|
110
112
|
async with self._lock:
|
|
111
113
|
await self._record_error(server_id, error)
|
|
112
|
-
|
|
114
|
+
|
|
113
115
|
# Re-raise the error
|
|
114
116
|
raise
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
async def quarantine_server(self, server_id: str, duration: int) -> None:
|
|
117
119
|
"""
|
|
118
120
|
Manually quarantine a server for a specific duration.
|
|
119
|
-
|
|
121
|
+
|
|
120
122
|
Args:
|
|
121
123
|
server_id: ID of the server to quarantine
|
|
122
124
|
duration: Quarantine duration in seconds
|
|
@@ -125,40 +127,40 @@ class MCPErrorIsolator:
|
|
|
125
127
|
stats = self._get_or_create_stats(server_id)
|
|
126
128
|
stats.quarantine_until = datetime.now() + timedelta(seconds=duration)
|
|
127
129
|
stats.quarantine_count += 1
|
|
128
|
-
|
|
130
|
+
|
|
129
131
|
logger.warning(
|
|
130
132
|
f"Server {server_id} quarantined for {duration}s "
|
|
131
133
|
f"(count: {stats.quarantine_count})"
|
|
132
134
|
)
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
def is_quarantined(self, server_id: str) -> bool:
|
|
135
137
|
"""
|
|
136
138
|
Check if a server is currently quarantined.
|
|
137
|
-
|
|
139
|
+
|
|
138
140
|
Args:
|
|
139
141
|
server_id: ID of the server to check
|
|
140
|
-
|
|
142
|
+
|
|
141
143
|
Returns:
|
|
142
144
|
True if the server is quarantined, False otherwise
|
|
143
145
|
"""
|
|
144
146
|
if server_id not in self.server_stats:
|
|
145
147
|
return False
|
|
146
|
-
|
|
148
|
+
|
|
147
149
|
stats = self.server_stats[server_id]
|
|
148
150
|
if stats.quarantine_until is None:
|
|
149
151
|
return False
|
|
150
|
-
|
|
152
|
+
|
|
151
153
|
# Check if quarantine has expired
|
|
152
154
|
if datetime.now() >= stats.quarantine_until:
|
|
153
155
|
stats.quarantine_until = None
|
|
154
156
|
return False
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
return True
|
|
157
|
-
|
|
159
|
+
|
|
158
160
|
async def release_quarantine(self, server_id: str) -> None:
|
|
159
161
|
"""
|
|
160
162
|
Manually release a server from quarantine.
|
|
161
|
-
|
|
163
|
+
|
|
162
164
|
Args:
|
|
163
165
|
server_id: ID of the server to release
|
|
164
166
|
"""
|
|
@@ -166,180 +168,225 @@ class MCPErrorIsolator:
|
|
|
166
168
|
if server_id in self.server_stats:
|
|
167
169
|
self.server_stats[server_id].quarantine_until = None
|
|
168
170
|
logger.info(f"Server {server_id} released from quarantine")
|
|
169
|
-
|
|
171
|
+
|
|
170
172
|
def get_error_stats(self, server_id: str) -> ErrorStats:
|
|
171
173
|
"""
|
|
172
174
|
Get error statistics for a server.
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
Args:
|
|
175
177
|
server_id: ID of the server
|
|
176
|
-
|
|
178
|
+
|
|
177
179
|
Returns:
|
|
178
180
|
ErrorStats object with current statistics
|
|
179
181
|
"""
|
|
180
182
|
if server_id not in self.server_stats:
|
|
181
183
|
return ErrorStats()
|
|
182
|
-
|
|
184
|
+
|
|
183
185
|
return self.server_stats[server_id]
|
|
184
|
-
|
|
186
|
+
|
|
185
187
|
def should_quarantine(self, server_id: str) -> bool:
|
|
186
188
|
"""
|
|
187
189
|
Check if a server should be quarantined based on error count.
|
|
188
|
-
|
|
190
|
+
|
|
189
191
|
Args:
|
|
190
192
|
server_id: ID of the server to check
|
|
191
|
-
|
|
193
|
+
|
|
192
194
|
Returns:
|
|
193
195
|
True if the server should be quarantined
|
|
194
196
|
"""
|
|
195
197
|
if server_id not in self.server_stats:
|
|
196
198
|
return False
|
|
197
|
-
|
|
199
|
+
|
|
198
200
|
stats = self.server_stats[server_id]
|
|
199
201
|
return stats.consecutive_errors >= self.quarantine_threshold
|
|
200
|
-
|
|
202
|
+
|
|
201
203
|
def _get_or_create_stats(self, server_id: str) -> ErrorStats:
|
|
202
204
|
"""Get or create error stats for a server."""
|
|
203
205
|
if server_id not in self.server_stats:
|
|
204
206
|
self.server_stats[server_id] = ErrorStats()
|
|
205
207
|
return self.server_stats[server_id]
|
|
206
|
-
|
|
208
|
+
|
|
207
209
|
async def _record_success(self, server_id: str) -> None:
|
|
208
210
|
"""Record a successful call and reset consecutive error count."""
|
|
209
211
|
stats = self._get_or_create_stats(server_id)
|
|
210
212
|
stats.consecutive_errors = 0
|
|
211
|
-
|
|
212
|
-
logger.debug(
|
|
213
|
-
|
|
213
|
+
|
|
214
|
+
logger.debug(
|
|
215
|
+
f"Success recorded for server {server_id}, consecutive errors reset"
|
|
216
|
+
)
|
|
217
|
+
|
|
214
218
|
async def _record_error(self, server_id: str, error: Exception) -> None:
|
|
215
219
|
"""Record an error and potentially quarantine the server."""
|
|
216
220
|
stats = self._get_or_create_stats(server_id)
|
|
217
|
-
|
|
221
|
+
|
|
218
222
|
# Update error statistics
|
|
219
223
|
stats.total_errors += 1
|
|
220
224
|
stats.consecutive_errors += 1
|
|
221
225
|
stats.last_error = datetime.now()
|
|
222
|
-
|
|
226
|
+
|
|
223
227
|
# Categorize the error
|
|
224
228
|
error_category = self._categorize_error(error)
|
|
225
229
|
error_type = error_category.value
|
|
226
230
|
stats.error_types[error_type] = stats.error_types.get(error_type, 0) + 1
|
|
227
|
-
|
|
231
|
+
|
|
228
232
|
logger.warning(
|
|
229
233
|
f"Error recorded for server {server_id}: {error_type} - {str(error)} "
|
|
230
234
|
f"(consecutive: {stats.consecutive_errors})"
|
|
231
235
|
)
|
|
232
|
-
|
|
236
|
+
|
|
233
237
|
# Check if quarantine is needed
|
|
234
238
|
if self.should_quarantine(server_id):
|
|
235
|
-
quarantine_duration = self._calculate_quarantine_duration(
|
|
236
|
-
|
|
239
|
+
quarantine_duration = self._calculate_quarantine_duration(
|
|
240
|
+
stats.quarantine_count
|
|
241
|
+
)
|
|
242
|
+
stats.quarantine_until = datetime.now() + timedelta(
|
|
243
|
+
seconds=quarantine_duration
|
|
244
|
+
)
|
|
237
245
|
stats.quarantine_count += 1
|
|
238
|
-
|
|
246
|
+
|
|
239
247
|
logger.error(
|
|
240
248
|
f"Server {server_id} quarantined for {quarantine_duration}s "
|
|
241
249
|
f"after {stats.consecutive_errors} consecutive errors "
|
|
242
250
|
f"(quarantine count: {stats.quarantine_count})"
|
|
243
251
|
)
|
|
244
|
-
|
|
252
|
+
|
|
245
253
|
def _categorize_error(self, error: Exception) -> ErrorCategory:
|
|
246
254
|
"""
|
|
247
255
|
Categorize an error based on its type and properties.
|
|
248
|
-
|
|
256
|
+
|
|
249
257
|
Args:
|
|
250
258
|
error: The exception to categorize
|
|
251
|
-
|
|
259
|
+
|
|
252
260
|
Returns:
|
|
253
261
|
ErrorCategory enum value
|
|
254
262
|
"""
|
|
255
263
|
error_type = type(error).__name__.lower()
|
|
256
264
|
error_message = str(error).lower()
|
|
257
|
-
|
|
265
|
+
|
|
258
266
|
# Network errors
|
|
259
|
-
if any(
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
if any(
|
|
268
|
+
keyword in error_type
|
|
269
|
+
for keyword in ["connection", "timeout", "network", "socket", "dns", "ssl"]
|
|
270
|
+
):
|
|
262
271
|
return ErrorCategory.NETWORK
|
|
263
|
-
|
|
264
|
-
if any(
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
|
|
273
|
+
if any(
|
|
274
|
+
keyword in error_message
|
|
275
|
+
for keyword in [
|
|
276
|
+
"connection",
|
|
277
|
+
"timeout",
|
|
278
|
+
"network",
|
|
279
|
+
"unreachable",
|
|
280
|
+
"refused",
|
|
281
|
+
]
|
|
282
|
+
):
|
|
267
283
|
return ErrorCategory.NETWORK
|
|
268
|
-
|
|
284
|
+
|
|
269
285
|
# Protocol errors
|
|
270
|
-
if any(
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
if any(
|
|
287
|
+
keyword in error_type
|
|
288
|
+
for keyword in [
|
|
289
|
+
"json",
|
|
290
|
+
"decode",
|
|
291
|
+
"parse",
|
|
292
|
+
"schema",
|
|
293
|
+
"validation",
|
|
294
|
+
"protocol",
|
|
295
|
+
]
|
|
296
|
+
):
|
|
273
297
|
return ErrorCategory.PROTOCOL
|
|
274
|
-
|
|
275
|
-
if any(
|
|
276
|
-
|
|
277
|
-
|
|
298
|
+
|
|
299
|
+
if any(
|
|
300
|
+
keyword in error_message
|
|
301
|
+
for keyword in ["json", "decode", "parse", "invalid", "malformed", "schema"]
|
|
302
|
+
):
|
|
278
303
|
return ErrorCategory.PROTOCOL
|
|
279
|
-
|
|
304
|
+
|
|
280
305
|
# Authentication errors
|
|
281
|
-
if any(
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
if any(
|
|
307
|
+
keyword in error_type
|
|
308
|
+
for keyword in ["auth", "permission", "unauthorized", "forbidden"]
|
|
309
|
+
):
|
|
284
310
|
return ErrorCategory.AUTHENTICATION
|
|
285
|
-
|
|
286
|
-
if any(
|
|
287
|
-
|
|
288
|
-
|
|
311
|
+
|
|
312
|
+
if any(
|
|
313
|
+
keyword in error_message
|
|
314
|
+
for keyword in [
|
|
315
|
+
"401",
|
|
316
|
+
"403",
|
|
317
|
+
"unauthorized",
|
|
318
|
+
"forbidden",
|
|
319
|
+
"authentication",
|
|
320
|
+
"permission",
|
|
321
|
+
]
|
|
322
|
+
):
|
|
289
323
|
return ErrorCategory.AUTHENTICATION
|
|
290
|
-
|
|
324
|
+
|
|
291
325
|
# Rate limit errors
|
|
292
|
-
if any(keyword in error_type for keyword in [
|
|
326
|
+
if any(keyword in error_type for keyword in ["rate", "limit", "throttle"]):
|
|
293
327
|
return ErrorCategory.RATE_LIMIT
|
|
294
|
-
|
|
295
|
-
if any(
|
|
296
|
-
|
|
297
|
-
|
|
328
|
+
|
|
329
|
+
if any(
|
|
330
|
+
keyword in error_message
|
|
331
|
+
for keyword in ["429", "rate limit", "too many requests", "throttle"]
|
|
332
|
+
):
|
|
298
333
|
return ErrorCategory.RATE_LIMIT
|
|
299
|
-
|
|
334
|
+
|
|
300
335
|
# Server errors (5xx responses)
|
|
301
|
-
if any(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
336
|
+
if any(
|
|
337
|
+
keyword in error_message
|
|
338
|
+
for keyword in [
|
|
339
|
+
"500",
|
|
340
|
+
"501",
|
|
341
|
+
"502",
|
|
342
|
+
"503",
|
|
343
|
+
"504",
|
|
344
|
+
"505",
|
|
345
|
+
"internal server error",
|
|
346
|
+
"bad gateway",
|
|
347
|
+
"service unavailable",
|
|
348
|
+
"gateway timeout",
|
|
349
|
+
]
|
|
350
|
+
):
|
|
305
351
|
return ErrorCategory.SERVER
|
|
306
|
-
|
|
307
|
-
if any(keyword in error_type for keyword in [
|
|
352
|
+
|
|
353
|
+
if any(keyword in error_type for keyword in ["server", "internal"]):
|
|
308
354
|
return ErrorCategory.SERVER
|
|
309
|
-
|
|
355
|
+
|
|
310
356
|
# Default to unknown
|
|
311
357
|
return ErrorCategory.UNKNOWN
|
|
312
|
-
|
|
358
|
+
|
|
313
359
|
def _calculate_quarantine_duration(self, quarantine_count: int) -> int:
|
|
314
360
|
"""
|
|
315
361
|
Calculate quarantine duration using exponential backoff.
|
|
316
|
-
|
|
362
|
+
|
|
317
363
|
Args:
|
|
318
364
|
quarantine_count: Number of times this server has been quarantined
|
|
319
|
-
|
|
365
|
+
|
|
320
366
|
Returns:
|
|
321
367
|
Quarantine duration in seconds
|
|
322
368
|
"""
|
|
323
369
|
# Base duration: 30 seconds
|
|
324
370
|
base_duration = 30
|
|
325
|
-
|
|
371
|
+
|
|
326
372
|
# Exponential backoff: 30s, 60s, 120s, 240s, etc.
|
|
327
|
-
duration = base_duration * (2
|
|
328
|
-
|
|
373
|
+
duration = base_duration * (2**quarantine_count)
|
|
374
|
+
|
|
329
375
|
# Cap at maximum duration (convert to seconds)
|
|
330
376
|
max_seconds = int(self.max_quarantine_duration.total_seconds())
|
|
331
377
|
duration = min(duration, max_seconds)
|
|
332
|
-
|
|
378
|
+
|
|
333
379
|
logger.debug(
|
|
334
380
|
f"Calculated quarantine duration: {duration}s "
|
|
335
381
|
f"(count: {quarantine_count}, max: {max_seconds}s)"
|
|
336
382
|
)
|
|
337
|
-
|
|
383
|
+
|
|
338
384
|
return duration
|
|
339
385
|
|
|
340
386
|
|
|
341
387
|
class QuarantinedServerError(Exception):
|
|
342
388
|
"""Raised when attempting to call a quarantined server."""
|
|
389
|
+
|
|
343
390
|
pass
|
|
344
391
|
|
|
345
392
|
|
|
@@ -350,11 +397,11 @@ _isolator_instance: Optional[MCPErrorIsolator] = None
|
|
|
350
397
|
def get_error_isolator() -> MCPErrorIsolator:
|
|
351
398
|
"""
|
|
352
399
|
Get the global MCPErrorIsolator instance.
|
|
353
|
-
|
|
400
|
+
|
|
354
401
|
Returns:
|
|
355
402
|
MCPErrorIsolator instance
|
|
356
403
|
"""
|
|
357
404
|
global _isolator_instance
|
|
358
405
|
if _isolator_instance is None:
|
|
359
406
|
_isolator_instance = MCPErrorIsolator()
|
|
360
|
-
return _isolator_instance
|
|
407
|
+
return _isolator_instance
|