claude-mpm 5.1.9__py3-none-any.whl → 5.4.48__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.

Files changed (248) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  5. claude_mpm/agents/MEMORY.md +1 -1
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
  7. claude_mpm/agents/WORKFLOW.md +5 -254
  8. claude_mpm/agents/agent_loader.py +13 -44
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/frontmatter_validator.py +2 -2
  11. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  12. claude_mpm/cli/__main__.py +4 -0
  13. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  14. claude_mpm/cli/commands/agent_state_manager.py +18 -27
  15. claude_mpm/cli/commands/agents.py +9 -40
  16. claude_mpm/cli/commands/auto_configure.py +210 -25
  17. claude_mpm/cli/commands/config.py +88 -2
  18. claude_mpm/cli/commands/configure.py +1098 -159
  19. claude_mpm/cli/commands/configure_agent_display.py +25 -6
  20. claude_mpm/cli/commands/mpm_init/core.py +225 -46
  21. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  22. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  23. claude_mpm/cli/commands/postmortem.py +1 -1
  24. claude_mpm/cli/commands/profile.py +277 -0
  25. claude_mpm/cli/commands/skills.py +218 -197
  26. claude_mpm/cli/commands/summarize.py +413 -0
  27. claude_mpm/cli/executor.py +21 -3
  28. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  29. claude_mpm/cli/parsers/agents_parser.py +0 -9
  30. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  31. claude_mpm/cli/parsers/base_parser.py +12 -0
  32. claude_mpm/cli/parsers/config_parser.py +153 -83
  33. claude_mpm/cli/parsers/profile_parser.py +148 -0
  34. claude_mpm/cli/parsers/skills_parser.py +0 -5
  35. claude_mpm/cli/startup.py +876 -149
  36. claude_mpm/commands/mpm-config.md +28 -0
  37. claude_mpm/commands/mpm-doctor.md +9 -22
  38. claude_mpm/commands/mpm-help.md +5 -287
  39. claude_mpm/commands/mpm-init.md +81 -507
  40. claude_mpm/commands/mpm-monitor.md +15 -402
  41. claude_mpm/commands/mpm-organize.md +120 -0
  42. claude_mpm/commands/mpm-postmortem.md +6 -108
  43. claude_mpm/commands/mpm-session-resume.md +12 -363
  44. claude_mpm/commands/mpm-status.md +5 -69
  45. claude_mpm/commands/mpm-ticket-view.md +52 -495
  46. claude_mpm/commands/mpm-version.md +5 -107
  47. claude_mpm/config/agent_sources.py +27 -0
  48. claude_mpm/core/config.py +2 -4
  49. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  50. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  51. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  52. claude_mpm/core/framework_loader.py +4 -2
  53. claude_mpm/core/logger.py +13 -0
  54. claude_mpm/core/optimized_startup.py +59 -0
  55. claude_mpm/core/shared/config_loader.py +1 -1
  56. claude_mpm/core/socketio_pool.py +3 -3
  57. claude_mpm/core/unified_agent_registry.py +5 -15
  58. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  74. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  75. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  85. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  86. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  87. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  88. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  89. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  90. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  91. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  92. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  97. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  98. claude_mpm/hooks/memory_integration_hook.py +46 -1
  99. claude_mpm/init.py +63 -19
  100. claude_mpm/models/git_repository.py +3 -3
  101. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  102. claude_mpm/scripts/launch_monitor.py +93 -13
  103. claude_mpm/services/agents/agent_builder.py +3 -3
  104. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  105. claude_mpm/services/agents/agent_review_service.py +280 -0
  106. claude_mpm/services/agents/cache_git_manager.py +6 -6
  107. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  108. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
  109. claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
  110. claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
  111. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  112. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  113. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  114. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +247 -35
  115. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
  116. claude_mpm/services/agents/git_source_manager.py +53 -4
  117. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  118. claude_mpm/services/agents/recommender.py +5 -3
  119. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  120. claude_mpm/services/agents/sources/git_source_sync_service.py +120 -7
  121. claude_mpm/services/agents/startup_sync.py +22 -2
  122. claude_mpm/services/agents/toolchain_detector.py +10 -6
  123. claude_mpm/services/analysis/__init__.py +11 -1
  124. claude_mpm/services/analysis/clone_detector.py +1030 -0
  125. claude_mpm/services/command_deployment_service.py +81 -10
  126. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  127. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  128. claude_mpm/services/event_bus/config.py +3 -1
  129. claude_mpm/services/git/git_operations_service.py +101 -16
  130. claude_mpm/services/monitor/daemon.py +9 -2
  131. claude_mpm/services/monitor/daemon_manager.py +39 -3
  132. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  133. claude_mpm/services/monitor/server.py +698 -22
  134. claude_mpm/services/pm_skills_deployer.py +711 -0
  135. claude_mpm/services/profile_manager.py +331 -0
  136. claude_mpm/services/self_upgrade_service.py +120 -12
  137. claude_mpm/services/skills/__init__.py +3 -0
  138. claude_mpm/services/skills/git_skill_source_manager.py +130 -2
  139. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  140. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  141. claude_mpm/services/skills_deployer.py +127 -9
  142. claude_mpm/services/socketio/dashboard_server.py +1 -0
  143. claude_mpm/services/socketio/event_normalizer.py +51 -6
  144. claude_mpm/services/socketio/server/core.py +386 -108
  145. claude_mpm/services/version_control/git_operations.py +103 -0
  146. claude_mpm/skills/skill_manager.py +92 -3
  147. claude_mpm/utils/agent_dependency_loader.py +14 -2
  148. claude_mpm/utils/agent_filters.py +17 -44
  149. claude_mpm/utils/migration.py +4 -4
  150. claude_mpm/utils/robust_installer.py +47 -3
  151. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
  152. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
  153. claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
  154. claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
  155. claude_mpm-5.4.48.dist-info/licenses/LICENSE-FAQ.md +153 -0
  156. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  157. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  158. claude_mpm/agents/BASE_OPS.md +0 -219
  159. claude_mpm/agents/BASE_PM.md +0 -480
  160. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  161. claude_mpm/agents/BASE_QA.md +0 -167
  162. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  163. claude_mpm/agents/base_agent_loader.py +0 -601
  164. claude_mpm/cli/commands/agents_detect.py +0 -380
  165. claude_mpm/cli/commands/agents_recommend.py +0 -309
  166. claude_mpm/cli/ticket_cli.py +0 -35
  167. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  168. claude_mpm/commands/mpm-agents-detect.md +0 -177
  169. claude_mpm/commands/mpm-agents-list.md +0 -131
  170. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  171. claude_mpm/commands/mpm-config-view.md +0 -150
  172. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  173. claude_mpm/dashboard/analysis_runner.py +0 -455
  174. claude_mpm/dashboard/index.html +0 -13
  175. claude_mpm/dashboard/open_dashboard.py +0 -66
  176. claude_mpm/dashboard/static/css/activity.css +0 -1958
  177. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  178. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  179. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  180. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  181. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  182. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  183. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  184. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  185. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  186. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  187. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  188. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  189. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  190. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  191. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  192. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  193. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  194. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  195. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  196. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  197. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  198. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  199. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  200. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  201. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  202. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  203. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  204. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  205. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  206. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  207. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  208. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  209. claude_mpm/dashboard/templates/code_simple.html +0 -153
  210. claude_mpm/dashboard/templates/index.html +0 -606
  211. claude_mpm/dashboard/test_dashboard.html +0 -372
  212. claude_mpm/scripts/mcp_server.py +0 -75
  213. claude_mpm/scripts/mcp_wrapper.py +0 -39
  214. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  215. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  216. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  217. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  218. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  219. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  220. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  221. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  222. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  223. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  224. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  225. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  226. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  227. claude_mpm/services/mcp_gateway/main.py +0 -589
  228. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  229. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  230. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  231. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  232. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  233. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  234. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  235. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  236. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  237. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  238. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  239. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  240. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  241. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  242. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  243. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  244. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  245. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  246. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  247. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
  248. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
@@ -1,712 +0,0 @@
1
- """
2
- Simplified MCP Stdio Server
3
- ============================
4
-
5
- A proper stdio-based MCP server that communicates via JSON-RPC over stdin/stdout.
6
- This server is spawned on-demand by Claude Code/Code and exits when the connection closes.
7
-
8
- WHY: MCP servers should be simple stdio-based processes that Claude can spawn and control.
9
- They should NOT run as persistent background services with lock files.
10
-
11
- DESIGN DECISION: We follow the MCP specification exactly - read from stdin, write to stdout,
12
- use JSON-RPC protocol, and exit cleanly when stdin closes.
13
- """
14
-
15
- import asyncio
16
- import logging
17
- import os
18
- import sys
19
- import time
20
- from datetime import timezone
21
- from pathlib import Path
22
- from typing import Any, Dict
23
-
24
- # Import MCP SDK components
25
- from mcp.server import NotificationOptions, Server
26
- from mcp.server.models import InitializationOptions
27
- from mcp.server.stdio import stdio_server
28
- from mcp.types import TextContent, Tool
29
-
30
- # Import pydantic for model patching
31
- from claude_mpm.core.logger import get_logger
32
-
33
- # Ticket tools removed - using mcp-ticketer instead
34
- TICKET_TOOLS_AVAILABLE = False
35
-
36
-
37
- def apply_backward_compatibility_patches():
38
- """
39
- Apply backward compatibility patches for MCP protocol differences.
40
-
41
- This function patches the MCP Server to handle missing clientInfo
42
- in initialize requests from older Claude Code versions.
43
- """
44
- try:
45
- from mcp.server import Server
46
-
47
- logger = get_logger("MCPPatcher")
48
- logger.info(
49
- "Applying MCP Server message handling patch for backward compatibility"
50
- )
51
-
52
- # Store the original _handle_message method
53
- original_handle_message = Server._handle_message
54
-
55
- async def patched_handle_message(
56
- self, message, session, lifespan_context, raise_exceptions=False
57
- ):
58
- """Patched message handler that adds clientInfo if missing from initialize requests."""
59
- try:
60
- # Check if this is a request responder with initialize method
61
- if hasattr(message, "request") and hasattr(message.request, "method"):
62
- request = message.request
63
- if (
64
- request.method == "initialize"
65
- and hasattr(request, "params")
66
- and request.params is not None
67
- ):
68
- # Convert params to dict to check for clientInfo
69
- params_dict = request.params
70
- if hasattr(params_dict, "model_dump"):
71
- params_dict = params_dict.model_dump()
72
- elif hasattr(params_dict, "dict"):
73
- params_dict = params_dict.dict()
74
-
75
- if (
76
- isinstance(params_dict, dict)
77
- and "clientInfo" not in params_dict
78
- ):
79
- logger.info(
80
- "Adding default clientInfo for backward compatibility"
81
- )
82
-
83
- # Add default clientInfo
84
- params_dict["clientInfo"] = {
85
- "name": "claude-desktop",
86
- "version": "unknown",
87
- }
88
-
89
- # Try to update the params object
90
- if hasattr(request.params, "__dict__"):
91
- request.params.clientInfo = params_dict["clientInfo"]
92
-
93
- # Call the original handler
94
- return await original_handle_message(
95
- self, message, session, lifespan_context, raise_exceptions
96
- )
97
-
98
- except Exception as e:
99
- logger.warning(f"Error in patched message handler: {e}")
100
- # Fall back to original handler
101
- return await original_handle_message(
102
- self, message, session, lifespan_context, raise_exceptions
103
- )
104
-
105
- # Apply the patch
106
- Server._handle_message = patched_handle_message
107
- logger.info("Applied MCP Server message handling patch")
108
- return True
109
-
110
- except ImportError as e:
111
- get_logger("MCPPatcher").warning(
112
- f"Could not import MCP Server for patching: {e}"
113
- )
114
- return False
115
- except Exception as e:
116
- get_logger("MCPPatcher").error(
117
- f"Failed to apply backward compatibility patch: {e}"
118
- )
119
- return False
120
-
121
-
122
- class SimpleMCPServer:
123
- """
124
- A simple stdio-based MCP server implementation.
125
-
126
- WHY: This server follows the MCP specification for stdio communication,
127
- making it compatible with Claude Code/Code's MCP client.
128
-
129
- DESIGN DECISIONS:
130
- - No persistent state or lock files
131
- - Spawned on-demand by Claude
132
- - Communicates via stdin/stdout
133
- - Exits when connection closes
134
- - Includes backward compatibility for protocol differences
135
- """
136
-
137
- def __init__(self, name: str = "claude-mpm-gateway", version: str = "1.0.0"):
138
- """
139
- Initialize the MCP server.
140
-
141
- Args:
142
- name: Server name for identification
143
- version: Server version
144
- """
145
- self.name = name
146
- self.version = version
147
- self.logger = get_logger("MCPStdioServer")
148
- self.startup_time = time.time()
149
-
150
- # Log startup timing
151
- self.logger.info(f"Initializing MCP server {name} v{version}")
152
- start_time = time.time()
153
-
154
- # Apply backward compatibility patches before creating server
155
- apply_backward_compatibility_patches()
156
-
157
- # Create MCP server instance
158
- self.server = Server(name)
159
-
160
- # Register default tools
161
- self._register_tools()
162
-
163
- # Log initialization time
164
- init_time = time.time() - start_time
165
- self.logger.info(f"MCP server initialized in {init_time:.2f} seconds")
166
-
167
- async def _summarize_content(
168
- self, content: str, style: str, max_length: int
169
- ) -> str:
170
- """
171
- Summarize text content based on style and length constraints.
172
-
173
- Args:
174
- content: The text to summarize
175
- style: Summary style (brief, detailed, bullet_points, executive)
176
- max_length: Maximum length in words
177
-
178
- Returns:
179
- Summarized text
180
- """
181
- if not content or not content.strip():
182
- return "No content provided to summarize."
183
-
184
- # Split content into sentences for processing
185
- import re
186
-
187
- sentences = re.split(r"(?<=[.!?])\s+", content.strip())
188
-
189
- if not sentences:
190
- return content[: max_length * 5] # Rough estimate: 5 chars per word
191
-
192
- if style == "brief":
193
- # Brief: First and last portions with key sentences
194
- return self._create_brief_summary(sentences, max_length)
195
-
196
- if style == "detailed":
197
- # Detailed: More comprehensive with section preservation
198
- return self._create_detailed_summary(sentences, content, max_length)
199
-
200
- if style == "bullet_points":
201
- # Extract key points as bullet list
202
- return self._create_bullet_summary(sentences, content, max_length)
203
-
204
- if style == "executive":
205
- # Executive: Summary + key findings + recommendations
206
- return self._create_executive_summary(sentences, content, max_length)
207
-
208
- # Default to brief
209
- return self._create_brief_summary(sentences, max_length)
210
-
211
- def _create_brief_summary(self, sentences: list[str], max_length: int) -> str:
212
- """Create a brief summary by selecting most important sentences."""
213
- if not sentences:
214
- return ""
215
-
216
- # If very short summary requested, just return truncated first sentence
217
- if max_length < 10:
218
- words = sentences[0].split()[:max_length]
219
- if len(words) < len(sentences[0].split()):
220
- return " ".join(words) + "..."
221
- return " ".join(words)
222
-
223
- if len(sentences) <= 3:
224
- text = " ".join(sentences)
225
- words = text.split()
226
- if len(words) <= max_length:
227
- return text
228
- # Truncate to word limit
229
- return " ".join(words[:max_length]) + "..."
230
-
231
- # Calculate importance scores for sentences
232
- scored_sentences = []
233
- for i, sentence in enumerate(sentences):
234
- score = 0
235
-
236
- # Position scoring
237
- if i == 0: # First sentence
238
- score += 3
239
- elif i == len(sentences) - 1: # Last sentence
240
- score += 2
241
- elif i < 3: # Early sentences
242
- score += 1
243
-
244
- # Content scoring
245
- important_words = [
246
- "important",
247
- "key",
248
- "main",
249
- "critical",
250
- "essential",
251
- "summary",
252
- "conclusion",
253
- "result",
254
- "therefore",
255
- "however",
256
- ]
257
- for word in important_words:
258
- if word in sentence.lower():
259
- score += 1
260
-
261
- # Length scoring (prefer medium-length sentences)
262
- word_count = len(sentence.split())
263
- if 10 <= word_count <= 25:
264
- score += 1
265
-
266
- scored_sentences.append((score, i, sentence))
267
-
268
- # Sort by score and select top sentences
269
- scored_sentences.sort(reverse=True, key=lambda x: x[0])
270
-
271
- # Select sentences up to word limit
272
- selected = []
273
- word_count = 0
274
- for score, orig_idx, sentence in scored_sentences:
275
- sentence_words = len(sentence.split())
276
- if word_count + sentence_words <= max_length:
277
- selected.append((orig_idx, sentence))
278
- word_count += sentence_words
279
-
280
- # Sort by original order
281
- selected.sort(key=lambda x: x[0])
282
-
283
- if not selected:
284
- # If no sentences fit, truncate the first sentence
285
- words = sentences[0].split()[:max_length]
286
- if len(words) < len(sentences[0].split()):
287
- return " ".join(words) + "..."
288
- return " ".join(words)
289
-
290
- return " ".join(s[1] for s in selected)
291
-
292
- def _create_detailed_summary(
293
- self, sentences: list[str], content: str, max_length: int
294
- ) -> str:
295
- """Create a detailed summary preserving document structure."""
296
- import re
297
-
298
- # Split into paragraphs
299
- paragraphs = content.split("\n\n")
300
-
301
- if len(paragraphs) <= 2:
302
- return self._create_brief_summary(sentences, max_length)
303
-
304
- # Summarize each paragraph
305
- summary_parts = []
306
- max_length // len(paragraphs)
307
-
308
- for para in paragraphs:
309
- if not para.strip():
310
- continue
311
-
312
- para_sentences = re.split(r"(?<=[.!?])\s+", para.strip())
313
- if para_sentences:
314
- # Take first sentence of each paragraph
315
- summary_parts.append(para_sentences[0])
316
-
317
- result = " ".join(summary_parts)
318
-
319
- # Trim to word limit
320
- words = result.split()[:max_length]
321
- return " ".join(words) + ("..." if len(result.split()) > max_length else "")
322
-
323
- def _create_bullet_summary(
324
- self, sentences: list[str], content: str, max_length: int
325
- ) -> str:
326
- """Extract key points as a bullet list."""
327
- import re
328
-
329
- # Look for existing bullet points or lists
330
- bullet_patterns = [
331
- re.compile(r"^\s*[-•*]\s+(.+)$", re.MULTILINE),
332
- re.compile(r"^\s*\d+[.)]\s+(.+)$", re.MULTILINE),
333
- re.compile(r"^([A-Z][^.!?]+):(.+)$", re.MULTILINE),
334
- ]
335
-
336
- points = []
337
- for pattern in bullet_patterns:
338
- matches = pattern.findall(content)
339
- for match in matches:
340
- if isinstance(match, tuple):
341
- point = " ".join(match).strip()
342
- else:
343
- point = match.strip()
344
- if point and len(point.split()) <= 20: # Keep concise points
345
- points.append(point)
346
-
347
- # If no bullet points found, extract key sentences
348
- if not points:
349
- # Use brief summary sentences as bullet points
350
- brief = self._create_brief_summary(sentences, max_length)
351
- points = brief.split(". ")
352
-
353
- # Format as bullet list
354
- result_lines = []
355
- word_count = 0
356
- for point in points:
357
- point_words = len(point.split())
358
- if word_count + point_words <= max_length:
359
- result_lines.append(f"• {point.strip('.')}")
360
- word_count += point_words
361
-
362
- if not result_lines:
363
- return "• " + " ".join(sentences[0].split()[:max_length]) + "..."
364
-
365
- return "\n".join(result_lines)
366
-
367
- def _create_executive_summary(
368
- self, sentences: list[str], content: str, max_length: int
369
- ) -> str:
370
- """Create an executive summary with overview, findings, and recommendations."""
371
- # Allocate words across sections
372
- overview_words = max_length // 3
373
- findings_words = max_length // 3
374
- recommendations_words = max_length - overview_words - findings_words
375
-
376
- sections = []
377
-
378
- # Overview section
379
- overview = self._create_brief_summary(
380
- sentences[: len(sentences) // 2], overview_words
381
- )
382
- if overview:
383
- sections.append(f"OVERVIEW:\n{overview}")
384
-
385
- # Key Findings
386
-
387
- findings = []
388
-
389
- # Look for sentences with conclusion/result indicators
390
- conclusion_patterns = [
391
- "found",
392
- "discovered",
393
- "shows",
394
- "indicates",
395
- "reveals",
396
- "demonstrates",
397
- "proves",
398
- "confirms",
399
- "suggests",
400
- ]
401
-
402
- for sentence in sentences:
403
- if any(word in sentence.lower() for word in conclusion_patterns):
404
- findings.append(sentence)
405
- if len(" ".join(findings).split()) >= findings_words:
406
- break
407
-
408
- if findings:
409
- sections.append("\nKEY FINDINGS:\n• " + "\n• ".join(findings[:3]))
410
-
411
- # Recommendations (look for action-oriented sentences)
412
- action_patterns = [
413
- "should",
414
- "must",
415
- "need to",
416
- "recommend",
417
- "suggest",
418
- "important to",
419
- "critical to",
420
- "require",
421
- ]
422
-
423
- recommendations = []
424
- for sentence in sentences:
425
- if any(word in sentence.lower() for word in action_patterns):
426
- recommendations.append(sentence)
427
- if len(" ".join(recommendations).split()) >= recommendations_words:
428
- break
429
-
430
- if recommendations:
431
- sections.append("\nRECOMMENDATIONS:\n• " + "\n• ".join(recommendations[:3]))
432
-
433
- # If no sections were created, fall back to brief summary
434
- if not sections:
435
- return self._create_brief_summary(sentences, max_length)
436
-
437
- result = "\n".join(sections)
438
-
439
- # Ensure we don't exceed word limit
440
- words = result.split()[:max_length]
441
- return " ".join(words) + ("..." if len(result.split()) > max_length else "")
442
-
443
- def _register_tools(self):
444
- """
445
- Register MCP tools with the server.
446
-
447
- WHY: Tools are the primary way MCP servers extend Claude's capabilities.
448
- We register them using decorators on handler functions.
449
- """
450
- # Initialize unified ticket tool if available
451
- # NOTE: Defer initialization to avoid event loop issues
452
- self.unified_ticket_tool = None
453
- self._ticket_tool_initialized = False
454
-
455
- @self.server.list_tools()
456
- async def handle_list_tools() -> list[Tool]:
457
- """List available tools."""
458
- # Initialize ticket tool lazily if needed
459
- if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
460
- await self._initialize_ticket_tool()
461
-
462
- tools = [
463
- Tool(
464
- name="status",
465
- description="Get system and service status information",
466
- inputSchema={
467
- "type": "object",
468
- "properties": {
469
- "info_type": {
470
- "type": "string",
471
- "enum": ["platform", "python_version", "cwd", "all"],
472
- "description": "Type of status information to retrieve (default: all)",
473
- "default": "all",
474
- }
475
- },
476
- },
477
- ),
478
- Tool(
479
- name="document_summarizer",
480
- description="Summarize documents or text content",
481
- inputSchema={
482
- "type": "object",
483
- "properties": {
484
- "content": {
485
- "type": "string",
486
- "description": "The text/document to summarize",
487
- },
488
- "style": {
489
- "type": "string",
490
- "enum": [
491
- "brief",
492
- "detailed",
493
- "bullet_points",
494
- "executive",
495
- ],
496
- "description": "Summary style",
497
- "default": "brief",
498
- },
499
- "max_length": {
500
- "type": "integer",
501
- "description": "Maximum length of summary in words",
502
- "default": 150,
503
- },
504
- },
505
- "required": ["content"],
506
- },
507
- ),
508
- ]
509
-
510
- # Add unified ticket tool if available
511
- if self.unified_ticket_tool:
512
- tool_def = self.unified_ticket_tool.get_definition()
513
- tools.append(
514
- Tool(
515
- name=tool_def.name,
516
- description=tool_def.description,
517
- inputSchema=tool_def.input_schema,
518
- )
519
- )
520
-
521
- self.logger.info(f"Listing {len(tools)} available tools")
522
- return tools
523
-
524
- @self.server.call_tool()
525
- async def handle_call_tool(
526
- name: str, arguments: Dict[str, Any]
527
- ) -> list[TextContent]:
528
- """Handle tool invocation."""
529
- self.logger.info(f"Invoking tool: {name} with arguments: {arguments}")
530
-
531
- try:
532
- if name == "status":
533
- info_type = arguments.get("info_type", "all")
534
-
535
- if info_type == "platform":
536
- import platform
537
-
538
- result = f"Platform: {platform.system()} {platform.release()}"
539
- elif info_type == "python_version":
540
- import sys
541
-
542
- result = f"Python: {sys.version}"
543
- elif info_type == "cwd":
544
- result = f"Working Directory: {Path.cwd()}"
545
- elif info_type == "all":
546
- import datetime
547
- import platform
548
- import sys
549
-
550
- result = (
551
- f"=== System Status ===\n"
552
- f"Platform: {platform.system()} {platform.release()}\n"
553
- f"Python: {sys.version.split()[0]}\n"
554
- f"Working Directory: {Path.cwd()}\n"
555
- f"Server: {self.name} v{self.version}\n"
556
- f"Timestamp: {datetime.datetime.now(timezone.utc).isoformat()}\n"
557
- f"Tools Available: status, document_summarizer{', ticket' if self.unified_ticket_tool else ''}"
558
- )
559
- else:
560
- result = f"Unknown info type: {info_type}"
561
-
562
- elif name == "document_summarizer":
563
- content = arguments.get("content", "")
564
- style = arguments.get("style", "brief")
565
- max_length = arguments.get("max_length", 150)
566
-
567
- result = await self._summarize_content(content, style, max_length)
568
-
569
- elif name == "ticket":
570
- # Initialize ticket tool lazily if needed
571
- if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
572
- await self._initialize_ticket_tool()
573
-
574
- if self.unified_ticket_tool:
575
- # Handle unified ticket tool invocations
576
- from claude_mpm.services.mcp_gateway.core.interfaces import (
577
- MCPToolInvocation,
578
- )
579
-
580
- invocation = MCPToolInvocation(
581
- tool_name=name,
582
- parameters=arguments,
583
- request_id=f"req_{name}_{id(arguments)}",
584
- )
585
-
586
- tool_result = await self.unified_ticket_tool.invoke(invocation)
587
-
588
- if tool_result.success:
589
- result = (
590
- tool_result.data
591
- if isinstance(tool_result.data, str)
592
- else str(tool_result.data)
593
- )
594
- else:
595
- result = f"Error: {tool_result.error}"
596
- else:
597
- result = "Ticket tool not available"
598
-
599
- else:
600
- result = f"Unknown tool: {name}"
601
-
602
- self.logger.info(f"Tool {name} completed successfully")
603
- return [TextContent(type="text", text=result)]
604
-
605
- except Exception as e:
606
- error_msg = f"Error executing tool {name}: {e!s}"
607
- self.logger.error(error_msg)
608
- return [TextContent(type="text", text=error_msg)]
609
-
610
- async def _initialize_ticket_tool(self):
611
- """
612
- Initialize the unified ticket tool asynchronously.
613
-
614
- This is called lazily when the tool is first needed,
615
- ensuring an event loop is available.
616
- """
617
- if self._ticket_tool_initialized or not TICKET_TOOLS_AVAILABLE:
618
- return
619
-
620
- try:
621
- self.logger.info("Initializing unified ticket tool...")
622
- # Ticket tools removed - using mcp-ticketer instead
623
- self.unified_ticket_tool = None
624
- # If the tool has an async init method, call it
625
- if hasattr(self.unified_ticket_tool, "initialize"):
626
- await self.unified_ticket_tool.initialize()
627
- self._ticket_tool_initialized = True
628
- self.logger.info("Unified ticket tool initialized successfully")
629
- except Exception as e:
630
- self.logger.warning(f"Failed to initialize unified ticket tool: {e}")
631
- self.unified_ticket_tool = None
632
- self._ticket_tool_initialized = True # Mark as attempted
633
-
634
- async def run(self):
635
- """
636
- Run the MCP server using stdio communication with backward compatibility.
637
-
638
- WHY: This is the main entry point that sets up stdio communication
639
- and runs the server until the connection is closed. The backward
640
- compatibility patches are applied during server initialization.
641
- """
642
- try:
643
- self.logger.info(f"Starting {self.name} v{self.version}")
644
-
645
- # Run the server with stdio transport
646
- async with stdio_server() as (read_stream, write_stream):
647
- self.logger.info("Stdio connection established")
648
-
649
- # Create initialization options
650
- init_options = InitializationOptions(
651
- server_name=self.name,
652
- server_version=self.version,
653
- capabilities=self.server.get_capabilities(
654
- notification_options=NotificationOptions(),
655
- experimental_capabilities={},
656
- ),
657
- )
658
-
659
- # Run the server (with patches already applied)
660
- await self.server.run(read_stream, write_stream, init_options)
661
-
662
- self.logger.info("Server shutting down normally")
663
-
664
- except Exception as e:
665
- self.logger.error(f"Server error: {e}")
666
- raise
667
-
668
-
669
- async def main():
670
- """
671
- Main entry point for the MCP stdio server.
672
-
673
- WHY: This function creates and runs the server instance.
674
- It's called when the script is executed directly.
675
- """
676
- # Configure logging to stderr so it doesn't interfere with stdio protocol
677
- logging.basicConfig(
678
- level=logging.INFO,
679
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
680
- stream=sys.stderr,
681
- force=True, # Force reconfiguration even if already configured
682
- )
683
-
684
- # Ensure all loggers output to stderr
685
- for logger_name in logging.Logger.manager.loggerDict:
686
- logger = logging.getLogger(logger_name)
687
- for handler in logger.handlers[:]:
688
- # Remove any handlers that might write to stdout
689
- if hasattr(handler, "stream") and handler.stream == sys.stdout:
690
- logger.removeHandler(handler)
691
-
692
- # Create and run server
693
- server = SimpleMCPServer()
694
- await server.run()
695
-
696
-
697
- def main_sync():
698
- """Synchronous entry point for use as a console script."""
699
- import os
700
-
701
- # Disable telemetry by default
702
- os.environ.setdefault("DISABLE_TELEMETRY", "1")
703
- asyncio.run(main())
704
-
705
-
706
- if __name__ == "__main__":
707
- import os
708
-
709
- # Disable telemetry by default
710
- os.environ.setdefault("DISABLE_TELEMETRY", "1")
711
- # Run the async main function
712
- main_sync()