mcp-ticketer 0.12.0__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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/adapter.py +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +1288 -105
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Token counting and pagination utilities for MCP tool responses.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for estimating token counts and implementing
|
|
4
|
+
token-aware pagination to ensure responses stay under 20k token limits.
|
|
5
|
+
|
|
6
|
+
Design Decision: Token estimation vs. exact counting
|
|
7
|
+
- Uses 4-chars-per-token heuristic (conservative)
|
|
8
|
+
- Rationale: Actual tokenization requires tiktoken library and GPT-specific
|
|
9
|
+
tokenizer, which adds dependency and runtime overhead
|
|
10
|
+
- Trade-off: Approximate (±10%) vs. exact, but fast and dependency-free
|
|
11
|
+
- Extension Point: Can add tiktoken support via optional dependency later
|
|
12
|
+
|
|
13
|
+
Performance: O(1) for token estimation (string length only)
|
|
14
|
+
Memory: O(1) auxiliary space (no allocations beyond JSON serialization)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import Any, TypeVar
|
|
21
|
+
|
|
22
|
+
# Type variable for generic list items
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
# Conservative token estimation: 1 token ≈ 4 characters
|
|
26
|
+
# Based on OpenAI/Anthropic averages for English text + JSON structure
|
|
27
|
+
CHARS_PER_TOKEN = 4
|
|
28
|
+
|
|
29
|
+
# Default maximum tokens per MCP response
|
|
30
|
+
DEFAULT_MAX_TOKENS = 20_000
|
|
31
|
+
|
|
32
|
+
# Overhead estimation for response metadata (status, adapter info, etc.)
|
|
33
|
+
BASE_RESPONSE_OVERHEAD = 100
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def estimate_tokens(text: str) -> int:
|
|
37
|
+
"""Estimate token count for a text string.
|
|
38
|
+
|
|
39
|
+
Uses conservative heuristic: 1 token ≈ 4 characters.
|
|
40
|
+
This works reasonably well for English text and JSON structures.
|
|
41
|
+
|
|
42
|
+
Design Trade-off:
|
|
43
|
+
- Fast: O(len(text)) string length check
|
|
44
|
+
- Approximate: ±10% accuracy vs. exact tokenization
|
|
45
|
+
- Zero dependencies: No tiktoken or model-specific tokenizers needed
|
|
46
|
+
|
|
47
|
+
Performance:
|
|
48
|
+
- Time Complexity: O(n) where n is string length
|
|
49
|
+
- Space Complexity: O(1)
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
text: Input text to estimate token count for
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Estimated token count (conservative, may overestimate slightly)
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> estimate_tokens("Hello world")
|
|
59
|
+
3 # "Hello world" = 11 chars / 4 ≈ 2.75 → rounds to 3
|
|
60
|
+
>>> estimate_tokens(json.dumps({"id": "123", "title": "Test"}))
|
|
61
|
+
8 # JSON structure increases char count
|
|
62
|
+
"""
|
|
63
|
+
if not text:
|
|
64
|
+
return 0
|
|
65
|
+
return max(1, len(text) // CHARS_PER_TOKEN)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def estimate_json_tokens(data: dict | list | Any) -> int:
|
|
69
|
+
"""Estimate token count for JSON-serializable data.
|
|
70
|
+
|
|
71
|
+
Serializes data to JSON string then estimates tokens.
|
|
72
|
+
Accounts for JSON structure overhead (brackets, quotes, commas).
|
|
73
|
+
|
|
74
|
+
Performance:
|
|
75
|
+
- Time Complexity: O(n) where n is serialized JSON size
|
|
76
|
+
- Space Complexity: O(n) for JSON string (temporary)
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
data: Any JSON-serializable data (dict, list, primitives)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Estimated token count for serialized representation
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> estimate_json_tokens({"id": "123", "title": "Test"})
|
|
86
|
+
8
|
|
87
|
+
>>> estimate_json_tokens([1, 2, 3])
|
|
88
|
+
2
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
json_str = json.dumps(data, default=str) # default=str for non-serializable
|
|
92
|
+
return estimate_tokens(json_str)
|
|
93
|
+
except (TypeError, ValueError) as e:
|
|
94
|
+
logging.warning(f"Failed to serialize data for token estimation: {e}")
|
|
95
|
+
# Fallback: estimate based on string representation
|
|
96
|
+
return estimate_tokens(str(data))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def paginate_response(
|
|
100
|
+
items: list[T],
|
|
101
|
+
limit: int = 20,
|
|
102
|
+
offset: int = 0,
|
|
103
|
+
max_tokens: int = DEFAULT_MAX_TOKENS,
|
|
104
|
+
serialize_fn: Callable[[T], dict] | None = None,
|
|
105
|
+
compact_fn: Callable[[dict], dict] | None = None,
|
|
106
|
+
compact: bool = True,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
"""Paginate a list of items with token-aware limiting.
|
|
109
|
+
|
|
110
|
+
This function implements automatic pagination that:
|
|
111
|
+
1. Respects explicit limit/offset parameters
|
|
112
|
+
2. Stops adding items if response would exceed max_tokens
|
|
113
|
+
3. Optionally applies compact transformation to reduce token usage
|
|
114
|
+
4. Returns pagination metadata for client-side handling
|
|
115
|
+
|
|
116
|
+
Design Decision: Token-aware vs. count-based pagination
|
|
117
|
+
- Hybrid approach: Uses both item count AND token limits
|
|
118
|
+
- Rationale: Prevents oversized responses even with small item counts
|
|
119
|
+
- Example: 10 tickets with huge descriptions could exceed 20k tokens
|
|
120
|
+
- Trade-off: Slightly more complex but safer for production use
|
|
121
|
+
|
|
122
|
+
Performance:
|
|
123
|
+
- Time Complexity: O(n) where n is min(limit, items until token limit)
|
|
124
|
+
- Space Complexity: O(n) for result items list
|
|
125
|
+
- Early termination: Stops as soon as token limit would be exceeded
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
items: List of items to paginate
|
|
129
|
+
limit: Maximum number of items to return (default: 20)
|
|
130
|
+
offset: Number of items to skip (default: 0)
|
|
131
|
+
max_tokens: Maximum tokens allowed in response (default: 20,000)
|
|
132
|
+
serialize_fn: Optional function to convert item to dict (e.g., model.model_dump)
|
|
133
|
+
compact_fn: Optional function to create compact representation
|
|
134
|
+
compact: Whether to apply compact_fn if provided (default: True)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dictionary containing:
|
|
138
|
+
- items: List of paginated items (serialized)
|
|
139
|
+
- count: Number of items returned
|
|
140
|
+
- total: Total items available (before pagination)
|
|
141
|
+
- offset: Offset used for this page
|
|
142
|
+
- limit: Limit requested
|
|
143
|
+
- has_more: Boolean indicating if more items exist
|
|
144
|
+
- truncated_by_tokens: Boolean indicating if token limit caused truncation
|
|
145
|
+
- estimated_tokens: Approximate token count for response
|
|
146
|
+
|
|
147
|
+
Error Conditions:
|
|
148
|
+
- Invalid limit (<= 0): Returns empty result with error flag
|
|
149
|
+
- Invalid offset (< 0): Uses offset=0
|
|
150
|
+
- serialize_fn fails: Logs warning and skips item
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> tickets = [Ticket(...), Ticket(...), ...] # 100 tickets
|
|
154
|
+
>>> result = paginate_response(
|
|
155
|
+
... tickets,
|
|
156
|
+
... limit=20,
|
|
157
|
+
... offset=0,
|
|
158
|
+
... serialize_fn=lambda t: t.model_dump(),
|
|
159
|
+
... compact_fn=_compact_ticket,
|
|
160
|
+
... )
|
|
161
|
+
>>> result["count"] # 20 (or less if token limit hit)
|
|
162
|
+
>>> result["has_more"] # True
|
|
163
|
+
>>> result["estimated_tokens"] # ~2500
|
|
164
|
+
"""
|
|
165
|
+
# Validate parameters
|
|
166
|
+
if limit <= 0:
|
|
167
|
+
logging.warning(f"Invalid limit {limit}, using default 20")
|
|
168
|
+
limit = 20
|
|
169
|
+
|
|
170
|
+
if offset < 0:
|
|
171
|
+
logging.warning(f"Invalid offset {offset}, using 0")
|
|
172
|
+
offset = 0
|
|
173
|
+
|
|
174
|
+
total_items = len(items)
|
|
175
|
+
|
|
176
|
+
# Apply offset
|
|
177
|
+
items_after_offset = items[offset:]
|
|
178
|
+
|
|
179
|
+
# Track token usage
|
|
180
|
+
estimated_tokens = BASE_RESPONSE_OVERHEAD # Base response overhead
|
|
181
|
+
result_items: list[dict] = []
|
|
182
|
+
truncated_by_tokens = False
|
|
183
|
+
|
|
184
|
+
# Process items up to limit or token threshold
|
|
185
|
+
for idx, item in enumerate(items_after_offset):
|
|
186
|
+
# Check if we've hit the limit
|
|
187
|
+
if idx >= limit:
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
# Serialize item
|
|
191
|
+
try:
|
|
192
|
+
if serialize_fn:
|
|
193
|
+
item_dict = serialize_fn(item)
|
|
194
|
+
elif hasattr(item, "model_dump"):
|
|
195
|
+
item_dict = item.model_dump()
|
|
196
|
+
elif isinstance(item, dict):
|
|
197
|
+
item_dict = item
|
|
198
|
+
else:
|
|
199
|
+
item_dict = {"data": str(item)}
|
|
200
|
+
|
|
201
|
+
# Apply compact mode if requested and function provided
|
|
202
|
+
if compact and compact_fn:
|
|
203
|
+
item_dict = compact_fn(item_dict)
|
|
204
|
+
|
|
205
|
+
# Estimate tokens for this item
|
|
206
|
+
item_tokens = estimate_json_tokens(item_dict)
|
|
207
|
+
|
|
208
|
+
# Check if adding this item would exceed token limit
|
|
209
|
+
if estimated_tokens + item_tokens > max_tokens:
|
|
210
|
+
logging.info(
|
|
211
|
+
f"Token limit reached: {estimated_tokens + item_tokens} > {max_tokens}. "
|
|
212
|
+
f"Returning {len(result_items)} items instead of requested {limit}."
|
|
213
|
+
)
|
|
214
|
+
truncated_by_tokens = True
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
# Add item to results
|
|
218
|
+
result_items.append(item_dict)
|
|
219
|
+
estimated_tokens += item_tokens
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logging.warning(f"Failed to serialize item at index {idx + offset}: {e}")
|
|
223
|
+
# Skip this item and continue
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Calculate pagination metadata
|
|
227
|
+
items_returned = len(result_items)
|
|
228
|
+
has_more = (offset + items_returned) < total_items
|
|
229
|
+
|
|
230
|
+
# Warn if approaching token limit
|
|
231
|
+
if estimated_tokens > max_tokens * 0.8:
|
|
232
|
+
logging.warning(
|
|
233
|
+
f"Response approaching token limit: {estimated_tokens}/{max_tokens} tokens "
|
|
234
|
+
f"({estimated_tokens/max_tokens*100:.1f}%). Consider using compact mode or reducing limit."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
"items": result_items,
|
|
239
|
+
"count": items_returned,
|
|
240
|
+
"total": total_items,
|
|
241
|
+
"offset": offset,
|
|
242
|
+
"limit": limit,
|
|
243
|
+
"has_more": has_more,
|
|
244
|
+
"truncated_by_tokens": truncated_by_tokens,
|
|
245
|
+
"estimated_tokens": estimated_tokens,
|
|
246
|
+
}
|