@@ -53,8 +53,8 @@ const MAX_COMMAND_ARGS: usize = 100;
5353/// Maximum length for a single command argument
5454const MAX_COMMAND_ARG_LENGTH : usize = 4096 ;
5555
56- /// Allowed URL schemes for MCP HTTP transport
57- const ALLOWED_URL_SCHEMES : & [ & str ] = & [ "http" , "https" ] ;
56+ /// Allowed URL schemes for MCP HTTP transport (including WebSocket)
57+ const ALLOWED_URL_SCHEMES : & [ & str ] = & [ "http" , "https" , "ws" , "wss" ] ;
5858
5959/// Dangerous URL patterns that should be blocked
6060const BLOCKED_URL_PATTERNS : & [ & str ] = & [
@@ -87,14 +87,19 @@ const BLOCKED_URL_PATTERNS: &[&str] = &[
8787 "172.31." , // Private network end
8888] ;
8989
90- /// Validates and sanitizes a URL for MCP HTTP transport.
90+ /// Validates and sanitizes a URL for MCP HTTP/WebSocket transport.
9191///
9292/// # Validation Rules:
9393/// - Must not exceed maximum length
94- /// - Must use allowed schemes (http/https)
95- /// - Must not contain dangerous patterns
94+ /// - Must use allowed schemes (http/https/ws/wss )
95+ /// - Must not contain dangerous patterns (unless allow_local is true)
9696/// - Must be a valid URL format
9797fn validate_url ( url : & str ) -> Result < ( ) > {
98+ validate_url_internal ( url, false )
99+ }
100+
101+ /// Validates URL with option to allow local addresses.
102+ fn validate_url_internal ( url : & str , allow_local : bool ) -> Result < ( ) > {
98103 // Check length
99104 if url. is_empty ( ) {
100105 bail ! ( "URL cannot be empty" ) ;
@@ -113,25 +118,27 @@ fn validate_url(url: &str) -> Result<()> {
113118
114119 let url_lower = url. to_lowercase ( ) ;
115120
116- // Check scheme - must start with http:// or https ://
121+ // Check scheme - must start with http://, https://, ws://, or wss ://
117122 let has_valid_scheme = ALLOWED_URL_SCHEMES
118123 . iter ( )
119124 . any ( |& scheme| url_lower. starts_with ( & format ! ( "{}://" , scheme) ) ) ;
120125 if !has_valid_scheme {
121126 bail ! (
122- "URL must start with http:// or https ://. Got: {}" ,
127+ "URL must start with http://, https://, ws://, or wss ://. Got: {}" ,
123128 url. chars( ) . take( 20 ) . collect:: <String >( )
124129 ) ;
125130 }
126131
127- // Check for blocked patterns
128- for pattern in BLOCKED_URL_PATTERNS {
129- if url_lower. contains ( pattern) {
130- bail ! (
131- "URL contains blocked pattern '{}'. For security, local/private network URLs are not allowed by default. \
132- Use environment variables for local development servers.",
133- pattern
134- ) ;
132+ // Check for blocked patterns (skip if allow_local is true)
133+ if !allow_local {
134+ for pattern in BLOCKED_URL_PATTERNS {
135+ if url_lower. contains ( pattern) {
136+ bail ! (
137+ "URL contains blocked pattern '{}'. For security, local/private network URLs are not allowed by default. \
138+ Use --allow-local flag to enable local development servers.",
139+ pattern
140+ ) ;
141+ }
135142 }
136143 }
137144
@@ -140,6 +147,10 @@ fn validate_url(url: &str) -> Result<()> {
140147 & url[ 8 ..]
141148 } else if url_lower. starts_with ( "http://" ) {
142149 & url[ 7 ..]
150+ } else if url_lower. starts_with ( "wss://" ) {
151+ & url[ 6 ..]
152+ } else if url_lower. starts_with ( "ws://" ) {
153+ & url[ 5 ..]
143154 } else {
144155 bail ! ( "Invalid URL scheme" ) ;
145156 } ;
@@ -310,6 +321,10 @@ pub struct ListArgs {
310321 /// Output the configured servers as JSON.
311322 #[ arg( long) ]
312323 pub json : bool ,
324+
325+ /// Show all servers including disabled ones.
326+ #[ arg( long) ]
327+ pub all : bool ,
313328}
314329
315330/// Arguments for get command.
@@ -344,6 +359,13 @@ pub struct AddArgs {
344359 #[ arg( long, short = 'f' ) ]
345360 pub force : bool ,
346361
362+ /// Allow localhost and private network URLs (for local development).
363+ /// By default, URLs containing localhost, 127.0.0.1, or private network
364+ /// addresses are blocked for security. Use this flag to enable local
365+ /// development servers.
366+ #[ arg( long) ]
367+ pub allow_local : bool ,
368+
347369 #[ command( flatten) ]
348370 pub transport_args : AddMcpTransportArgs ,
349371}
@@ -741,6 +763,7 @@ async fn run_add(args: AddArgs) -> Result<()> {
741763 let AddArgs {
742764 name,
743765 force,
766+ allow_local,
744767 transport_args,
745768 } = args;
746769
@@ -925,8 +948,8 @@ command = "{command_bin_escaped}"
925948 } ) ,
926949 ..
927950 } => {
928- // Validate URL format and safety
929- validate_url ( & url) ?;
951+ // Validate URL format and safety (allow_local bypasses localhost/private network check)
952+ validate_url_internal ( & url, allow_local ) ?;
930953
931954 // Validate bearer token env var if provided
932955 if let Some ( ref token_var) = bearer_token_env_var {
0 commit comments