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.

Files changed (34) hide show
  1. mcp_ticketer/__init__.py +12 -3
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +10 -3
  4. mcp_ticketer/adapters/github.py +1 -2
  5. mcp_ticketer/adapters/jira.py +1 -2
  6. mcp_ticketer/adapters/linear/adapter.py +129 -10
  7. mcp_ticketer/adapters/linear/client.py +1 -2
  8. mcp_ticketer/adapters/linear/mappers.py +1 -2
  9. mcp_ticketer/cli/adapter_diagnostics.py +2 -4
  10. mcp_ticketer/cli/auggie_configure.py +9 -4
  11. mcp_ticketer/cli/codex_configure.py +9 -5
  12. mcp_ticketer/cli/configure.py +9 -3
  13. mcp_ticketer/cli/discover.py +6 -2
  14. mcp_ticketer/cli/gemini_configure.py +8 -4
  15. mcp_ticketer/cli/main.py +23 -4
  16. mcp_ticketer/cli/mcp_configure.py +144 -32
  17. mcp_ticketer/core/__init__.py +1 -2
  18. mcp_ticketer/mcp/server/__init__.py +6 -2
  19. mcp_ticketer/mcp/server/main.py +34 -14
  20. mcp_ticketer/mcp/server/server_sdk.py +2 -2
  21. mcp_ticketer/mcp/server/tools/__init__.py +9 -7
  22. mcp_ticketer/mcp/server/tools/attachment_tools.py +1 -1
  23. mcp_ticketer/mcp/server/tools/bulk_tools.py +1 -1
  24. mcp_ticketer/mcp/server/tools/comment_tools.py +1 -1
  25. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +1 -1
  26. mcp_ticketer/mcp/server/tools/pr_tools.py +1 -1
  27. mcp_ticketer/mcp/server/tools/search_tools.py +1 -1
  28. mcp_ticketer/mcp/server/tools/ticket_tools.py +1 -1
  29. {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/METADATA +1 -1
  30. {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/RECORD +34 -34
  31. {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/WHEEL +0 -0
  32. {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/entry_points.txt +0 -0
  33. {mcp_ticketer-0.4.8.dist-info → mcp_ticketer-0.4.10.dist-info}/licenses/LICENSE +0 -0
  34. {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 (__author__, __author_email__, __copyright__,
4
- __description__, __license__, __title__, __version__,
5
- __version_info__, get_user_agent, get_version)
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__",
@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.4.8"
3
+ __version__ = "0.4.10"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -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:
@@ -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 (Comment, Epic, Priority, SearchQuery, Task,
13
- TicketState)
12
+ from ..core.models import Comment, Epic, Priority, SearchQuery, Task, TicketState
14
13
  from ..core.registry import AdapterRegistry
15
14
 
16
15
 
@@ -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 (Comment, Epic, Priority, SearchQuery, Task,
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 (build_linear_issue_input,
24
- build_linear_issue_update_input,
25
- map_linear_comment_to_comment, map_linear_issue_to_task,
26
- map_linear_project_to_epic)
27
- from .queries import (ALL_FRAGMENTS, CREATE_ISSUE_MUTATION, LIST_ISSUES_QUERY,
28
- SEARCH_ISSUES_QUERY, UPDATE_ISSUE_MUTATION,
29
- WORKFLOW_STATES_QUERY)
30
- from .types import (LinearStateMapping, build_issue_filter,
31
- get_linear_priority, get_linear_state_type)
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 ("LINEAR_API_KEY", "API_KEY"):
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 (AdapterError, AuthenticationError,
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 (extract_linear_metadata, get_universal_priority,
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 module invocation pattern: python -m mcp_ticketer.mcp.server
138
- args = ["-m", "mcp_ticketer.mcp.server"]
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": python_path,
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: python -m mcp_ticketer.mcp.server")
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 module invocation pattern: python -m mcp_ticketer.mcp.server
140
- args = ["-m", "mcp_ticketer.mcp.server"]
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": python_path,
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: python -m mcp_ticketer.mcp.server")
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:
@@ -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 (AdapterConfig, AdapterType, ConfigResolver,
12
- ConfigValidator, HybridConfig, SyncStrategy,
13
- TicketerConfig)
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
 
@@ -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 (AdapterConfig, ConfigResolver,
10
- ConfigValidator, TicketerConfig)
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 module invocation pattern: python -m mcp_ticketer.mcp.server
140
- args = ["-m", "mcp_ticketer.mcp.server"]
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": python_path,
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: python -m mcp_ticketer.mcp.server")
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 (configure_wizard, set_adapter_config,
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
- # Project-level configuration for Claude Code
111
- config_path = Path.cwd() / ".claude" / "settings.local.json"
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
- with open(config_path) as f:
128
- return json.load(f)
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 (Claude Code uses mcpServers key)
131
- return {"mcpServers": {}}
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
- # Ensure python3 is used (not python)
165
- if python_path.endswith("/python"):
166
- python_path = python_path.replace("/python", "/python3")
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 module invocation pattern: python -m mcp_ticketer.mcp.server
169
- args = ["-m", "mcp_ticketer.mcp.server"]
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": python_path,
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 "project-level"
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]Config location: {mcp_config_path}[/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
- mcp_config = load_claude_mcp_config(mcp_config_path)
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
- if "mcp-ticketer" not in mcp_config.get("mcpServers", {}):
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
- del mcp_config["mcpServers"]["mcp-ticketer"]
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 "project-level"
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]Config location: {mcp_config_path}[/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
- mcp_config = load_claude_mcp_config(mcp_config_path)
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
- if "mcp-ticketer" in mcp_config.get("mcpServers", {}):
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=project_path,
442
+ project_path=absolute_project_path,
369
443
  )
370
444
 
371
- # Step 7: Update MCP configuration
372
- if "mcpServers" not in mcp_config:
373
- mcp_config["mcpServers"] = {}
374
-
375
- mcp_config["mcpServers"]["mcp-ticketer"] = server_config
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: python -m mcp_ticketer.mcp.server")
389
- if project_path:
390
- console.print(f" Project path: {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())}"
@@ -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 (Attachment, Comment, Epic, Priority, Task, TicketState,
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}")
@@ -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 (Comment, Epic, Priority, SearchQuery, Task,
16
- TicketState)
17
- from .constants import (DEFAULT_BASE_PATH, DEFAULT_LIMIT, DEFAULT_MAX_DEPTH,
18
- DEFAULT_OFFSET, ERROR_INTERNAL, ERROR_METHOD_NOT_FOUND,
19
- ERROR_PARSE, JSONRPC_VERSION, MCP_PROTOCOL_VERSION,
20
- MSG_EPIC_NOT_FOUND, MSG_INTERNAL_ERROR,
21
- MSG_MISSING_TICKET_ID, MSG_MISSING_TITLE,
22
- MSG_NO_TICKETS_PROVIDED, MSG_NO_UPDATES_PROVIDED,
23
- MSG_TICKET_NOT_FOUND, MSG_TRANSITION_FAILED,
24
- MSG_UNKNOWN_METHOD, MSG_UNKNOWN_OPERATION,
25
- MSG_UPDATE_FAILED, SERVER_NAME, SERVER_VERSION,
26
- STATUS_COMPLETED, STATUS_ERROR)
27
- from .dto import (CreateEpicRequest, CreateIssueRequest, CreateTaskRequest,
28
- CreateTicketRequest, ReadTicketRequest)
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 ..core.adapter import BaseAdapter
18
- from ..core.registry import AdapterRegistry
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 attachment_tools # noqa: F401
21
- from . import bulk_tools # noqa: F401
22
- from . import comment_tools # noqa: F401
23
- from . import hierarchy_tools # noqa: F401
24
- from . import pr_tools # noqa: F401
25
- from . import search_tools # noqa: F401
26
- from . import ticket_tools # noqa: F401
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",
@@ -7,7 +7,7 @@ available in all adapters.
7
7
 
8
8
  from typing import Any
9
9
 
10
- from ...core.models import Comment
10
+ from ....core.models import Comment
11
11
  from ..server_sdk import get_adapter, mcp
12
12
 
13
13
 
@@ -6,7 +6,7 @@ efficiency when working with multiple items.
6
6
 
7
7
  from typing import Any
8
8
 
9
- from ...core.models import Priority, Task, TicketState, TicketType
9
+ from ....core.models import Priority, Task, TicketState, TicketType
10
10
  from ..server_sdk import get_adapter, mcp
11
11
 
12
12
 
@@ -5,7 +5,7 @@ This module implements tools for adding and retrieving comments on tickets.
5
5
 
6
6
  from typing import Any
7
7
 
8
- from ...core.models import Comment
8
+ from ....core.models import Comment
9
9
  from ..server_sdk import get_adapter, mcp
10
10
 
11
11
 
@@ -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 ...core.models import Epic, Priority, Task, TicketType
12
+ from ....core.models import Epic, Priority, Task, TicketType
13
13
  from ..server_sdk import get_adapter, mcp
14
14
 
15
15
 
@@ -129,7 +129,7 @@ async def ticket_link_pr(
129
129
  }
130
130
 
131
131
  # Fallback: Add PR link as comment
132
- from ...core.models import Comment
132
+ from ....core.models import Comment
133
133
 
134
134
  comment = Comment(
135
135
  ticket_id=ticket_id,
@@ -6,7 +6,7 @@ various filters and criteria.
6
6
 
7
7
  from typing import Any
8
8
 
9
- from ...core.models import Priority, SearchQuery, TicketState
9
+ from ....core.models import Priority, SearchQuery, TicketState
10
10
  from ..server_sdk import get_adapter, mcp
11
11
 
12
12
 
@@ -6,7 +6,7 @@ operations for tickets using the FastMCP SDK.
6
6
 
7
7
  from typing import Any
8
8
 
9
- from ...core.models import Priority, Task, TicketState
9
+ from ....core.models import Priority, Task, TicketState
10
10
  from ..server_sdk import get_adapter, mcp
11
11
 
12
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.4.8
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=-8ko5W0ZFF5rWaiSCyTAiK6XLomlwnAWGfG9ntQHV5o,506
2
- mcp_ticketer/__version__.py,sha256=FMOVq48ReNbDNx45K2GXmJbg-G-9wct72RfE7DidR60,1117
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=hLjmc1RtWjsUEPZD-v_gQ1BGyTMe1tcuArKFknsMDtY,30316
6
- mcp_ticketer/adapters/github.py,sha256=tsMPeF-qwkul0WEPrXx9T-DjTx0O--aAIePqJ3hqCt8,47367
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=VR1Xnlhp6EI1XmQbinFn9lch3G7MFUvDYnPg4kv1lUk,35233
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=kx1XQGhHWpco_QyEEw08ZKMT40A_GVi9KfGpIpyVD2k,31831
12
- mcp_ticketer/adapters/linear/client.py,sha256=KcLDTVOgUnOaSJS4domu0dmkslBipNfG5pPoVJ3M0a0,8837
13
- mcp_ticketer/adapters/linear/mappers.py,sha256=76UbR2GvkVuAcT4T7EAQSFfagcGOQcLKElwVAXsFQ10,9653
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=k7RBEbDx5_hiv8IqcJIDjvktmlDFO8mOqRW8JkZqUZ0,15040
20
- mcp_ticketer/cli/auggie_configure.py,sha256=BA31HvOXljPqi3QMKt5eI5jYUWCnnH00sDqwR3y2nSY,11701
21
- mcp_ticketer/cli/codex_configure.py,sha256=LLGzsFjCNO3irtabSazCpsZ5eUmG6eAjCNn6B5M4aHQ,12249
22
- mcp_ticketer/cli/configure.py,sha256=nO1G7E0y0GN5AcCRhBnU23j7kTLxF0GkQukLIYyB_0E,16197
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=CcQo5w2ISYMM1VDp34SHgyCMNazZ8w6DYkzCk49OU3Q,13164
25
- mcp_ticketer/cli/gemini_configure.py,sha256=GXbQkfZGsmx_2cJKxH_5z3JdcwpMopF0duI_TzbKip8,12644
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=p4_Ryjtaz2pmvGJPkSDc4O5rS11EAdL3LTXjF8uC84o,89290
28
- mcp_ticketer/cli/mcp_configure.py,sha256=dgApMLBApLxU0fvFOyDBkX69GOGVNTXhAKM-hZGw5v8,14142
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=-G2Pn9sXdC1XxdFXfxTWV5QS3vC0HvqCGxPM1MtC6lE,402
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=2ll9kLEEiKP_NtvJghHVz6TG2p7xwIUBRqlxZKWRCvU,509
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=FYkZ-sg3eKJMu8ICmmRBGDgfqV6oXY2Fv1l2sMH4gUc,48976
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=orgOTrvrtBn2BeMJt5HWeodvCU9sWH4o5pQIZ_geXao,2552
56
- mcp_ticketer/mcp/server/tools/__init__.py,sha256=uXViWZoVOkU3MqvnNvuMRMD4BNcbruwvMQstFr_cLh4,1101
57
- mcp_ticketer/mcp/server/tools/attachment_tools.py,sha256=hDeQV1rkSjZwT__FKLkR04FH_AsAF6QysyeP_CKxdiU,5673
58
- mcp_ticketer/mcp/server/tools/bulk_tools.py,sha256=UWY1T5DnWhpBJnqMdLMu7Feen515UAlcn_hOjTmDncc,9199
59
- mcp_ticketer/mcp/server/tools/comment_tools.py,sha256=fVJZeSg_rr6tVoVAKF8rv5nUooqg5d4S44s1mKFTYM0,2701
60
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py,sha256=08KxOawUKGZK3kYPZ5w9mRk-lVaSFxQvaJJ0tmMuBLQ,10501
61
- mcp_ticketer/mcp/server/tools/pr_tools.py,sha256=SnKJRsvkmmr2eh1qaIqxC0PHD6rVXKg6mkk9Skdpc0Y,4539
62
- mcp_ticketer/mcp/server/tools/search_tools.py,sha256=6qwSBRkGx-zO0rxebeQqMcBj006ZovAgfyLGfw3kxig,6951
63
- mcp_ticketer/mcp/server/tools/ticket_tools.py,sha256=KYMl2h-nf7hZq-kOi1z4H0TEj7MzMklOsRIl4HaP8WQ,8121
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.8.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
73
- mcp_ticketer-0.4.8.dist-info/METADATA,sha256=FFZaF8DklB3q8kWIaM1BJ-drrjbi6vzh1RM5D8SxZtU,16020
74
- mcp_ticketer-0.4.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
- mcp_ticketer-0.4.8.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
76
- mcp_ticketer-0.4.8.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
77
- mcp_ticketer-0.4.8.dist-info/RECORD,,
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,,