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

Files changed (87) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +385 -6
  4. mcp_ticketer/adapters/asana/adapter.py +108 -0
  5. mcp_ticketer/adapters/asana/mappers.py +14 -0
  6. mcp_ticketer/adapters/github.py +525 -11
  7. mcp_ticketer/adapters/hybrid.py +47 -5
  8. mcp_ticketer/adapters/jira.py +521 -0
  9. mcp_ticketer/adapters/linear/adapter.py +1784 -101
  10. mcp_ticketer/adapters/linear/client.py +85 -3
  11. mcp_ticketer/adapters/linear/mappers.py +96 -8
  12. mcp_ticketer/adapters/linear/queries.py +168 -1
  13. mcp_ticketer/adapters/linear/types.py +80 -4
  14. mcp_ticketer/analysis/__init__.py +56 -0
  15. mcp_ticketer/analysis/dependency_graph.py +255 -0
  16. mcp_ticketer/analysis/health_assessment.py +304 -0
  17. mcp_ticketer/analysis/orphaned.py +218 -0
  18. mcp_ticketer/analysis/project_status.py +594 -0
  19. mcp_ticketer/analysis/similarity.py +224 -0
  20. mcp_ticketer/analysis/staleness.py +266 -0
  21. mcp_ticketer/automation/__init__.py +11 -0
  22. mcp_ticketer/automation/project_updates.py +378 -0
  23. mcp_ticketer/cli/adapter_diagnostics.py +3 -1
  24. mcp_ticketer/cli/auggie_configure.py +17 -5
  25. mcp_ticketer/cli/codex_configure.py +97 -61
  26. mcp_ticketer/cli/configure.py +851 -103
  27. mcp_ticketer/cli/cursor_configure.py +314 -0
  28. mcp_ticketer/cli/diagnostics.py +13 -12
  29. mcp_ticketer/cli/discover.py +5 -0
  30. mcp_ticketer/cli/gemini_configure.py +17 -5
  31. mcp_ticketer/cli/init_command.py +880 -0
  32. mcp_ticketer/cli/instruction_commands.py +6 -0
  33. mcp_ticketer/cli/main.py +233 -3151
  34. mcp_ticketer/cli/mcp_configure.py +672 -98
  35. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  36. mcp_ticketer/cli/platform_detection.py +77 -12
  37. mcp_ticketer/cli/platform_installer.py +536 -0
  38. mcp_ticketer/cli/project_update_commands.py +350 -0
  39. mcp_ticketer/cli/setup_command.py +639 -0
  40. mcp_ticketer/cli/simple_health.py +12 -10
  41. mcp_ticketer/cli/ticket_commands.py +264 -24
  42. mcp_ticketer/core/__init__.py +28 -6
  43. mcp_ticketer/core/adapter.py +166 -1
  44. mcp_ticketer/core/config.py +21 -21
  45. mcp_ticketer/core/exceptions.py +7 -1
  46. mcp_ticketer/core/label_manager.py +732 -0
  47. mcp_ticketer/core/mappers.py +31 -19
  48. mcp_ticketer/core/models.py +135 -0
  49. mcp_ticketer/core/onepassword_secrets.py +1 -1
  50. mcp_ticketer/core/priority_matcher.py +463 -0
  51. mcp_ticketer/core/project_config.py +132 -14
  52. mcp_ticketer/core/session_state.py +171 -0
  53. mcp_ticketer/core/state_matcher.py +592 -0
  54. mcp_ticketer/core/url_parser.py +425 -0
  55. mcp_ticketer/core/validators.py +69 -0
  56. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  57. mcp_ticketer/mcp/server/main.py +106 -25
  58. mcp_ticketer/mcp/server/routing.py +655 -0
  59. mcp_ticketer/mcp/server/server_sdk.py +58 -0
  60. mcp_ticketer/mcp/server/tools/__init__.py +31 -12
  61. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  62. mcp_ticketer/mcp/server/tools/attachment_tools.py +6 -8
  63. mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
  64. mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
  65. mcp_ticketer/mcp/server/tools/config_tools.py +1184 -136
  66. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  67. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
  68. mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
  69. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  70. mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
  71. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  72. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  73. mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
  74. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  75. mcp_ticketer/mcp/server/tools/ticket_tools.py +1070 -123
  76. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
  77. mcp_ticketer/queue/worker.py +1 -1
  78. mcp_ticketer/utils/__init__.py +5 -0
  79. mcp_ticketer/utils/token_utils.py +246 -0
  80. mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
  81. mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
  82. mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
  83. mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
  84. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
  85. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
  86. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
  87. {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
@@ -58,6 +58,7 @@ class MCPTicketServer:
58
58
  """Initialize MCP server.
59
59
 
60
60
  Args:
61
+ ----
61
62
  adapter_type: Type of adapter to use
62
63
  config: Adapter configuration
63
64
 
@@ -71,9 +72,11 @@ class MCPTicketServer:
71
72
  """Handle JSON-RPC request.
72
73
 
73
74
  Args:
75
+ ----
74
76
  request: JSON-RPC request
75
77
 
76
78
  Returns:
79
+ -------
77
80
  JSON-RPC response
78
81
 
79
82
  """
@@ -158,11 +161,13 @@ class MCPTicketServer:
158
161
  """Create error response.
159
162
 
160
163
  Args:
164
+ ----
161
165
  request_id: Request ID
162
166
  code: Error code
163
167
  message: Error message
164
168
 
165
169
  Returns:
170
+ -------
166
171
  Error response
167
172
 
168
173
  """
@@ -178,7 +183,7 @@ class MCPTicketServer:
178
183
  request = CreateTicketRequest(**params)
179
184
 
180
185
  # Build task from validated DTO
181
- task = Task(
186
+ task = Task( # type: ignore[call-arg]
182
187
  title=request.title,
183
188
  description=request.description,
184
189
  priority=Priority(request.priority),
@@ -256,7 +261,7 @@ class MCPTicketServer:
256
261
 
257
262
  async def _handle_search(self, params: dict[str, Any]) -> dict[str, Any]:
258
263
  """Handle ticket search - SYNCHRONOUS."""
259
- query = SearchQuery(
264
+ query = SearchQuery( # type: ignore[call-arg]
260
265
  query=params.get("query"),
261
266
  state=TicketState(params["state"]) if params.get("state") else None,
262
267
  priority=Priority(params["priority"]) if params.get("priority") else None,
@@ -292,7 +297,7 @@ class MCPTicketServer:
292
297
  operation = params.get("operation", "add")
293
298
 
294
299
  if operation == "add":
295
- comment = Comment(
300
+ comment = Comment( # type: ignore[call-arg]
296
301
  ticket_id=params["ticket_id"],
297
302
  content=params["content"],
298
303
  author=params.get("author"),
@@ -326,12 +331,17 @@ class MCPTicketServer:
326
331
  request = CreateEpicRequest(**params)
327
332
 
328
333
  # Build epic from validated DTO
329
- epic = Epic(
334
+ metadata: dict[str, Any] = {}
335
+ if request.target_date:
336
+ metadata["target_date"] = request.target_date
337
+ if request.lead_id:
338
+ metadata["lead_id"] = request.lead_id
339
+
340
+ epic = Epic( # type: ignore[call-arg]
330
341
  title=request.title,
331
342
  description=request.description,
332
343
  child_issues=request.child_issues,
333
- target_date=request.target_date,
334
- lead_id=request.lead_id,
344
+ metadata=metadata,
335
345
  )
336
346
 
337
347
  # Create directly
@@ -372,7 +382,7 @@ class MCPTicketServer:
372
382
  request = CreateIssueRequest(**params)
373
383
 
374
384
  # Build task (issue) from validated DTO
375
- task = Task(
385
+ task = Task( # type: ignore[call-arg]
376
386
  title=request.title,
377
387
  description=request.description,
378
388
  parent_epic=request.epic_id, # Issues are tasks under epics
@@ -405,7 +415,7 @@ class MCPTicketServer:
405
415
  request = CreateTaskRequest(**params)
406
416
 
407
417
  # Build task from validated DTO
408
- task = Task(
418
+ task = Task( # type: ignore[call-arg]
409
419
  title=request.title,
410
420
  parent_issue=request.parent_id,
411
421
  description=request.description,
@@ -437,16 +447,19 @@ class MCPTicketServer:
437
447
  )
438
448
 
439
449
  # Build tree structure
440
- tree = {"epic": epic.model_dump(), "issues": []}
450
+ tree: dict[str, Any] = {"epic": epic.model_dump(), "issues": []}
441
451
 
442
452
  # Get issues in epic if depth allows (depth 1 = epic only, depth 2+ = issues)
443
453
  if max_depth > 1:
444
454
  issues = await self.adapter.list_issues_by_epic(epic_id)
445
455
  for issue in issues:
446
- issue_node = {"issue": issue.model_dump(), "tasks": []}
456
+ issue_node: dict[str, Any] = {
457
+ "issue": issue.model_dump(),
458
+ "tasks": [],
459
+ }
447
460
 
448
461
  # Get tasks in issue if depth allows (depth 3+ = tasks)
449
- if max_depth > 2:
462
+ if max_depth > 2 and issue.id:
450
463
  tasks = await self.adapter.list_tasks_by_issue(issue.id)
451
464
  issue_node["tasks"] = [task.model_dump() for task in tasks]
452
465
 
@@ -544,7 +557,7 @@ class MCPTicketServer:
544
557
  include_parents = params.get("include_parents", True)
545
558
 
546
559
  # Perform basic search
547
- search_query = SearchQuery(
560
+ search_query = SearchQuery( # type: ignore[call-arg]
548
561
  query=query,
549
562
  state=TicketState(params["state"]) if params.get("state") else None,
550
563
  priority=Priority(params["priority"]) if params.get("priority") else None,
@@ -649,6 +662,12 @@ class MCPTicketServer:
649
662
  "error": str(e),
650
663
  "ticket_id": ticket_id,
651
664
  }
665
+ # Fallback if not GitHub adapter instance
666
+ return {
667
+ "success": False,
668
+ "error": "GitHub adapter not properly initialized",
669
+ "ticket_id": ticket_id,
670
+ }
652
671
  elif "linear" in adapter_name:
653
672
  # Linear adapter needs GitHub config for PR creation
654
673
  from ..adapters.linear import LinearAdapter
@@ -693,6 +712,12 @@ class MCPTicketServer:
693
712
  "error": str(e),
694
713
  "ticket_id": ticket_id,
695
714
  }
715
+ # Fallback if not Linear adapter instance
716
+ return {
717
+ "success": False,
718
+ "error": "Linear adapter not properly initialized",
719
+ "ticket_id": ticket_id,
720
+ }
696
721
  else:
697
722
  return {
698
723
  "success": False,
@@ -717,9 +742,11 @@ class MCPTicketServer:
717
742
 
718
743
  if isinstance(self.adapter, GitHubAdapter):
719
744
  try:
720
- result = await self.adapter.link_existing_pull_request(
721
- ticket_id=ticket_id,
722
- pr_url=pr_url,
745
+ result: dict[str, Any] = (
746
+ await self.adapter.link_existing_pull_request(
747
+ ticket_id=ticket_id,
748
+ pr_url=pr_url,
749
+ )
723
750
  )
724
751
  return result
725
752
  except Exception as e:
@@ -729,16 +756,25 @@ class MCPTicketServer:
729
756
  "ticket_id": ticket_id,
730
757
  "pr_url": pr_url,
731
758
  }
759
+ # Fallback if not GitHub adapter instance
760
+ return {
761
+ "success": False,
762
+ "error": "GitHub adapter not properly initialized",
763
+ "ticket_id": ticket_id,
764
+ "pr_url": pr_url,
765
+ }
732
766
  elif "linear" in adapter_name:
733
767
  from ..adapters.linear import LinearAdapter
734
768
 
735
769
  if isinstance(self.adapter, LinearAdapter):
736
770
  try:
737
- result = await self.adapter.link_to_pull_request(
738
- ticket_id=ticket_id,
739
- pr_url=pr_url,
771
+ link_result: dict[str, Any] = (
772
+ await self.adapter.link_to_pull_request(
773
+ ticket_id=ticket_id,
774
+ pr_url=pr_url,
775
+ )
740
776
  )
741
- return result
777
+ return link_result
742
778
  except Exception as e:
743
779
  return {
744
780
  "success": False,
@@ -746,6 +782,13 @@ class MCPTicketServer:
746
782
  "ticket_id": ticket_id,
747
783
  "pr_url": pr_url,
748
784
  }
785
+ # Fallback if not Linear adapter instance
786
+ return {
787
+ "success": False,
788
+ "error": "Linear adapter not properly initialized",
789
+ "ticket_id": ticket_id,
790
+ "pr_url": pr_url,
791
+ }
749
792
  else:
750
793
  return {
751
794
  "success": False,
@@ -758,9 +801,11 @@ class MCPTicketServer:
758
801
  """Handle initialize request from MCP client.
759
802
 
760
803
  Args:
804
+ ----
761
805
  params: Initialize parameters
762
806
 
763
807
  Returns:
808
+ -------
764
809
  Server capabilities
765
810
 
766
811
  """
@@ -889,9 +934,11 @@ class MCPTicketServer:
889
934
  """Handle tool invocation from MCP client.
890
935
 
891
936
  Args:
937
+ ----
892
938
  params: Contains 'name' and 'arguments' fields
893
939
 
894
940
  Returns:
941
+ -------
895
942
  MCP formatted response with content array
896
943
 
897
944
  """
@@ -1145,18 +1192,48 @@ async def main() -> None:
1145
1192
 
1146
1193
 
1147
1194
  def _load_env_configuration() -> dict[str, Any] | None:
1148
- """Load adapter configuration from .env files.
1195
+ """Load adapter configuration from environment variables and .env files.
1149
1196
 
1150
- Checks .env.local first (highest priority), then .env.
1197
+ Priority order (highest to lowest):
1198
+ 1. os.environ (set by MCP clients like Claude Desktop)
1199
+ 2. .env.local file (local overrides)
1200
+ 3. .env file (default configuration)
1151
1201
 
1152
1202
  Returns:
1203
+ -------
1153
1204
  Dictionary with 'adapter_type' and 'adapter_config' keys, or None if no config found
1154
1205
 
1155
1206
  """
1156
- # Check for .env files in order of preference
1157
- env_files = [".env.local", ".env"]
1207
+ import os
1208
+
1158
1209
  env_vars = {}
1159
1210
 
1211
+ # Priority 1: Check process environment variables (set by MCP client)
1212
+ # This allows Claude Desktop and other MCP clients to configure the adapter
1213
+ relevant_env_keys = [
1214
+ "MCP_TICKETER_ADAPTER",
1215
+ "LINEAR_API_KEY",
1216
+ "LINEAR_TEAM_ID",
1217
+ "LINEAR_TEAM_KEY",
1218
+ "LINEAR_API_URL",
1219
+ "JIRA_SERVER",
1220
+ "JIRA_EMAIL",
1221
+ "JIRA_API_TOKEN",
1222
+ "JIRA_PROJECT_KEY",
1223
+ "GITHUB_TOKEN",
1224
+ "GITHUB_OWNER",
1225
+ "GITHUB_REPO",
1226
+ "MCP_TICKETER_BASE_PATH",
1227
+ ]
1228
+
1229
+ for key in relevant_env_keys:
1230
+ if os.environ.get(key):
1231
+ env_vars[key] = os.environ[key]
1232
+
1233
+ # Priority 2: Check .env files (only for keys not already set)
1234
+ # This allows .env files to provide fallback values
1235
+ env_files = [".env.local", ".env"]
1236
+
1160
1237
  for env_file in env_files:
1161
1238
  env_path = Path.cwd() / env_file
1162
1239
  if env_path.exists():
@@ -1169,7 +1246,9 @@ def _load_env_configuration() -> dict[str, Any] | None:
1169
1246
  key, value = line.split("=", 1)
1170
1247
  key = key.strip()
1171
1248
  value = value.strip().strip('"').strip("'")
1172
- if value: # Only add non-empty values
1249
+
1250
+ # Only set if not already in env_vars (os.environ takes priority)
1251
+ if key not in env_vars and value:
1173
1252
  env_vars[key] = value
1174
1253
  except Exception:
1175
1254
  continue
@@ -1205,14 +1284,16 @@ def _build_adapter_config_from_env_vars(
1205
1284
  """Build adapter configuration from parsed environment variables.
1206
1285
 
1207
1286
  Args:
1287
+ ----
1208
1288
  adapter_type: Type of adapter to configure
1209
1289
  env_vars: Dictionary of environment variables from .env files
1210
1290
 
1211
1291
  Returns:
1292
+ -------
1212
1293
  Dictionary of adapter configuration
1213
1294
 
1214
1295
  """
1215
- config = {}
1296
+ config: dict[str, Any] = {}
1216
1297
 
1217
1298
  if adapter_type == "linear":
1218
1299
  # Linear adapter configuration