Stabilize RMCP streamable HTTP readiness tests (#13880)

## What changed
- The RMCP streamable HTTP tests now wait for metadata and tool
readiness before issuing tool calls.
- OAuth state is isolated per test home.
- The helper server startup path now uses bounded bind retries so
transient `AddrInUse` collisions do not fail the test immediately.

## Why this fixes the flake
- The old tests could begin issuing tool requests before the helper
server had finished advertising its metadata and tools, so the first
request sometimes raced the server startup sequence.
- On top of that, shared OAuth state and occasional bind collisions on
CI runners introduced cross-test environmental noise unrelated to the
functionality under test.
- Readiness polling makes the client wait for an observable “server is
ready” signal, while isolated state and bounded bind retries remove
external contention that was causing intermittent failures.

## Scope
- Test-only change.
This commit is contained in:
Ahmed Ibrahim
2026-03-09 12:52:55 -07:00
committed by GitHub
parent da991bdf3a
commit 44ecc527cb
2 changed files with 71 additions and 22 deletions

View File

@@ -3,6 +3,7 @@ use std::collections::HashMap;
use std::io::ErrorKind;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use axum::Router;
use axum::body::Body;
@@ -44,6 +45,7 @@ use serde::Deserialize;
use serde_json::json;
use tokio::sync::Mutex;
use tokio::task;
use tokio::time::sleep;
#[derive(Clone)]
struct TestToolServer {
@@ -275,15 +277,25 @@ fn parse_bind_addr() -> Result<SocketAddr, Box<dyn std::error::Error>> {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let bind_addr = parse_bind_addr()?;
let session_failure_state = SessionFailureState::default();
let listener = match tokio::net::TcpListener::bind(&bind_addr).await {
Ok(listener) => listener,
Err(err) if err.kind() == ErrorKind::PermissionDenied => {
eprintln!(
"failed to bind to {bind_addr}: {err}. make sure the process has network access"
);
return Ok(());
const MAX_BIND_RETRIES: u32 = 20;
const BIND_RETRY_DELAY: Duration = Duration::from_millis(50);
let mut bind_retries = 0;
let listener = loop {
match tokio::net::TcpListener::bind(&bind_addr).await {
Ok(listener) => break listener,
Err(err) if err.kind() == ErrorKind::PermissionDenied => {
eprintln!(
"failed to bind to {bind_addr}: {err}. make sure the process has network access"
);
return Ok(());
}
Err(err) if err.kind() == ErrorKind::AddrInUse && bind_retries < MAX_BIND_RETRIES => {
bind_retries += 1;
sleep(BIND_RETRY_DELAY).await;
}
Err(err) => return Err(err.into()),
}
Err(err) => return Err(err.into()),
};
eprintln!("starting rmcp streamable http test server on http://{bind_addr}/mcp");