Skip to content
Merged
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions crates/mcpmux-gateway/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ mod startup;
mod state;

// Exposed for integration tests that mount these routes against a real
// ServiceContainer (e.g. asserting the OAuth-discovery endpoints 404 when
// inbound auth is disabled). AppState is also used throughout this module.
pub use handlers::{oauth_metadata, resource_metadata, AppState};
// ServiceContainer — e.g. asserting the OAuth-discovery endpoints 404 when
// inbound auth is disabled, and driving the full inbound OAuth flow
// (register → authorize → consent → token → authenticated /mcp) end to end.
// AppState is also used throughout this module.
pub use handlers::{
oauth_authorize, oauth_consent_approve, oauth_metadata, oauth_register, oauth_token,
resource_metadata, AppState,
};

pub use dependencies::{DependenciesBuilder, GatewayDependencies};
pub use handlers::PendingAuthorization;
Expand Down
5 changes: 5 additions & 0 deletions tests/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ reqwest = { version = "0.13", features = ["json"] }
# URL parsing for OAuth tests
url = "2.5"

# PKCE + JWT-secret material for the inbound-OAuth end-to-end test
base64 = "0.22"
sha2 = "0.10"
zeroize = "1.8"

# Sync primitives for tests
parking_lot = "0.12"

Expand Down
36 changes: 29 additions & 7 deletions tests/rust/tests/streamable_http/auth_disable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ impl Harness {
"/.well-known/oauth-protected-resource",
get(resource_metadata),
)
// RFC 9728 resource-specific variant — this is the one editors like
// VS Code probe first (`/.well-known/oauth-protected-resource/mcp`).
.route(
"/.well-known/oauth-protected-resource/mcp",
get(resource_metadata),
)
.route(
"/.well-known/oauth-authorization-server",
get(oauth_metadata),
Expand Down Expand Up @@ -211,8 +217,12 @@ async fn authless_gateway_does_not_advertise_oauth_discovery() {
// without a token.
let h = Harness::start(true).await;
let client = reqwest::Client::new();
// Includes the RFC 9728 `/mcp` sub-path — the endpoint VS Code probes first
// (its 200 was what pushed editors into an OAuth flow against an authless
// gateway, leaving them stuck waiting on `initialize`).
for path in [
"/.well-known/oauth-protected-resource",
"/.well-known/oauth-protected-resource/mcp",
"/.well-known/oauth-authorization-server",
] {
let resp = client
Expand All @@ -230,12 +240,24 @@ async fn authless_gateway_does_not_advertise_oauth_discovery() {

#[tokio::test]
async fn auth_required_gateway_advertises_oauth_discovery() {
// The default (auth required) still serves discovery so real OAuth works.
// The default (auth required) still serves discovery so real OAuth works —
// every endpoint, including the RFC 9728 sub-path.
let h = Harness::start(false).await;
let resp = reqwest::Client::new()
.get(format!("{}/.well-known/oauth-protected-resource", h.base))
.send()
.await
.expect("request");
assert_eq!(resp.status(), reqwest::StatusCode::OK);
let client = reqwest::Client::new();
for path in [
"/.well-known/oauth-protected-resource",
"/.well-known/oauth-protected-resource/mcp",
"/.well-known/oauth-authorization-server",
] {
let resp = client
.get(format!("{}{path}", h.base))
.send()
.await
.expect("request");
assert_eq!(
resp.status(),
reqwest::StatusCode::OK,
"{path} must be served when auth is required"
);
}
}
Loading
Loading