claude-mpm 4.0.17__py3-none-any.whl → 4.0.20__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/__main__.py +4 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
- claude_mpm/agents/OUTPUT_STYLE.md +84 -0
- claude_mpm/agents/templates/qa.json +24 -12
- claude_mpm/cli/__init__.py +85 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/mcp_install_commands.py +62 -5
- claude_mpm/cli/commands/mcp_server_commands.py +60 -79
- claude_mpm/cli/commands/memory.py +32 -5
- claude_mpm/cli/commands/run.py +33 -6
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/utils.py +17 -4
- claude_mpm/core/base_service.py +1 -1
- claude_mpm/core/config.py +70 -5
- claude_mpm/core/framework_loader.py +342 -31
- claude_mpm/core/interactive_session.py +55 -1
- claude_mpm/core/oneshot_session.py +7 -1
- claude_mpm/core/output_style_manager.py +468 -0
- claude_mpm/core/unified_paths.py +190 -21
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
- claude_mpm/init.py +1 -0
- claude_mpm/scripts/mcp_server.py +68 -0
- claude_mpm/scripts/mcp_wrapper.py +39 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +151 -7
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
- claude_mpm/services/agents/memory/__init__.py +0 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +737 -43
- claude_mpm/services/agents/memory/content_manager.py +144 -14
- claude_mpm/services/agents/memory/template_generator.py +7 -354
- 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 +184 -176
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +453 -0
- claude_mpm/services/subprocess_launcher_service.py +5 -0
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/METADATA +1 -1
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/RECORD +45 -38
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/entry_points.txt +1 -0
- claude_mpm/services/agents/memory/analyzer.py +0 -430
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/top_level.txt +0 -0
|
@@ -13,9 +13,10 @@ 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
|
-
from typing import Any, Dict,
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
19
20
|
|
|
20
21
|
# Import MCP SDK components
|
|
21
22
|
from mcp.server import NotificationOptions, Server
|
|
@@ -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
|
|
|
@@ -113,7 +190,7 @@ class SimpleMCPServer:
|
|
|
113
190
|
# Default to brief
|
|
114
191
|
return self._create_brief_summary(sentences, max_length)
|
|
115
192
|
|
|
116
|
-
def _create_brief_summary(self, sentences:
|
|
193
|
+
def _create_brief_summary(self, sentences: list[str], max_length: int) -> str:
|
|
117
194
|
"""Create a brief summary by selecting most important sentences."""
|
|
118
195
|
if not sentences:
|
|
119
196
|
return ""
|
|
@@ -195,7 +272,7 @@ class SimpleMCPServer:
|
|
|
195
272
|
return " ".join(s[1] for s in selected)
|
|
196
273
|
|
|
197
274
|
def _create_detailed_summary(
|
|
198
|
-
self, sentences:
|
|
275
|
+
self, sentences: list[str], content: str, max_length: int
|
|
199
276
|
) -> str:
|
|
200
277
|
"""Create a detailed summary preserving document structure."""
|
|
201
278
|
import re
|
|
@@ -226,7 +303,7 @@ class SimpleMCPServer:
|
|
|
226
303
|
return " ".join(words) + ("..." if len(result.split()) > max_length else "")
|
|
227
304
|
|
|
228
305
|
def _create_bullet_summary(
|
|
229
|
-
self, sentences:
|
|
306
|
+
self, sentences: list[str], content: str, max_length: int
|
|
230
307
|
) -> str:
|
|
231
308
|
"""Extract key points as a bullet list."""
|
|
232
309
|
import re
|
|
@@ -270,7 +347,7 @@ class SimpleMCPServer:
|
|
|
270
347
|
return "\n".join(result_lines)
|
|
271
348
|
|
|
272
349
|
def _create_executive_summary(
|
|
273
|
-
self, sentences:
|
|
350
|
+
self, sentences: list[str], content: str, max_length: int
|
|
274
351
|
) -> str:
|
|
275
352
|
"""Create an executive summary with overview, findings, and recommendations."""
|
|
276
353
|
# Allocate words across sections
|
|
@@ -356,84 +433,35 @@ 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
|
-
self.unified_ticket_tool = UnifiedTicketTool()
|
|
363
|
-
# Initialize the unified ticket tool
|
|
364
|
-
asyncio.create_task(self.unified_ticket_tool.initialize())
|
|
365
|
-
except Exception as e:
|
|
366
|
-
self.logger.warning(f"Failed to initialize unified ticket tool: {e}")
|
|
367
|
-
self.unified_ticket_tool = None
|
|
368
|
-
|
|
438
|
+
self._ticket_tool_initialized = False
|
|
439
|
+
|
|
369
440
|
@self.server.list_tools()
|
|
370
|
-
async def handle_list_tools() ->
|
|
441
|
+
async def handle_list_tools() -> list[Tool]:
|
|
371
442
|
"""List available tools."""
|
|
443
|
+
# Initialize ticket tool lazily if needed
|
|
444
|
+
if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
|
|
445
|
+
await self._initialize_ticket_tool()
|
|
446
|
+
|
|
372
447
|
tools = [
|
|
373
448
|
Tool(
|
|
374
|
-
name="
|
|
375
|
-
description="
|
|
376
|
-
inputSchema={
|
|
377
|
-
"type": "object",
|
|
378
|
-
"properties": {
|
|
379
|
-
"message": {
|
|
380
|
-
"type": "string",
|
|
381
|
-
"description": "Message to echo",
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
"required": ["message"],
|
|
385
|
-
},
|
|
386
|
-
),
|
|
387
|
-
Tool(
|
|
388
|
-
name="calculator",
|
|
389
|
-
description="Perform basic arithmetic calculations",
|
|
390
|
-
inputSchema={
|
|
391
|
-
"type": "object",
|
|
392
|
-
"properties": {
|
|
393
|
-
"expression": {
|
|
394
|
-
"type": "string",
|
|
395
|
-
"description": "Mathematical expression to evaluate",
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
"required": ["expression"],
|
|
399
|
-
},
|
|
400
|
-
),
|
|
401
|
-
Tool(
|
|
402
|
-
name="system_info",
|
|
403
|
-
description="Get system information",
|
|
449
|
+
name="status",
|
|
450
|
+
description="Get system and service status information",
|
|
404
451
|
inputSchema={
|
|
405
452
|
"type": "object",
|
|
406
453
|
"properties": {
|
|
407
454
|
"info_type": {
|
|
408
455
|
"type": "string",
|
|
409
|
-
"enum": ["platform", "python_version", "cwd"],
|
|
410
|
-
"description": "Type of
|
|
456
|
+
"enum": ["platform", "python_version", "cwd", "all"],
|
|
457
|
+
"description": "Type of status information to retrieve (default: all)",
|
|
458
|
+
"default": "all",
|
|
411
459
|
}
|
|
412
460
|
},
|
|
413
|
-
"required": ["info_type"],
|
|
414
|
-
},
|
|
415
|
-
),
|
|
416
|
-
Tool(
|
|
417
|
-
name="run_command",
|
|
418
|
-
description="Execute a shell command",
|
|
419
|
-
inputSchema={
|
|
420
|
-
"type": "object",
|
|
421
|
-
"properties": {
|
|
422
|
-
"command": {
|
|
423
|
-
"type": "string",
|
|
424
|
-
"description": "Shell command to execute",
|
|
425
|
-
},
|
|
426
|
-
"timeout": {
|
|
427
|
-
"type": "number",
|
|
428
|
-
"description": "Command timeout in seconds",
|
|
429
|
-
"default": 30,
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
"required": ["command"],
|
|
433
461
|
},
|
|
434
462
|
),
|
|
435
463
|
Tool(
|
|
436
|
-
name="
|
|
464
|
+
name="document_summarizer",
|
|
437
465
|
description="Summarize documents or text content",
|
|
438
466
|
inputSchema={
|
|
439
467
|
"type": "object",
|
|
@@ -478,60 +506,17 @@ class SimpleMCPServer:
|
|
|
478
506
|
self.logger.info(f"Listing {len(tools)} available tools")
|
|
479
507
|
return tools
|
|
480
508
|
|
|
509
|
+
|
|
481
510
|
@self.server.call_tool()
|
|
482
511
|
async def handle_call_tool(
|
|
483
512
|
name: str, arguments: Dict[str, Any]
|
|
484
|
-
) ->
|
|
513
|
+
) -> list[TextContent]:
|
|
485
514
|
"""Handle tool invocation."""
|
|
486
515
|
self.logger.info(f"Invoking tool: {name} with arguments: {arguments}")
|
|
487
516
|
|
|
488
517
|
try:
|
|
489
|
-
if name == "
|
|
490
|
-
|
|
491
|
-
result = f"Echo: {message}"
|
|
492
|
-
|
|
493
|
-
elif name == "calculator":
|
|
494
|
-
expression = arguments.get("expression", "")
|
|
495
|
-
try:
|
|
496
|
-
# Safe evaluation of mathematical expressions
|
|
497
|
-
import ast
|
|
498
|
-
import operator as op
|
|
499
|
-
|
|
500
|
-
# Supported operators
|
|
501
|
-
ops = {
|
|
502
|
-
ast.Add: op.add,
|
|
503
|
-
ast.Sub: op.sub,
|
|
504
|
-
ast.Mult: op.mul,
|
|
505
|
-
ast.Div: op.truediv,
|
|
506
|
-
ast.Pow: op.pow,
|
|
507
|
-
ast.Mod: op.mod,
|
|
508
|
-
ast.USub: op.neg,
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
def eval_expr(expr):
|
|
512
|
-
"""Safely evaluate mathematical expression."""
|
|
513
|
-
|
|
514
|
-
def _eval(node):
|
|
515
|
-
if isinstance(node, ast.Constant):
|
|
516
|
-
return node.value
|
|
517
|
-
elif isinstance(node, ast.BinOp):
|
|
518
|
-
return ops[type(node.op)](
|
|
519
|
-
_eval(node.left), _eval(node.right)
|
|
520
|
-
)
|
|
521
|
-
elif isinstance(node, ast.UnaryOp):
|
|
522
|
-
return ops[type(node.op)](_eval(node.operand))
|
|
523
|
-
else:
|
|
524
|
-
raise TypeError(f"Unsupported operation: {node}")
|
|
525
|
-
|
|
526
|
-
return _eval(ast.parse(expr, mode="eval").body)
|
|
527
|
-
|
|
528
|
-
result_value = eval_expr(expression)
|
|
529
|
-
result = f"{expression} = {result_value}"
|
|
530
|
-
except Exception as e:
|
|
531
|
-
result = f"Error evaluating expression: {str(e)}"
|
|
532
|
-
|
|
533
|
-
elif name == "system_info":
|
|
534
|
-
info_type = arguments.get("info_type", "platform")
|
|
518
|
+
if name == "status":
|
|
519
|
+
info_type = arguments.get("info_type", "all")
|
|
535
520
|
|
|
536
521
|
if info_type == "platform":
|
|
537
522
|
import platform
|
|
@@ -545,77 +530,60 @@ class SimpleMCPServer:
|
|
|
545
530
|
import os
|
|
546
531
|
|
|
547
532
|
result = f"Working Directory: {os.getcwd()}"
|
|
533
|
+
elif info_type == "all":
|
|
534
|
+
import platform
|
|
535
|
+
import sys
|
|
536
|
+
import os
|
|
537
|
+
import datetime
|
|
538
|
+
|
|
539
|
+
result = (
|
|
540
|
+
f"=== System Status ===\n"
|
|
541
|
+
f"Platform: {platform.system()} {platform.release()}\n"
|
|
542
|
+
f"Python: {sys.version.split()[0]}\n"
|
|
543
|
+
f"Working Directory: {os.getcwd()}\n"
|
|
544
|
+
f"Server: {self.name} v{self.version}\n"
|
|
545
|
+
f"Timestamp: {datetime.datetime.now().isoformat()}\n"
|
|
546
|
+
f"Tools Available: status, document_summarizer{', ticket' if self.unified_ticket_tool else ''}"
|
|
547
|
+
)
|
|
548
548
|
else:
|
|
549
549
|
result = f"Unknown info type: {info_type}"
|
|
550
550
|
|
|
551
|
-
elif name == "
|
|
552
|
-
command = arguments.get("command", "")
|
|
553
|
-
timeout = arguments.get("timeout", 30)
|
|
554
|
-
|
|
555
|
-
import shlex
|
|
556
|
-
import subprocess
|
|
557
|
-
|
|
558
|
-
try:
|
|
559
|
-
# Split command string into a list to avoid shell injection
|
|
560
|
-
command_parts = shlex.split(command)
|
|
561
|
-
|
|
562
|
-
# Use create_subprocess_exec instead of create_subprocess_shell
|
|
563
|
-
# to prevent command injection vulnerabilities
|
|
564
|
-
proc = await asyncio.create_subprocess_exec(
|
|
565
|
-
*command_parts,
|
|
566
|
-
stdout=subprocess.PIPE,
|
|
567
|
-
stderr=subprocess.PIPE,
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
stdout, stderr = await asyncio.wait_for(
|
|
571
|
-
proc.communicate(), timeout=timeout
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
if proc.returncode == 0:
|
|
575
|
-
result = (
|
|
576
|
-
stdout.decode()
|
|
577
|
-
if stdout
|
|
578
|
-
else "Command completed successfully"
|
|
579
|
-
)
|
|
580
|
-
else:
|
|
581
|
-
result = f"Command failed with code {proc.returncode}: {stderr.decode()}"
|
|
582
|
-
except asyncio.TimeoutError:
|
|
583
|
-
result = f"Command timed out after {timeout} seconds"
|
|
584
|
-
except ValueError as e:
|
|
585
|
-
# Handle shlex parsing errors (e.g., unmatched quotes)
|
|
586
|
-
result = f"Invalid command syntax: {str(e)}"
|
|
587
|
-
except Exception as e:
|
|
588
|
-
result = f"Error running command: {str(e)}"
|
|
589
|
-
|
|
590
|
-
elif name == "summarize_document":
|
|
551
|
+
elif name == "document_summarizer":
|
|
591
552
|
content = arguments.get("content", "")
|
|
592
553
|
style = arguments.get("style", "brief")
|
|
593
554
|
max_length = arguments.get("max_length", 150)
|
|
594
555
|
|
|
595
556
|
result = await self._summarize_content(content, style, max_length)
|
|
596
557
|
|
|
597
|
-
elif name == "ticket"
|
|
598
|
-
#
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
558
|
+
elif name == "ticket":
|
|
559
|
+
# Initialize ticket tool lazily if needed
|
|
560
|
+
if not self._ticket_tool_initialized and TICKET_TOOLS_AVAILABLE:
|
|
561
|
+
await self._initialize_ticket_tool()
|
|
562
|
+
|
|
563
|
+
if self.unified_ticket_tool:
|
|
564
|
+
# Handle unified ticket tool invocations
|
|
565
|
+
from claude_mpm.services.mcp_gateway.core.interfaces import (
|
|
566
|
+
MCPToolInvocation,
|
|
567
|
+
)
|
|
602
568
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
569
|
+
invocation = MCPToolInvocation(
|
|
570
|
+
tool_name=name,
|
|
571
|
+
parameters=arguments,
|
|
572
|
+
request_id=f"req_{name}_{id(arguments)}",
|
|
573
|
+
)
|
|
608
574
|
|
|
609
|
-
|
|
575
|
+
tool_result = await self.unified_ticket_tool.invoke(invocation)
|
|
610
576
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
577
|
+
if tool_result.success:
|
|
578
|
+
result = (
|
|
579
|
+
tool_result.data
|
|
580
|
+
if isinstance(tool_result.data, str)
|
|
581
|
+
else str(tool_result.data)
|
|
582
|
+
)
|
|
583
|
+
else:
|
|
584
|
+
result = f"Error: {tool_result.error}"
|
|
617
585
|
else:
|
|
618
|
-
result =
|
|
586
|
+
result = "Ticket tool not available"
|
|
619
587
|
|
|
620
588
|
else:
|
|
621
589
|
result = f"Unknown tool: {name}"
|
|
@@ -628,12 +596,37 @@ class SimpleMCPServer:
|
|
|
628
596
|
self.logger.error(error_msg)
|
|
629
597
|
return [TextContent(type="text", text=error_msg)]
|
|
630
598
|
|
|
599
|
+
|
|
600
|
+
async def _initialize_ticket_tool(self):
|
|
601
|
+
"""
|
|
602
|
+
Initialize the unified ticket tool asynchronously.
|
|
603
|
+
|
|
604
|
+
This is called lazily when the tool is first needed,
|
|
605
|
+
ensuring an event loop is available.
|
|
606
|
+
"""
|
|
607
|
+
if self._ticket_tool_initialized or not TICKET_TOOLS_AVAILABLE:
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
try:
|
|
611
|
+
self.logger.info("Initializing unified ticket tool...")
|
|
612
|
+
self.unified_ticket_tool = UnifiedTicketTool()
|
|
613
|
+
# If the tool has an async init method, call it
|
|
614
|
+
if hasattr(self.unified_ticket_tool, 'initialize'):
|
|
615
|
+
await self.unified_ticket_tool.initialize()
|
|
616
|
+
self._ticket_tool_initialized = True
|
|
617
|
+
self.logger.info("Unified ticket tool initialized successfully")
|
|
618
|
+
except Exception as e:
|
|
619
|
+
self.logger.warning(f"Failed to initialize unified ticket tool: {e}")
|
|
620
|
+
self.unified_ticket_tool = None
|
|
621
|
+
self._ticket_tool_initialized = True # Mark as attempted
|
|
622
|
+
|
|
631
623
|
async def run(self):
|
|
632
624
|
"""
|
|
633
|
-
Run the MCP server using stdio communication.
|
|
625
|
+
Run the MCP server using stdio communication with backward compatibility.
|
|
634
626
|
|
|
635
627
|
WHY: This is the main entry point that sets up stdio communication
|
|
636
|
-
and runs the server until the connection is closed.
|
|
628
|
+
and runs the server until the connection is closed. The backward
|
|
629
|
+
compatibility patches are applied during server initialization.
|
|
637
630
|
"""
|
|
638
631
|
try:
|
|
639
632
|
self.logger.info(f"Starting {self.name} v{self.version}")
|
|
@@ -652,7 +645,7 @@ class SimpleMCPServer:
|
|
|
652
645
|
),
|
|
653
646
|
)
|
|
654
647
|
|
|
655
|
-
# Run the server
|
|
648
|
+
# Run the server (with patches already applied)
|
|
656
649
|
await self.server.run(read_stream, write_stream, init_options)
|
|
657
650
|
|
|
658
651
|
self.logger.info("Server shutting down normally")
|
|
@@ -674,7 +667,16 @@ async def main():
|
|
|
674
667
|
level=logging.INFO,
|
|
675
668
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
676
669
|
stream=sys.stderr,
|
|
670
|
+
force=True # Force reconfiguration even if already configured
|
|
677
671
|
)
|
|
672
|
+
|
|
673
|
+
# Ensure all loggers output to stderr
|
|
674
|
+
for logger_name in logging.Logger.manager.loggerDict:
|
|
675
|
+
logger = logging.getLogger(logger_name)
|
|
676
|
+
for handler in logger.handlers[:]:
|
|
677
|
+
# Remove any handlers that might write to stdout
|
|
678
|
+
if hasattr(handler, 'stream') and handler.stream == sys.stdout:
|
|
679
|
+
logger.removeHandler(handler)
|
|
678
680
|
|
|
679
681
|
# Create and run server
|
|
680
682
|
server = SimpleMCPServer()
|
|
@@ -683,9 +685,15 @@ async def main():
|
|
|
683
685
|
|
|
684
686
|
def main_sync():
|
|
685
687
|
"""Synchronous entry point for use as a console script."""
|
|
688
|
+
import os
|
|
689
|
+
# Disable telemetry by default
|
|
690
|
+
os.environ.setdefault('DISABLE_TELEMETRY', '1')
|
|
686
691
|
asyncio.run(main())
|
|
687
692
|
|
|
688
693
|
|
|
689
694
|
if __name__ == "__main__":
|
|
695
|
+
import os
|
|
696
|
+
# Disable telemetry by default
|
|
697
|
+
os.environ.setdefault('DISABLE_TELEMETRY', '1')
|
|
690
698
|
# Run the async main function
|
|
691
699
|
main_sync()
|