mcp-ticketer 0.1.16__py3-none-any.whl → 0.1.17__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.1.16"
3
+ __version__ = "0.1.17"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -40,6 +40,20 @@ class AITrackdownAdapter(BaseAdapter[Task]):
40
40
  self.tracker = None
41
41
  self.tickets_dir.mkdir(parents=True, exist_ok=True)
42
42
 
43
+ def validate_credentials(self) -> tuple[bool, str]:
44
+ """Validate that required credentials are present.
45
+
46
+ AITrackdown is file-based and doesn't require credentials.
47
+
48
+ Returns:
49
+ (is_valid, error_message) - Always returns (True, "") for AITrackdown
50
+ """
51
+ # AITrackdown is file-based and doesn't require API credentials
52
+ # Just verify the base_path is accessible
53
+ if not self.base_path:
54
+ return False, "AITrackdown base_path is required in configuration"
55
+ return True, ""
56
+
43
57
  def _get_state_mapping(self) -> Dict[TicketState, str]:
44
58
  """Map universal states to AI-Trackdown states."""
45
59
  return {
@@ -191,6 +191,20 @@ class GitHubAdapter(BaseAdapter[Task]):
191
191
  self._milestones_cache: Optional[List[Dict[str, Any]]] = None
192
192
  self._rate_limit: Dict[str, Any] = {}
193
193
 
194
+ def validate_credentials(self) -> tuple[bool, str]:
195
+ """Validate that required credentials are present.
196
+
197
+ Returns:
198
+ (is_valid, error_message) - Tuple of validation result and error message
199
+ """
200
+ if not self.token:
201
+ return False, "GITHUB_TOKEN is required but not found. Set it in .env.local or environment."
202
+ if not self.owner:
203
+ return False, "GitHub owner is required in configuration. Set GITHUB_OWNER in .env.local or configure with 'mcp-ticketer init --adapter github --github-owner <owner>'"
204
+ if not self.repo:
205
+ return False, "GitHub repo is required in configuration. Set GITHUB_REPO in .env.local or configure with 'mcp-ticketer init --adapter github --github-repo <repo>'"
206
+ return True, ""
207
+
194
208
  def _get_state_mapping(self) -> Dict[TicketState, str]:
195
209
  """Map universal states to GitHub states."""
196
210
  return {
@@ -379,6 +393,11 @@ class GitHubAdapter(BaseAdapter[Task]):
379
393
 
380
394
  async def create(self, ticket: Task) -> Task:
381
395
  """Create a new GitHub issue."""
396
+ # Validate credentials before attempting operation
397
+ is_valid, error_message = self.validate_credentials()
398
+ if not is_valid:
399
+ raise ValueError(error_message)
400
+
382
401
  # Prepare labels
383
402
  labels = ticket.tags.copy() if ticket.tags else []
384
403
 
@@ -448,6 +467,11 @@ class GitHubAdapter(BaseAdapter[Task]):
448
467
 
449
468
  async def read(self, ticket_id: str) -> Optional[Task]:
450
469
  """Read a GitHub issue by number."""
470
+ # Validate credentials before attempting operation
471
+ is_valid, error_message = self.validate_credentials()
472
+ if not is_valid:
473
+ raise ValueError(error_message)
474
+
451
475
  try:
452
476
  issue_number = int(ticket_id)
453
477
  except ValueError:
@@ -468,6 +492,11 @@ class GitHubAdapter(BaseAdapter[Task]):
468
492
 
469
493
  async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[Task]:
470
494
  """Update a GitHub issue."""
495
+ # Validate credentials before attempting operation
496
+ is_valid, error_message = self.validate_credentials()
497
+ if not is_valid:
498
+ raise ValueError(error_message)
499
+
471
500
  try:
472
501
  issue_number = int(ticket_id)
473
502
  except ValueError:
@@ -584,6 +613,11 @@ class GitHubAdapter(BaseAdapter[Task]):
584
613
 
585
614
  async def delete(self, ticket_id: str) -> bool:
586
615
  """Delete (close) a GitHub issue."""
616
+ # Validate credentials before attempting operation
617
+ is_valid, error_message = self.validate_credentials()
618
+ if not is_valid:
619
+ raise ValueError(error_message)
620
+
587
621
  try:
588
622
  issue_number = int(ticket_id)
589
623
  except ValueError:
@@ -89,6 +89,20 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
89
89
  self._issue_types_cache: Dict[str, Any] = {}
90
90
  self._custom_fields_cache: Dict[str, Any] = {}
91
91
 
92
+ def validate_credentials(self) -> tuple[bool, str]:
93
+ """Validate that required credentials are present.
94
+
95
+ Returns:
96
+ (is_valid, error_message) - Tuple of validation result and error message
97
+ """
98
+ if not self.server:
99
+ return False, "JIRA_SERVER is required but not found. Set it in .env.local or environment."
100
+ if not self.email:
101
+ return False, "JIRA_EMAIL is required but not found. Set it in .env.local or environment."
102
+ if not self.api_token:
103
+ return False, "JIRA_API_TOKEN is required but not found. Set it in .env.local or environment."
104
+ return True, ""
105
+
92
106
  def _get_state_mapping(self) -> Dict[TicketState, str]:
93
107
  """Map universal states to common JIRA workflow states."""
94
108
  return {
@@ -457,6 +471,11 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
457
471
 
458
472
  async def create(self, ticket: Union[Epic, Task]) -> Union[Epic, Task]:
459
473
  """Create a new JIRA issue."""
474
+ # Validate credentials before attempting operation
475
+ is_valid, error_message = self.validate_credentials()
476
+ if not is_valid:
477
+ raise ValueError(error_message)
478
+
460
479
  # Prepare issue fields
461
480
  fields = self._ticket_to_issue_fields(ticket)
462
481
 
@@ -476,6 +495,11 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
476
495
 
477
496
  async def read(self, ticket_id: str) -> Optional[Union[Epic, Task]]:
478
497
  """Read a JIRA issue by key."""
498
+ # Validate credentials before attempting operation
499
+ is_valid, error_message = self.validate_credentials()
500
+ if not is_valid:
501
+ raise ValueError(error_message)
502
+
479
503
  try:
480
504
  issue = await self._make_request(
481
505
  "GET",
@@ -494,6 +518,11 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
494
518
  updates: Dict[str, Any]
495
519
  ) -> Optional[Union[Epic, Task]]:
496
520
  """Update a JIRA issue."""
521
+ # Validate credentials before attempting operation
522
+ is_valid, error_message = self.validate_credentials()
523
+ if not is_valid:
524
+ raise ValueError(error_message)
525
+
497
526
  # Read current issue
498
527
  current = await self.read(ticket_id)
499
528
  if not current:
@@ -530,6 +559,11 @@ class JiraAdapter(BaseAdapter[Union[Epic, Task]]):
530
559
 
531
560
  async def delete(self, ticket_id: str) -> bool:
532
561
  """Delete a JIRA issue."""
562
+ # Validate credentials before attempting operation
563
+ is_valid, error_message = self.validate_credentials()
564
+ if not is_valid:
565
+ raise ValueError(error_message)
566
+
533
567
  try:
534
568
  await self._make_request("DELETE", f"issue/{ticket_id}")
535
569
  return True
@@ -520,6 +520,18 @@ class LinearAdapter(BaseAdapter[Task]):
520
520
 
521
521
  return None
522
522
 
523
+ def validate_credentials(self) -> tuple[bool, str]:
524
+ """Validate that required credentials are present.
525
+
526
+ Returns:
527
+ (is_valid, error_message) - Tuple of validation result and error message
528
+ """
529
+ if not self.api_key:
530
+ return False, "LINEAR_API_KEY is required but not found. Set it in .env.local or environment."
531
+ if not self.team_key:
532
+ return False, "Linear team_key is required in configuration. Set it in .mcp-ticketer/config.json"
533
+ return True, ""
534
+
523
535
  def _get_state_mapping(self) -> Dict[TicketState, str]:
524
536
  """Get mapping from universal states to Linear state types.
525
537
 
@@ -711,6 +723,11 @@ class LinearAdapter(BaseAdapter[Task]):
711
723
 
712
724
  async def create(self, ticket: Task) -> Task:
713
725
  """Create a new Linear issue with full field support."""
726
+ # Validate credentials before attempting operation
727
+ is_valid, error_message = self.validate_credentials()
728
+ if not is_valid:
729
+ raise ValueError(error_message)
730
+
714
731
  team_id = await self._ensure_team_id()
715
732
  states = await self._get_workflow_states()
716
733
 
@@ -821,6 +838,11 @@ class LinearAdapter(BaseAdapter[Task]):
821
838
 
822
839
  async def read(self, ticket_id: str) -> Optional[Task]:
823
840
  """Read a Linear issue by identifier with full details."""
841
+ # Validate credentials before attempting operation
842
+ is_valid, error_message = self.validate_credentials()
843
+ if not is_valid:
844
+ raise ValueError(error_message)
845
+
824
846
  query = gql(ALL_FRAGMENTS + """
825
847
  query GetIssue($identifier: String!) {
826
848
  issue(id: $identifier) {
@@ -846,6 +868,11 @@ class LinearAdapter(BaseAdapter[Task]):
846
868
 
847
869
  async def update(self, ticket_id: str, updates: Dict[str, Any]) -> Optional[Task]:
848
870
  """Update a Linear issue with comprehensive field support."""
871
+ # Validate credentials before attempting operation
872
+ is_valid, error_message = self.validate_credentials()
873
+ if not is_valid:
874
+ raise ValueError(error_message)
875
+
849
876
  # First get the Linear internal ID
850
877
  query = gql("""
851
878
  query GetIssueId($identifier: String!) {
@@ -944,6 +971,11 @@ class LinearAdapter(BaseAdapter[Task]):
944
971
 
945
972
  async def delete(self, ticket_id: str) -> bool:
946
973
  """Archive (soft delete) a Linear issue."""
974
+ # Validate credentials before attempting operation
975
+ is_valid, error_message = self.validate_credentials()
976
+ if not is_valid:
977
+ raise ValueError(error_message)
978
+
947
979
  # Get Linear ID
948
980
  query = gql("""
949
981
  query GetIssueId($identifier: String!) {
@@ -29,6 +29,15 @@ class BaseAdapter(ABC, Generic[T]):
29
29
  """
30
30
  pass
31
31
 
32
+ @abstractmethod
33
+ def validate_credentials(self) -> tuple[bool, str]:
34
+ """Validate that required credentials are present.
35
+
36
+ Returns:
37
+ (is_valid, error_message) - Tuple of validation result and error message
38
+ """
39
+ pass
40
+
32
41
  @abstractmethod
33
42
  async def create(self, ticket: T) -> T:
34
43
  """Create a new ticket.
@@ -4,12 +4,31 @@ import asyncio
4
4
  import json
5
5
  import sys
6
6
  from typing import Any, Dict, List, Optional
7
+ from pathlib import Path
8
+ from dotenv import load_dotenv
7
9
 
8
10
  from ..core import Task, TicketState, Priority, AdapterRegistry
9
11
  from ..core.models import SearchQuery, Comment
10
12
  from ..adapters import AITrackdownAdapter
11
13
  from ..queue import Queue, QueueStatus, WorkerManager
12
14
 
15
+ # Load environment variables early (prioritize .env.local)
16
+ # Check for .env.local first (takes precedence)
17
+ env_local_file = Path.cwd() / ".env.local"
18
+ if env_local_file.exists():
19
+ load_dotenv(env_local_file, override=True)
20
+ sys.stderr.write(f"[MCP Server] Loaded environment from: {env_local_file}\n")
21
+ else:
22
+ # Fall back to .env
23
+ env_file = Path.cwd() / ".env"
24
+ if env_file.exists():
25
+ load_dotenv(env_file, override=True)
26
+ sys.stderr.write(f"[MCP Server] Loaded environment from: {env_file}\n")
27
+ else:
28
+ # Try default dotenv loading (searches upward)
29
+ load_dotenv(override=True)
30
+ sys.stderr.write("[MCP Server] Loaded environment from default search path\n")
31
+
13
32
 
14
33
  class MCPTicketServer:
15
34
  """MCP server for ticket operations over stdio."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.1.16
3
+ Version: 0.1.17
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,12 +1,12 @@
1
1
  mcp_ticketer/__init__.py,sha256=ayPQdFr6msypD06_G96a1H0bdFCT1m1wDtv8MZBpY4I,496
2
- mcp_ticketer/__version__.py,sha256=VU-W9BGb5-hfF3jvOPn-c-W8RnOMrAL8rswIa9JjVSE,1115
2
+ mcp_ticketer/__version__.py,sha256=WgT2pB0LHF_awU8L3zAq7pQyDkXi1wLI6eY0gHjmjUw,1115
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=K_1egvhHb5F_7yFceUx2YzPGEoc7vX-q8dMVaS4K6gw,356
5
- mcp_ticketer/adapters/aitrackdown.py,sha256=gqS_N6VGLoG5itUu17ANG5SefaAITYoW-t2xL9SrY-Y,15372
6
- mcp_ticketer/adapters/github.py,sha256=onT8NhYaf9fIw2eCOTbZSkk7q4IoM7ZADRvRl9qrUz8,43850
5
+ mcp_ticketer/adapters/aitrackdown.py,sha256=916SpzQcG6gSdQit5Ptm9AdGOsZFXpt9nNWlRjCReMY,15924
6
+ mcp_ticketer/adapters/github.py,sha256=NdUPaSlOEi4zZN_VBvAjSJANJhp1IBwdOkkF6fGbaKs,45410
7
7
  mcp_ticketer/adapters/hybrid.py,sha256=H9B-pfWmDKXO3GgzxB8undEcZTMzLz_1a6zWhj7xfR0,18556
8
- mcp_ticketer/adapters/jira.py,sha256=rd-8PseTsRyQNPjsrUJ8vJ8vfBpa6HWFBieOUyvw0Tg,28954
9
- mcp_ticketer/adapters/linear.py,sha256=BUK40EF4yNVJV5ldJPFx7Ne4FXHYbrGCUAaFy2gWC9Y,65211
8
+ mcp_ticketer/adapters/jira.py,sha256=jxoQS22wjOl1FhsYiGK-r1pLXOenUmbe5wa0ehD6xDg,30373
9
+ mcp_ticketer/adapters/linear.py,sha256=VmZ9UI5cDvbsiFgWEopnUTwWGcHZDeGtsTSnR_Z5_BA,66506
10
10
  mcp_ticketer/cache/__init__.py,sha256=MSi3GLXancfP2-edPC9TFAJk7r0j6H5-XmpMHnkGPbI,137
11
11
  mcp_ticketer/cache/memory.py,sha256=gTzv-xF7qGfiYVUjG7lnzo0ZcqgXQajMl4NAYUcaytg,5133
12
12
  mcp_ticketer/cli/__init__.py,sha256=YeljyLtv906TqkvRuEPhmKO-Uk0CberQ9I6kx1tx2UA,88
@@ -18,7 +18,7 @@ mcp_ticketer/cli/migrate_config.py,sha256=iZIstnlr9vkhiW_MlnSyJOkMi4KHQqrZ6Hz1EC
18
18
  mcp_ticketer/cli/queue_commands.py,sha256=f3pEHKZ43dBHEIoCBvdfvjfMB9_WJltps9ATwTzorY0,8160
19
19
  mcp_ticketer/cli/utils.py,sha256=cdP-7GHtELAPZtqInUC24k_SAnRbIRkafIP3T4kMZDM,19509
20
20
  mcp_ticketer/core/__init__.py,sha256=qpCZveQMyqU2JvYG9MG_c6X35z_VoSmjWdXGcUZqqmA,348
21
- mcp_ticketer/core/adapter.py,sha256=6KfhceINHfDjs--RyA_rOLeM2phVD_D85S9D6s2lcuU,10075
21
+ mcp_ticketer/core/adapter.py,sha256=W87W-hEmgCxw5BkvaFlCGZtouN49aW2KHND53zgg6-c,10339
22
22
  mcp_ticketer/core/config.py,sha256=9a2bksbcFr7KXeHSPY6KoSP5Pzt54utYPCmbM-1QKmk,13932
23
23
  mcp_ticketer/core/env_discovery.py,sha256=SPoyq_y5j-3gJG5gYNVjCIIrbdzimOdDbTYySmQWZOA,17536
24
24
  mcp_ticketer/core/http_client.py,sha256=RM9CEMNcuRb-FxhAijmM_FeBMgxgh1OII9HIPBdJue0,13855
@@ -27,16 +27,16 @@ mcp_ticketer/core/models.py,sha256=GhuTitY6t_QlqfEUvWT6Q2zvY7qAtx_SQyCMMn8iYkk,6
27
27
  mcp_ticketer/core/project_config.py,sha256=VVSeCwuESuemL-iC4fqbPrJxR4i5k5fhUpujnY7MCZA,22389
28
28
  mcp_ticketer/core/registry.py,sha256=fwje0fnjp0YKPZ0SrVWk82SMNLs7CD0JlHQmx7SigNo,3537
29
29
  mcp_ticketer/mcp/__init__.py,sha256=Bvzof9vBu6VwcXcIZK8RgKv6ycRV9tDlO-9TUmd8zqQ,122
30
- mcp_ticketer/mcp/server.py,sha256=TDuU8ChZC2QYSRo0uGHkVReblTf--hriOjxo-pSAF_Y,34068
30
+ mcp_ticketer/mcp/server.py,sha256=CC1iaeugUbiVrNvNgOgm2mRb4AW-5e0X2ygLjH8I6mM,34835
31
31
  mcp_ticketer/queue/__init__.py,sha256=xHBoUwor8ZdO8bIHc7nP25EsAp5Si5Co4g_8ybb7fes,230
32
32
  mcp_ticketer/queue/__main__.py,sha256=kQd6iOCKbbFqpRdbIRavuI4_G7-oE898JE4a0yLEYPE,108
33
33
  mcp_ticketer/queue/manager.py,sha256=79AH9oUxdBXH3lmJ3kIlFf2GQkWHL6XB6u5JqVWPq60,7571
34
34
  mcp_ticketer/queue/queue.py,sha256=z4aivQCtsH5_OUr2OfXSfnFKzugTahNnwHw0LS3ZhZc,11549
35
35
  mcp_ticketer/queue/run_worker.py,sha256=HFoykfDpOoz8OUxWbQ2Fka_UlGrYwjPVZ-DEimGFH9o,802
36
36
  mcp_ticketer/queue/worker.py,sha256=cVjHR_kfnGKAkiUg0HuXCnbKeKNBBEuj0XZHgIuIn4k,14017
37
- mcp_ticketer-0.1.16.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
38
- mcp_ticketer-0.1.16.dist-info/METADATA,sha256=2Fa_rzfQvRRW-6e52xz_umAJlztAjQn1DMSQaTzcTEE,11211
39
- mcp_ticketer-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
- mcp_ticketer-0.1.16.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
41
- mcp_ticketer-0.1.16.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
42
- mcp_ticketer-0.1.16.dist-info/RECORD,,
37
+ mcp_ticketer-0.1.17.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
38
+ mcp_ticketer-0.1.17.dist-info/METADATA,sha256=Mcy0c4cYLlswlEJbYolMAdS4shFf27maFmydb2LtnaA,11211
39
+ mcp_ticketer-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ mcp_ticketer-0.1.17.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
41
+ mcp_ticketer-0.1.17.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
42
+ mcp_ticketer-0.1.17.dist-info/RECORD,,