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.
Files changed (74) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/cli/commands/configure.py +2 -29
  4. claude_mpm/cli/commands/doctor.py +2 -2
  5. claude_mpm/cli/commands/mpm_init.py +3 -3
  6. claude_mpm/cli/parsers/configure_parser.py +4 -15
  7. claude_mpm/core/framework/__init__.py +38 -0
  8. claude_mpm/core/framework/formatters/__init__.py +11 -0
  9. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  10. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  11. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  12. claude_mpm/core/framework/loaders/__init__.py +13 -0
  13. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  14. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  15. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  16. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  17. claude_mpm/core/framework/processors/__init__.py +11 -0
  18. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  19. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  20. claude_mpm/core/framework/processors/template_processor.py +238 -0
  21. claude_mpm/core/framework_loader.py +277 -1798
  22. claude_mpm/hooks/__init__.py +9 -1
  23. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  24. claude_mpm/hooks/memory_integration_hook.py +1 -1
  25. claude_mpm/services/agents/memory/content_manager.py +5 -2
  26. claude_mpm/services/agents/memory/memory_file_service.py +1 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
  28. claude_mpm/services/core/path_resolver.py +1 -0
  29. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  30. claude_mpm/services/mcp_config_manager.py +67 -4
  31. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  32. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  33. claude_mpm/services/mcp_gateway/main.py +3 -13
  34. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  35. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  36. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  37. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  38. claude_mpm/services/shared/__init__.py +2 -1
  39. claude_mpm/services/shared/service_factory.py +8 -5
  40. claude_mpm/services/unified/__init__.py +65 -0
  41. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  42. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  43. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  44. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  45. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  46. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  47. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  48. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  49. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  50. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  51. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  52. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  53. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  54. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  55. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  56. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  57. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  58. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  59. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  60. claude_mpm/services/unified/interfaces.py +499 -0
  61. claude_mpm/services/unified/migration.py +532 -0
  62. claude_mpm/services/unified/strategies.py +551 -0
  63. claude_mpm/services/unified/unified_analyzer.py +534 -0
  64. claude_mpm/services/unified/unified_config.py +688 -0
  65. claude_mpm/services/unified/unified_deployment.py +470 -0
  66. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  67. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
  68. claude_mpm/cli/commands/configure_tui.py +0 -1927
  69. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  70. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  71. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  72. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  73. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  74. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -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__ = ["BaseHook", "HookContext", "HookResult", "HookType"]
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.logger import get_logger
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 = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
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
@@ -45,6 +45,7 @@ class DiagnosticRunner:
45
45
  """
46
46
  self.verbose = verbose
47
47
  self.fix = fix
48
+ self.logger = logger # Add logger initialization
48
49
  # Define check order (dependencies first)
49
50
  self.check_classes: List[Type[BaseDiagnosticCheck]] = [
50
51
  InstallationCheck,
@@ -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. Pipx installation (preferred)
57
- 2. System PATH (likely from pipx)
58
- 3. Local venv (fallback)
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.warning(f"Service {service_name} not found")
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"] = []