4949 print_success ,
5050)
5151
52- app = typer .Typer (help = "Install MCP server to client applications" , no_args_is_help = False )
52+ app = typer .Typer (
53+ help = "Install MCP server to client applications" , no_args_is_help = False
54+ )
5355
5456
5557def _get_claude_desktop_config_path () -> Path :
5658 """Get the Claude Desktop config path based on platform."""
5759 if platform .system () == "Darwin" : # macOS
58- return Path .home () / "Library/Application Support/Claude/claude_desktop_config.json"
60+ return (
61+ Path .home ()
62+ / "Library/Application Support/Claude/claude_desktop_config.json"
63+ )
5964 elif platform .system () == "Windows" :
6065 return Path .home () / "AppData/Roaming/Claude/claude_desktop_config.json"
6166 else : # Linux
@@ -83,7 +88,9 @@ def _get_claude_desktop_config_path() -> Path:
8388}
8489
8590
86- def _merge_mcp_json (existing : dict , server_name : str , server_config : dict , format_type : str = "mcp" ) -> dict :
91+ def _merge_mcp_json (
92+ existing : dict , server_name : str , server_config : dict , format_type : str = "mcp"
93+ ) -> dict :
8794 """
8895 Merge a server configuration into existing MCP JSON.
8996
@@ -111,7 +118,9 @@ def _merge_mcp_json(existing: dict, server_name: str, server_config: dict, forma
111118 servers = dict (existing ["mcp" ].get ("servers" ) or {})
112119 else :
113120 for k , v in existing .items ():
114- if isinstance (v , dict ) and ("url" in v or "transport" in v or "command" in v or "type" in v ):
121+ if isinstance (v , dict ) and (
122+ "url" in v or "transport" in v or "command" in v or "type" in v
123+ ):
115124 servers [k ] = v
116125
117126 servers [server_name ] = server_config
@@ -141,7 +150,9 @@ def walk(obj):
141150 walk (v )
142151 elif isinstance (obj , list ):
143152 for i , v in enumerate (obj ):
144- if isinstance (v , str ) and v .lower ().startswith ("authorization: bearer " ):
153+ if isinstance (v , str ) and v .lower ().startswith (
154+ "authorization: bearer "
155+ ):
145156 obj [i ] = "Authorization: Bearer ***"
146157 else :
147158 walk (v )
@@ -158,7 +169,9 @@ def _write_json(path: Path, data: dict) -> None:
158169 if path .exists () and os .name == "posix" :
159170 original_mode = os .stat (path ).st_mode & 0o777
160171
161- tmp_fd , tmp_name = tempfile .mkstemp (dir = str (path .parent ), prefix = path .name , suffix = ".tmp" )
172+ tmp_fd , tmp_name = tempfile .mkstemp (
173+ dir = str (path .parent ), prefix = path .name , suffix = ".tmp"
174+ )
162175 try :
163176 with os .fdopen (tmp_fd , "w" , encoding = "utf-8" ) as f :
164177 f .write (json .dumps (data , indent = 2 ))
@@ -203,7 +216,13 @@ def _server_hostname(server_url: str, app_name: Optional[str] = None) -> str:
203216 return hostname or "mcp-server"
204217
205218
206- def _build_server_config (server_url : str , transport : str = "http" , for_claude_desktop : bool = False , for_vscode : bool = False , api_key : str = None ) -> dict :
219+ def _build_server_config (
220+ server_url : str ,
221+ transport : str = "http" ,
222+ for_claude_desktop : bool = False ,
223+ for_vscode : bool = False ,
224+ api_key : str = None ,
225+ ) -> dict :
207226 """Build server configuration dictionary with auth header.
208227
209228 For Claude Desktop, wraps HTTP/SSE servers with mcp-remote stdio wrapper with actual API key.
@@ -228,39 +247,39 @@ def _build_server_config(server_url: str, transport: str = "http", for_claude_de
228247 "mcp-remote" ,
229248 server_url ,
230249 "--header" ,
231- f"Authorization: Bearer { api_key } "
232- ]
250+ f"Authorization: Bearer { api_key } " ,
251+ ],
233252 }
234253 elif for_vscode :
235254 # VSCode uses "type" instead of "transport"
236255 return {
237256 "type" : transport ,
238257 "url" : server_url ,
239- "headers" : {
240- "Authorization" : f"Bearer { api_key } "
241- }
258+ "headers" : {"Authorization" : f"Bearer { api_key } " },
242259 }
243260 else :
244261 # Direct HTTP/SSE connection for Cursor with embedded API key
245262 return {
246263 "url" : server_url ,
247264 "transport" : transport ,
248- "headers" : {
249- "Authorization" : f"Bearer { api_key } "
250- }
265+ "headers" : {"Authorization" : f"Bearer { api_key } " },
251266 }
252267
253268
254269@app .callback (invoke_without_command = True )
255270def install (
256- server_identifier : str = typer .Argument (
257- ..., help = "Server URL to install"
258- ),
271+ server_identifier : str = typer .Argument (..., help = "Server URL to install" ),
259272 client : str = typer .Option (
260- ..., "--client" , "-c" , help = "Client to install to: vscode|claude_code|cursor|claude_desktop|chatgpt"
273+ ...,
274+ "--client" ,
275+ "-c" ,
276+ help = "Client to install to: vscode|claude_code|cursor|claude_desktop|chatgpt" ,
261277 ),
262278 name : Optional [str ] = typer .Option (
263- None , "--name" , "-n" , help = "Server name in client config (auto-generated if not provided)"
279+ None ,
280+ "--name" ,
281+ "-n" ,
282+ help = "Server name in client config (auto-generated if not provided)" ,
264283 ),
265284 dry_run : bool = typer .Option (
266285 False , "--dry-run" , help = "Show what would be installed without writing files"
@@ -310,15 +329,16 @@ def install(
310329 f"Unsupported client: { client } . Supported clients: vscode, claude_code, cursor, claude_desktop, chatgpt"
311330 )
312331
313-
314332 effective_api_key = api_key or settings .API_KEY or load_api_key_credentials ()
315333 if not effective_api_key :
316334 raise CLIError (
317335 "Must be logged in to install. Run 'mcp-agent login', set MCP_API_KEY environment variable, or specify --api-key option."
318336 )
319337
320338 server_url = server_identifier
321- if not server_identifier .startswith ("http://" ) and not server_identifier .startswith ("https://" ):
339+ if not server_identifier .startswith ("http://" ) and not server_identifier .startswith (
340+ "https://"
341+ ):
322342 raise CLIError (
323343 f"Server identifier must be a URL starting with http:// or https://. Got: { server_identifier } "
324344 )
@@ -329,7 +349,9 @@ def install(
329349
330350 console .print ("\n [bold cyan]Installing MCP Server[/bold cyan]\n " )
331351 print_info (f"Server URL: { server_url } " )
332- print_info (f"Client: { CLIENT_CONFIGS .get (client_lc , {}).get ('description' , client_lc )} " )
352+ print_info (
353+ f"Client: { CLIENT_CONFIGS .get (client_lc , {}).get ('description' , client_lc )} "
354+ )
333355
334356 mcp_client = MCPAppClient (
335357 api_url = api_url or DEFAULT_API_BASE_URL , api_key = effective_api_key
@@ -349,9 +371,9 @@ def install(
349371 if not app_info :
350372 app_info = run_async (mcp_client .get_app (server_url = server_url ))
351373
352- has_unauth_access = (
353- app_info .unauthenticatedAccess is True or
354- ( app_info . appServerInfo and app_info .appServerInfo .unauthenticatedAccess is True )
374+ has_unauth_access = app_info . unauthenticatedAccess is True or (
375+ app_info .appServerInfo
376+ and app_info .appServerInfo .unauthenticatedAccess is True
355377 )
356378
357379 if not has_unauth_access :
@@ -379,7 +401,9 @@ def install(
379401 raise
380402 except Exception as e :
381403 print_info (f"Warning: Could not verify unauthenticated access: { e } " )
382- print_info ("Proceeding with installation, but ChatGPT may not be able to connect." )
404+ print_info (
405+ "Proceeding with installation, but ChatGPT may not be able to connect."
406+ )
383407
384408 console .print (
385409 Panel (
@@ -405,19 +429,28 @@ def install(
405429 if client_lc == "claude_code" :
406430 if dry_run :
407431 console .print ("\n [bold yellow]DRY RUN - Would run:[/bold yellow]" )
408- console .print (f"claude mcp add { server_name } { server_url } -t { transport } -H 'Authorization: Bearer <api-key>' -s user" )
432+ console .print (
433+ f"claude mcp add { server_name } { server_url } -t { transport } -H 'Authorization: Bearer <api-key>' -s user"
434+ )
409435 return
410436
411437 try :
412438 cmd = [
413- "claude" , "mcp" , "add" ,
439+ "claude" ,
440+ "mcp" ,
441+ "add" ,
414442 server_name ,
415443 server_url ,
416- "-t" , transport ,
417- "-H" , f"Authorization: Bearer { effective_api_key } " ,
418- "-s" , "user"
444+ "-t" ,
445+ transport ,
446+ "-H" ,
447+ f"Authorization: Bearer { effective_api_key } " ,
448+ "-s" ,
449+ "user" ,
419450 ]
420- result = subprocess .run (cmd , capture_output = True , text = True , check = True , timeout = 30 )
451+ result = subprocess .run (
452+ cmd , capture_output = True , text = True , check = True , timeout = 30
453+ )
421454 print_success (f"Server '{ server_name } ' installed to Claude Code" )
422455 console .print (result .stdout )
423456 return
@@ -455,14 +488,16 @@ def install(
455488 f"Server '{ server_name } ' already exists in { config_path } . Use --force to overwrite."
456489 )
457490 except json .JSONDecodeError as e :
458- raise CLIError (f"Failed to parse existing config at { config_path } : { e } " ) from e
491+ raise CLIError (
492+ f"Failed to parse existing config at { config_path } : { e } "
493+ ) from e
459494
460495 server_config = _build_server_config (
461496 server_url ,
462497 transport ,
463498 for_claude_desktop = is_claude_desktop ,
464499 for_vscode = is_vscode ,
465- api_key = effective_api_key
500+ api_key = effective_api_key ,
466501 )
467502
468503 if is_claude_desktop or is_cursor :
@@ -472,7 +507,9 @@ def install(
472507 else :
473508 format_type = "mcp"
474509
475- merged_config = _merge_mcp_json (existing_config , server_name , server_config , format_type )
510+ merged_config = _merge_mcp_json (
511+ existing_config , server_name , server_config , format_type
512+ )
476513
477514 if dry_run :
478515 console .print ("\n [bold]Would write to:[/bold]" , config_path )
0 commit comments