mcp-ticketer 0.3.3__py3-none-any.whl → 0.3.4__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.

@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.3.3"
3
+ __version__ = "0.3.4"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -137,8 +137,9 @@ class LinearAdapter(BaseAdapter[Task]):
137
137
  # Load team data and workflow states concurrently
138
138
  team_id = await self._ensure_team_id()
139
139
 
140
- # Load workflow states for the team
140
+ # Load workflow states and labels for the team
141
141
  await self._load_workflow_states(team_id)
142
+ await self._load_team_labels(team_id)
142
143
 
143
144
  self._initialized = True
144
145
 
@@ -216,6 +217,85 @@ class LinearAdapter(BaseAdapter[Task]):
216
217
  except Exception as e:
217
218
  raise ValueError(f"Failed to load workflow states: {e}")
218
219
 
220
+ async def _load_team_labels(self, team_id: str) -> None:
221
+ """Load and cache labels for the team.
222
+
223
+ Args:
224
+ team_id: Linear team ID
225
+
226
+ """
227
+ query = """
228
+ query GetTeamLabels($teamId: ID!) {
229
+ team(id: $teamId) {
230
+ labels {
231
+ nodes {
232
+ id
233
+ name
234
+ color
235
+ description
236
+ }
237
+ }
238
+ }
239
+ }
240
+ """
241
+
242
+ try:
243
+ result = await self.client.execute_query(query, {"teamId": team_id})
244
+ self._labels_cache = result["team"]["labels"]["nodes"]
245
+ except Exception:
246
+ # Log error but don't fail - labels are optional
247
+ self._labels_cache = []
248
+
249
+ async def _resolve_label_ids(self, label_names: list[str]) -> list[str]:
250
+ """Resolve label names to Linear label IDs.
251
+
252
+ Args:
253
+ label_names: List of label names
254
+
255
+ Returns:
256
+ List of Linear label IDs that exist
257
+
258
+ """
259
+ import logging
260
+
261
+ logger = logging.getLogger(__name__)
262
+
263
+ if not self._labels_cache:
264
+ team_id = await self._ensure_team_id()
265
+ await self._load_team_labels(team_id)
266
+
267
+ if not self._labels_cache:
268
+ logger.warning("No labels found in team cache")
269
+ return []
270
+
271
+ # Create name -> ID mapping (case-insensitive)
272
+ label_map = {label["name"].lower(): label["id"] for label in self._labels_cache}
273
+
274
+ logger.debug(f"Available labels in team: {list(label_map.keys())}")
275
+
276
+ # Resolve label names to IDs
277
+ label_ids = []
278
+ unmatched_labels = []
279
+
280
+ for name in label_names:
281
+ label_id = label_map.get(name.lower())
282
+ if label_id:
283
+ label_ids.append(label_id)
284
+ logger.debug(f"Resolved label '{name}' to ID: {label_id}")
285
+ else:
286
+ unmatched_labels.append(name)
287
+ logger.warning(
288
+ f"Label '{name}' not found in team. Available labels: {list(label_map.keys())}"
289
+ )
290
+
291
+ if unmatched_labels:
292
+ logger.warning(
293
+ f"Could not resolve labels: {unmatched_labels}. "
294
+ f"Create them in Linear first or check spelling."
295
+ )
296
+
297
+ return label_ids
298
+
219
299
  def _get_state_mapping(self) -> dict[TicketState, str]:
220
300
  """Get mapping from universal states to Linear workflow state IDs.
221
301
 
@@ -311,12 +391,28 @@ class LinearAdapter(BaseAdapter[Task]):
311
391
  # Build issue input using mapper
312
392
  issue_input = build_linear_issue_input(task, team_id)
313
393
 
394
+ # Set default state if not provided
395
+ # Map OPEN to "unstarted" state (typically "To-Do" in Linear)
396
+ if task.state == TicketState.OPEN and self._workflow_states:
397
+ state_mapping = self._get_state_mapping()
398
+ if TicketState.OPEN in state_mapping:
399
+ issue_input["stateId"] = state_mapping[TicketState.OPEN]
400
+
314
401
  # Resolve assignee to user ID if provided
315
402
  if task.assignee:
316
403
  user_id = await self._get_user_id(task.assignee)
317
404
  if user_id:
318
405
  issue_input["assigneeId"] = user_id
319
406
 
407
+ # Resolve label names to IDs if provided
408
+ if task.tags:
409
+ label_ids = await self._resolve_label_ids(task.tags)
410
+ if label_ids:
411
+ issue_input["labelIds"] = label_ids
412
+ else:
413
+ # Remove labelIds if no labels resolved
414
+ issue_input.pop("labelIds", None)
415
+
320
416
  try:
321
417
  result = await self.client.execute_mutation(
322
418
  CREATE_ISSUE_MUTATION, {"input": issue_input}
@@ -489,6 +585,12 @@ class LinearAdapter(BaseAdapter[Task]):
489
585
  if user_id:
490
586
  update_input["assigneeId"] = user_id
491
587
 
588
+ # Resolve label names to IDs if provided
589
+ if "tags" in updates and updates["tags"]:
590
+ label_ids = await self._resolve_label_ids(updates["tags"])
591
+ if label_ids:
592
+ update_input["labelIds"] = label_ids
593
+
492
594
  # Execute update
493
595
  result = await self.client.execute_mutation(
494
596
  UPDATE_ISSUE_MUTATION, {"id": linear_id, "input": update_input}
@@ -242,9 +242,8 @@ def build_linear_issue_input(task: Task, team_id: str) -> dict[str, Any]:
242
242
 
243
243
  # Add labels (tags) if provided
244
244
  if task.tags:
245
- # Note: Linear requires label IDs, not names
246
- # This would need to be resolved by the adapter
247
- pass
245
+ # Note: This returns label names, will be resolved to IDs by adapter
246
+ issue_input["labelIds"] = task.tags # Temporary - adapter will resolve
248
247
 
249
248
  # Add Linear-specific metadata
250
249
  if task.metadata and "linear" in task.metadata:
mcp_ticketer/cli/main.py CHANGED
@@ -1266,6 +1266,16 @@ def create(
1266
1266
  assignee: Optional[str] = typer.Option(
1267
1267
  None, "--assignee", "-a", help="Assignee username"
1268
1268
  ),
1269
+ project: Optional[str] = typer.Option(
1270
+ None,
1271
+ "--project",
1272
+ help="Parent project/epic ID (synonym for --epic)",
1273
+ ),
1274
+ epic: Optional[str] = typer.Option(
1275
+ None,
1276
+ "--epic",
1277
+ help="Parent epic/project ID (synonym for --project)",
1278
+ ),
1269
1279
  adapter: Optional[AdapterType] = typer.Option(
1270
1280
  None, "--adapter", help="Override default adapter"
1271
1281
  ),
@@ -1337,6 +1347,9 @@ def create(
1337
1347
  # Priority 4: Default
1338
1348
  adapter_name = "aitrackdown"
1339
1349
 
1350
+ # Resolve project/epic synonym - prefer whichever is provided
1351
+ parent_epic_id = project or epic
1352
+
1340
1353
  # Create task data
1341
1354
  # Import Priority for type checking
1342
1355
  from ..core.models import Priority as PriorityEnum
@@ -1347,6 +1360,7 @@ def create(
1347
1360
  "priority": priority.value if isinstance(priority, PriorityEnum) else priority,
1348
1361
  "tags": tags or [],
1349
1362
  "assignee": assignee,
1363
+ "parent_epic": parent_epic_id,
1350
1364
  }
1351
1365
 
1352
1366
  # WORKAROUND: Use direct operation for Linear adapter to bypass worker subprocess issue
@@ -1377,6 +1391,7 @@ def create(
1377
1391
  ),
1378
1392
  tags=task_data.get("tags", []),
1379
1393
  assignee=task_data.get("assignee"),
1394
+ parent_epic=task_data.get("parent_epic"),
1380
1395
  )
1381
1396
 
1382
1397
  # Create ticket synchronously
@@ -260,13 +260,21 @@ class Epic(BaseTicket):
260
260
 
261
261
 
262
262
  class Task(BaseTicket):
263
- """Task - individual work item (can be ISSUE or TASK type)."""
263
+ """Task - individual work item (can be ISSUE or TASK type).
264
+
265
+ Note: The `project` field is a synonym for `parent_epic` to provide
266
+ flexibility in CLI and API usage. Both fields map to the same underlying
267
+ value (the parent epic/project ID).
268
+ """
264
269
 
265
270
  ticket_type: TicketType = Field(
266
271
  default=TicketType.ISSUE, description="Ticket type in hierarchy"
267
272
  )
268
273
  parent_issue: Optional[str] = Field(None, description="Parent issue ID (for tasks)")
269
- parent_epic: Optional[str] = Field(None, description="Parent epic ID (for issues)")
274
+ parent_epic: Optional[str] = Field(
275
+ None,
276
+ description="Parent epic/project ID (for issues). Synonym: 'project'",
277
+ )
270
278
  assignee: Optional[str] = Field(None, description="Assigned user")
271
279
  children: list[str] = Field(default_factory=list, description="Child task IDs")
272
280
 
@@ -274,6 +282,26 @@ class Task(BaseTicket):
274
282
  estimated_hours: Optional[float] = Field(None, description="Time estimate")
275
283
  actual_hours: Optional[float] = Field(None, description="Actual time spent")
276
284
 
285
+ @property
286
+ def project(self) -> Optional[str]:
287
+ """Synonym for parent_epic.
288
+
289
+ Returns:
290
+ Parent epic/project ID
291
+
292
+ """
293
+ return self.parent_epic
294
+
295
+ @project.setter
296
+ def project(self, value: Optional[str]) -> None:
297
+ """Set parent_epic via project synonym.
298
+
299
+ Args:
300
+ value: Parent epic/project ID
301
+
302
+ """
303
+ self.parent_epic = value
304
+
277
305
  def is_epic(self) -> bool:
278
306
  """Check if this is an epic (should use Epic class instead)."""
279
307
  return self.ticket_type == TicketType.EPIC
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Universal ticket management interface for AI agents with MCP support
5
5
  Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
6
6
  Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
@@ -1,5 +1,5 @@
1
1
  mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
2
- mcp_ticketer/__version__.py,sha256=j309F4mrlZqf8FcC91dNHQzvReN2SW3ubVrcqvAI7YY,1117
2
+ mcp_ticketer/__version__.py,sha256=XrTye_cA_opF1wX2csBbZnPCpEQ57tGLh94P_i6prws,1117
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
5
5
  mcp_ticketer/adapters/aitrackdown.py,sha256=Ecw2SQAGVQs5yMH6m2pj61LxCJsuy-g2bvF8uwTpLUE,22588
@@ -8,9 +8,9 @@ mcp_ticketer/adapters/hybrid.py,sha256=UADYZLc_UNw0xHPSbgguBNzvUCnuYn12Qi9ea-zdl
8
8
  mcp_ticketer/adapters/jira.py,sha256=labZFqOy_mmMmizC-RD1EQbu9m4LLtJywwZ956-_x5E,35347
9
9
  mcp_ticketer/adapters/linear.py,sha256=trm6ZhmlUl80sj51WAPAox_R2HQZXZ-h1QXJsrFYDCQ,587
10
10
  mcp_ticketer/adapters/linear/__init__.py,sha256=6l0ZoR6ZHSRcytLfps2AZuk5R189Pq1GfR5-YDQt8-Q,731
11
- mcp_ticketer/adapters/linear/adapter.py,sha256=wXZyhK1nK1QxPoGz2VsNdgtrktvICz4g4pk6naRcfYE,26144
11
+ mcp_ticketer/adapters/linear/adapter.py,sha256=IwmMCm9UFss1NcOnf3gdAlBJ8AeiVTG3FbeqW36gpVg,29606
12
12
  mcp_ticketer/adapters/linear/client.py,sha256=0UmWlSEcRiwnSMFYKL89KMrPPL8S8uZ5V6rIY_KFOQU,8803
13
- mcp_ticketer/adapters/linear/mappers.py,sha256=DbUrCI2gEYkK_PddjR62wva4q8PWcaBh5j1hWOczkPY,9596
13
+ mcp_ticketer/adapters/linear/mappers.py,sha256=GN1X7bOcU-5dhDW3dAtSEGivinhFBc8hoKYot8c5tCo,9631
14
14
  mcp_ticketer/adapters/linear/queries.py,sha256=LScSwVb9QA6gPiJJ1vHHoTwA6NA3TEPPD7dUzd1zA4g,7196
15
15
  mcp_ticketer/adapters/linear/types.py,sha256=ugXtRGLljDw6yoCnEVgdFs0xLR9ErLdnv4ffh9EAUhk,7874
16
16
  mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d4,138
@@ -24,7 +24,7 @@ mcp_ticketer/cli/diagnostics.py,sha256=jHF68ydW3RNVGumBnHUjUmq6YOjQD2UDkx0O7M__x
24
24
  mcp_ticketer/cli/discover.py,sha256=AF_qlQc1Oo0UkWayoF5pmRChS5J3fJjH6f2YZzd_k8w,13188
25
25
  mcp_ticketer/cli/gemini_configure.py,sha256=ZNSA1lBW-itVToza-JxW95Po7daVXKiZAh7lp6pmXMU,9343
26
26
  mcp_ticketer/cli/linear_commands.py,sha256=_8f8ze_1MbiUweU6RFHpldgfHLirysIdPjHr2_S0YhI,17319
27
- mcp_ticketer/cli/main.py,sha256=C06b393NtgRwLEp4CjOFCQQCNEQ9V-UoMMrJy33r_P0,73982
27
+ mcp_ticketer/cli/main.py,sha256=BFo7QYU0tTOHWSfTmegzDqflSYFytCE8Jw6QGB7TwaY,74470
28
28
  mcp_ticketer/cli/mcp_configure.py,sha256=RzV50UjXgOmvMp-9S0zS39psuvjffVByaMrqrUaAGAM,9594
29
29
  mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
30
30
  mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19OtHiZI,7884
@@ -38,7 +38,7 @@ mcp_ticketer/core/env_loader.py,sha256=VLCQhK50quM5e3LkrAGHD5on5vTBj18Z2IgNMNES1
38
38
  mcp_ticketer/core/exceptions.py,sha256=H1gUmNiOjVXn4CT-JLQcGXmjWxHaxxdFvwcpJLTrs-U,3621
39
39
  mcp_ticketer/core/http_client.py,sha256=s5ikMiwEJ8TJjNn73wu3gv3OdAtyBEpAqPnSroRMW2k,13971
40
40
  mcp_ticketer/core/mappers.py,sha256=1aG1jFsHTCwmGRVgOlXW-VOSTGzc86gv7qjDfiR1ups,17462
41
- mcp_ticketer/core/models.py,sha256=O3t7x1Q5l6OYS93HNSg7Kp-d6JapVjdTc6gAAfUBxUo,12495
41
+ mcp_ticketer/core/models.py,sha256=r3BqH0pK2ag2_7c7SSKMZe2G0K7eQID6qy2tkC-GvfI,13155
42
42
  mcp_ticketer/core/project_config.py,sha256=5W9YvDBBASEo5fBcSF-rlA1W3LwzkTv6_CEJXxnsOjc,23346
43
43
  mcp_ticketer/core/registry.py,sha256=ShYLDPE62KFJpB0kj_zFyQzRxSH3LkQEEuo1jaakb1k,3483
44
44
  mcp_ticketer/mcp/__init__.py,sha256=Y05eTzsPk0wH8yKNIM-ekpGjgSDO0bQr0EME-vOP4GE,123
@@ -54,9 +54,9 @@ mcp_ticketer/queue/queue.py,sha256=PIB_8gOE4rCb5_tBNKw9qD6YhSgH3Ei3IzVrUSY3F_o,1
54
54
  mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
55
55
  mcp_ticketer/queue/ticket_registry.py,sha256=FE6W_D8NA-66cJQ6VqghChF3JasYW845JVfEZdiqLbA,15449
56
56
  mcp_ticketer/queue/worker.py,sha256=AF6W1bdxWnHiJd6-iBWqTHkZ4lFflsS65CAtgFPR0FA,20983
57
- mcp_ticketer-0.3.3.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
58
- mcp_ticketer-0.3.3.dist-info/METADATA,sha256=jUhjNp9zoOr4y9AXssKHVoe6p7YEyS--WQhKDnEzmrA,13219
59
- mcp_ticketer-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
- mcp_ticketer-0.3.3.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
61
- mcp_ticketer-0.3.3.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
62
- mcp_ticketer-0.3.3.dist-info/RECORD,,
57
+ mcp_ticketer-0.3.4.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
58
+ mcp_ticketer-0.3.4.dist-info/METADATA,sha256=mcdoktp4RGFdnMy0_fMGrCQXJ72rTjCtVsF3fqrpDOg,13219
59
+ mcp_ticketer-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
+ mcp_ticketer-0.3.4.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
61
+ mcp_ticketer-0.3.4.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
62
+ mcp_ticketer-0.3.4.dist-info/RECORD,,