claude-mpm 5.6.1__py3-none-any.whl → 5.6.76__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/commander.py +174 -4
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/commander_parser.py +43 -10
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +300 -33
- claude_mpm/cli/startup_display.py +4 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/commander/__init__.py +6 -0
- claude_mpm/commander/adapters/__init__.py +32 -3
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +98 -1
- claude_mpm/commander/adapters/claude_code.py +32 -1
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/app.py +32 -16
- claude_mpm/commander/api/errors.py +21 -0
- claude_mpm/commander/api/routes/messages.py +11 -11
- claude_mpm/commander/api/routes/projects.py +20 -20
- claude_mpm/commander/api/routes/sessions.py +37 -26
- claude_mpm/commander/api/routes/work.py +86 -50
- claude_mpm/commander/api/schemas.py +4 -0
- claude_mpm/commander/chat/cli.py +47 -5
- claude_mpm/commander/chat/commands.py +44 -16
- claude_mpm/commander/chat/repl.py +1729 -82
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +215 -10
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +91 -1
- claude_mpm/commander/frameworks/mpm.py +9 -14
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/instance_manager.py +546 -15
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -2
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/commander/work/executor.py +38 -20
- claude_mpm/commander/workflow/event_handler.py +25 -3
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +30 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +16 -2
- claude_mpm/core/logging_utils.py +40 -16
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +37 -7
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
- claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +222 -54
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +21 -14
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +10 -9
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/server.py +6 -1
- claude_mpm/services/pm_skills_deployer.py +5 -3
- claude_mpm/services/skills/git_skill_source_manager.py +79 -8
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +17 -1
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
"""Service for
|
|
1
|
+
"""Service for managing MPM slash commands in user's Claude configuration.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
DEPRECATED: User-level commands in ~/.claude/commands/ are deprecated.
|
|
4
|
+
Project-level skills (.claude/skills/) are now the only source for commands.
|
|
5
|
+
|
|
6
|
+
This service now only handles:
|
|
7
|
+
1. Cleanup of deprecated commands from previous versions
|
|
8
|
+
2. Cleanup of stale commands that no longer exist in source
|
|
9
|
+
3. Parsing and validating YAML frontmatter (for internal use)
|
|
10
|
+
|
|
11
|
+
New command deployment is intentionally disabled - see deploy_commands_on_startup().
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
from pathlib import Path
|
|
@@ -17,20 +21,34 @@ from claude_mpm.core.logger import get_logger
|
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class CommandDeploymentService(BaseService):
|
|
20
|
-
"""Service for
|
|
24
|
+
"""Service for managing MPM slash commands (cleanup only - deployment deprecated)."""
|
|
21
25
|
|
|
22
|
-
# Deprecated commands that
|
|
26
|
+
# Deprecated commands that should be removed from ~/.claude/commands/
|
|
27
|
+
# ALL user-level commands are now deprecated - project-level skills are the only source
|
|
23
28
|
DEPRECATED_COMMANDS = [
|
|
29
|
+
# Legacy deprecated commands (historical)
|
|
24
30
|
"mpm-agents.md", # Replaced by mpm-agents-list.md
|
|
25
31
|
"mpm-auto-configure.md", # Replaced by mpm-agents-auto-configure.md
|
|
26
32
|
"mpm-config-view.md", # Replaced by mpm-config.md
|
|
27
33
|
"mpm-resume.md", # Replaced by mpm-session-resume.md
|
|
28
34
|
"mpm-ticket.md", # Replaced by mpm-ticket-view.md
|
|
29
|
-
# Removed - consolidated into /mpm-configure
|
|
30
35
|
"mpm-agents-list.md", # Consolidated into /mpm-configure
|
|
31
36
|
"mpm-agents-detect.md", # Consolidated into /mpm-configure
|
|
32
37
|
"mpm-agents-auto-configure.md", # Consolidated into /mpm-configure
|
|
33
38
|
"mpm-agents-recommend.md", # Consolidated into /mpm-configure
|
|
39
|
+
# ALL user-level commands are now deprecated (use project-level skills)
|
|
40
|
+
"mpm.md",
|
|
41
|
+
"mpm-config.md",
|
|
42
|
+
"mpm-doctor.md",
|
|
43
|
+
"mpm-help.md",
|
|
44
|
+
"mpm-init.md",
|
|
45
|
+
"mpm-monitor.md",
|
|
46
|
+
"mpm-organize.md",
|
|
47
|
+
"mpm-postmortem.md",
|
|
48
|
+
"mpm-session-resume.md",
|
|
49
|
+
"mpm-status.md",
|
|
50
|
+
"mpm-ticket-view.md",
|
|
51
|
+
"mpm-version.md",
|
|
34
52
|
]
|
|
35
53
|
|
|
36
54
|
def __init__(self):
|
|
@@ -414,33 +432,33 @@ class CommandDeploymentService(BaseService):
|
|
|
414
432
|
def deploy_commands_on_startup(force: bool = False) -> None:
|
|
415
433
|
"""Convenience function to deploy commands during startup.
|
|
416
434
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
435
|
+
DEPRECATED: User-level commands in ~/.claude/commands/ are deprecated.
|
|
436
|
+
Project-level skills should be the only source for commands.
|
|
437
|
+
|
|
438
|
+
This function now only cleans up any existing deprecated/stale commands
|
|
439
|
+
without deploying new ones.
|
|
421
440
|
|
|
422
441
|
Args:
|
|
423
|
-
force: Force deployment even if files exist
|
|
442
|
+
force: Force deployment even if files exist (ignored - deployment disabled)
|
|
424
443
|
"""
|
|
425
|
-
service = CommandDeploymentService()
|
|
426
444
|
logger = get_logger("startup")
|
|
445
|
+
logger.debug(
|
|
446
|
+
"User-level command deployment is deprecated - "
|
|
447
|
+
"project-level skills are the only command source"
|
|
448
|
+
)
|
|
427
449
|
|
|
428
|
-
#
|
|
450
|
+
# Still clean up any lingering deprecated/stale commands from previous versions
|
|
451
|
+
service = CommandDeploymentService()
|
|
452
|
+
|
|
453
|
+
# Clean up deprecated commands
|
|
429
454
|
deprecated_count = service.remove_deprecated_commands()
|
|
430
455
|
if deprecated_count > 0:
|
|
431
456
|
logger.info(f"Cleaned up {deprecated_count} deprecated command(s)")
|
|
432
457
|
|
|
433
|
-
# Clean up stale commands
|
|
458
|
+
# Clean up stale commands
|
|
434
459
|
stale_count = service.remove_stale_commands()
|
|
435
460
|
if stale_count > 0:
|
|
436
461
|
logger.info(f"Cleaned up {stale_count} stale command(s)")
|
|
437
462
|
|
|
438
|
-
#
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if result["deployed"]:
|
|
442
|
-
logger.info(f"MPM commands deployed: {', '.join(result['deployed'])}")
|
|
443
|
-
|
|
444
|
-
if result["errors"]:
|
|
445
|
-
for error in result["errors"]:
|
|
446
|
-
logger.warning(f"Command deployment issue: {error}")
|
|
463
|
+
# NOTE: Deployment of new commands is intentionally disabled.
|
|
464
|
+
# Project-level skills (.claude/skills/) are the only source for commands.
|
|
@@ -20,8 +20,13 @@ class HookInstallerService:
|
|
|
20
20
|
def __init__(self):
|
|
21
21
|
"""Initialize the hook installer service."""
|
|
22
22
|
self.logger = get_logger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
# Use project-level paths, NEVER global ~/.claude/settings.json
|
|
24
|
+
# This ensures hooks are scoped to the current project only
|
|
25
|
+
self.project_root = Path.cwd()
|
|
26
|
+
self.claude_dir = self.project_root / ".claude"
|
|
27
|
+
# Use settings.local.json for project-level hook settings
|
|
28
|
+
# Claude Code reads project-level settings from .claude/settings.local.json
|
|
29
|
+
self.settings_file = self.claude_dir / "settings.local.json"
|
|
25
30
|
|
|
26
31
|
def is_hooks_configured(self) -> bool:
|
|
27
32
|
"""Check if hooks are configured in Claude settings.
|
|
@@ -299,16 +304,77 @@ class HookInstallerService:
|
|
|
299
304
|
self.logger.debug("Creating new Claude settings")
|
|
300
305
|
|
|
301
306
|
# Configure hooks
|
|
302
|
-
|
|
303
|
-
"matcher": "*",
|
|
304
|
-
"hooks": [{"type": "command", "command": hook_script_path}],
|
|
305
|
-
}
|
|
307
|
+
new_hook_command = {"type": "command", "command": hook_script_path}
|
|
306
308
|
|
|
307
309
|
# Update settings
|
|
308
310
|
if "hooks" not in settings:
|
|
309
311
|
settings["hooks"] = {}
|
|
310
312
|
|
|
311
|
-
|
|
313
|
+
def is_our_hook(cmd: Dict[str, Any]) -> bool:
|
|
314
|
+
"""Check if a hook command belongs to claude-mpm."""
|
|
315
|
+
if cmd.get("type") != "command":
|
|
316
|
+
return False
|
|
317
|
+
command = cmd.get("command", "")
|
|
318
|
+
return (
|
|
319
|
+
"hook_wrapper.sh" in command
|
|
320
|
+
or "claude-hook-handler.sh" in command
|
|
321
|
+
or "claude-mpm" in command
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def merge_hooks_for_event(
|
|
325
|
+
existing_hooks: list, hook_command: Dict[str, Any]
|
|
326
|
+
) -> list:
|
|
327
|
+
"""Merge new hook command into existing hooks without duplication.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
existing_hooks: Current hooks configuration for an event type
|
|
331
|
+
hook_command: The claude-mpm hook command to add
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Updated hooks list with our hook merged in
|
|
335
|
+
"""
|
|
336
|
+
# Check if our hook already exists in any existing hook config
|
|
337
|
+
our_hook_exists = False
|
|
338
|
+
|
|
339
|
+
for hook_config in existing_hooks:
|
|
340
|
+
if "hooks" in hook_config and isinstance(
|
|
341
|
+
hook_config["hooks"], list
|
|
342
|
+
):
|
|
343
|
+
for hook in hook_config["hooks"]:
|
|
344
|
+
if is_our_hook(hook):
|
|
345
|
+
# Update existing hook command path (in case it changed)
|
|
346
|
+
hook["command"] = hook_command["command"]
|
|
347
|
+
our_hook_exists = True
|
|
348
|
+
break
|
|
349
|
+
if our_hook_exists:
|
|
350
|
+
break
|
|
351
|
+
|
|
352
|
+
if our_hook_exists:
|
|
353
|
+
# Our hook already exists, just return the updated list
|
|
354
|
+
return existing_hooks
|
|
355
|
+
|
|
356
|
+
# Our hook doesn't exist - need to add it
|
|
357
|
+
# Strategy: Add our hook to the first "*" matcher config, or create new
|
|
358
|
+
added = False
|
|
359
|
+
|
|
360
|
+
for hook_config in existing_hooks:
|
|
361
|
+
# Check if this config has matcher: "*"
|
|
362
|
+
if hook_config.get("matcher") == "*":
|
|
363
|
+
# Add our hook to this config's hooks array
|
|
364
|
+
if "hooks" not in hook_config:
|
|
365
|
+
hook_config["hooks"] = []
|
|
366
|
+
hook_config["hooks"].append(hook_command)
|
|
367
|
+
added = True
|
|
368
|
+
break
|
|
369
|
+
|
|
370
|
+
if not added:
|
|
371
|
+
# No suitable config found, create a new one
|
|
372
|
+
new_config = {"matcher": "*", "hooks": [hook_command]}
|
|
373
|
+
existing_hooks.append(new_config)
|
|
374
|
+
|
|
375
|
+
return existing_hooks
|
|
376
|
+
|
|
377
|
+
# Add hooks for all event types - MERGE instead of overwrite
|
|
312
378
|
for event_type in [
|
|
313
379
|
"UserPromptSubmit",
|
|
314
380
|
"PreToolUse",
|
|
@@ -316,7 +382,10 @@ class HookInstallerService:
|
|
|
316
382
|
"Stop",
|
|
317
383
|
"SubagentStop",
|
|
318
384
|
]:
|
|
319
|
-
settings["hooks"]
|
|
385
|
+
existing = settings["hooks"].get(event_type, [])
|
|
386
|
+
settings["hooks"][event_type] = merge_hooks_for_event(
|
|
387
|
+
existing, new_hook_command
|
|
388
|
+
)
|
|
320
389
|
|
|
321
390
|
# Write settings
|
|
322
391
|
with self.settings_file.open("w") as f:
|
|
@@ -10,7 +10,7 @@ MCP service installations.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import subprocess
|
|
13
|
+
import subprocess # nosec B404 - Required for MCP service management
|
|
14
14
|
import sys
|
|
15
15
|
from enum import Enum
|
|
16
16
|
from pathlib import Path
|
|
@@ -150,6 +150,86 @@ class MCPConfigManager:
|
|
|
150
150
|
|
|
151
151
|
return is_enabled
|
|
152
152
|
|
|
153
|
+
def get_registry_service_config(
|
|
154
|
+
self, service_name: str, env_overrides: Optional[Dict[str, str]] = None
|
|
155
|
+
) -> Optional[Dict]:
|
|
156
|
+
"""
|
|
157
|
+
Get configuration for a service from the MCP Service Registry.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
service_name: Name of the service
|
|
161
|
+
env_overrides: Optional environment variable overrides
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Service configuration dict or None if service not in registry
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
from .mcp_service_registry import MCPServiceRegistry
|
|
168
|
+
|
|
169
|
+
service = MCPServiceRegistry.get(service_name)
|
|
170
|
+
if not service:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
return MCPServiceRegistry.generate_config(service, env_overrides)
|
|
174
|
+
except ImportError:
|
|
175
|
+
self.logger.debug("MCP Service Registry not available")
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def filter_services_by_mcp_flag(
|
|
179
|
+
self, mcp_flag: Optional[str], all_services: Dict[str, Dict]
|
|
180
|
+
) -> Dict[str, Dict]:
|
|
181
|
+
"""
|
|
182
|
+
Filter MCP services based on the --mcp command line flag.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
mcp_flag: Comma-separated list of service names, or None for all
|
|
186
|
+
all_services: Dict of all available service configurations
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Filtered dict of service configurations
|
|
190
|
+
"""
|
|
191
|
+
if not mcp_flag:
|
|
192
|
+
return all_services
|
|
193
|
+
|
|
194
|
+
# Parse comma-separated service names
|
|
195
|
+
requested_services = {s.strip() for s in mcp_flag.split(",") if s.strip()}
|
|
196
|
+
|
|
197
|
+
# Filter services
|
|
198
|
+
filtered = {}
|
|
199
|
+
for name, config in all_services.items():
|
|
200
|
+
if name in requested_services:
|
|
201
|
+
filtered[name] = config
|
|
202
|
+
else:
|
|
203
|
+
self.logger.debug(f"MCP service '{name}' excluded by --mcp flag")
|
|
204
|
+
|
|
205
|
+
# Warn about requested services that don't exist
|
|
206
|
+
available = set(all_services.keys())
|
|
207
|
+
missing = requested_services - available
|
|
208
|
+
if missing:
|
|
209
|
+
self.logger.warning(
|
|
210
|
+
f"Requested MCP services not available: {', '.join(missing)}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return filtered
|
|
214
|
+
|
|
215
|
+
def list_available_services(self) -> list[str]:
|
|
216
|
+
"""
|
|
217
|
+
List all available MCP services from registry and static configs.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of service names
|
|
221
|
+
"""
|
|
222
|
+
services = set(self.STATIC_MCP_CONFIGS.keys())
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
from .mcp_service_registry import MCPServiceRegistry
|
|
226
|
+
|
|
227
|
+
services.update(MCPServiceRegistry.list_names())
|
|
228
|
+
except ImportError:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
return sorted(services)
|
|
232
|
+
|
|
153
233
|
def detect_service_path(self, service_name: str) -> Optional[str]:
|
|
154
234
|
"""
|
|
155
235
|
Detect the best path for an MCP service.
|
|
@@ -185,7 +265,7 @@ class MCPConfigManager:
|
|
|
185
265
|
# Choose the best candidate (prefer v1.1.0+ with MCP support)
|
|
186
266
|
for path in candidates:
|
|
187
267
|
try:
|
|
188
|
-
result = subprocess.run(
|
|
268
|
+
result = subprocess.run( # nosec B603 B607 - Controlled service help check
|
|
189
269
|
[path, "--help"],
|
|
190
270
|
capture_output=True,
|
|
191
271
|
text=True,
|
|
@@ -258,7 +338,7 @@ class MCPConfigManager:
|
|
|
258
338
|
def _check_system_path(self, service_name: str) -> Optional[str]:
|
|
259
339
|
"""Check if service is available in system PATH."""
|
|
260
340
|
try:
|
|
261
|
-
result = subprocess.run(
|
|
341
|
+
result = subprocess.run( # nosec B603 B607 - Controlled which command
|
|
262
342
|
["which", service_name],
|
|
263
343
|
capture_output=True,
|
|
264
344
|
text=True,
|
|
@@ -356,7 +436,7 @@ class MCPConfigManager:
|
|
|
356
436
|
cmd.append("--help")
|
|
357
437
|
|
|
358
438
|
# Run test command with timeout
|
|
359
|
-
result = subprocess.run(
|
|
439
|
+
result = subprocess.run( # nosec B603 - Controlled service test command
|
|
360
440
|
cmd,
|
|
361
441
|
capture_output=True,
|
|
362
442
|
text=True,
|
|
@@ -560,7 +640,7 @@ class MCPConfigManager:
|
|
|
560
640
|
# Try pipx run test
|
|
561
641
|
if shutil.which("pipx"):
|
|
562
642
|
try:
|
|
563
|
-
result = subprocess.run(
|
|
643
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pipx run command
|
|
564
644
|
["pipx", "run", service_name, "--version"],
|
|
565
645
|
capture_output=True,
|
|
566
646
|
text=True,
|
|
@@ -576,7 +656,7 @@ class MCPConfigManager:
|
|
|
576
656
|
# Try uvx if pipx run not available
|
|
577
657
|
if not use_pipx_run and shutil.which("uvx"):
|
|
578
658
|
try:
|
|
579
|
-
result = subprocess.run(
|
|
659
|
+
result = subprocess.run( # nosec B603 B607 - Controlled uvx command
|
|
580
660
|
["uvx", service_name, "--version"],
|
|
581
661
|
capture_output=True,
|
|
582
662
|
text=True,
|
|
@@ -876,7 +956,7 @@ class MCPConfigManager:
|
|
|
876
956
|
command_path = service_config.get("command", "")
|
|
877
957
|
results[service_name] = Path(command_path).exists()
|
|
878
958
|
return results
|
|
879
|
-
except Exception:
|
|
959
|
+
except Exception: # nosec B110 - Graceful fallback to empty dict
|
|
880
960
|
pass
|
|
881
961
|
return {}
|
|
882
962
|
|
|
@@ -946,7 +1026,7 @@ class MCPConfigManager:
|
|
|
946
1026
|
if shutil.which("pipx"):
|
|
947
1027
|
try:
|
|
948
1028
|
self.logger.debug(f"Attempting to install {service_name} via pipx...")
|
|
949
|
-
result = subprocess.run(
|
|
1029
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pipx install
|
|
950
1030
|
["pipx", "install", service_name],
|
|
951
1031
|
capture_output=True,
|
|
952
1032
|
text=True,
|
|
@@ -980,7 +1060,7 @@ class MCPConfigManager:
|
|
|
980
1060
|
if shutil.which("uvx"):
|
|
981
1061
|
try:
|
|
982
1062
|
self.logger.debug(f"Attempting to install {service_name} via uvx...")
|
|
983
|
-
result = subprocess.run(
|
|
1063
|
+
result = subprocess.run( # nosec B603 B607 - Controlled uvx install
|
|
984
1064
|
["uvx", "install", service_name],
|
|
985
1065
|
capture_output=True,
|
|
986
1066
|
text=True,
|
|
@@ -997,7 +1077,7 @@ class MCPConfigManager:
|
|
|
997
1077
|
# Method 3: Try pip install --user
|
|
998
1078
|
try:
|
|
999
1079
|
self.logger.debug(f"Attempting to install {service_name} via pip --user...")
|
|
1000
|
-
result = subprocess.run(
|
|
1080
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pip install
|
|
1001
1081
|
[sys.executable, "-m", "pip", "install", "--user", service_name],
|
|
1002
1082
|
capture_output=True,
|
|
1003
1083
|
text=True,
|
|
@@ -1093,7 +1173,7 @@ class MCPConfigManager:
|
|
|
1093
1173
|
self.logger.debug(
|
|
1094
1174
|
f" Testing {service_name} from installed pipx venv: {pipx_venv_bin}"
|
|
1095
1175
|
)
|
|
1096
|
-
result = subprocess.run(
|
|
1176
|
+
result = subprocess.run( # nosec B603 - Controlled service help check
|
|
1097
1177
|
[str(pipx_venv_bin), "--help"],
|
|
1098
1178
|
capture_output=True,
|
|
1099
1179
|
text=True,
|
|
@@ -1145,7 +1225,7 @@ class MCPConfigManager:
|
|
|
1145
1225
|
self.logger.debug(
|
|
1146
1226
|
f" Testing {service_name} via pipx run (not installed in venv)"
|
|
1147
1227
|
)
|
|
1148
|
-
result = subprocess.run(
|
|
1228
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pipx run command
|
|
1149
1229
|
["pipx", "run", service_name, "--help"],
|
|
1150
1230
|
capture_output=True,
|
|
1151
1231
|
text=True,
|
|
@@ -1216,7 +1296,7 @@ class MCPConfigManager:
|
|
|
1216
1296
|
self.logger.debug(f"Uninstalling {service_name}...")
|
|
1217
1297
|
|
|
1218
1298
|
# First uninstall the corrupted version
|
|
1219
|
-
uninstall_result = subprocess.run(
|
|
1299
|
+
uninstall_result = subprocess.run( # nosec B603 B607 - Controlled pipx uninstall
|
|
1220
1300
|
["pipx", "uninstall", service_name],
|
|
1221
1301
|
capture_output=True,
|
|
1222
1302
|
text=True,
|
|
@@ -1229,7 +1309,7 @@ class MCPConfigManager:
|
|
|
1229
1309
|
|
|
1230
1310
|
# Now reinstall
|
|
1231
1311
|
self.logger.debug(f"Installing fresh {service_name}...")
|
|
1232
|
-
install_result = subprocess.run(
|
|
1312
|
+
install_result = subprocess.run( # nosec B603 B607 - Controlled pipx install
|
|
1233
1313
|
["pipx", "install", service_name],
|
|
1234
1314
|
capture_output=True,
|
|
1235
1315
|
text=True,
|
|
@@ -1294,7 +1374,7 @@ class MCPConfigManager:
|
|
|
1294
1374
|
for dep in missing_deps:
|
|
1295
1375
|
try:
|
|
1296
1376
|
self.logger.debug(f" Injecting {dep} into {service_name}...")
|
|
1297
|
-
result = subprocess.run(
|
|
1377
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pipx inject
|
|
1298
1378
|
["pipx", "inject", service_name, dep],
|
|
1299
1379
|
capture_output=True,
|
|
1300
1380
|
text=True,
|
|
@@ -1348,7 +1428,7 @@ class MCPConfigManager:
|
|
|
1348
1428
|
return False
|
|
1349
1429
|
|
|
1350
1430
|
self.logger.info(f" → Uninstalling {service_name}...")
|
|
1351
|
-
uninstall_result = subprocess.run(
|
|
1431
|
+
uninstall_result = subprocess.run( # nosec B603 B607 - Controlled pipx uninstall
|
|
1352
1432
|
["pipx", "uninstall", service_name],
|
|
1353
1433
|
capture_output=True,
|
|
1354
1434
|
text=True,
|
|
@@ -1363,7 +1443,7 @@ class MCPConfigManager:
|
|
|
1363
1443
|
)
|
|
1364
1444
|
|
|
1365
1445
|
self.logger.info(f" → Installing fresh {service_name}...")
|
|
1366
|
-
install_result = subprocess.run(
|
|
1446
|
+
install_result = subprocess.run( # nosec B603 B607 - Controlled pipx install
|
|
1367
1447
|
["pipx", "install", service_name],
|
|
1368
1448
|
capture_output=True,
|
|
1369
1449
|
text=True,
|
|
@@ -1439,7 +1519,7 @@ class MCPConfigManager:
|
|
|
1439
1519
|
# Try pipx run as fallback for pipx installations
|
|
1440
1520
|
if method == "pipx":
|
|
1441
1521
|
try:
|
|
1442
|
-
result = subprocess.run(
|
|
1522
|
+
result = subprocess.run( # nosec B603 B607 - Controlled pipx command
|
|
1443
1523
|
["pipx", "run", service_name, "--version"],
|
|
1444
1524
|
capture_output=True,
|
|
1445
1525
|
text=True,
|
|
@@ -1462,7 +1542,7 @@ class MCPConfigManager:
|
|
|
1462
1542
|
]
|
|
1463
1543
|
|
|
1464
1544
|
for cmd in test_commands:
|
|
1465
|
-
result = subprocess.run(
|
|
1545
|
+
result = subprocess.run( # nosec B603 - Controlled service verification
|
|
1466
1546
|
cmd,
|
|
1467
1547
|
capture_output=True,
|
|
1468
1548
|
text=True,
|