mcp-ticketer 0.3.0__py3-none-any.whl → 2.2.9__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.
Files changed (160) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/__init__.py +2 -0
  5. mcp_ticketer/adapters/aitrackdown.py +930 -52
  6. mcp_ticketer/adapters/asana/__init__.py +15 -0
  7. mcp_ticketer/adapters/asana/adapter.py +1537 -0
  8. mcp_ticketer/adapters/asana/client.py +292 -0
  9. mcp_ticketer/adapters/asana/mappers.py +348 -0
  10. mcp_ticketer/adapters/asana/types.py +146 -0
  11. mcp_ticketer/adapters/github/__init__.py +26 -0
  12. mcp_ticketer/adapters/github/adapter.py +3229 -0
  13. mcp_ticketer/adapters/github/client.py +335 -0
  14. mcp_ticketer/adapters/github/mappers.py +797 -0
  15. mcp_ticketer/adapters/github/queries.py +692 -0
  16. mcp_ticketer/adapters/github/types.py +460 -0
  17. mcp_ticketer/adapters/hybrid.py +58 -16
  18. mcp_ticketer/adapters/jira/__init__.py +35 -0
  19. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  20. mcp_ticketer/adapters/jira/client.py +271 -0
  21. mcp_ticketer/adapters/jira/mappers.py +246 -0
  22. mcp_ticketer/adapters/jira/queries.py +216 -0
  23. mcp_ticketer/adapters/jira/types.py +304 -0
  24. mcp_ticketer/adapters/linear/__init__.py +1 -1
  25. mcp_ticketer/adapters/linear/adapter.py +3810 -462
  26. mcp_ticketer/adapters/linear/client.py +312 -69
  27. mcp_ticketer/adapters/linear/mappers.py +305 -85
  28. mcp_ticketer/adapters/linear/queries.py +317 -17
  29. mcp_ticketer/adapters/linear/types.py +187 -64
  30. mcp_ticketer/adapters/linear.py +2 -2
  31. mcp_ticketer/analysis/__init__.py +56 -0
  32. mcp_ticketer/analysis/dependency_graph.py +255 -0
  33. mcp_ticketer/analysis/health_assessment.py +304 -0
  34. mcp_ticketer/analysis/orphaned.py +218 -0
  35. mcp_ticketer/analysis/project_status.py +594 -0
  36. mcp_ticketer/analysis/similarity.py +224 -0
  37. mcp_ticketer/analysis/staleness.py +266 -0
  38. mcp_ticketer/automation/__init__.py +11 -0
  39. mcp_ticketer/automation/project_updates.py +378 -0
  40. mcp_ticketer/cache/memory.py +9 -8
  41. mcp_ticketer/cli/adapter_diagnostics.py +91 -54
  42. mcp_ticketer/cli/auggie_configure.py +116 -15
  43. mcp_ticketer/cli/codex_configure.py +274 -82
  44. mcp_ticketer/cli/configure.py +1323 -151
  45. mcp_ticketer/cli/cursor_configure.py +314 -0
  46. mcp_ticketer/cli/diagnostics.py +209 -114
  47. mcp_ticketer/cli/discover.py +297 -26
  48. mcp_ticketer/cli/gemini_configure.py +119 -26
  49. mcp_ticketer/cli/init_command.py +880 -0
  50. mcp_ticketer/cli/install_mcp_server.py +418 -0
  51. mcp_ticketer/cli/instruction_commands.py +435 -0
  52. mcp_ticketer/cli/linear_commands.py +256 -130
  53. mcp_ticketer/cli/main.py +140 -1544
  54. mcp_ticketer/cli/mcp_configure.py +1013 -100
  55. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  56. mcp_ticketer/cli/migrate_config.py +12 -8
  57. mcp_ticketer/cli/platform_commands.py +123 -0
  58. mcp_ticketer/cli/platform_detection.py +477 -0
  59. mcp_ticketer/cli/platform_installer.py +545 -0
  60. mcp_ticketer/cli/project_update_commands.py +350 -0
  61. mcp_ticketer/cli/python_detection.py +126 -0
  62. mcp_ticketer/cli/queue_commands.py +15 -15
  63. mcp_ticketer/cli/setup_command.py +794 -0
  64. mcp_ticketer/cli/simple_health.py +84 -59
  65. mcp_ticketer/cli/ticket_commands.py +1375 -0
  66. mcp_ticketer/cli/update_checker.py +313 -0
  67. mcp_ticketer/cli/utils.py +195 -72
  68. mcp_ticketer/core/__init__.py +64 -1
  69. mcp_ticketer/core/adapter.py +618 -18
  70. mcp_ticketer/core/config.py +77 -68
  71. mcp_ticketer/core/env_discovery.py +75 -16
  72. mcp_ticketer/core/env_loader.py +121 -97
  73. mcp_ticketer/core/exceptions.py +32 -24
  74. mcp_ticketer/core/http_client.py +26 -26
  75. mcp_ticketer/core/instructions.py +405 -0
  76. mcp_ticketer/core/label_manager.py +732 -0
  77. mcp_ticketer/core/mappers.py +42 -30
  78. mcp_ticketer/core/milestone_manager.py +252 -0
  79. mcp_ticketer/core/models.py +566 -19
  80. mcp_ticketer/core/onepassword_secrets.py +379 -0
  81. mcp_ticketer/core/priority_matcher.py +463 -0
  82. mcp_ticketer/core/project_config.py +189 -49
  83. mcp_ticketer/core/project_utils.py +281 -0
  84. mcp_ticketer/core/project_validator.py +376 -0
  85. mcp_ticketer/core/registry.py +3 -3
  86. mcp_ticketer/core/session_state.py +176 -0
  87. mcp_ticketer/core/state_matcher.py +592 -0
  88. mcp_ticketer/core/url_parser.py +425 -0
  89. mcp_ticketer/core/validators.py +69 -0
  90. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  91. mcp_ticketer/mcp/__init__.py +29 -1
  92. mcp_ticketer/mcp/__main__.py +60 -0
  93. mcp_ticketer/mcp/server/__init__.py +25 -0
  94. mcp_ticketer/mcp/server/__main__.py +60 -0
  95. mcp_ticketer/mcp/server/constants.py +58 -0
  96. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  97. mcp_ticketer/mcp/server/dto.py +195 -0
  98. mcp_ticketer/mcp/server/main.py +1343 -0
  99. mcp_ticketer/mcp/server/response_builder.py +206 -0
  100. mcp_ticketer/mcp/server/routing.py +723 -0
  101. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  102. mcp_ticketer/mcp/server/tools/__init__.py +69 -0
  103. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  104. mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
  105. mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
  106. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  107. mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
  108. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  109. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
  110. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  111. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  112. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  113. mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
  114. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  115. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  116. mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
  117. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  118. mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
  119. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  120. mcp_ticketer/queue/__init__.py +1 -0
  121. mcp_ticketer/queue/health_monitor.py +168 -136
  122. mcp_ticketer/queue/manager.py +78 -63
  123. mcp_ticketer/queue/queue.py +108 -21
  124. mcp_ticketer/queue/run_worker.py +2 -2
  125. mcp_ticketer/queue/ticket_registry.py +213 -155
  126. mcp_ticketer/queue/worker.py +96 -58
  127. mcp_ticketer/utils/__init__.py +5 -0
  128. mcp_ticketer/utils/token_utils.py +246 -0
  129. mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
  130. mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
  131. mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
  132. py_mcp_installer/examples/phase3_demo.py +178 -0
  133. py_mcp_installer/scripts/manage_version.py +54 -0
  134. py_mcp_installer/setup.py +6 -0
  135. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  136. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  137. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  138. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  139. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  140. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  141. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  142. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  143. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  144. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  145. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  146. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  147. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  148. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  149. py_mcp_installer/tests/__init__.py +0 -0
  150. py_mcp_installer/tests/platforms/__init__.py +0 -0
  151. py_mcp_installer/tests/test_platform_detector.py +17 -0
  152. mcp_ticketer/adapters/github.py +0 -1354
  153. mcp_ticketer/adapters/jira.py +0 -1011
  154. mcp_ticketer/mcp/server.py +0 -2030
  155. mcp_ticketer-0.3.0.dist-info/METADATA +0 -414
  156. mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
  157. mcp_ticketer-0.3.0.dist-info/top_level.txt +0 -1
  158. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
  159. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
  160. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,335 @@
1
+ """HTTP and GraphQL client for GitHub API.
2
+
3
+ This module provides a unified client for both REST API v3 and GraphQL API v4,
4
+ with error handling, retry logic, and rate limiting support.
5
+
6
+ Design Decision: Dual API Client
7
+ ---------------------------------
8
+ GitHub requires both REST and GraphQL APIs:
9
+ - REST API: For mutations (create/update/delete) and simple operations
10
+ - GraphQL API: For complex queries and efficient data fetching
11
+
12
+ Rationale:
13
+ - REST API is more stable and better documented for write operations
14
+ - GraphQL API reduces over-fetching and enables precise field selection
15
+ - Some features only available in one API (e.g., Projects V2 in GraphQL)
16
+
17
+ Trade-offs:
18
+ - Maintaining two client methods increases complexity
19
+ - GraphQL requires fragment management and query composition
20
+ - REST API can be chatty (multiple round trips for related data)
21
+
22
+ Error Handling Strategy:
23
+ -----------------------
24
+ - HTTP 401/403 → Authentication errors (fail fast)
25
+ - HTTP 429 → Rate limiting (could retry with backoff, currently fails fast)
26
+ - HTTP 5xx → Transient errors (fail fast, caller can retry)
27
+ - GraphQL errors → Validation errors (fail fast)
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import logging
33
+ from typing import Any
34
+
35
+ import httpx
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ class GitHubClient:
41
+ """GitHub API client supporting both REST v3 and GraphQL v4.
42
+
43
+ This client handles:
44
+ - Authentication with Personal Access Tokens (PAT)
45
+ - REST API requests with proper headers
46
+ - GraphQL query execution
47
+ - Error handling and HTTP status codes
48
+ - Rate limit tracking (optional)
49
+
50
+ Performance Notes:
51
+ -----------------
52
+ - Uses httpx.AsyncClient for async/await support
53
+ - Connection pooling handled by httpx
54
+ - Timeout: 30 seconds default
55
+ - No automatic retries (caller should implement retry logic)
56
+
57
+ Example:
58
+ -------
59
+ client = GitHubClient(
60
+ token="ghp_xxxxx",
61
+ owner="octocat",
62
+ repo="hello-world"
63
+ )
64
+
65
+ # REST API
66
+ response = await client.execute_rest("GET", "repos/octocat/hello-world")
67
+
68
+ # GraphQL API
69
+ data = await client.execute_graphql(query, variables)
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ token: str,
75
+ owner: str,
76
+ repo: str,
77
+ api_url: str = "https://api.github.com",
78
+ timeout: float = 30.0,
79
+ ):
80
+ """Initialize GitHub API client.
81
+
82
+ Args:
83
+ ----
84
+ token: GitHub Personal Access Token (PAT)
85
+ owner: Repository owner (username or organization)
86
+ repo: Repository name
87
+ api_url: Base API URL (default: https://api.github.com)
88
+ timeout: Request timeout in seconds (default: 30.0)
89
+ """
90
+ self.token = token
91
+ self.owner = owner
92
+ self.repo = repo
93
+ self.api_url = api_url
94
+
95
+ # GraphQL endpoint
96
+ self.graphql_url = (
97
+ f"{api_url}/graphql"
98
+ if "github.com" in api_url
99
+ else f"{api_url}/api/graphql"
100
+ )
101
+
102
+ # HTTP headers for authentication and API version
103
+ self.headers = {
104
+ "Authorization": f"Bearer {token}",
105
+ "Accept": "application/vnd.github.v3+json",
106
+ "X-GitHub-Api-Version": "2022-11-28",
107
+ }
108
+
109
+ # Async HTTP client with connection pooling
110
+ self.client = httpx.AsyncClient(
111
+ base_url=api_url,
112
+ headers=self.headers,
113
+ timeout=timeout,
114
+ )
115
+
116
+ # Rate limit tracking (optional, populated on API responses)
117
+ self._rate_limit: dict[str, Any] = {}
118
+
119
+ async def execute_rest(
120
+ self,
121
+ method: str,
122
+ endpoint: str,
123
+ json_data: dict[str, Any] | None = None,
124
+ params: dict[str, Any] | None = None,
125
+ ) -> dict[str, Any] | list[Any]:
126
+ """Execute a REST API request.
127
+
128
+ Args:
129
+ ----
130
+ method: HTTP method (GET, POST, PATCH, DELETE)
131
+ endpoint: API endpoint (e.g., "repos/owner/repo/issues")
132
+ json_data: Optional JSON body for POST/PATCH requests
133
+ params: Optional query parameters
134
+
135
+ Returns:
136
+ -------
137
+ Parsed JSON response (dict or list)
138
+
139
+ Raises:
140
+ ------
141
+ httpx.HTTPStatusError: On HTTP error status codes
142
+ ValueError: On invalid JSON response
143
+
144
+ Performance:
145
+ -----------
146
+ Time Complexity: O(1) for request, O(n) for JSON parsing
147
+ Expected Latency: 100-500ms depending on GitHub API response time
148
+
149
+ Example:
150
+ -------
151
+ # GET request
152
+ issues = await client.execute_rest("GET", "repos/owner/repo/issues")
153
+
154
+ # POST request
155
+ issue = await client.execute_rest(
156
+ "POST",
157
+ "repos/owner/repo/issues",
158
+ json_data={"title": "Bug", "body": "Description"}
159
+ )
160
+ """
161
+ # Ensure endpoint starts with / for proper URL construction
162
+ if not endpoint.startswith("/"):
163
+ endpoint = f"/{endpoint}"
164
+
165
+ logger.debug(f"REST {method} {endpoint}")
166
+
167
+ try:
168
+ response = await self.client.request(
169
+ method=method,
170
+ url=endpoint,
171
+ json=json_data,
172
+ params=params,
173
+ )
174
+
175
+ # Update rate limit information from headers
176
+ self._update_rate_limit_from_headers(response.headers)
177
+
178
+ # Raise for HTTP errors (4xx, 5xx)
179
+ response.raise_for_status()
180
+
181
+ # Parse JSON response
182
+ if response.text:
183
+ return response.json()
184
+ return {}
185
+
186
+ except httpx.HTTPStatusError as e:
187
+ logger.error(
188
+ f"REST API error: {method} {endpoint} -> "
189
+ f"HTTP {e.response.status_code}: {e.response.text}"
190
+ )
191
+ # Let the error propagate - adapter should handle error translation
192
+ raise
193
+
194
+ async def execute_graphql(
195
+ self,
196
+ query: str,
197
+ variables: dict[str, Any] | None = None,
198
+ ) -> dict[str, Any]:
199
+ """Execute a GraphQL query or mutation.
200
+
201
+ Args:
202
+ ----
203
+ query: GraphQL query string (including fragments)
204
+ variables: Optional query variables
205
+
206
+ Returns:
207
+ -------
208
+ GraphQL response data (dict)
209
+
210
+ Raises:
211
+ ------
212
+ httpx.HTTPStatusError: On HTTP error status codes
213
+ ValueError: On GraphQL errors in response
214
+
215
+ Performance:
216
+ -----------
217
+ Time Complexity: O(1) for request, O(n) for JSON parsing
218
+ Expected Latency: 100-500ms depending on query complexity
219
+ Token Usage: Depends on fragments used (compact vs. full)
220
+
221
+ Example:
222
+ -------
223
+ data = await client.execute_graphql(
224
+ query=GET_ISSUE,
225
+ variables={"owner": "octocat", "repo": "hello-world", "number": 1}
226
+ )
227
+ issue = data["repository"]["issue"]
228
+ """
229
+ logger.debug(f"GraphQL query: {query[:100]}...")
230
+
231
+ try:
232
+ response = await self.client.post(
233
+ self.graphql_url,
234
+ json={"query": query, "variables": variables or {}},
235
+ )
236
+
237
+ # Update rate limit information
238
+ self._update_rate_limit_from_headers(response.headers)
239
+
240
+ # Raise for HTTP errors
241
+ response.raise_for_status()
242
+
243
+ # Parse JSON response
244
+ data = response.json()
245
+
246
+ # Check for GraphQL-level errors
247
+ if "errors" in data:
248
+ error_messages = [
249
+ err.get("message", str(err)) for err in data["errors"]
250
+ ]
251
+ raise ValueError(f"GraphQL errors: {', '.join(error_messages)}")
252
+
253
+ return data.get("data", {})
254
+
255
+ except httpx.HTTPStatusError as e:
256
+ logger.error(
257
+ f"GraphQL API error: HTTP {e.response.status_code}: {e.response.text}"
258
+ )
259
+ raise
260
+
261
+ async def test_connection(self) -> bool:
262
+ """Test GitHub API connection and authentication.
263
+
264
+ Returns:
265
+ -------
266
+ True if connection is valid, False otherwise
267
+
268
+ Example:
269
+ -------
270
+ if await client.test_connection():
271
+ print("GitHub connection successful")
272
+ """
273
+ try:
274
+ await self.execute_rest("GET", "/user")
275
+ return True
276
+ except Exception as e:
277
+ logger.error(f"Connection test failed: {e}")
278
+ return False
279
+
280
+ async def get_rate_limit(self) -> dict[str, Any]:
281
+ """Get current rate limit status.
282
+
283
+ Returns:
284
+ -------
285
+ Rate limit information dict with keys:
286
+ - limit: Maximum requests per hour
287
+ - remaining: Remaining requests
288
+ - reset: Unix timestamp when limit resets
289
+
290
+ Example:
291
+ -------
292
+ rate_limit = await client.get_rate_limit()
293
+ print(f"Remaining: {rate_limit['remaining']}/{rate_limit['limit']}")
294
+ """
295
+ response = await self.execute_rest("GET", "/rate_limit")
296
+ return response.get("rate", {})
297
+
298
+ def _update_rate_limit_from_headers(self, headers: httpx.Headers) -> None:
299
+ """Extract rate limit info from response headers.
300
+
301
+ GitHub includes rate limit information in response headers:
302
+ - X-RateLimit-Limit: Maximum requests per hour
303
+ - X-RateLimit-Remaining: Remaining requests
304
+ - X-RateLimit-Reset: Unix timestamp when limit resets
305
+
306
+ Args:
307
+ ----
308
+ headers: HTTP response headers
309
+ """
310
+ if "X-RateLimit-Limit" in headers:
311
+ self._rate_limit = {
312
+ "limit": int(headers["X-RateLimit-Limit"]),
313
+ "remaining": int(headers.get("X-RateLimit-Remaining", 0)),
314
+ "reset": int(headers.get("X-RateLimit-Reset", 0)),
315
+ }
316
+
317
+ async def close(self) -> None:
318
+ """Close the HTTP client and cleanup resources.
319
+
320
+ Should be called when adapter is no longer needed to avoid
321
+ resource leaks.
322
+
323
+ Example:
324
+ -------
325
+ await client.close()
326
+ """
327
+ await self.client.aclose()
328
+
329
+ async def __aenter__(self):
330
+ """Async context manager entry."""
331
+ return self
332
+
333
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
334
+ """Async context manager exit - ensures cleanup."""
335
+ await self.close()