claude-mpm 5.1.9__py3-none-any.whl → 5.4.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/agents.py +0 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1097 -158
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +550 -94
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +500 -0
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +82 -161
- claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.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()
|