Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use async_trait::async_trait;
use reqwest::{header::AUTHORIZATION, StatusCode};
use rust_mcp_sdk::{
auth::{
decode_token_header, Audience, AuthInfo, AuthenticationError, IntrospectionResponse,
JsonWebKeySet, OauthTokenVerifier,
decode_token_header, shared_http_client, Audience, AuthInfo, AuthenticationError,
IntrospectionResponse, JsonWebKeySet, OauthTokenVerifier,
},
mcp_http::error_message_from_response,
};
Expand Down Expand Up @@ -212,7 +212,7 @@ impl GenericOauthTokenVerifier {
}
};

let client = reqwest::Client::new();
let client = shared_http_client();

let response = client
.get(user_info_endpoint.to_owned())
Expand Down Expand Up @@ -255,7 +255,7 @@ impl GenericOauthTokenVerifier {
token: &str,
introspection_endpoint: &Url,
) -> Result<AuthInfo, AuthenticationError> {
let client = reqwest::Client::new();
let client = shared_http_client();

// Form data body
let mut form = HashMap::new();
Expand Down Expand Up @@ -343,7 +343,9 @@ impl GenericOauthTokenVerifier {
}

async fn populate_jwks(&self, jwks_uri: &Url) -> Result<(), AuthenticationError> {
let response = reqwest::get(jwks_uri.to_owned())
let response = shared_http_client()
.get(jwks_uri.to_owned())
.send()
.await
.map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
let jwks: JsonWebKeySet = response
Expand Down
19 changes: 19 additions & 0 deletions crates/rust-mcp-sdk/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,22 @@ pub use spec::Audience;
pub use spec::*;
#[cfg(feature = "auth")]
pub use token_verifier::*;

#[cfg(feature = "auth")]
use std::sync::LazyLock;

/// Process-wide shared `reqwest::Client` used for OAuth discovery, metadata, and
/// JWKS fetches.
///
/// Constructing a new `Client` per call creates a fresh connection pool and TLS
/// configuration on every request. Cloning this shared client (cheap; it is
/// `Arc`-backed) reuses the same pool, which matters during key-rotation events
/// where many verifications fetch JWKS concurrently.
#[cfg(feature = "auth")]
static SHARED_HTTP_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);

/// Returns a clone of the process-wide shared [`reqwest::Client`].
#[cfg(feature = "auth")]
pub fn shared_http_client() -> reqwest::Client {
SHARED_HTTP_CLIENT.clone()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use async_trait::async_trait;
use bytes::Bytes;
use http::{header::CONTENT_TYPE, StatusCode};
use http_body_util::{BodyExt, Full};
use reqwest::Client;
use std::{collections::HashMap, sync::Arc};

/// Represents a **Remote OAuth authentication provider** integrated with the MCP server.
Expand Down Expand Up @@ -68,7 +67,7 @@ impl RemoteAuthProvider {
token_verifier: Box<dyn OauthTokenVerifier>,
required_scopes: Option<Vec<String>>,
) -> Result<Self, reqwest::Error> {
let client = Client::new();
let client = crate::auth::shared_http_client();

let auth_server_meta = client
.get(authorization_server_metadata_url)
Expand Down
3 changes: 1 addition & 2 deletions crates/rust-mcp-sdk/src/auth/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
error::McpSdkError,
utils::join_url,
};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
Expand Down Expand Up @@ -139,7 +138,7 @@ impl<'a> AuthMetadataBuilder<'a> {
where
S: Into<Cow<'a, str>>,
{
let client = Client::new();
let client = crate::auth::shared_http_client();
let json: Value = client
.get(discovery_url)
.send()
Expand Down
3 changes: 1 addition & 2 deletions crates/rust-mcp-sdk/src/auth/spec/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::{
error::McpSdkError,
mcp_http::url_base,
};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
Expand Down Expand Up @@ -151,7 +150,7 @@ impl AuthorizationServerMetadata {
/// to RFC 8414 (OAuth 2.0 Authorization Server Metadata) or OpenID Connect Discovery 1.0.
///
pub async fn from_discovery_url(discovery_url: &str) -> Result<Self, McpSdkError> {
let client = Client::new();
let client = crate::auth::shared_http_client();
let metadata = client
.get(discovery_url)
.send()
Expand Down
Loading