code-puppy 0.0.132__py3-none-any.whl → 0.0.134__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/command_line/mcp_commands.py +6 -0
- code_puppy/mcp/managed_server.py +3 -2
- code_puppy/tui/screens/mcp_install_wizard.py +186 -5
- {code_puppy-0.0.132.dist-info → code_puppy-0.0.134.dist-info}/METADATA +1 -1
- {code_puppy-0.0.132.dist-info → code_puppy-0.0.134.dist-info}/RECORD +9 -9
- {code_puppy-0.0.132.data → code_puppy-0.0.134.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.132.dist-info → code_puppy-0.0.134.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.132.dist-info → code_puppy-0.0.134.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.132.dist-info → code_puppy-0.0.134.dist-info}/licenses/LICENSE +0 -0
|
@@ -597,6 +597,12 @@ class MCPCommandHandler:
|
|
|
597
597
|
import uuid
|
|
598
598
|
group_id = str(uuid.uuid4())
|
|
599
599
|
|
|
600
|
+
# Check if in TUI mode and guide user to use Ctrl+T instead
|
|
601
|
+
if is_tui_mode() and not args:
|
|
602
|
+
emit_info("💡 In TUI mode, press Ctrl+T to open the MCP Install Wizard", message_group=group_id)
|
|
603
|
+
emit_info(" The wizard provides a better interface for browsing and installing MCP servers.", message_group=group_id)
|
|
604
|
+
return
|
|
605
|
+
|
|
600
606
|
try:
|
|
601
607
|
if args:
|
|
602
608
|
# Parse JSON from arguments
|
code_puppy/mcp/managed_server.py
CHANGED
|
@@ -18,6 +18,7 @@ from pydantic_ai import RunContext
|
|
|
18
18
|
|
|
19
19
|
from pydantic_ai.mcp import MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP, CallToolFunc, ToolResult
|
|
20
20
|
|
|
21
|
+
from code_puppy.http_utils import create_async_client
|
|
21
22
|
from code_puppy.messaging import emit_info
|
|
22
23
|
from code_puppy.mcp.blocking_startup import BlockingMCPServerStdio
|
|
23
24
|
|
|
@@ -251,11 +252,11 @@ class ManagedMCPServer:
|
|
|
251
252
|
"""
|
|
252
253
|
headers = self.config.config.get("headers", {})
|
|
253
254
|
timeout = self.config.config.get("timeout", 30)
|
|
254
|
-
|
|
255
|
-
return httpx.AsyncClient(
|
|
255
|
+
client = create_async_client(
|
|
256
256
|
headers=headers,
|
|
257
257
|
timeout=timeout
|
|
258
258
|
)
|
|
259
|
+
return client
|
|
259
260
|
|
|
260
261
|
def enable(self) -> None:
|
|
261
262
|
"""Enable server availability."""
|
|
@@ -31,8 +31,9 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
31
31
|
super().__init__(**kwargs)
|
|
32
32
|
self.selected_server = None
|
|
33
33
|
self.env_vars = {}
|
|
34
|
-
self.step = "search" # search -> configure -> install
|
|
34
|
+
self.step = "search" # search -> configure -> install -> custom_json
|
|
35
35
|
self.search_counter = 0 # Counter to ensure unique IDs
|
|
36
|
+
self.custom_json_mode = False # Track if we're in custom JSON mode
|
|
36
37
|
|
|
37
38
|
DEFAULT_CSS = """
|
|
38
39
|
MCPInstallWizardScreen {
|
|
@@ -139,6 +140,43 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
139
140
|
width: 2fr;
|
|
140
141
|
border: solid $primary;
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
#custom-json-container {
|
|
145
|
+
width: 100%;
|
|
146
|
+
height: 1fr;
|
|
147
|
+
layout: vertical;
|
|
148
|
+
display: none;
|
|
149
|
+
padding: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#custom-json-header {
|
|
153
|
+
width: 100%;
|
|
154
|
+
height: 2;
|
|
155
|
+
text-align: left;
|
|
156
|
+
color: $warning;
|
|
157
|
+
margin-bottom: 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#custom-name-input {
|
|
161
|
+
width: 100%;
|
|
162
|
+
margin-bottom: 1;
|
|
163
|
+
border: solid $primary;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#custom-json-input {
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 1fr;
|
|
169
|
+
border: solid $primary;
|
|
170
|
+
margin-bottom: 1;
|
|
171
|
+
background: $surface-darken-1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#custom-json-button {
|
|
175
|
+
width: auto;
|
|
176
|
+
height: 3;
|
|
177
|
+
margin: 0 1;
|
|
178
|
+
min-width: 14;
|
|
179
|
+
}
|
|
142
180
|
"""
|
|
143
181
|
|
|
144
182
|
def compose(self) -> ComposeResult:
|
|
@@ -157,10 +195,17 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
157
195
|
yield Container(id="server-info")
|
|
158
196
|
yield Container(id="env-vars-container")
|
|
159
197
|
|
|
198
|
+
# Step 3: Custom JSON configuration (hidden initially)
|
|
199
|
+
with Container(id="custom-json-container"):
|
|
200
|
+
yield Static("📝 Custom JSON Configuration", id="custom-json-header")
|
|
201
|
+
yield Input(placeholder="Server name (e.g. 'my-sqlite-db')", id="custom-name-input")
|
|
202
|
+
yield TextArea(id="custom-json-input")
|
|
203
|
+
|
|
160
204
|
# Navigation buttons
|
|
161
205
|
with Horizontal(id="button-container"):
|
|
162
206
|
yield Button("Cancel", id="cancel-button", variant="default")
|
|
163
207
|
yield Button("Back", id="back-button", variant="default")
|
|
208
|
+
yield Button("Custom JSON", id="custom-json-button", variant="warning")
|
|
164
209
|
yield Button("Next", id="next-button", variant="primary")
|
|
165
210
|
yield Button("Install", id="install-button", variant="success")
|
|
166
211
|
|
|
@@ -176,40 +221,78 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
176
221
|
def _show_search_step(self) -> None:
|
|
177
222
|
"""Show the search step."""
|
|
178
223
|
self.step = "search"
|
|
224
|
+
self.custom_json_mode = False
|
|
179
225
|
self.query_one("#search-container").display = True
|
|
180
226
|
self.query_one("#config-container").display = False
|
|
227
|
+
self.query_one("#custom-json-container").display = False
|
|
181
228
|
|
|
182
229
|
self.query_one("#back-button").display = False
|
|
230
|
+
self.query_one("#custom-json-button").display = True
|
|
183
231
|
self.query_one("#next-button").display = True
|
|
184
232
|
self.query_one("#install-button").display = False
|
|
185
233
|
|
|
186
234
|
def _show_config_step(self) -> None:
|
|
187
235
|
"""Show the configuration step."""
|
|
188
236
|
self.step = "configure"
|
|
237
|
+
self.custom_json_mode = False
|
|
189
238
|
self.query_one("#search-container").display = False
|
|
190
239
|
self.query_one("#config-container").display = True
|
|
240
|
+
self.query_one("#custom-json-container").display = False
|
|
191
241
|
|
|
192
242
|
self.query_one("#back-button").display = True
|
|
243
|
+
self.query_one("#custom-json-button").display = False
|
|
193
244
|
self.query_one("#next-button").display = False
|
|
194
245
|
self.query_one("#install-button").display = True
|
|
195
246
|
|
|
196
247
|
self._setup_server_config()
|
|
248
|
+
|
|
249
|
+
def _show_custom_json_step(self) -> None:
|
|
250
|
+
"""Show the custom JSON configuration step."""
|
|
251
|
+
self.step = "custom_json"
|
|
252
|
+
self.custom_json_mode = True
|
|
253
|
+
self.query_one("#search-container").display = False
|
|
254
|
+
self.query_one("#config-container").display = False
|
|
255
|
+
self.query_one("#custom-json-container").display = True
|
|
256
|
+
|
|
257
|
+
self.query_one("#back-button").display = True
|
|
258
|
+
self.query_one("#custom-json-button").display = False
|
|
259
|
+
self.query_one("#next-button").display = False
|
|
260
|
+
self.query_one("#install-button").display = True
|
|
261
|
+
|
|
262
|
+
# Pre-populate with SQLite example
|
|
263
|
+
name_input = self.query_one("#custom-name-input", Input)
|
|
264
|
+
name_input.value = "my-sqlite-db"
|
|
265
|
+
|
|
266
|
+
json_input = self.query_one("#custom-json-input", TextArea)
|
|
267
|
+
json_input.text = """{
|
|
268
|
+
"type": "stdio",
|
|
269
|
+
"command": "npx",
|
|
270
|
+
"args": ["-y", "@modelcontextprotocol/server-sqlite", "./database.db"],
|
|
271
|
+
"timeout": 30
|
|
272
|
+
}"""
|
|
273
|
+
|
|
274
|
+
# Focus the name input
|
|
275
|
+
name_input.focus()
|
|
197
276
|
|
|
198
277
|
def _load_popular_servers(self) -> None:
|
|
199
|
-
"""Load
|
|
278
|
+
"""Load all available servers into the list."""
|
|
200
279
|
self.search_counter += 1
|
|
201
280
|
counter = self.search_counter
|
|
202
281
|
|
|
203
282
|
try:
|
|
204
283
|
from code_puppy.mcp.server_registry_catalog import catalog
|
|
205
|
-
servers
|
|
284
|
+
# Load ALL servers instead of just popular ones
|
|
285
|
+
servers = catalog.servers
|
|
206
286
|
|
|
207
287
|
results_list = self.query_one("#results-list", ListView)
|
|
208
288
|
# Force clear by removing all children
|
|
209
289
|
results_list.remove_children()
|
|
210
290
|
|
|
211
291
|
if servers:
|
|
212
|
-
|
|
292
|
+
# Sort servers to show popular and verified first
|
|
293
|
+
sorted_servers = sorted(servers, key=lambda s: (not s.popular, not s.verified, s.display_name))
|
|
294
|
+
|
|
295
|
+
for i, server in enumerate(sorted_servers):
|
|
213
296
|
indicators = []
|
|
214
297
|
if server.verified:
|
|
215
298
|
indicators.append("✓")
|
|
@@ -240,7 +323,7 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
240
323
|
query = event.value.strip()
|
|
241
324
|
|
|
242
325
|
if not query:
|
|
243
|
-
self._load_popular_servers()
|
|
326
|
+
self._load_popular_servers() # This now loads all servers
|
|
244
327
|
return
|
|
245
328
|
|
|
246
329
|
self.search_counter += 1
|
|
@@ -301,12 +384,21 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
301
384
|
"""Handle back button click."""
|
|
302
385
|
if self.step == "configure":
|
|
303
386
|
self._show_search_step()
|
|
387
|
+
elif self.step == "custom_json":
|
|
388
|
+
self._show_search_step()
|
|
304
389
|
|
|
390
|
+
@on(Button.Pressed, "#custom-json-button")
|
|
391
|
+
def on_custom_json_clicked(self) -> None:
|
|
392
|
+
"""Handle custom JSON button click."""
|
|
393
|
+
self._show_custom_json_step()
|
|
394
|
+
|
|
305
395
|
@on(Button.Pressed, "#install-button")
|
|
306
396
|
def on_install_clicked(self) -> None:
|
|
307
397
|
"""Handle install button click."""
|
|
308
398
|
if self.step == "configure" and self.selected_server:
|
|
309
399
|
self._install_server()
|
|
400
|
+
elif self.step == "custom_json":
|
|
401
|
+
self._install_custom_json()
|
|
310
402
|
|
|
311
403
|
@on(Button.Pressed, "#cancel-button")
|
|
312
404
|
def on_cancel_clicked(self) -> None:
|
|
@@ -587,6 +679,95 @@ class MCPInstallWizardScreen(ModalScreen):
|
|
|
587
679
|
"message": f"Installation failed: {str(e)}"
|
|
588
680
|
})
|
|
589
681
|
|
|
682
|
+
def _install_custom_json(self) -> None:
|
|
683
|
+
"""Install server from custom JSON configuration."""
|
|
684
|
+
try:
|
|
685
|
+
name_input = self.query_one("#custom-name-input", Input)
|
|
686
|
+
json_input = self.query_one("#custom-json-input", TextArea)
|
|
687
|
+
|
|
688
|
+
server_name = name_input.value.strip()
|
|
689
|
+
json_text = json_input.text.strip()
|
|
690
|
+
|
|
691
|
+
if not server_name:
|
|
692
|
+
# Show error - need a name
|
|
693
|
+
return
|
|
694
|
+
|
|
695
|
+
if not json_text:
|
|
696
|
+
# Show error - need JSON config
|
|
697
|
+
return
|
|
698
|
+
|
|
699
|
+
# Parse JSON
|
|
700
|
+
try:
|
|
701
|
+
config_dict = json.loads(json_text)
|
|
702
|
+
except json.JSONDecodeError as e:
|
|
703
|
+
# Show error - invalid JSON
|
|
704
|
+
return
|
|
705
|
+
|
|
706
|
+
# Validate required fields
|
|
707
|
+
if 'type' not in config_dict:
|
|
708
|
+
# Show error - missing type
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
# Extract type and create server config
|
|
712
|
+
server_type = config_dict.pop('type')
|
|
713
|
+
|
|
714
|
+
# Create and register the server
|
|
715
|
+
from code_puppy.mcp import ServerConfig
|
|
716
|
+
from code_puppy.mcp.manager import get_mcp_manager
|
|
717
|
+
|
|
718
|
+
server_config = ServerConfig(
|
|
719
|
+
id=server_name,
|
|
720
|
+
name=server_name,
|
|
721
|
+
type=server_type,
|
|
722
|
+
enabled=True,
|
|
723
|
+
config=config_dict
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
manager = get_mcp_manager()
|
|
727
|
+
server_id = manager.register_server(server_config)
|
|
728
|
+
|
|
729
|
+
if server_id:
|
|
730
|
+
# Save to mcp_servers.json
|
|
731
|
+
from code_puppy.config import MCP_SERVERS_FILE
|
|
732
|
+
|
|
733
|
+
if os.path.exists(MCP_SERVERS_FILE):
|
|
734
|
+
with open(MCP_SERVERS_FILE, 'r') as f:
|
|
735
|
+
data = json.load(f)
|
|
736
|
+
servers = data.get("mcp_servers", {})
|
|
737
|
+
else:
|
|
738
|
+
servers = {}
|
|
739
|
+
data = {"mcp_servers": servers}
|
|
740
|
+
|
|
741
|
+
# Add the full config including type
|
|
742
|
+
full_config = config_dict.copy()
|
|
743
|
+
full_config['type'] = server_type
|
|
744
|
+
servers[server_name] = full_config
|
|
745
|
+
|
|
746
|
+
os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
|
|
747
|
+
with open(MCP_SERVERS_FILE, 'w') as f:
|
|
748
|
+
json.dump(data, f, indent=2)
|
|
749
|
+
|
|
750
|
+
# Reload MCP servers
|
|
751
|
+
from code_puppy.agent import reload_mcp_servers
|
|
752
|
+
reload_mcp_servers()
|
|
753
|
+
|
|
754
|
+
self.dismiss({
|
|
755
|
+
"success": True,
|
|
756
|
+
"message": f"Successfully installed custom server '{server_name}'",
|
|
757
|
+
"server_name": server_name
|
|
758
|
+
})
|
|
759
|
+
else:
|
|
760
|
+
self.dismiss({
|
|
761
|
+
"success": False,
|
|
762
|
+
"message": "Failed to register custom server"
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
except Exception as e:
|
|
766
|
+
self.dismiss({
|
|
767
|
+
"success": False,
|
|
768
|
+
"message": f"Installation failed: {str(e)}"
|
|
769
|
+
})
|
|
770
|
+
|
|
590
771
|
def on_key(self, event) -> None:
|
|
591
772
|
"""Handle key events."""
|
|
592
773
|
if event.key == "escape":
|
|
@@ -25,7 +25,7 @@ code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZ
|
|
|
25
25
|
code_puppy/command_line/command_handler.py,sha256=IhDaDa0GmpW0BCFfmkgpL_6iQBJlXEKQC9SDmpj_XeI,20730
|
|
26
26
|
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
|
27
27
|
code_puppy/command_line/load_context_completion.py,sha256=6eZxV6Bs-EFwZjN93V8ZDZUC-6RaWxvtZk-04Wtikyw,2240
|
|
28
|
-
code_puppy/command_line/mcp_commands.py,sha256=
|
|
28
|
+
code_puppy/command_line/mcp_commands.py,sha256=v9faF0G3Ux_JwiQRUPEfvvy1DRDylHyrhCPnrBuwngM,78784
|
|
29
29
|
code_puppy/command_line/meta_command_handler.py,sha256=02NU4Lspf5qRMPTsrGiMRLSUshZhdmS0XQA26k8vUjw,5665
|
|
30
30
|
code_puppy/command_line/model_picker_completion.py,sha256=xvwgthVmLRA9a8RJG6iFImxR2yD6rJYPJJav0YJoVCc,3599
|
|
31
31
|
code_puppy/command_line/motd.py,sha256=PEdkp3ZnydVfvd7mNJylm8YyFNUKg9jmY6uwkA1em8c,2152
|
|
@@ -40,7 +40,7 @@ code_puppy/mcp/config_wizard.py,sha256=d0o29ZcnGLdpV778nwTUAPH7B8lHAC2XggU-ql37F
|
|
|
40
40
|
code_puppy/mcp/dashboard.py,sha256=fHBdAVidQUWuhjMjF7hIy0SjrQlrz31zSqzqJv8H2eI,9396
|
|
41
41
|
code_puppy/mcp/error_isolation.py,sha256=xw8DGTItT8-RY5TVYzORtrtfdI1s1ZfUUT88Pr7h_pI,12270
|
|
42
42
|
code_puppy/mcp/health_monitor.py,sha256=oNgPyEwzkF61gNc7nk-FJU_yO2MERjASn4g5shcabic,20428
|
|
43
|
-
code_puppy/mcp/managed_server.py,sha256=
|
|
43
|
+
code_puppy/mcp/managed_server.py,sha256=ULz4AF9bBasg6Ou0HBZ9YPqmxc1eB1-o5dXOBq-l-BA,15003
|
|
44
44
|
code_puppy/mcp/manager.py,sha256=ZfYoz3nmQqrkSnDzgr9s_QvGKFCthA1bvt3gQm0W6VI,27280
|
|
45
45
|
code_puppy/mcp/registry.py,sha256=YJ-VPFjk1ZFrSftbu9bKVIWvGYX4zIk2TnrM_h_us1M,15841
|
|
46
46
|
code_puppy/mcp/retry_manager.py,sha256=B4q1MyzZQ9RZRW3FKhyqhq-ebSG8Do01j4A70zHTxgA,10988
|
|
@@ -82,7 +82,7 @@ code_puppy/tui/models/command_history.py,sha256=bPWr_xnyQvjG5tPg_5pwqlEzn2fR170H
|
|
|
82
82
|
code_puppy/tui/models/enums.py,sha256=1ulsei95Gxy4r1sk-m-Sm5rdmejYCGRI-YtUwJmKFfM,501
|
|
83
83
|
code_puppy/tui/screens/__init__.py,sha256=Sa_R_caykfsa7D55Zuc9VYpFfmQZAYxBFxfn_7Qe41M,287
|
|
84
84
|
code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjBM,3232
|
|
85
|
-
code_puppy/tui/screens/mcp_install_wizard.py,sha256=
|
|
85
|
+
code_puppy/tui/screens/mcp_install_wizard.py,sha256=sUaoJa03xqpK5CUCOYbnMffD8IrLRzPid82o2SupVF4,28219
|
|
86
86
|
code_puppy/tui/screens/settings.py,sha256=GMpv-qa08rorAE9mj3AjmqjZFPhmeJ_GWd-DBHG6iAA,10671
|
|
87
87
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
|
88
88
|
code_puppy/tui/tests/__init__.py,sha256=Fzb4un4eeKfaKsIa5tqI5pTuwfpS8qD7Z6W7KeqWe84,23
|
|
@@ -105,9 +105,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
|
|
|
105
105
|
code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
|
|
106
106
|
code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
|
|
107
107
|
code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
|
|
108
|
-
code_puppy-0.0.
|
|
109
|
-
code_puppy-0.0.
|
|
110
|
-
code_puppy-0.0.
|
|
111
|
-
code_puppy-0.0.
|
|
112
|
-
code_puppy-0.0.
|
|
113
|
-
code_puppy-0.0.
|
|
108
|
+
code_puppy-0.0.134.data/data/code_puppy/models.json,sha256=GpvtWnBKERm6T7HCZJQUIVAS5256-tZ_bFuRtnKXEsY,3128
|
|
109
|
+
code_puppy-0.0.134.dist-info/METADATA,sha256=l6rRGnt2D52-BORKDBDxSrjQPh6lrE4EPKPeTG7Ptmg,19873
|
|
110
|
+
code_puppy-0.0.134.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
111
|
+
code_puppy-0.0.134.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
112
|
+
code_puppy-0.0.134.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
113
|
+
code_puppy-0.0.134.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|