claude-mpm 5.4.85__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/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- 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/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- 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/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/run_parser.py +10 -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 +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -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/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +124 -0
- claude_mpm/commander/chat/repl.py +1957 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- 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 +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +868 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- 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/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +403 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- 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 +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- 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 +75 -77
- claude_mpm/hooks/session_resume_hook.py +89 -1
- 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 +22 -15
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Context Usage Tracker Service.
|
|
2
|
+
|
|
3
|
+
WHY: Track cumulative token usage across Claude Code hook invocations to prevent
|
|
4
|
+
context window exhaustion and enable intelligent auto-pause behavior.
|
|
5
|
+
|
|
6
|
+
DESIGN DECISIONS:
|
|
7
|
+
- File-based persistence (hooks run in separate processes)
|
|
8
|
+
- Atomic file operations using StateStorage
|
|
9
|
+
- Threshold detection at 70% (caution), 85% (warning), 90% (auto_pause), 95% (critical)
|
|
10
|
+
- Session-scoped tracking with reset capability
|
|
11
|
+
- Compatible with 200k context budget for Claude Sonnet 4.5
|
|
12
|
+
|
|
13
|
+
USAGE:
|
|
14
|
+
tracker = ContextUsageTracker()
|
|
15
|
+
state = tracker.update_usage(input_tokens=15000, output_tokens=2000)
|
|
16
|
+
if tracker.should_auto_pause():
|
|
17
|
+
# Trigger auto-pause workflow
|
|
18
|
+
pass
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from dataclasses import asdict, dataclass, field
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from claude_mpm.core.logger import get_logger
|
|
27
|
+
from claude_mpm.storage.state_storage import StateStorage
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ContextUsageState:
|
|
34
|
+
"""State tracking for cumulative context/token usage.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
session_id: Unique session identifier
|
|
38
|
+
cumulative_input_tokens: Total input tokens across all hook invocations
|
|
39
|
+
cumulative_output_tokens: Total output tokens across all hook invocations
|
|
40
|
+
cache_creation_tokens: Total tokens spent creating prompt cache
|
|
41
|
+
cache_read_tokens: Total tokens read from prompt cache
|
|
42
|
+
percentage_used: Percentage of 200k context budget used (0.0-100.0)
|
|
43
|
+
threshold_reached: Highest threshold crossed ('caution', 'warning', 'auto_pause', 'critical')
|
|
44
|
+
auto_pause_active: Whether auto-pause has been triggered
|
|
45
|
+
last_updated: ISO timestamp of last update
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
session_id: str
|
|
49
|
+
cumulative_input_tokens: int = 0
|
|
50
|
+
cumulative_output_tokens: int = 0
|
|
51
|
+
cache_creation_tokens: int = 0
|
|
52
|
+
cache_read_tokens: int = 0
|
|
53
|
+
percentage_used: float = 0.0
|
|
54
|
+
threshold_reached: Optional[str] = None
|
|
55
|
+
auto_pause_active: bool = False
|
|
56
|
+
last_updated: str = field(
|
|
57
|
+
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ContextUsageTracker:
|
|
62
|
+
"""Track cumulative context/token usage across hook invocations.
|
|
63
|
+
|
|
64
|
+
Features:
|
|
65
|
+
- Cumulative tracking across multiple API calls
|
|
66
|
+
- File-based persistence for cross-process state sharing
|
|
67
|
+
- Atomic file operations for concurrent safety
|
|
68
|
+
- Threshold detection (70%, 85%, 90%, 95%)
|
|
69
|
+
- Auto-pause triggering at 90%+ usage
|
|
70
|
+
- Session management with reset capability
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Claude Sonnet 4.5 context window
|
|
74
|
+
CONTEXT_BUDGET = 200000
|
|
75
|
+
|
|
76
|
+
# Threshold levels for warnings and auto-pause
|
|
77
|
+
THRESHOLDS = {
|
|
78
|
+
"caution": 0.70, # Yellow warning
|
|
79
|
+
"warning": 0.85, # Orange warning
|
|
80
|
+
"auto_pause": 0.90, # Trigger auto-pause
|
|
81
|
+
"critical": 0.95, # Red critical alert
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def __init__(self, project_path: Optional[Path] = None):
|
|
85
|
+
"""Initialize context usage tracker.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
project_path: Project root path (default: current directory)
|
|
89
|
+
"""
|
|
90
|
+
self.project_path = (project_path or Path.cwd()).resolve()
|
|
91
|
+
self.state_dir = self.project_path / ".claude-mpm" / "state"
|
|
92
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
self.state_file = self.state_dir / "context-usage.json"
|
|
94
|
+
|
|
95
|
+
# Use StateStorage for atomic operations
|
|
96
|
+
self.storage = StateStorage(self.state_dir)
|
|
97
|
+
|
|
98
|
+
logger.debug(f"ContextUsageTracker initialized: {self.state_file}")
|
|
99
|
+
|
|
100
|
+
def update_usage(
|
|
101
|
+
self,
|
|
102
|
+
input_tokens: int,
|
|
103
|
+
output_tokens: int,
|
|
104
|
+
cache_creation: int = 0,
|
|
105
|
+
cache_read: int = 0,
|
|
106
|
+
) -> ContextUsageState:
|
|
107
|
+
"""Update cumulative usage from API response.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
input_tokens: Input tokens from this API call
|
|
111
|
+
output_tokens: Output tokens from this API call
|
|
112
|
+
cache_creation: Cache creation tokens (optional)
|
|
113
|
+
cache_read: Cache read tokens (optional)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Updated context usage state
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If token counts are negative
|
|
120
|
+
"""
|
|
121
|
+
if any(
|
|
122
|
+
t < 0 for t in [input_tokens, output_tokens, cache_creation, cache_read]
|
|
123
|
+
):
|
|
124
|
+
raise ValueError("Token counts cannot be negative")
|
|
125
|
+
|
|
126
|
+
# Load current state
|
|
127
|
+
state = self._load_state()
|
|
128
|
+
|
|
129
|
+
# Update cumulative counters
|
|
130
|
+
state.cumulative_input_tokens += input_tokens
|
|
131
|
+
state.cumulative_output_tokens += output_tokens
|
|
132
|
+
state.cache_creation_tokens += cache_creation
|
|
133
|
+
state.cache_read_tokens += cache_read
|
|
134
|
+
|
|
135
|
+
# Calculate total effective tokens (input + output, cache read is "free")
|
|
136
|
+
total_tokens = state.cumulative_input_tokens + state.cumulative_output_tokens
|
|
137
|
+
state.percentage_used = (total_tokens / self.CONTEXT_BUDGET) * 100
|
|
138
|
+
|
|
139
|
+
# Check thresholds
|
|
140
|
+
state.threshold_reached = self.check_thresholds(state)
|
|
141
|
+
|
|
142
|
+
# Activate auto-pause if threshold reached
|
|
143
|
+
if state.threshold_reached in {"auto_pause", "critical"}:
|
|
144
|
+
state.auto_pause_active = True
|
|
145
|
+
|
|
146
|
+
# Update timestamp
|
|
147
|
+
state.last_updated = datetime.now(timezone.utc).isoformat()
|
|
148
|
+
|
|
149
|
+
# Persist state atomically
|
|
150
|
+
self._save_state(state)
|
|
151
|
+
|
|
152
|
+
logger.debug(
|
|
153
|
+
f"Usage updated: {total_tokens}/{self.CONTEXT_BUDGET} tokens "
|
|
154
|
+
f"({state.percentage_used:.1f}%), threshold: {state.threshold_reached}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return state
|
|
158
|
+
|
|
159
|
+
def check_thresholds(
|
|
160
|
+
self, state: Optional[ContextUsageState] = None
|
|
161
|
+
) -> Optional[str]:
|
|
162
|
+
"""Check which threshold (if any) has been exceeded.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
state: Optional state to check (uses current state if None)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Highest threshold exceeded ('caution', 'warning', 'auto_pause', 'critical')
|
|
169
|
+
or None if no thresholds exceeded
|
|
170
|
+
"""
|
|
171
|
+
if state is None:
|
|
172
|
+
state = self.get_current_state()
|
|
173
|
+
|
|
174
|
+
percentage = state.percentage_used / 100 # Convert to 0.0-1.0
|
|
175
|
+
|
|
176
|
+
# Check thresholds in descending order (highest first)
|
|
177
|
+
for threshold_name in ["critical", "auto_pause", "warning", "caution"]:
|
|
178
|
+
if percentage >= self.THRESHOLDS[threshold_name]:
|
|
179
|
+
return threshold_name
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def should_auto_pause(self) -> bool:
|
|
184
|
+
"""Check if auto-pause should be triggered.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if 90%+ context budget used
|
|
188
|
+
"""
|
|
189
|
+
state = self.get_current_state()
|
|
190
|
+
return state.percentage_used >= (self.THRESHOLDS["auto_pause"] * 100)
|
|
191
|
+
|
|
192
|
+
def get_current_state(self) -> ContextUsageState:
|
|
193
|
+
"""Get current usage state without modifying.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Current context usage state
|
|
197
|
+
"""
|
|
198
|
+
return self._load_state()
|
|
199
|
+
|
|
200
|
+
def reset_session(self, new_session_id: str) -> None:
|
|
201
|
+
"""Reset tracking for a new session.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
new_session_id: New session identifier
|
|
205
|
+
"""
|
|
206
|
+
state = ContextUsageState(session_id=new_session_id)
|
|
207
|
+
self._save_state(state)
|
|
208
|
+
logger.info(f"Context usage reset for new session: {new_session_id}")
|
|
209
|
+
|
|
210
|
+
def _load_state(self) -> ContextUsageState:
|
|
211
|
+
"""Load state from persistence file.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Loaded state or default state if file doesn't exist/is corrupted
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
if not self.state_file.exists():
|
|
218
|
+
# Generate initial session ID
|
|
219
|
+
session_id = (
|
|
220
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
221
|
+
)
|
|
222
|
+
logger.debug("No state file found, creating default state")
|
|
223
|
+
return ContextUsageState(session_id=session_id)
|
|
224
|
+
|
|
225
|
+
# Load JSON state
|
|
226
|
+
data = self.storage.read_json(self.state_file)
|
|
227
|
+
|
|
228
|
+
if data is None:
|
|
229
|
+
logger.warning("Failed to read state file, using default state")
|
|
230
|
+
session_id = (
|
|
231
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
232
|
+
)
|
|
233
|
+
return ContextUsageState(session_id=session_id)
|
|
234
|
+
|
|
235
|
+
# Reconstruct ContextUsageState from dict
|
|
236
|
+
return ContextUsageState(**data)
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Error loading state, using default: {e}")
|
|
240
|
+
session_id = (
|
|
241
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
242
|
+
)
|
|
243
|
+
return ContextUsageState(session_id=session_id)
|
|
244
|
+
|
|
245
|
+
def _save_state(self, state: ContextUsageState) -> None:
|
|
246
|
+
"""Save state to persistence file atomically.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
state: Context usage state to persist
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
RuntimeError: If atomic write fails
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
# Convert dataclass to dict
|
|
256
|
+
state_dict = asdict(state)
|
|
257
|
+
|
|
258
|
+
# Atomic write using StateStorage
|
|
259
|
+
if not self.storage.write_json(state_dict, self.state_file, atomic=True):
|
|
260
|
+
raise RuntimeError(f"Failed to write state to {self.state_file}")
|
|
261
|
+
|
|
262
|
+
logger.debug(f"State saved: {self.state_file}")
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Error saving state: {e}")
|
|
266
|
+
raise RuntimeError(f"Failed to persist context usage state: {e}") from e
|
|
267
|
+
|
|
268
|
+
def get_usage_summary(self) -> dict:
|
|
269
|
+
"""Get human-readable usage summary.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dictionary with usage statistics
|
|
273
|
+
"""
|
|
274
|
+
state = self.get_current_state()
|
|
275
|
+
total_tokens = state.cumulative_input_tokens + state.cumulative_output_tokens
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"session_id": state.session_id,
|
|
279
|
+
"total_tokens": total_tokens,
|
|
280
|
+
"budget": self.CONTEXT_BUDGET,
|
|
281
|
+
"percentage_used": round(state.percentage_used, 2),
|
|
282
|
+
"threshold_reached": state.threshold_reached,
|
|
283
|
+
"auto_pause_active": state.auto_pause_active,
|
|
284
|
+
"breakdown": {
|
|
285
|
+
"input_tokens": state.cumulative_input_tokens,
|
|
286
|
+
"output_tokens": state.cumulative_output_tokens,
|
|
287
|
+
"cache_creation_tokens": state.cache_creation_tokens,
|
|
288
|
+
"cache_read_tokens": state.cache_read_tokens,
|
|
289
|
+
},
|
|
290
|
+
"last_updated": state.last_updated,
|
|
291
|
+
}
|
|
@@ -6,7 +6,8 @@ Integrates with session management and response tracking infrastructure.
|
|
|
6
6
|
Triggers:
|
|
7
7
|
- model_context_window_exceeded (stop_reason)
|
|
8
8
|
- Manual pause command
|
|
9
|
-
- 95% token threshold reached
|
|
9
|
+
- 95% token threshold reached (critical)
|
|
10
|
+
- 90% token threshold reached (auto-pause)
|
|
10
11
|
- Session end with high token usage (>85%)
|
|
11
12
|
|
|
12
13
|
Design Principles:
|
|
@@ -71,6 +72,7 @@ class ResumeLogGenerator:
|
|
|
71
72
|
thresholds = self.config.get("context_management", {}).get("thresholds", {})
|
|
72
73
|
self.threshold_caution = thresholds.get("caution", 0.70)
|
|
73
74
|
self.threshold_warning = thresholds.get("warning", 0.85)
|
|
75
|
+
self.threshold_auto_pause = thresholds.get("auto_pause", 0.90)
|
|
74
76
|
self.threshold_critical = thresholds.get("critical", 0.95)
|
|
75
77
|
|
|
76
78
|
logger.info(
|
|
@@ -96,14 +98,14 @@ class ResumeLogGenerator:
|
|
|
96
98
|
if not self.enabled or not self.auto_generate:
|
|
97
99
|
return manual_trigger # Only generate on manual trigger if auto is disabled
|
|
98
100
|
|
|
99
|
-
# Trigger conditions
|
|
101
|
+
# Trigger conditions (ordered by severity)
|
|
100
102
|
triggers = [
|
|
101
103
|
stop_reason == "max_tokens",
|
|
102
104
|
stop_reason == "model_context_window_exceeded",
|
|
103
105
|
manual_trigger,
|
|
104
|
-
token_usage_pct and token_usage_pct >= self.threshold_critical,
|
|
105
|
-
token_usage_pct
|
|
106
|
-
and token_usage_pct >= self.threshold_warning, #
|
|
106
|
+
token_usage_pct and token_usage_pct >= self.threshold_critical, # 95%
|
|
107
|
+
token_usage_pct and token_usage_pct >= self.threshold_auto_pause, # 90%
|
|
108
|
+
token_usage_pct and token_usage_pct >= self.threshold_warning, # 85%
|
|
107
109
|
]
|
|
108
110
|
|
|
109
111
|
should_gen = any(triggers)
|
|
@@ -121,6 +123,22 @@ class ResumeLogGenerator:
|
|
|
121
123
|
|
|
122
124
|
return should_gen
|
|
123
125
|
|
|
126
|
+
def should_auto_pause(self, token_usage_pct: Optional[float]) -> bool:
|
|
127
|
+
"""Check if auto-pause threshold (90%) has been reached.
|
|
128
|
+
|
|
129
|
+
This is a convenience method to check specifically for the 90% threshold
|
|
130
|
+
which triggers automatic session pausing.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
token_usage_pct: Current token usage percentage (0.0-1.0)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if auto-pause threshold has been reached
|
|
137
|
+
"""
|
|
138
|
+
if token_usage_pct is None:
|
|
139
|
+
return False
|
|
140
|
+
return token_usage_pct >= self.threshold_auto_pause
|
|
141
|
+
|
|
124
142
|
def generate_from_session_state(
|
|
125
143
|
self,
|
|
126
144
|
session_id: str,
|
|
@@ -427,6 +445,7 @@ class ResumeLogGenerator:
|
|
|
427
445
|
"thresholds": {
|
|
428
446
|
"caution": f"{self.threshold_caution:.0%}",
|
|
429
447
|
"warning": f"{self.threshold_warning:.0%}",
|
|
448
|
+
"auto_pause": f"{self.threshold_auto_pause:.0%}",
|
|
430
449
|
"critical": f"{self.threshold_critical:.0%}",
|
|
431
450
|
},
|
|
432
451
|
}
|
|
@@ -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,
|