feat: Added support for specifying the oauth port and client ID in MCP server configs
This commit is contained in:
@@ -132,7 +132,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some(url.to_string()),
|
url: Some(url.to_string()),
|
||||||
headers,
|
headers,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3698,7 +3698,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -158,7 +158,13 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let url = spec.url.as_deref().expect("validated: remote spec has url");
|
let url = spec.url.as_deref().expect("validated: remote spec has url");
|
||||||
mcp::oauth::run_mcp_oauth_flow(server_name, url, spec.oauth_client_id.as_deref()).await?;
|
mcp::oauth::run_mcp_oauth_flow(
|
||||||
|
server_name,
|
||||||
|
url,
|
||||||
|
spec.oauth.as_ref().and_then(|o| o.client_id.as_deref()),
|
||||||
|
spec.oauth.as_ref().and_then(|o| o.callback_port),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
println!("Authentication saved. '{server_name}' is now available for use.");
|
println!("Authentication saved. '{server_name}' is now available for use.");
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
+22
-14
@@ -57,6 +57,14 @@ pub(crate) struct McpServersConfig {
|
|||||||
pub mcp_servers: IndexMap<String, McpServer>,
|
pub mcp_servers: IndexMap<String, McpServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
pub(crate) struct McpOAuthConfig {
|
||||||
|
#[serde(rename = "clientId", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub client_id: Option<String>,
|
||||||
|
#[serde(rename = "callbackPort", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub callback_port: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub(crate) struct McpServer {
|
pub(crate) struct McpServer {
|
||||||
@@ -75,7 +83,7 @@ pub(crate) struct McpServer {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub headers: Option<IndexMap<String, String>>,
|
pub headers: Option<IndexMap<String, String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub oauth_client_id: Option<String>,
|
pub oauth: Option<McpOAuthConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McpServer {
|
impl McpServer {
|
||||||
@@ -110,10 +118,10 @@ impl McpServer {
|
|||||||
"MCP server '{name}' is missing a \"command\" field (required for stdio transport)"
|
"MCP server '{name}' is missing a \"command\" field (required for stdio transport)"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.url.is_some() || self.headers.is_some() || self.oauth_client_id.is_some() {
|
if self.url.is_some() || self.headers.is_some() || self.oauth.is_some() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"MCP server '{name}' has type \"stdio\" but also specifies remote fields \
|
"MCP server '{name}' has type \"stdio\" but also specifies remote fields \
|
||||||
(url/headers/oauth_client_id). Remove the remote fields or change the type to \"http\" or \"sse\"."
|
(url/headers/oauth). Remove the remote fields or change the type to \"http\" or \"sse\"."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,7 +519,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,7 +532,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some(url.to_string()),
|
url: Some(url.to_string()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +545,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some(url.to_string()),
|
url: Some(url.to_string()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +577,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -587,7 +595,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some("http://localhost".into()),
|
url: Some("http://localhost".into()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -607,7 +615,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: Some(headers),
|
headers: Some(headers),
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -632,7 +640,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -650,7 +658,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some("http://localhost".into()),
|
url: Some("http://localhost".into()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -668,7 +676,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: Some("http://localhost".into()),
|
url: Some("http://localhost".into()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -686,7 +694,7 @@ mod tests {
|
|||||||
cwd: Some("/tmp".into()),
|
cwd: Some("/tmp".into()),
|
||||||
url: Some("http://localhost".into()),
|
url: Some("http://localhost".into()),
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
@@ -711,7 +719,7 @@ mod tests {
|
|||||||
cwd: None,
|
cwd: None,
|
||||||
url: None,
|
url: None,
|
||||||
headers: None,
|
headers: None,
|
||||||
oauth_client_id: None,
|
oauth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = spec.validate("test").unwrap_err();
|
let err = spec.validate("test").unwrap_err();
|
||||||
|
|||||||
+3
-1
@@ -80,10 +80,12 @@ pub async fn run_mcp_oauth_flow(
|
|||||||
server_name: &str,
|
server_name: &str,
|
||||||
server_url: &str,
|
server_url: &str,
|
||||||
configured_client_id: Option<&str>,
|
configured_client_id: Option<&str>,
|
||||||
|
callback_port: Option<u16>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let metadata = discover_oauth_metadata(server_url).await?;
|
let metadata = discover_oauth_metadata(server_url).await?;
|
||||||
|
|
||||||
let listener = TcpListener::bind("127.0.0.1:0")?;
|
let bind_addr = format!("127.0.0.1:{}", callback_port.unwrap_or(0));
|
||||||
|
let listener = TcpListener::bind(&bind_addr)?;
|
||||||
let port = listener.local_addr()?.port();
|
let port = listener.local_addr()?.port();
|
||||||
drop(listener);
|
drop(listener);
|
||||||
let redirect_uri = format!("http://127.0.0.1:{port}/callback");
|
let redirect_uri = format!("http://127.0.0.1:{port}/callback");
|
||||||
|
|||||||
+13
-3
@@ -640,9 +640,19 @@ pub async fn run_repl_command(
|
|||||||
.url
|
.url
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.expect("validated: remote spec has url");
|
.expect("validated: remote spec has url");
|
||||||
let client_id = spec.oauth_client_id.as_deref();
|
let client_id = spec
|
||||||
mcp::oauth::run_mcp_oauth_flow(server_name, url, client_id)
|
.oauth
|
||||||
.await?;
|
.as_ref()
|
||||||
|
.and_then(|o| o.client_id.as_deref());
|
||||||
|
let callback_port =
|
||||||
|
spec.oauth.as_ref().and_then(|o| o.callback_port);
|
||||||
|
mcp::oauth::run_mcp_oauth_flow(
|
||||||
|
server_name,
|
||||||
|
url,
|
||||||
|
client_id,
|
||||||
|
callback_port,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
println!(
|
println!(
|
||||||
"Authentication saved. \
|
"Authentication saved. \
|
||||||
Restart Coyote to connect to '{server_name}'."
|
Restart Coyote to connect to '{server_name}'."
|
||||||
|
|||||||
Reference in New Issue
Block a user