mcp-ticketer 0.4.2__py3-none-any.whl → 0.4.3__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 (54) hide show
  1. mcp_ticketer/__init__.py +3 -12
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +243 -11
  4. mcp_ticketer/adapters/github.py +15 -14
  5. mcp_ticketer/adapters/hybrid.py +11 -11
  6. mcp_ticketer/adapters/jira.py +22 -25
  7. mcp_ticketer/adapters/linear/adapter.py +9 -21
  8. mcp_ticketer/adapters/linear/client.py +2 -1
  9. mcp_ticketer/adapters/linear/mappers.py +2 -1
  10. mcp_ticketer/cache/memory.py +6 -5
  11. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  12. mcp_ticketer/cli/codex_configure.py +2 -2
  13. mcp_ticketer/cli/configure.py +7 -14
  14. mcp_ticketer/cli/diagnostics.py +2 -2
  15. mcp_ticketer/cli/discover.py +6 -11
  16. mcp_ticketer/cli/gemini_configure.py +2 -2
  17. mcp_ticketer/cli/linear_commands.py +6 -7
  18. mcp_ticketer/cli/main.py +218 -242
  19. mcp_ticketer/cli/mcp_configure.py +1 -2
  20. mcp_ticketer/cli/ticket_commands.py +27 -30
  21. mcp_ticketer/cli/utils.py +23 -22
  22. mcp_ticketer/core/__init__.py +3 -1
  23. mcp_ticketer/core/adapter.py +82 -13
  24. mcp_ticketer/core/config.py +27 -29
  25. mcp_ticketer/core/env_discovery.py +10 -10
  26. mcp_ticketer/core/env_loader.py +8 -8
  27. mcp_ticketer/core/http_client.py +16 -16
  28. mcp_ticketer/core/mappers.py +10 -10
  29. mcp_ticketer/core/models.py +50 -20
  30. mcp_ticketer/core/project_config.py +40 -34
  31. mcp_ticketer/core/registry.py +2 -2
  32. mcp_ticketer/mcp/dto.py +32 -32
  33. mcp_ticketer/mcp/response_builder.py +2 -2
  34. mcp_ticketer/mcp/server.py +17 -37
  35. mcp_ticketer/mcp/server_sdk.py +2 -2
  36. mcp_ticketer/mcp/tools/__init__.py +7 -9
  37. mcp_ticketer/mcp/tools/attachment_tools.py +3 -4
  38. mcp_ticketer/mcp/tools/comment_tools.py +2 -2
  39. mcp_ticketer/mcp/tools/hierarchy_tools.py +8 -8
  40. mcp_ticketer/mcp/tools/pr_tools.py +2 -2
  41. mcp_ticketer/mcp/tools/search_tools.py +6 -6
  42. mcp_ticketer/mcp/tools/ticket_tools.py +12 -12
  43. mcp_ticketer/queue/health_monitor.py +4 -4
  44. mcp_ticketer/queue/manager.py +2 -2
  45. mcp_ticketer/queue/queue.py +16 -16
  46. mcp_ticketer/queue/ticket_registry.py +7 -7
  47. mcp_ticketer/queue/worker.py +2 -2
  48. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/METADATA +61 -2
  49. mcp_ticketer-0.4.3.dist-info/RECORD +73 -0
  50. mcp_ticketer-0.4.2.dist-info/RECORD +0 -73
  51. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/WHEEL +0 -0
  52. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/entry_points.txt +0 -0
  53. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/licenses/LICENSE +0 -0
  54. {mcp_ticketer-0.4.2.dist-info → mcp_ticketer-0.4.3.dist-info}/top_level.txt +0 -0
mcp_ticketer/mcp/dto.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Data Transfer Objects for MCP requests and responses."""
2
2
 
3
- from typing import Any, Optional
3
+ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
@@ -10,32 +10,32 @@ class CreateTicketRequest(BaseModel):
10
10
  """Request to create a ticket."""
11
11
 
12
12
  title: str = Field(..., min_length=1, description="Ticket title")
13
- description: Optional[str] = Field(None, description="Ticket description")
13
+ description: str | None = Field(None, description="Ticket description")
14
14
  priority: str = Field("medium", description="Ticket priority")
15
15
  tags: list[str] = Field(default_factory=list, description="Ticket tags")
16
- assignee: Optional[str] = Field(None, description="Ticket assignee")
16
+ assignee: str | None = Field(None, description="Ticket assignee")
17
17
 
18
18
 
19
19
  class CreateEpicRequest(BaseModel):
20
20
  """Request to create an epic."""
21
21
 
22
22
  title: str = Field(..., min_length=1, description="Epic title")
23
- description: Optional[str] = Field(None, description="Epic description")
23
+ description: str | None = Field(None, description="Epic description")
24
24
  child_issues: list[str] = Field(default_factory=list, description="Child issue IDs")
25
- target_date: Optional[str] = Field(None, description="Target completion date")
26
- lead_id: Optional[str] = Field(None, description="Epic lead/owner ID")
25
+ target_date: str | None = Field(None, description="Target completion date")
26
+ lead_id: str | None = Field(None, description="Epic lead/owner ID")
27
27
 
28
28
 
29
29
  class CreateIssueRequest(BaseModel):
30
30
  """Request to create an issue."""
31
31
 
32
32
  title: str = Field(..., min_length=1, description="Issue title")
33
- description: Optional[str] = Field(None, description="Issue description")
34
- epic_id: Optional[str] = Field(None, description="Parent epic ID")
33
+ description: str | None = Field(None, description="Issue description")
34
+ epic_id: str | None = Field(None, description="Parent epic ID")
35
35
  priority: str = Field("medium", description="Issue priority")
36
- assignee: Optional[str] = Field(None, description="Issue assignee")
36
+ assignee: str | None = Field(None, description="Issue assignee")
37
37
  tags: list[str] = Field(default_factory=list, description="Issue tags")
38
- estimated_hours: Optional[float] = Field(
38
+ estimated_hours: float | None = Field(
39
39
  None, description="Estimated hours to complete"
40
40
  )
41
41
 
@@ -45,11 +45,11 @@ class CreateTaskRequest(BaseModel):
45
45
 
46
46
  title: str = Field(..., min_length=1, description="Task title")
47
47
  parent_id: str = Field(..., description="Parent issue ID")
48
- description: Optional[str] = Field(None, description="Task description")
48
+ description: str | None = Field(None, description="Task description")
49
49
  priority: str = Field("medium", description="Task priority")
50
- assignee: Optional[str] = Field(None, description="Task assignee")
50
+ assignee: str | None = Field(None, description="Task assignee")
51
51
  tags: list[str] = Field(default_factory=list, description="Task tags")
52
- estimated_hours: Optional[float] = Field(
52
+ estimated_hours: float | None = Field(
53
53
  None, description="Estimated hours to complete"
54
54
  )
55
55
 
@@ -77,11 +77,11 @@ class TransitionRequest(BaseModel):
77
77
  class SearchRequest(BaseModel):
78
78
  """Request to search tickets."""
79
79
 
80
- query: Optional[str] = Field(None, description="Search query text")
81
- state: Optional[str] = Field(None, description="Filter by ticket state")
82
- priority: Optional[str] = Field(None, description="Filter by priority")
83
- assignee: Optional[str] = Field(None, description="Filter by assignee")
84
- tags: Optional[list[str]] = Field(None, description="Filter by tags")
80
+ query: str | None = Field(None, description="Search query text")
81
+ state: str | None = Field(None, description="Filter by ticket state")
82
+ priority: str | None = Field(None, description="Filter by priority")
83
+ assignee: str | None = Field(None, description="Filter by assignee")
84
+ tags: list[str] | None = Field(None, description="Filter by tags")
85
85
  limit: int = Field(10, description="Maximum number of results")
86
86
 
87
87
 
@@ -90,7 +90,7 @@ class ListRequest(BaseModel):
90
90
 
91
91
  limit: int = Field(10, description="Maximum number of tickets to return")
92
92
  offset: int = Field(0, description="Number of tickets to skip")
93
- filters: Optional[dict[str, Any]] = Field(None, description="Additional filters")
93
+ filters: dict[str, Any] | None = Field(None, description="Additional filters")
94
94
 
95
95
 
96
96
  class DeleteTicketRequest(BaseModel):
@@ -104,8 +104,8 @@ class CommentRequest(BaseModel):
104
104
 
105
105
  operation: str = Field("add", description="Operation: 'add' or 'list'")
106
106
  ticket_id: str = Field(..., description="Ticket ID")
107
- content: Optional[str] = Field(None, description="Comment content (for add)")
108
- author: Optional[str] = Field(None, description="Comment author (for add)")
107
+ content: str | None = Field(None, description="Comment content (for add)")
108
+ author: str | None = Field(None, description="Comment author (for add)")
109
109
  limit: int = Field(10, description="Max comments to return (for list)")
110
110
  offset: int = Field(0, description="Number of comments to skip (for list)")
111
111
 
@@ -115,12 +115,12 @@ class CreatePRRequest(BaseModel):
115
115
 
116
116
  ticket_id: str = Field(..., description="Ticket ID")
117
117
  base_branch: str = Field("main", description="Base branch")
118
- head_branch: Optional[str] = Field(None, description="Head branch")
119
- title: Optional[str] = Field(None, description="PR title")
120
- body: Optional[str] = Field(None, description="PR body")
118
+ head_branch: str | None = Field(None, description="Head branch")
119
+ title: str | None = Field(None, description="PR title")
120
+ body: str | None = Field(None, description="PR body")
121
121
  draft: bool = Field(False, description="Create as draft PR")
122
- github_owner: Optional[str] = Field(None, description="GitHub owner (for Linear)")
123
- github_repo: Optional[str] = Field(None, description="GitHub repo (for Linear)")
122
+ github_owner: str | None = Field(None, description="GitHub owner (for Linear)")
123
+ github_repo: str | None = Field(None, description="GitHub repo (for Linear)")
124
124
 
125
125
 
126
126
  class LinkPRRequest(BaseModel):
@@ -152,7 +152,7 @@ class IssueTasksRequest(BaseModel):
152
152
  class HierarchyTreeRequest(BaseModel):
153
153
  """Request to get hierarchy tree."""
154
154
 
155
- epic_id: Optional[str] = Field(None, description="Specific epic ID (optional)")
155
+ epic_id: str | None = Field(None, description="Specific epic ID (optional)")
156
156
  max_depth: int = Field(3, description="Maximum depth of tree")
157
157
  limit: int = Field(10, description="Max epics to return (if no epic_id)")
158
158
 
@@ -173,8 +173,8 @@ class SearchHierarchyRequest(BaseModel):
173
173
  """Request to search with hierarchy context."""
174
174
 
175
175
  query: str = Field("", description="Search query")
176
- state: Optional[str] = Field(None, description="Filter by state")
177
- priority: Optional[str] = Field(None, description="Filter by priority")
176
+ state: str | None = Field(None, description="Filter by state")
177
+ priority: str | None = Field(None, description="Filter by priority")
178
178
  include_children: bool = Field(True, description="Include child tickets")
179
179
  include_parents: bool = Field(True, description="Include parent tickets")
180
180
  limit: int = Field(50, description="Maximum number of results")
@@ -184,9 +184,9 @@ class AttachRequest(BaseModel):
184
184
  """Request to attach file to ticket."""
185
185
 
186
186
  ticket_id: str = Field(..., description="Ticket ID")
187
- file_path: Optional[str] = Field(None, description="File path to attach")
188
- file_content: Optional[str] = Field(None, description="File content (base64)")
189
- file_name: Optional[str] = Field(None, description="File name")
187
+ file_path: str | None = Field(None, description="File path to attach")
188
+ file_content: str | None = Field(None, description="File content (base64)")
189
+ file_name: str | None = Field(None, description="File name")
190
190
 
191
191
 
192
192
  class ListAttachmentsRequest(BaseModel):
@@ -1,6 +1,6 @@
1
1
  """Response builder utility for consistent MCP responses."""
2
2
 
3
- from typing import Any, Optional
3
+ from typing import Any
4
4
 
5
5
  from .constants import JSONRPC_VERSION, STATUS_COMPLETED
6
6
 
@@ -36,7 +36,7 @@ class ResponseBuilder:
36
36
  request_id: Any,
37
37
  code: int,
38
38
  message: str,
39
- data: Optional[dict[str, Any]] = None,
39
+ data: dict[str, Any] | None = None,
40
40
  ) -> dict[str, Any]:
41
41
  """Build error response.
42
42
 
@@ -4,7 +4,7 @@ import asyncio
4
4
  import json
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  from dotenv import load_dotenv
10
10
 
@@ -12,40 +12,20 @@ from dotenv import load_dotenv
12
12
  import mcp_ticketer.adapters # noqa: F401
13
13
 
14
14
  from ..core import AdapterRegistry
15
- from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
16
- from .constants import (
17
- DEFAULT_BASE_PATH,
18
- DEFAULT_LIMIT,
19
- DEFAULT_MAX_DEPTH,
20
- DEFAULT_OFFSET,
21
- ERROR_INTERNAL,
22
- ERROR_METHOD_NOT_FOUND,
23
- ERROR_PARSE,
24
- JSONRPC_VERSION,
25
- MCP_PROTOCOL_VERSION,
26
- MSG_EPIC_NOT_FOUND,
27
- MSG_INTERNAL_ERROR,
28
- MSG_MISSING_TICKET_ID,
29
- MSG_MISSING_TITLE,
30
- MSG_NO_TICKETS_PROVIDED,
31
- MSG_NO_UPDATES_PROVIDED,
32
- MSG_TICKET_NOT_FOUND,
33
- MSG_TRANSITION_FAILED,
34
- MSG_UNKNOWN_METHOD,
35
- MSG_UNKNOWN_OPERATION,
36
- MSG_UPDATE_FAILED,
37
- SERVER_NAME,
38
- SERVER_VERSION,
39
- STATUS_COMPLETED,
40
- STATUS_ERROR,
41
- )
42
- from .dto import (
43
- CreateEpicRequest,
44
- CreateIssueRequest,
45
- CreateTaskRequest,
46
- CreateTicketRequest,
47
- ReadTicketRequest,
48
- )
15
+ from ..core.models import (Comment, Epic, Priority, SearchQuery, Task,
16
+ TicketState)
17
+ from .constants import (DEFAULT_BASE_PATH, DEFAULT_LIMIT, DEFAULT_MAX_DEPTH,
18
+ DEFAULT_OFFSET, ERROR_INTERNAL, ERROR_METHOD_NOT_FOUND,
19
+ ERROR_PARSE, JSONRPC_VERSION, MCP_PROTOCOL_VERSION,
20
+ MSG_EPIC_NOT_FOUND, MSG_INTERNAL_ERROR,
21
+ MSG_MISSING_TICKET_ID, MSG_MISSING_TITLE,
22
+ MSG_NO_TICKETS_PROVIDED, MSG_NO_UPDATES_PROVIDED,
23
+ MSG_TICKET_NOT_FOUND, MSG_TRANSITION_FAILED,
24
+ MSG_UNKNOWN_METHOD, MSG_UNKNOWN_OPERATION,
25
+ MSG_UPDATE_FAILED, SERVER_NAME, SERVER_VERSION,
26
+ STATUS_COMPLETED, STATUS_ERROR)
27
+ from .dto import (CreateEpicRequest, CreateIssueRequest, CreateTaskRequest,
28
+ CreateTicketRequest, ReadTicketRequest)
49
29
  from .response_builder import ResponseBuilder
50
30
 
51
31
  # Load environment variables early (prioritize .env.local)
@@ -70,7 +50,7 @@ class MCPTicketServer:
70
50
  """MCP server for ticket operations over stdio - synchronous implementation."""
71
51
 
72
52
  def __init__(
73
- self, adapter_type: str = "aitrackdown", config: Optional[dict[str, Any]] = None
53
+ self, adapter_type: str = "aitrackdown", config: dict[str, Any] | None = None
74
54
  ):
75
55
  """Initialize MCP server.
76
56
 
@@ -1129,7 +1109,7 @@ async def main():
1129
1109
  await server.run()
1130
1110
 
1131
1111
 
1132
- def _load_env_configuration() -> Optional[dict[str, Any]]:
1112
+ def _load_env_configuration() -> dict[str, Any] | None:
1133
1113
  """Load adapter configuration from .env files.
1134
1114
 
1135
1115
  Checks .env.local first (highest priority), then .env.
@@ -10,7 +10,7 @@ startup and used by all tool implementations.
10
10
  """
11
11
 
12
12
  import logging
13
- from typing import Any, Optional
13
+ from typing import Any
14
14
 
15
15
  from mcp.server.fastmcp import FastMCP
16
16
 
@@ -21,7 +21,7 @@ from ..core.registry import AdapterRegistry
21
21
  mcp = FastMCP("mcp-ticketer")
22
22
 
23
23
  # Global adapter instance
24
- _adapter: Optional[BaseAdapter] = None
24
+ _adapter: BaseAdapter | None = None
25
25
 
26
26
  # Configure logging
27
27
  logger = logging.getLogger(__name__)
@@ -17,15 +17,13 @@ Modules:
17
17
 
18
18
  # Import all tool modules to register them with FastMCP
19
19
  # Order matters - import core functionality first
20
- from . import (
21
- attachment_tools, # noqa: F401
22
- bulk_tools, # noqa: F401
23
- comment_tools, # noqa: F401
24
- hierarchy_tools, # noqa: F401
25
- pr_tools, # noqa: F401
26
- search_tools, # noqa: F401
27
- ticket_tools, # noqa: F401
28
- )
20
+ from . import attachment_tools # noqa: F401
21
+ from . import bulk_tools # noqa: F401
22
+ from . import comment_tools # noqa: F401
23
+ from . import hierarchy_tools # noqa: F401
24
+ from . import pr_tools # noqa: F401
25
+ from . import search_tools # noqa: F401
26
+ from . import ticket_tools # noqa: F401
29
27
 
30
28
  __all__ = [
31
29
  "ticket_tools",
@@ -7,6 +7,7 @@ available in all adapters.
7
7
 
8
8
  from typing import Any
9
9
 
10
+ from ...core.models import Comment
10
11
  from ..server_sdk import get_adapter, mcp
11
12
 
12
13
 
@@ -15,7 +16,7 @@ async def ticket_attach(
15
16
  ticket_id: str,
16
17
  file_path: str,
17
18
  description: str = "",
18
- ) -> dict[str, Any]:
19
+ ) -> dict[str, Any]: # Keep as dict for MCP compatibility
19
20
  """Attach a file to a ticket.
20
21
 
21
22
  Uploads a file and associates it with the specified ticket. This
@@ -63,8 +64,6 @@ async def ticket_attach(
63
64
 
64
65
  except AttributeError:
65
66
  # Fallback: Add file reference as comment
66
- from ...core.models import Comment
67
-
68
67
  comment_text = f"Attachment: {file_path}"
69
68
  if description:
70
69
  comment_text += f"\nDescription: {description}"
@@ -102,7 +101,7 @@ async def ticket_attach(
102
101
  @mcp.tool()
103
102
  async def ticket_attachments(
104
103
  ticket_id: str,
105
- ) -> dict[str, Any]:
104
+ ) -> dict[str, Any]: # Keep as dict for MCP compatibility
106
105
  """Get all attachments for a ticket.
107
106
 
108
107
  Retrieves a list of all files attached to the specified ticket.
@@ -3,7 +3,7 @@
3
3
  This module implements tools for adding and retrieving comments on tickets.
4
4
  """
5
5
 
6
- from typing import Any, Optional
6
+ from typing import Any
7
7
 
8
8
  from ...core.models import Comment
9
9
  from ..server_sdk import get_adapter, mcp
@@ -13,7 +13,7 @@ from ..server_sdk import get_adapter, mcp
13
13
  async def ticket_comment(
14
14
  ticket_id: str,
15
15
  operation: str,
16
- text: Optional[str] = None,
16
+ text: str | None = None,
17
17
  limit: int = 10,
18
18
  offset: int = 0,
19
19
  ) -> dict[str, Any]:
@@ -7,7 +7,7 @@ This module implements tools for managing the three-level ticket hierarchy:
7
7
  """
8
8
 
9
9
  from datetime import datetime
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  from ...core.models import Epic, Priority, Task, TicketType
13
13
  from ..server_sdk import get_adapter, mcp
@@ -17,9 +17,9 @@ from ..server_sdk import get_adapter, mcp
17
17
  async def epic_create(
18
18
  title: str,
19
19
  description: str = "",
20
- target_date: Optional[str] = None,
21
- lead_id: Optional[str] = None,
22
- child_issues: Optional[list[str]] = None,
20
+ target_date: str | None = None,
21
+ lead_id: str | None = None,
22
+ child_issues: list[str] | None = None,
23
23
  ) -> dict[str, Any]:
24
24
  """Create a new epic (strategic level container).
25
25
 
@@ -156,8 +156,8 @@ async def epic_issues(epic_id: str) -> dict[str, Any]:
156
156
  async def issue_create(
157
157
  title: str,
158
158
  description: str = "",
159
- epic_id: Optional[str] = None,
160
- assignee: Optional[str] = None,
159
+ epic_id: str | None = None,
160
+ assignee: str | None = None,
161
161
  priority: str = "medium",
162
162
  ) -> dict[str, Any]:
163
163
  """Create a new issue (standard work item).
@@ -258,8 +258,8 @@ async def issue_tasks(issue_id: str) -> dict[str, Any]:
258
258
  async def task_create(
259
259
  title: str,
260
260
  description: str = "",
261
- issue_id: Optional[str] = None,
262
- assignee: Optional[str] = None,
261
+ issue_id: str | None = None,
262
+ assignee: str | None = None,
263
263
  priority: str = "medium",
264
264
  ) -> dict[str, Any]:
265
265
  """Create a new task (sub-work item).
@@ -5,7 +5,7 @@ creating PRs from tickets. Note that PR functionality may not be available
5
5
  in all adapters.
6
6
  """
7
7
 
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
  from ..server_sdk import get_adapter, mcp
11
11
 
@@ -15,7 +15,7 @@ async def ticket_create_pr(
15
15
  ticket_id: str,
16
16
  title: str,
17
17
  description: str = "",
18
- source_branch: Optional[str] = None,
18
+ source_branch: str | None = None,
19
19
  target_branch: str = "main",
20
20
  ) -> dict[str, Any]:
21
21
  """Create a pull request linked to a ticket.
@@ -4,7 +4,7 @@ This module implements advanced search capabilities for tickets using
4
4
  various filters and criteria.
5
5
  """
6
6
 
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  from ...core.models import Priority, SearchQuery, TicketState
10
10
  from ..server_sdk import get_adapter, mcp
@@ -12,11 +12,11 @@ from ..server_sdk import get_adapter, mcp
12
12
 
13
13
  @mcp.tool()
14
14
  async def ticket_search(
15
- query: Optional[str] = None,
16
- state: Optional[str] = None,
17
- priority: Optional[str] = None,
18
- tags: Optional[list[str]] = None,
19
- assignee: Optional[str] = None,
15
+ query: str | None = None,
16
+ state: str | None = None,
17
+ priority: str | None = None,
18
+ tags: list[str] | None = None,
19
+ assignee: str | None = None,
20
20
  limit: int = 10,
21
21
  ) -> dict[str, Any]:
22
22
  """Search tickets using advanced filters.
@@ -4,7 +4,7 @@ This module implements the core create, read, update, delete, and list
4
4
  operations for tickets using the FastMCP SDK.
5
5
  """
6
6
 
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  from ...core.models import Priority, Task, TicketState
10
10
  from ..server_sdk import get_adapter, mcp
@@ -15,8 +15,8 @@ async def ticket_create(
15
15
  title: str,
16
16
  description: str = "",
17
17
  priority: str = "medium",
18
- tags: Optional[list[str]] = None,
19
- assignee: Optional[str] = None,
18
+ tags: list[str] | None = None,
19
+ assignee: str | None = None,
20
20
  ) -> dict[str, Any]:
21
21
  """Create a new ticket with specified details.
22
22
 
@@ -101,12 +101,12 @@ async def ticket_read(ticket_id: str) -> dict[str, Any]:
101
101
  @mcp.tool()
102
102
  async def ticket_update(
103
103
  ticket_id: str,
104
- title: Optional[str] = None,
105
- description: Optional[str] = None,
106
- priority: Optional[str] = None,
107
- state: Optional[str] = None,
108
- assignee: Optional[str] = None,
109
- tags: Optional[list[str]] = None,
104
+ title: str | None = None,
105
+ description: str | None = None,
106
+ priority: str | None = None,
107
+ state: str | None = None,
108
+ assignee: str | None = None,
109
+ tags: list[str] | None = None,
110
110
  ) -> dict[str, Any]:
111
111
  """Update an existing ticket.
112
112
 
@@ -214,9 +214,9 @@ async def ticket_delete(ticket_id: str) -> dict[str, Any]:
214
214
  async def ticket_list(
215
215
  limit: int = 10,
216
216
  offset: int = 0,
217
- state: Optional[str] = None,
218
- priority: Optional[str] = None,
219
- assignee: Optional[str] = None,
217
+ state: str | None = None,
218
+ priority: str | None = None,
219
+ assignee: str | None = None,
220
220
  ) -> dict[str, Any]:
221
221
  """List tickets with pagination and optional filters.
222
222
 
@@ -4,7 +4,7 @@ import logging
4
4
  import time
5
5
  from datetime import datetime, timedelta
6
6
  from enum import Enum
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  import psutil
10
10
 
@@ -30,8 +30,8 @@ class HealthAlert:
30
30
  self,
31
31
  level: HealthStatus,
32
32
  message: str,
33
- details: Optional[dict[str, Any]] = None,
34
- timestamp: Optional[datetime] = None,
33
+ details: dict[str, Any] | None = None,
34
+ timestamp: datetime | None = None,
35
35
  ):
36
36
  self.level = level
37
37
  self.message = message
@@ -52,7 +52,7 @@ class QueueHealthMonitor:
52
52
  QUEUE_BACKLOG_WARNING = 10 # Warn if more than 10 pending items
53
53
  QUEUE_BACKLOG_CRITICAL = 50 # Critical if more than 50 pending items
54
54
 
55
- def __init__(self, queue: Optional[Queue] = None):
55
+ def __init__(self, queue: Queue | None = None):
56
56
  """Initialize health monitor.
57
57
 
58
58
  Args:
@@ -7,7 +7,7 @@ import subprocess
7
7
  import sys
8
8
  import time
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  import psutil
13
13
 
@@ -304,7 +304,7 @@ class WorkerManager:
304
304
 
305
305
  return status
306
306
 
307
- def _get_pid(self) -> Optional[int]:
307
+ def _get_pid(self) -> int | None:
308
308
  """Get worker PID from file.
309
309
 
310
310
  Returns:
@@ -8,7 +8,7 @@ from dataclasses import asdict, dataclass
8
8
  from datetime import datetime, timedelta
9
9
  from enum import Enum
10
10
  from pathlib import Path
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
 
13
13
 
14
14
  class QueueStatus(str, Enum):
@@ -30,12 +30,12 @@ class QueueItem:
30
30
  operation: str
31
31
  status: QueueStatus
32
32
  created_at: datetime
33
- processed_at: Optional[datetime] = None
34
- error_message: Optional[str] = None
33
+ processed_at: datetime | None = None
34
+ error_message: str | None = None
35
35
  retry_count: int = 0
36
- result: Optional[dict[str, Any]] = None
37
- project_dir: Optional[str] = None
38
- adapter_config: Optional[dict[str, Any]] = None # Adapter configuration
36
+ result: dict[str, Any] | None = None
37
+ project_dir: str | None = None
38
+ adapter_config: dict[str, Any] | None = None # Adapter configuration
39
39
 
40
40
  def to_dict(self) -> dict:
41
41
  """Convert to dictionary for storage."""
@@ -67,7 +67,7 @@ class QueueItem:
67
67
  class Queue:
68
68
  """Thread-safe SQLite queue for ticket operations."""
69
69
 
70
- def __init__(self, db_path: Optional[Path] = None):
70
+ def __init__(self, db_path: Path | None = None):
71
71
  """Initialize queue with database connection.
72
72
 
73
73
  Args:
@@ -139,8 +139,8 @@ class Queue:
139
139
  ticket_data: dict[str, Any],
140
140
  adapter: str,
141
141
  operation: str,
142
- project_dir: Optional[str] = None,
143
- adapter_config: Optional[dict[str, Any]] = None,
142
+ project_dir: str | None = None,
143
+ adapter_config: dict[str, Any] | None = None,
144
144
  ) -> str:
145
145
  """Add item to queue.
146
146
 
@@ -186,7 +186,7 @@ class Queue:
186
186
 
187
187
  return queue_id
188
188
 
189
- def get_next_pending(self) -> Optional[QueueItem]:
189
+ def get_next_pending(self) -> QueueItem | None:
190
190
  """Get next pending item from queue atomically.
191
191
 
192
192
  Returns:
@@ -251,9 +251,9 @@ class Queue:
251
251
  self,
252
252
  queue_id: str,
253
253
  status: QueueStatus,
254
- error_message: Optional[str] = None,
255
- result: Optional[dict[str, Any]] = None,
256
- expected_status: Optional[QueueStatus] = None,
254
+ error_message: str | None = None,
255
+ result: dict[str, Any] | None = None,
256
+ expected_status: QueueStatus | None = None,
257
257
  ) -> bool:
258
258
  """Update queue item status atomically.
259
259
 
@@ -328,7 +328,7 @@ class Queue:
328
328
  raise
329
329
 
330
330
  def increment_retry(
331
- self, queue_id: str, expected_status: Optional[QueueStatus] = None
331
+ self, queue_id: str, expected_status: QueueStatus | None = None
332
332
  ) -> int:
333
333
  """Increment retry count and reset to pending atomically.
334
334
 
@@ -387,7 +387,7 @@ class Queue:
387
387
  conn.rollback()
388
388
  raise
389
389
 
390
- def get_item(self, queue_id: str) -> Optional[QueueItem]:
390
+ def get_item(self, queue_id: str) -> QueueItem | None:
391
391
  """Get specific queue item by ID.
392
392
 
393
393
  Args:
@@ -409,7 +409,7 @@ class Queue:
409
409
  return QueueItem.from_row(row) if row else None
410
410
 
411
411
  def list_items(
412
- self, status: Optional[QueueStatus] = None, limit: int = 50
412
+ self, status: QueueStatus | None = None, limit: int = 50
413
413
  ) -> list[QueueItem]:
414
414
  """List queue items.
415
415
 
@@ -5,13 +5,13 @@ import sqlite3
5
5
  import threading
6
6
  from datetime import datetime, timedelta
7
7
  from pathlib import Path
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
 
11
11
  class TicketRegistry:
12
12
  """Persistent registry for tracking ticket IDs and their lifecycle."""
13
13
 
14
- def __init__(self, db_path: Optional[Path] = None):
14
+ def __init__(self, db_path: Path | None = None):
15
15
  """Initialize ticket registry.
16
16
 
17
17
  Args:
@@ -130,10 +130,10 @@ class TicketRegistry:
130
130
  self,
131
131
  queue_id: str,
132
132
  status: str,
133
- ticket_id: Optional[str] = None,
134
- result_data: Optional[dict[str, Any]] = None,
135
- error_message: Optional[str] = None,
136
- retry_count: Optional[int] = None,
133
+ ticket_id: str | None = None,
134
+ result_data: dict[str, Any] | None = None,
135
+ error_message: str | None = None,
136
+ retry_count: int | None = None,
137
137
  ) -> None:
138
138
  """Update ticket operation status.
139
139
 
@@ -179,7 +179,7 @@ class TicketRegistry:
179
179
  )
180
180
  conn.commit()
181
181
 
182
- def get_ticket_info(self, queue_id: str) -> Optional[dict[str, Any]]:
182
+ def get_ticket_info(self, queue_id: str) -> dict[str, Any] | None:
183
183
  """Get ticket information by queue ID.
184
184
 
185
185
  Args: