mcp-ticketer 0.3.5__py3-none-any.whl → 0.12.0__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 (84) hide show
  1. mcp_ticketer/__version__.py +3 -3
  2. mcp_ticketer/adapters/__init__.py +2 -0
  3. mcp_ticketer/adapters/aitrackdown.py +263 -14
  4. mcp_ticketer/adapters/asana/__init__.py +15 -0
  5. mcp_ticketer/adapters/asana/adapter.py +1308 -0
  6. mcp_ticketer/adapters/asana/client.py +292 -0
  7. mcp_ticketer/adapters/asana/mappers.py +334 -0
  8. mcp_ticketer/adapters/asana/types.py +146 -0
  9. mcp_ticketer/adapters/github.py +326 -109
  10. mcp_ticketer/adapters/hybrid.py +11 -11
  11. mcp_ticketer/adapters/jira.py +271 -25
  12. mcp_ticketer/adapters/linear/adapter.py +693 -39
  13. mcp_ticketer/adapters/linear/client.py +61 -9
  14. mcp_ticketer/adapters/linear/mappers.py +9 -3
  15. mcp_ticketer/adapters/linear/queries.py +9 -7
  16. mcp_ticketer/cache/memory.py +9 -8
  17. mcp_ticketer/cli/adapter_diagnostics.py +1 -1
  18. mcp_ticketer/cli/auggie_configure.py +104 -15
  19. mcp_ticketer/cli/codex_configure.py +188 -32
  20. mcp_ticketer/cli/configure.py +37 -48
  21. mcp_ticketer/cli/diagnostics.py +20 -18
  22. mcp_ticketer/cli/discover.py +292 -26
  23. mcp_ticketer/cli/gemini_configure.py +107 -26
  24. mcp_ticketer/cli/instruction_commands.py +429 -0
  25. mcp_ticketer/cli/linear_commands.py +105 -22
  26. mcp_ticketer/cli/main.py +1830 -435
  27. mcp_ticketer/cli/mcp_configure.py +296 -89
  28. mcp_ticketer/cli/migrate_config.py +12 -8
  29. mcp_ticketer/cli/platform_commands.py +123 -0
  30. mcp_ticketer/cli/platform_detection.py +412 -0
  31. mcp_ticketer/cli/python_detection.py +126 -0
  32. mcp_ticketer/cli/queue_commands.py +15 -15
  33. mcp_ticketer/cli/simple_health.py +1 -1
  34. mcp_ticketer/cli/ticket_commands.py +773 -0
  35. mcp_ticketer/cli/update_checker.py +313 -0
  36. mcp_ticketer/cli/utils.py +67 -62
  37. mcp_ticketer/core/__init__.py +14 -1
  38. mcp_ticketer/core/adapter.py +84 -15
  39. mcp_ticketer/core/config.py +44 -39
  40. mcp_ticketer/core/env_discovery.py +42 -12
  41. mcp_ticketer/core/env_loader.py +15 -14
  42. mcp_ticketer/core/exceptions.py +3 -3
  43. mcp_ticketer/core/http_client.py +26 -26
  44. mcp_ticketer/core/instructions.py +405 -0
  45. mcp_ticketer/core/mappers.py +11 -11
  46. mcp_ticketer/core/models.py +50 -20
  47. mcp_ticketer/core/onepassword_secrets.py +379 -0
  48. mcp_ticketer/core/project_config.py +57 -35
  49. mcp_ticketer/core/registry.py +3 -3
  50. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  51. mcp_ticketer/mcp/__init__.py +29 -1
  52. mcp_ticketer/mcp/__main__.py +60 -0
  53. mcp_ticketer/mcp/server/__init__.py +25 -0
  54. mcp_ticketer/mcp/server/__main__.py +60 -0
  55. mcp_ticketer/mcp/{dto.py → server/dto.py} +32 -32
  56. mcp_ticketer/mcp/{server.py → server/main.py} +127 -74
  57. mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +2 -2
  58. mcp_ticketer/mcp/server/server_sdk.py +93 -0
  59. mcp_ticketer/mcp/server/tools/__init__.py +47 -0
  60. mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
  61. mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
  62. mcp_ticketer/mcp/server/tools/comment_tools.py +90 -0
  63. mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
  64. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +532 -0
  65. mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
  66. mcp_ticketer/mcp/server/tools/pr_tools.py +154 -0
  67. mcp_ticketer/mcp/server/tools/search_tools.py +206 -0
  68. mcp_ticketer/mcp/server/tools/ticket_tools.py +430 -0
  69. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
  70. mcp_ticketer/queue/__init__.py +1 -0
  71. mcp_ticketer/queue/health_monitor.py +5 -4
  72. mcp_ticketer/queue/manager.py +15 -51
  73. mcp_ticketer/queue/queue.py +19 -19
  74. mcp_ticketer/queue/run_worker.py +1 -1
  75. mcp_ticketer/queue/ticket_registry.py +14 -14
  76. mcp_ticketer/queue/worker.py +16 -14
  77. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +168 -32
  78. mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
  79. mcp_ticketer-0.3.5.dist-info/RECORD +0 -62
  80. /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
  81. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
  82. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
  83. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
  84. {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
@@ -47,29 +47,29 @@ class AdapterConfig:
47
47
  enabled: bool = True
48
48
 
49
49
  # Common fields (not all adapters use all fields)
50
- api_key: Optional[str] = None
51
- token: Optional[str] = None
50
+ api_key: str | None = None
51
+ token: str | None = None
52
52
 
53
53
  # Linear-specific
54
- team_id: Optional[str] = None
55
- team_key: Optional[str] = None
56
- workspace: Optional[str] = None
54
+ team_id: str | None = None
55
+ team_key: str | None = None
56
+ workspace: str | None = None
57
57
 
58
58
  # JIRA-specific
59
- server: Optional[str] = None
60
- email: Optional[str] = None
61
- api_token: Optional[str] = None
62
- project_key: Optional[str] = None
59
+ server: str | None = None
60
+ email: str | None = None
61
+ api_token: str | None = None
62
+ project_key: str | None = None
63
63
 
64
64
  # GitHub-specific
65
- owner: Optional[str] = None
66
- repo: Optional[str] = None
65
+ owner: str | None = None
66
+ repo: str | None = None
67
67
 
68
68
  # AITrackdown-specific
69
- base_path: Optional[str] = None
69
+ base_path: str | None = None
70
70
 
71
71
  # Project ID (can be used by any adapter for scoping)
72
- project_id: Optional[str] = None
72
+ project_id: str | None = None
73
73
 
74
74
  # Additional adapter-specific configuration
75
75
  additional_config: dict[str, Any] = field(default_factory=dict)
@@ -126,9 +126,9 @@ class ProjectConfig:
126
126
  """Configuration for a specific project."""
127
127
 
128
128
  adapter: str
129
- api_key: Optional[str] = None
130
- project_id: Optional[str] = None
131
- team_id: Optional[str] = None
129
+ api_key: str | None = None
130
+ project_id: str | None = None
131
+ team_id: str | None = None
132
132
  additional_config: dict[str, Any] = field(default_factory=dict)
133
133
 
134
134
  def to_dict(self) -> dict[str, Any]:
@@ -147,7 +147,7 @@ class HybridConfig:
147
147
 
148
148
  enabled: bool = False
149
149
  adapters: list[str] = field(default_factory=list)
150
- primary_adapter: Optional[str] = None
150
+ primary_adapter: str | None = None
151
151
  sync_strategy: SyncStrategy = SyncStrategy.PRIMARY_SOURCE
152
152
 
153
153
  def to_dict(self) -> dict[str, Any]:
@@ -172,11 +172,16 @@ class TicketerConfig:
172
172
  default_adapter: str = "aitrackdown"
173
173
  project_configs: dict[str, ProjectConfig] = field(default_factory=dict)
174
174
  adapters: dict[str, AdapterConfig] = field(default_factory=dict)
175
- hybrid_mode: Optional[HybridConfig] = None
175
+ hybrid_mode: HybridConfig | None = None
176
+
177
+ # Default values for ticket operations
178
+ default_user: str | None = None # Default assignee (user_id or email)
179
+ default_project: str | None = None # Default project/epic ID
180
+ default_epic: str | None = None # Alias for default_project (backward compat)
176
181
 
177
182
  def to_dict(self) -> dict[str, Any]:
178
183
  """Convert to dictionary for JSON serialization."""
179
- return {
184
+ result = {
180
185
  "default_adapter": self.default_adapter,
181
186
  "project_configs": {
182
187
  path: config.to_dict() for path, config in self.project_configs.items()
@@ -186,6 +191,14 @@ class TicketerConfig:
186
191
  },
187
192
  "hybrid_mode": self.hybrid_mode.to_dict() if self.hybrid_mode else None,
188
193
  }
194
+ # Add optional fields if set
195
+ if self.default_user is not None:
196
+ result["default_user"] = self.default_user
197
+ if self.default_project is not None:
198
+ result["default_project"] = self.default_project
199
+ if self.default_epic is not None:
200
+ result["default_epic"] = self.default_epic
201
+ return result
189
202
 
190
203
  @classmethod
191
204
  def from_dict(cls, data: dict[str, Any]) -> "TicketerConfig":
@@ -212,6 +225,9 @@ class TicketerConfig:
212
225
  project_configs=project_configs,
213
226
  adapters=adapters,
214
227
  hybrid_mode=hybrid_mode,
228
+ default_user=data.get("default_user"),
229
+ default_project=data.get("default_project"),
230
+ default_epic=data.get("default_epic"),
215
231
  )
216
232
 
217
233
 
@@ -219,7 +235,7 @@ class ConfigValidator:
219
235
  """Validate adapter configurations."""
220
236
 
221
237
  @staticmethod
222
- def validate_linear_config(config: dict[str, Any]) -> tuple[bool, Optional[str]]:
238
+ def validate_linear_config(config: dict[str, Any]) -> tuple[bool, str | None]:
223
239
  """Validate Linear adapter configuration.
224
240
 
225
241
  Returns:
@@ -241,7 +257,7 @@ class ConfigValidator:
241
257
  return True, None
242
258
 
243
259
  @staticmethod
244
- def validate_github_config(config: dict[str, Any]) -> tuple[bool, Optional[str]]:
260
+ def validate_github_config(config: dict[str, Any]) -> tuple[bool, str | None]:
245
261
  """Validate GitHub adapter configuration.
246
262
 
247
263
  Returns:
@@ -270,7 +286,7 @@ class ConfigValidator:
270
286
  return True, None
271
287
 
272
288
  @staticmethod
273
- def validate_jira_config(config: dict[str, Any]) -> tuple[bool, Optional[str]]:
289
+ def validate_jira_config(config: dict[str, Any]) -> tuple[bool, str | None]:
274
290
  """Validate JIRA adapter configuration.
275
291
 
276
292
  Returns:
@@ -292,7 +308,7 @@ class ConfigValidator:
292
308
  @staticmethod
293
309
  def validate_aitrackdown_config(
294
310
  config: dict[str, Any],
295
- ) -> tuple[bool, Optional[str]]:
311
+ ) -> tuple[bool, str | None]:
296
312
  """Validate AITrackdown adapter configuration.
297
313
 
298
314
  Returns:
@@ -306,7 +322,7 @@ class ConfigValidator:
306
322
  @classmethod
307
323
  def validate(
308
324
  cls, adapter_type: str, config: dict[str, Any]
309
- ) -> tuple[bool, Optional[str]]:
325
+ ) -> tuple[bool, str | None]:
310
326
  """Validate configuration for any adapter type.
311
327
 
312
328
  Args:
@@ -350,7 +366,7 @@ class ConfigResolver:
350
366
  PROJECT_CONFIG_SUBPATH = ".mcp-ticketer" / Path("config.json")
351
367
 
352
368
  def __init__(
353
- self, project_path: Optional[Path] = None, enable_env_discovery: bool = True
369
+ self, project_path: Path | None = None, enable_env_discovery: bool = True
354
370
  ):
355
371
  """Initialize config resolver.
356
372
 
@@ -361,8 +377,8 @@ class ConfigResolver:
361
377
  """
362
378
  self.project_path = project_path or Path.cwd()
363
379
  self.enable_env_discovery = enable_env_discovery
364
- self._project_config: Optional[TicketerConfig] = None
365
- self._discovered_config: Optional[DiscoveryResult] = None
380
+ self._project_config: TicketerConfig | None = None
381
+ self._discovered_config: DiscoveryResult | None = None
366
382
 
367
383
  def load_global_config(self) -> TicketerConfig:
368
384
  """Load default configuration (global config loading removed for security).
@@ -382,8 +398,8 @@ class ConfigResolver:
382
398
  return default_config
383
399
 
384
400
  def load_project_config(
385
- self, project_path: Optional[Path] = None
386
- ) -> Optional[TicketerConfig]:
401
+ self, project_path: Path | None = None
402
+ ) -> TicketerConfig | None:
387
403
  """Load project-specific configuration.
388
404
 
389
405
  Args:
@@ -424,7 +440,7 @@ class ConfigResolver:
424
440
  self.save_project_config(config)
425
441
 
426
442
  def save_project_config(
427
- self, config: TicketerConfig, project_path: Optional[Path] = None
443
+ self, config: TicketerConfig, project_path: Path | None = None
428
444
  ) -> None:
429
445
  """Save project-specific configuration.
430
446
 
@@ -461,8 +477,8 @@ class ConfigResolver:
461
477
 
462
478
  def resolve_adapter_config(
463
479
  self,
464
- adapter_name: Optional[str] = None,
465
- cli_overrides: Optional[dict[str, Any]] = None,
480
+ adapter_name: str | None = None,
481
+ cli_overrides: dict[str, Any] | None = None,
466
482
  ) -> dict[str, Any]:
467
483
  """Resolve adapter configuration with hierarchical precedence.
468
484
 
@@ -582,6 +598,12 @@ class ConfigResolver:
582
598
  overrides["team_id"] = os.getenv("MCP_TICKETER_LINEAR_TEAM_ID")
583
599
  if os.getenv("LINEAR_API_KEY"):
584
600
  overrides["api_key"] = os.getenv("LINEAR_API_KEY")
601
+ if os.getenv("LINEAR_TEAM_ID"):
602
+ overrides["team_id"] = os.getenv("LINEAR_TEAM_ID")
603
+ if os.getenv("LINEAR_TEAM_KEY"):
604
+ overrides["team_key"] = os.getenv("LINEAR_TEAM_KEY")
605
+ if os.getenv("MCP_TICKETER_LINEAR_TEAM_KEY"):
606
+ overrides["team_key"] = os.getenv("MCP_TICKETER_LINEAR_TEAM_KEY")
585
607
 
586
608
  elif adapter_type == AdapterType.GITHUB.value:
587
609
  if os.getenv("MCP_TICKETER_GITHUB_TOKEN"):
@@ -623,7 +645,7 @@ class ConfigResolver:
623
645
 
624
646
  return overrides
625
647
 
626
- def get_hybrid_config(self) -> Optional[HybridConfig]:
648
+ def get_hybrid_config(self) -> HybridConfig | None:
627
649
  """Get hybrid mode configuration if enabled.
628
650
 
629
651
  Returns:
@@ -655,10 +677,10 @@ class ConfigResolver:
655
677
 
656
678
 
657
679
  # Singleton instance for global access
658
- _default_resolver: Optional[ConfigResolver] = None
680
+ _default_resolver: ConfigResolver | None = None
659
681
 
660
682
 
661
- def get_config_resolver(project_path: Optional[Path] = None) -> ConfigResolver:
683
+ def get_config_resolver(project_path: Path | None = None) -> ConfigResolver:
662
684
  """Get the global config resolver instance.
663
685
 
664
686
  Args:
@@ -1,6 +1,6 @@
1
1
  """Adapter registry for dynamic adapter management."""
2
2
 
3
- from typing import Any, Optional
3
+ from typing import Any
4
4
 
5
5
  from .adapter import BaseAdapter
6
6
 
@@ -37,7 +37,7 @@ class AdapterRegistry:
37
37
 
38
38
  @classmethod
39
39
  def get_adapter(
40
- cls, name: str, config: Optional[dict[str, Any]] = None, force_new: bool = False
40
+ cls, name: str, config: dict[str, Any] | None = None, force_new: bool = False
41
41
  ) -> BaseAdapter:
42
42
  """Get or create an adapter instance.
43
43
 
@@ -115,7 +115,7 @@ class AdapterRegistry:
115
115
 
116
116
 
117
117
  def adapter_factory(adapter_type: str, config: dict[str, Any]) -> BaseAdapter:
118
- """Factory function for creating adapters.
118
+ """Create adapter instance using factory pattern.
119
119
 
120
120
  Args:
121
121
  adapter_type: Type of adapter to create