mcp-ticketer 0.1.14__py3-none-any.whl → 0.1.16__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.

@@ -1,6 +1,6 @@
1
1
  """Version information for mcp-ticketer package."""
2
2
 
3
- __version__ = "0.1.14"
3
+ __version__ = "0.1.16"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
mcp_ticketer/cli/main.py CHANGED
@@ -71,10 +71,34 @@ class AdapterType(str, Enum):
71
71
 
72
72
 
73
73
  def load_config() -> dict:
74
- """Load configuration from file."""
74
+ """Load configuration from file.
75
+
76
+ Resolution order:
77
+ 1. Project-specific config (.mcp-ticketer/config.json in cwd)
78
+ 2. Global config (~/.mcp-ticketer/config.json)
79
+
80
+ Returns:
81
+ Configuration dictionary
82
+ """
83
+ # Check project-specific config first
84
+ project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
85
+ if project_config.exists():
86
+ try:
87
+ with open(project_config, "r") as f:
88
+ return json.load(f)
89
+ except (json.JSONDecodeError, IOError) as e:
90
+ console.print(f"[yellow]Warning: Could not load project config: {e}[/yellow]")
91
+ # Fall through to global config
92
+
93
+ # Fall back to global config
75
94
  if CONFIG_FILE.exists():
76
- with open(CONFIG_FILE, "r") as f:
77
- return json.load(f)
95
+ try:
96
+ with open(CONFIG_FILE, "r") as f:
97
+ return json.load(f)
98
+ except (json.JSONDecodeError, IOError) as e:
99
+ console.print(f"[yellow]Warning: Could not load global config: {e}[/yellow]")
100
+
101
+ # Default fallback
78
102
  return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
79
103
 
80
104
 
@@ -1135,7 +1159,7 @@ def check(
1135
1159
 
1136
1160
 
1137
1161
  @app.command()
1138
- def mcp(
1162
+ def serve(
1139
1163
  adapter: Optional[AdapterType] = typer.Option(
1140
1164
  None,
1141
1165
  "--adapter",
@@ -1148,10 +1172,22 @@ def mcp(
1148
1172
  help="Base path for AITrackdown adapter"
1149
1173
  ),
1150
1174
  ):
1151
- """Start MCP server for JSON-RPC communication over stdio."""
1175
+ """Start MCP server for JSON-RPC communication over stdio.
1176
+
1177
+ This command is used by Claude Code/Desktop when connecting to the MCP server.
1178
+ You typically don't need to run this manually - use 'mcp-ticketer mcp' to configure.
1179
+
1180
+ Configuration Resolution:
1181
+ - When MCP server starts, it uses the current working directory (cwd)
1182
+ - The cwd is set by Claude Code/Desktop from the 'cwd' field in .mcp/config.json
1183
+ - Configuration is loaded with this priority:
1184
+ 1. Project-specific: .mcp-ticketer/config.json in cwd
1185
+ 2. Global: ~/.mcp-ticketer/config.json
1186
+ 3. Default: aitrackdown adapter with .aitrackdown base path
1187
+ """
1152
1188
  from ..mcp.server import MCPTicketServer
1153
1189
 
1154
- # Load configuration
1190
+ # Load configuration (respects project-specific config in cwd)
1155
1191
  config = load_config()
1156
1192
 
1157
1193
  # Determine adapter type
@@ -1194,6 +1230,38 @@ def mcp(
1194
1230
  sys.exit(1)
1195
1231
 
1196
1232
 
1233
+ @app.command()
1234
+ def mcp(
1235
+ global_config: bool = typer.Option(
1236
+ False,
1237
+ "--global",
1238
+ "-g",
1239
+ help="Configure Claude Desktop instead of project-level"
1240
+ ),
1241
+ force: bool = typer.Option(
1242
+ False,
1243
+ "--force",
1244
+ "-f",
1245
+ help="Overwrite existing configuration"
1246
+ ),
1247
+ ):
1248
+ """Configure Claude Code to use mcp-ticketer MCP server.
1249
+
1250
+ Reads configuration from .mcp-ticketer/config.json and updates
1251
+ Claude Code's MCP settings accordingly.
1252
+
1253
+ By default, configures project-level (.mcp/config.json).
1254
+ Use --global to configure Claude Desktop instead.
1255
+ """
1256
+ from ..cli.mcp_configure import configure_claude_mcp
1257
+
1258
+ try:
1259
+ configure_claude_mcp(global_config=global_config, force=force)
1260
+ except Exception as e:
1261
+ console.print(f"[red]✗ Configuration failed:[/red] {e}")
1262
+ raise typer.Exit(1)
1263
+
1264
+
1197
1265
  def main():
1198
1266
  """Main entry point."""
1199
1267
  app()
@@ -0,0 +1,285 @@
1
+ """MCP configuration for Claude Code integration."""
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from rich.console import Console
11
+
12
+ console = Console()
13
+
14
+
15
+ def find_mcp_ticketer_binary() -> str:
16
+ """Find the mcp-ticketer binary path.
17
+
18
+ Returns:
19
+ Path to mcp-ticketer binary
20
+
21
+ Raises:
22
+ FileNotFoundError: If binary not found
23
+ """
24
+ # Check if running from development environment
25
+ import mcp_ticketer
26
+ package_path = Path(mcp_ticketer.__file__).parent.parent.parent
27
+
28
+ # Check for virtual environment bin
29
+ possible_paths = [
30
+ # Development paths
31
+ package_path / "venv" / "bin" / "mcp-ticketer",
32
+ package_path / ".venv" / "bin" / "mcp-ticketer",
33
+ package_path / "test_venv" / "bin" / "mcp-ticketer",
34
+ # System installation
35
+ Path.home() / ".local" / "bin" / "mcp-ticketer",
36
+ # pipx installation
37
+ Path.home() / ".local" / "pipx" / "venvs" / "mcp-ticketer" / "bin" / "mcp-ticketer",
38
+ ]
39
+
40
+ # Check PATH
41
+ which_result = shutil.which("mcp-ticketer")
42
+ if which_result:
43
+ return which_result
44
+
45
+ # Check possible paths
46
+ for path in possible_paths:
47
+ if path.exists():
48
+ return str(path.resolve())
49
+
50
+ raise FileNotFoundError(
51
+ "Could not find mcp-ticketer binary. Please ensure mcp-ticketer is installed.\n"
52
+ "Install with: pip install mcp-ticketer"
53
+ )
54
+
55
+
56
+ def load_project_config() -> dict:
57
+ """Load mcp-ticketer project configuration.
58
+
59
+ Returns:
60
+ Project configuration dict
61
+
62
+ Raises:
63
+ FileNotFoundError: If config not found
64
+ ValueError: If config is invalid
65
+ """
66
+ # Check for project-specific config first
67
+ project_config_path = Path.cwd() / ".mcp-ticketer" / "config.json"
68
+
69
+ if not project_config_path.exists():
70
+ # Check global config
71
+ global_config_path = Path.home() / ".mcp-ticketer" / "config.json"
72
+ if global_config_path.exists():
73
+ project_config_path = global_config_path
74
+ else:
75
+ raise FileNotFoundError(
76
+ "No mcp-ticketer configuration found.\n"
77
+ "Run 'mcp-ticketer init' to create configuration."
78
+ )
79
+
80
+ with open(project_config_path, "r") as f:
81
+ config = json.load(f)
82
+
83
+ # Validate config
84
+ if "default_adapter" not in config:
85
+ raise ValueError("Invalid config: missing 'default_adapter'")
86
+
87
+ return config
88
+
89
+
90
+ def find_claude_mcp_config(global_config: bool = False) -> Path:
91
+ """Find or create Claude Code MCP configuration file.
92
+
93
+ Args:
94
+ global_config: If True, use Claude Desktop config instead of project-level
95
+
96
+ Returns:
97
+ Path to MCP configuration file
98
+ """
99
+ if global_config:
100
+ # Claude Desktop configuration
101
+ if sys.platform == "darwin": # macOS
102
+ config_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
103
+ elif sys.platform == "win32": # Windows
104
+ config_path = Path(os.environ.get("APPDATA", "")) / "Claude" / "claude_desktop_config.json"
105
+ else: # Linux
106
+ config_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
107
+ else:
108
+ # Project-level configuration
109
+ config_path = Path.cwd() / ".mcp" / "config.json"
110
+
111
+ return config_path
112
+
113
+
114
+ def load_claude_mcp_config(config_path: Path) -> dict:
115
+ """Load existing Claude MCP configuration or return empty structure.
116
+
117
+ Args:
118
+ config_path: Path to MCP config file
119
+
120
+ Returns:
121
+ MCP configuration dict
122
+ """
123
+ if config_path.exists():
124
+ with open(config_path, "r") as f:
125
+ return json.load(f)
126
+
127
+ # Return empty structure
128
+ return {"mcpServers": {}}
129
+
130
+
131
+ def save_claude_mcp_config(config_path: Path, config: dict) -> None:
132
+ """Save Claude MCP configuration to file.
133
+
134
+ Args:
135
+ config_path: Path to MCP config file
136
+ config: Configuration to save
137
+ """
138
+ # Ensure directory exists
139
+ config_path.parent.mkdir(parents=True, exist_ok=True)
140
+
141
+ # Write with formatting
142
+ with open(config_path, "w") as f:
143
+ json.dump(config, f, indent=2)
144
+
145
+
146
+ def create_mcp_server_config(
147
+ binary_path: str,
148
+ project_config: dict,
149
+ cwd: Optional[str] = None
150
+ ) -> dict:
151
+ """Create MCP server configuration for mcp-ticketer.
152
+
153
+ Args:
154
+ binary_path: Path to mcp-ticketer binary
155
+ project_config: Project configuration from .mcp-ticketer/config.json
156
+ cwd: Working directory for server (optional)
157
+
158
+ Returns:
159
+ MCP server configuration dict
160
+ """
161
+ config = {
162
+ "command": binary_path,
163
+ "args": ["serve"], # Use 'serve' command to start MCP server
164
+ }
165
+
166
+ # Add working directory if provided
167
+ if cwd:
168
+ config["cwd"] = cwd
169
+
170
+ # Add environment variables based on adapter
171
+ adapter = project_config.get("default_adapter", "aitrackdown")
172
+ adapters_config = project_config.get("adapters", {})
173
+ adapter_config = adapters_config.get(adapter, {})
174
+
175
+ env_vars = {}
176
+
177
+ # Add adapter-specific environment variables
178
+ if adapter == "linear" and "api_key" in adapter_config:
179
+ env_vars["LINEAR_API_KEY"] = adapter_config["api_key"]
180
+ elif adapter == "github" and "token" in adapter_config:
181
+ env_vars["GITHUB_TOKEN"] = adapter_config["token"]
182
+ elif adapter == "jira":
183
+ if "api_token" in adapter_config:
184
+ env_vars["JIRA_API_TOKEN"] = adapter_config["api_token"]
185
+ if "email" in adapter_config:
186
+ env_vars["JIRA_EMAIL"] = adapter_config["email"]
187
+
188
+ if env_vars:
189
+ config["env"] = env_vars
190
+
191
+ return config
192
+
193
+
194
+ def configure_claude_mcp(global_config: bool = False, force: bool = False) -> None:
195
+ """Configure Claude Code to use mcp-ticketer.
196
+
197
+ Args:
198
+ global_config: Configure Claude Desktop instead of project-level
199
+ force: Overwrite existing configuration
200
+
201
+ Raises:
202
+ FileNotFoundError: If binary or project config not found
203
+ ValueError: If configuration is invalid
204
+ """
205
+ # Step 1: Find mcp-ticketer binary
206
+ console.print("[cyan]🔍 Finding mcp-ticketer binary...[/cyan]")
207
+ try:
208
+ binary_path = find_mcp_ticketer_binary()
209
+ console.print(f"[green]✓[/green] Found: {binary_path}")
210
+ except FileNotFoundError as e:
211
+ console.print(f"[red]✗[/red] {e}")
212
+ raise
213
+
214
+ # Step 2: Load project configuration
215
+ console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
216
+ try:
217
+ project_config = load_project_config()
218
+ adapter = project_config.get("default_adapter", "aitrackdown")
219
+ console.print(f"[green]✓[/green] Adapter: {adapter}")
220
+ except (FileNotFoundError, ValueError) as e:
221
+ console.print(f"[red]✗[/red] {e}")
222
+ raise
223
+
224
+ # Step 3: Find Claude MCP config location
225
+ config_type = "Claude Desktop" if global_config else "project-level"
226
+ console.print(f"\n[cyan]🔧 Configuring {config_type} MCP...[/cyan]")
227
+
228
+ mcp_config_path = find_claude_mcp_config(global_config)
229
+ console.print(f"[dim]Config location: {mcp_config_path}[/dim]")
230
+
231
+ # Step 4: Load existing MCP configuration
232
+ mcp_config = load_claude_mcp_config(mcp_config_path)
233
+
234
+ # Step 5: Check if mcp-ticketer already configured
235
+ if "mcp-ticketer" in mcp_config.get("mcpServers", {}):
236
+ if not force:
237
+ console.print("[yellow]⚠ mcp-ticketer is already configured[/yellow]")
238
+ console.print("[dim]Use --force to overwrite existing configuration[/dim]")
239
+ return
240
+ else:
241
+ console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
242
+
243
+ # Step 6: Create mcp-ticketer server config
244
+ cwd = str(Path.cwd()) if not global_config else None
245
+ server_config = create_mcp_server_config(
246
+ binary_path=binary_path,
247
+ project_config=project_config,
248
+ cwd=cwd
249
+ )
250
+
251
+ # Step 7: Update MCP configuration
252
+ if "mcpServers" not in mcp_config:
253
+ mcp_config["mcpServers"] = {}
254
+
255
+ mcp_config["mcpServers"]["mcp-ticketer"] = server_config
256
+
257
+ # Step 8: Save configuration
258
+ try:
259
+ save_claude_mcp_config(mcp_config_path, mcp_config)
260
+ console.print(f"\n[green]✓ Successfully configured mcp-ticketer[/green]")
261
+ console.print(f"[dim]Configuration saved to: {mcp_config_path}[/dim]")
262
+
263
+ # Print configuration details
264
+ console.print("\n[bold]Configuration Details:[/bold]")
265
+ console.print(f" Server name: mcp-ticketer")
266
+ console.print(f" Adapter: {adapter}")
267
+ console.print(f" Binary: {binary_path}")
268
+ if cwd:
269
+ console.print(f" Working directory: {cwd}")
270
+ if "env" in server_config:
271
+ console.print(f" Environment variables: {list(server_config['env'].keys())}")
272
+
273
+ # Next steps
274
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
275
+ if global_config:
276
+ console.print("1. Restart Claude Desktop")
277
+ console.print("2. Open a conversation")
278
+ else:
279
+ console.print("1. Restart Claude Code")
280
+ console.print("2. Open this project in Claude Code")
281
+ console.print("3. mcp-ticketer tools will be available in the MCP menu")
282
+
283
+ except Exception as e:
284
+ console.print(f"\n[red]✗ Failed to save configuration:[/red] {e}")
285
+ raise
mcp_ticketer/cli/utils.py CHANGED
@@ -31,10 +31,34 @@ class CommonPatterns:
31
31
 
32
32
  @staticmethod
33
33
  def load_config() -> dict:
34
- """Load configuration from file."""
34
+ """Load configuration from file.
35
+
36
+ Resolution order:
37
+ 1. Project-specific config (.mcp-ticketer/config.json in cwd)
38
+ 2. Global config (~/.mcp-ticketer/config.json)
39
+
40
+ Returns:
41
+ Configuration dictionary
42
+ """
43
+ # Check project-specific config first
44
+ project_config = Path.cwd() / ".mcp-ticketer" / "config.json"
45
+ if project_config.exists():
46
+ try:
47
+ with open(project_config, "r") as f:
48
+ return json.load(f)
49
+ except (json.JSONDecodeError, IOError) as e:
50
+ console.print(f"[yellow]Warning: Could not load project config: {e}[/yellow]")
51
+ # Fall through to global config
52
+
53
+ # Fall back to global config
35
54
  if CommonPatterns.CONFIG_FILE.exists():
36
- with open(CommonPatterns.CONFIG_FILE, "r") as f:
37
- return json.load(f)
55
+ try:
56
+ with open(CommonPatterns.CONFIG_FILE, "r") as f:
57
+ return json.load(f)
58
+ except (json.JSONDecodeError, IOError) as e:
59
+ console.print(f"[yellow]Warning: Could not load global config: {e}[/yellow]")
60
+
61
+ # Default fallback
38
62
  return {"adapter": "aitrackdown", "config": {"base_path": ".aitrackdown"}}
39
63
 
40
64
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-ticketer
3
- Version: 0.1.14
3
+ Version: 0.1.16
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,5 +1,5 @@
1
1
  mcp_ticketer/__init__.py,sha256=ayPQdFr6msypD06_G96a1H0bdFCT1m1wDtv8MZBpY4I,496
2
- mcp_ticketer/__version__.py,sha256=BR9V-DmEH6HYXFWNVQkJRJ36WxlCMQn4UvkIRrMi6_c,1115
2
+ mcp_ticketer/__version__.py,sha256=VU-W9BGb5-hfF3jvOPn-c-W8RnOMrAL8rswIa9JjVSE,1115
3
3
  mcp_ticketer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  mcp_ticketer/adapters/__init__.py,sha256=K_1egvhHb5F_7yFceUx2YzPGEoc7vX-q8dMVaS4K6gw,356
5
5
  mcp_ticketer/adapters/aitrackdown.py,sha256=gqS_N6VGLoG5itUu17ANG5SefaAITYoW-t2xL9SrY-Y,15372
@@ -12,10 +12,11 @@ mcp_ticketer/cache/memory.py,sha256=gTzv-xF7qGfiYVUjG7lnzo0ZcqgXQajMl4NAYUcaytg,
12
12
  mcp_ticketer/cli/__init__.py,sha256=YeljyLtv906TqkvRuEPhmKO-Uk0CberQ9I6kx1tx2UA,88
13
13
  mcp_ticketer/cli/configure.py,sha256=etFutvc0QpaVDMOsZiiN7wKuaT98Od1Tj9W6lsEWw5A,16351
14
14
  mcp_ticketer/cli/discover.py,sha256=putWrGcctUH8K0fOMtr9MZA9VnWoXzbtoe7mXSkDxhg,13156
15
- mcp_ticketer/cli/main.py,sha256=mhM7sYSVZ4kHhmsj5bNrK3jOTDjlWZwpSrXPAU2KjtI,38073
15
+ mcp_ticketer/cli/main.py,sha256=WGYXZ0r0Iv296qrCMZLPGHUAWagRhkAgqAm-lJSnyUY,40428
16
+ mcp_ticketer/cli/mcp_configure.py,sha256=1WbBdF25OvxfAGcjxtYa9xmBOGEPQu-wh_nkefmWjMQ,9352
16
17
  mcp_ticketer/cli/migrate_config.py,sha256=iZIstnlr9vkhiW_MlnSyJOkMi4KHQqrZ6Hz1ECD_VUk,6045
17
18
  mcp_ticketer/cli/queue_commands.py,sha256=f3pEHKZ43dBHEIoCBvdfvjfMB9_WJltps9ATwTzorY0,8160
18
- mcp_ticketer/cli/utils.py,sha256=NxsS91vKA8xZnDXKU2y0Gcyc4I_ctRyJj-wT7Xd1Q_Q,18589
19
+ mcp_ticketer/cli/utils.py,sha256=cdP-7GHtELAPZtqInUC24k_SAnRbIRkafIP3T4kMZDM,19509
19
20
  mcp_ticketer/core/__init__.py,sha256=qpCZveQMyqU2JvYG9MG_c6X35z_VoSmjWdXGcUZqqmA,348
20
21
  mcp_ticketer/core/adapter.py,sha256=6KfhceINHfDjs--RyA_rOLeM2phVD_D85S9D6s2lcuU,10075
21
22
  mcp_ticketer/core/config.py,sha256=9a2bksbcFr7KXeHSPY6KoSP5Pzt54utYPCmbM-1QKmk,13932
@@ -33,9 +34,9 @@ mcp_ticketer/queue/manager.py,sha256=79AH9oUxdBXH3lmJ3kIlFf2GQkWHL6XB6u5JqVWPq60
33
34
  mcp_ticketer/queue/queue.py,sha256=z4aivQCtsH5_OUr2OfXSfnFKzugTahNnwHw0LS3ZhZc,11549
34
35
  mcp_ticketer/queue/run_worker.py,sha256=HFoykfDpOoz8OUxWbQ2Fka_UlGrYwjPVZ-DEimGFH9o,802
35
36
  mcp_ticketer/queue/worker.py,sha256=cVjHR_kfnGKAkiUg0HuXCnbKeKNBBEuj0XZHgIuIn4k,14017
36
- mcp_ticketer-0.1.14.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
37
- mcp_ticketer-0.1.14.dist-info/METADATA,sha256=AD51kbqeeWDh5nalVzmsmypReM-1f2cajlksTYJVouc,11211
38
- mcp_ticketer-0.1.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- mcp_ticketer-0.1.14.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
40
- mcp_ticketer-0.1.14.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
41
- mcp_ticketer-0.1.14.dist-info/RECORD,,
37
+ mcp_ticketer-0.1.16.dist-info/licenses/LICENSE,sha256=KOVrunjtILSzY-2N8Lqa3-Q8dMaZIG4LrlLTr9UqL08,1073
38
+ mcp_ticketer-0.1.16.dist-info/METADATA,sha256=2Fa_rzfQvRRW-6e52xz_umAJlztAjQn1DMSQaTzcTEE,11211
39
+ mcp_ticketer-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ mcp_ticketer-0.1.16.dist-info/entry_points.txt,sha256=o1IxVhnHnBNG7FZzbFq-Whcs1Djbofs0qMjiUYBLx2s,60
41
+ mcp_ticketer-0.1.16.dist-info/top_level.txt,sha256=WnAG4SOT1Vm9tIwl70AbGG_nA217YyV3aWFhxLH2rxw,13
42
+ mcp_ticketer-0.1.16.dist-info/RECORD,,