fix(web): report invalid MCP startup configuration

Closes #10986
This commit is contained in:
t8y2
2026-10-03 20:19:02 +00:00
parent 92a29cb715
commit 5f0ad9217a
2 changed files with 74 additions and 6 deletions
+17 -6
View File
@@ -9,6 +9,7 @@ mod web_mcp;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, SocketAddr};
use std::process::ExitCode;
use std::sync::Arc;
use argon2::password_hash::rand_core::OsRng;
@@ -456,18 +457,28 @@ fn add_mq_routes(router: Router<Arc<WebState>>) -> Router<Arc<WebState>> {
router
}
fn main() {
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
if help_requested(&args) {
print!("{HELP_TEXT}");
return;
return ExitCode::SUCCESS;
}
let runtime = dbx_core::scheduled_backup::worker_runtime().expect("Failed to build tokio runtime");
runtime.block_on(serve());
match runtime.block_on(serve()) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}
async fn serve() {
fn web_mcp_startup_error(error: String) -> String {
format!("Failed to start DBX Web: invalid Web MCP configuration: {error}")
}
async fn serve() -> Result<(), String> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
@@ -542,7 +553,7 @@ async fn serve() {
let web_mcp = Arc::new(if migration_ready {
WebMcpRuntime::load(&app_state.storage, !password_disabled && password_hash.is_some())
.await
.expect("Invalid DBX Web MCP configuration")
.map_err(web_mcp_startup_error)?
} else {
WebMcpRuntime::disabled()
});
@@ -1457,7 +1468,7 @@ async fn serve() {
.layer(CompressionLayer::new().compress_when(web_compression_predicate()))
.layer(tower_http::trace::TraceLayer::new_for_http());
let mcp_router = web_mcp_router(&web_state).expect("Invalid DBX Web MCP configuration");
let mcp_router = web_mcp_router(&web_state).map_err(web_mcp_startup_error)?;
app = app.merge(
mcp_router
.layer(middleware::from_fn_with_state(web_state.clone(), web_mcp_demo_gate))
+57
View File
@@ -318,6 +318,63 @@ mod tests {
assert!(validate_token("token\n".to_string()).is_err());
}
#[tokio::test]
async fn deployment_token_without_allowed_hosts_returns_error() {
let (_dir, storage) = test_storage().await;
let result = WebMcpRuntime::load_with_deployment(
&storage,
Some(DeploymentConfig {
token: "deployment-secret".to_string(),
source: TokenSource::Environment,
allowed_hosts: vec![],
allowed_origins: vec![],
}),
false,
)
.await;
assert_eq!(result.err().as_deref(), Some("DBX_WEB_MCP_ALLOWED_HOSTS or a Web MCP allowed Host is required"));
}
#[tokio::test]
async fn page_managed_settings_without_allowed_hosts_returns_dual_path_error() {
let (_dir, storage) = test_storage().await;
let runtime = WebMcpRuntime::load_with_deployment(&storage, None, true).await.unwrap();
let result = runtime.update(&storage, request(true, &[], false)).await;
assert_eq!(result.err().as_deref(), Some("DBX_WEB_MCP_ALLOWED_HOSTS or a Web MCP allowed Host is required"));
assert!(!runtime.auth.enabled());
assert!(storage.get_secret(SECRET_NAMESPACE, SECRET_KEY).await.unwrap().is_none());
}
#[tokio::test]
async fn valid_deployment_configuration_enables_mcp() {
let (_dir, storage) = test_storage().await;
let runtime = WebMcpRuntime::load_with_deployment(
&storage,
Some(DeploymentConfig {
token: "deployment-secret".to_string(),
source: TokenSource::Environment,
allowed_hosts: vec!["dbx.example.test:4224".to_string()],
allowed_origins: vec![],
}),
false,
)
.await
.unwrap();
assert!(runtime.status("/mcp".to_string(), false).enabled);
}
#[tokio::test]
async fn absent_deployment_configuration_leaves_mcp_disabled() {
let (_dir, storage) = test_storage().await;
let runtime = WebMcpRuntime::load_with_deployment(&storage, None, false).await.unwrap();
assert!(!runtime.status("/mcp".to_string(), false).enabled);
}
#[tokio::test]
async fn managed_token_survives_restart_and_is_encrypted() {
let (_dir, storage) = test_storage().await;