code-puppy 0.0.130__py3-none-any.whl → 0.0.132__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.
@@ -15,8 +15,9 @@ from rich.table import Table
15
15
  from rich.console import Console
16
16
  from rich.text import Text
17
17
  from rich.panel import Panel
18
- from rich.columns import Columns
19
18
 
19
+ from code_puppy.state_management import is_tui_mode
20
+ from code_puppy.messaging import emit_prompt
20
21
  from code_puppy.mcp.manager import get_mcp_manager, ServerInfo
21
22
  from code_puppy.mcp.managed_server import ServerConfig, ServerState
22
23
  from code_puppy.messaging import emit_info, emit_system_message
@@ -671,10 +672,8 @@ class MCPCommandHandler:
671
672
  emit_info(f"Failed to add server '{name}'", message_group=group_id)
672
673
 
673
674
  else:
674
- # No arguments - launch interactive wizard
675
- from code_puppy.mcp.config_wizard import run_add_wizard
676
-
677
- success = run_add_wizard()
675
+ # No arguments - launch interactive wizard with server templates
676
+ success = self._run_interactive_install_wizard(group_id)
678
677
 
679
678
  if success:
680
679
  # Reload the agent to pick up new server
@@ -688,6 +687,398 @@ class MCPCommandHandler:
688
687
  logger.error(f"Error adding server: {e}")
689
688
  emit_info(f"Failed to add server: {e}", message_group=group_id)
690
689
 
690
+ def _run_interactive_install_wizard(self, group_id: str) -> bool:
691
+ """Run the interactive MCP server installation wizard using server templates."""
692
+ try:
693
+ from code_puppy.mcp.server_registry_catalog import catalog
694
+ from code_puppy.mcp.system_tools import detector
695
+ from code_puppy.messaging import emit_prompt
696
+ import os
697
+ import json
698
+
699
+ emit_info("🧙 Interactive MCP Server Installation Wizard", message_group=group_id)
700
+ emit_info("", message_group=group_id)
701
+
702
+ # Step 1: Browse and select server
703
+ selected_server = self._interactive_server_selection(group_id)
704
+ if not selected_server:
705
+ return False
706
+
707
+ # Step 2: Get custom server name
708
+ server_name = self._interactive_get_server_name(selected_server, group_id)
709
+ if not server_name:
710
+ return False
711
+
712
+ # Step 3: Handle requirements and configuration
713
+ success = self._interactive_configure_server(selected_server, server_name, group_id)
714
+ return success
715
+
716
+ except ImportError:
717
+ emit_info("Server catalog not available, falling back to basic wizard", message_group=group_id)
718
+ # Fall back to the old wizard
719
+ from code_puppy.mcp.config_wizard import run_add_wizard
720
+ return run_add_wizard(group_id)
721
+ except Exception as e:
722
+ emit_info(f"Installation wizard failed: {e}", message_group=group_id)
723
+ return False
724
+
725
+ def _interactive_server_selection(self, group_id: str):
726
+ """Interactive server selection from catalog."""
727
+ from code_puppy.mcp.server_registry_catalog import catalog
728
+ from code_puppy.messaging import emit_prompt
729
+
730
+ while True:
731
+ emit_info("📦 Available MCP Servers:", message_group=group_id)
732
+ emit_info("", message_group=group_id)
733
+
734
+ # Show popular servers first
735
+ popular = catalog.get_popular(5)
736
+ if popular:
737
+ emit_info("[bold]Popular Servers:[/bold]", message_group=group_id)
738
+ for i, server in enumerate(popular):
739
+ indicators = []
740
+ if server.verified:
741
+ indicators.append("✓")
742
+ if server.popular:
743
+ indicators.append("⭐")
744
+
745
+ emit_info(f" {i+1}. {server.display_name} {''.join(indicators)}", message_group=group_id)
746
+ emit_info(f" {server.description[:80]}...", message_group=group_id)
747
+ emit_info("", message_group=group_id)
748
+
749
+ # Prompt for selection
750
+ choice = emit_prompt("Enter server number (1-5), 'search <term>' to search, or 'list' to see all categories: ")
751
+
752
+ if not choice.strip():
753
+ if emit_prompt("Cancel installation? [y/N]: ").lower().startswith('y'):
754
+ return None
755
+ continue
756
+
757
+ choice = choice.strip()
758
+
759
+ # Handle numeric selection
760
+ if choice.isdigit():
761
+ try:
762
+ index = int(choice) - 1
763
+ if 0 <= index < len(popular):
764
+ return popular[index]
765
+ else:
766
+ emit_info("Invalid selection. Please try again.", message_group=group_id)
767
+ continue
768
+ except ValueError:
769
+ pass
770
+
771
+ # Handle search
772
+ if choice.lower().startswith('search '):
773
+ search_term = choice[7:].strip()
774
+ results = catalog.search(search_term)
775
+ if results:
776
+ emit_info(f"\n🔍 Search results for '{search_term}':", message_group=group_id)
777
+ for i, server in enumerate(results[:10]):
778
+ indicators = []
779
+ if server.verified:
780
+ indicators.append("✓")
781
+ if server.popular:
782
+ indicators.append("⭐")
783
+ emit_info(f" {i+1}. {server.display_name} {''.join(indicators)}", message_group=group_id)
784
+ emit_info(f" {server.description[:80]}...", message_group=group_id)
785
+
786
+ selection = emit_prompt(f"\nSelect server (1-{min(len(results), 10)}): ")
787
+ if selection.isdigit():
788
+ try:
789
+ index = int(selection) - 1
790
+ if 0 <= index < len(results):
791
+ return results[index]
792
+ except ValueError:
793
+ pass
794
+ else:
795
+ emit_info(f"No servers found for '{search_term}'", message_group=group_id)
796
+ continue
797
+
798
+ # Handle list categories
799
+ if choice.lower() == 'list':
800
+ categories = catalog.list_categories()
801
+ emit_info("\n📂 Categories:", message_group=group_id)
802
+ for i, category in enumerate(categories):
803
+ servers_count = len(catalog.get_by_category(category))
804
+ emit_info(f" {i+1}. {category} ({servers_count} servers)", message_group=group_id)
805
+
806
+ cat_choice = emit_prompt(f"\nSelect category (1-{len(categories)}): ")
807
+ if cat_choice.isdigit():
808
+ try:
809
+ index = int(cat_choice) - 1
810
+ if 0 <= index < len(categories):
811
+ category_servers = catalog.get_by_category(categories[index])
812
+ emit_info(f"\n📦 {categories[index]} Servers:", message_group=group_id)
813
+ for i, server in enumerate(category_servers):
814
+ indicators = []
815
+ if server.verified:
816
+ indicators.append("✓")
817
+ if server.popular:
818
+ indicators.append("⭐")
819
+ emit_info(f" {i+1}. {server.display_name} {''.join(indicators)}", message_group=group_id)
820
+ emit_info(f" {server.description[:80]}...", message_group=group_id)
821
+
822
+ server_choice = emit_prompt(f"\nSelect server (1-{len(category_servers)}): ")
823
+ if server_choice.isdigit():
824
+ try:
825
+ index = int(server_choice) - 1
826
+ if 0 <= index < len(category_servers):
827
+ return category_servers[index]
828
+ except ValueError:
829
+ pass
830
+ except ValueError:
831
+ pass
832
+ continue
833
+
834
+ emit_info("Invalid choice. Please try again.", message_group=group_id)
835
+
836
+ def _interactive_get_server_name(self, selected_server, group_id: str) -> str:
837
+ """Get custom server name from user."""
838
+ from code_puppy.messaging import emit_prompt
839
+
840
+ emit_info(f"\n🏷️ Server: {selected_server.display_name}", message_group=group_id)
841
+ emit_info(f"Description: {selected_server.description}", message_group=group_id)
842
+ emit_info("", message_group=group_id)
843
+
844
+ while True:
845
+ name = emit_prompt(f"Enter custom name for this server [{selected_server.name}]: ").strip()
846
+
847
+ if not name:
848
+ name = selected_server.name
849
+
850
+ # Validate name
851
+ if not name.replace('-', '').replace('_', '').replace('.', '').isalnum():
852
+ emit_info("Name must contain only letters, numbers, hyphens, underscores, and dots", message_group=group_id)
853
+ continue
854
+
855
+ # Check if name already exists
856
+ existing_server = self._find_server_id_by_name(name)
857
+ if existing_server:
858
+ override = emit_prompt(f"Server '{name}' already exists. Override it? [y/N]: ")
859
+ if not override.lower().startswith('y'):
860
+ continue
861
+
862
+ return name
863
+
864
+ def _interactive_configure_server(self, selected_server, server_name: str, group_id: str) -> bool:
865
+ """Configure the server with requirements validation."""
866
+ from code_puppy.mcp.system_tools import detector
867
+ from code_puppy.messaging import emit_prompt
868
+ import os
869
+ import json
870
+
871
+ requirements = selected_server.get_requirements()
872
+
873
+ emit_info(f"\n⚙️ Configuring server: {server_name}", message_group=group_id)
874
+ emit_info("", message_group=group_id)
875
+
876
+ # Step 1: Check system requirements
877
+ if not self._interactive_check_system_requirements(requirements, group_id):
878
+ return False
879
+
880
+ # Step 2: Collect environment variables
881
+ env_vars = self._interactive_collect_env_vars(requirements, group_id)
882
+
883
+ # Step 3: Collect command line arguments
884
+ cmd_args = self._interactive_collect_cmd_args(requirements, group_id)
885
+
886
+ # Step 4: Show summary and confirm
887
+ if not self._interactive_confirm_installation(selected_server, server_name, env_vars, cmd_args, group_id):
888
+ return False
889
+
890
+ # Step 5: Install the server
891
+ return self._interactive_install_server(selected_server, server_name, env_vars, cmd_args, group_id)
892
+
893
+ def _interactive_check_system_requirements(self, requirements, group_id: str) -> bool:
894
+ """Check and validate system requirements."""
895
+ from code_puppy.mcp.system_tools import detector
896
+
897
+ required_tools = requirements.required_tools
898
+ if not required_tools:
899
+ return True
900
+
901
+ emit_info("🔧 Checking system requirements...", message_group=group_id)
902
+
903
+ tool_status = detector.detect_tools(required_tools)
904
+ all_good = True
905
+
906
+ for tool_name, tool_info in tool_status.items():
907
+ if tool_info.available:
908
+ status_text = f"✅ {tool_name}"
909
+ if tool_info.version:
910
+ status_text += f" ({tool_info.version})"
911
+ emit_info(status_text, message_group=group_id)
912
+ else:
913
+ status_text = f"❌ {tool_name} - {tool_info.error or 'Not found'}"
914
+ emit_info(status_text, message_group=group_id)
915
+
916
+ # Show installation suggestions
917
+ suggestions = detector.get_installation_suggestions(tool_name)
918
+ if suggestions:
919
+ emit_info(f" Install: {suggestions[0]}", message_group=group_id)
920
+ all_good = False
921
+
922
+ if not all_good:
923
+ emit_info("", message_group=group_id)
924
+ cont = emit_prompt("Some tools are missing. Continue anyway? [y/N]: ")
925
+ if not cont.lower().startswith('y'):
926
+ emit_info("Installation cancelled", message_group=group_id)
927
+ return False
928
+
929
+ emit_info("", message_group=group_id)
930
+ return True
931
+
932
+ def _interactive_collect_env_vars(self, requirements, group_id: str) -> dict:
933
+ """Collect environment variables from user."""
934
+ from code_puppy.messaging import emit_prompt
935
+ import os
936
+
937
+ env_vars = {}
938
+ required_env_vars = requirements.environment_vars
939
+
940
+ if not required_env_vars:
941
+ return env_vars
942
+
943
+ emit_info("🔐 Environment Variables:", message_group=group_id)
944
+
945
+ for var in required_env_vars:
946
+ # Check if already set
947
+ current_value = os.environ.get(var, "")
948
+
949
+ if current_value:
950
+ emit_info(f"✅ {var} (already set)", message_group=group_id)
951
+ env_vars[var] = current_value
952
+ else:
953
+ value = emit_prompt(f"📝 Enter value for {var}: ").strip()
954
+ if value:
955
+ env_vars[var] = value
956
+ # Set in current environment too
957
+ os.environ[var] = value
958
+ else:
959
+ emit_info(f"⚠️ {var} left empty", message_group=group_id)
960
+
961
+ emit_info("", message_group=group_id)
962
+ return env_vars
963
+
964
+ def _interactive_collect_cmd_args(self, requirements, group_id: str) -> dict:
965
+ """Collect command line arguments from user."""
966
+ from code_puppy.messaging import emit_prompt
967
+
968
+ cmd_args = {}
969
+ required_args = requirements.command_line_args
970
+
971
+ if not required_args:
972
+ return cmd_args
973
+
974
+ emit_info("⚡ Command Line Arguments:", message_group=group_id)
975
+
976
+ for arg_config in required_args:
977
+ name = arg_config.get("name", "")
978
+ prompt_text = arg_config.get("prompt", name)
979
+ default = arg_config.get("default", "")
980
+ required = arg_config.get("required", True)
981
+
982
+ indicator = "⚡" if required else "🔧"
983
+ label = f"{indicator} {prompt_text}"
984
+ if not required:
985
+ label += " (optional)"
986
+ if default:
987
+ label += f" [{default}]"
988
+
989
+ value = emit_prompt(f"{label}: ").strip()
990
+
991
+ if not value and default:
992
+ value = default
993
+
994
+ if value:
995
+ cmd_args[name] = value
996
+ elif required:
997
+ emit_info(f"⚠️ Required argument '{name}' left empty", message_group=group_id)
998
+
999
+ emit_info("", message_group=group_id)
1000
+ return cmd_args
1001
+
1002
+ def _interactive_confirm_installation(self, selected_server, server_name: str, env_vars: dict, cmd_args: dict, group_id: str) -> bool:
1003
+ """Show summary and confirm installation."""
1004
+ from code_puppy.messaging import emit_prompt
1005
+
1006
+ emit_info("📋 Installation Summary:", message_group=group_id)
1007
+ emit_info(f" Server: {selected_server.display_name}", message_group=group_id)
1008
+ emit_info(f" Name: {server_name}", message_group=group_id)
1009
+ emit_info(f" Type: {selected_server.type}", message_group=group_id)
1010
+
1011
+ if env_vars:
1012
+ emit_info(f" Environment variables: {len(env_vars)} set", message_group=group_id)
1013
+
1014
+ if cmd_args:
1015
+ emit_info(f" Command arguments: {len(cmd_args)} configured", message_group=group_id)
1016
+
1017
+ emit_info("", message_group=group_id)
1018
+
1019
+ confirm = emit_prompt("Install this server configuration? [Y/n]: ")
1020
+ return not confirm.lower().startswith('n')
1021
+
1022
+ def _interactive_install_server(self, selected_server, server_name: str, env_vars: dict, cmd_args: dict, group_id: str) -> bool:
1023
+ """Actually install and register the server."""
1024
+ try:
1025
+ # Get server config with command line argument overrides
1026
+ config_dict = selected_server.to_server_config(server_name, **cmd_args)
1027
+
1028
+ # Update the config with actual environment variable values
1029
+ if 'env' in config_dict:
1030
+ for env_key, env_value in config_dict['env'].items():
1031
+ # If it's a placeholder like $GITHUB_TOKEN, replace with actual value
1032
+ if env_value.startswith('$'):
1033
+ var_name = env_value[1:] # Remove the $
1034
+ if var_name in env_vars:
1035
+ config_dict['env'][env_key] = env_vars[var_name]
1036
+
1037
+ # Create and register the server
1038
+ from code_puppy.mcp import ServerConfig
1039
+
1040
+ server_config = ServerConfig(
1041
+ id=server_name,
1042
+ name=server_name,
1043
+ type=config_dict.pop('type'),
1044
+ enabled=True,
1045
+ config=config_dict
1046
+ )
1047
+
1048
+ server_id = self.manager.register_server(server_config)
1049
+
1050
+ if server_id:
1051
+ # Save to mcp_servers.json for persistence
1052
+ from code_puppy.config import MCP_SERVERS_FILE
1053
+ import json
1054
+ import os
1055
+
1056
+ if os.path.exists(MCP_SERVERS_FILE):
1057
+ with open(MCP_SERVERS_FILE, 'r') as f:
1058
+ data = json.load(f)
1059
+ servers = data.get("mcp_servers", {})
1060
+ else:
1061
+ servers = {}
1062
+ data = {"mcp_servers": servers}
1063
+
1064
+ servers[server_name] = config_dict
1065
+ servers[server_name]['type'] = server_config.type
1066
+
1067
+ os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
1068
+ with open(MCP_SERVERS_FILE, 'w') as f:
1069
+ json.dump(data, f, indent=2)
1070
+
1071
+ emit_info(f"✅ Successfully installed '{server_name}' from {selected_server.display_name}!", message_group=group_id)
1072
+ emit_info(f"Use '/mcp start {server_name}' to start the server", message_group=group_id)
1073
+ return True
1074
+ else:
1075
+ emit_info(f"❌ Failed to register server", message_group=group_id)
1076
+ return False
1077
+
1078
+ except Exception as e:
1079
+ emit_info(f"❌ Installation failed: {str(e)}", message_group=group_id)
1080
+ return False
1081
+
691
1082
  def cmd_remove(self, args: List[str]) -> None:
692
1083
  """
693
1084
  Remove an MCP server.
@@ -990,116 +1381,119 @@ class MCPCommandHandler:
990
1381
  group_id = str(uuid.uuid4())
991
1382
 
992
1383
  try:
993
- from code_puppy.mcp.server_registry_catalog import catalog
994
- from code_puppy.mcp import ServerConfig
995
- import json
1384
+ # If in TUI mode, show message to use Ctrl+T
1385
+ if is_tui_mode():
1386
+ emit_info("In TUI mode, use Ctrl+T to open the MCP Install Wizard", message_group=group_id)
1387
+ return
996
1388
 
1389
+ # In interactive mode, use the new comprehensive installer
997
1390
  if not args:
998
- emit_info("Usage: /mcp install <server-id> [custom-name]", message_group=group_id)
999
- emit_info("Use '/mcp search' to find available servers", message_group=group_id)
1391
+ # No args - launch interactive wizard
1392
+ success = self._run_interactive_install_wizard(group_id)
1393
+ if success:
1394
+ from code_puppy.agent import reload_mcp_servers
1395
+ reload_mcp_servers()
1000
1396
  return
1001
1397
 
1398
+ # Has args - install directly from catalog
1002
1399
  server_id = args[0]
1003
- custom_name = args[1] if len(args) > 1 else None
1400
+ success = self._install_from_catalog(server_id, group_id)
1401
+ if success:
1402
+ from code_puppy.agent import reload_mcp_servers
1403
+ reload_mcp_servers()
1404
+ return
1004
1405
 
1005
- # Find server in registry
1006
- template = catalog.get_by_id(server_id)
1007
- if not template:
1008
- emit_info(f"Server '{server_id}' not found in registry", message_group=group_id)
1009
-
1010
- # Suggest similar servers
1011
- suggestions = catalog.search(server_id)
1012
- if suggestions:
1013
- emit_info("Did you mean one of these?", message_group=group_id)
1014
- for s in suggestions[:5]:
1015
- emit_info(f" • {s.id} - {s.display_name}", message_group=group_id)
1016
- return
1406
+ except ImportError:
1407
+ emit_info("Server registry not available", message_group=group_id)
1408
+ except Exception as e:
1409
+ logger.error(f"Error installing server: {e}")
1410
+ emit_info(f"Installation failed: {e}", message_group=group_id)
1411
+
1412
+ def _install_from_catalog(self, server_name_or_id: str, group_id: str) -> bool:
1413
+ """Install a server directly from the catalog by name or ID."""
1414
+ try:
1415
+ from code_puppy.mcp.server_registry_catalog import catalog
1017
1416
 
1018
- # Show server details
1019
- emit_info(f"[bold cyan]Installing: {template.display_name}[/bold cyan]", message_group=group_id)
1020
- emit_info(f"[dim]{template.description}[/dim]", message_group=group_id)
1417
+ # Try to find server by ID first, then by name/search
1418
+ selected_server = catalog.get_by_id(server_name_or_id)
1419
+
1420
+ if not selected_server:
1421
+ # Try searching by name
1422
+ results = catalog.search(server_name_or_id)
1423
+ if not results:
1424
+ emit_info(f"❌ No server found matching '{server_name_or_id}'", message_group=group_id)
1425
+ emit_info("Try '/mcp add' to browse available servers", message_group=group_id)
1426
+ return False
1427
+ elif len(results) == 1:
1428
+ selected_server = results[0]
1429
+ else:
1430
+ # Multiple matches, show them
1431
+ emit_info(f"🔍 Multiple servers found matching '{server_name_or_id}':", message_group=group_id)
1432
+ for i, server in enumerate(results[:5]):
1433
+ indicators = []
1434
+ if server.verified:
1435
+ indicators.append("✓")
1436
+ if server.popular:
1437
+ indicators.append("⭐")
1438
+
1439
+ indicator_str = ''
1440
+ if indicators:
1441
+ indicator_str = ' ' + ''.join(indicators)
1442
+
1443
+ emit_info(f" {i+1}. {server.display_name}{indicator_str}", message_group=group_id)
1444
+ emit_info(f" ID: {server.id}", message_group=group_id)
1445
+
1446
+ emit_info(f"Please use the exact server ID: '/mcp add <server_id>'", message_group=group_id)
1447
+ return False
1021
1448
 
1022
- # Check requirements
1023
- if template.requires:
1024
- emit_info(f"[yellow]Requirements:[/yellow] {', '.join(template.requires)}", message_group=group_id)
1449
+ # Show what we're installing
1450
+ emit_info(f"📦 Installing: {selected_server.display_name}", message_group=group_id)
1451
+ description = selected_server.description if selected_server.description else "No description available"
1452
+ emit_info(f"Description: {description}", message_group=group_id)
1453
+ emit_info("", message_group=group_id)
1025
1454
 
1026
- # Use custom name or generate one
1027
- if not custom_name:
1028
- # Check if default name exists
1029
- existing = self.manager.registry.get_by_name(template.name)
1030
- if existing:
1031
- # Generate unique name
1032
- import time
1033
- custom_name = f"{template.name}-{int(time.time()) % 10000}"
1034
- emit_info(f"[dim]Using name: {custom_name} (original already exists)[/dim]", message_group=group_id)
1035
- else:
1036
- custom_name = template.name
1455
+ # Get custom name (default to server name)
1456
+ from code_puppy.messaging import emit_prompt
1457
+ server_name = emit_prompt(f"Enter custom name for this server [{selected_server.name}]: ").strip()
1458
+ if not server_name:
1459
+ server_name = selected_server.name
1460
+
1461
+ # Check if name already exists
1462
+ existing_server = self._find_server_id_by_name(server_name)
1463
+ if existing_server:
1464
+ override = emit_prompt(f"Server '{server_name}' already exists. Override it? [y/N]: ")
1465
+ if not override.lower().startswith('y'):
1466
+ emit_info("Installation cancelled", message_group=group_id)
1467
+ return False
1468
+
1469
+ # Configure the server with requirements
1470
+ requirements = selected_server.get_requirements()
1471
+
1472
+ # Check system requirements
1473
+ if not self._interactive_check_system_requirements(requirements, group_id):
1474
+ return False
1037
1475
 
1038
- # Convert template to server config
1039
- config_dict = template.to_server_config(custom_name)
1476
+ # Collect environment variables
1477
+ env_vars = self._interactive_collect_env_vars(requirements, group_id)
1040
1478
 
1041
- # Create ServerConfig
1042
- server_config = ServerConfig(
1043
- id=f"{custom_name}_{hash(custom_name)}",
1044
- name=custom_name,
1045
- type=config_dict.pop('type'),
1046
- enabled=True,
1047
- config=config_dict
1048
- )
1479
+ # Collect command line arguments
1480
+ cmd_args = self._interactive_collect_cmd_args(requirements, group_id)
1049
1481
 
1050
- # Register the server
1051
- server_id = self.manager.register_server(server_config)
1482
+ # Show summary and confirm
1483
+ if not self._interactive_confirm_installation(selected_server, server_name, env_vars, cmd_args, group_id):
1484
+ return False
1485
+
1486
+ # Install the server
1487
+ return self._interactive_install_server(selected_server, server_name, env_vars, cmd_args, group_id)
1052
1488
 
1053
- if server_id:
1054
- emit_info(f"✅ Installed '{custom_name}' from {template.display_name}", message_group=group_id)
1055
-
1056
- # Save to mcp_servers.json
1057
- from code_puppy.config import MCP_SERVERS_FILE
1058
- import os
1059
-
1060
- if os.path.exists(MCP_SERVERS_FILE):
1061
- with open(MCP_SERVERS_FILE, 'r') as f:
1062
- data = json.load(f)
1063
- servers = data.get("mcp_servers", {})
1064
- else:
1065
- servers = {}
1066
- data = {"mcp_servers": servers}
1067
-
1068
- servers[custom_name] = config_dict
1069
- servers[custom_name]['type'] = server_config.type
1070
-
1071
- os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
1072
- with open(MCP_SERVERS_FILE, 'w') as f:
1073
- json.dump(data, f, indent=2)
1074
-
1075
- # Show next steps
1076
- if template.example_usage:
1077
- emit_info(f"[yellow]Example:[/yellow] {template.example_usage}", message_group=group_id)
1078
-
1079
- # Check for environment variables
1080
- env_vars = []
1081
- if 'env' in config_dict:
1082
- for key, value in config_dict['env'].items():
1083
- if value.startswith('$'):
1084
- env_vars.append(value[1:])
1085
-
1086
- if env_vars:
1087
- emit_info(f"[yellow]Required environment variables:[/yellow] {', '.join(env_vars)}", message_group=group_id)
1088
- emit_info("Set these before starting the server", message_group=group_id)
1089
-
1090
- emit_info(f"Use '/mcp start {custom_name}' to start the server", message_group=group_id)
1091
-
1092
- # Reload MCP servers
1093
- from code_puppy.agent import reload_mcp_servers
1094
- reload_mcp_servers()
1095
- else:
1096
- emit_info(f"Failed to install server", message_group=group_id)
1097
-
1098
1489
  except ImportError:
1099
- emit_info("Server registry not available", message_group=group_id)
1490
+ emit_info("Server catalog not available", message_group=group_id)
1491
+ return False
1100
1492
  except Exception as e:
1101
- logger.error(f"Error installing server: {e}")
1102
- emit_info(f"Installation failed: {e}", message_group=group_id)
1493
+ import traceback
1494
+ emit_info(f"Installation failed: {str(e)}", message_group=group_id)
1495
+ emit_info(f"[dim]Error details: {traceback.format_exc()}[/dim]", message_group=group_id)
1496
+ return False
1103
1497
 
1104
1498
  def _find_server_id_by_name(self, server_name: str) -> Optional[str]:
1105
1499
  """
@@ -1173,20 +1567,20 @@ class MCPCommandHandler:
1173
1567
 
1174
1568
  display, color = state_map.get(state, ("? Unk", "dim"))
1175
1569
  return Text(display, style=color)
1176
-
1570
+
1177
1571
  def _format_uptime(self, uptime_seconds: Optional[float]) -> str:
1178
1572
  """
1179
1573
  Format uptime in a human-readable format.
1180
-
1574
+
1181
1575
  Args:
1182
1576
  uptime_seconds: Uptime in seconds, or None
1183
-
1577
+
1184
1578
  Returns:
1185
1579
  Formatted uptime string
1186
1580
  """
1187
1581
  if uptime_seconds is None or uptime_seconds <= 0:
1188
1582
  return "-"
1189
-
1583
+
1190
1584
  # Convert to readable format
1191
1585
  if uptime_seconds < 60:
1192
1586
  return f"{int(uptime_seconds)}s"
@@ -1198,7 +1592,7 @@ class MCPCommandHandler:
1198
1592
  hours = int(uptime_seconds // 3600)
1199
1593
  minutes = int((uptime_seconds % 3600) // 60)
1200
1594
  return f"{hours}h {minutes}m"
1201
-
1595
+
1202
1596
  def _show_detailed_server_status(self, server_id: str, server_name: str, group_id: str = None) -> None:
1203
1597
  """
1204
1598
  Show comprehensive status information for a specific server.
@@ -1295,4 +1689,95 @@ class MCPCommandHandler:
1295
1689
 
1296
1690
  except Exception as e:
1297
1691
  logger.error(f"Error showing detailed status for server '{server_name}': {e}")
1298
- emit_info(f"Failed to get detailed status: {e}", message_group=group_id)
1692
+ emit_info(f"Failed to get detailed status: {e}", message_group=group_id)
1693
+
1694
+ def _handle_interactive_requirements(self, template, custom_name: str, group_id: str) -> Dict:
1695
+ """Handle comprehensive requirements in interactive mode."""
1696
+ from code_puppy.messaging import emit_prompt
1697
+
1698
+ requirements = template.get_requirements()
1699
+ config_overrides = {}
1700
+
1701
+ # 1. Check system requirements
1702
+ if requirements.required_tools:
1703
+ emit_info("[bold cyan]Checking system requirements...[/bold cyan]", message_group=group_id)
1704
+ from code_puppy.mcp.system_tools import detector
1705
+
1706
+ tool_status = detector.detect_tools(requirements.required_tools)
1707
+ missing_tools = []
1708
+
1709
+ for tool_name, tool_info in tool_status.items():
1710
+ if tool_info.available:
1711
+ emit_info(f"✅ {tool_name} ({tool_info.version or 'found'})", message_group=group_id)
1712
+ else:
1713
+ emit_info(f"❌ {tool_name} - {tool_info.error}", message_group=group_id)
1714
+ missing_tools.append(tool_name)
1715
+
1716
+ if missing_tools:
1717
+ emit_info(f"[red]Missing required tools: {', '.join(missing_tools)}[/red]", message_group=group_id)
1718
+
1719
+ # Show installation suggestions
1720
+ for tool in missing_tools:
1721
+ suggestions = detector.get_installation_suggestions(tool)
1722
+ emit_info(f"Install {tool}: {suggestions[0]}", message_group=group_id)
1723
+
1724
+ proceed = emit_prompt("Continue installation anyway? (y/N): ")
1725
+ if proceed.lower() not in ['y', 'yes']:
1726
+ raise Exception("Installation cancelled due to missing requirements")
1727
+
1728
+ # 2. Environment variables
1729
+ env_vars = template.get_environment_vars()
1730
+ if env_vars:
1731
+ emit_info("[bold yellow]Environment Variables:[/bold yellow]", message_group=group_id)
1732
+
1733
+ for var in env_vars:
1734
+ import os
1735
+ if var in os.environ:
1736
+ emit_info(f"✅ {var} (already set)", message_group=group_id)
1737
+ else:
1738
+ try:
1739
+ value = emit_prompt(f"Enter {var}: ")
1740
+ if value.strip():
1741
+ os.environ[var] = value.strip()
1742
+ emit_info(f"[green]Set {var}[/green]", message_group=group_id)
1743
+ else:
1744
+ emit_info(f"[yellow]Skipped {var} (empty value)[/yellow]", message_group=group_id)
1745
+ except Exception as e:
1746
+ emit_info(f"[yellow]Failed to get {var}: {e}[/yellow]", message_group=group_id)
1747
+
1748
+ # 3. Command line arguments
1749
+ cmd_args = requirements.command_line_args
1750
+ if cmd_args:
1751
+ emit_info("[bold green]Command Line Arguments:[/bold green]", message_group=group_id)
1752
+
1753
+ for arg_config in cmd_args:
1754
+ name = arg_config.get("name", "")
1755
+ prompt_text = arg_config.get("prompt", name)
1756
+ default = arg_config.get("default", "")
1757
+ required = arg_config.get("required", True)
1758
+
1759
+ try:
1760
+ if default:
1761
+ value = emit_prompt(f"{prompt_text} (default: {default}): ")
1762
+ value = value.strip() or default
1763
+ else:
1764
+ value = emit_prompt(f"{prompt_text}: ")
1765
+ value = value.strip()
1766
+
1767
+ if value:
1768
+ config_overrides[name] = value
1769
+ emit_info(f"[green]Set {name}={value}[/green]", message_group=group_id)
1770
+ elif required:
1771
+ emit_info(f"[yellow]Required argument {name} not provided[/yellow]", message_group=group_id)
1772
+
1773
+ except Exception as e:
1774
+ emit_info(f"[yellow]Failed to get {name}: {e}[/yellow]", message_group=group_id)
1775
+
1776
+ # 4. Package dependencies (informational)
1777
+ packages = requirements.package_dependencies
1778
+ if packages:
1779
+ emit_info("[bold magenta]Package Dependencies:[/bold magenta]", message_group=group_id)
1780
+ emit_info(f"This server requires: {', '.join(packages)}", message_group=group_id)
1781
+ emit_info("These will be installed automatically when the server starts.", message_group=group_id)
1782
+
1783
+ return config_overrides