@@ -8,6 +8,7 @@ use crossterm::{
8
8
style,
9
9
} ;
10
10
use eyre:: Result ;
11
+ use regex:: Regex ;
11
12
use schemars:: JsonSchema ;
12
13
use serde:: {
13
14
Deserialize ,
@@ -23,6 +24,7 @@ use crate::cli::agent::{
23
24
} ;
24
25
use crate :: cli:: chat:: CONTINUATION_LINE ;
25
26
use crate :: cli:: chat:: token_counter:: TokenCounter ;
27
+ use crate :: os:: Os ;
26
28
use crate :: mcp_client:: {
27
29
Client as McpClient ,
28
30
ClientConfig as McpClientConfig ,
@@ -35,7 +37,6 @@ use crate::mcp_client::{
35
37
StdioTransport ,
36
38
ToolCallResult ,
37
39
} ;
38
- use crate :: os:: Os ;
39
40
40
41
// TODO: support http transport type
41
42
#[ derive( Clone , Serialize , Deserialize , Debug , Eq , PartialEq , JsonSchema ) ]
@@ -63,6 +64,26 @@ pub fn default_timeout() -> u64 {
63
64
120 * 1000
64
65
}
65
66
67
+ /// Substitutes environment variables in the format ${env:VAR_NAME} with their actual values
68
+ fn substitute_env_vars ( input : & str , env : & crate :: os:: Env ) -> String {
69
+ // Create a regex to match ${env:VAR_NAME} pattern
70
+ let re = Regex :: new ( r"\$\{env:([^}]+)\}" ) . unwrap ( ) ;
71
+
72
+ re. replace_all ( input, |caps : & regex:: Captures < ' _ > | {
73
+ let var_name = & caps[ 1 ] ;
74
+ env. get ( var_name) . unwrap_or_else ( |_| format ! ( "${{{}}}" , var_name) )
75
+ } )
76
+ . to_string ( )
77
+ }
78
+
79
+ /// Process a HashMap of environment variables, substituting any ${env:VAR_NAME} patterns
80
+ /// with their actual values from the environment
81
+ fn process_env_vars ( env_vars : & mut HashMap < String , String > , env : & crate :: os:: Env ) {
82
+ for ( _, value) in env_vars. iter_mut ( ) {
83
+ * value = substitute_env_vars ( value, env) ;
84
+ }
85
+ }
86
+
66
87
#[ derive( Debug ) ]
67
88
pub enum CustomToolClient {
68
89
Stdio {
@@ -75,7 +96,7 @@ pub enum CustomToolClient {
75
96
76
97
impl CustomToolClient {
77
98
// TODO: add support for http transport
78
- pub fn from_config ( server_name : String , config : CustomToolConfig ) -> Result < Self > {
99
+ pub fn from_config ( server_name : String , config : CustomToolConfig , os : & crate :: os :: Os ) -> Result < Self > {
79
100
let CustomToolConfig {
80
101
command,
81
102
args,
@@ -84,6 +105,13 @@ impl CustomToolClient {
84
105
disabled : _,
85
106
..
86
107
} = config;
108
+
109
+ // Process environment variables if present
110
+ let processed_env = env. map ( |mut env_vars| {
111
+ process_env_vars ( & mut env_vars, & os. env ) ;
112
+ env_vars
113
+ } ) ;
114
+
87
115
let mcp_client_config = McpClientConfig {
88
116
server_name : server_name. clone ( ) ,
89
117
bin_path : command. clone ( ) ,
@@ -93,7 +121,7 @@ impl CustomToolClient {
93
121
"name" : "Q CLI Chat" ,
94
122
"version" : "1.0.0"
95
123
} ) ,
96
- env,
124
+ env : processed_env ,
97
125
} ;
98
126
let client = McpClient :: < JsonRpcStdioTransport > :: from_config ( mcp_client_config) ?;
99
127
Ok ( CustomToolClient :: Stdio {
@@ -279,3 +307,52 @@ impl CustomTool {
279
307
}
280
308
}
281
309
}
310
+
311
+ #[ cfg( test) ]
312
+ mod tests {
313
+ use super :: * ;
314
+
315
+ #[ tokio:: test]
316
+ async fn test_substitute_env_vars ( ) {
317
+ // Set a test environment variable
318
+ let os = Os :: new ( ) . await . unwrap ( ) ;
319
+ unsafe {
320
+ os. env . set_var ( "TEST_VAR" , "test_value" ) ;
321
+ }
322
+
323
+ // Test basic substitution
324
+ assert_eq ! ( substitute_env_vars( "Value is ${env:TEST_VAR}" , & os. env) , "Value is test_value" ) ;
325
+
326
+ // Test multiple substitutions
327
+ assert_eq ! (
328
+ substitute_env_vars( "${env:TEST_VAR} and ${env:TEST_VAR}" , & os. env) ,
329
+ "test_value and test_value"
330
+ ) ;
331
+
332
+ // Test non-existent variable
333
+ assert_eq ! ( substitute_env_vars( "${env:NON_EXISTENT_VAR}" , & os. env) , "${NON_EXISTENT_VAR}" ) ;
334
+
335
+ // Test mixed content
336
+ assert_eq ! (
337
+ substitute_env_vars( "Prefix ${env:TEST_VAR} suffix" , & os. env) ,
338
+ "Prefix test_value suffix"
339
+ ) ;
340
+ }
341
+
342
+ #[ tokio:: test]
343
+ async fn test_process_env_vars ( ) {
344
+ let os = Os :: new ( ) . await . unwrap ( ) ;
345
+ unsafe {
346
+ os. env . set_var ( "TEST_VAR" , "test_value" ) ;
347
+ }
348
+
349
+ let mut env_vars = HashMap :: new ( ) ;
350
+ env_vars. insert ( "KEY1" . to_string ( ) , "Value is ${env:TEST_VAR}" . to_string ( ) ) ;
351
+ env_vars. insert ( "KEY2" . to_string ( ) , "No substitution" . to_string ( ) ) ;
352
+
353
+ process_env_vars ( & mut env_vars, & os. env ) ;
354
+
355
+ assert_eq ! ( env_vars. get( "KEY1" ) . unwrap( ) , "Value is test_value" ) ;
356
+ assert_eq ! ( env_vars. get( "KEY2" ) . unwrap( ) , "No substitution" ) ;
357
+ }
358
+ }
0 commit comments