claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
- claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/agents.py +169 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1111 -161
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +550 -94
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +500 -0
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
- claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MCP Gateway Auto-Configuration Service
|
|
3
|
-
======================================
|
|
4
|
-
|
|
5
|
-
Provides automatic MCP configuration for pipx installations with user consent.
|
|
6
|
-
Detects unconfigured MCP setups and offers one-time configuration prompts.
|
|
7
|
-
|
|
8
|
-
WHY: Users installing via pipx should have MCP work out-of-the-box with minimal
|
|
9
|
-
friction. This service detects unconfigured installations and offers automatic
|
|
10
|
-
setup with user consent.
|
|
11
|
-
|
|
12
|
-
DESIGN DECISIONS:
|
|
13
|
-
- Only prompts once (saves preference to avoid repeated prompts)
|
|
14
|
-
- Quick timeout with safe default (no configuration)
|
|
15
|
-
- Non-intrusive with environment variable override
|
|
16
|
-
- Creates backups before modifying any configuration
|
|
17
|
-
- Validates JSON before and after modifications
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
import json
|
|
21
|
-
import os
|
|
22
|
-
import sys
|
|
23
|
-
from datetime import datetime, timezone
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
from typing import Any, Dict, Optional
|
|
26
|
-
|
|
27
|
-
from claude_mpm.config.paths import paths
|
|
28
|
-
from claude_mpm.core.logger import get_logger
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class MCPAutoConfigurator:
|
|
32
|
-
"""
|
|
33
|
-
Handles automatic MCP configuration for pipx installations.
|
|
34
|
-
|
|
35
|
-
Provides a one-time prompt to configure MCP Gateway with user consent,
|
|
36
|
-
making the experience seamless for pipx users while respecting choice.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
def __init__(self):
|
|
40
|
-
"""Initialize the auto-configurator."""
|
|
41
|
-
self.logger = get_logger("MCPAutoConfig")
|
|
42
|
-
self.config_dir = paths.claude_mpm_dir_hidden
|
|
43
|
-
self.preference_file = self.config_dir / "mcp_auto_config_preference.json"
|
|
44
|
-
self.claude_config_path = Path.home() / ".claude.json"
|
|
45
|
-
|
|
46
|
-
def should_auto_configure(self) -> bool:
|
|
47
|
-
"""
|
|
48
|
-
Check if auto-configuration should be attempted.
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
True if auto-configuration should be offered, False otherwise
|
|
52
|
-
"""
|
|
53
|
-
# Check environment variable override
|
|
54
|
-
if os.environ.get("CLAUDE_MPM_NO_AUTO_CONFIG"):
|
|
55
|
-
self.logger.debug("Auto-configuration disabled via environment variable")
|
|
56
|
-
return False
|
|
57
|
-
|
|
58
|
-
# Check if already configured
|
|
59
|
-
if self._is_mcp_configured():
|
|
60
|
-
self.logger.debug("MCP already configured")
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
# Check if this is a pipx installation
|
|
64
|
-
if not self._is_pipx_installation():
|
|
65
|
-
self.logger.debug("Not a pipx installation")
|
|
66
|
-
return False
|
|
67
|
-
|
|
68
|
-
# Check if we've already asked
|
|
69
|
-
if self._has_user_preference():
|
|
70
|
-
self.logger.debug("User preference already saved")
|
|
71
|
-
return False
|
|
72
|
-
|
|
73
|
-
return True
|
|
74
|
-
|
|
75
|
-
def _is_mcp_configured(self) -> bool:
|
|
76
|
-
"""Check if MCP is already configured in Claude Code."""
|
|
77
|
-
if not self.claude_config_path.exists():
|
|
78
|
-
return False
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
with self.claude_config_path.open() as f:
|
|
82
|
-
config = json.load(f)
|
|
83
|
-
|
|
84
|
-
# Check if claude-mpm-gateway is configured
|
|
85
|
-
mcp_servers = config.get("mcpServers", {})
|
|
86
|
-
return "claude-mpm-gateway" in mcp_servers
|
|
87
|
-
|
|
88
|
-
except (OSError, json.JSONDecodeError):
|
|
89
|
-
return False
|
|
90
|
-
|
|
91
|
-
def _is_pipx_installation(self) -> bool:
|
|
92
|
-
"""Check if claude-mpm is installed via pipx."""
|
|
93
|
-
# Check if running from pipx virtual environment
|
|
94
|
-
if "pipx" in sys.executable.lower():
|
|
95
|
-
return True
|
|
96
|
-
|
|
97
|
-
# Check module path
|
|
98
|
-
try:
|
|
99
|
-
import claude_mpm
|
|
100
|
-
|
|
101
|
-
module_path = Path(claude_mpm.__file__).parent
|
|
102
|
-
if "pipx" in str(module_path):
|
|
103
|
-
return True
|
|
104
|
-
except Exception:
|
|
105
|
-
pass
|
|
106
|
-
|
|
107
|
-
# Check for pipx in PATH for claude-mpm command
|
|
108
|
-
try:
|
|
109
|
-
import platform
|
|
110
|
-
import subprocess
|
|
111
|
-
|
|
112
|
-
# Use appropriate command for OS
|
|
113
|
-
if platform.system() == "Windows":
|
|
114
|
-
cmd = ["where", "claude-mpm"]
|
|
115
|
-
else:
|
|
116
|
-
cmd = ["which", "claude-mpm"]
|
|
117
|
-
|
|
118
|
-
result = subprocess.run(
|
|
119
|
-
cmd, capture_output=True, text=True, timeout=2, check=False
|
|
120
|
-
)
|
|
121
|
-
if result.returncode == 0 and "pipx" in result.stdout:
|
|
122
|
-
return True
|
|
123
|
-
except Exception:
|
|
124
|
-
pass
|
|
125
|
-
|
|
126
|
-
return False
|
|
127
|
-
|
|
128
|
-
def _has_user_preference(self) -> bool:
|
|
129
|
-
"""Check if user has already been asked about auto-configuration."""
|
|
130
|
-
if not self.preference_file.exists():
|
|
131
|
-
return False
|
|
132
|
-
|
|
133
|
-
try:
|
|
134
|
-
with self.preference_file.open() as f:
|
|
135
|
-
prefs = json.load(f)
|
|
136
|
-
return prefs.get("asked", False)
|
|
137
|
-
except (OSError, json.JSONDecodeError):
|
|
138
|
-
return False
|
|
139
|
-
|
|
140
|
-
def _save_user_preference(self, choice: str):
|
|
141
|
-
"""Save user's preference to avoid asking again."""
|
|
142
|
-
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
143
|
-
|
|
144
|
-
prefs = {
|
|
145
|
-
"asked": True,
|
|
146
|
-
"choice": choice,
|
|
147
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try:
|
|
151
|
-
with self.preference_file.open("w") as f:
|
|
152
|
-
json.dump(prefs, f, indent=2)
|
|
153
|
-
except Exception as e:
|
|
154
|
-
self.logger.debug(f"Could not save preference: {e}")
|
|
155
|
-
|
|
156
|
-
def prompt_user(self, timeout: int = 10) -> Optional[bool]:
|
|
157
|
-
"""
|
|
158
|
-
Prompt user for auto-configuration with timeout.
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
timeout: Seconds to wait for response (default 10)
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
True if user agrees, False if declines, None if timeout
|
|
165
|
-
"""
|
|
166
|
-
print("\n" + "=" * 60, file=sys.stderr)
|
|
167
|
-
print("🔧 MCP Gateway Configuration", file=sys.stderr)
|
|
168
|
-
print("=" * 60, file=sys.stderr)
|
|
169
|
-
print(
|
|
170
|
-
"\nClaude MPM can automatically configure MCP Gateway for", file=sys.stderr
|
|
171
|
-
)
|
|
172
|
-
print(
|
|
173
|
-
"Claude Code integration. This enables advanced features:", file=sys.stderr
|
|
174
|
-
)
|
|
175
|
-
print(" • File analysis and summarization", file=sys.stderr)
|
|
176
|
-
print(" • System diagnostics", file=sys.stderr)
|
|
177
|
-
print(" • Ticket management", file=sys.stderr)
|
|
178
|
-
print(" • And more...", file=sys.stderr)
|
|
179
|
-
print("\nWould you like to configure it now? (y/n)", file=sys.stderr)
|
|
180
|
-
print(f"(Auto-declining in {timeout} seconds)", file=sys.stderr)
|
|
181
|
-
|
|
182
|
-
# Use threading for cross-platform timeout support
|
|
183
|
-
# Python 3.7+ has queue built-in - no need to check, we require 3.10+
|
|
184
|
-
import importlib.util
|
|
185
|
-
import threading
|
|
186
|
-
|
|
187
|
-
if importlib.util.find_spec("queue") is None:
|
|
188
|
-
# Extremely unlikely in Python 3.10+, but for completeness
|
|
189
|
-
pass
|
|
190
|
-
|
|
191
|
-
user_input = None
|
|
192
|
-
|
|
193
|
-
def get_input():
|
|
194
|
-
nonlocal user_input
|
|
195
|
-
try:
|
|
196
|
-
user_input = input("> ").strip().lower()
|
|
197
|
-
except (EOFError, KeyboardInterrupt):
|
|
198
|
-
user_input = "n"
|
|
199
|
-
|
|
200
|
-
# Start input thread
|
|
201
|
-
input_thread = threading.Thread(target=get_input)
|
|
202
|
-
input_thread.daemon = True
|
|
203
|
-
input_thread.start()
|
|
204
|
-
|
|
205
|
-
# Wait for input or timeout
|
|
206
|
-
input_thread.join(timeout)
|
|
207
|
-
|
|
208
|
-
if input_thread.is_alive():
|
|
209
|
-
# Timed out
|
|
210
|
-
print("\n(Timed out - declining)", file=sys.stderr)
|
|
211
|
-
return None
|
|
212
|
-
# Got input
|
|
213
|
-
return user_input in ["y", "yes"]
|
|
214
|
-
|
|
215
|
-
def auto_configure(self) -> bool:
|
|
216
|
-
"""
|
|
217
|
-
Perform automatic MCP configuration.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
True if configuration successful, False otherwise
|
|
221
|
-
"""
|
|
222
|
-
try:
|
|
223
|
-
# Create backup if config exists
|
|
224
|
-
if self.claude_config_path.exists():
|
|
225
|
-
backup_path = self._create_backup()
|
|
226
|
-
if backup_path:
|
|
227
|
-
print(f"✅ Backup created: {backup_path}", file=sys.stderr)
|
|
228
|
-
|
|
229
|
-
# Load or create configuration
|
|
230
|
-
config = self._load_or_create_config()
|
|
231
|
-
|
|
232
|
-
# Add MCP Gateway configuration
|
|
233
|
-
if "mcpServers" not in config:
|
|
234
|
-
config["mcpServers"] = {}
|
|
235
|
-
|
|
236
|
-
# Find claude-mpm executable
|
|
237
|
-
executable = self._find_claude_mpm_executable()
|
|
238
|
-
if not executable:
|
|
239
|
-
print("❌ Could not find claude-mpm executable", file=sys.stderr)
|
|
240
|
-
return False
|
|
241
|
-
|
|
242
|
-
# Configure MCP server
|
|
243
|
-
config["mcpServers"]["claude-mpm-gateway"] = {
|
|
244
|
-
"command": str(executable),
|
|
245
|
-
"args": ["mcp", "server"],
|
|
246
|
-
"env": {"MCP_MODE": "production"},
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
# Save configuration
|
|
250
|
-
with self.claude_config_path.open("w") as f:
|
|
251
|
-
json.dump(config, f, indent=2)
|
|
252
|
-
|
|
253
|
-
print(
|
|
254
|
-
f"✅ Configuration saved to: {self.claude_config_path}", file=sys.stderr
|
|
255
|
-
)
|
|
256
|
-
print("\n🎉 MCP Gateway configured successfully!", file=sys.stderr)
|
|
257
|
-
print("\nNext steps:", file=sys.stderr)
|
|
258
|
-
print("1. Restart Claude Code (if running)", file=sys.stderr)
|
|
259
|
-
print("2. Look for the MCP icon in the interface", file=sys.stderr)
|
|
260
|
-
print("3. Try @claude-mpm-gateway in a conversation", file=sys.stderr)
|
|
261
|
-
|
|
262
|
-
return True
|
|
263
|
-
|
|
264
|
-
except Exception as e:
|
|
265
|
-
self.logger.error(f"Auto-configuration failed: {e}")
|
|
266
|
-
print(f"❌ Configuration failed: {e}", file=sys.stderr)
|
|
267
|
-
print("\nYou can configure manually with:", file=sys.stderr)
|
|
268
|
-
print(" claude-mpm mcp install", file=sys.stderr)
|
|
269
|
-
return False
|
|
270
|
-
|
|
271
|
-
def _create_backup(self) -> Optional[Path]:
|
|
272
|
-
"""Create backup of existing configuration."""
|
|
273
|
-
try:
|
|
274
|
-
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
275
|
-
backup_path = self.claude_config_path.with_suffix(
|
|
276
|
-
f".backup.{timestamp}.json"
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
import shutil
|
|
280
|
-
|
|
281
|
-
shutil.copy2(self.claude_config_path, backup_path)
|
|
282
|
-
return backup_path
|
|
283
|
-
|
|
284
|
-
except Exception as e:
|
|
285
|
-
self.logger.debug(f"Could not create backup: {e}")
|
|
286
|
-
return None
|
|
287
|
-
|
|
288
|
-
def _load_or_create_config(self) -> Dict[str, Any]:
|
|
289
|
-
"""Load existing config or create new one."""
|
|
290
|
-
if self.claude_config_path.exists():
|
|
291
|
-
try:
|
|
292
|
-
with self.claude_config_path.open() as f:
|
|
293
|
-
return json.load(f)
|
|
294
|
-
except json.JSONDecodeError:
|
|
295
|
-
self.logger.warning("Existing config is invalid JSON, creating new")
|
|
296
|
-
|
|
297
|
-
return {}
|
|
298
|
-
|
|
299
|
-
def _find_claude_mpm_executable(self) -> Optional[str]:
|
|
300
|
-
"""Find the claude-mpm executable path."""
|
|
301
|
-
# Try direct command first
|
|
302
|
-
import platform
|
|
303
|
-
import subprocess
|
|
304
|
-
|
|
305
|
-
try:
|
|
306
|
-
# Use appropriate command for OS
|
|
307
|
-
if platform.system() == "Windows":
|
|
308
|
-
cmd = ["where", "claude-mpm"]
|
|
309
|
-
else:
|
|
310
|
-
cmd = ["which", "claude-mpm"]
|
|
311
|
-
|
|
312
|
-
result = subprocess.run(
|
|
313
|
-
cmd, capture_output=True, text=True, timeout=2, check=False
|
|
314
|
-
)
|
|
315
|
-
if result.returncode == 0:
|
|
316
|
-
executable_path = result.stdout.strip()
|
|
317
|
-
# On Windows, 'where' might return multiple paths
|
|
318
|
-
if platform.system() == "Windows" and "\n" in executable_path:
|
|
319
|
-
executable_path = executable_path.split("\n")[0]
|
|
320
|
-
return executable_path
|
|
321
|
-
except Exception:
|
|
322
|
-
pass
|
|
323
|
-
|
|
324
|
-
# Try to find via shutil.which (more portable)
|
|
325
|
-
import shutil
|
|
326
|
-
|
|
327
|
-
claude_mpm_path = shutil.which("claude-mpm")
|
|
328
|
-
if claude_mpm_path:
|
|
329
|
-
return claude_mpm_path
|
|
330
|
-
|
|
331
|
-
# Fallback to Python module invocation
|
|
332
|
-
return sys.executable
|
|
333
|
-
|
|
334
|
-
def run(self) -> bool:
|
|
335
|
-
"""
|
|
336
|
-
Main entry point for auto-configuration.
|
|
337
|
-
|
|
338
|
-
Returns:
|
|
339
|
-
True if configured (or already configured), False otherwise
|
|
340
|
-
"""
|
|
341
|
-
if not self.should_auto_configure():
|
|
342
|
-
return True # Already configured or not applicable
|
|
343
|
-
|
|
344
|
-
# Prompt user
|
|
345
|
-
user_choice = self.prompt_user()
|
|
346
|
-
|
|
347
|
-
# Save preference to not ask again
|
|
348
|
-
self._save_user_preference("yes" if user_choice else "no")
|
|
349
|
-
|
|
350
|
-
if user_choice:
|
|
351
|
-
return self.auto_configure()
|
|
352
|
-
if user_choice is False: # User explicitly said no
|
|
353
|
-
print("\n📝 You can configure MCP later with:", file=sys.stderr)
|
|
354
|
-
print(" claude-mpm mcp install", file=sys.stderr)
|
|
355
|
-
# If timeout (None), don't show additional message
|
|
356
|
-
return False
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def check_and_configure_mcp() -> bool:
|
|
360
|
-
"""
|
|
361
|
-
Check and potentially configure MCP for pipx installations.
|
|
362
|
-
|
|
363
|
-
This is the main entry point called during CLI initialization.
|
|
364
|
-
|
|
365
|
-
Returns:
|
|
366
|
-
True if MCP is configured (or configuration was successful), False otherwise
|
|
367
|
-
"""
|
|
368
|
-
configurator = MCPAutoConfigurator()
|
|
369
|
-
return configurator.run()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MCP Gateway Configuration Module
|
|
3
|
-
=================================
|
|
4
|
-
|
|
5
|
-
Configuration management for the MCP Gateway service.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from .config_loader import MCPConfigLoader
|
|
9
|
-
from .config_schema import MCPConfigSchema, validate_config
|
|
10
|
-
from .configuration import MCPConfiguration
|
|
11
|
-
|
|
12
|
-
__all__ = [
|
|
13
|
-
"MCPConfigLoader",
|
|
14
|
-
"MCPConfigSchema",
|
|
15
|
-
"MCPConfiguration",
|
|
16
|
-
"validate_config",
|
|
17
|
-
]
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
MCP Gateway Configuration Loader
|
|
5
|
-
================================
|
|
6
|
-
|
|
7
|
-
Handles loading and discovery of MCP configuration files.
|
|
8
|
-
|
|
9
|
-
Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
|
|
10
|
-
|
|
11
|
-
UPDATED: Migrated to use shared ConfigLoader pattern (TSK-0141)
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import os
|
|
15
|
-
from typing import List, Optional
|
|
16
|
-
|
|
17
|
-
import yaml
|
|
18
|
-
|
|
19
|
-
from claude_mpm.core.logger import get_logger
|
|
20
|
-
from claude_mpm.core.shared.config_loader import ConfigLoader, ConfigPattern
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class MCPConfigLoader:
|
|
24
|
-
"""
|
|
25
|
-
Configuration loader for MCP Gateway.
|
|
26
|
-
|
|
27
|
-
This class handles discovering and loading configuration files from
|
|
28
|
-
standard locations, supporting both user and system configurations.
|
|
29
|
-
|
|
30
|
-
WHY: We separate configuration loading from the main configuration
|
|
31
|
-
service to support multiple configuration sources and provide a clean
|
|
32
|
-
abstraction for configuration discovery.
|
|
33
|
-
|
|
34
|
-
UPDATED: Now uses shared ConfigLoader pattern for consistency (TSK-0141)
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
# MCP Gateway configuration pattern
|
|
38
|
-
MCP_CONFIG_PATTERN = ConfigPattern(
|
|
39
|
-
filenames=[
|
|
40
|
-
"mcp_gateway.yaml",
|
|
41
|
-
"mcp_gateway.yml",
|
|
42
|
-
".mcp_gateway.yaml",
|
|
43
|
-
".mcp_gateway.yml",
|
|
44
|
-
"config.yaml",
|
|
45
|
-
"config.yml",
|
|
46
|
-
],
|
|
47
|
-
search_paths=[
|
|
48
|
-
"~/.claude/mcp",
|
|
49
|
-
"~/.config/claude-mpm",
|
|
50
|
-
".",
|
|
51
|
-
"./config",
|
|
52
|
-
"./.claude",
|
|
53
|
-
"/etc/claude-mpm",
|
|
54
|
-
],
|
|
55
|
-
env_prefix="CLAUDE_MPM_MCP_",
|
|
56
|
-
defaults={"host": "localhost", "port": 3000, "debug": False, "timeout": 30},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
def __init__(self):
|
|
60
|
-
"""Initialize configuration loader."""
|
|
61
|
-
self.logger = get_logger("MCPConfigLoader")
|
|
62
|
-
self._shared_loader = ConfigLoader()
|
|
63
|
-
|
|
64
|
-
def find_config_file(self) -> Optional[Path]:
|
|
65
|
-
"""
|
|
66
|
-
Find the first available configuration file.
|
|
67
|
-
|
|
68
|
-
Searches through standard locations and returns the first
|
|
69
|
-
existing configuration file.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
Path to configuration file if found, None otherwise
|
|
73
|
-
"""
|
|
74
|
-
for config_path in self.CONFIG_SEARCH_PATHS:
|
|
75
|
-
expanded_path = config_path.expanduser()
|
|
76
|
-
if expanded_path.exists() and expanded_path.is_file():
|
|
77
|
-
self.logger.info(f"Found configuration file: {expanded_path}")
|
|
78
|
-
return expanded_path
|
|
79
|
-
|
|
80
|
-
self.logger.debug("No configuration file found in standard locations")
|
|
81
|
-
return None
|
|
82
|
-
|
|
83
|
-
def load_from_file(self, config_path: Path) -> Optional[dict]:
|
|
84
|
-
"""
|
|
85
|
-
Load configuration from a specific file.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
config_path: Path to configuration file
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
Configuration dictionary if successful, None otherwise
|
|
92
|
-
"""
|
|
93
|
-
try:
|
|
94
|
-
expanded_path = config_path.expanduser()
|
|
95
|
-
|
|
96
|
-
if not expanded_path.exists():
|
|
97
|
-
self.logger.error(f"Configuration file not found: {expanded_path}")
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
with expanded_path.open() as f:
|
|
101
|
-
config = yaml.safe_load(f)
|
|
102
|
-
|
|
103
|
-
self.logger.info(f"Configuration loaded from {expanded_path}")
|
|
104
|
-
return config or {}
|
|
105
|
-
|
|
106
|
-
except yaml.YAMLError as e:
|
|
107
|
-
self.logger.error(f"Failed to parse YAML configuration: {e}")
|
|
108
|
-
return None
|
|
109
|
-
except Exception as e:
|
|
110
|
-
self.logger.error(f"Failed to load configuration: {e}")
|
|
111
|
-
return None
|
|
112
|
-
|
|
113
|
-
def load_from_env(self) -> dict:
|
|
114
|
-
"""
|
|
115
|
-
Load configuration from environment variables.
|
|
116
|
-
|
|
117
|
-
Environment variables follow the pattern: MCP_GATEWAY_<SECTION>_<KEY>
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Configuration dictionary built from environment variables
|
|
121
|
-
"""
|
|
122
|
-
config = {}
|
|
123
|
-
prefix = "MCP_GATEWAY_"
|
|
124
|
-
|
|
125
|
-
for env_key, env_value in os.environ.items():
|
|
126
|
-
if not env_key.startswith(prefix):
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
# Parse environment variable into configuration path
|
|
130
|
-
config_path = env_key[len(prefix) :].lower().split("_")
|
|
131
|
-
|
|
132
|
-
# Build nested configuration structure
|
|
133
|
-
current = config
|
|
134
|
-
for part in config_path[:-1]:
|
|
135
|
-
if part not in current:
|
|
136
|
-
current[part] = {}
|
|
137
|
-
current = current[part]
|
|
138
|
-
|
|
139
|
-
# Set the value
|
|
140
|
-
key = config_path[-1]
|
|
141
|
-
try:
|
|
142
|
-
# Try to parse as JSON for complex types
|
|
143
|
-
import json
|
|
144
|
-
|
|
145
|
-
current[key] = json.loads(env_value)
|
|
146
|
-
except Exception:
|
|
147
|
-
# Fall back to string value
|
|
148
|
-
current[key] = env_value
|
|
149
|
-
|
|
150
|
-
self.logger.debug(f"Loaded from environment: {env_key}")
|
|
151
|
-
|
|
152
|
-
return config
|
|
153
|
-
|
|
154
|
-
def load(self, config_path: Optional[Path] = None) -> dict:
|
|
155
|
-
"""
|
|
156
|
-
Load configuration from all sources.
|
|
157
|
-
|
|
158
|
-
Loads configuration in the following priority order:
|
|
159
|
-
1. Default configuration
|
|
160
|
-
2. File configuration (if found or specified)
|
|
161
|
-
3. Environment variable overrides
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
config_path: Optional specific configuration file path
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
Merged configuration dictionary
|
|
168
|
-
"""
|
|
169
|
-
from .configuration import MCPConfiguration
|
|
170
|
-
|
|
171
|
-
if config_path:
|
|
172
|
-
# Use specific config file with shared loader
|
|
173
|
-
pattern = ConfigPattern(
|
|
174
|
-
filenames=[config_path.name],
|
|
175
|
-
search_paths=[str(config_path.parent)],
|
|
176
|
-
env_prefix=self.MCP_CONFIG_PATTERN.env_prefix,
|
|
177
|
-
defaults=MCPConfiguration.DEFAULT_CONFIG.copy(),
|
|
178
|
-
)
|
|
179
|
-
config_obj = self._shared_loader.load_config(
|
|
180
|
-
pattern, cache_key=f"mcp_{config_path}"
|
|
181
|
-
)
|
|
182
|
-
return config_obj.to_dict()
|
|
183
|
-
# Use standard MCP pattern with defaults
|
|
184
|
-
pattern = ConfigPattern(
|
|
185
|
-
filenames=self.MCP_CONFIG_PATTERN.filenames,
|
|
186
|
-
search_paths=self.MCP_CONFIG_PATTERN.search_paths,
|
|
187
|
-
env_prefix=self.MCP_CONFIG_PATTERN.env_prefix,
|
|
188
|
-
defaults=MCPConfiguration.DEFAULT_CONFIG.copy(),
|
|
189
|
-
)
|
|
190
|
-
config_obj = self._shared_loader.load_config(pattern, cache_key="mcp_gateway")
|
|
191
|
-
return config_obj.to_dict()
|
|
192
|
-
|
|
193
|
-
# Backward compatibility methods (deprecated)
|
|
194
|
-
def find_config_file(self) -> Optional[Path]:
|
|
195
|
-
"""Find configuration file using legacy method (deprecated)."""
|
|
196
|
-
import warnings
|
|
197
|
-
|
|
198
|
-
warnings.warn(
|
|
199
|
-
"find_config_file is deprecated. Use load() method instead.",
|
|
200
|
-
DeprecationWarning,
|
|
201
|
-
stacklevel=2,
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
# Use shared loader to find config file
|
|
205
|
-
return self._shared_loader._find_config_file(self.MCP_CONFIG_PATTERN)
|
|
206
|
-
|
|
207
|
-
def load_from_file(self, config_path: Path) -> Optional[dict]:
|
|
208
|
-
"""Load from file using legacy method (deprecated)."""
|
|
209
|
-
import warnings
|
|
210
|
-
|
|
211
|
-
warnings.warn(
|
|
212
|
-
"load_from_file is deprecated. Use load() method instead.",
|
|
213
|
-
DeprecationWarning,
|
|
214
|
-
stacklevel=2,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
# Use shared loader
|
|
218
|
-
return self._shared_loader._load_config_file(config_path)
|
|
219
|
-
|
|
220
|
-
def load_from_env(self, prefix: str = "CLAUDE_MPM_MCP_") -> dict:
|
|
221
|
-
"""Load from environment using legacy method (deprecated)."""
|
|
222
|
-
import warnings
|
|
223
|
-
|
|
224
|
-
warnings.warn(
|
|
225
|
-
"load_from_env is deprecated. Use load() method instead.",
|
|
226
|
-
DeprecationWarning,
|
|
227
|
-
stacklevel=2,
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
# Use shared loader
|
|
231
|
-
return self._shared_loader._load_env_config(prefix)
|
|
232
|
-
|
|
233
|
-
def _merge_configs(self, base: dict, overlay: dict) -> dict:
|
|
234
|
-
"""
|
|
235
|
-
Recursively merge two configuration dictionaries.
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
base: Base configuration
|
|
239
|
-
overlay: Configuration to merge in
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
Merged configuration
|
|
243
|
-
"""
|
|
244
|
-
result = base.copy()
|
|
245
|
-
|
|
246
|
-
for key, value in overlay.items():
|
|
247
|
-
if (
|
|
248
|
-
key in result
|
|
249
|
-
and isinstance(result[key], dict)
|
|
250
|
-
and isinstance(value, dict)
|
|
251
|
-
):
|
|
252
|
-
result[key] = self._merge_configs(result[key], value)
|
|
253
|
-
else:
|
|
254
|
-
result[key] = value
|
|
255
|
-
|
|
256
|
-
return result
|
|
257
|
-
|
|
258
|
-
def create_default_config(self, path: Path) -> bool:
|
|
259
|
-
"""
|
|
260
|
-
Create a default configuration file.
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
path: Path where to create the configuration file
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
True if file created successfully
|
|
267
|
-
"""
|
|
268
|
-
from .configuration import MCPConfiguration
|
|
269
|
-
|
|
270
|
-
try:
|
|
271
|
-
expanded_path = path.expanduser()
|
|
272
|
-
expanded_path.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
-
|
|
274
|
-
with expanded_path.open("w") as f:
|
|
275
|
-
yaml.dump(
|
|
276
|
-
MCPConfiguration.DEFAULT_CONFIG,
|
|
277
|
-
f,
|
|
278
|
-
default_flow_style=False,
|
|
279
|
-
sort_keys=True,
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
self.logger.info(f"Created default configuration at {expanded_path}")
|
|
283
|
-
return True
|
|
284
|
-
|
|
285
|
-
except Exception as e:
|
|
286
|
-
self.logger.error(f"Failed to create default configuration: {e}")
|
|
287
|
-
return False
|
|
288
|
-
|
|
289
|
-
def list_config_locations(self) -> List[str]:
|
|
290
|
-
"""
|
|
291
|
-
List all configuration file search locations.
|
|
292
|
-
|
|
293
|
-
Returns:
|
|
294
|
-
List of configuration file paths (as strings)
|
|
295
|
-
"""
|
|
296
|
-
return [str(path.expanduser()) for path in self.CONFIG_SEARCH_PATHS]
|