claude-mpm 4.4.0__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 (50) 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/mpm_init.py +3 -3
  5. claude_mpm/cli/parsers/configure_parser.py +4 -15
  6. claude_mpm/core/framework/__init__.py +38 -0
  7. claude_mpm/core/framework/formatters/__init__.py +11 -0
  8. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  9. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  10. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  11. claude_mpm/core/framework/loaders/__init__.py +13 -0
  12. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  13. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  14. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  15. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  16. claude_mpm/core/framework/processors/__init__.py +11 -0
  17. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  18. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  19. claude_mpm/core/framework/processors/template_processor.py +238 -0
  20. claude_mpm/core/framework_loader.py +277 -1798
  21. claude_mpm/hooks/__init__.py +9 -1
  22. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  23. claude_mpm/services/core/path_resolver.py +1 -0
  24. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  25. claude_mpm/services/mcp_config_manager.py +67 -4
  26. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  27. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  28. claude_mpm/services/mcp_gateway/main.py +3 -13
  29. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  30. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  31. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  32. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  33. claude_mpm/services/shared/__init__.py +2 -1
  34. claude_mpm/services/shared/service_factory.py +8 -5
  35. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  36. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  37. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  38. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  39. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  40. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  41. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  42. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  43. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +47 -27
  44. claude_mpm/cli/commands/configure_tui.py +0 -1927
  45. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  46. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  47. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  48. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  49. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  50. {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -1,602 +0,0 @@
1
- """
2
- Unified Ticket Tool for MCP Gateway
3
- ====================================
4
-
5
- Provides a single, unified interface for all ticket management operations
6
- through the MCP Gateway, consolidating create, list, update, view, and search
7
- functionality into one tool with an operation parameter.
8
-
9
- WHY: Having 5 separate ticket tools creates unnecessary complexity. A single
10
- tool with an operation parameter provides a cleaner, more intuitive API that
11
- matches the mental model of "ticket operations" better.
12
-
13
- DESIGN DECISIONS:
14
- - Single tool with operation parameter for cleaner API
15
- - Conditional parameter validation based on operation type
16
- - Reuses existing logic from separate tools for consistency
17
- - Maintains same error handling and metrics tracking patterns
18
- - Uses JSON Schema oneOf for operation-specific parameter validation
19
- """
20
-
21
- import asyncio
22
- import json
23
- import re
24
- from datetime import datetime, timezone
25
- from typing import Any, Dict, Optional
26
-
27
- from claude_mpm.services.mcp_gateway.core.interfaces import (
28
- MCPToolDefinition,
29
- MCPToolInvocation,
30
- MCPToolResult,
31
- )
32
- from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseToolAdapter
33
-
34
-
35
- class UnifiedTicketTool(BaseToolAdapter):
36
- """
37
- Unified ticket management tool for aitrackdown operations.
38
-
39
- WHY: Consolidates all ticket operations (create, list, update, view, search)
40
- into a single tool with an operation parameter, providing a cleaner and more
41
- intuitive interface for ticket management.
42
-
43
- DESIGN DECISIONS:
44
- - Use operation parameter to route to appropriate handler
45
- - Implement conditional parameter validation per operation
46
- - Maintain backward compatibility with existing aitrackdown CLI
47
- - Preserve all error handling and metrics from original tools
48
- """
49
-
50
- def __init__(self):
51
- """Initialize the unified ticket tool with comprehensive schema."""
52
- definition = MCPToolDefinition(
53
- name="ticket",
54
- description="Unified ticket management tool for all aitrackdown operations",
55
- input_schema={
56
- "type": "object",
57
- "properties": {
58
- "operation": {
59
- "type": "string",
60
- "enum": ["create", "list", "update", "view", "search"],
61
- "description": "The ticket operation to perform",
62
- },
63
- # Create operation parameters
64
- "type": {
65
- "type": "string",
66
- "enum": ["task", "issue", "epic"],
67
- "description": "Type of ticket (for create operation)",
68
- },
69
- "title": {
70
- "type": "string",
71
- "description": "Title of the ticket (for create operation)",
72
- },
73
- "description": {
74
- "type": "string",
75
- "description": "Detailed description (for create operation)",
76
- },
77
- "priority": {
78
- "type": "string",
79
- "enum": ["low", "medium", "high", "critical"],
80
- "description": "Priority level",
81
- },
82
- "tags": {
83
- "type": "array",
84
- "items": {"type": "string"},
85
- "description": "Tags to associate with the ticket (for create)",
86
- },
87
- "parent_epic": {
88
- "type": "string",
89
- "description": "Parent epic ID for issues (create operation)",
90
- },
91
- "parent_issue": {
92
- "type": "string",
93
- "description": "Parent issue ID for tasks (create operation)",
94
- },
95
- # List operation parameters
96
- "limit": {
97
- "type": "number",
98
- "description": "Maximum number of results to return",
99
- "default": 10,
100
- },
101
- # Update operation parameters
102
- "ticket_id": {
103
- "type": "string",
104
- "description": "Ticket ID (for update/view operations)",
105
- },
106
- "status": {
107
- "type": "string",
108
- "enum": [
109
- "all",
110
- "open",
111
- "in-progress",
112
- "ready",
113
- "tested",
114
- "done",
115
- "waiting",
116
- "closed",
117
- "blocked",
118
- ],
119
- "description": "Status filter or new status",
120
- },
121
- "comment": {
122
- "type": "string",
123
- "description": "Comment for update operation",
124
- },
125
- # View operation parameters
126
- "format": {
127
- "type": "string",
128
- "enum": ["json", "text"],
129
- "description": "Output format (for view operation)",
130
- "default": "json",
131
- },
132
- # Search operation parameters
133
- "query": {
134
- "type": "string",
135
- "description": "Search query keywords (for search operation)",
136
- },
137
- },
138
- "required": ["operation"],
139
- # Note: Additional validation is handled in the invoke method
140
- # to avoid using allOf/oneOf/anyOf at the top level which is not
141
- # supported by the Claude API
142
- },
143
- )
144
- super().__init__(definition)
145
-
146
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
147
- """
148
- Route the invocation to the appropriate operation handler.
149
-
150
- Args:
151
- invocation: Tool invocation request
152
-
153
- Returns:
154
- Tool execution result from the specific operation
155
- """
156
- operation = invocation.parameters.get("operation")
157
-
158
- if not operation:
159
- return MCPToolResult(
160
- success=False,
161
- error="Operation parameter is required",
162
- execution_time=0.0,
163
- )
164
-
165
- # Validate required parameters based on operation type
166
- validation_error = self._validate_parameters(operation, invocation.parameters)
167
- if validation_error:
168
- return MCPToolResult(
169
- success=False,
170
- error=validation_error,
171
- execution_time=0.0,
172
- )
173
-
174
- # Route to appropriate handler based on operation
175
- handlers = {
176
- "create": self._handle_create,
177
- "list": self._handle_list,
178
- "update": self._handle_update,
179
- "view": self._handle_view,
180
- "search": self._handle_search,
181
- }
182
-
183
- handler = handlers.get(operation)
184
- if not handler:
185
- return MCPToolResult(
186
- success=False,
187
- error=f"Unknown operation: {operation}",
188
- execution_time=0.0,
189
- )
190
-
191
- return await handler(invocation.parameters)
192
-
193
- def _validate_parameters(
194
- self, operation: str, params: Dict[str, Any]
195
- ) -> Optional[str]:
196
- """
197
- Validate parameters based on the operation type.
198
-
199
- Args:
200
- operation: The operation being performed
201
- params: Parameters provided for the operation
202
-
203
- Returns:
204
- Error message if validation fails, None if valid
205
- """
206
- if operation == "create":
207
- if "type" not in params:
208
- return "'type' parameter is required for create operation"
209
- if "title" not in params:
210
- return "'title' parameter is required for create operation"
211
- if params["type"] not in ["task", "issue", "epic"]:
212
- return f"Invalid type '{params['type']}'. Must be 'task', 'issue', or 'epic'"
213
-
214
- elif operation == "update":
215
- if "ticket_id" not in params:
216
- return "'ticket_id' parameter is required for update operation"
217
-
218
- elif operation == "view":
219
- if "ticket_id" not in params:
220
- return "'ticket_id' parameter is required for view operation"
221
-
222
- elif operation == "search":
223
- if "query" not in params:
224
- return "'query' parameter is required for search operation"
225
-
226
- elif operation == "list":
227
- # List operation has no required parameters beyond operation itself
228
- pass
229
-
230
- else:
231
- return f"Unknown operation: {operation}"
232
-
233
- return None
234
-
235
- async def _handle_create(self, params: Dict[str, Any]) -> MCPToolResult:
236
- """
237
- Handle ticket creation operation.
238
-
239
- Args:
240
- params: Parameters for ticket creation
241
-
242
- Returns:
243
- Tool execution result with created ticket ID
244
- """
245
- start_time = datetime.now(timezone.utc)
246
-
247
- try:
248
- # Build aitrackdown command
249
- cmd = ["aitrackdown", "create", params["type"], params["title"]]
250
-
251
- # Add optional parameters
252
- if "description" in params:
253
- cmd.extend(["--description", params["description"]])
254
-
255
- if "priority" in params:
256
- cmd.extend(["--priority", params["priority"]])
257
-
258
- if params.get("tags"):
259
- for tag in params["tags"]:
260
- cmd.extend(["--tag", tag])
261
-
262
- # For tasks, use --issue to associate with parent issue
263
- if params["type"] == "task" and "parent_issue" in params:
264
- cmd.extend(["--issue", params["parent_issue"]])
265
-
266
- # Execute command asynchronously
267
- process = await asyncio.create_subprocess_exec(
268
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
269
- )
270
-
271
- stdout, stderr = await process.communicate()
272
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
273
-
274
- if process.returncode == 0:
275
- # Parse ticket ID from output
276
- output = stdout.decode().strip()
277
- ticket_id = None
278
- for line in output.split("\n"):
279
- if "TSK-" in line or "ISS-" in line or "EP-" in line:
280
- match = re.search(r"(TSK|ISS|EP)-\d+", line)
281
- if match:
282
- ticket_id = match.group(0)
283
- break
284
-
285
- self._update_metrics(True, execution_time)
286
-
287
- return MCPToolResult(
288
- success=True,
289
- data={
290
- "ticket_id": ticket_id or "Unknown",
291
- "type": params["type"],
292
- "title": params["title"],
293
- "message": output,
294
- },
295
- execution_time=execution_time,
296
- metadata={"tool": "ticket", "operation": "create"},
297
- )
298
- error_msg = stderr.decode() if stderr else stdout.decode()
299
- self._update_metrics(False, execution_time)
300
-
301
- return MCPToolResult(
302
- success=False,
303
- error=f"Failed to create ticket: {error_msg}",
304
- execution_time=execution_time,
305
- )
306
-
307
- except Exception as e:
308
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
309
- self._update_metrics(False, execution_time)
310
-
311
- return MCPToolResult(
312
- success=False,
313
- error=f"Ticket creation failed: {e!s}",
314
- execution_time=execution_time,
315
- )
316
-
317
- async def _handle_list(self, params: Dict[str, Any]) -> MCPToolResult:
318
- """
319
- Handle ticket listing operation.
320
-
321
- Args:
322
- params: Parameters for ticket listing
323
-
324
- Returns:
325
- Tool execution result with list of tickets
326
- """
327
- start_time = datetime.now(timezone.utc)
328
-
329
- try:
330
- limit = params.get("limit", 10)
331
-
332
- # Build aitrackdown command - use status tasks for listing
333
- cmd = ["aitrackdown", "status", "tasks", "--limit", str(limit)]
334
-
335
- # Add filters
336
- if params.get("status") and params["status"] != "all":
337
- cmd.extend(["--status", params["status"]])
338
-
339
- # Execute command asynchronously
340
- process = await asyncio.create_subprocess_exec(
341
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
342
- )
343
-
344
- stdout, stderr = await process.communicate()
345
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
346
-
347
- if process.returncode == 0:
348
- try:
349
- # Try to parse JSON output
350
- tickets = json.loads(stdout.decode())
351
- except json.JSONDecodeError:
352
- # Fallback to text parsing if JSON fails
353
- output = stdout.decode().strip()
354
- tickets = {"raw_output": output, "count": output.count("\n") + 1}
355
-
356
- self._update_metrics(True, execution_time)
357
-
358
- return MCPToolResult(
359
- success=True,
360
- data=tickets,
361
- execution_time=execution_time,
362
- metadata={
363
- "tool": "ticket",
364
- "operation": "list",
365
- "count": len(tickets) if isinstance(tickets, list) else 1,
366
- },
367
- )
368
- error_msg = stderr.decode() if stderr else stdout.decode()
369
- self._update_metrics(False, execution_time)
370
-
371
- return MCPToolResult(
372
- success=False,
373
- error=f"Failed to list tickets: {error_msg}",
374
- execution_time=execution_time,
375
- )
376
-
377
- except Exception as e:
378
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
379
- self._update_metrics(False, execution_time)
380
-
381
- return MCPToolResult(
382
- success=False,
383
- error=f"Ticket listing failed: {e!s}",
384
- execution_time=execution_time,
385
- )
386
-
387
- async def _handle_update(self, params: Dict[str, Any]) -> MCPToolResult:
388
- """
389
- Handle ticket update operation.
390
-
391
- Args:
392
- params: Parameters for ticket update
393
-
394
- Returns:
395
- Tool execution result
396
- """
397
- start_time = datetime.now(timezone.utc)
398
-
399
- try:
400
- ticket_id = params["ticket_id"]
401
-
402
- # Determine which update to perform
403
- if "status" in params and params["status"] != "all":
404
- # Use transition command for status updates
405
- cmd = ["aitrackdown", "transition", ticket_id, params["status"]]
406
-
407
- if "comment" in params:
408
- cmd.extend(["--comment", params["comment"]])
409
- elif "priority" in params:
410
- # For priority updates, use transition with comment
411
- cmd = ["aitrackdown", "transition", ticket_id, "open"]
412
- cmd.extend(["--comment", f"Priority changed to {params['priority']}"])
413
-
414
- if "comment" in params:
415
- cmd.extend(["--comment", params["comment"]])
416
- else:
417
- return MCPToolResult(
418
- success=False,
419
- error="No update fields provided (status or priority required)",
420
- execution_time=0.0,
421
- )
422
-
423
- # Execute command asynchronously
424
- process = await asyncio.create_subprocess_exec(
425
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
426
- )
427
-
428
- stdout, stderr = await process.communicate()
429
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
430
-
431
- if process.returncode == 0:
432
- self._update_metrics(True, execution_time)
433
-
434
- return MCPToolResult(
435
- success=True,
436
- data={
437
- "ticket_id": ticket_id,
438
- "updated_fields": [
439
- k for k in ["status", "priority"] if k in params
440
- ],
441
- "message": stdout.decode().strip(),
442
- },
443
- execution_time=execution_time,
444
- metadata={"tool": "ticket", "operation": "update"},
445
- )
446
- error_msg = stderr.decode() if stderr else stdout.decode()
447
- self._update_metrics(False, execution_time)
448
-
449
- return MCPToolResult(
450
- success=False,
451
- error=f"Failed to update ticket: {error_msg}",
452
- execution_time=execution_time,
453
- )
454
-
455
- except Exception as e:
456
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
457
- self._update_metrics(False, execution_time)
458
-
459
- return MCPToolResult(
460
- success=False,
461
- error=f"Ticket update failed: {e!s}",
462
- execution_time=execution_time,
463
- )
464
-
465
- async def _handle_view(self, params: Dict[str, Any]) -> MCPToolResult:
466
- """
467
- Handle ticket viewing operation.
468
-
469
- Args:
470
- params: Parameters for ticket viewing
471
-
472
- Returns:
473
- Tool execution result with ticket details
474
- """
475
- start_time = datetime.now(timezone.utc)
476
-
477
- try:
478
- ticket_id = params["ticket_id"]
479
- format_type = params.get("format", "json")
480
-
481
- # Build aitrackdown command - use show for viewing
482
- cmd = ["aitrackdown", "show", ticket_id]
483
-
484
- # Execute command asynchronously
485
- process = await asyncio.create_subprocess_exec(
486
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
487
- )
488
-
489
- stdout, stderr = await process.communicate()
490
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
491
-
492
- if process.returncode == 0:
493
- output = stdout.decode().strip()
494
-
495
- if format_type == "json":
496
- try:
497
- ticket_data = json.loads(output)
498
- except json.JSONDecodeError:
499
- ticket_data = {"raw_output": output}
500
- else:
501
- ticket_data = {"raw_output": output}
502
-
503
- self._update_metrics(True, execution_time)
504
-
505
- return MCPToolResult(
506
- success=True,
507
- data=ticket_data,
508
- execution_time=execution_time,
509
- metadata={
510
- "tool": "ticket",
511
- "operation": "view",
512
- "ticket_id": ticket_id,
513
- },
514
- )
515
- error_msg = stderr.decode() if stderr else stdout.decode()
516
- self._update_metrics(False, execution_time)
517
-
518
- return MCPToolResult(
519
- success=False,
520
- error=f"Failed to view ticket: {error_msg}",
521
- execution_time=execution_time,
522
- )
523
-
524
- except Exception as e:
525
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
526
- self._update_metrics(False, execution_time)
527
-
528
- return MCPToolResult(
529
- success=False,
530
- error=f"Ticket view failed: {e!s}",
531
- execution_time=execution_time,
532
- )
533
-
534
- async def _handle_search(self, params: Dict[str, Any]) -> MCPToolResult:
535
- """
536
- Handle ticket search operation.
537
-
538
- Args:
539
- params: Parameters for ticket search
540
-
541
- Returns:
542
- Tool execution result with matching tickets
543
- """
544
- start_time = datetime.now(timezone.utc)
545
-
546
- try:
547
- query = params["query"]
548
- limit = params.get("limit", 10)
549
-
550
- # Build aitrackdown command - use search tasks
551
- cmd = ["aitrackdown", "search", "tasks", query, "--limit", str(limit)]
552
-
553
- if params.get("type") and params["type"] != "all":
554
- cmd.extend(["--type", params["type"]])
555
-
556
- # Execute command asynchronously
557
- process = await asyncio.create_subprocess_exec(
558
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
559
- )
560
-
561
- stdout, stderr = await process.communicate()
562
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
563
-
564
- if process.returncode == 0:
565
- try:
566
- # Try to parse JSON output
567
- results = json.loads(stdout.decode())
568
- except json.JSONDecodeError:
569
- # Fallback to text parsing if JSON fails
570
- output = stdout.decode().strip()
571
- results = {"raw_output": output, "query": query}
572
-
573
- self._update_metrics(True, execution_time)
574
-
575
- return MCPToolResult(
576
- success=True,
577
- data=results,
578
- execution_time=execution_time,
579
- metadata={"tool": "ticket", "operation": "search", "query": query},
580
- )
581
- error_msg = stderr.decode() if stderr else stdout.decode()
582
- self._update_metrics(False, execution_time)
583
-
584
- return MCPToolResult(
585
- success=False,
586
- error=f"Failed to search tickets: {error_msg}",
587
- execution_time=execution_time,
588
- )
589
-
590
- except Exception as e:
591
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
592
- self._update_metrics(False, execution_time)
593
-
594
- return MCPToolResult(
595
- success=False,
596
- error=f"Ticket search failed: {e!s}",
597
- execution_time=execution_time,
598
- )
599
-
600
-
601
- # Export the unified ticket tool
602
- __all__ = ["UnifiedTicketTool"]