use std::sync::Arc;
use axum::Router;
use rmcp::{
model::{ServerCapabilities, ServerInfo},
transport::streamable_http_server::{
session::local::LocalSessionManager, StreamableHttpServerConfig, StreamableHttpService,
},
ServerHandler,
};
use tokio_util::sync::CancellationToken;
#[derive(Clone, Default)]
struct EmptySpikeHandler;
impl ServerHandler for EmptySpikeHandler {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().build())
}
}
#[tokio::test]
async fn rmcp_streamable_http_binds_loopback_and_shuts_down() {
let cancellation = CancellationToken::new();
let mut config = StreamableHttpServerConfig::default();
config.cancellation_token = cancellation.clone();
config.sse_keep_alive = None;
config.sse_retry = None;
let service: StreamableHttpService<EmptySpikeHandler, LocalSessionManager> =
StreamableHttpService::new(
|| Ok(EmptySpikeHandler),
Arc::new(LocalSessionManager::default()),
config,
);
let router = Router::new().nest_service("/mcp", service);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("loopback bind");
let addr = listener.local_addr().expect("local addr");
assert!(
addr.ip().is_loopback(),
"MCP spike must bind loopback only; got {addr}"
);
let serve = tokio::spawn({
let cancellation = cancellation.clone();
async move {
let _ = axum::serve(listener, router)
.with_graceful_shutdown(async move {
cancellation.cancelled().await;
})
.await;
}
});
let _ = tokio::net::TcpStream::connect(addr).await;
cancellation.cancel();
serve.await.expect("serve task join");
}
#[test]
fn wp00_selected_deps_typecheck() {
let _builder = reqwest::Client::builder();
let secret = secrecy::SecretString::from("wp00-not-a-real-secret");
let _len = secrecy::ExposeSecret::expose_secret(&secret).len();
let schema = serde_json::json!({
"type": "object",
"properties": { "n": { "type": "integer" } },
"required": ["n"]
});
let validator = jsonschema::validator_for(&schema).expect("compile schema");
assert!(validator.is_valid(&serde_json::json!({ "n": 1 })));
assert!(!validator.is_valid(&serde_json::json!({ "n": "x" })));
let mut token = [0u8; 32];
rand::TryRngCore::try_fill_bytes(&mut rand::rngs::OsRng, &mut token)
.expect("OS CSPRNG must provide entropy");
assert_ne!(token, [0u8; 32]);
}