claude-mpm 4.0.16__py3-none-any.whl → 4.0.19__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/templates/qa.json +23 -11
- claude_mpm/cli/__init__.py +62 -0
- claude_mpm/cli/commands/mcp_install_commands.py +62 -5
- claude_mpm/cli/commands/mcp_server_commands.py +60 -79
- claude_mpm/scripts/mcp_server.py +68 -0
- claude_mpm/scripts/mcp_wrapper.py +39 -0
- claude_mpm/services/agent_capabilities_service.py +1 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +8 -0
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +312 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +315 -0
- claude_mpm/services/mcp_gateway/main.py +7 -0
- claude_mpm/services/mcp_gateway/server/stdio_server.py +145 -29
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +453 -0
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/RECORD +20 -15
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/entry_points.txt +1 -0
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.16.dist-info → claude_mpm-4.0.19.dist-info}/top_level.txt +0 -0
|
@@ -13,6 +13,7 @@ use JSON-RPC protocol, and exit cleanly when stdin closes.
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
16
|
+
import json
|
|
16
17
|
import logging
|
|
17
18
|
import sys
|
|
18
19
|
from typing import Any, Dict, List, Optional
|
|
@@ -23,6 +24,9 @@ from mcp.server.models import InitializationOptions
|
|
|
23
24
|
from mcp.server.stdio import stdio_server
|
|
24
25
|
from mcp.types import TextContent, Tool
|
|
25
26
|
|
|
27
|
+
# Import pydantic for model patching
|
|
28
|
+
from pydantic import BaseModel
|
|
29
|
+
|
|
26
30
|
from claude_mpm.core.logger import get_logger
|
|
27
31
|
|
|
28
32
|
# Import unified ticket tool if available
|
|
@@ -36,6 +40,75 @@ except ImportError:
|
|
|
36
40
|
TICKET_TOOLS_AVAILABLE = False
|
|
37
41
|
|
|
38
42
|
|
|
43
|
+
def apply_backward_compatibility_patches():
|
|
44
|
+
"""
|
|
45
|
+
Apply backward compatibility patches for MCP protocol differences.
|
|
46
|
+
|
|
47
|
+
This function patches the MCP Server to handle missing clientInfo
|
|
48
|
+
in initialize requests from older Claude Desktop versions.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
from mcp.server import Server
|
|
52
|
+
|
|
53
|
+
logger = get_logger("MCPPatcher")
|
|
54
|
+
logger.info("Applying MCP Server message handling patch for backward compatibility")
|
|
55
|
+
|
|
56
|
+
# Store the original _handle_message method
|
|
57
|
+
original_handle_message = Server._handle_message
|
|
58
|
+
|
|
59
|
+
async def patched_handle_message(self, message, session, lifespan_context, raise_exceptions=False):
|
|
60
|
+
"""Patched message handler that adds clientInfo if missing from initialize requests."""
|
|
61
|
+
try:
|
|
62
|
+
# Check if this is a request responder with initialize method
|
|
63
|
+
if hasattr(message, 'request') and hasattr(message.request, 'method'):
|
|
64
|
+
request = message.request
|
|
65
|
+
if (request.method == 'initialize' and
|
|
66
|
+
hasattr(request, 'params') and
|
|
67
|
+
request.params is not None):
|
|
68
|
+
|
|
69
|
+
# Convert params to dict to check for clientInfo
|
|
70
|
+
params_dict = request.params
|
|
71
|
+
if hasattr(params_dict, 'model_dump'):
|
|
72
|
+
params_dict = params_dict.model_dump()
|
|
73
|
+
elif hasattr(params_dict, 'dict'):
|
|
74
|
+
params_dict = params_dict.dict()
|
|
75
|
+
|
|
76
|
+
if isinstance(params_dict, dict) and 'clientInfo' not in params_dict:
|
|
77
|
+
logger.info("Adding default clientInfo for backward compatibility")
|
|
78
|
+
|
|
79
|
+
# Add default clientInfo
|
|
80
|
+
params_dict['clientInfo'] = {
|
|
81
|
+
'name': 'claude-desktop',
|
|
82
|
+
'version': 'unknown'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Try to update the params object
|
|
86
|
+
if hasattr(request.params, '__dict__'):
|
|
87
|
+
request.params.clientInfo = params_dict['clientInfo']
|
|
88
|
+
|
|
89
|
+
# Call the original handler
|
|
90
|
+
return await original_handle_message(self, message, session, lifespan_context, raise_exceptions)
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.warning(f"Error in patched message handler: {e}")
|
|
94
|
+
# Fall back to original handler
|
|
95
|
+
return await original_handle_message(self, message, session, lifespan_context, raise_exceptions)
|
|
96
|
+
|
|
97
|
+
# Apply the patch
|
|
98
|
+
Server._handle_message = patched_handle_message
|
|
99
|
+
logger.info("Applied MCP Server message handling patch")
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
except ImportError as e:
|
|
103
|
+
get_logger("MCPPatcher").warning(f"Could not import MCP Server for patching: {e}")
|
|
104
|
+
return False
|
|
105
|
+
except Exception as e:
|
|
106
|
+
get_logger("MCPPatcher").error(f"Failed to apply backward compatibility patch: {e}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
39
112
|
class SimpleMCPServer:
|
|
40
113
|
"""
|
|
41
114
|
A simple stdio-based MCP server implementation.
|
|
@@ -48,6 +121,7 @@ class SimpleMCPServer:
|
|
|
48
121
|
- Spawned on-demand by Claude
|
|
49
122
|
- Communicates via stdin/stdout
|
|
50
123
|
- Exits when connection closes
|
|
124
|
+
- Includes backward compatibility for protocol differences
|
|
51
125
|
"""
|
|
52
126
|
|
|
53
127
|
def __init__(self, name: str = "claude-mpm-gateway", version: str = "1.0.0"):
|
|
@@ -62,6 +136,9 @@ class SimpleMCPServer:
|
|
|
62
136
|
self.version = version
|
|
63
137
|
self.logger = get_logger("MCPStdioServer")
|
|
64
138
|
|
|
139
|
+
# Apply backward compatibility patches before creating server
|
|
140
|
+
apply_backward_compatibility_patches()
|
|
141
|
+
|
|
65
142
|
# Create MCP server instance
|
|
66
143
|
self.server = Server(name)
|
|
67
144
|
|
|
@@ -356,19 +433,40 @@ class SimpleMCPServer:
|
|
|
356
433
|
We register them using decorators on handler functions.
|
|
357
434
|
"""
|
|
358
435
|
# Initialize unified ticket tool if available
|
|
436
|
+
# NOTE: Defer initialization to avoid event loop issues
|
|
359
437
|
self.unified_ticket_tool = None
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
438
|
+
self._ticket_tool_initialized = False
|
|
439
|
+
|
|
440
|
+
async def _initialize_ticket_tool(self):
|
|
441
|
+
"""
|
|
442
|
+
Initialize the unified ticket tool asynchronously.
|
|
443
|
+
|
|
444
|
+
This is called lazily when the tool is first needed,
|
|
445
|
+
ensuring an event loop is available.
|
|
446
|
+
"""
|
|
447
|
+
if self._ticket_tool_initialized or not TICKET_TOOLS_AVAILABLE:
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
self.logger.info("Initializing unified ticket tool...")
|
|
452
|
+
self.unified_ticket_tool = UnifiedTicketTool()
|
|
453
|
+
# If the tool has an async init method, call it
|
|
454
|
+
if hasattr(self.unified_ticket_tool, 'initialize'):
|
|
455
|
+
await self.unified_ticket_tool.initialize()
|
|
456
|
+
self._ticket_tool_initialized = True
|
|
457
|
+
self.logger.info("Unified ticket tool initialized successfully")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
self.logger.warning(f"Failed to initialize unified ticket tool: {e}")
|
|
460
|
+
self.unified_ticket_tool = None
|
|
461
|
+
self._ticket_tool_initialized = True # Mark as attempted
|
|
368
462
|
|
|
369
463
|
@self.server.list_tools()
|
|
370
464
|
async def handle_list_tools() -> List[Tool]:
|
|
371
465
|
"""List available tools."""
|
|
466
|
+
# Initialize ticket tool lazily if needed
|
|
467
|
+
if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
|
|
468
|
+
await self._initialize_ticket_tool()
|
|
469
|
+
|
|
372
470
|
tools = [
|
|
373
471
|
Tool(
|
|
374
472
|
name="echo",
|
|
@@ -594,28 +692,35 @@ class SimpleMCPServer:
|
|
|
594
692
|
|
|
595
693
|
result = await self._summarize_content(content, style, max_length)
|
|
596
694
|
|
|
597
|
-
elif name == "ticket"
|
|
598
|
-
#
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
695
|
+
elif name == "ticket":
|
|
696
|
+
# Initialize ticket tool lazily if needed
|
|
697
|
+
if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
|
|
698
|
+
await self._initialize_ticket_tool()
|
|
699
|
+
|
|
700
|
+
if self.unified_ticket_tool:
|
|
701
|
+
# Handle unified ticket tool invocations
|
|
702
|
+
from claude_mpm.services.mcp_gateway.core.interfaces import (
|
|
703
|
+
MCPToolInvocation,
|
|
704
|
+
)
|
|
602
705
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
706
|
+
invocation = MCPToolInvocation(
|
|
707
|
+
tool_name=name,
|
|
708
|
+
parameters=arguments,
|
|
709
|
+
request_id=f"req_{name}_{id(arguments)}",
|
|
710
|
+
)
|
|
608
711
|
|
|
609
|
-
|
|
712
|
+
tool_result = await self.unified_ticket_tool.invoke(invocation)
|
|
610
713
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
714
|
+
if tool_result.success:
|
|
715
|
+
result = (
|
|
716
|
+
tool_result.data
|
|
717
|
+
if isinstance(tool_result.data, str)
|
|
718
|
+
else str(tool_result.data)
|
|
719
|
+
)
|
|
720
|
+
else:
|
|
721
|
+
result = f"Error: {tool_result.error}"
|
|
617
722
|
else:
|
|
618
|
-
result =
|
|
723
|
+
result = "Ticket tool not available"
|
|
619
724
|
|
|
620
725
|
else:
|
|
621
726
|
result = f"Unknown tool: {name}"
|
|
@@ -628,12 +733,14 @@ class SimpleMCPServer:
|
|
|
628
733
|
self.logger.error(error_msg)
|
|
629
734
|
return [TextContent(type="text", text=error_msg)]
|
|
630
735
|
|
|
736
|
+
|
|
631
737
|
async def run(self):
|
|
632
738
|
"""
|
|
633
|
-
Run the MCP server using stdio communication.
|
|
739
|
+
Run the MCP server using stdio communication with backward compatibility.
|
|
634
740
|
|
|
635
741
|
WHY: This is the main entry point that sets up stdio communication
|
|
636
|
-
and runs the server until the connection is closed.
|
|
742
|
+
and runs the server until the connection is closed. The backward
|
|
743
|
+
compatibility patches are applied during server initialization.
|
|
637
744
|
"""
|
|
638
745
|
try:
|
|
639
746
|
self.logger.info(f"Starting {self.name} v{self.version}")
|
|
@@ -652,7 +759,7 @@ class SimpleMCPServer:
|
|
|
652
759
|
),
|
|
653
760
|
)
|
|
654
761
|
|
|
655
|
-
# Run the server
|
|
762
|
+
# Run the server (with patches already applied)
|
|
656
763
|
await self.server.run(read_stream, write_stream, init_options)
|
|
657
764
|
|
|
658
765
|
self.logger.info("Server shutting down normally")
|
|
@@ -674,7 +781,16 @@ async def main():
|
|
|
674
781
|
level=logging.INFO,
|
|
675
782
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
676
783
|
stream=sys.stderr,
|
|
784
|
+
force=True # Force reconfiguration even if already configured
|
|
677
785
|
)
|
|
786
|
+
|
|
787
|
+
# Ensure all loggers output to stderr
|
|
788
|
+
for logger_name in logging.Logger.manager.loggerDict:
|
|
789
|
+
logger = logging.getLogger(logger_name)
|
|
790
|
+
for handler in logger.handlers[:]:
|
|
791
|
+
# Remove any handlers that might write to stdout
|
|
792
|
+
if hasattr(handler, 'stream') and handler.stream == sys.stdout:
|
|
793
|
+
logger.removeHandler(handler)
|
|
678
794
|
|
|
679
795
|
# Create and run server
|
|
680
796
|
server = SimpleMCPServer()
|