mcp-ticketer 0.4.8__py3-none-any.whl → 0.4.10__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 +12 -3
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +10 -3
- mcp_ticketer/adapters/github.py +1 -2
- mcp_ticketer/adapters/jira.py +1 -2
- mcp_ticketer/adapters/linear/adapter.py +129 -10
- mcp_ticketer/adapters/linear/client.py +1 -2
- mcp_ticketer/adapters/linear/mappers.py +1 -2
- mcp_ticketer/cli/adapter_diagnostics.py +2 -4
- mcp_ticketer/cli/auggie_configure.py +9 -4
- mcp_ticketer/cli/codex_configure.py +9 -5
- mcp_ticketer/cli/configure.py +9 -3
- mcp_ticketer/cli/discover.py +6 -2
- mcp_ticketer/cli/gemini_configure.py +8 -4
- mcp_ticketer/cli/main.py +23 -4
- mcp_ticketer/cli/mcp_configure.py +144 -32
- mcp_ticketer/core/__init__.py +1 -2
- mcp_ticketer/mcp/server/__init__.py +6 -2
- mcp_ticketer/mcp/server/main.py +34 -14
- mcp_ticketer/mcp/server/server_sdk.py +2 -2
- mcp_ticketer/mcp/server/tools/__init__.py +9 -7
- mcp_ticketer/mcp/server/tools/attachment_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/bulk_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/comment_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/pr_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/search_tools.py +1 -1
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1 -1
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/METADATA +1 -1
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/RECORD +34 -34
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/top_level.txt +0 -0
mcp_ticketer/__init__.py
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
"""MCP Ticketer - Universal ticket management interface."""
|
|
2
2
|
|
|
3
|
-
from .__version__ import (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from .__version__ import (
|
|
4
|
+
__author__,
|
|
5
|
+
__author_email__,
|
|
6
|
+
__copyright__,
|
|
7
|
+
__description__,
|
|
8
|
+
__license__,
|
|
9
|
+
__title__,
|
|
10
|
+
__version__,
|
|
11
|
+
__version_info__,
|
|
12
|
+
get_user_agent,
|
|
13
|
+
get_version,
|
|
14
|
+
)
|
|
6
15
|
|
|
7
16
|
__all__ = [
|
|
8
17
|
"__version__",
|
mcp_ticketer/__version__.py
CHANGED
|
@@ -8,11 +8,18 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from ..core.adapter import BaseAdapter
|
|
11
|
+
from ..core.models import (
|
|
12
|
+
Attachment,
|
|
13
|
+
Comment,
|
|
14
|
+
Epic,
|
|
15
|
+
Priority,
|
|
16
|
+
SearchQuery,
|
|
17
|
+
Task,
|
|
18
|
+
TicketState,
|
|
19
|
+
)
|
|
20
|
+
from ..core.registry import AdapterRegistry
|
|
11
21
|
|
|
12
22
|
logger = logging.getLogger(__name__)
|
|
13
|
-
from ..core.models import (Attachment, Comment, Epic, Priority, SearchQuery,
|
|
14
|
-
Task, TicketState)
|
|
15
|
-
from ..core.registry import AdapterRegistry
|
|
16
23
|
|
|
17
24
|
# Import ai-trackdown-pytools when available
|
|
18
25
|
try:
|
mcp_ticketer/adapters/github.py
CHANGED
|
@@ -9,8 +9,7 @@ import httpx
|
|
|
9
9
|
|
|
10
10
|
from ..core.adapter import BaseAdapter
|
|
11
11
|
from ..core.env_loader import load_adapter_config, validate_adapter_config
|
|
12
|
-
from ..core.models import
|
|
13
|
-
TicketState)
|
|
12
|
+
from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
|
|
14
13
|
from ..core.registry import AdapterRegistry
|
|
15
14
|
|
|
16
15
|
|
mcp_ticketer/adapters/jira.py
CHANGED
|
@@ -13,8 +13,7 @@ from httpx import AsyncClient, HTTPStatusError, TimeoutException
|
|
|
13
13
|
|
|
14
14
|
from ..core.adapter import BaseAdapter
|
|
15
15
|
from ..core.env_loader import load_adapter_config, validate_adapter_config
|
|
16
|
-
from ..core.models import
|
|
17
|
-
TicketState)
|
|
16
|
+
from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
|
|
18
17
|
from ..core.registry import AdapterRegistry
|
|
19
18
|
|
|
20
19
|
logger = logging.getLogger(__name__)
|
|
@@ -20,15 +20,27 @@ from ...core.adapter import BaseAdapter
|
|
|
20
20
|
from ...core.models import Comment, Epic, SearchQuery, Task, TicketState
|
|
21
21
|
from ...core.registry import AdapterRegistry
|
|
22
22
|
from .client import LinearGraphQLClient
|
|
23
|
-
from .mappers import (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
from .
|
|
31
|
-
|
|
23
|
+
from .mappers import (
|
|
24
|
+
build_linear_issue_input,
|
|
25
|
+
build_linear_issue_update_input,
|
|
26
|
+
map_linear_comment_to_comment,
|
|
27
|
+
map_linear_issue_to_task,
|
|
28
|
+
map_linear_project_to_epic,
|
|
29
|
+
)
|
|
30
|
+
from .queries import (
|
|
31
|
+
ALL_FRAGMENTS,
|
|
32
|
+
CREATE_ISSUE_MUTATION,
|
|
33
|
+
LIST_ISSUES_QUERY,
|
|
34
|
+
SEARCH_ISSUES_QUERY,
|
|
35
|
+
UPDATE_ISSUE_MUTATION,
|
|
36
|
+
WORKFLOW_STATES_QUERY,
|
|
37
|
+
)
|
|
38
|
+
from .types import (
|
|
39
|
+
LinearStateMapping,
|
|
40
|
+
build_issue_filter,
|
|
41
|
+
get_linear_priority,
|
|
42
|
+
get_linear_state_type,
|
|
43
|
+
)
|
|
32
44
|
|
|
33
45
|
|
|
34
46
|
class LinearAdapter(BaseAdapter[Task]):
|
|
@@ -91,7 +103,10 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
91
103
|
# Remove environment variable name prefix (e.g., "LINEAR_API_KEY=")
|
|
92
104
|
if "=" in self.api_key:
|
|
93
105
|
parts = self.api_key.split("=", 1)
|
|
94
|
-
if len(parts) == 2 and parts[0].upper() in (
|
|
106
|
+
if len(parts) == 2 and parts[0].upper() in (
|
|
107
|
+
"LINEAR_API_KEY",
|
|
108
|
+
"API_KEY",
|
|
109
|
+
):
|
|
95
110
|
self.api_key = parts[1]
|
|
96
111
|
|
|
97
112
|
# Validate API key format (Linear keys start with "lin_api_")
|
|
@@ -197,6 +212,96 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
197
212
|
except Exception as e:
|
|
198
213
|
raise ValueError(f"Failed to resolve team '{self.team_key}': {e}")
|
|
199
214
|
|
|
215
|
+
async def _resolve_project_id(self, project_identifier: str) -> str | None:
|
|
216
|
+
"""Resolve project identifier (slug, name, short ID, or URL) to full UUID.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
project_identifier: Project slug, name, short ID, or URL
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Full Linear project UUID, or None if not found
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
ValueError: If project lookup fails
|
|
226
|
+
|
|
227
|
+
Examples:
|
|
228
|
+
- "crm-smart-monitoring-system" (slug)
|
|
229
|
+
- "CRM Smart Monitoring System" (name)
|
|
230
|
+
- "f59a41a96c52" (short ID from URL)
|
|
231
|
+
- "https://linear.app/travel-bta/project/crm-smart-monitoring-system-f59a41a96c52/overview" (full URL)
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
if not project_identifier:
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
# Extract slug/ID from URL if full URL provided
|
|
238
|
+
if project_identifier.startswith("http"):
|
|
239
|
+
# Extract slug-shortid from URL like:
|
|
240
|
+
# https://linear.app/travel-bta/project/crm-smart-monitoring-system-f59a41a96c52/overview
|
|
241
|
+
parts = project_identifier.split("/project/")
|
|
242
|
+
if len(parts) > 1:
|
|
243
|
+
slug_with_id = parts[1].split("/")[
|
|
244
|
+
0
|
|
245
|
+
] # Get "crm-smart-monitoring-system-f59a41a96c52"
|
|
246
|
+
project_identifier = slug_with_id
|
|
247
|
+
else:
|
|
248
|
+
raise ValueError(f"Invalid Linear project URL: {project_identifier}")
|
|
249
|
+
|
|
250
|
+
# If it looks like a full UUID already (exactly 36 chars with exactly 4 dashes), return it
|
|
251
|
+
# UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
252
|
+
if len(project_identifier) == 36 and project_identifier.count("-") == 4:
|
|
253
|
+
return project_identifier
|
|
254
|
+
|
|
255
|
+
# Query all projects and search for matching slug, name, or slugId
|
|
256
|
+
query = """
|
|
257
|
+
query GetProjects {
|
|
258
|
+
projects(first: 100) {
|
|
259
|
+
nodes {
|
|
260
|
+
id
|
|
261
|
+
name
|
|
262
|
+
slugId
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
result = await self.client.execute_query(query, {})
|
|
270
|
+
projects = result.get("projects", {}).get("nodes", [])
|
|
271
|
+
|
|
272
|
+
# Search for match by slug, slugId, name (case-insensitive)
|
|
273
|
+
project_lower = project_identifier.lower()
|
|
274
|
+
for project in projects:
|
|
275
|
+
# Check if identifier matches slug pattern (extracted from slugId)
|
|
276
|
+
slug_id = project.get("slugId", "")
|
|
277
|
+
if slug_id:
|
|
278
|
+
# slugId format: "crm-smart-monitoring-system-f59a41a96c52"
|
|
279
|
+
# Extract both the slug part and short ID
|
|
280
|
+
if "-" in slug_id:
|
|
281
|
+
parts = slug_id.rsplit(
|
|
282
|
+
"-", 1
|
|
283
|
+
) # Split from right to get last part
|
|
284
|
+
slug_part = parts[0] # "crm-smart-monitoring-system"
|
|
285
|
+
short_id = parts[1] if len(parts) > 1 else "" # "f59a41a96c52"
|
|
286
|
+
|
|
287
|
+
# Match full slugId, slug part, or short ID
|
|
288
|
+
if (
|
|
289
|
+
slug_id.lower() == project_lower
|
|
290
|
+
or slug_part.lower() == project_lower
|
|
291
|
+
or short_id.lower() == project_lower
|
|
292
|
+
):
|
|
293
|
+
return project["id"]
|
|
294
|
+
|
|
295
|
+
# Also check exact name match (case-insensitive)
|
|
296
|
+
if project["name"].lower() == project_lower:
|
|
297
|
+
return project["id"]
|
|
298
|
+
|
|
299
|
+
# No match found
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
raise ValueError(f"Failed to resolve project '{project_identifier}': {e}")
|
|
304
|
+
|
|
200
305
|
async def _load_workflow_states(self, team_id: str) -> None:
|
|
201
306
|
"""Load and cache workflow states for the team.
|
|
202
307
|
|
|
@@ -446,6 +551,20 @@ class LinearAdapter(BaseAdapter[Task]):
|
|
|
446
551
|
# Remove labelIds if no labels resolved
|
|
447
552
|
issue_input.pop("labelIds", None)
|
|
448
553
|
|
|
554
|
+
# Resolve project ID if parent_epic is provided (supports slug, name, short ID, or URL)
|
|
555
|
+
if task.parent_epic:
|
|
556
|
+
project_id = await self._resolve_project_id(task.parent_epic)
|
|
557
|
+
if project_id:
|
|
558
|
+
issue_input["projectId"] = project_id
|
|
559
|
+
else:
|
|
560
|
+
# Log warning but don't fail - user may have provided invalid project
|
|
561
|
+
logging.getLogger(__name__).warning(
|
|
562
|
+
f"Could not resolve project identifier '{task.parent_epic}' to UUID. "
|
|
563
|
+
"Issue will be created without project assignment."
|
|
564
|
+
)
|
|
565
|
+
# Remove projectId if we couldn't resolve it
|
|
566
|
+
issue_input.pop("projectId", None)
|
|
567
|
+
|
|
449
568
|
try:
|
|
450
569
|
result = await self.client.execute_mutation(
|
|
451
570
|
CREATE_ISSUE_MUTATION, {"input": issue_input}
|
|
@@ -16,8 +16,7 @@ except ImportError:
|
|
|
16
16
|
HTTPXAsyncTransport = None
|
|
17
17
|
TransportError = Exception
|
|
18
18
|
|
|
19
|
-
from ...core.exceptions import
|
|
20
|
-
RateLimitError)
|
|
19
|
+
from ...core.exceptions import AdapterError, AuthenticationError, RateLimitError
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class LinearGraphQLClient:
|
|
@@ -6,8 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from ...core.models import Comment, Epic, Priority, Task, TicketState
|
|
9
|
-
from .types import
|
|
10
|
-
get_universal_state)
|
|
9
|
+
from .types import extract_linear_metadata, get_universal_priority, get_universal_state
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def map_linear_issue_to_task(issue_data: dict[str, Any]) -> Task:
|
|
@@ -197,8 +197,7 @@ def _test_adapter_instantiation(console: Console) -> None:
|
|
|
197
197
|
if primary:
|
|
198
198
|
adapter_type = primary.adapter_type
|
|
199
199
|
# Build config from discovery
|
|
200
|
-
from ..mcp.server import
|
|
201
|
-
_build_adapter_config_from_env_vars
|
|
200
|
+
from ..mcp.server import _build_adapter_config_from_env_vars
|
|
202
201
|
|
|
203
202
|
config = _build_adapter_config_from_env_vars(adapter_type, {})
|
|
204
203
|
else:
|
|
@@ -385,8 +384,7 @@ def get_adapter_status() -> dict[str, Any]:
|
|
|
385
384
|
adapter_type = primary.adapter_type
|
|
386
385
|
status["configuration_source"] = primary.found_in
|
|
387
386
|
# Build basic config
|
|
388
|
-
from ..mcp.server import
|
|
389
|
-
_build_adapter_config_from_env_vars
|
|
387
|
+
from ..mcp.server import _build_adapter_config_from_env_vars
|
|
390
388
|
|
|
391
389
|
config = _build_adapter_config_from_env_vars(adapter_type, {})
|
|
392
390
|
else:
|
|
@@ -85,6 +85,11 @@ def create_auggie_server_config(
|
|
|
85
85
|
Auggie MCP server configuration dict
|
|
86
86
|
|
|
87
87
|
"""
|
|
88
|
+
# Get mcp-ticketer CLI command from same directory as Python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
python_dir = Path(python_path).parent
|
|
92
|
+
mcp_ticketer_cmd = str(python_dir / "mcp-ticketer")
|
|
88
93
|
# Get adapter configuration
|
|
89
94
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
90
95
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -134,14 +139,14 @@ def create_auggie_server_config(
|
|
|
134
139
|
if "project_key" in adapter_config:
|
|
135
140
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
136
141
|
|
|
137
|
-
# Use
|
|
138
|
-
args = ["
|
|
142
|
+
# Use CLI command: mcp-ticketer mcp
|
|
143
|
+
args = ["mcp"]
|
|
139
144
|
if project_path:
|
|
140
145
|
args.append(project_path)
|
|
141
146
|
|
|
142
147
|
# Create server configuration (simpler than Gemini - no timeout/trust)
|
|
143
148
|
config = {
|
|
144
|
-
"command":
|
|
149
|
+
"command": mcp_ticketer_cmd,
|
|
145
150
|
"args": args,
|
|
146
151
|
"env": env_vars,
|
|
147
152
|
}
|
|
@@ -298,7 +303,7 @@ def configure_auggie_mcp(force: bool = False) -> None:
|
|
|
298
303
|
console.print(" Server name: mcp-ticketer")
|
|
299
304
|
console.print(f" Adapter: {adapter}")
|
|
300
305
|
console.print(f" Python: {python_path}")
|
|
301
|
-
console.print(" Command:
|
|
306
|
+
console.print(" Command: mcp-ticketer mcp")
|
|
302
307
|
console.print(" Scope: Global (affects all projects)")
|
|
303
308
|
console.print(f" Project path: {project_path}")
|
|
304
309
|
if "env" in server_config:
|
|
@@ -4,11 +4,11 @@ Codex CLI only supports global configuration at ~/.codex/config.toml.
|
|
|
4
4
|
Unlike Claude Code and Gemini CLI, there is no project-level configuration support.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import tomllib
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from typing import Any
|
|
10
9
|
|
|
11
10
|
import tomli_w
|
|
11
|
+
import tomllib
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
|
|
14
14
|
from .mcp_configure import load_project_config
|
|
@@ -87,6 +87,10 @@ def create_codex_server_config(
|
|
|
87
87
|
Codex MCP server configuration dict
|
|
88
88
|
|
|
89
89
|
"""
|
|
90
|
+
# Get mcp-ticketer CLI command from same directory as Python
|
|
91
|
+
python_dir = Path(python_path).parent
|
|
92
|
+
mcp_ticketer_cmd = str(python_dir / "mcp-ticketer")
|
|
93
|
+
|
|
90
94
|
# Get adapter configuration
|
|
91
95
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
92
96
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -136,14 +140,14 @@ def create_codex_server_config(
|
|
|
136
140
|
if "project_key" in adapter_config:
|
|
137
141
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
138
142
|
|
|
139
|
-
# Use
|
|
140
|
-
args = ["
|
|
143
|
+
# Use CLI command: mcp-ticketer mcp
|
|
144
|
+
args = ["mcp"]
|
|
141
145
|
if project_path:
|
|
142
146
|
args.append(project_path)
|
|
143
147
|
|
|
144
148
|
# Create server configuration with Codex-specific structure
|
|
145
149
|
config: dict[str, Any] = {
|
|
146
|
-
"command":
|
|
150
|
+
"command": mcp_ticketer_cmd,
|
|
147
151
|
"args": args,
|
|
148
152
|
"env": env_vars,
|
|
149
153
|
}
|
|
@@ -307,7 +311,7 @@ def configure_codex_mcp(force: bool = False) -> None:
|
|
|
307
311
|
console.print(" Server name: mcp-ticketer")
|
|
308
312
|
console.print(f" Adapter: {adapter}")
|
|
309
313
|
console.print(f" Python: {python_path}")
|
|
310
|
-
console.print(" Command:
|
|
314
|
+
console.print(" Command: mcp-ticketer mcp")
|
|
311
315
|
console.print(" Scope: global (Codex only supports global config)")
|
|
312
316
|
console.print(f" Project path: {project_path}")
|
|
313
317
|
if "env" in server_config:
|
mcp_ticketer/cli/configure.py
CHANGED
|
@@ -8,9 +8,15 @@ from rich.panel import Panel
|
|
|
8
8
|
from rich.prompt import Confirm, Prompt
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
|
-
from ..core.project_config import (
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from ..core.project_config import (
|
|
12
|
+
AdapterConfig,
|
|
13
|
+
AdapterType,
|
|
14
|
+
ConfigResolver,
|
|
15
|
+
ConfigValidator,
|
|
16
|
+
HybridConfig,
|
|
17
|
+
SyncStrategy,
|
|
18
|
+
TicketerConfig,
|
|
19
|
+
)
|
|
14
20
|
|
|
15
21
|
console = Console()
|
|
16
22
|
|
mcp_ticketer/cli/discover.py
CHANGED
|
@@ -6,8 +6,12 @@ import typer
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
8
|
from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
|
|
9
|
-
from ..core.project_config import (
|
|
10
|
-
|
|
9
|
+
from ..core.project_config import (
|
|
10
|
+
AdapterConfig,
|
|
11
|
+
ConfigResolver,
|
|
12
|
+
ConfigValidator,
|
|
13
|
+
TicketerConfig,
|
|
14
|
+
)
|
|
11
15
|
|
|
12
16
|
console = Console()
|
|
13
17
|
app = typer.Typer(help="Auto-discover configuration from .env files")
|
|
@@ -87,6 +87,10 @@ def create_gemini_server_config(
|
|
|
87
87
|
Gemini MCP server configuration dict
|
|
88
88
|
|
|
89
89
|
"""
|
|
90
|
+
# Get mcp-ticketer CLI command from same directory as Python
|
|
91
|
+
python_dir = Path(python_path).parent
|
|
92
|
+
mcp_ticketer_cmd = str(python_dir / "mcp-ticketer")
|
|
93
|
+
|
|
90
94
|
# Get adapter configuration
|
|
91
95
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
92
96
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -136,14 +140,14 @@ def create_gemini_server_config(
|
|
|
136
140
|
if "project_key" in adapter_config:
|
|
137
141
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
138
142
|
|
|
139
|
-
# Use
|
|
140
|
-
args = ["
|
|
143
|
+
# Use CLI command: mcp-ticketer mcp
|
|
144
|
+
args = ["mcp"]
|
|
141
145
|
if project_path:
|
|
142
146
|
args.append(project_path)
|
|
143
147
|
|
|
144
148
|
# Create server configuration with Gemini-specific options
|
|
145
149
|
config = {
|
|
146
|
-
"command":
|
|
150
|
+
"command": mcp_ticketer_cmd,
|
|
147
151
|
"args": args,
|
|
148
152
|
"env": env_vars,
|
|
149
153
|
"timeout": 15000, # 15 seconds timeout
|
|
@@ -300,7 +304,7 @@ def configure_gemini_mcp(
|
|
|
300
304
|
console.print(" Server name: mcp-ticketer")
|
|
301
305
|
console.print(f" Adapter: {adapter}")
|
|
302
306
|
console.print(f" Python: {python_path}")
|
|
303
|
-
console.print(" Command:
|
|
307
|
+
console.print(" Command: mcp-ticketer mcp")
|
|
304
308
|
console.print(f" Timeout: {server_config['timeout']}ms")
|
|
305
309
|
console.print(f" Trust: {server_config['trust']}")
|
|
306
310
|
if project_path:
|
mcp_ticketer/cli/main.py
CHANGED
|
@@ -20,8 +20,7 @@ from ..core.models import Comment, SearchQuery
|
|
|
20
20
|
from ..queue import Queue, QueueStatus, WorkerManager
|
|
21
21
|
from ..queue.health_monitor import HealthStatus, QueueHealthMonitor
|
|
22
22
|
from ..queue.ticket_registry import TicketRegistry
|
|
23
|
-
from .configure import
|
|
24
|
-
show_current_config)
|
|
23
|
+
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
25
24
|
from .diagnostics import run_diagnostics
|
|
26
25
|
from .discover import app as discover_app
|
|
27
26
|
from .migrate_config import migrate_config_command
|
|
@@ -1869,9 +1868,29 @@ mcp_app = typer.Typer(
|
|
|
1869
1868
|
name="mcp",
|
|
1870
1869
|
help="Configure MCP integration for AI clients (Claude, Gemini, Codex, Auggie)",
|
|
1871
1870
|
add_completion=False,
|
|
1871
|
+
invoke_without_command=True,
|
|
1872
1872
|
)
|
|
1873
1873
|
|
|
1874
1874
|
|
|
1875
|
+
@mcp_app.callback()
|
|
1876
|
+
def mcp_callback(
|
|
1877
|
+
ctx: typer.Context,
|
|
1878
|
+
project_path: str | None = typer.Argument(
|
|
1879
|
+
None, help="Project directory path (optional - uses cwd if not provided)"
|
|
1880
|
+
),
|
|
1881
|
+
):
|
|
1882
|
+
"""MCP command group - runs MCP server if no subcommand provided."""
|
|
1883
|
+
if ctx.invoked_subcommand is None:
|
|
1884
|
+
# No subcommand provided, run the serve command
|
|
1885
|
+
# Change to project directory if provided
|
|
1886
|
+
if project_path:
|
|
1887
|
+
import os
|
|
1888
|
+
|
|
1889
|
+
os.chdir(project_path)
|
|
1890
|
+
# Invoke the serve command through context
|
|
1891
|
+
ctx.invoke(mcp_serve, adapter=None, base_path=None)
|
|
1892
|
+
|
|
1893
|
+
|
|
1875
1894
|
@app.command()
|
|
1876
1895
|
def install(
|
|
1877
1896
|
platform: str | None = typer.Argument(
|
|
@@ -2211,8 +2230,8 @@ def mcp_serve(
|
|
|
2211
2230
|
2. Global: ~/.mcp-ticketer/config.json
|
|
2212
2231
|
3. Default: aitrackdown adapter with .aitrackdown base path
|
|
2213
2232
|
"""
|
|
2214
|
-
from ..mcp.server_sdk import configure_adapter
|
|
2215
|
-
from ..mcp.server_sdk import main as sdk_main
|
|
2233
|
+
from ..mcp.server.server_sdk import configure_adapter
|
|
2234
|
+
from ..mcp.server.server_sdk import main as sdk_main
|
|
2216
2235
|
|
|
2217
2236
|
# Load configuration (respects project-specific config in cwd)
|
|
2218
2237
|
config = load_config()
|
|
@@ -107,28 +107,44 @@ def find_claude_mcp_config(global_config: bool = False) -> Path:
|
|
|
107
107
|
Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
108
108
|
)
|
|
109
109
|
else:
|
|
110
|
-
#
|
|
111
|
-
config_path = Path.
|
|
110
|
+
# Claude Code configuration (project-specific)
|
|
111
|
+
config_path = Path.home() / ".claude.json"
|
|
112
112
|
|
|
113
113
|
return config_path
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
def load_claude_mcp_config(config_path: Path) -> dict:
|
|
116
|
+
def load_claude_mcp_config(config_path: Path, is_claude_code: bool = False) -> dict:
|
|
117
117
|
"""Load existing Claude MCP configuration or return empty structure.
|
|
118
118
|
|
|
119
119
|
Args:
|
|
120
120
|
config_path: Path to MCP config file
|
|
121
|
+
is_claude_code: If True, return Claude Code structure with projects
|
|
121
122
|
|
|
122
123
|
Returns:
|
|
123
124
|
MCP configuration dict
|
|
124
125
|
|
|
125
126
|
"""
|
|
126
127
|
if config_path.exists():
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
try:
|
|
129
|
+
with open(config_path) as f:
|
|
130
|
+
content = f.read().strip()
|
|
131
|
+
if not content:
|
|
132
|
+
# Empty file, return default structure
|
|
133
|
+
return {"projects": {}} if is_claude_code else {"mcpServers": {}}
|
|
134
|
+
return json.loads(content)
|
|
135
|
+
except json.JSONDecodeError as e:
|
|
136
|
+
console.print(
|
|
137
|
+
f"[yellow]⚠ Warning: Invalid JSON in {config_path}, creating new config[/yellow]"
|
|
138
|
+
)
|
|
139
|
+
console.print(f"[dim]Error: {e}[/dim]")
|
|
140
|
+
# Return default structure on parse error
|
|
141
|
+
return {"projects": {}} if is_claude_code else {"mcpServers": {}}
|
|
129
142
|
|
|
130
|
-
# Return empty structure
|
|
131
|
-
|
|
143
|
+
# Return empty structure based on config type
|
|
144
|
+
if is_claude_code:
|
|
145
|
+
return {"projects": {}}
|
|
146
|
+
else:
|
|
147
|
+
return {"mcpServers": {}}
|
|
132
148
|
|
|
133
149
|
|
|
134
150
|
def save_claude_mcp_config(config_path: Path, config: dict) -> None:
|
|
@@ -161,12 +177,12 @@ def create_mcp_server_config(
|
|
|
161
177
|
MCP server configuration dict matching Claude Code stdio pattern
|
|
162
178
|
|
|
163
179
|
"""
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
# Get mcp-ticketer CLI command from same directory as Python
|
|
181
|
+
python_dir = Path(python_path).parent
|
|
182
|
+
mcp_ticketer_cmd = str(python_dir / "mcp-ticketer")
|
|
167
183
|
|
|
168
|
-
# Use
|
|
169
|
-
args = ["
|
|
184
|
+
# Use CLI command: mcp-ticketer mcp
|
|
185
|
+
args = ["mcp"]
|
|
170
186
|
|
|
171
187
|
# Add project path if provided
|
|
172
188
|
if project_path:
|
|
@@ -175,7 +191,7 @@ def create_mcp_server_config(
|
|
|
175
191
|
# REQUIRED: Add "type": "stdio" for Claude Code compatibility
|
|
176
192
|
config = {
|
|
177
193
|
"type": "stdio",
|
|
178
|
-
"command":
|
|
194
|
+
"command": mcp_ticketer_cmd,
|
|
179
195
|
"args": args,
|
|
180
196
|
}
|
|
181
197
|
|
|
@@ -246,11 +262,14 @@ def remove_claude_mcp(global_config: bool = False, dry_run: bool = False) -> Non
|
|
|
246
262
|
|
|
247
263
|
"""
|
|
248
264
|
# Step 1: Find Claude MCP config location
|
|
249
|
-
config_type = "Claude Desktop" if global_config else "
|
|
265
|
+
config_type = "Claude Desktop" if global_config else "Claude Code"
|
|
250
266
|
console.print(f"[cyan]🔍 Removing {config_type} MCP configuration...[/cyan]")
|
|
251
267
|
|
|
252
268
|
mcp_config_path = find_claude_mcp_config(global_config)
|
|
253
|
-
console.print(f"[dim]
|
|
269
|
+
console.print(f"[dim]Primary config: {mcp_config_path}[/dim]")
|
|
270
|
+
|
|
271
|
+
# Get absolute project path for Claude Code
|
|
272
|
+
absolute_project_path = str(Path.cwd().resolve()) if not global_config else None
|
|
254
273
|
|
|
255
274
|
# Step 2: Check if config file exists
|
|
256
275
|
if not mcp_config_path.exists():
|
|
@@ -259,10 +278,22 @@ def remove_claude_mcp(global_config: bool = False, dry_run: bool = False) -> Non
|
|
|
259
278
|
return
|
|
260
279
|
|
|
261
280
|
# Step 3: Load existing MCP configuration
|
|
262
|
-
|
|
281
|
+
is_claude_code = not global_config
|
|
282
|
+
mcp_config = load_claude_mcp_config(mcp_config_path, is_claude_code=is_claude_code)
|
|
263
283
|
|
|
264
284
|
# Step 4: Check if mcp-ticketer is configured
|
|
265
|
-
|
|
285
|
+
is_configured = False
|
|
286
|
+
if is_claude_code:
|
|
287
|
+
# Check Claude Code structure: .projects[path].mcpServers["mcp-ticketer"]
|
|
288
|
+
if absolute_project_path:
|
|
289
|
+
projects = mcp_config.get("projects", {})
|
|
290
|
+
project_config_entry = projects.get(absolute_project_path, {})
|
|
291
|
+
is_configured = "mcp-ticketer" in project_config_entry.get("mcpServers", {})
|
|
292
|
+
else:
|
|
293
|
+
# Check Claude Desktop structure: .mcpServers["mcp-ticketer"]
|
|
294
|
+
is_configured = "mcp-ticketer" in mcp_config.get("mcpServers", {})
|
|
295
|
+
|
|
296
|
+
if not is_configured:
|
|
266
297
|
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
267
298
|
console.print(f"[dim]No mcp-ticketer entry found in {mcp_config_path}[/dim]")
|
|
268
299
|
return
|
|
@@ -272,10 +303,37 @@ def remove_claude_mcp(global_config: bool = False, dry_run: bool = False) -> Non
|
|
|
272
303
|
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
273
304
|
console.print(" Server name: mcp-ticketer")
|
|
274
305
|
console.print(f" From: {mcp_config_path}")
|
|
306
|
+
if absolute_project_path:
|
|
307
|
+
console.print(f" Project: {absolute_project_path}")
|
|
275
308
|
return
|
|
276
309
|
|
|
277
310
|
# Step 6: Remove mcp-ticketer from configuration
|
|
278
|
-
|
|
311
|
+
if is_claude_code and absolute_project_path:
|
|
312
|
+
# Remove from Claude Code structure
|
|
313
|
+
del mcp_config["projects"][absolute_project_path]["mcpServers"]["mcp-ticketer"]
|
|
314
|
+
|
|
315
|
+
# Clean up empty structures
|
|
316
|
+
if not mcp_config["projects"][absolute_project_path]["mcpServers"]:
|
|
317
|
+
del mcp_config["projects"][absolute_project_path]["mcpServers"]
|
|
318
|
+
if not mcp_config["projects"][absolute_project_path]:
|
|
319
|
+
del mcp_config["projects"][absolute_project_path]
|
|
320
|
+
|
|
321
|
+
# Also remove from legacy location if it exists
|
|
322
|
+
legacy_config_path = Path.cwd() / ".claude" / "mcp.local.json"
|
|
323
|
+
if legacy_config_path.exists():
|
|
324
|
+
try:
|
|
325
|
+
legacy_config = load_claude_mcp_config(
|
|
326
|
+
legacy_config_path, is_claude_code=False
|
|
327
|
+
)
|
|
328
|
+
if "mcp-ticketer" in legacy_config.get("mcpServers", {}):
|
|
329
|
+
del legacy_config["mcpServers"]["mcp-ticketer"]
|
|
330
|
+
save_claude_mcp_config(legacy_config_path, legacy_config)
|
|
331
|
+
console.print("[dim]✓ Removed from legacy config as well[/dim]")
|
|
332
|
+
except Exception as e:
|
|
333
|
+
console.print(f"[dim]⚠ Could not remove from legacy config: {e}[/dim]")
|
|
334
|
+
else:
|
|
335
|
+
# Remove from Claude Desktop structure
|
|
336
|
+
del mcp_config["mcpServers"]["mcp-ticketer"]
|
|
279
337
|
|
|
280
338
|
# Step 7: Save updated configuration
|
|
281
339
|
try:
|
|
@@ -342,17 +400,34 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
342
400
|
raise
|
|
343
401
|
|
|
344
402
|
# Step 3: Find Claude MCP config location
|
|
345
|
-
config_type = "Claude Desktop" if global_config else "
|
|
403
|
+
config_type = "Claude Desktop" if global_config else "Claude Code"
|
|
346
404
|
console.print(f"\n[cyan]🔧 Configuring {config_type} MCP...[/cyan]")
|
|
347
405
|
|
|
348
406
|
mcp_config_path = find_claude_mcp_config(global_config)
|
|
349
|
-
console.print(f"[dim]
|
|
407
|
+
console.print(f"[dim]Primary config: {mcp_config_path}[/dim]")
|
|
408
|
+
|
|
409
|
+
# Get absolute project path for Claude Code
|
|
410
|
+
absolute_project_path = str(Path.cwd().resolve()) if not global_config else None
|
|
350
411
|
|
|
351
412
|
# Step 4: Load existing MCP configuration
|
|
352
|
-
|
|
413
|
+
is_claude_code = not global_config
|
|
414
|
+
mcp_config = load_claude_mcp_config(mcp_config_path, is_claude_code=is_claude_code)
|
|
353
415
|
|
|
354
416
|
# Step 5: Check if mcp-ticketer already configured
|
|
355
|
-
|
|
417
|
+
already_configured = False
|
|
418
|
+
if is_claude_code:
|
|
419
|
+
# Check Claude Code structure: .projects[path].mcpServers["mcp-ticketer"]
|
|
420
|
+
if absolute_project_path:
|
|
421
|
+
projects = mcp_config.get("projects", {})
|
|
422
|
+
project_config_entry = projects.get(absolute_project_path, {})
|
|
423
|
+
already_configured = "mcp-ticketer" in project_config_entry.get(
|
|
424
|
+
"mcpServers", {}
|
|
425
|
+
)
|
|
426
|
+
else:
|
|
427
|
+
# Check Claude Desktop structure: .mcpServers["mcp-ticketer"]
|
|
428
|
+
already_configured = "mcp-ticketer" in mcp_config.get("mcpServers", {})
|
|
429
|
+
|
|
430
|
+
if already_configured:
|
|
356
431
|
if not force:
|
|
357
432
|
console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
|
|
358
433
|
console.print("[dim]Use --force to overwrite existing configuration[/dim]")
|
|
@@ -361,18 +436,55 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
361
436
|
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
362
437
|
|
|
363
438
|
# Step 6: Create mcp-ticketer server config
|
|
364
|
-
project_path = str(Path.cwd()) if not global_config else None
|
|
365
439
|
server_config = create_mcp_server_config(
|
|
366
440
|
python_path=python_path,
|
|
367
441
|
project_config=project_config,
|
|
368
|
-
project_path=
|
|
442
|
+
project_path=absolute_project_path,
|
|
369
443
|
)
|
|
370
444
|
|
|
371
|
-
# Step 7: Update MCP configuration
|
|
372
|
-
if
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
445
|
+
# Step 7: Update MCP configuration based on platform
|
|
446
|
+
if is_claude_code:
|
|
447
|
+
# Claude Code: Write to ~/.claude.json with project-specific path
|
|
448
|
+
if absolute_project_path:
|
|
449
|
+
# Ensure projects structure exists
|
|
450
|
+
if "projects" not in mcp_config:
|
|
451
|
+
mcp_config["projects"] = {}
|
|
452
|
+
|
|
453
|
+
# Ensure project entry exists
|
|
454
|
+
if absolute_project_path not in mcp_config["projects"]:
|
|
455
|
+
mcp_config["projects"][absolute_project_path] = {}
|
|
456
|
+
|
|
457
|
+
# Ensure mcpServers for this project exists
|
|
458
|
+
if "mcpServers" not in mcp_config["projects"][absolute_project_path]:
|
|
459
|
+
mcp_config["projects"][absolute_project_path]["mcpServers"] = {}
|
|
460
|
+
|
|
461
|
+
# Add mcp-ticketer configuration
|
|
462
|
+
mcp_config["projects"][absolute_project_path]["mcpServers"][
|
|
463
|
+
"mcp-ticketer"
|
|
464
|
+
] = server_config
|
|
465
|
+
|
|
466
|
+
# Also write to backward-compatible location for older Claude Code versions
|
|
467
|
+
legacy_config_path = Path.cwd() / ".claude" / "mcp.local.json"
|
|
468
|
+
console.print(f"[dim]Legacy config: {legacy_config_path}[/dim]")
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
legacy_config = load_claude_mcp_config(
|
|
472
|
+
legacy_config_path, is_claude_code=False
|
|
473
|
+
)
|
|
474
|
+
if "mcpServers" not in legacy_config:
|
|
475
|
+
legacy_config["mcpServers"] = {}
|
|
476
|
+
legacy_config["mcpServers"]["mcp-ticketer"] = server_config
|
|
477
|
+
save_claude_mcp_config(legacy_config_path, legacy_config)
|
|
478
|
+
console.print("[dim]✓ Backward-compatible config also written[/dim]")
|
|
479
|
+
except Exception as e:
|
|
480
|
+
console.print(
|
|
481
|
+
f"[dim]⚠ Could not write legacy config (non-fatal): {e}[/dim]"
|
|
482
|
+
)
|
|
483
|
+
else:
|
|
484
|
+
# Claude Desktop: Write to platform-specific config
|
|
485
|
+
if "mcpServers" not in mcp_config:
|
|
486
|
+
mcp_config["mcpServers"] = {}
|
|
487
|
+
mcp_config["mcpServers"]["mcp-ticketer"] = server_config
|
|
376
488
|
|
|
377
489
|
# Step 8: Save configuration
|
|
378
490
|
try:
|
|
@@ -385,9 +497,9 @@ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> No
|
|
|
385
497
|
console.print(" Server name: mcp-ticketer")
|
|
386
498
|
console.print(f" Adapter: {adapter}")
|
|
387
499
|
console.print(f" Python: {python_path}")
|
|
388
|
-
console.print(" Command:
|
|
389
|
-
if
|
|
390
|
-
console.print(f" Project path: {
|
|
500
|
+
console.print(" Command: mcp-ticketer mcp")
|
|
501
|
+
if absolute_project_path:
|
|
502
|
+
console.print(f" Project path: {absolute_project_path}")
|
|
391
503
|
if "env" in server_config:
|
|
392
504
|
console.print(
|
|
393
505
|
f" Environment variables: {list(server_config['env'].keys())}"
|
mcp_ticketer/core/__init__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Core models and abstractions for MCP Ticketer."""
|
|
2
2
|
|
|
3
3
|
from .adapter import BaseAdapter
|
|
4
|
-
from .models import
|
|
5
|
-
TicketType)
|
|
4
|
+
from .models import Attachment, Comment, Epic, Priority, Task, TicketState, TicketType
|
|
6
5
|
from .registry import AdapterRegistry
|
|
7
6
|
|
|
8
7
|
__all__ = [
|
|
@@ -7,9 +7,9 @@ operations via the Model Context Protocol (MCP).
|
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from .main import main
|
|
10
|
+
from .main import MCPTicketServer, main
|
|
11
11
|
|
|
12
|
-
__all__ = ["main"]
|
|
12
|
+
__all__ = ["main", "MCPTicketServer"]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def __getattr__(name: str):
|
|
@@ -18,4 +18,8 @@ def __getattr__(name: str):
|
|
|
18
18
|
from .main import main
|
|
19
19
|
|
|
20
20
|
return main
|
|
21
|
+
if name == "MCPTicketServer":
|
|
22
|
+
from .main import MCPTicketServer
|
|
23
|
+
|
|
24
|
+
return MCPTicketServer
|
|
21
25
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
mcp_ticketer/mcp/server/main.py
CHANGED
|
@@ -12,20 +12,40 @@ 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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
)
|
|
29
49
|
from .response_builder import ResponseBuilder
|
|
30
50
|
|
|
31
51
|
# Load environment variables early (prioritize .env.local)
|
|
@@ -14,8 +14,8 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from mcp.server.fastmcp import FastMCP
|
|
16
16
|
|
|
17
|
-
from
|
|
18
|
-
from
|
|
17
|
+
from ...core.adapter import BaseAdapter
|
|
18
|
+
from ...core.registry import AdapterRegistry
|
|
19
19
|
|
|
20
20
|
# Initialize FastMCP server
|
|
21
21
|
mcp = FastMCP("mcp-ticketer")
|
|
@@ -17,13 +17,15 @@ 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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
)
|
|
27
29
|
|
|
28
30
|
__all__ = [
|
|
29
31
|
"ticket_tools",
|
|
@@ -9,7 +9,7 @@ This module implements tools for managing the three-level ticket hierarchy:
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from ....core.models import Epic, Priority, Task, TicketType
|
|
13
13
|
from ..server_sdk import get_adapter, mcp
|
|
14
14
|
|
|
15
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-ticketer
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.10
|
|
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,31 +1,31 @@
|
|
|
1
|
-
mcp_ticketer/__init__.py,sha256
|
|
2
|
-
mcp_ticketer/__version__.py,sha256=
|
|
1
|
+
mcp_ticketer/__init__.py,sha256=Xx4WaprO5PXhVPbYi1L6tBmwmJMkYS-lMyG4ieN6QP0,717
|
|
2
|
+
mcp_ticketer/__version__.py,sha256=oWDc7uOVe8F1WN-3EHrCf-MWxQOm4C59Xr7bB3uDYy8,1118
|
|
3
3
|
mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
mcp_ticketer/adapters/__init__.py,sha256=B5DFllWn23hkhmrLykNO5uMMSdcFuuPHXyLw_jyFzuE,358
|
|
5
|
-
mcp_ticketer/adapters/aitrackdown.py,sha256=
|
|
6
|
-
mcp_ticketer/adapters/github.py,sha256=
|
|
5
|
+
mcp_ticketer/adapters/aitrackdown.py,sha256=cm8L7amB64ZCoGvWbnpH9zxZlXu6t-l8TWiYVSwDhlg,30320
|
|
6
|
+
mcp_ticketer/adapters/github.py,sha256=QeZefKs204g2nXZ9yDb3j-HwrufbXBPoXB0zLp6bvW0,47338
|
|
7
7
|
mcp_ticketer/adapters/hybrid.py,sha256=7ocRjK7N7FdXSUCeFc23jFevfVwcPvHPIsEPXV_4o1w,18997
|
|
8
|
-
mcp_ticketer/adapters/jira.py,sha256=
|
|
8
|
+
mcp_ticketer/adapters/jira.py,sha256=9OtYAQfUdUQqEYjs61jzYpVrHu23hyP22mm-Bfn5KqA,35204
|
|
9
9
|
mcp_ticketer/adapters/linear.py,sha256=trm6ZhmlUl80sj51WAPAox_R2HQZXZ-h1QXJsrFYDCQ,587
|
|
10
10
|
mcp_ticketer/adapters/linear/__init__.py,sha256=6l0ZoR6ZHSRcytLfps2AZuk5R189Pq1GfR5-YDQt8-Q,731
|
|
11
|
-
mcp_ticketer/adapters/linear/adapter.py,sha256=
|
|
12
|
-
mcp_ticketer/adapters/linear/client.py,sha256=
|
|
13
|
-
mcp_ticketer/adapters/linear/mappers.py,sha256=
|
|
11
|
+
mcp_ticketer/adapters/linear/adapter.py,sha256=oQw_KgZWSdktfUhYEAonHUfK8z4CaiP18TC9pZqYv5Q,36251
|
|
12
|
+
mcp_ticketer/adapters/linear/client.py,sha256=0UmWlSEcRiwnSMFYKL89KMrPPL8S8uZ5V6rIY_KFOQU,8803
|
|
13
|
+
mcp_ticketer/adapters/linear/mappers.py,sha256=GN1X7bOcU-5dhDW3dAtSEGivinhFBc8hoKYot8c5tCo,9631
|
|
14
14
|
mcp_ticketer/adapters/linear/queries.py,sha256=K8y7xc3iH-q9LEUmg-0YDBhh546LAwLZDvVLkzx3yY4,7223
|
|
15
15
|
mcp_ticketer/adapters/linear/types.py,sha256=ugXtRGLljDw6yoCnEVgdFs0xLR9ErLdnv4ffh9EAUhk,7874
|
|
16
16
|
mcp_ticketer/cache/__init__.py,sha256=Xcd-cKnt-Cx7jBzvfzUUUPaGkmyXFi5XUFWw3Z4b7d4,138
|
|
17
17
|
mcp_ticketer/cache/memory.py,sha256=rWphWZy7XTbHezC7HMRQN9ISUhYo0Pc2OTgLG30vHnI,5047
|
|
18
18
|
mcp_ticketer/cli/__init__.py,sha256=l9Q8iKmfGkTu0cssHBVqNZTsL4eAtFzOB25AED_0G6g,89
|
|
19
|
-
mcp_ticketer/cli/adapter_diagnostics.py,sha256=
|
|
20
|
-
mcp_ticketer/cli/auggie_configure.py,sha256=
|
|
21
|
-
mcp_ticketer/cli/codex_configure.py,sha256=
|
|
22
|
-
mcp_ticketer/cli/configure.py,sha256=
|
|
19
|
+
mcp_ticketer/cli/adapter_diagnostics.py,sha256=pQDdtDgBwSW04wdFEPVzwbul3KgfB9g6ZMS85qpYulY,14988
|
|
20
|
+
mcp_ticketer/cli/auggie_configure.py,sha256=47Xay__bMDGmNPWR6u9zxk4IVsSlEB02xVI40v8dhkQ,11825
|
|
21
|
+
mcp_ticketer/cli/codex_configure.py,sha256=EYgyEPoAWoc0-vLLiUSEEFkDMl0K-eOQx6p_R_iqhWI,12344
|
|
22
|
+
mcp_ticketer/cli/configure.py,sha256=T4LczvZIc2FZM-joqICL8NpjCeThAUknEhJkHsmpdc8,16158
|
|
23
23
|
mcp_ticketer/cli/diagnostics.py,sha256=s7P4vDzPpthgiBJfAOXpunlhZ62buHg_BA5W7ghQBqg,29952
|
|
24
|
-
mcp_ticketer/cli/discover.py,sha256=
|
|
25
|
-
mcp_ticketer/cli/gemini_configure.py,sha256=
|
|
24
|
+
mcp_ticketer/cli/discover.py,sha256=paG8zElIFEK6lgVD-tHWoFDTuWQ185LirFp0fVlYgB0,13148
|
|
25
|
+
mcp_ticketer/cli/gemini_configure.py,sha256=6Czg-gJA_HrzMopKCvCLfRZeqkdR77OLV5W7W4ZG9hw,12739
|
|
26
26
|
mcp_ticketer/cli/linear_commands.py,sha256=YnmqRacAfTdF0CLr4BXOxFs3uwm_hVifbGdTe6t2CfA,17273
|
|
27
|
-
mcp_ticketer/cli/main.py,sha256=
|
|
28
|
-
mcp_ticketer/cli/mcp_configure.py,sha256=
|
|
27
|
+
mcp_ticketer/cli/main.py,sha256=W36hKxZZKoojjBUYl5U5MlXLaw-qX9i0nPrmvtsndtI,89926
|
|
28
|
+
mcp_ticketer/cli/mcp_configure.py,sha256=hkr4jaZpdcqBc_K6uEGUq3z4NIUvUmdyruLLchbL3uk,19524
|
|
29
29
|
mcp_ticketer/cli/migrate_config.py,sha256=MYsr_C5ZxsGg0P13etWTWNrJ_lc6ElRCkzfQADYr3DM,5956
|
|
30
30
|
mcp_ticketer/cli/platform_commands.py,sha256=pTLRT2wot8dAmy1-roJWWOT0Cxu7j-06BlWDnZ9a4jY,3624
|
|
31
31
|
mcp_ticketer/cli/python_detection.py,sha256=qmhi0CIDKH_AUVGkJ9jyY1zBpx1cwiQNv0vnEvMYDFQ,4272
|
|
@@ -33,7 +33,7 @@ mcp_ticketer/cli/queue_commands.py,sha256=mm-3H6jmkUGJDyU_E46o9iRpek8tvFCm77F19O
|
|
|
33
33
|
mcp_ticketer/cli/simple_health.py,sha256=GlOLRRFoifCna995NoHuKpb3xmFkLi2b3Ke1hyeDvq4,7950
|
|
34
34
|
mcp_ticketer/cli/ticket_commands.py,sha256=zmtePGhZzhw_r-0xWQMzXSJMo684PlzEN5LYfcR3_dw,26589
|
|
35
35
|
mcp_ticketer/cli/utils.py,sha256=JU2CtdA8pLaH0R5Xpb_Z4-W-PvQfzhbXl9VR04vzMSE,22992
|
|
36
|
-
mcp_ticketer/core/__init__.py,sha256
|
|
36
|
+
mcp_ticketer/core/__init__.py,sha256=aOnzv5YBfxvd6HvZeEnXeajizde53TcFaMWL3PJh5lY,379
|
|
37
37
|
mcp_ticketer/core/adapter.py,sha256=K8bQ9fQRN6Xjaxgl24f6X5u0PVmj9WFof_MOKapDHbU,12136
|
|
38
38
|
mcp_ticketer/core/config.py,sha256=q95coT6zDAVbN6eFFe6HUHXyqBm669z8g8nKWNfL8fs,19251
|
|
39
39
|
mcp_ticketer/core/env_discovery.py,sha256=iZnmtv1RWnmjjih0iFEInOoK9CU9_oNpfNgmiToQ5wk,19934
|
|
@@ -46,21 +46,21 @@ mcp_ticketer/core/project_config.py,sha256=DmLekuMuOgNtzg-olOU4Utv00DdCH1-CXuooo
|
|
|
46
46
|
mcp_ticketer/core/registry.py,sha256=gBeXcZ3grHl9gYFbyRp-C4IM7SD_KGTeXT_1jG8XrCc,3470
|
|
47
47
|
mcp_ticketer/mcp/__init__.py,sha256=XscFBOFeIxBNgA_8yPLl6c75-YJCcIN2I9eoLEGsBdM,864
|
|
48
48
|
mcp_ticketer/mcp/__main__.py,sha256=Fo_5KJOFako2gi1Z1kk5zEt2sGJW6BX6oXlYp7twYTs,1713
|
|
49
|
-
mcp_ticketer/mcp/server/__init__.py,sha256
|
|
49
|
+
mcp_ticketer/mcp/server/__init__.py,sha256=-mBqZiNloAlle9ds7jZXG2wJIRnEV0ALSQfdrMK-lVo,653
|
|
50
50
|
mcp_ticketer/mcp/server/__main__.py,sha256=xE1n94M5n2tKyT6qFIOXaqRXX7L--SxmCglKUPcljG0,1711
|
|
51
51
|
mcp_ticketer/mcp/server/constants.py,sha256=EBGsJtBPaTCvAm5rOMknckrXActrNIls7lRklnh1L4s,2072
|
|
52
52
|
mcp_ticketer/mcp/server/dto.py,sha256=FR_OBtaxrno8AsHynPwUUW715iAoaBkrr7Ud8HZTQW8,7233
|
|
53
|
-
mcp_ticketer/mcp/server/main.py,sha256=
|
|
53
|
+
mcp_ticketer/mcp/server/main.py,sha256=ddYl7u2vUwnuPRg-QP0sZS_Utils9nx-PEdxcIvqfy4,48834
|
|
54
54
|
mcp_ticketer/mcp/server/response_builder.py,sha256=DUfe1e0CcXPlepLq-cGH6b_THqoZEynYfVKkZEeLe0M,4933
|
|
55
|
-
mcp_ticketer/mcp/server/server_sdk.py,sha256=
|
|
56
|
-
mcp_ticketer/mcp/server/tools/__init__.py,sha256=
|
|
57
|
-
mcp_ticketer/mcp/server/tools/attachment_tools.py,sha256=
|
|
58
|
-
mcp_ticketer/mcp/server/tools/bulk_tools.py,sha256=
|
|
59
|
-
mcp_ticketer/mcp/server/tools/comment_tools.py,sha256=
|
|
60
|
-
mcp_ticketer/mcp/server/tools/hierarchy_tools.py,sha256=
|
|
61
|
-
mcp_ticketer/mcp/server/tools/pr_tools.py,sha256=
|
|
62
|
-
mcp_ticketer/mcp/server/tools/search_tools.py,sha256=
|
|
63
|
-
mcp_ticketer/mcp/server/tools/ticket_tools.py,sha256=
|
|
55
|
+
mcp_ticketer/mcp/server/server_sdk.py,sha256=KGpMvvJAckKl5ReLsyYvNJCM44nZRgY-V7dkgENTFX0,2554
|
|
56
|
+
mcp_ticketer/mcp/server/tools/__init__.py,sha256=6miiC2Cru8u2TCrm9RYF1jxd7vu9SI7BPLUjtzwOxT8,1056
|
|
57
|
+
mcp_ticketer/mcp/server/tools/attachment_tools.py,sha256=c2ySBs0Zz1M_zbFszbsD0S8lJT8dnCzTy-mSsKEDPV0,5674
|
|
58
|
+
mcp_ticketer/mcp/server/tools/bulk_tools.py,sha256=H5RZylDsrYuTCDF1afUcVcm-Yv3cu052JwwBPW9-4YA,9200
|
|
59
|
+
mcp_ticketer/mcp/server/tools/comment_tools.py,sha256=XXPS4TCIDaxnoITdG4B38iOX9LOElqCZFus4FnULJeg,2702
|
|
60
|
+
mcp_ticketer/mcp/server/tools/hierarchy_tools.py,sha256=KcsZ4NAd00vOm-PZziAtAanOX8CB7baGkWNoIKDIako,10502
|
|
61
|
+
mcp_ticketer/mcp/server/tools/pr_tools.py,sha256=PoB5YABYIlrABw5-RPA8bTh8uHH3hituslV9ib9xUUU,4540
|
|
62
|
+
mcp_ticketer/mcp/server/tools/search_tools.py,sha256=60OwDXN9bLQKbe9apLmJDyM0TcTPCAv9Vp2X2gPBQX4,6952
|
|
63
|
+
mcp_ticketer/mcp/server/tools/ticket_tools.py,sha256=DrNxx8gKvmFOKpxR80hcmtXK1tZOmemBgAABlPAPl6E,8122
|
|
64
64
|
mcp_ticketer/queue/__init__.py,sha256=ut4EkrXng9RJlFPZRKUa3elhHo3MFGhshBXquZ16vcs,278
|
|
65
65
|
mcp_ticketer/queue/__main__.py,sha256=gc_tE9NUdK07OJfTZuD4t6KeBD_vxFQIhknGTQUG_jk,109
|
|
66
66
|
mcp_ticketer/queue/health_monitor.py,sha256=TDmPnYuZJb3yHNJlGFvE9UU-LfsKTrC4Vapyvdb3fso,12226
|
|
@@ -69,9 +69,9 @@ mcp_ticketer/queue/queue.py,sha256=q9HDXgnlwspamMJIeu9og7qONttXHmFZHPSaMtJDPlw,1
|
|
|
69
69
|
mcp_ticketer/queue/run_worker.py,sha256=WhoeamL8LKZ66TM8W1PkMPwjF2w_EDFMP-mevs6C1TM,1019
|
|
70
70
|
mcp_ticketer/queue/ticket_registry.py,sha256=xVg3i7Eb5rtQY-4bbw3zYY1h-C6jF1t1NZEGhObzD8g,15491
|
|
71
71
|
mcp_ticketer/queue/worker.py,sha256=AJHtpJZEhGoPuCDPXSMsn9DeODelo5f__0C__3zoN08,20970
|
|
72
|
-
mcp_ticketer-0.4.
|
|
73
|
-
mcp_ticketer-0.4.
|
|
74
|
-
mcp_ticketer-0.4.
|
|
75
|
-
mcp_ticketer-0.4.
|
|
76
|
-
mcp_ticketer-0.4.
|
|
77
|
-
mcp_ticketer-0.4.
|
|
72
|
+
mcp_ticketer-0.4.10.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
|
|
73
|
+
mcp_ticketer-0.4.10.dist-info/METADATA,sha256=FhrWkaotqhB44HB3KyZzkRs3NGRReEBnvNAd2MexAu8,16021
|
|
74
|
+
mcp_ticketer-0.4.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
mcp_ticketer-0.4.10.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
|
|
76
|
+
mcp_ticketer-0.4.10.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
|
|
77
|
+
mcp_ticketer-0.4.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|