mcp-ticketer 0.4.0__py3-none-any.whl → 0.4.2__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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/cli/auggie_configure.py +66 -0
- mcp_ticketer/cli/codex_configure.py +68 -0
- mcp_ticketer/cli/gemini_configure.py +66 -0
- mcp_ticketer/cli/main.py +276 -39
- mcp_ticketer/cli/mcp_configure.py +71 -8
- mcp_ticketer/cli/platform_commands.py +5 -15
- mcp_ticketer/cli/ticket_commands.py +15 -5
- mcp_ticketer/mcp/server_sdk.py +93 -0
- mcp_ticketer/mcp/tools/__init__.py +38 -0
- mcp_ticketer/mcp/tools/attachment_tools.py +180 -0
- mcp_ticketer/mcp/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/tools/comment_tools.py +90 -0
- mcp_ticketer/mcp/tools/hierarchy_tools.py +383 -0
- mcp_ticketer/mcp/tools/pr_tools.py +154 -0
- mcp_ticketer/mcp/tools/search_tools.py +206 -0
- mcp_ticketer/mcp/tools/ticket_tools.py +277 -0
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/METADATA +30 -16
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/RECORD +23 -14
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.0.dist-info → mcp_ticketer-0.4.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Bulk operations for creating and updating multiple tickets.
|
|
2
|
+
|
|
3
|
+
This module implements tools for batch operations on tickets to improve
|
|
4
|
+
efficiency when working with multiple items.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ...core.models import Priority, Task, TicketState, TicketType
|
|
10
|
+
from ..server_sdk import get_adapter, mcp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@mcp.tool()
|
|
14
|
+
async def ticket_bulk_create(
|
|
15
|
+
tickets: list[dict[str, Any]],
|
|
16
|
+
) -> dict[str, Any]:
|
|
17
|
+
"""Create multiple tickets in a single operation.
|
|
18
|
+
|
|
19
|
+
Each ticket dict should contain at minimum a 'title' field, with optional
|
|
20
|
+
fields: description, priority, tags, assignee, ticket_type, parent_epic, parent_issue.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
tickets: List of ticket dictionaries to create
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Results of bulk creation including successes and failures
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
adapter = get_adapter()
|
|
31
|
+
|
|
32
|
+
if not tickets:
|
|
33
|
+
return {
|
|
34
|
+
"status": "error",
|
|
35
|
+
"error": "No tickets provided for bulk creation",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
results = {
|
|
39
|
+
"created": [],
|
|
40
|
+
"failed": [],
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for i, ticket_data in enumerate(tickets):
|
|
44
|
+
try:
|
|
45
|
+
# Validate required fields
|
|
46
|
+
if "title" not in ticket_data:
|
|
47
|
+
results["failed"].append(
|
|
48
|
+
{
|
|
49
|
+
"index": i,
|
|
50
|
+
"error": "Missing required field: title",
|
|
51
|
+
"data": ticket_data,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# Parse priority if provided
|
|
57
|
+
priority = Priority.MEDIUM # Default
|
|
58
|
+
if "priority" in ticket_data:
|
|
59
|
+
try:
|
|
60
|
+
priority = Priority(ticket_data["priority"].lower())
|
|
61
|
+
except ValueError:
|
|
62
|
+
results["failed"].append(
|
|
63
|
+
{
|
|
64
|
+
"index": i,
|
|
65
|
+
"error": f"Invalid priority: {ticket_data['priority']}",
|
|
66
|
+
"data": ticket_data,
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# Parse ticket type if provided
|
|
72
|
+
ticket_type = TicketType.ISSUE # Default
|
|
73
|
+
if "ticket_type" in ticket_data:
|
|
74
|
+
try:
|
|
75
|
+
ticket_type = TicketType(ticket_data["ticket_type"].lower())
|
|
76
|
+
except ValueError:
|
|
77
|
+
results["failed"].append(
|
|
78
|
+
{
|
|
79
|
+
"index": i,
|
|
80
|
+
"error": f"Invalid ticket_type: {ticket_data['ticket_type']}",
|
|
81
|
+
"data": ticket_data,
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
# Create task object
|
|
87
|
+
task = Task(
|
|
88
|
+
title=ticket_data["title"],
|
|
89
|
+
description=ticket_data.get("description", ""),
|
|
90
|
+
priority=priority,
|
|
91
|
+
ticket_type=ticket_type,
|
|
92
|
+
tags=ticket_data.get("tags", []),
|
|
93
|
+
assignee=ticket_data.get("assignee"),
|
|
94
|
+
parent_epic=ticket_data.get("parent_epic"),
|
|
95
|
+
parent_issue=ticket_data.get("parent_issue"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create via adapter
|
|
99
|
+
created = await adapter.create(task)
|
|
100
|
+
results["created"].append(
|
|
101
|
+
{
|
|
102
|
+
"index": i,
|
|
103
|
+
"ticket": created.model_dump(),
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
results["failed"].append(
|
|
109
|
+
{
|
|
110
|
+
"index": i,
|
|
111
|
+
"error": str(e),
|
|
112
|
+
"data": ticket_data,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"status": "completed",
|
|
118
|
+
"summary": {
|
|
119
|
+
"total": len(tickets),
|
|
120
|
+
"created": len(results["created"]),
|
|
121
|
+
"failed": len(results["failed"]),
|
|
122
|
+
},
|
|
123
|
+
"results": results,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return {
|
|
128
|
+
"status": "error",
|
|
129
|
+
"error": f"Bulk creation failed: {str(e)}",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@mcp.tool()
|
|
134
|
+
async def ticket_bulk_update(
|
|
135
|
+
updates: list[dict[str, Any]],
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Update multiple tickets in a single operation.
|
|
138
|
+
|
|
139
|
+
Each update dict must contain 'ticket_id' and at least one field to update.
|
|
140
|
+
Valid update fields: title, description, priority, state, assignee, tags.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
updates: List of update operation dictionaries
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Results of bulk update including successes and failures
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
adapter = get_adapter()
|
|
151
|
+
|
|
152
|
+
if not updates:
|
|
153
|
+
return {
|
|
154
|
+
"status": "error",
|
|
155
|
+
"error": "No updates provided for bulk operation",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
results = {
|
|
159
|
+
"updated": [],
|
|
160
|
+
"failed": [],
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for i, update_data in enumerate(updates):
|
|
164
|
+
try:
|
|
165
|
+
# Validate required fields
|
|
166
|
+
if "ticket_id" not in update_data:
|
|
167
|
+
results["failed"].append(
|
|
168
|
+
{
|
|
169
|
+
"index": i,
|
|
170
|
+
"error": "Missing required field: ticket_id",
|
|
171
|
+
"data": update_data,
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
ticket_id = update_data["ticket_id"]
|
|
177
|
+
|
|
178
|
+
# Build update dict
|
|
179
|
+
update_fields: dict[str, Any] = {}
|
|
180
|
+
|
|
181
|
+
if "title" in update_data:
|
|
182
|
+
update_fields["title"] = update_data["title"]
|
|
183
|
+
if "description" in update_data:
|
|
184
|
+
update_fields["description"] = update_data["description"]
|
|
185
|
+
if "assignee" in update_data:
|
|
186
|
+
update_fields["assignee"] = update_data["assignee"]
|
|
187
|
+
if "tags" in update_data:
|
|
188
|
+
update_fields["tags"] = update_data["tags"]
|
|
189
|
+
|
|
190
|
+
# Parse priority if provided
|
|
191
|
+
if "priority" in update_data:
|
|
192
|
+
try:
|
|
193
|
+
update_fields["priority"] = Priority(
|
|
194
|
+
update_data["priority"].lower()
|
|
195
|
+
)
|
|
196
|
+
except ValueError:
|
|
197
|
+
results["failed"].append(
|
|
198
|
+
{
|
|
199
|
+
"index": i,
|
|
200
|
+
"error": f"Invalid priority: {update_data['priority']}",
|
|
201
|
+
"data": update_data,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Parse state if provided
|
|
207
|
+
if "state" in update_data:
|
|
208
|
+
try:
|
|
209
|
+
update_fields["state"] = TicketState(
|
|
210
|
+
update_data["state"].lower()
|
|
211
|
+
)
|
|
212
|
+
except ValueError:
|
|
213
|
+
results["failed"].append(
|
|
214
|
+
{
|
|
215
|
+
"index": i,
|
|
216
|
+
"error": f"Invalid state: {update_data['state']}",
|
|
217
|
+
"data": update_data,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
if not update_fields:
|
|
223
|
+
results["failed"].append(
|
|
224
|
+
{
|
|
225
|
+
"index": i,
|
|
226
|
+
"error": "No valid update fields provided",
|
|
227
|
+
"data": update_data,
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
# Update via adapter
|
|
233
|
+
updated = await adapter.update(ticket_id, update_fields)
|
|
234
|
+
if updated is None:
|
|
235
|
+
results["failed"].append(
|
|
236
|
+
{
|
|
237
|
+
"index": i,
|
|
238
|
+
"error": f"Ticket {ticket_id} not found or update failed",
|
|
239
|
+
"data": update_data,
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
results["updated"].append(
|
|
244
|
+
{
|
|
245
|
+
"index": i,
|
|
246
|
+
"ticket": updated.model_dump(),
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
results["failed"].append(
|
|
252
|
+
{
|
|
253
|
+
"index": i,
|
|
254
|
+
"error": str(e),
|
|
255
|
+
"data": update_data,
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
"status": "completed",
|
|
261
|
+
"summary": {
|
|
262
|
+
"total": len(updates),
|
|
263
|
+
"updated": len(results["updated"]),
|
|
264
|
+
"failed": len(results["failed"]),
|
|
265
|
+
},
|
|
266
|
+
"results": results,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
return {
|
|
271
|
+
"status": "error",
|
|
272
|
+
"error": f"Bulk update failed: {str(e)}",
|
|
273
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Comment management tools for tickets.
|
|
2
|
+
|
|
3
|
+
This module implements tools for adding and retrieving comments on tickets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from ...core.models import Comment
|
|
9
|
+
from ..server_sdk import get_adapter, mcp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def ticket_comment(
|
|
14
|
+
ticket_id: str,
|
|
15
|
+
operation: str,
|
|
16
|
+
text: Optional[str] = None,
|
|
17
|
+
limit: int = 10,
|
|
18
|
+
offset: int = 0,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
"""Add or list comments on a ticket.
|
|
21
|
+
|
|
22
|
+
This tool supports two operations:
|
|
23
|
+
- 'add': Add a new comment to a ticket (requires 'text' parameter)
|
|
24
|
+
- 'list': Retrieve comments from a ticket (supports pagination)
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ticket_id: Unique identifier of the ticket
|
|
28
|
+
operation: Operation to perform - must be 'add' or 'list'
|
|
29
|
+
text: Comment text (required when operation='add')
|
|
30
|
+
limit: Maximum number of comments to return (used when operation='list', default: 10)
|
|
31
|
+
offset: Number of comments to skip for pagination (used when operation='list', default: 0)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Comment data or list of comments, or error information
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
adapter = get_adapter()
|
|
39
|
+
|
|
40
|
+
# Validate operation
|
|
41
|
+
if operation not in ["add", "list"]:
|
|
42
|
+
return {
|
|
43
|
+
"status": "error",
|
|
44
|
+
"error": f"Invalid operation '{operation}'. Must be 'add' or 'list'",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if operation == "add":
|
|
48
|
+
# Add comment operation
|
|
49
|
+
if not text:
|
|
50
|
+
return {
|
|
51
|
+
"status": "error",
|
|
52
|
+
"error": "Parameter 'text' is required when operation='add'",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Create comment object
|
|
56
|
+
comment = Comment(
|
|
57
|
+
ticket_id=ticket_id,
|
|
58
|
+
content=text,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Add comment via adapter
|
|
62
|
+
created = await adapter.add_comment(comment)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"status": "completed",
|
|
66
|
+
"operation": "add",
|
|
67
|
+
"comment": created.model_dump(),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
else: # operation == "list"
|
|
71
|
+
# List comments operation
|
|
72
|
+
comments = await adapter.get_comments(
|
|
73
|
+
ticket_id=ticket_id, limit=limit, offset=offset
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"status": "completed",
|
|
78
|
+
"operation": "list",
|
|
79
|
+
"ticket_id": ticket_id,
|
|
80
|
+
"comments": [comment.model_dump() for comment in comments],
|
|
81
|
+
"count": len(comments),
|
|
82
|
+
"limit": limit,
|
|
83
|
+
"offset": offset,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return {
|
|
88
|
+
"status": "error",
|
|
89
|
+
"error": f"Comment operation failed: {str(e)}",
|
|
90
|
+
}
|