code-puppy 0.0.170__py3-none-any.whl → 0.0.172__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 +10 -2
- code_puppy/agents/agent_creator_agent.py +0 -3
- code_puppy/agents/agent_qa_kitten.py +203 -0
- code_puppy/agents/base_agent.py +9 -0
- code_puppy/command_line/command_handler.py +68 -28
- code_puppy/command_line/mcp/add_command.py +1 -1
- code_puppy/command_line/mcp/base.py +1 -1
- code_puppy/command_line/mcp/install_command.py +1 -1
- code_puppy/command_line/mcp/list_command.py +1 -1
- code_puppy/command_line/mcp/search_command.py +1 -1
- code_puppy/command_line/mcp/start_all_command.py +1 -1
- code_puppy/command_line/mcp/status_command.py +2 -2
- code_puppy/command_line/mcp/stop_all_command.py +1 -1
- code_puppy/command_line/mcp/utils.py +1 -1
- code_puppy/command_line/mcp/wizard_utils.py +2 -2
- code_puppy/config.py +142 -12
- code_puppy/http_utils.py +50 -24
- code_puppy/{mcp → mcp_}/config_wizard.py +1 -1
- code_puppy/{mcp → mcp_}/examples/retry_example.py +1 -1
- code_puppy/{mcp → mcp_}/managed_server.py +1 -1
- code_puppy/{mcp → mcp_}/server_registry_catalog.py +1 -3
- code_puppy/message_history_processor.py +121 -125
- code_puppy/state_management.py +86 -127
- code_puppy/tools/__init__.py +103 -6
- code_puppy/tools/browser/__init__.py +0 -0
- code_puppy/tools/browser/browser_control.py +293 -0
- code_puppy/tools/browser/browser_interactions.py +552 -0
- code_puppy/tools/browser/browser_locators.py +642 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +242 -0
- code_puppy/tools/browser/browser_scripts.py +478 -0
- code_puppy/tools/browser/browser_workflows.py +196 -0
- code_puppy/tools/browser/camoufox_manager.py +194 -0
- code_puppy/tools/browser/vqa_agent.py +66 -0
- code_puppy/tools/browser_control.py +293 -0
- code_puppy/tools/browser_interactions.py +552 -0
- code_puppy/tools/browser_locators.py +642 -0
- code_puppy/tools/browser_navigation.py +251 -0
- code_puppy/tools/browser_screenshot.py +278 -0
- code_puppy/tools/browser_scripts.py +478 -0
- code_puppy/tools/browser_workflows.py +215 -0
- code_puppy/tools/camoufox_manager.py +150 -0
- code_puppy/tools/command_runner.py +12 -7
- code_puppy/tools/file_operations.py +7 -7
- code_puppy/tui/app.py +4 -2
- code_puppy/tui/components/custom_widgets.py +1 -1
- code_puppy/tui/screens/mcp_install_wizard.py +8 -8
- {code_puppy-0.0.170.dist-info → code_puppy-0.0.172.dist-info}/METADATA +4 -2
- {code_puppy-0.0.170.dist-info → code_puppy-0.0.172.dist-info}/RECORD +66 -47
- /code_puppy/{mcp → mcp_}/__init__.py +0 -0
- /code_puppy/{mcp → mcp_}/async_lifecycle.py +0 -0
- /code_puppy/{mcp → mcp_}/blocking_startup.py +0 -0
- /code_puppy/{mcp → mcp_}/captured_stdio_server.py +0 -0
- /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
- /code_puppy/{mcp → mcp_}/dashboard.py +0 -0
- /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
- /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
- /code_puppy/{mcp → mcp_}/manager.py +0 -0
- /code_puppy/{mcp → mcp_}/registry.py +0 -0
- /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
- /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
- /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
- {code_puppy-0.0.170.data → code_puppy-0.0.172.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.170.dist-info → code_puppy-0.0.172.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.170.dist-info → code_puppy-0.0.172.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.170.dist-info → code_puppy-0.0.172.dist-info}/licenses/LICENSE +0 -0
code_puppy/config.py
CHANGED
|
@@ -14,6 +14,12 @@ AGENTS_DIR = os.path.join(CONFIG_DIR, "agents")
|
|
|
14
14
|
DEFAULT_SECTION = "puppy"
|
|
15
15
|
REQUIRED_KEYS = ["puppy_name", "owner_name"]
|
|
16
16
|
|
|
17
|
+
# Cache containers for model validation and defaults
|
|
18
|
+
_model_validation_cache = {}
|
|
19
|
+
_default_model_cache = None
|
|
20
|
+
_default_vision_model_cache = None
|
|
21
|
+
_default_vqa_model_cache = None
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
def ensure_config_exists():
|
|
19
25
|
"""
|
|
@@ -109,6 +115,7 @@ def get_config_keys():
|
|
|
109
115
|
default_keys = [
|
|
110
116
|
"yolo_mode",
|
|
111
117
|
"model",
|
|
118
|
+
"vqa_model_name",
|
|
112
119
|
"compaction_strategy",
|
|
113
120
|
"protected_token_count",
|
|
114
121
|
"compaction_threshold",
|
|
@@ -156,9 +163,6 @@ def load_mcp_server_configs():
|
|
|
156
163
|
return {}
|
|
157
164
|
|
|
158
165
|
|
|
159
|
-
# Cache for model validation to prevent hitting ModelFactory on every call
|
|
160
|
-
_model_validation_cache = {}
|
|
161
|
-
_default_model_cache = None
|
|
162
166
|
|
|
163
167
|
|
|
164
168
|
def _default_model_from_models_json():
|
|
@@ -169,30 +173,107 @@ def _default_model_from_models_json():
|
|
|
169
173
|
"""
|
|
170
174
|
global _default_model_cache
|
|
171
175
|
|
|
172
|
-
# Return cached default if we have one
|
|
173
176
|
if _default_model_cache is not None:
|
|
174
177
|
return _default_model_cache
|
|
175
178
|
|
|
176
179
|
try:
|
|
177
|
-
# Local import to avoid potential circular dependency on module import
|
|
178
180
|
from code_puppy.model_factory import ModelFactory
|
|
179
181
|
|
|
180
182
|
models_config = ModelFactory.load_config()
|
|
181
183
|
if models_config:
|
|
182
|
-
# Get the first key from the models config
|
|
183
184
|
first_key = next(iter(models_config))
|
|
184
185
|
_default_model_cache = first_key
|
|
185
186
|
return first_key
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
_default_model_cache = "gpt-5"
|
|
189
|
-
return "gpt-5"
|
|
187
|
+
_default_model_cache = "gpt-5"
|
|
188
|
+
return "gpt-5"
|
|
190
189
|
except Exception:
|
|
191
|
-
# Any problem (network, file missing, empty dict, etc.) => fall back to gpt-5
|
|
192
190
|
_default_model_cache = "gpt-5"
|
|
193
191
|
return "gpt-5"
|
|
194
192
|
|
|
195
193
|
|
|
194
|
+
def _default_vision_model_from_models_json() -> str:
|
|
195
|
+
"""Select a default vision-capable model from models.json with caching."""
|
|
196
|
+
global _default_vision_model_cache
|
|
197
|
+
|
|
198
|
+
if _default_vision_model_cache is not None:
|
|
199
|
+
return _default_vision_model_cache
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
from code_puppy.model_factory import ModelFactory
|
|
203
|
+
|
|
204
|
+
models_config = ModelFactory.load_config()
|
|
205
|
+
if models_config:
|
|
206
|
+
# Prefer explicitly tagged vision models
|
|
207
|
+
for name, config in models_config.items():
|
|
208
|
+
if config.get("supports_vision"):
|
|
209
|
+
_default_vision_model_cache = name
|
|
210
|
+
return name
|
|
211
|
+
|
|
212
|
+
# Fallback heuristic: common multimodal models
|
|
213
|
+
preferred_candidates = (
|
|
214
|
+
"gpt-4.1",
|
|
215
|
+
"gpt-4.1-mini",
|
|
216
|
+
"gpt-4.1-nano",
|
|
217
|
+
"claude-4-0-sonnet",
|
|
218
|
+
"gemini-2.5-flash-preview-05-20",
|
|
219
|
+
)
|
|
220
|
+
for candidate in preferred_candidates:
|
|
221
|
+
if candidate in models_config:
|
|
222
|
+
_default_vision_model_cache = candidate
|
|
223
|
+
return candidate
|
|
224
|
+
|
|
225
|
+
# Last resort: use the general default model
|
|
226
|
+
_default_vision_model_cache = _default_model_from_models_json()
|
|
227
|
+
return _default_vision_model_cache
|
|
228
|
+
|
|
229
|
+
_default_vision_model_cache = "gpt-4.1"
|
|
230
|
+
return "gpt-4.1"
|
|
231
|
+
except Exception:
|
|
232
|
+
_default_vision_model_cache = "gpt-4.1"
|
|
233
|
+
return "gpt-4.1"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _default_vqa_model_from_models_json() -> str:
|
|
237
|
+
"""Select a default VQA-capable model, preferring vision-ready options."""
|
|
238
|
+
global _default_vqa_model_cache
|
|
239
|
+
|
|
240
|
+
if _default_vqa_model_cache is not None:
|
|
241
|
+
return _default_vqa_model_cache
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
from code_puppy.model_factory import ModelFactory
|
|
245
|
+
|
|
246
|
+
models_config = ModelFactory.load_config()
|
|
247
|
+
if models_config:
|
|
248
|
+
# Allow explicit VQA hints if present
|
|
249
|
+
for name, config in models_config.items():
|
|
250
|
+
if config.get("supports_vqa"):
|
|
251
|
+
_default_vqa_model_cache = name
|
|
252
|
+
return name
|
|
253
|
+
|
|
254
|
+
# Reuse multimodal heuristics before falling back to generic default
|
|
255
|
+
preferred_candidates = (
|
|
256
|
+
"gpt-4.1",
|
|
257
|
+
"gpt-4.1-mini",
|
|
258
|
+
"claude-4-0-sonnet",
|
|
259
|
+
"gemini-2.5-flash-preview-05-20",
|
|
260
|
+
"gpt-4.1-nano",
|
|
261
|
+
)
|
|
262
|
+
for candidate in preferred_candidates:
|
|
263
|
+
if candidate in models_config:
|
|
264
|
+
_default_vqa_model_cache = candidate
|
|
265
|
+
return candidate
|
|
266
|
+
|
|
267
|
+
_default_vqa_model_cache = _default_model_from_models_json()
|
|
268
|
+
return _default_vqa_model_cache
|
|
269
|
+
|
|
270
|
+
_default_vqa_model_cache = "gpt-4.1"
|
|
271
|
+
return "gpt-4.1"
|
|
272
|
+
except Exception:
|
|
273
|
+
_default_vqa_model_cache = "gpt-4.1"
|
|
274
|
+
return "gpt-4.1"
|
|
275
|
+
|
|
276
|
+
|
|
196
277
|
def _validate_model_exists(model_name: str) -> bool:
|
|
197
278
|
"""Check if a model exists in models.json with caching to avoid redundant calls."""
|
|
198
279
|
global _model_validation_cache
|
|
@@ -218,9 +299,11 @@ def _validate_model_exists(model_name: str) -> bool:
|
|
|
218
299
|
|
|
219
300
|
def clear_model_cache():
|
|
220
301
|
"""Clear the model validation cache. Call this when models.json changes."""
|
|
221
|
-
global _model_validation_cache, _default_model_cache
|
|
302
|
+
global _model_validation_cache, _default_model_cache, _default_vision_model_cache, _default_vqa_model_cache
|
|
222
303
|
_model_validation_cache.clear()
|
|
223
304
|
_default_model_cache = None
|
|
305
|
+
_default_vision_model_cache = None
|
|
306
|
+
_default_vqa_model_cache = None
|
|
224
307
|
|
|
225
308
|
|
|
226
309
|
def get_model_name():
|
|
@@ -258,6 +341,20 @@ def set_model_name(model: str):
|
|
|
258
341
|
clear_model_cache()
|
|
259
342
|
|
|
260
343
|
|
|
344
|
+
def get_vqa_model_name() -> str:
|
|
345
|
+
"""Return the configured VQA model, falling back to an inferred default."""
|
|
346
|
+
stored_model = get_value("vqa_model_name")
|
|
347
|
+
if stored_model and _validate_model_exists(stored_model):
|
|
348
|
+
return stored_model
|
|
349
|
+
return _default_vqa_model_from_models_json()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def set_vqa_model_name(model: str):
|
|
353
|
+
"""Persist the configured VQA model name and refresh caches."""
|
|
354
|
+
set_config_value("vqa_model_name", model or "")
|
|
355
|
+
clear_model_cache()
|
|
356
|
+
|
|
357
|
+
|
|
261
358
|
def get_puppy_token():
|
|
262
359
|
"""Returns the puppy_token from config, or None if not set."""
|
|
263
360
|
return get_value("puppy_token")
|
|
@@ -493,3 +590,36 @@ def save_command_to_history(command: str):
|
|
|
493
590
|
f"❌ An unexpected error occurred while saving command history: {str(e)}"
|
|
494
591
|
)
|
|
495
592
|
direct_console.print(f"[bold red]{error_msg}[/bold red]")
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def get_agent_pinned_model(agent_name: str) -> str:
|
|
596
|
+
"""Get the pinned model for a specific agent.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
agent_name: Name of the agent to get the pinned model for.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
Pinned model name, or None if no model is pinned for this agent.
|
|
603
|
+
"""
|
|
604
|
+
return get_value(f"agent_model_{agent_name}")
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def set_agent_pinned_model(agent_name: str, model_name: str):
|
|
608
|
+
"""Set the pinned model for a specific agent.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
agent_name: Name of the agent to pin the model for.
|
|
612
|
+
model_name: Model name to pin to this agent.
|
|
613
|
+
"""
|
|
614
|
+
set_config_value(f"agent_model_{agent_name}", model_name)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def clear_agent_pinned_model(agent_name: str):
|
|
618
|
+
"""Clear the pinned model for a specific agent.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
agent_name: Name of the agent to clear the pinned model for.
|
|
622
|
+
"""
|
|
623
|
+
# We can't easily delete keys from configparser, so set to empty string
|
|
624
|
+
# which will be treated as None by get_agent_pinned_model
|
|
625
|
+
set_config_value(f"agent_model_{agent_name}", "")
|
code_puppy/http_utils.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Dict, Optional, Union
|
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
import requests
|
|
13
|
-
from tenacity import
|
|
13
|
+
from tenacity import stop_after_attempt, wait_exponential
|
|
14
14
|
|
|
15
15
|
try:
|
|
16
16
|
from pydantic_ai.retries import (
|
|
@@ -57,26 +57,32 @@ def create_client(
|
|
|
57
57
|
|
|
58
58
|
# If retry components are available, create a client with retry transport
|
|
59
59
|
if TenacityTransport and RetryConfig and wait_retry_after:
|
|
60
|
+
|
|
60
61
|
def should_retry_status(response):
|
|
61
62
|
"""Raise exceptions for retryable HTTP status codes."""
|
|
62
63
|
if response.status_code in retry_status_codes:
|
|
63
|
-
emit_info(
|
|
64
|
+
emit_info(
|
|
65
|
+
f"HTTP retry: Retrying request due to status code {response.status_code}"
|
|
66
|
+
)
|
|
64
67
|
response.raise_for_status()
|
|
65
68
|
|
|
66
69
|
transport = TenacityTransport(
|
|
67
70
|
config=RetryConfig(
|
|
68
|
-
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
71
|
+
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
72
|
+
and e.response.status_code in retry_status_codes,
|
|
69
73
|
wait=wait_retry_after(
|
|
70
74
|
fallback_strategy=wait_exponential(multiplier=1, max=60),
|
|
71
|
-
max_wait=300
|
|
75
|
+
max_wait=300,
|
|
72
76
|
),
|
|
73
77
|
stop=stop_after_attempt(10),
|
|
74
|
-
reraise=True
|
|
78
|
+
reraise=True,
|
|
75
79
|
),
|
|
76
|
-
validate_response=should_retry_status
|
|
80
|
+
validate_response=should_retry_status,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return httpx.Client(
|
|
84
|
+
transport=transport, verify=verify, headers=headers or {}, timeout=timeout
|
|
77
85
|
)
|
|
78
|
-
|
|
79
|
-
return httpx.Client(transport=transport, verify=verify, headers=headers or {}, timeout=timeout)
|
|
80
86
|
else:
|
|
81
87
|
# Fallback to regular client if retry components are not available
|
|
82
88
|
return httpx.Client(verify=verify, headers=headers or {}, timeout=timeout)
|
|
@@ -93,26 +99,32 @@ def create_async_client(
|
|
|
93
99
|
|
|
94
100
|
# If retry components are available, create a client with retry transport
|
|
95
101
|
if AsyncTenacityTransport and RetryConfig and wait_retry_after:
|
|
102
|
+
|
|
96
103
|
def should_retry_status(response):
|
|
97
104
|
"""Raise exceptions for retryable HTTP status codes."""
|
|
98
105
|
if response.status_code in retry_status_codes:
|
|
99
|
-
emit_info(
|
|
106
|
+
emit_info(
|
|
107
|
+
f"HTTP retry: Retrying request due to status code {response.status_code}"
|
|
108
|
+
)
|
|
100
109
|
response.raise_for_status()
|
|
101
110
|
|
|
102
111
|
transport = AsyncTenacityTransport(
|
|
103
112
|
config=RetryConfig(
|
|
104
|
-
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
113
|
+
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
114
|
+
and e.response.status_code in retry_status_codes,
|
|
105
115
|
wait=wait_retry_after(
|
|
106
116
|
fallback_strategy=wait_exponential(multiplier=1, max=60),
|
|
107
|
-
max_wait=300
|
|
117
|
+
max_wait=300,
|
|
108
118
|
),
|
|
109
119
|
stop=stop_after_attempt(10),
|
|
110
|
-
reraise=True
|
|
120
|
+
reraise=True,
|
|
111
121
|
),
|
|
112
|
-
validate_response=should_retry_status
|
|
122
|
+
validate_response=should_retry_status,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return httpx.AsyncClient(
|
|
126
|
+
transport=transport, verify=verify, headers=headers or {}, timeout=timeout
|
|
113
127
|
)
|
|
114
|
-
|
|
115
|
-
return httpx.AsyncClient(transport=transport, verify=verify, headers=headers or {}, timeout=timeout)
|
|
116
128
|
else:
|
|
117
129
|
# Fallback to regular client if retry components are not available
|
|
118
130
|
return httpx.AsyncClient(verify=verify, headers=headers or {}, timeout=timeout)
|
|
@@ -169,32 +181,44 @@ def create_reopenable_async_client(
|
|
|
169
181
|
|
|
170
182
|
# If retry components are available, create a client with retry transport
|
|
171
183
|
if AsyncTenacityTransport and RetryConfig and wait_retry_after:
|
|
184
|
+
|
|
172
185
|
def should_retry_status(response):
|
|
173
186
|
"""Raise exceptions for retryable HTTP status codes."""
|
|
174
187
|
if response.status_code in retry_status_codes:
|
|
175
|
-
emit_info(
|
|
188
|
+
emit_info(
|
|
189
|
+
f"HTTP retry: Retrying request due to status code {response.status_code}"
|
|
190
|
+
)
|
|
176
191
|
response.raise_for_status()
|
|
177
192
|
|
|
178
193
|
transport = AsyncTenacityTransport(
|
|
179
194
|
config=RetryConfig(
|
|
180
|
-
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
195
|
+
retry=lambda e: isinstance(e, httpx.HTTPStatusError)
|
|
196
|
+
and e.response.status_code in retry_status_codes,
|
|
181
197
|
wait=wait_retry_after(
|
|
182
198
|
fallback_strategy=wait_exponential(multiplier=1, max=60),
|
|
183
|
-
max_wait=300
|
|
199
|
+
max_wait=300,
|
|
184
200
|
),
|
|
185
201
|
stop=stop_after_attempt(10),
|
|
186
|
-
reraise=True
|
|
202
|
+
reraise=True,
|
|
187
203
|
),
|
|
188
|
-
validate_response=should_retry_status
|
|
204
|
+
validate_response=should_retry_status,
|
|
189
205
|
)
|
|
190
|
-
|
|
206
|
+
|
|
191
207
|
if ReopenableAsyncClient is not None:
|
|
192
208
|
return ReopenableAsyncClient(
|
|
193
|
-
transport=transport,
|
|
209
|
+
transport=transport,
|
|
210
|
+
verify=verify,
|
|
211
|
+
headers=headers or {},
|
|
212
|
+
timeout=timeout,
|
|
194
213
|
)
|
|
195
214
|
else:
|
|
196
215
|
# Fallback to regular AsyncClient if ReopenableAsyncClient is not available
|
|
197
|
-
return httpx.AsyncClient(
|
|
216
|
+
return httpx.AsyncClient(
|
|
217
|
+
transport=transport,
|
|
218
|
+
verify=verify,
|
|
219
|
+
headers=headers or {},
|
|
220
|
+
timeout=timeout,
|
|
221
|
+
)
|
|
198
222
|
else:
|
|
199
223
|
# Fallback to regular clients if retry components are not available
|
|
200
224
|
if ReopenableAsyncClient is not None:
|
|
@@ -203,7 +227,9 @@ def create_reopenable_async_client(
|
|
|
203
227
|
)
|
|
204
228
|
else:
|
|
205
229
|
# Fallback to regular AsyncClient if ReopenableAsyncClient is not available
|
|
206
|
-
return httpx.AsyncClient(
|
|
230
|
+
return httpx.AsyncClient(
|
|
231
|
+
verify=verify, headers=headers or {}, timeout=timeout
|
|
232
|
+
)
|
|
207
233
|
|
|
208
234
|
|
|
209
235
|
def is_cert_bundle_available() -> bool:
|
|
@@ -11,7 +11,7 @@ from urllib.parse import urlparse
|
|
|
11
11
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
|
|
14
|
-
from code_puppy.
|
|
14
|
+
from code_puppy.mcp_.manager import ServerConfig, get_mcp_manager
|
|
15
15
|
from code_puppy.messaging import (
|
|
16
16
|
emit_error,
|
|
17
17
|
emit_info,
|
|
@@ -17,7 +17,7 @@ from typing import Any
|
|
|
17
17
|
project_root = Path(__file__).parents[3]
|
|
18
18
|
sys.path.insert(0, str(project_root))
|
|
19
19
|
|
|
20
|
-
from code_puppy.
|
|
20
|
+
from code_puppy.mcp_.retry_manager import get_retry_manager, retry_mcp_call # noqa: E402
|
|
21
21
|
|
|
22
22
|
logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
@@ -24,7 +24,7 @@ from pydantic_ai.mcp import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
from code_puppy.http_utils import create_async_client
|
|
27
|
-
from code_puppy.
|
|
27
|
+
from code_puppy.mcp_.blocking_startup import BlockingMCPServerStdio
|
|
28
28
|
from code_puppy.messaging import emit_info
|
|
29
29
|
|
|
30
30
|
# Configure logging
|
|
@@ -794,9 +794,7 @@ MCP_SERVER_REGISTRY: List[MCPServerTemplate] = [
|
|
|
794
794
|
type="http",
|
|
795
795
|
config={
|
|
796
796
|
"url": "https://mcp.context7.com/mcp",
|
|
797
|
-
|
|
798
|
-
"Authorization": "Bearer $CONTEXT7_API_KEY"
|
|
799
|
-
}
|
|
797
|
+
"headers": {"Authorization": "Bearer $CONTEXT7_API_KEY"},
|
|
800
798
|
},
|
|
801
799
|
verified=True,
|
|
802
800
|
popular=True,
|