99import socket
1010import subprocess
1111import sys
12- import uuid
1312
1413import click
1514import psutil
@@ -41,6 +40,7 @@ def is_process_running(pid):
4140 except Exception :
4241 return False
4342
43+
4444def is_port_listening (host , port ) -> bool :
4545 """
4646 Check if the specified (host, port) is being listened on.
@@ -133,9 +133,12 @@ def start_router(verbose):
133133 return
134134
135135 # get router config
136- config = ConfigManager ().get_router_config ()
136+ config_manager = ConfigManager ()
137+ config = config_manager .get_router_config ()
137138 host = config ["host" ]
138139 port = config ["port" ]
140+ auth_enabled = config .get ("auth_enabled" , False )
141+ api_key = config .get ("api_key" )
139142
140143 # prepare uvicorn command
141144 uvicorn_cmd = [
@@ -185,9 +188,28 @@ def start_router(verbose):
185188 pid = process .pid
186189 write_pid_file (pid )
187190
191+ # Display router started information
188192 console .print (f"[bold green]MCPRouter started[/] at http://{ host } :{ port } (PID: { pid } )" )
189193 console .print (f"Log file: { log_file } " )
190- console .print ("Use 'mcpm router off' to stop the router." )
194+
195+ # Display connection instructions
196+ console .print ("\n [bold cyan]Connection Information:[/]" )
197+
198+ api_key = api_key if auth_enabled else None
199+
200+ # Show URL with or without authentication based on API key availability
201+ if api_key :
202+ # Show authenticated URL
203+ console .print (f"SSE Server URL: [green]http://{ host } :{ port } /sse?s={ api_key } [/]" )
204+ console .print ("\n [bold cyan]To use a specific profile with authentication:[/]" )
205+ console .print (f"[green]http://{ host } :{ port } /sse?s={ api_key } &profile=<profile_name>[/]" )
206+ else :
207+ # Show URL without authentication
208+ console .print (f"SSE Server URL: [green]http://{ host } :{ port } /sse[/]" )
209+ console .print ("\n [bold cyan]To use a specific profile:[/]" )
210+ console .print (f"[green]http://{ host } :{ port } /sse?profile=<profile_name>[/]" )
211+
212+ console .print ("\n [yellow]Use 'mcpm router off' to stop the router.[/]" )
191213
192214 except Exception as e :
193215 console .print (f"[bold red]Error:[/] Failed to start MCPRouter: { e } " )
@@ -197,17 +219,23 @@ def start_router(verbose):
197219@click .option ("-H" , "--host" , type = str , help = "Host to bind the SSE server to" )
198220@click .option ("-p" , "--port" , type = int , help = "Port to bind the SSE server to" )
199221@click .option ("-a" , "--address" , type = str , help = "Remote address to share the router" )
222+ @click .option (
223+ "--auth/--no-auth" , default = True , is_flag = True , help = "Enable/disable API key authentication (default: enabled)"
224+ )
225+ @click .option ("-s" , "--secret" , type = str , help = "Secret key for authentication" )
200226@click .help_option ("-h" , "--help" )
201- def set_router_config (host , port , address ):
227+ def set_router_config (host , port , address , auth , secret : str | None = None ):
202228 """Set MCPRouter global configuration.
203229
204230 Example:
205231 mcpm router set -H localhost -p 8888
206232 mcpm router set --host 127.0.0.1 --port 9000
233+ mcpm router set --no-auth # disable authentication
234+ mcpm router set --auth # enable authentication
207235 """
208- if not host and not port and not address :
236+ if not host and not port and not address and auth is None :
209237 console .print (
210- "[yellow]No changes were made. Please specify at least one option (--host, --port, or --address)[/]"
238+ "[yellow]No changes were made. Please specify at least one option (--host, --port, --address, --auth/--no-auth )[/]"
211239 )
212240 return
213241
@@ -219,9 +247,23 @@ def set_router_config(host, port, address):
219247 host = host or current_config ["host" ]
220248 port = port or current_config ["port" ]
221249 share_address = address or current_config ["share_address" ]
250+ api_key = secret
251+
252+ if auth :
253+ # Enable authentication
254+ if api_key is None :
255+ # Generate a new API key if authentication is enabled but no key exists
256+ api_key = secrets .token_urlsafe (32 )
257+ console .print ("[bold green]API key authentication enabled.[/] Generated new API key." )
258+ else :
259+ console .print ("[bold green]API key authentication enabled.[/] Using provided API key." )
260+ else :
261+ # Disable authentication by clearing the API key
262+ api_key = None
263+ console .print ("[bold yellow]API key authentication disabled.[/]" )
222264
223- # save config
224- if config_manager .save_router_config (host , port , share_address ):
265+ # save router config
266+ if config_manager .save_router_config (host , port , share_address , api_key = api_key , auth_enabled = auth ):
225267 console .print (
226268 f"[bold green]Router configuration updated:[/] host={ host } , port={ port } , share_address={ share_address } "
227269 )
@@ -329,7 +371,7 @@ def router_status():
329371 if share_config .get ("pid" ):
330372 if not is_process_running (share_config ["pid" ]):
331373 console .print ("[yellow]Share link is not active, cleaning.[/]" )
332- ConfigManager ().save_share_config (share_url = None , share_pid = None , api_key = None )
374+ ConfigManager ().save_share_config (share_url = None , share_pid = None )
333375 console .print ("[green]Share link cleaned[/]" )
334376 else :
335377 console .print (
@@ -389,17 +431,17 @@ def share(address, profile, http):
389431 tunnel = Tunnel (remote_host , remote_port , config ["host" ], config ["port" ], secrets .token_urlsafe (32 ), http , None )
390432 share_url = tunnel .start_tunnel ()
391433 share_pid = tunnel .proc .pid if tunnel .proc else None
392- # generate random api key
393- api_key = str (uuid .uuid4 ())
394- console .print (f"[bold green]Generated secret for share link: { api_key } [/]" )
434+ api_key = config .get ("api_key" ) if config .get ("auth_enabled" ) else None
395435 share_url = share_url + "/sse"
396436 # save share pid and link to config
397- config_manager .save_share_config (share_url , share_pid , api_key )
437+ config_manager .save_share_config (share_url , share_pid )
398438 profile = profile or "<your_profile>"
399439
400440 # print share link
401441 console .print (f"[bold green]Router is sharing at { share_url } [/]" )
402- console .print (f"[green]Your profile can be accessed with the url { share_url } ?s={ api_key } &profile={ profile } [/]\n " )
442+ console .print (
443+ f"[green]Your profile can be accessed with the url { share_url } ?profile={ profile } { f'&s={ api_key } ' if api_key else '' } [/]\n "
444+ )
403445 console .print (
404446 "[bold yellow]Be careful about the share link, it will be exposed to the public. Make sure to share to trusted users only.[/]"
405447 )
@@ -409,17 +451,17 @@ def try_clear_share():
409451 console .print ("[bold yellow]Clearing share config...[/]" )
410452 config_manager = ConfigManager ()
411453 share_config = config_manager .read_share_config ()
412- if share_config [ "url" ] :
454+ if share_config . get ( "url" ) :
413455 try :
414456 console .print ("[bold yellow]Disabling share link...[/]" )
415- config_manager .save_share_config (share_url = None , share_pid = None , api_key = None )
457+ config_manager .save_share_config (share_url = None , share_pid = None )
416458 console .print ("[bold green]Share link disabled[/]" )
417- if share_config [ "pid" ] :
459+ if share_config . get ( "pid" ) :
418460 os .kill (share_config ["pid" ], signal .SIGTERM )
419461 except OSError as e :
420462 if e .errno == 3 : # "No such process"
421463 console .print ("[yellow]Share process does not exist, cleaning up share config...[/]" )
422- config_manager .save_share_config (share_url = None , share_pid = None , api_key = None )
464+ config_manager .save_share_config (share_url = None , share_pid = None )
423465 else :
424466 console .print (f"[bold red]Error:[/] Failed to stop share link: { e } " )
425467
@@ -431,11 +473,11 @@ def stop_share():
431473 # check if there is a share link already running
432474 config_manager = ConfigManager ()
433475 share_config = config_manager .read_share_config ()
434- if not share_config [ "url" ] :
476+ if not share_config . get ( "url" ) :
435477 console .print ("[yellow]No share link is active.[/]" )
436478 return
437479
438- pid = share_config [ "pid" ]
480+ pid = share_config . get ( "pid" )
439481 if not pid :
440482 console .print ("[yellow]No share link is active.[/]" )
441483 return
0 commit comments