claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__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/WORKFLOW.md +2 -14
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +3 -3
- claude_mpm/cli/parsers/configure_parser.py +4 -15
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +356 -0
- claude_mpm/core/framework/formatters/content_formatter.py +283 -0
- claude_mpm/core/framework/formatters/context_generator.py +180 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +202 -0
- claude_mpm/core/framework/loaders/file_loader.py +213 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +222 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +238 -0
- claude_mpm/core/framework_loader.py +277 -1798
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/kuzu_memory_hook.py +352 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +1 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/config_strategies/__init__.py +190 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
claude_mpm/hooks/__init__.py
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
"""Hook system for claude-mpm."""
|
2
2
|
|
3
3
|
from .base_hook import BaseHook, HookContext, HookResult, HookType
|
4
|
+
from .kuzu_memory_hook import KuzuMemoryHook, get_kuzu_memory_hook
|
4
5
|
|
5
|
-
__all__ = [
|
6
|
+
__all__ = [
|
7
|
+
"BaseHook",
|
8
|
+
"HookContext",
|
9
|
+
"HookResult",
|
10
|
+
"HookType",
|
11
|
+
"KuzuMemoryHook",
|
12
|
+
"get_kuzu_memory_hook",
|
13
|
+
]
|
@@ -0,0 +1,352 @@
|
|
1
|
+
"""
|
2
|
+
Kuzu-Memory Integration Hook
|
3
|
+
============================
|
4
|
+
|
5
|
+
Integrates kuzu-memory knowledge graph with Claude MPM for persistent memory
|
6
|
+
across conversations. This hook intercepts user prompts to enrich them with
|
7
|
+
relevant memories and stores new learnings after responses.
|
8
|
+
|
9
|
+
WHY: Claude MPM needs a way to persistently remember information across
|
10
|
+
different conversations and sessions. Kuzu-memory provides a graph database
|
11
|
+
for structured memory storage with semantic search capabilities.
|
12
|
+
|
13
|
+
DESIGN DECISIONS:
|
14
|
+
- Priority 10 for early execution to enrich prompts before other hooks
|
15
|
+
- Uses subprocess to call kuzu-memory directly for maximum compatibility
|
16
|
+
- Graceful degradation if kuzu-memory is not installed
|
17
|
+
- Automatic extraction and storage of important information
|
18
|
+
"""
|
19
|
+
|
20
|
+
import json
|
21
|
+
import re
|
22
|
+
import shutil
|
23
|
+
import subprocess
|
24
|
+
from datetime import datetime
|
25
|
+
from pathlib import Path
|
26
|
+
from typing import Any, Dict, List, Optional, Tuple
|
27
|
+
|
28
|
+
from claude_mpm.core.logging_utils import get_logger
|
29
|
+
from claude_mpm.hooks.base_hook import HookContext, HookResult, HookType, SubmitHook
|
30
|
+
|
31
|
+
logger = get_logger(__name__)
|
32
|
+
|
33
|
+
|
34
|
+
class KuzuMemoryHook(SubmitHook):
|
35
|
+
"""
|
36
|
+
Hook that integrates kuzu-memory for persistent knowledge management.
|
37
|
+
|
38
|
+
This hook:
|
39
|
+
1. Checks if kuzu-memory is installed via pipx
|
40
|
+
2. Enriches user prompts with relevant memories
|
41
|
+
3. Stores important information from conversations
|
42
|
+
4. Provides context-aware memory retrieval
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self):
|
46
|
+
"""Initialize the kuzu-memory integration hook."""
|
47
|
+
super().__init__(name="kuzu_memory_integration", priority=10)
|
48
|
+
|
49
|
+
# Check if kuzu-memory is available
|
50
|
+
self.kuzu_memory_cmd = self._detect_kuzu_memory()
|
51
|
+
self.enabled = self.kuzu_memory_cmd is not None
|
52
|
+
|
53
|
+
if not self.enabled:
|
54
|
+
logger.info(
|
55
|
+
"Kuzu-memory not found. Install with: pipx install kuzu-memory"
|
56
|
+
)
|
57
|
+
else:
|
58
|
+
logger.info(f"Kuzu-memory integration enabled: {self.kuzu_memory_cmd}")
|
59
|
+
|
60
|
+
# Use current project directory (kuzu-memory works with project-specific databases)
|
61
|
+
self.project_path = Path.cwd()
|
62
|
+
|
63
|
+
# Memory extraction patterns
|
64
|
+
self.memory_patterns = [
|
65
|
+
r"#\s*(?:Remember|Memorize|Store):\s*(.+?)(?:#|$)",
|
66
|
+
r"(?:Important|Note|Key point):\s*(.+?)(?:\n|$)",
|
67
|
+
r"(?:Learned|Discovered|Found that):\s*(.+?)(?:\n|$)",
|
68
|
+
]
|
69
|
+
|
70
|
+
def _detect_kuzu_memory(self) -> Optional[str]:
|
71
|
+
"""
|
72
|
+
Detect if kuzu-memory is installed and return its command path.
|
73
|
+
|
74
|
+
Priority:
|
75
|
+
1. Check pipx installation
|
76
|
+
2. Check system PATH
|
77
|
+
3. Return None if not found
|
78
|
+
"""
|
79
|
+
# Check pipx installation
|
80
|
+
pipx_path = Path.home() / ".local" / "pipx" / "venvs" / "kuzu-memory" / "bin" / "kuzu-memory"
|
81
|
+
if pipx_path.exists():
|
82
|
+
return str(pipx_path)
|
83
|
+
|
84
|
+
# Check system PATH
|
85
|
+
kuzu_cmd = shutil.which("kuzu-memory")
|
86
|
+
if kuzu_cmd:
|
87
|
+
return kuzu_cmd
|
88
|
+
|
89
|
+
return None
|
90
|
+
|
91
|
+
def execute(self, context: HookContext) -> HookResult:
|
92
|
+
"""
|
93
|
+
Process user prompts with kuzu-memory integration.
|
94
|
+
|
95
|
+
This method:
|
96
|
+
1. Retrieves relevant memories for the prompt
|
97
|
+
2. Enriches the prompt with memory context
|
98
|
+
3. Stores new memories after processing
|
99
|
+
"""
|
100
|
+
if not self.enabled:
|
101
|
+
return HookResult(success=True, data=context.data, modified=False)
|
102
|
+
|
103
|
+
try:
|
104
|
+
# Extract user prompt
|
105
|
+
prompt = context.data.get("prompt", "")
|
106
|
+
if not prompt:
|
107
|
+
return HookResult(success=True, data=context.data, modified=False)
|
108
|
+
|
109
|
+
# Retrieve relevant memories
|
110
|
+
memories = self._retrieve_memories(prompt)
|
111
|
+
|
112
|
+
if memories:
|
113
|
+
# Enrich prompt with memories
|
114
|
+
enriched_data = self._enrich_prompt(context.data, prompt, memories)
|
115
|
+
|
116
|
+
logger.info(f"Enriched prompt with {len(memories)} memories")
|
117
|
+
|
118
|
+
# Store the original prompt for later processing
|
119
|
+
enriched_data["_original_prompt"] = prompt
|
120
|
+
enriched_data["_memory_enriched"] = True
|
121
|
+
|
122
|
+
return HookResult(
|
123
|
+
success=True,
|
124
|
+
data=enriched_data,
|
125
|
+
modified=True,
|
126
|
+
metadata={
|
127
|
+
"memories_added": len(memories),
|
128
|
+
"memory_source": "kuzu",
|
129
|
+
}
|
130
|
+
)
|
131
|
+
|
132
|
+
return HookResult(success=True, data=context.data, modified=False)
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
logger.error(f"Kuzu-memory hook failed: {e}")
|
136
|
+
# Don't fail the request if memory integration fails
|
137
|
+
return HookResult(
|
138
|
+
success=True,
|
139
|
+
data=context.data,
|
140
|
+
modified=False,
|
141
|
+
error=f"Memory integration failed: {e}"
|
142
|
+
)
|
143
|
+
|
144
|
+
def _retrieve_memories(self, query: str) -> List[Dict[str, Any]]:
|
145
|
+
"""
|
146
|
+
Retrieve relevant memories for the given query.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
query: The user prompt to find memories for
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
List of relevant memory dictionaries
|
153
|
+
"""
|
154
|
+
try:
|
155
|
+
# Use kuzu-memory recall command
|
156
|
+
result = subprocess.run(
|
157
|
+
[self.kuzu_memory_cmd, "recall", query, "--format", "json"],
|
158
|
+
capture_output=True,
|
159
|
+
text=True,
|
160
|
+
timeout=5,
|
161
|
+
cwd=str(self.project_path),
|
162
|
+
)
|
163
|
+
|
164
|
+
if result.returncode == 0 and result.stdout:
|
165
|
+
memories = json.loads(result.stdout)
|
166
|
+
return memories if isinstance(memories, list) else []
|
167
|
+
|
168
|
+
except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception) as e:
|
169
|
+
logger.debug(f"Memory retrieval failed: {e}")
|
170
|
+
|
171
|
+
return []
|
172
|
+
|
173
|
+
def _enrich_prompt(
|
174
|
+
self, original_data: Dict[str, Any], prompt: str, memories: List[Dict[str, Any]]
|
175
|
+
) -> Dict[str, Any]:
|
176
|
+
"""
|
177
|
+
Enrich the user prompt with relevant memories.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
original_data: Original hook context data
|
181
|
+
prompt: User prompt
|
182
|
+
memories: Retrieved memories
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
Enriched context data
|
186
|
+
"""
|
187
|
+
# Format memories for context
|
188
|
+
memory_context = self._format_memories(memories)
|
189
|
+
|
190
|
+
# Create enriched prompt
|
191
|
+
enriched_prompt = f"""
|
192
|
+
## RELEVANT MEMORIES FROM KUZU KNOWLEDGE GRAPH
|
193
|
+
|
194
|
+
{memory_context}
|
195
|
+
|
196
|
+
## USER REQUEST
|
197
|
+
|
198
|
+
{prompt}
|
199
|
+
|
200
|
+
Note: Use the memories above to provide more informed and contextual responses.
|
201
|
+
"""
|
202
|
+
|
203
|
+
# Create new data with enriched prompt
|
204
|
+
enriched_data = original_data.copy()
|
205
|
+
enriched_data["prompt"] = enriched_prompt
|
206
|
+
|
207
|
+
return enriched_data
|
208
|
+
|
209
|
+
def _format_memories(self, memories: List[Dict[str, Any]]) -> str:
|
210
|
+
"""
|
211
|
+
Format memories into a readable context string.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
memories: List of memory dictionaries
|
215
|
+
|
216
|
+
Returns:
|
217
|
+
Formatted memory context
|
218
|
+
"""
|
219
|
+
if not memories:
|
220
|
+
return "No relevant memories found."
|
221
|
+
|
222
|
+
formatted = []
|
223
|
+
for i, memory in enumerate(memories, 1):
|
224
|
+
# Extract memory content and metadata
|
225
|
+
content = memory.get("content", "")
|
226
|
+
tags = memory.get("tags", [])
|
227
|
+
timestamp = memory.get("timestamp", "")
|
228
|
+
relevance = memory.get("relevance", 0.0)
|
229
|
+
|
230
|
+
# Format memory entry
|
231
|
+
entry = f"{i}. {content}"
|
232
|
+
if tags:
|
233
|
+
entry += f" [Tags: {', '.join(tags)}]"
|
234
|
+
if relevance > 0:
|
235
|
+
entry += f" (Relevance: {relevance:.2f})"
|
236
|
+
|
237
|
+
formatted.append(entry)
|
238
|
+
|
239
|
+
return "\n".join(formatted)
|
240
|
+
|
241
|
+
def store_memory(self, content: str, tags: Optional[List[str]] = None) -> bool:
|
242
|
+
"""
|
243
|
+
Store a memory using kuzu-memory.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
content: The memory content to store
|
247
|
+
tags: Optional tags for categorization
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
True if storage was successful
|
251
|
+
"""
|
252
|
+
if not self.enabled:
|
253
|
+
return False
|
254
|
+
|
255
|
+
try:
|
256
|
+
# Use kuzu-memory remember command (synchronous)
|
257
|
+
cmd = [self.kuzu_memory_cmd, "remember", content]
|
258
|
+
|
259
|
+
# Execute store command in project directory
|
260
|
+
result = subprocess.run(
|
261
|
+
cmd,
|
262
|
+
capture_output=True,
|
263
|
+
text=True,
|
264
|
+
timeout=5,
|
265
|
+
cwd=str(self.project_path),
|
266
|
+
)
|
267
|
+
|
268
|
+
if result.returncode == 0:
|
269
|
+
logger.debug(f"Stored memory: {content[:50]}...")
|
270
|
+
return True
|
271
|
+
|
272
|
+
except Exception as e:
|
273
|
+
logger.error(f"Failed to store memory: {e}")
|
274
|
+
|
275
|
+
return False
|
276
|
+
|
277
|
+
def extract_and_store_learnings(self, text: str) -> int:
|
278
|
+
"""
|
279
|
+
Extract learnings from text and store them as memories.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
text: Text to extract learnings from
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
Number of learnings stored
|
286
|
+
"""
|
287
|
+
if not self.enabled:
|
288
|
+
return 0
|
289
|
+
|
290
|
+
stored_count = 0
|
291
|
+
|
292
|
+
# Extract learnings using patterns
|
293
|
+
for pattern in self.memory_patterns:
|
294
|
+
matches = re.finditer(pattern, text, re.IGNORECASE | re.MULTILINE)
|
295
|
+
for match in matches:
|
296
|
+
learning = match.group(1).strip()
|
297
|
+
if learning and len(learning) > 10: # Minimum length check
|
298
|
+
# Determine tags based on content
|
299
|
+
tags = self._infer_tags(learning)
|
300
|
+
|
301
|
+
# Store the learning
|
302
|
+
if self.store_memory(learning, tags):
|
303
|
+
stored_count += 1
|
304
|
+
|
305
|
+
return stored_count
|
306
|
+
|
307
|
+
def _infer_tags(self, content: str) -> List[str]:
|
308
|
+
"""
|
309
|
+
Infer tags based on memory content.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
content: Memory content
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
List of inferred tags
|
316
|
+
"""
|
317
|
+
tags = []
|
318
|
+
content_lower = content.lower()
|
319
|
+
|
320
|
+
# Technical tags
|
321
|
+
if any(word in content_lower for word in ["code", "function", "class", "module"]):
|
322
|
+
tags.append("technical")
|
323
|
+
if any(word in content_lower for word in ["bug", "error", "fix", "issue"]):
|
324
|
+
tags.append("debugging")
|
325
|
+
if any(word in content_lower for word in ["pattern", "architecture", "design"]):
|
326
|
+
tags.append("architecture")
|
327
|
+
if any(word in content_lower for word in ["performance", "optimize", "speed"]):
|
328
|
+
tags.append("performance")
|
329
|
+
|
330
|
+
# Project context tags
|
331
|
+
if "claude-mpm" in content_lower or "mpm" in content_lower:
|
332
|
+
tags.append("claude-mpm")
|
333
|
+
if any(word in content_lower for word in ["hook", "agent", "service"]):
|
334
|
+
tags.append("framework")
|
335
|
+
|
336
|
+
# Default tag if no others found
|
337
|
+
if not tags:
|
338
|
+
tags.append("general")
|
339
|
+
|
340
|
+
return tags
|
341
|
+
|
342
|
+
|
343
|
+
# Create a singleton instance
|
344
|
+
_kuzu_memory_hook = None
|
345
|
+
|
346
|
+
|
347
|
+
def get_kuzu_memory_hook() -> KuzuMemoryHook:
|
348
|
+
"""Get the singleton kuzu-memory hook instance."""
|
349
|
+
global _kuzu_memory_hook
|
350
|
+
if _kuzu_memory_hook is None:
|
351
|
+
_kuzu_memory_hook = KuzuMemoryHook()
|
352
|
+
return _kuzu_memory_hook
|
@@ -16,7 +16,7 @@ import re
|
|
16
16
|
from typing import Dict, List
|
17
17
|
|
18
18
|
from claude_mpm.core.config import Config
|
19
|
-
from claude_mpm.core.
|
19
|
+
from claude_mpm.core.logging_utils import get_logger
|
20
20
|
from claude_mpm.core.shared.config_loader import ConfigLoader
|
21
21
|
from claude_mpm.hooks.base_hook import (
|
22
22
|
HookContext,
|
@@ -12,12 +12,15 @@ This module provides:
|
|
12
12
|
- Content repair and structure validation
|
13
13
|
"""
|
14
14
|
|
15
|
-
import logging
|
16
15
|
import re
|
17
16
|
from datetime import datetime, timezone
|
18
17
|
from difflib import SequenceMatcher
|
19
18
|
from typing import Any, Dict, List, Optional, Tuple
|
20
19
|
|
20
|
+
from claude_mpm.core.logging_utils import get_logger
|
21
|
+
|
22
|
+
logger = get_logger(__name__)
|
23
|
+
|
21
24
|
|
22
25
|
class MemoryContentManager:
|
23
26
|
"""Manages memory content manipulation and validation.
|
@@ -34,7 +37,7 @@ class MemoryContentManager:
|
|
34
37
|
memory_limits: Dictionary containing memory limits configuration
|
35
38
|
"""
|
36
39
|
self.memory_limits = memory_limits
|
37
|
-
self.logger =
|
40
|
+
self.logger = logger # Use the module-level logger
|
38
41
|
|
39
42
|
def add_item_to_list(self, content: str, new_item: str) -> str:
|
40
43
|
"""Add item to memory list with deduplication.
|
@@ -18,6 +18,7 @@ class MemoryFileService:
|
|
18
18
|
memories_dir: Directory where memory files are stored
|
19
19
|
"""
|
20
20
|
self.memories_dir = memories_dir
|
21
|
+
self.logger = logger # Use the module-level logger
|
21
22
|
|
22
23
|
def get_memory_file_with_migration(self, directory: Path, agent_id: str) -> Path:
|
23
24
|
"""Get memory file path with migration support.
|
@@ -26,6 +26,7 @@ class MemoryLimitsService:
|
|
26
26
|
config: Optional Config object for reading configuration
|
27
27
|
"""
|
28
28
|
self.config = config or Config()
|
29
|
+
self.logger = logger # Use the module-level logger
|
29
30
|
self.memory_limits = self._init_memory_limits()
|
30
31
|
|
31
32
|
def _init_memory_limits(self) -> Dict[str, Any]:
|
@@ -50,6 +50,7 @@ class PathResolver(IPathResolver):
|
|
50
50
|
Args:
|
51
51
|
cache_manager: Optional cache manager for caching resolved paths
|
52
52
|
"""
|
53
|
+
self.logger = get_logger("path_resolver")
|
53
54
|
self.cache_manager = cache_manager
|
54
55
|
self._framework_path: Optional[Path] = None
|
55
56
|
self._deployment_context: Optional[DeploymentContext] = None
|
@@ -37,6 +37,7 @@ class MCPConfigManager:
|
|
37
37
|
"mcp-vector-search",
|
38
38
|
"mcp-browser",
|
39
39
|
"mcp-ticketer",
|
40
|
+
"kuzu-memory",
|
40
41
|
}
|
41
42
|
|
42
43
|
def __init__(self):
|
@@ -53,9 +54,10 @@ class MCPConfigManager:
|
|
53
54
|
Detect the best path for an MCP service.
|
54
55
|
|
55
56
|
Priority order:
|
56
|
-
1.
|
57
|
-
2.
|
58
|
-
3.
|
57
|
+
1. For kuzu-memory: prefer v1.1.0+ with MCP support
|
58
|
+
2. Pipx installation (preferred)
|
59
|
+
3. System PATH (likely from pipx or homebrew)
|
60
|
+
4. Local venv (fallback)
|
59
61
|
|
60
62
|
Args:
|
61
63
|
service_name: Name of the MCP service
|
@@ -63,6 +65,46 @@ class MCPConfigManager:
|
|
63
65
|
Returns:
|
64
66
|
Path to the service executable or None if not found
|
65
67
|
"""
|
68
|
+
# Special handling for kuzu-memory - prefer v1.1.0+ with MCP support
|
69
|
+
if service_name == "kuzu-memory":
|
70
|
+
candidates = []
|
71
|
+
|
72
|
+
# Check pipx installation
|
73
|
+
pipx_path = self._check_pipx_installation(service_name)
|
74
|
+
if pipx_path:
|
75
|
+
candidates.append(pipx_path)
|
76
|
+
|
77
|
+
# Check system PATH (including homebrew)
|
78
|
+
import shutil
|
79
|
+
system_path = shutil.which(service_name)
|
80
|
+
if system_path and system_path not in candidates:
|
81
|
+
candidates.append(system_path)
|
82
|
+
|
83
|
+
# Choose the best candidate (prefer v1.1.0+ with MCP support)
|
84
|
+
for path in candidates:
|
85
|
+
try:
|
86
|
+
result = subprocess.run(
|
87
|
+
[path, "--help"],
|
88
|
+
capture_output=True,
|
89
|
+
text=True,
|
90
|
+
timeout=5
|
91
|
+
)
|
92
|
+
# Check if this version has MCP support
|
93
|
+
if "claude" in result.stdout or "mcp" in result.stdout:
|
94
|
+
self.logger.debug(f"Found kuzu-memory with MCP support at {path}")
|
95
|
+
return path
|
96
|
+
except:
|
97
|
+
pass
|
98
|
+
|
99
|
+
# If no MCP-capable version found, log warning but return None
|
100
|
+
if candidates:
|
101
|
+
self.logger.warning(
|
102
|
+
f"Found kuzu-memory at {candidates[0]} but it lacks MCP support. "
|
103
|
+
f"Upgrade to v1.1.0+ for MCP integration: pipx upgrade kuzu-memory"
|
104
|
+
)
|
105
|
+
return None # Don't configure MCP for incompatible versions
|
106
|
+
|
107
|
+
# Standard detection for other services
|
66
108
|
# Check pipx installation first
|
67
109
|
pipx_path = self._check_pipx_installation(service_name)
|
68
110
|
if pipx_path:
|
@@ -83,7 +125,7 @@ class MCPConfigManager:
|
|
83
125
|
)
|
84
126
|
return local_path
|
85
127
|
|
86
|
-
self.logger.
|
128
|
+
self.logger.debug(f"Service {service_name} not found - will auto-install when needed")
|
87
129
|
return None
|
88
130
|
|
89
131
|
def _check_pipx_installation(self, service_name: str) -> Optional[str]:
|
@@ -178,6 +220,27 @@ class MCPConfigManager:
|
|
178
220
|
config["env"] = {"MCP_BROWSER_HOME": str(Path.home() / ".mcp-browser")}
|
179
221
|
elif service_name == "mcp-ticketer":
|
180
222
|
config["args"] = ["mcp"]
|
223
|
+
elif service_name == "kuzu-memory":
|
224
|
+
# Check kuzu-memory version to determine correct command
|
225
|
+
# v1.1.0+ has "claude mcp-server", v1.0.0 has "serve"
|
226
|
+
import subprocess
|
227
|
+
try:
|
228
|
+
result = subprocess.run(
|
229
|
+
[service_path, "--help"],
|
230
|
+
capture_output=True,
|
231
|
+
text=True,
|
232
|
+
timeout=10
|
233
|
+
)
|
234
|
+
if "claude" in result.stdout:
|
235
|
+
# v1.1.0+ with claude command
|
236
|
+
config["args"] = ["claude", "mcp-server"]
|
237
|
+
else:
|
238
|
+
# v1.0.0 with serve command
|
239
|
+
config["args"] = ["serve"]
|
240
|
+
except:
|
241
|
+
# Default to older version command
|
242
|
+
config["args"] = ["serve"]
|
243
|
+
# kuzu-memory works with project-specific databases, no custom path needed
|
181
244
|
else:
|
182
245
|
# Generic config for unknown services
|
183
246
|
config["args"] = []
|