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,645 +0,0 @@
1
- """
2
- MCP Tool Adapters for aitrackdown Ticket Management
3
- ====================================================
4
-
5
- Provides MCP tool wrappers for common aitrackdown operations, enabling
6
- ticket management through Claude Code's MCP interface.
7
-
8
- WHY: These tools allow Claude Code to interact with aitrackdown for
9
- ticket management without requiring direct CLI access, providing a
10
- seamless integration experience.
11
-
12
- DESIGN DECISIONS:
13
- - Thin wrappers that delegate to aitrackdown CLI for actual operations
14
- - Focus on the most common operations that benefit from Claude integration
15
- - Structured responses for better error handling and user feedback
16
- - Async execution to avoid blocking the MCP gateway
17
- """
18
-
19
- import asyncio
20
- import json
21
- from datetime import datetime, timezone
22
-
23
- from claude_mpm.services.mcp_gateway.core.interfaces import (
24
- MCPToolDefinition,
25
- MCPToolInvocation,
26
- MCPToolResult,
27
- )
28
- from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseToolAdapter
29
-
30
-
31
- class TicketCreateTool(BaseToolAdapter):
32
- """
33
- Create new tickets using aitrackdown.
34
-
35
- WHY: Creating tickets is the most fundamental operation for tracking work items.
36
- This tool provides a simple interface for creating tasks, issues, and epics.
37
- """
38
-
39
- def __init__(self):
40
- """Initialize the ticket create tool."""
41
- definition = MCPToolDefinition(
42
- name="ticket_create",
43
- description="Create a new ticket (task, issue, or epic) using aitrackdown",
44
- input_schema={
45
- "type": "object",
46
- "properties": {
47
- "type": {
48
- "type": "string",
49
- "enum": ["task", "issue", "epic"],
50
- "description": "Type of ticket to create",
51
- },
52
- "title": {"type": "string", "description": "Title of the ticket"},
53
- "description": {
54
- "type": "string",
55
- "description": "Detailed description of the ticket",
56
- },
57
- "priority": {
58
- "type": "string",
59
- "enum": ["low", "medium", "high", "critical"],
60
- "description": "Priority level",
61
- "default": "medium",
62
- },
63
- "tags": {
64
- "type": "array",
65
- "items": {"type": "string"},
66
- "description": "Tags to associate with the ticket",
67
- },
68
- "parent_epic": {
69
- "type": "string",
70
- "description": "Parent epic ID (for issues)",
71
- },
72
- "parent_issue": {
73
- "type": "string",
74
- "description": "Parent issue ID (for tasks)",
75
- },
76
- },
77
- "required": ["type", "title"],
78
- },
79
- )
80
- super().__init__(definition)
81
-
82
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
83
- """
84
- Create a ticket using aitrackdown CLI.
85
-
86
- Args:
87
- invocation: Tool invocation request
88
-
89
- Returns:
90
- Tool execution result with created ticket ID
91
- """
92
- start_time = datetime.now(timezone.utc)
93
-
94
- try:
95
- params = invocation.parameters
96
-
97
- # Build aitrackdown command
98
- cmd = ["aitrackdown", "create", params["type"], params["title"]]
99
-
100
- # Add optional parameters
101
- if "description" in params:
102
- cmd.extend(["--description", params["description"]])
103
-
104
- if "priority" in params:
105
- cmd.extend(["--priority", params["priority"]])
106
-
107
- if params.get("tags"):
108
- # aitrackdown uses --tag for tags (singular)
109
- for tag in params["tags"]:
110
- cmd.extend(["--tag", tag])
111
-
112
- # For tasks, use --issue to associate with parent issue
113
- if params["type"] == "task" and "parent_issue" in params:
114
- cmd.extend(["--issue", params["parent_issue"]])
115
-
116
- # Execute command asynchronously
117
- process = await asyncio.create_subprocess_exec(
118
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
119
- )
120
-
121
- stdout, stderr = await process.communicate()
122
-
123
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
124
-
125
- if process.returncode == 0:
126
- # Parse ticket ID from output
127
- output = stdout.decode().strip()
128
- # aitrackdown typically returns "Created ticket: TSK-XXXX" or similar
129
- ticket_id = None
130
- for line in output.split("\n"):
131
- if "TSK-" in line or "ISS-" in line or "EP-" in line:
132
- # Extract ticket ID
133
- import re
134
-
135
- match = re.search(r"(TSK|ISS|EP)-\d+", line)
136
- if match:
137
- ticket_id = match.group(0)
138
- break
139
-
140
- self._update_metrics(True, execution_time)
141
-
142
- return MCPToolResult(
143
- success=True,
144
- data={
145
- "ticket_id": ticket_id or "Unknown",
146
- "type": params["type"],
147
- "title": params["title"],
148
- "message": output,
149
- },
150
- execution_time=execution_time,
151
- metadata={"tool": "ticket_create", "operation": "create"},
152
- )
153
- error_msg = stderr.decode() if stderr else stdout.decode()
154
- self._update_metrics(False, execution_time)
155
-
156
- return MCPToolResult(
157
- success=False,
158
- error=f"Failed to create ticket: {error_msg}",
159
- execution_time=execution_time,
160
- )
161
-
162
- except Exception as e:
163
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
164
- self._update_metrics(False, execution_time)
165
-
166
- return MCPToolResult(
167
- success=False,
168
- error=f"Ticket creation failed: {e!s}",
169
- execution_time=execution_time,
170
- )
171
-
172
-
173
- class TicketListTool(BaseToolAdapter):
174
- """
175
- List tickets with optional filters using aitrackdown status command.
176
-
177
- WHY: Users need to review and browse existing tickets. This tool provides
178
- a quick way to list recent tickets with filtering capabilities.
179
- """
180
-
181
- def __init__(self):
182
- """Initialize the ticket list tool."""
183
- definition = MCPToolDefinition(
184
- name="ticket_list",
185
- description="List tickets with optional filters using status command",
186
- input_schema={
187
- "type": "object",
188
- "properties": {
189
- "limit": {
190
- "type": "number",
191
- "description": "Maximum number of tickets to return",
192
- "default": 10,
193
- },
194
- "type": {
195
- "type": "string",
196
- "enum": ["all", "task", "issue", "epic"],
197
- "description": "Filter by ticket type",
198
- "default": "all",
199
- },
200
- "status": {
201
- "type": "string",
202
- "enum": [
203
- "all",
204
- "open",
205
- "in_progress",
206
- "done",
207
- "closed",
208
- "blocked",
209
- ],
210
- "description": "Filter by status",
211
- "default": "all",
212
- },
213
- "priority": {
214
- "type": "string",
215
- "enum": ["all", "low", "medium", "high", "critical"],
216
- "description": "Filter by priority",
217
- "default": "all",
218
- },
219
- },
220
- "required": [],
221
- },
222
- )
223
- super().__init__(definition)
224
-
225
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
226
- """
227
- List tickets using aitrackdown CLI.
228
-
229
- Args:
230
- invocation: Tool invocation request
231
-
232
- Returns:
233
- Tool execution result with list of tickets
234
- """
235
- start_time = datetime.now(timezone.utc)
236
-
237
- try:
238
- params = invocation.parameters
239
- limit = params.get("limit", 10)
240
-
241
- # Build aitrackdown command - use status tasks for listing
242
- cmd = ["aitrackdown", "status", "tasks", "--limit", str(limit)]
243
-
244
- # Add filters
245
- if params.get("status") and params["status"] != "all":
246
- cmd.extend(["--status", params["status"]])
247
-
248
- # Execute command asynchronously
249
- process = await asyncio.create_subprocess_exec(
250
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
251
- )
252
-
253
- stdout, stderr = await process.communicate()
254
-
255
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
256
-
257
- if process.returncode == 0:
258
- try:
259
- # Try to parse JSON output
260
- tickets = json.loads(stdout.decode())
261
- except json.JSONDecodeError:
262
- # Fallback to text parsing if JSON fails
263
- output = stdout.decode().strip()
264
- tickets = {"raw_output": output, "count": output.count("\n") + 1}
265
-
266
- self._update_metrics(True, execution_time)
267
-
268
- return MCPToolResult(
269
- success=True,
270
- data=tickets,
271
- execution_time=execution_time,
272
- metadata={
273
- "tool": "ticket_list",
274
- "operation": "list",
275
- "count": len(tickets) if isinstance(tickets, list) else 1,
276
- },
277
- )
278
- error_msg = stderr.decode() if stderr else stdout.decode()
279
- self._update_metrics(False, execution_time)
280
-
281
- return MCPToolResult(
282
- success=False,
283
- error=f"Failed to list tickets: {error_msg}",
284
- execution_time=execution_time,
285
- )
286
-
287
- except Exception as e:
288
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
289
- self._update_metrics(False, execution_time)
290
-
291
- return MCPToolResult(
292
- success=False,
293
- error=f"Ticket listing failed: {e!s}",
294
- execution_time=execution_time,
295
- )
296
-
297
-
298
- class TicketUpdateTool(BaseToolAdapter):
299
- """
300
- Update ticket status or priority.
301
-
302
- WHY: Tickets need to be updated as work progresses. This tool provides
303
- a simple interface for updating ticket status and priority.
304
- """
305
-
306
- def __init__(self):
307
- """Initialize the ticket update tool."""
308
- definition = MCPToolDefinition(
309
- name="ticket_update",
310
- description="Update a ticket's status or priority",
311
- input_schema={
312
- "type": "object",
313
- "properties": {
314
- "ticket_id": {
315
- "type": "string",
316
- "description": "Ticket ID to update (e.g., TSK-0001)",
317
- },
318
- "status": {
319
- "type": "string",
320
- "enum": [
321
- "open",
322
- "in-progress",
323
- "ready",
324
- "tested",
325
- "done",
326
- "waiting",
327
- ],
328
- "description": "New status for the ticket (workflow state)",
329
- },
330
- "priority": {
331
- "type": "string",
332
- "enum": ["low", "medium", "high", "critical"],
333
- "description": "New priority for the ticket",
334
- },
335
- "comment": {
336
- "type": "string",
337
- "description": "Optional comment for the update",
338
- },
339
- },
340
- "required": ["ticket_id"],
341
- },
342
- )
343
- super().__init__(definition)
344
-
345
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
346
- """
347
- Update a ticket using aitrackdown CLI.
348
-
349
- Args:
350
- invocation: Tool invocation request
351
-
352
- Returns:
353
- Tool execution result
354
- """
355
- start_time = datetime.now(timezone.utc)
356
-
357
- try:
358
- params = invocation.parameters
359
- ticket_id = params["ticket_id"]
360
-
361
- # Determine which update to perform
362
- if "status" in params:
363
- # Use transition command for status updates
364
- cmd = ["aitrackdown", "transition", ticket_id, params["status"]]
365
-
366
- if "comment" in params:
367
- cmd.extend(["--comment", params["comment"]])
368
- elif "priority" in params:
369
- # For priority updates, we might need to use edit or transition
370
- # aitrackdown doesn't have a direct update command, so use transition
371
- cmd = ["aitrackdown", "transition", ticket_id, "open"]
372
- cmd.extend(["--comment", f"Priority changed to {params['priority']}"])
373
-
374
- if "comment" in params:
375
- cmd.extend(["--comment", params["comment"]])
376
- else:
377
- return MCPToolResult(
378
- success=False,
379
- error="No update fields provided (status or priority required)",
380
- execution_time=0.0,
381
- )
382
-
383
- # Execute command asynchronously
384
- process = await asyncio.create_subprocess_exec(
385
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
386
- )
387
-
388
- stdout, stderr = await process.communicate()
389
-
390
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
391
-
392
- if process.returncode == 0:
393
- self._update_metrics(True, execution_time)
394
-
395
- return MCPToolResult(
396
- success=True,
397
- data={
398
- "ticket_id": ticket_id,
399
- "updated_fields": [
400
- k for k in ["status", "priority"] if k in params
401
- ],
402
- "message": stdout.decode().strip(),
403
- },
404
- execution_time=execution_time,
405
- metadata={"tool": "ticket_update", "operation": "update"},
406
- )
407
- error_msg = stderr.decode() if stderr else stdout.decode()
408
- self._update_metrics(False, execution_time)
409
-
410
- return MCPToolResult(
411
- success=False,
412
- error=f"Failed to update ticket: {error_msg}",
413
- execution_time=execution_time,
414
- )
415
-
416
- except Exception as e:
417
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
418
- self._update_metrics(False, execution_time)
419
-
420
- return MCPToolResult(
421
- success=False,
422
- error=f"Ticket update failed: {e!s}",
423
- execution_time=execution_time,
424
- )
425
-
426
-
427
- class TicketViewTool(BaseToolAdapter):
428
- """
429
- View detailed ticket information.
430
-
431
- WHY: Users need to see full ticket details including description, metadata,
432
- and all associated information for understanding context and status.
433
- """
434
-
435
- def __init__(self):
436
- """Initialize the ticket view tool."""
437
- definition = MCPToolDefinition(
438
- name="ticket_view",
439
- description="View detailed information about a specific ticket",
440
- input_schema={
441
- "type": "object",
442
- "properties": {
443
- "ticket_id": {
444
- "type": "string",
445
- "description": "Ticket ID to view (e.g., TSK-0001)",
446
- },
447
- "format": {
448
- "type": "string",
449
- "enum": ["json", "text"],
450
- "description": "Output format",
451
- "default": "json",
452
- },
453
- },
454
- "required": ["ticket_id"],
455
- },
456
- )
457
- super().__init__(definition)
458
-
459
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
460
- """
461
- View a ticket using aitrackdown CLI.
462
-
463
- Args:
464
- invocation: Tool invocation request
465
-
466
- Returns:
467
- Tool execution result with ticket details
468
- """
469
- start_time = datetime.now(timezone.utc)
470
-
471
- try:
472
- params = invocation.parameters
473
- ticket_id = params["ticket_id"]
474
- format_type = params.get("format", "json")
475
-
476
- # Build aitrackdown command - use show for viewing
477
- cmd = ["aitrackdown", "show", ticket_id]
478
-
479
- # Execute command asynchronously
480
- process = await asyncio.create_subprocess_exec(
481
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
482
- )
483
-
484
- stdout, stderr = await process.communicate()
485
-
486
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
487
-
488
- if process.returncode == 0:
489
- output = stdout.decode().strip()
490
-
491
- if format_type == "json":
492
- try:
493
- ticket_data = json.loads(output)
494
- except json.JSONDecodeError:
495
- ticket_data = {"raw_output": output}
496
- else:
497
- ticket_data = {"raw_output": output}
498
-
499
- self._update_metrics(True, execution_time)
500
-
501
- return MCPToolResult(
502
- success=True,
503
- data=ticket_data,
504
- execution_time=execution_time,
505
- metadata={
506
- "tool": "ticket_view",
507
- "operation": "view",
508
- "ticket_id": ticket_id,
509
- },
510
- )
511
- error_msg = stderr.decode() if stderr else stdout.decode()
512
- self._update_metrics(False, execution_time)
513
-
514
- return MCPToolResult(
515
- success=False,
516
- error=f"Failed to view ticket: {error_msg}",
517
- execution_time=execution_time,
518
- )
519
-
520
- except Exception as e:
521
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
522
- self._update_metrics(False, execution_time)
523
-
524
- return MCPToolResult(
525
- success=False,
526
- error=f"Ticket view failed: {e!s}",
527
- execution_time=execution_time,
528
- )
529
-
530
-
531
- class TicketSearchTool(BaseToolAdapter):
532
- """
533
- Search tickets by keywords.
534
-
535
- WHY: Users need to find specific tickets based on content, tags, or other criteria.
536
- This tool provides keyword search across ticket titles and descriptions.
537
- """
538
-
539
- def __init__(self):
540
- """Initialize the ticket search tool."""
541
- definition = MCPToolDefinition(
542
- name="ticket_search",
543
- description="Search tickets by keywords in title or description",
544
- input_schema={
545
- "type": "object",
546
- "properties": {
547
- "query": {"type": "string", "description": "Search query keywords"},
548
- "limit": {
549
- "type": "number",
550
- "description": "Maximum number of results",
551
- "default": 10,
552
- },
553
- "type": {
554
- "type": "string",
555
- "enum": ["all", "task", "issue", "epic"],
556
- "description": "Filter by ticket type",
557
- "default": "all",
558
- },
559
- },
560
- "required": ["query"],
561
- },
562
- )
563
- super().__init__(definition)
564
-
565
- async def invoke(self, invocation: MCPToolInvocation) -> MCPToolResult:
566
- """
567
- Search tickets using aitrackdown CLI.
568
-
569
- Args:
570
- invocation: Tool invocation request
571
-
572
- Returns:
573
- Tool execution result with matching tickets
574
- """
575
- start_time = datetime.now(timezone.utc)
576
-
577
- try:
578
- params = invocation.parameters
579
- query = params["query"]
580
- limit = params.get("limit", 10)
581
-
582
- # Build aitrackdown command - use search tasks
583
- cmd = ["aitrackdown", "search", "tasks", query, "--limit", str(limit)]
584
-
585
- if params.get("type") and params["type"] != "all":
586
- cmd.extend(["--type", params["type"]])
587
-
588
- # Execute command asynchronously
589
- process = await asyncio.create_subprocess_exec(
590
- *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
591
- )
592
-
593
- stdout, stderr = await process.communicate()
594
-
595
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
596
-
597
- if process.returncode == 0:
598
- try:
599
- # Try to parse JSON output
600
- results = json.loads(stdout.decode())
601
- except json.JSONDecodeError:
602
- # Fallback to text parsing if JSON fails
603
- output = stdout.decode().strip()
604
- results = {"raw_output": output, "query": query}
605
-
606
- self._update_metrics(True, execution_time)
607
-
608
- return MCPToolResult(
609
- success=True,
610
- data=results,
611
- execution_time=execution_time,
612
- metadata={
613
- "tool": "ticket_search",
614
- "operation": "search",
615
- "query": query,
616
- },
617
- )
618
- error_msg = stderr.decode() if stderr else stdout.decode()
619
- self._update_metrics(False, execution_time)
620
-
621
- return MCPToolResult(
622
- success=False,
623
- error=f"Failed to search tickets: {error_msg}",
624
- execution_time=execution_time,
625
- )
626
-
627
- except Exception as e:
628
- execution_time = (datetime.now(timezone.utc) - start_time).total_seconds()
629
- self._update_metrics(False, execution_time)
630
-
631
- return MCPToolResult(
632
- success=False,
633
- error=f"Ticket search failed: {e!s}",
634
- execution_time=execution_time,
635
- )
636
-
637
-
638
- # Export all ticket tools
639
- __all__ = [
640
- "TicketCreateTool",
641
- "TicketListTool",
642
- "TicketSearchTool",
643
- "TicketUpdateTool",
644
- "TicketViewTool",
645
- ]