mcp-ticketer 2.0.1__py3-none-any.whl → 2.2.13__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 (73) hide show
  1. mcp_ticketer/__version__.py +1 -1
  2. mcp_ticketer/_version_scm.py +1 -0
  3. mcp_ticketer/adapters/aitrackdown.py +122 -0
  4. mcp_ticketer/adapters/asana/adapter.py +121 -0
  5. mcp_ticketer/adapters/github/__init__.py +26 -0
  6. mcp_ticketer/adapters/{github.py → github/adapter.py} +1506 -365
  7. mcp_ticketer/adapters/github/client.py +335 -0
  8. mcp_ticketer/adapters/github/mappers.py +797 -0
  9. mcp_ticketer/adapters/github/queries.py +692 -0
  10. mcp_ticketer/adapters/github/types.py +460 -0
  11. mcp_ticketer/adapters/jira/__init__.py +35 -0
  12. mcp_ticketer/adapters/{jira.py → jira/adapter.py} +250 -678
  13. mcp_ticketer/adapters/jira/client.py +271 -0
  14. mcp_ticketer/adapters/jira/mappers.py +246 -0
  15. mcp_ticketer/adapters/jira/queries.py +216 -0
  16. mcp_ticketer/adapters/jira/types.py +304 -0
  17. mcp_ticketer/adapters/linear/adapter.py +1000 -92
  18. mcp_ticketer/adapters/linear/client.py +91 -1
  19. mcp_ticketer/adapters/linear/mappers.py +107 -0
  20. mcp_ticketer/adapters/linear/queries.py +112 -2
  21. mcp_ticketer/adapters/linear/types.py +50 -10
  22. mcp_ticketer/cli/configure.py +524 -89
  23. mcp_ticketer/cli/install_mcp_server.py +418 -0
  24. mcp_ticketer/cli/main.py +10 -0
  25. mcp_ticketer/cli/mcp_configure.py +177 -49
  26. mcp_ticketer/cli/platform_installer.py +9 -0
  27. mcp_ticketer/cli/setup_command.py +157 -1
  28. mcp_ticketer/cli/ticket_commands.py +443 -81
  29. mcp_ticketer/cli/utils.py +113 -0
  30. mcp_ticketer/core/__init__.py +28 -0
  31. mcp_ticketer/core/adapter.py +367 -1
  32. mcp_ticketer/core/milestone_manager.py +252 -0
  33. mcp_ticketer/core/models.py +345 -0
  34. mcp_ticketer/core/project_utils.py +281 -0
  35. mcp_ticketer/core/project_validator.py +376 -0
  36. mcp_ticketer/core/session_state.py +6 -1
  37. mcp_ticketer/core/state_matcher.py +36 -3
  38. mcp_ticketer/mcp/server/__main__.py +2 -1
  39. mcp_ticketer/mcp/server/routing.py +68 -0
  40. mcp_ticketer/mcp/server/tools/__init__.py +7 -4
  41. mcp_ticketer/mcp/server/tools/attachment_tools.py +3 -1
  42. mcp_ticketer/mcp/server/tools/config_tools.py +233 -35
  43. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  44. mcp_ticketer/mcp/server/tools/search_tools.py +30 -1
  45. mcp_ticketer/mcp/server/tools/ticket_tools.py +37 -1
  46. mcp_ticketer/queue/queue.py +68 -0
  47. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/METADATA +33 -3
  48. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/RECORD +72 -36
  49. mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
  50. py_mcp_installer/examples/phase3_demo.py +178 -0
  51. py_mcp_installer/scripts/manage_version.py +54 -0
  52. py_mcp_installer/setup.py +6 -0
  53. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  54. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  55. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  56. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  57. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  58. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  59. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  60. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  61. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  62. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  63. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  64. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  65. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  66. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  67. py_mcp_installer/tests/__init__.py +0 -0
  68. py_mcp_installer/tests/platforms/__init__.py +0 -0
  69. py_mcp_installer/tests/test_platform_detector.py +17 -0
  70. mcp_ticketer-2.0.1.dist-info/top_level.txt +0 -1
  71. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
  72. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
  73. {mcp_ticketer-2.0.1.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,460 @@
1
+ """GitHub-specific type definitions and conversion utilities.
2
+
3
+ This module contains:
4
+ - State and priority mappings between GitHub and universal models
5
+ - Type conversion helper functions
6
+ - GitHub-specific constants and enums
7
+ - TypedDict definitions for GitHub API responses
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, TypedDict
13
+
14
+ from ...core.models import Priority, TicketState
15
+
16
+
17
+ class GitHubStateMapping:
18
+ """GitHub issue states and label-based extended states.
19
+
20
+ Design Decision: GitHub's two-state model (open/closed)
21
+
22
+ GitHub natively only supports two states: 'open' and 'closed'.
23
+ To support richer workflow states, we use labels to extend the state model.
24
+
25
+ Rationale:
26
+ - Maintains compatibility with GitHub's API limitations
27
+ - Allows flexible workflow states through labeling
28
+ - Enables state transitions without closing issues
29
+
30
+ Trade-offs:
31
+ - State changes require label management (more API calls)
32
+ - Labels are user-visible and can be manually modified
33
+ - No built-in state transition validation in GitHub
34
+
35
+ Extension Point: Custom state labels can be configured per repository
36
+ through adapter configuration.
37
+ """
38
+
39
+ # GitHub native states
40
+ OPEN = "open"
41
+ CLOSED = "closed"
42
+
43
+ # Extended states via labels
44
+ # These labels represent workflow states beyond GitHub's native open/closed
45
+ STATE_LABELS = {
46
+ TicketState.IN_PROGRESS: "in-progress",
47
+ TicketState.READY: "ready",
48
+ TicketState.TESTED: "tested",
49
+ TicketState.WAITING: "waiting",
50
+ TicketState.BLOCKED: "blocked",
51
+ }
52
+
53
+ # Priority labels mapping
54
+ # Multiple label patterns support different team conventions
55
+ PRIORITY_LABELS = {
56
+ Priority.CRITICAL: ["P0", "critical", "urgent"],
57
+ Priority.HIGH: ["P1", "high"],
58
+ Priority.MEDIUM: ["P2", "medium"],
59
+ Priority.LOW: ["P3", "low"],
60
+ }
61
+
62
+
63
+ def get_universal_state(
64
+ github_state: str,
65
+ labels: list[str],
66
+ ) -> TicketState:
67
+ """Convert GitHub state + labels to universal TicketState.
68
+
69
+ GitHub has only two states (open/closed), so we use labels to infer
70
+ the extended workflow state.
71
+
72
+ Args:
73
+ ----
74
+ github_state: GitHub issue state ('open' or 'closed')
75
+ labels: List of label names attached to the issue
76
+
77
+ Returns:
78
+ -------
79
+ Universal ticket state enum value
80
+
81
+ Performance:
82
+ -----------
83
+ Time Complexity: O(n*m) where n=number of labels, m=state labels to check
84
+ Worst case: ~5 state labels * ~20 issue labels = 100 comparisons
85
+
86
+ Example:
87
+ -------
88
+ >>> get_universal_state("open", ["in-progress", "bug"])
89
+ TicketState.IN_PROGRESS
90
+ >>> get_universal_state("closed", [])
91
+ TicketState.CLOSED
92
+ """
93
+ # Closed issues are always CLOSED state
94
+ if github_state == "closed":
95
+ return TicketState.CLOSED
96
+
97
+ # Normalize labels for comparison
98
+ label_names = [label.lower() for label in labels]
99
+
100
+ # Check for extended state labels
101
+ for state, label_name in GitHubStateMapping.STATE_LABELS.items():
102
+ if label_name.lower() in label_names:
103
+ return state
104
+
105
+ # Default to OPEN if no state label found
106
+ return TicketState.OPEN
107
+
108
+
109
+ def extract_state_from_issue(issue: dict[str, Any]) -> TicketState:
110
+ """Extract ticket state from GitHub issue data.
111
+
112
+ Handles multiple GitHub API response formats:
113
+ - REST API v3: labels as array of objects
114
+ - GraphQL API v4: labels.nodes as array
115
+ - Legacy formats: labels as array of strings
116
+
117
+ Args:
118
+ ----
119
+ issue: GitHub issue data from REST or GraphQL API
120
+
121
+ Returns:
122
+ -------
123
+ Universal ticket state
124
+
125
+ Example:
126
+ -------
127
+ >>> issue = {"state": "open", "labels": [{"name": "ready"}]}
128
+ >>> extract_state_from_issue(issue)
129
+ TicketState.READY
130
+ """
131
+ # Extract labels from various formats
132
+ labels = []
133
+ if "labels" in issue:
134
+ if isinstance(issue["labels"], list):
135
+ # REST API format: array of objects or strings
136
+ labels = [
137
+ label.get("name", "") if isinstance(label, dict) else str(label)
138
+ for label in issue["labels"]
139
+ ]
140
+ elif isinstance(issue["labels"], dict) and "nodes" in issue["labels"]:
141
+ # GraphQL format: labels.nodes array
142
+ labels = [label["name"] for label in issue["labels"]["nodes"]]
143
+
144
+ return get_universal_state(issue["state"], labels)
145
+
146
+
147
+ def get_priority_from_labels(
148
+ labels: list[str],
149
+ custom_priority_scheme: dict[str, list[str]] | None = None,
150
+ ) -> Priority:
151
+ """Extract priority from GitHub issue labels.
152
+
153
+ Priority is inferred from labels since GitHub has no native priority field.
154
+ Supports custom priority label schemes for team-specific conventions.
155
+
156
+ Args:
157
+ ----
158
+ labels: List of label names
159
+ custom_priority_scheme: Optional custom mapping of priority -> label patterns
160
+
161
+ Returns:
162
+ -------
163
+ Priority enum value (defaults to MEDIUM if not found)
164
+
165
+ Performance:
166
+ -----------
167
+ Time Complexity: O(n*m) where n=labels, m=priority patterns
168
+ Expected: ~20 labels * ~12 priority patterns = 240 comparisons worst case
169
+
170
+ Example:
171
+ -------
172
+ >>> get_priority_from_labels(["P0", "bug"])
173
+ Priority.CRITICAL
174
+ >>> get_priority_from_labels(["enhancement"])
175
+ Priority.MEDIUM # default
176
+ """
177
+ label_names = [label.lower() for label in labels]
178
+
179
+ # Check custom priority scheme first
180
+ if custom_priority_scheme:
181
+ for priority_str, label_patterns in custom_priority_scheme.items():
182
+ for pattern in label_patterns:
183
+ if any(pattern.lower() in label for label in label_names):
184
+ return Priority(priority_str)
185
+
186
+ # Check default priority labels
187
+ for priority, priority_labels in GitHubStateMapping.PRIORITY_LABELS.items():
188
+ for priority_label in priority_labels:
189
+ if priority_label.lower() in label_names:
190
+ return priority
191
+
192
+ return Priority.MEDIUM
193
+
194
+
195
+ def get_priority_label(
196
+ priority: Priority,
197
+ custom_priority_scheme: dict[str, list[str]] | None = None,
198
+ ) -> str:
199
+ """Get label name for a priority level.
200
+
201
+ Returns the first matching label from custom scheme or default labels.
202
+ Falls back to P0/P1/P2/P3 notation if no match found.
203
+
204
+ Args:
205
+ ----
206
+ priority: Universal priority enum
207
+ custom_priority_scheme: Optional custom priority label mapping
208
+
209
+ Returns:
210
+ -------
211
+ Label name to apply to issue
212
+
213
+ Example:
214
+ -------
215
+ >>> get_priority_label(Priority.CRITICAL)
216
+ 'P0'
217
+ >>> get_priority_label(Priority.HIGH, {"high": ["urgent", "high-priority"]})
218
+ 'urgent'
219
+ """
220
+ # Check custom scheme first
221
+ if custom_priority_scheme:
222
+ labels = custom_priority_scheme.get(priority.value, [])
223
+ if labels:
224
+ return labels[0]
225
+
226
+ # Use default labels
227
+ labels = GitHubStateMapping.PRIORITY_LABELS.get(priority, [])
228
+ if labels:
229
+ return labels[0]
230
+
231
+ # Fallback to P0-P3 notation
232
+ priority_index = list(Priority).index(priority)
233
+ return f"P{priority_index}"
234
+
235
+
236
+ def get_state_label(state: TicketState) -> str | None:
237
+ """Get the label name for extended workflow states.
238
+
239
+ Args:
240
+ ----
241
+ state: Universal ticket state
242
+
243
+ Returns:
244
+ -------
245
+ Label name if state requires a label, None for native GitHub states
246
+
247
+ Example:
248
+ -------
249
+ >>> get_state_label(TicketState.IN_PROGRESS)
250
+ 'in-progress'
251
+ >>> get_state_label(TicketState.OPEN)
252
+ None # Native GitHub state, no label needed
253
+ """
254
+ return GitHubStateMapping.STATE_LABELS.get(state)
255
+
256
+
257
+ def get_github_state(state: TicketState) -> str:
258
+ """Map universal state to GitHub native state.
259
+
260
+ Only two valid values: 'open' or 'closed'.
261
+ Extended states map to 'open' with additional labels.
262
+
263
+ Args:
264
+ ----
265
+ state: Universal ticket state
266
+
267
+ Returns:
268
+ -------
269
+ GitHub state string ('open' or 'closed')
270
+
271
+ Example:
272
+ -------
273
+ >>> get_github_state(TicketState.IN_PROGRESS)
274
+ 'open'
275
+ >>> get_github_state(TicketState.CLOSED)
276
+ 'closed'
277
+ """
278
+ if state in (TicketState.DONE, TicketState.CLOSED):
279
+ return GitHubStateMapping.CLOSED
280
+ return GitHubStateMapping.OPEN
281
+
282
+
283
+ # =============================================================================
284
+ # GitHub Projects V2 Type Definitions
285
+ # =============================================================================
286
+
287
+
288
+ class ProjectV2Owner(TypedDict, total=False):
289
+ """GitHub ProjectV2 owner (Organization or User).
290
+
291
+ Attributes:
292
+ __typename: Type discriminator ("Organization" or "User")
293
+ login: Owner login name
294
+ id: Owner node ID
295
+ """
296
+
297
+ __typename: str
298
+ login: str
299
+ id: str
300
+
301
+
302
+ class ProjectV2PageInfo(TypedDict, total=False):
303
+ """GraphQL pagination info for ProjectV2 queries.
304
+
305
+ Attributes:
306
+ hasNextPage: Whether more results exist
307
+ endCursor: Cursor for next page
308
+ """
309
+
310
+ hasNextPage: bool
311
+ endCursor: str | None
312
+
313
+
314
+ class ProjectV2ItemsConnection(TypedDict, total=False):
315
+ """ProjectV2 items connection.
316
+
317
+ Attributes:
318
+ totalCount: Total number of items in project
319
+ """
320
+
321
+ totalCount: int
322
+
323
+
324
+ class ProjectV2Node(TypedDict, total=False):
325
+ """GitHub ProjectV2 GraphQL node.
326
+
327
+ Represents a single GitHub Projects V2 project from the GraphQL API.
328
+ This type matches the structure returned by PROJECT_V2_FRAGMENT.
329
+
330
+ Design Decision: Total vs Required Fields
331
+ -----------------------------------------
332
+ Using total=False allows optional fields to be omitted, matching the
333
+ GraphQL API where many fields are nullable or may not be queried.
334
+
335
+ Required fields (id, number, title) are still enforced at the Pydantic
336
+ model level in map_github_projectv2_to_project().
337
+
338
+ Attributes:
339
+ id: GitHub node ID (e.g., "PVT_kwDOABcdefgh")
340
+ number: Project number (e.g., 5)
341
+ title: Project title
342
+ shortDescription: Brief description (max 256 chars)
343
+ readme: Markdown readme content
344
+ public: Whether project is publicly visible
345
+ closed: Whether project is closed
346
+ url: Direct URL to project
347
+ createdAt: ISO timestamp of creation
348
+ updatedAt: ISO timestamp of last update
349
+ closedAt: ISO timestamp of closure (if closed)
350
+ owner: Owner (Organization or User)
351
+ items: Items connection with totalCount
352
+ """
353
+
354
+ id: str
355
+ number: int
356
+ title: str
357
+ shortDescription: str | None
358
+ readme: str | None
359
+ public: bool
360
+ closed: bool
361
+ url: str
362
+ createdAt: str
363
+ updatedAt: str
364
+ closedAt: str | None
365
+ owner: ProjectV2Owner
366
+ items: ProjectV2ItemsConnection | None
367
+
368
+
369
+ class ProjectV2Response(TypedDict, total=False):
370
+ """Response from GET_PROJECT_QUERY or GET_PROJECT_BY_ID_QUERY.
371
+
372
+ Single project query response wrapping the project node.
373
+
374
+ Attributes:
375
+ organization: Organization containing projectV2 field
376
+ node: Direct node lookup result
377
+ """
378
+
379
+ organization: dict[str, ProjectV2Node | None]
380
+ node: ProjectV2Node | None
381
+
382
+
383
+ class ProjectV2Connection(TypedDict, total=False):
384
+ """Connection of ProjectV2 nodes with pagination.
385
+
386
+ Attributes:
387
+ totalCount: Total number of projects
388
+ pageInfo: Pagination information
389
+ nodes: List of project nodes
390
+ """
391
+
392
+ totalCount: int
393
+ pageInfo: ProjectV2PageInfo
394
+ nodes: list[ProjectV2Node]
395
+
396
+
397
+ class ProjectListResponse(TypedDict, total=False):
398
+ """Response from LIST_PROJECTS_QUERY.
399
+
400
+ Attributes:
401
+ organization: Organization containing projectsV2 connection
402
+ """
403
+
404
+ organization: dict[str, ProjectV2Connection]
405
+
406
+
407
+ class ProjectItemContent(TypedDict, total=False):
408
+ """Content of a project item (Issue, PR, or DraftIssue).
409
+
410
+ Attributes:
411
+ __typename: Content type discriminator
412
+ id: Content node ID
413
+ number: Issue/PR number (not present for DraftIssue)
414
+ title: Content title
415
+ state: Content state (OPEN/CLOSED for issues, etc.)
416
+ labels: Labels connection (issues only)
417
+ """
418
+
419
+ __typename: str
420
+ id: str
421
+ number: int | None
422
+ title: str
423
+ state: str | None
424
+ labels: dict[str, list[dict[str, str]]] | None
425
+
426
+
427
+ class ProjectItemNode(TypedDict, total=False):
428
+ """Single project item node.
429
+
430
+ Attributes:
431
+ id: Project item ID (not the same as content ID)
432
+ content: The actual content (Issue, PR, or DraftIssue)
433
+ """
434
+
435
+ id: str
436
+ content: ProjectItemContent
437
+
438
+
439
+ class ProjectItemsConnection(TypedDict, total=False):
440
+ """Connection of project items with pagination.
441
+
442
+ Attributes:
443
+ totalCount: Total items in project
444
+ pageInfo: Pagination info
445
+ nodes: List of project item nodes
446
+ """
447
+
448
+ totalCount: int
449
+ pageInfo: ProjectV2PageInfo
450
+ nodes: list[ProjectItemNode]
451
+
452
+
453
+ class ProjectItemsResponse(TypedDict, total=False):
454
+ """Response from PROJECT_ITEMS_QUERY.
455
+
456
+ Attributes:
457
+ node: ProjectV2 node containing items connection
458
+ """
459
+
460
+ node: dict[str, ProjectItemsConnection]
@@ -0,0 +1,35 @@
1
+ """JIRA adapter for universal ticket management.
2
+
3
+ This module provides a unified interface to JIRA REST API v3, supporting both
4
+ JIRA Cloud and JIRA Server/Data Center.
5
+
6
+ Public API:
7
+ -----------
8
+ JiraAdapter: Main adapter class for JIRA operations
9
+ JiraIssueType: Enum of common JIRA issue types
10
+ JiraPriority: Enum of standard JIRA priority levels
11
+
12
+ Usage:
13
+ ------
14
+ from mcp_ticketer.adapters.jira import JiraAdapter
15
+
16
+ config = {
17
+ "server": "https://company.atlassian.net",
18
+ "email": "user@example.com",
19
+ "api_token": "your-token",
20
+ "project_key": "PROJ",
21
+ }
22
+
23
+ adapter = JiraAdapter(config)
24
+ tickets = await adapter.list(limit=10)
25
+
26
+ """
27
+
28
+ from .adapter import JiraAdapter
29
+ from .types import JiraIssueType, JiraPriority
30
+
31
+ __all__ = [
32
+ "JiraAdapter",
33
+ "JiraIssueType",
34
+ "JiraPriority",
35
+ ]