Skip to content

feat: mcp sdk conformance#687

Merged
alexhancock merged 1 commit intomainfrom
alexhancock/conformance
Feb 26, 2026
Merged

feat: mcp sdk conformance#687
alexhancock merged 1 commit intomainfrom
alexhancock/conformance

Conversation

@alexhancock
Copy link
Contributor

@alexhancock alexhancock requested a review from a team as a code owner February 25, 2026 19:29
@github-actions github-actions bot added T-documentation Documentation improvements T-dependencies Dependencies related changes T-config Configuration file changes T-core Core library changes T-model Model/data structure changes T-transport Transport layer changes labels Feb 25, 2026
let http = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()?;
let resp = http.get(&auth_url).send().await?;

Check failure

Code scanning / CodeQL

Cleartext transmission of sensitive information

This 'get' operation transmits data which may contain unencrypted sensitive data from [... .as_ref()](1).

Copilot Autofix

AI 2 days ago

In general, this problem is fixed by enforcing that sensitive information is only sent over encrypted transport. For HTTP client code, that typically means validating that URLs are HTTPS before sending a request, and failing otherwise. This removes reliance on external configuration and prevents accidental use of insecure endpoints.

For this specific case, the best fix without changing the functional behavior is to validate that auth_url uses the https scheme before calling http.get. If the authorization server were misconfigured to use http, the client will now fail fast instead of leaking the authorization request over cleartext. Concretely, in conformance/src/bin/client.rs inside perform_oauth_flow_preregistered, after obtaining auth_url on line 290, we should parse it as a url::Url, check that url.scheme() == "https", and only then pass it to reqwest::Client::get. This uses the already-imported url crate (as evidenced by later use of url::Url::parse(location)?;), so no new dependencies are required. The rest of the flow (redirect handling, extracting code and state, exchanging for a token) remains unchanged.

Suggested changeset 1
conformance/src/bin/client.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/conformance/src/bin/client.rs b/conformance/src/bin/client.rs
--- a/conformance/src/bin/client.rs
+++ b/conformance/src/bin/client.rs
@@ -288,12 +288,21 @@
     let scopes = manager.select_scopes(None, &[]);
     let scope_refs: Vec<&str> = scopes.iter().map(|s| s.as_str()).collect();
     let auth_url = manager.get_authorization_url(&scope_refs).await?;
+    let auth_url_parsed = url::Url::parse(&auth_url)?;
 
+    // Ensure we only send sensitive OAuth data over HTTPS
+    if auth_url_parsed.scheme() != "https" {
+        return Err(anyhow::anyhow!(
+            "Insecure authorization URL scheme: {}",
+            auth_url_parsed.scheme()
+        ));
+    }
+
     // Headless redirect
     let http = reqwest::Client::builder()
         .redirect(reqwest::redirect::Policy::none())
         .build()?;
-    let resp = http.get(&auth_url).send().await?;
+    let resp = http.get(auth_url_parsed.as_str()).send().await?;
     let location = resp
         .headers()
         .get("location")
EOF
@@ -288,12 +288,21 @@
let scopes = manager.select_scopes(None, &[]);
let scope_refs: Vec<&str> = scopes.iter().map(|s| s.as_str()).collect();
let auth_url = manager.get_authorization_url(&scope_refs).await?;
let auth_url_parsed = url::Url::parse(&auth_url)?;

// Ensure we only send sensitive OAuth data over HTTPS
if auth_url_parsed.scheme() != "https" {
return Err(anyhow::anyhow!(
"Insecure authorization URL scheme: {}",
auth_url_parsed.scheme()
));
}

// Headless redirect
let http = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()?;
let resp = http.get(&auth_url).send().await?;
let resp = http.get(auth_url_parsed.as_str()).send().await?;
let location = resp
.headers()
.get("location")
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

let http = reqwest::Client::new();
let resp = http
.post(&token_endpoint)

Check failure

Code scanning / CodeQL

Cleartext transmission of sensitive information

This 'post' operation transmits data which may contain unencrypted sensitive data from [self.discover_oauth_server_via_resource_metadata()](1). This 'post' operation transmits data which may contain unencrypted sensitive data from [self.try_discover_oauth_server(...)](2).

Copilot Autofix

AI 2 days ago

In general, the issue is best fixed by ensuring that sensitive information like client_secret is only ever sent over an encrypted channel (HTTPS). Since the token endpoint URL is discovered at runtime and could be attacker‑controlled or misconfigured, the client must validate that the URL uses a secure scheme before sending credentials, and fail if it does not.

The most direct fix, without changing overall functionality, is: after discovering metadata in run_client_credentials_basic, validate that metadata.token_endpoint uses the https scheme. If it is not HTTPS (e.g., missing scheme, HTTP, or anything else), return an error instead of sending the request. To implement this, we can parse token_endpoint with the standard url crate, or (to avoid new dependencies) with Rust’s standard reqwest::Url type, since reqwest is already in use. We only modify conformance/src/bin/client.rs: after let token_endpoint = metadata.token_endpoint.clone(); we parse it as a reqwest::Url, verify url.scheme() == "https", and abort with an error if not. Then we call .post(url) instead of .post(&token_endpoint), so we know the checked URL is what is used for the request. No changes are needed in crates/rmcp/src/transport/auth.rs for this fix.

Concretely, in run_client_credentials_basic:

  • Add parsing: let token_endpoint_url = reqwest::Url::parse(&token_endpoint)?;
  • Add a scheme check: if token_endpoint_url.scheme() != "https", return an anyhow::Error stating that a secure HTTPS token endpoint is required.
  • Use token_endpoint_url in the .post(...) call.
    No new imports are needed because reqwest::Url is in the same crate as reqwest::Client and can be referenced as reqwest::Url.
Suggested changeset 1
conformance/src/bin/client.rs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/conformance/src/bin/client.rs b/conformance/src/bin/client.rs
--- a/conformance/src/bin/client.rs
+++ b/conformance/src/bin/client.rs
@@ -570,9 +570,18 @@
     let token_endpoint = metadata.token_endpoint.clone();
     manager.set_metadata(metadata);
 
+    // Ensure that we only send client credentials over HTTPS.
+    let token_endpoint_url = reqwest::Url::parse(&token_endpoint)?;
+    if token_endpoint_url.scheme() != "https" {
+        return Err(anyhow::anyhow!(
+            "Insecure token endpoint URL ({}); HTTPS is required for client credentials",
+            token_endpoint_url
+        ));
+    }
+
     let http = reqwest::Client::new();
     let resp = http
-        .post(&token_endpoint)
+        .post(token_endpoint_url)
         .basic_auth(client_id, Some(client_secret))
         .header("content-type", "application/x-www-form-urlencoded")
         .body("grant_type=client_credentials")
EOF
@@ -570,9 +570,18 @@
let token_endpoint = metadata.token_endpoint.clone();
manager.set_metadata(metadata);

// Ensure that we only send client credentials over HTTPS.
let token_endpoint_url = reqwest::Url::parse(&token_endpoint)?;
if token_endpoint_url.scheme() != "https" {
return Err(anyhow::anyhow!(
"Insecure token endpoint URL ({}); HTTPS is required for client credentials",
token_endpoint_url
));
}

let http = reqwest::Client::new();
let resp = http
.post(&token_endpoint)
.post(token_endpoint_url)
.basic_auth(client_id, Some(client_secret))
.header("content-type", "application/x-www-form-urlencoded")
.body("grant_type=client_credentials")
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
urlencoding::encode(&key),
);
let resp = http
.post(&token_endpoint)

Check failure

Code scanning / CodeQL

Cleartext transmission of sensitive information

This 'post' operation transmits data which may contain unencrypted sensitive data from [self.discover_oauth_server_via_resource_metadata()](1). This 'post' operation transmits data which may contain unencrypted sensitive data from [self.try_discover_oauth_server(...)](2).
@alexhancock alexhancock changed the title feat: mcp sdk compliance feat: mcp sdk conformance Feb 25, 2026
@alexhancock alexhancock force-pushed the alexhancock/conformance branch from 751f597 to 345af49 Compare February 25, 2026 20:03
@github-actions github-actions bot added T-test Testing related changes T-CI Changes to CI/CD workflows and configuration labels Feb 25, 2026
@alexhancock alexhancock force-pushed the alexhancock/conformance branch from b5561fe to f7c920f Compare February 26, 2026 15:08
@github-actions github-actions bot removed T-test Testing related changes T-model Model/data structure changes labels Feb 26, 2026
DaleSeo
DaleSeo previously approved these changes Feb 26, 2026
Copy link
Member

@DaleSeo DaleSeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job on this, @alexhancock! Setting up the conformance suite is a big step forward!

I added some suggestions in my review. They're mostly about reducing duplication and a few naming and logging issues, but nothing is blocking.

pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05"));
// Keep LATEST at 2025-03-26 until full 2025-06-18 compliance and automated testing are in place.
pub const LATEST: Self = Self::V_2025_03_26;
pub const LATEST: Self = Self::V_2025_06_18;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! 🎉

Comment on lines 342 to 391
"test_tool_with_progress" => {
let progress_token = cx.meta.get_progress_token();

if let Some(token) = &progress_token {
let _ = cx
.peer
.notify_progress(ProgressNotificationParam {
progress_token: token.clone(),
progress: 0.0,
total: Some(100.0),
message: Some("Starting".into()),
})
.await;
}

tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;

if let Some(token) = &progress_token {
let _ = cx
.peer
.notify_progress(ProgressNotificationParam {
progress_token: token.clone(),
progress: 50.0,
total: Some(100.0),
message: Some("Halfway".into()),
})
.await;
}

tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;

if let Some(token) = &progress_token {
let _ = cx
.peer
.notify_progress(ProgressNotificationParam {
progress_token: token.clone(),
progress: 100.0,
total: Some(100.0),
message: Some("Complete".into()),
})
.await;
}

Ok(CallToolResult {
content: vec![Content::text("Progress test completed")],
structured_content: None,
is_error: None,
meta: None,
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to logging above: three almost identical blocks that could be a loop over [(0.0, "Starting"), (50.0, "Halfway"), (100.0, "Complete")].

* adds conformance server and client
* adds results from initial run of https://github.com/modelcontextprotocol/conformance/tree/main/.claude/skills/mcp-sdk-tier-audit skill
* various small changes applied during the testing loop

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
@alexhancock alexhancock force-pushed the alexhancock/conformance branch from 0c13e6b to 13e7b76 Compare February 26, 2026 17:54
Copy link
Member

@DaleSeo DaleSeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my feedback!

@alexhancock alexhancock merged commit a7e4ae3 into main Feb 26, 2026
16 checks passed
@alexhancock alexhancock deleted the alexhancock/conformance branch February 26, 2026 18:33
This was referenced Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-CI Changes to CI/CD workflows and configuration T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-documentation Documentation improvements T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants