@@ -4,6 +4,7 @@ use anyhow::Context;
4
4
use anyhow:: Result ;
5
5
use anyhow:: anyhow;
6
6
use anyhow:: bail;
7
+ use clap:: ArgGroup ;
7
8
use codex_common:: CliConfigOverrides ;
8
9
use codex_core:: config:: Config ;
9
10
use codex_core:: config:: ConfigOverrides ;
@@ -77,13 +78,61 @@ pub struct AddArgs {
77
78
/// Name for the MCP server configuration.
78
79
pub name : String ,
79
80
80
- /// Environment variables to set when launching the server.
81
- # [ arg ( long , value_parser = parse_env_pair , value_name = "KEY=VALUE" ) ]
82
- pub env : Vec < ( String , String ) > ,
81
+ # [ command ( flatten ) ]
82
+ pub transport_args : AddMcpTransportArgs ,
83
+ }
83
84
85
+ #[ derive( Debug , clap:: Args ) ]
86
+ #[ command(
87
+ group(
88
+ ArgGroup :: new( "transport" )
89
+ . args( [ "command" , "url" ] )
90
+ . required( true )
91
+ . multiple( false )
92
+ )
93
+ ) ]
94
+ pub struct AddMcpTransportArgs {
95
+ #[ command( flatten) ]
96
+ pub stdio : Option < AddMcpStdioArgs > ,
97
+
98
+ #[ command( flatten) ]
99
+ pub streamable_http : Option < AddMcpStreamableHttpArgs > ,
100
+ }
101
+
102
+ #[ derive( Debug , clap:: Args ) ]
103
+ pub struct AddMcpStdioArgs {
84
104
/// Command to launch the MCP server.
85
- #[ arg( trailing_var_arg = true , num_args = 1 ..) ]
105
+ /// Use --url for a streamable HTTP server.
106
+ #[ arg(
107
+ trailing_var_arg = true ,
108
+ num_args = 0 ..,
109
+ ) ]
86
110
pub command : Vec < String > ,
111
+
112
+ /// Environment variables to set when launching the server.
113
+ /// Only valid with stdio servers.
114
+ #[ arg(
115
+ long,
116
+ value_parser = parse_env_pair,
117
+ value_name = "KEY=VALUE" ,
118
+ ) ]
119
+ pub env : Vec < ( String , String ) > ,
120
+ }
121
+
122
+ #[ derive( Debug , clap:: Args ) ]
123
+ pub struct AddMcpStreamableHttpArgs {
124
+ /// URL for a streamable HTTP MCP server.
125
+ #[ arg( long) ]
126
+ pub url : String ,
127
+
128
+ /// Optional environment variable to read for a bearer token.
129
+ /// Only valid with streamable HTTP servers.
130
+ #[ arg(
131
+ long = "bearer-token-env-var" ,
132
+ value_name = "ENV_VAR" ,
133
+ requires = "url"
134
+ ) ]
135
+ pub bearer_token_env_var : Option < String > ,
87
136
}
88
137
89
138
#[ derive( Debug , clap:: Parser ) ]
@@ -140,37 +189,51 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
140
189
// Validate any provided overrides even though they are not currently applied.
141
190
config_overrides. parse_overrides ( ) . map_err ( |e| anyhow ! ( e) ) ?;
142
191
143
- let AddArgs { name, env, command } = add_args;
192
+ let AddArgs {
193
+ name,
194
+ transport_args,
195
+ } = add_args;
144
196
145
197
validate_server_name ( & name) ?;
146
198
147
- let mut command_parts = command. into_iter ( ) ;
148
- let command_bin = command_parts
149
- . next ( )
150
- . ok_or_else ( || anyhow ! ( "command is required" ) ) ?;
151
- let command_args: Vec < String > = command_parts. collect ( ) ;
152
-
153
- let env_map = if env. is_empty ( ) {
154
- None
155
- } else {
156
- let mut map = HashMap :: new ( ) ;
157
- for ( key, value) in env {
158
- map. insert ( key, value) ;
159
- }
160
- Some ( map)
161
- } ;
162
-
163
199
let codex_home = find_codex_home ( ) . context ( "failed to resolve CODEX_HOME" ) ?;
164
200
let mut servers = load_global_mcp_servers ( & codex_home)
165
201
. await
166
202
. with_context ( || format ! ( "failed to load MCP servers from {}" , codex_home. display( ) ) ) ?;
167
203
168
- let new_entry = McpServerConfig {
169
- transport : McpServerTransportConfig :: Stdio {
170
- command : command_bin,
171
- args : command_args,
172
- env : env_map,
204
+ let transport = match transport_args {
205
+ AddMcpTransportArgs {
206
+ stdio : Some ( stdio) , ..
207
+ } => {
208
+ let mut command_parts = stdio. command . into_iter ( ) ;
209
+ let command_bin = command_parts
210
+ . next ( )
211
+ . ok_or_else ( || anyhow ! ( "command is required" ) ) ?;
212
+ let command_args: Vec < String > = command_parts. collect ( ) ;
213
+
214
+ let env_map = if stdio. env . is_empty ( ) {
215
+ None
216
+ } else {
217
+ Some ( stdio. env . into_iter ( ) . collect :: < HashMap < _ , _ > > ( ) )
218
+ } ;
219
+ McpServerTransportConfig :: Stdio {
220
+ command : command_bin,
221
+ args : command_args,
222
+ env : env_map,
223
+ }
224
+ }
225
+ AddMcpTransportArgs {
226
+ streamable_http : Some ( streamable_http) ,
227
+ ..
228
+ } => McpServerTransportConfig :: StreamableHttp {
229
+ url : streamable_http. url ,
230
+ bearer_token_env_var : streamable_http. bearer_token_env_var ,
173
231
} ,
232
+ AddMcpTransportArgs { .. } => bail ! ( "exactly one of --command or --url must be provided" ) ,
233
+ } ;
234
+
235
+ let new_entry = McpServerConfig {
236
+ transport,
174
237
startup_timeout_sec : None ,
175
238
tool_timeout_sec : None ,
176
239
} ;
@@ -288,11 +351,14 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
288
351
"args" : args,
289
352
"env" : env,
290
353
} ) ,
291
- McpServerTransportConfig :: StreamableHttp { url, bearer_token } => {
354
+ McpServerTransportConfig :: StreamableHttp {
355
+ url,
356
+ bearer_token_env_var,
357
+ } => {
292
358
serde_json:: json!( {
293
359
"type" : "streamable_http" ,
294
360
"url" : url,
295
- "bearer_token " : bearer_token ,
361
+ "bearer_token_env_var " : bearer_token_env_var ,
296
362
} )
297
363
}
298
364
} ;
@@ -345,13 +411,15 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
345
411
} ;
346
412
stdio_rows. push ( [ name. clone ( ) , command. clone ( ) , args_display, env_display] ) ;
347
413
}
348
- McpServerTransportConfig :: StreamableHttp { url, bearer_token } => {
349
- let has_bearer = if bearer_token. is_some ( ) {
350
- "True"
351
- } else {
352
- "False"
353
- } ;
354
- http_rows. push ( [ name. clone ( ) , url. clone ( ) , has_bearer. into ( ) ] ) ;
414
+ McpServerTransportConfig :: StreamableHttp {
415
+ url,
416
+ bearer_token_env_var,
417
+ } => {
418
+ http_rows. push ( [
419
+ name. clone ( ) ,
420
+ url. clone ( ) ,
421
+ bearer_token_env_var. clone ( ) . unwrap_or ( "-" . to_string ( ) ) ,
422
+ ] ) ;
355
423
}
356
424
}
357
425
}
@@ -396,7 +464,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
396
464
}
397
465
398
466
if !http_rows. is_empty ( ) {
399
- let mut widths = [ "Name" . len ( ) , "Url" . len ( ) , "Has Bearer Token" . len ( ) ] ;
467
+ let mut widths = [ "Name" . len ( ) , "Url" . len ( ) , "Bearer Token Env Var " . len ( ) ] ;
400
468
for row in & http_rows {
401
469
for ( i, cell) in row. iter ( ) . enumerate ( ) {
402
470
widths[ i] = widths[ i] . max ( cell. len ( ) ) ;
@@ -407,7 +475,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
407
475
"{:<name_w$} {:<url_w$} {:<token_w$}" ,
408
476
"Name" ,
409
477
"Url" ,
410
- "Has Bearer Token" ,
478
+ "Bearer Token Env Var " ,
411
479
name_w = widths[ 0 ] ,
412
480
url_w = widths[ 1 ] ,
413
481
token_w = widths[ 2 ] ,
@@ -447,10 +515,13 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
447
515
"args" : args,
448
516
"env" : env,
449
517
} ) ,
450
- McpServerTransportConfig :: StreamableHttp { url, bearer_token } => serde_json:: json!( {
518
+ McpServerTransportConfig :: StreamableHttp {
519
+ url,
520
+ bearer_token_env_var,
521
+ } => serde_json:: json!( {
451
522
"type" : "streamable_http" ,
452
523
"url" : url,
453
- "bearer_token " : bearer_token ,
524
+ "bearer_token_env_var " : bearer_token_env_var ,
454
525
} ) ,
455
526
} ;
456
527
let output = serde_json:: to_string_pretty ( & serde_json:: json!( {
@@ -493,11 +564,14 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
493
564
} ;
494
565
println ! ( " env: {env_display}" ) ;
495
566
}
496
- McpServerTransportConfig :: StreamableHttp { url, bearer_token } => {
567
+ McpServerTransportConfig :: StreamableHttp {
568
+ url,
569
+ bearer_token_env_var,
570
+ } => {
497
571
println ! ( " transport: streamable_http" ) ;
498
572
println ! ( " url: {url}" ) ;
499
- let bearer = bearer_token . as_deref ( ) . unwrap_or ( "-" ) ;
500
- println ! ( " bearer_token : {bearer }" ) ;
573
+ let env_var = bearer_token_env_var . as_deref ( ) . unwrap_or ( "-" ) ;
574
+ println ! ( " bearer_token_env_var : {env_var }" ) ;
501
575
}
502
576
}
503
577
if let Some ( timeout) = server. startup_timeout_sec {
0 commit comments