Skip to content

Commit eb1c8a8

Browse files
feat: add configurable OAuth redirect uri (#3124)
1 parent da7689a commit eb1c8a8

File tree

4 files changed

+52
-3
lines changed

4 files changed

+52
-3
lines changed

crates/chat-cli/src/cli/chat/tools/custom_tool.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ impl Default for TransportType {
4545
}
4646
}
4747

48+
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)]
49+
#[serde(rename_all = "camelCase")]
50+
pub struct OAuthConfig {
51+
/// Custom redirect URI for OAuth flow (e.g., "127.0.0.1:7778")
52+
/// If not specified, a random available port will be assigned by the OS
53+
#[serde(skip_serializing_if = "Option::is_none")]
54+
pub redirect_uri: Option<String>,
55+
}
56+
4857
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)]
4958
#[serde(rename_all = "camelCase")]
5059
pub struct CustomToolConfig {
@@ -60,6 +69,9 @@ pub struct CustomToolConfig {
6069
/// Scopes with which oauth is done
6170
#[serde(default = "get_default_scopes")]
6271
pub oauth_scopes: Vec<String>,
72+
/// OAuth configuration for this server
73+
#[serde(skip_serializing_if = "Option::is_none")]
74+
pub oauth: Option<OAuthConfig>,
6375
/// The command string used to initialize the mcp server
6476
#[serde(default)]
6577
pub command: String,

crates/chat-cli/src/mcp_client/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,11 +453,12 @@ impl McpClientService {
453453
url,
454454
headers,
455455
oauth_scopes: scopes,
456+
oauth,
456457
timeout,
457458
..
458459
} = &self.config;
459460

460-
let http_service_builder = HttpServiceBuilder::new(url, os, url, *timeout, scopes, headers, messenger);
461+
let http_service_builder = HttpServiceBuilder::new(url, os, url, *timeout, scopes, headers, oauth, messenger);
461462

462463
let (service, auth_client_wrapper) = http_service_builder.try_build(&self).await?;
463464

crates/chat-cli/src/mcp_client/oauth_util.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ pub struct HttpServiceBuilder<'a> {
196196
pub timeout: u64,
197197
pub scopes: &'a [String],
198198
pub headers: &'a HashMap<String, String>,
199+
pub oauth_config: &'a Option<crate::cli::chat::tools::custom_tool::OAuthConfig>,
199200
pub messenger: &'a dyn Messenger,
200201
}
201202

@@ -207,6 +208,7 @@ impl<'a> HttpServiceBuilder<'a> {
207208
timeout: u64,
208209
scopes: &'a [String],
209210
headers: &'a HashMap<String, String>,
211+
oauth_config: &'a Option<crate::cli::chat::tools::custom_tool::OAuthConfig>,
210212
messenger: &'a dyn Messenger,
211213
) -> Self {
212214
Self {
@@ -216,6 +218,7 @@ impl<'a> HttpServiceBuilder<'a> {
216218
timeout,
217219
scopes,
218220
headers,
221+
oauth_config,
219222
messenger,
220223
}
221224
}
@@ -231,6 +234,7 @@ impl<'a> HttpServiceBuilder<'a> {
231234
timeout,
232235
scopes,
233236
headers,
237+
oauth_config,
234238
messenger,
235239
} = self;
236240

@@ -292,7 +296,9 @@ impl<'a> HttpServiceBuilder<'a> {
292296
cred_full_path.clone(),
293297
reg_full_path.clone(),
294298
scopes,
299+
oauth_config,
295300
messenger,
301+
os,
296302
)
297303
.await?;
298304

@@ -452,7 +458,9 @@ async fn get_auth_manager(
452458
cred_full_path: PathBuf,
453459
reg_full_path: PathBuf,
454460
scopes: &[String],
461+
oauth_config: &Option<crate::cli::chat::tools::custom_tool::OAuthConfig>,
455462
messenger: &dyn Messenger,
463+
os: &Os,
456464
) -> Result<AuthorizationManager, OauthUtilError> {
457465
let cred_as_bytes = tokio::fs::read(&cred_full_path).await;
458466
let reg_as_bytes = tokio::fs::read(&reg_full_path).await;
@@ -474,7 +482,7 @@ async fn get_auth_manager(
474482
_ => {
475483
info!("Error reading cached credentials");
476484
debug!("## mcp: cache read failed. constructing auth manager from scratch");
477-
let (am, redirect_uri) = get_auth_manager_impl(oauth_state, scopes, messenger).await?;
485+
let (am, redirect_uri) = get_auth_manager_impl(oauth_state, scopes, oauth_config, messenger, os).await?;
478486

479487
// Client registration is done in [start_authorization]
480488
// If we have gotten past that point that means we have the info to persist the
@@ -509,9 +517,21 @@ async fn get_auth_manager(
509517
async fn get_auth_manager_impl(
510518
mut oauth_state: OAuthState,
511519
scopes: &[String],
520+
oauth_config: &Option<crate::cli::chat::tools::custom_tool::OAuthConfig>,
512521
messenger: &dyn Messenger,
522+
_os: &Os,
513523
) -> Result<(AuthorizationManager, String), OauthUtilError> {
514-
let socket_addr = SocketAddr::from(([127, 0, 0, 1], 0));
524+
// Get port from per-server oauth config, or use 0 for random port assignment
525+
let port = oauth_config
526+
.as_ref()
527+
.and_then(|cfg| cfg.redirect_uri.as_ref())
528+
.and_then(|uri| {
529+
// Parse port from redirect_uri like "127.0.0.1:7778" or ":7778"
530+
uri.split(':').last().and_then(|p| p.parse::<u16>().ok())
531+
})
532+
.unwrap_or(0); // Port 0 = OS assigns random available port
533+
534+
let socket_addr = SocketAddr::from(([127, 0, 0, 1], port));
515535
let cancellation_token = tokio_util::sync::CancellationToken::new();
516536
let (tx, rx) = tokio::sync::oneshot::channel::<(String, String)>();
517537

schemas/agent-v1.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@
9494
"offline_access"
9595
]
9696
},
97+
"oauth": {
98+
"description": "OAuth configuration for this server",
99+
"type": [
100+
"object",
101+
"null"
102+
],
103+
"properties": {
104+
"redirectUri": {
105+
"description": "Custom redirect URI for OAuth flow (e.g., \"127.0.0.1:7778\"). If not specified, a random available port will be assigned by the OS",
106+
"type": [
107+
"string",
108+
"null"
109+
]
110+
}
111+
}
112+
},
97113
"command": {
98114
"description": "The command string used to initialize the mcp server",
99115
"type": "string",

0 commit comments

Comments
 (0)