mcp-ticketer 0.4.1__py3-none-any.whl → 0.4.2__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.4.1"
3
+ __version__ = "0.4.2"
4
4
  __version_info__ = tuple(int(part) for part in __version__.split("."))
5
5
 
6
6
  # Package metadata
@@ -138,6 +138,72 @@ def create_auggie_server_config(
138
138
  return config
139
139
 
140
140
 
141
+ def remove_auggie_mcp(dry_run: bool = False) -> None:
142
+ """Remove mcp-ticketer from Auggie CLI configuration.
143
+
144
+ IMPORTANT: Auggie CLI ONLY supports global configuration.
145
+ This will remove mcp-ticketer from ~/.augment/settings.json.
146
+
147
+ Args:
148
+ dry_run: Show what would be removed without making changes
149
+
150
+ """
151
+ # Step 1: Find Auggie config location
152
+ console.print("[cyan]🔍 Removing Auggie CLI global configuration...[/cyan]")
153
+ console.print(
154
+ "[yellow]⚠ NOTE: Auggie only supports global configuration (affects all projects)[/yellow]"
155
+ )
156
+
157
+ auggie_config_path = find_auggie_config()
158
+ console.print(f"[dim]Config location: {auggie_config_path}[/dim]")
159
+
160
+ # Step 2: Check if config file exists
161
+ if not auggie_config_path.exists():
162
+ console.print(
163
+ f"[yellow]⚠ No configuration found at {auggie_config_path}[/yellow]"
164
+ )
165
+ console.print("[dim]mcp-ticketer is not configured for Auggie[/dim]")
166
+ return
167
+
168
+ # Step 3: Load existing Auggie configuration
169
+ auggie_config = load_auggie_config(auggie_config_path)
170
+
171
+ # Step 4: Check if mcp-ticketer is configured
172
+ if "mcp-ticketer" not in auggie_config.get("mcpServers", {}):
173
+ console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
174
+ console.print(f"[dim]No mcp-ticketer entry found in {auggie_config_path}[/dim]")
175
+ return
176
+
177
+ # Step 5: Show what would be removed (dry run or actual removal)
178
+ if dry_run:
179
+ console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
180
+ console.print(" Server name: mcp-ticketer")
181
+ console.print(f" From: {auggie_config_path}")
182
+ console.print(" Scope: Global (all projects)")
183
+ return
184
+
185
+ # Step 6: Remove mcp-ticketer from configuration
186
+ del auggie_config["mcpServers"]["mcp-ticketer"]
187
+
188
+ # Step 7: Save updated configuration
189
+ try:
190
+ save_auggie_config(auggie_config_path, auggie_config)
191
+ console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
192
+ console.print(f"[dim]Configuration updated: {auggie_config_path}[/dim]")
193
+
194
+ # Next steps
195
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
196
+ console.print("1. Restart Auggie CLI for changes to take effect")
197
+ console.print("2. mcp-ticketer will no longer be available via MCP")
198
+ console.print(
199
+ "\n[yellow]⚠ Note: This removes global configuration affecting all projects[/yellow]"
200
+ )
201
+
202
+ except Exception as e:
203
+ console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
204
+ raise
205
+
206
+
141
207
  def configure_auggie_mcp(force: bool = False) -> None:
142
208
  """Configure Auggie CLI to use mcp-ticketer.
143
209
 
@@ -151,6 +151,74 @@ def create_codex_server_config(
151
151
  return config
152
152
 
153
153
 
154
+ def remove_codex_mcp(dry_run: bool = False) -> None:
155
+ """Remove mcp-ticketer from Codex CLI configuration.
156
+
157
+ IMPORTANT: Codex CLI ONLY supports global configuration at ~/.codex/config.toml.
158
+ This will remove mcp-ticketer from the global configuration.
159
+
160
+ Args:
161
+ dry_run: Show what would be removed without making changes
162
+
163
+ """
164
+ # Step 1: Find Codex config location (always global)
165
+ console.print("[cyan]🔍 Removing Codex CLI global configuration...[/cyan]")
166
+ console.print(
167
+ "[yellow]⚠ Note: Codex CLI only supports global configuration[/yellow]"
168
+ )
169
+
170
+ codex_config_path = find_codex_config()
171
+ console.print(f"[dim]Config location: {codex_config_path}[/dim]")
172
+
173
+ # Step 2: Check if config file exists
174
+ if not codex_config_path.exists():
175
+ console.print(
176
+ f"[yellow]⚠ No configuration found at {codex_config_path}[/yellow]"
177
+ )
178
+ console.print("[dim]mcp-ticketer is not configured for Codex CLI[/dim]")
179
+ return
180
+
181
+ # Step 3: Load existing Codex configuration
182
+ codex_config = load_codex_config(codex_config_path)
183
+
184
+ # Step 4: Check if mcp-ticketer is configured
185
+ # NOTE: Use underscore mcp_servers, not camelCase
186
+ mcp_servers = codex_config.get("mcp_servers", {})
187
+ if "mcp-ticketer" not in mcp_servers:
188
+ console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
189
+ console.print(f"[dim]No mcp-ticketer entry found in {codex_config_path}[/dim]")
190
+ return
191
+
192
+ # Step 5: Show what would be removed (dry run or actual removal)
193
+ if dry_run:
194
+ console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
195
+ console.print(" Server name: mcp-ticketer")
196
+ console.print(f" From: {codex_config_path}")
197
+ console.print(" Scope: Global (all sessions)")
198
+ return
199
+
200
+ # Step 6: Remove mcp-ticketer from configuration
201
+ del codex_config["mcp_servers"]["mcp-ticketer"]
202
+
203
+ # Step 7: Save updated configuration
204
+ try:
205
+ save_codex_config(codex_config_path, codex_config)
206
+ console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
207
+ console.print(f"[dim]Configuration updated: {codex_config_path}[/dim]")
208
+
209
+ # Next steps
210
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
211
+ console.print("1. [bold]Restart Codex CLI[/bold] (required for changes)")
212
+ console.print("2. mcp-ticketer will no longer be available via MCP")
213
+ console.print(
214
+ "\n[yellow]⚠ Note: This removes global configuration affecting all Codex sessions[/yellow]"
215
+ )
216
+
217
+ except Exception as e:
218
+ console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
219
+ raise
220
+
221
+
154
222
  def configure_codex_mcp(force: bool = False) -> None:
155
223
  """Configure Codex CLI to use mcp-ticketer.
156
224
 
@@ -147,6 +147,72 @@ def create_gemini_server_config(
147
147
  return config
148
148
 
149
149
 
150
+ def remove_gemini_mcp(
151
+ scope: Literal["project", "user"] = "project", dry_run: bool = False
152
+ ) -> None:
153
+ """Remove mcp-ticketer from Gemini CLI configuration.
154
+
155
+ Args:
156
+ scope: Configuration scope - "project" or "user"
157
+ dry_run: Show what would be removed without making changes
158
+
159
+ """
160
+ # Step 1: Find Gemini config location
161
+ config_type = "user-level" if scope == "user" else "project-level"
162
+ console.print(f"[cyan]🔍 Removing {config_type} Gemini CLI configuration...[/cyan]")
163
+
164
+ gemini_config_path = find_gemini_config(scope)
165
+ console.print(f"[dim]Config location: {gemini_config_path}[/dim]")
166
+
167
+ # Step 2: Check if config file exists
168
+ if not gemini_config_path.exists():
169
+ console.print(
170
+ f"[yellow]⚠ No configuration found at {gemini_config_path}[/yellow]"
171
+ )
172
+ console.print("[dim]mcp-ticketer is not configured for Gemini CLI[/dim]")
173
+ return
174
+
175
+ # Step 3: Load existing Gemini configuration
176
+ gemini_config = load_gemini_config(gemini_config_path)
177
+
178
+ # Step 4: Check if mcp-ticketer is configured
179
+ if "mcp-ticketer" not in gemini_config.get("mcpServers", {}):
180
+ console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
181
+ console.print(f"[dim]No mcp-ticketer entry found in {gemini_config_path}[/dim]")
182
+ return
183
+
184
+ # Step 5: Show what would be removed (dry run or actual removal)
185
+ if dry_run:
186
+ console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
187
+ console.print(" Server name: mcp-ticketer")
188
+ console.print(f" From: {gemini_config_path}")
189
+ console.print(f" Scope: {config_type}")
190
+ return
191
+
192
+ # Step 6: Remove mcp-ticketer from configuration
193
+ del gemini_config["mcpServers"]["mcp-ticketer"]
194
+
195
+ # Step 7: Save updated configuration
196
+ try:
197
+ save_gemini_config(gemini_config_path, gemini_config)
198
+ console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
199
+ console.print(f"[dim]Configuration updated: {gemini_config_path}[/dim]")
200
+
201
+ # Next steps
202
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
203
+ if scope == "user":
204
+ console.print("1. Gemini CLI global configuration updated")
205
+ console.print("2. mcp-ticketer will no longer be available in any project")
206
+ else:
207
+ console.print("1. Gemini CLI project configuration updated")
208
+ console.print("2. mcp-ticketer will no longer be available in this project")
209
+ console.print("3. Restart Gemini CLI if currently running")
210
+
211
+ except Exception as e:
212
+ console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
213
+ raise
214
+
215
+
150
216
  def configure_gemini_mcp(
151
217
  scope: Literal["project", "user"] = "project", force: bool = False
152
218
  ) -> None:
mcp_ticketer/cli/main.py CHANGED
@@ -1941,50 +1941,89 @@ mcp_app = typer.Typer(
1941
1941
  add_completion=False,
1942
1942
  )
1943
1943
 
1944
- # Create install command group (like kuzu-memory)
1945
- install_app = typer.Typer(
1946
- name="install",
1947
- help="Manage AI system integrations",
1948
- add_completion=False,
1949
- )
1950
-
1951
1944
 
1952
- @install_app.command(name="add")
1953
- def install_add(
1954
- platform: str = typer.Argument(
1955
- ..., help="Platform to install (claude-code, claude-desktop, etc.)"
1956
- ),
1957
- project_path: Optional[str] = typer.Option(
1958
- None, "--project", help="Project directory"
1945
+ @app.command()
1946
+ def install(
1947
+ platform: Optional[str] = typer.Argument(
1948
+ None,
1949
+ help="Platform to install (claude-code, claude-desktop, auggie, gemini, codex)",
1959
1950
  ),
1960
1951
  dry_run: bool = typer.Option(
1961
1952
  False, "--dry-run", help="Show what would be done without making changes"
1962
1953
  ),
1963
- verbose: bool = typer.Option(False, "--verbose", help="Enable verbose output"),
1964
1954
  ) -> None:
1965
- """Install mcp-ticketer integration for an AI platform.
1955
+ """Install mcp-ticketer for AI platforms.
1956
+
1957
+ Without arguments, shows installation status and available platforms.
1958
+ With a platform argument, installs MCP configuration for that platform.
1966
1959
 
1967
1960
  Each platform gets the right configuration automatically:
1968
1961
  - claude-code: Project-level MCP server
1969
1962
  - claude-desktop: Global MCP server
1963
+ - auggie: Project-level MCP server
1964
+ - gemini: Project-level MCP server
1965
+ - codex: Project-level MCP server
1970
1966
 
1971
1967
  Examples:
1968
+ # Show status and available platforms
1969
+ mcp-ticketer install
1970
+
1972
1971
  # Install for Claude Code (project-level)
1973
- mcp-ticketer install add claude-code
1972
+ mcp-ticketer install claude-code
1974
1973
 
1975
1974
  # Install for Claude Desktop (global)
1976
- mcp-ticketer install add claude-desktop
1975
+ mcp-ticketer install claude-desktop
1976
+
1977
+ # Install for Auggie
1978
+ mcp-ticketer install auggie
1977
1979
 
1978
1980
  # Dry run to preview changes
1979
- mcp-ticketer install add claude-code --dry-run
1981
+ mcp-ticketer install claude-code --dry-run
1980
1982
 
1981
1983
  """
1984
+ # If no platform specified, show help message
1985
+ if platform is None:
1986
+ console.print("[green]✓[/green] mcp-ticketer CLI is already installed.\n")
1987
+ console.print(
1988
+ "[bold]To configure MCP for a specific platform, use:[/bold]\n"
1989
+ " mcp-ticketer install <platform>\n"
1990
+ )
1991
+ console.print("[bold]Available platforms:[/bold]")
1992
+ console.print(" • claude-code - Claude Code (project-level)")
1993
+ console.print(" • claude-desktop - Claude Desktop (global)")
1994
+ console.print(" • auggie - Auggie (project-level)")
1995
+ console.print(" • gemini - Gemini CLI (project-level)")
1996
+ console.print(" • codex - Codex (project-level)")
1997
+ return
1998
+
1999
+ # Import configuration functions
2000
+ from .auggie_configure import configure_auggie_mcp
2001
+ from .codex_configure import configure_codex_mcp
2002
+ from .gemini_configure import configure_gemini_mcp
1982
2003
  from .mcp_configure import configure_claude_mcp
1983
2004
 
1984
- # Map platform names to configuration
2005
+ # Map platform names to configuration functions
1985
2006
  platform_mapping = {
1986
- "claude-code": {"global": False, "name": "Claude Code"},
1987
- "claude-desktop": {"global": True, "name": "Claude Desktop"},
2007
+ "claude-code": {
2008
+ "func": lambda: configure_claude_mcp(global_config=False, force=True),
2009
+ "name": "Claude Code",
2010
+ },
2011
+ "claude-desktop": {
2012
+ "func": lambda: configure_claude_mcp(global_config=True, force=True),
2013
+ "name": "Claude Desktop",
2014
+ },
2015
+ "auggie": {
2016
+ "func": lambda: configure_auggie_mcp(force=True),
2017
+ "name": "Auggie",
2018
+ },
2019
+ "gemini": {
2020
+ "func": lambda: configure_gemini_mcp(scope="project", force=True),
2021
+ "name": "Gemini CLI",
2022
+ },
2023
+ "codex": {
2024
+ "func": lambda: configure_codex_mcp(force=True),
2025
+ "name": "Codex",
2026
+ },
1988
2027
  }
1989
2028
 
1990
2029
  if platform not in platform_mapping:
@@ -2001,12 +2040,134 @@ def install_add(
2001
2040
  return
2002
2041
 
2003
2042
  try:
2004
- configure_claude_mcp(global_config=config["global"], force=True)
2043
+ config["func"]()
2005
2044
  except Exception as e:
2006
2045
  console.print(f"[red]Installation failed: {e}[/red]")
2007
2046
  raise typer.Exit(1)
2008
2047
 
2009
2048
 
2049
+ @app.command()
2050
+ def remove(
2051
+ platform: Optional[str] = typer.Argument(
2052
+ None,
2053
+ help="Platform to remove (claude-code, claude-desktop, auggie, gemini, codex)",
2054
+ ),
2055
+ dry_run: bool = typer.Option(
2056
+ False, "--dry-run", help="Show what would be done without making changes"
2057
+ ),
2058
+ ) -> None:
2059
+ """Remove mcp-ticketer from AI platforms.
2060
+
2061
+ Without arguments, shows help and available platforms.
2062
+ With a platform argument, removes MCP configuration for that platform.
2063
+
2064
+ Examples:
2065
+ # Remove from Claude Code (project-level)
2066
+ mcp-ticketer remove claude-code
2067
+
2068
+ # Remove from Claude Desktop (global)
2069
+ mcp-ticketer remove claude-desktop
2070
+
2071
+ # Remove from Auggie
2072
+ mcp-ticketer remove auggie
2073
+
2074
+ # Dry run to preview changes
2075
+ mcp-ticketer remove claude-code --dry-run
2076
+
2077
+ """
2078
+ # If no platform specified, show help message
2079
+ if platform is None:
2080
+ console.print("[bold]Remove mcp-ticketer from AI platforms[/bold]\n")
2081
+ console.print("Usage: mcp-ticketer remove <platform>\n")
2082
+ console.print("[bold]Available platforms:[/bold]")
2083
+ console.print(" • claude-code - Claude Code (project-level)")
2084
+ console.print(" • claude-desktop - Claude Desktop (global)")
2085
+ console.print(" • auggie - Auggie (global)")
2086
+ console.print(" • gemini - Gemini CLI (project-level by default)")
2087
+ console.print(" • codex - Codex (global)")
2088
+ return
2089
+
2090
+ # Import removal functions
2091
+ from .auggie_configure import remove_auggie_mcp
2092
+ from .codex_configure import remove_codex_mcp
2093
+ from .gemini_configure import remove_gemini_mcp
2094
+ from .mcp_configure import remove_claude_mcp
2095
+
2096
+ # Map platform names to removal functions
2097
+ platform_mapping = {
2098
+ "claude-code": {
2099
+ "func": lambda: remove_claude_mcp(global_config=False, dry_run=dry_run),
2100
+ "name": "Claude Code",
2101
+ },
2102
+ "claude-desktop": {
2103
+ "func": lambda: remove_claude_mcp(global_config=True, dry_run=dry_run),
2104
+ "name": "Claude Desktop",
2105
+ },
2106
+ "auggie": {
2107
+ "func": lambda: remove_auggie_mcp(dry_run=dry_run),
2108
+ "name": "Auggie",
2109
+ },
2110
+ "gemini": {
2111
+ "func": lambda: remove_gemini_mcp(scope="project", dry_run=dry_run),
2112
+ "name": "Gemini CLI",
2113
+ },
2114
+ "codex": {
2115
+ "func": lambda: remove_codex_mcp(dry_run=dry_run),
2116
+ "name": "Codex",
2117
+ },
2118
+ }
2119
+
2120
+ if platform not in platform_mapping:
2121
+ console.print(f"[red]Unknown platform: {platform}[/red]")
2122
+ console.print("\n[bold]Available platforms:[/bold]")
2123
+ for p in platform_mapping.keys():
2124
+ console.print(f" • {p}")
2125
+ raise typer.Exit(1)
2126
+
2127
+ config = platform_mapping[platform]
2128
+
2129
+ try:
2130
+ config["func"]()
2131
+ except Exception as e:
2132
+ console.print(f"[red]Removal failed: {e}[/red]")
2133
+ raise typer.Exit(1)
2134
+
2135
+
2136
+ @app.command()
2137
+ def uninstall(
2138
+ platform: Optional[str] = typer.Argument(
2139
+ None,
2140
+ help="Platform to uninstall (claude-code, claude-desktop, auggie, gemini, codex)",
2141
+ ),
2142
+ dry_run: bool = typer.Option(
2143
+ False, "--dry-run", help="Show what would be done without making changes"
2144
+ ),
2145
+ ) -> None:
2146
+ """Uninstall mcp-ticketer from AI platforms (alias for remove).
2147
+
2148
+ This is an alias for the 'remove' command.
2149
+
2150
+ Without arguments, shows help and available platforms.
2151
+ With a platform argument, removes MCP configuration for that platform.
2152
+
2153
+ Examples:
2154
+ # Uninstall from Claude Code (project-level)
2155
+ mcp-ticketer uninstall claude-code
2156
+
2157
+ # Uninstall from Claude Desktop (global)
2158
+ mcp-ticketer uninstall claude-desktop
2159
+
2160
+ # Uninstall from Auggie
2161
+ mcp-ticketer uninstall auggie
2162
+
2163
+ # Dry run to preview changes
2164
+ mcp-ticketer uninstall claude-code --dry-run
2165
+
2166
+ """
2167
+ # Call the remove command with the same parameters
2168
+ remove(platform=platform, dry_run=dry_run)
2169
+
2170
+
2010
2171
  @app.command(deprecated=True, hidden=True)
2011
2172
  def check(queue_id: str = typer.Argument(..., help="Queue ID to check")):
2012
2173
  """Check status of a queued operation.
@@ -2077,7 +2238,8 @@ def mcp_serve(
2077
2238
  2. Global: ~/.mcp-ticketer/config.json
2078
2239
  3. Default: aitrackdown adapter with .aitrackdown base path
2079
2240
  """
2080
- from ..mcp.server import MCPTicketServer
2241
+ from ..mcp.server_sdk import configure_adapter
2242
+ from ..mcp.server_sdk import main as sdk_main
2081
2243
 
2082
2244
  # Load configuration (respects project-specific config in cwd)
2083
2245
  config = load_config()
@@ -2118,21 +2280,22 @@ def mcp_serve(
2118
2280
  if sys.stderr.isatty():
2119
2281
  # Only print if stderr is a terminal (not redirected)
2120
2282
  console.file = sys.stderr
2121
- console.print(f"[green]Starting MCP server[/green] with {adapter_type} adapter")
2283
+ console.print(
2284
+ f"[green]Starting MCP SDK server[/green] with {adapter_type} adapter"
2285
+ )
2122
2286
  console.print(
2123
2287
  "[dim]Server running on stdio. Send JSON-RPC requests via stdin.[/dim]"
2124
2288
  )
2125
2289
 
2126
- # Create and run server
2290
+ # Configure adapter and run SDK server
2127
2291
  try:
2128
- server = MCPTicketServer(adapter_type, adapter_config)
2129
- asyncio.run(server.run())
2292
+ configure_adapter(adapter_type, adapter_config)
2293
+ sdk_main()
2130
2294
  except KeyboardInterrupt:
2131
- # Also send this to stderr
2295
+ # Send this to stderr
2132
2296
  if sys.stderr.isatty():
2133
2297
  console.print("\n[yellow]Server stopped by user[/yellow]")
2134
- if "server" in locals():
2135
- asyncio.run(server.stop())
2298
+ sys.exit(0)
2136
2299
  except Exception as e:
2137
2300
  # Log error to stderr
2138
2301
  sys.stderr.write(f"MCP server error: {e}\n")
@@ -2292,7 +2455,6 @@ def mcp_auggie(
2292
2455
 
2293
2456
  # Add command groups to main app (must be after all subcommands are defined)
2294
2457
  app.add_typer(mcp_app, name="mcp")
2295
- app.add_typer(install_app, name="install")
2296
2458
 
2297
2459
 
2298
2460
  def main():
@@ -217,6 +217,66 @@ def create_mcp_server_config(
217
217
  return config
218
218
 
219
219
 
220
+ def remove_claude_mcp(global_config: bool = False, dry_run: bool = False) -> None:
221
+ """Remove mcp-ticketer from Claude Code/Desktop configuration.
222
+
223
+ Args:
224
+ global_config: Remove from Claude Desktop instead of project-level
225
+ dry_run: Show what would be removed without making changes
226
+
227
+ """
228
+ # Step 1: Find Claude MCP config location
229
+ config_type = "Claude Desktop" if global_config else "project-level"
230
+ console.print(f"[cyan]🔍 Removing {config_type} MCP configuration...[/cyan]")
231
+
232
+ mcp_config_path = find_claude_mcp_config(global_config)
233
+ console.print(f"[dim]Config location: {mcp_config_path}[/dim]")
234
+
235
+ # Step 2: Check if config file exists
236
+ if not mcp_config_path.exists():
237
+ console.print(f"[yellow]⚠ No configuration found at {mcp_config_path}[/yellow]")
238
+ console.print("[dim]mcp-ticketer is not configured for this platform[/dim]")
239
+ return
240
+
241
+ # Step 3: Load existing MCP configuration
242
+ mcp_config = load_claude_mcp_config(mcp_config_path)
243
+
244
+ # Step 4: Check if mcp-ticketer is configured
245
+ if "mcp-ticketer" not in mcp_config.get("mcpServers", {}):
246
+ console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
247
+ console.print(f"[dim]No mcp-ticketer entry found in {mcp_config_path}[/dim]")
248
+ return
249
+
250
+ # Step 5: Show what would be removed (dry run or actual removal)
251
+ if dry_run:
252
+ console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
253
+ console.print(" Server name: mcp-ticketer")
254
+ console.print(f" From: {mcp_config_path}")
255
+ return
256
+
257
+ # Step 6: Remove mcp-ticketer from configuration
258
+ del mcp_config["mcpServers"]["mcp-ticketer"]
259
+
260
+ # Step 7: Save updated configuration
261
+ try:
262
+ save_claude_mcp_config(mcp_config_path, mcp_config)
263
+ console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
264
+ console.print(f"[dim]Configuration updated: {mcp_config_path}[/dim]")
265
+
266
+ # Next steps
267
+ console.print("\n[bold cyan]Next Steps:[/bold cyan]")
268
+ if global_config:
269
+ console.print("1. Restart Claude Desktop")
270
+ console.print("2. mcp-ticketer will no longer be available in MCP menu")
271
+ else:
272
+ console.print("1. Restart Claude Code")
273
+ console.print("2. mcp-ticketer will no longer be available in this project")
274
+
275
+ except Exception as e:
276
+ console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
277
+ raise
278
+
279
+
220
280
  def configure_claude_mcp(global_config: bool = False, force: bool = False) -> None:
221
281
  """Configure Claude Code to use mcp-ticketer.
222
282
 
@@ -0,0 +1,93 @@
1
+ """FastMCP-based MCP server implementation.
2
+
3
+ This module implements the MCP server using the official FastMCP SDK,
4
+ replacing the custom JSON-RPC implementation. It provides a cleaner,
5
+ more maintainable approach with automatic schema generation and
6
+ better error handling.
7
+
8
+ The server manages a global adapter instance that is configured at
9
+ startup and used by all tool implementations.
10
+ """
11
+
12
+ import logging
13
+ from typing import Any, Optional
14
+
15
+ from mcp.server.fastmcp import FastMCP
16
+
17
+ from ..core.adapter import BaseAdapter
18
+ from ..core.registry import AdapterRegistry
19
+
20
+ # Initialize FastMCP server
21
+ mcp = FastMCP("mcp-ticketer")
22
+
23
+ # Global adapter instance
24
+ _adapter: Optional[BaseAdapter] = None
25
+
26
+ # Configure logging
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def configure_adapter(adapter_type: str, config: dict[str, Any]) -> None:
31
+ """Configure the global adapter instance.
32
+
33
+ This must be called before starting the server to initialize the
34
+ adapter that will handle all ticket operations.
35
+
36
+ Args:
37
+ adapter_type: Type of adapter to create (e.g., "linear", "jira", "github")
38
+ config: Configuration dictionary for the adapter
39
+
40
+ Raises:
41
+ ValueError: If adapter type is not registered
42
+ RuntimeError: If adapter configuration fails
43
+
44
+ """
45
+ global _adapter
46
+
47
+ try:
48
+ # Get adapter from registry
49
+ _adapter = AdapterRegistry.get_adapter(adapter_type, config)
50
+ logger.info(f"Configured {adapter_type} adapter for MCP server")
51
+ except Exception as e:
52
+ logger.error(f"Failed to configure adapter: {e}")
53
+ raise RuntimeError(f"Adapter configuration failed: {e}") from e
54
+
55
+
56
+ def get_adapter() -> BaseAdapter:
57
+ """Get the configured adapter instance.
58
+
59
+ Returns:
60
+ The global adapter instance
61
+
62
+ Raises:
63
+ RuntimeError: If adapter has not been configured
64
+
65
+ """
66
+ if _adapter is None:
67
+ raise RuntimeError(
68
+ "Adapter not configured. Call configure_adapter() before starting server."
69
+ )
70
+ return _adapter
71
+
72
+
73
+ # Import all tool modules to register them with FastMCP
74
+ # These imports must come after mcp is initialized but before main()
75
+ from . import tools # noqa: E402, F401
76
+
77
+
78
+ def main() -> None:
79
+ """Run the FastMCP server.
80
+
81
+ This function starts the server using stdio transport for
82
+ JSON-RPC communication with Claude Desktop/Code.
83
+
84
+ The adapter must be configured via configure_adapter() before
85
+ calling this function.
86
+
87
+ """
88
+ # Run the server with stdio transport
89
+ mcp.run(transport="stdio")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()