<div align="center">
<img src="assets/logo.svg" alt="rust-mc-status logo" width="160" height="160">
<h1>rust-mc-status</h1>
<p>
<strong>High-performance, async Minecraft server status client for Rust โ Java & Bedrock Edition.</strong>
</p>
<p>
[](https://crates.io/crates/rust-mc-status)
[](https://docs.rs/rust-mc-status/3.0.0)


[](https://deps.rs/crate/rust-mc-status/3.0.0)
<br />

</p>
</div>
---
**Documentation**: [https://docs.rs/rust-mc-status](https://docs.rs/rust-mc-status/3.0.0)
**Source Code**: [https://github.com/NameOfShadow/rust-mc-status](https://github.com/NameOfShadow/rust-mc-status)
---
## ๐ v3.0.0 โ Released 2026-07-01
A major rewrite focused on ergonomics, performance, and extensibility. **Breaking changes** from 2.x โ see the [Migration Guide](CHANGELOG.md#migration-guide-2x--300).
**Headline changes:**
- **Builder API** โ `McClient::builder().timeout(...).response_cache(...).build()` replaces ad-hoc `with_*` chaining.
- **Per-request fluent pings** โ `client.java("addr").timeout(3s).await?` with optional `.is_online()`.
- **Response cache with in-flight deduplication** โ repeated pings to the same server collapse into one network request; cache hits return in ~0 ms.
- **Two-level error hierarchy** โ `McError::Network(NetworkError::Timeout)` etc. for fine-grained matching and retry policy.
- **Proxy support** *(feature = "proxy")* โ SOCKS5 (with optional UDP for Bedrock) and HTTP CONNECT, with auth.
- **Tower integration** *(feature = "tower")* โ `McService` + `McRetryPolicy` plug into the Tower middleware ecosystem.
- **Layer-based source tree** โ `client/`, `core/`, `models/`, `protocol/`, `proxy/`, `status/` for cleaner navigation.
- **Zero-cost defaults** โ without optional features, no extra tower / pin-project / tokio-socks dependencies are pulled in.
- **149 tests across 8 integration test files** โ DNS parsing, Bedrock/Java decoders, error hierarchy, response cache, proxy config, MOTD formatting.
Full release notes and the migration guide are in [CHANGELOG.md](CHANGELOG.md).
---
## Features
- **Dual Protocol Support** โ ping both Java Edition (port `25565`) and Bedrock Edition (port `19132`)
- **Ergonomic Builder API** โ `client.java("addr").timeout(3s).await?` with per-request timeout override
- **Typed Status Wrappers** โ `JavaServerStatus` and `BedrockServerStatus` with edition-specific methods
- **Facade Functions** โ `ping_java("addr").await?` without creating a client
- **Tower Middleware** *(optional feature)* โ rate limiting, retries, buffering, tracing via the Tower ecosystem
- **Async/Await** โ built on Tokio for non-blocking, high-concurrency operation
- **Batch Queries** โ ping multiple servers in parallel with configurable concurrency
- **LRU DNS Cache** โ DNS and SRV lookups cached with automatic eviction (default 1024 entries)
- **SRV Record Support** โ automatic SRV lookup for Java servers, mimicking the official client
- **JSON Serialisation** โ all status types implement `serde::Serialize`; favicon and raw data excluded automatically
- **MOTD Formatting** โ `motd_clean()` strips Minecraft `ยง`-color codes; `truncate_str()` safely slices Unicode
- **Zero-Panic Design** โ all server-supplied data validated before use; no silent index panics
- **SmallVec Optimisation** โ player sample, plugins, and mods use stack allocation for small lists
- **Zero-cost without Tower** โ the default build pulls in no tower, async-trait, or pin-project dependency
## Installation
```toml
[dependencies]
rust-mc-status = "3.0.0"
tokio = { version = "*", features = ["full"] }
```
To enable Tower middleware (rate limiting, retries, buffering, tracing):
```toml
rust-mc-status = { version = "3.0.0", features = ["tower"] }
```
## Quick Start
### One-liner (no client needed)
```rust
use rust_mc_status::{ping_java, ping_bedrock, StatusExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let status = ping_java("mc.hypixel.net").await?;
println!("{}", status); // "mc.hypixel.net [42 ms] 32000/200000 โ Hypixel Network"
println!("{}", status.display_players()); // "32000/200000"
println!("{}", status.motd_clean()); // MOTD without ยง-codes
Ok(())
}
```
### Client with custom settings
```rust
use rust_mc_status::{McClient, StatusExt};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = McClient::new()
.with_timeout(Duration::from_secs(10))
.with_max_parallel(20)
.with_cache_size(2048);
// Typed Java status
let java = client.java("mc.hypixel.net").await?;
println!("Version : {}", java.version());
println!("Players : {}", java.display_players());
println!("Latency : {:.0} ms", java.latency_ms());
println!("MOTD : {}", java.motd_clean());
// Per-request timeout override โ client config unchanged
let fast = client.java("mc.hypixel.net")
.timeout(Duration::from_secs(3))
.await?;
println!("Fast check: {} ms", fast.latency_ms() as u32);
// Typed Bedrock status
let bedrock = client.bedrock("geo.hivebedrock.network:19132").await?;
println!("Edition : {}", bedrock.edition());
println!("Players : {}", bedrock.display_players());
Ok(())
}
```
### Batch queries
```rust
use rust_mc_status::{McClient, ServerEdition, ServerInfo};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = McClient::new();
let servers = vec![
ServerInfo { address: "mc.hypixel.net".into(), edition: ServerEdition::Java },
ServerInfo { address: "geo.hivebedrock.network:19132".into(), edition: ServerEdition::Bedrock },
];
for (server, result) in client.ping_many(&servers).await {
match result {
Ok(s) => println!("{}: online ({:.0} ms)", server.address, s.latency),
Err(e) => println!("{}: {}", server.address, e),
}
}
Ok(())
}
```
### JSON serialisation
```rust
let status = client.java("mc.hypixel.net").await?;
let json = serde_json::to_string(&status)?; // compact
let json = serde_json::to_string_pretty(&status)?; // pretty-printed
// favicon and raw_data are excluded automatically
```
### Runtime edition selection
```rust
let edition: ServerEdition = "java".parse()?;
// Full status
let status = client.server("mc.hypixel.net", edition).await?;
// Lightweight reachability check only
let online = client.server("mc.hypixel.net", edition)
.timeout(Duration::from_secs(3))
.is_online()
.await;
```
### Custom address types
```rust
struct ServerId(String);
impl From<ServerId> for String { fn from(s: ServerId) -> Self { s.0 } }
let status = client.java(ServerId("mc.hypixel.net".into())).await?;
```
## Tower Middleware
Enable with `features = ["tower"]`. Converts `McClient` into a standard `tower::Service` that works with the entire Tower ecosystem โ rate limiting, retries, buffering, `tower-http` tracing, and any custom `tower::Layer`.
```rust
use tower::{ServiceBuilder, ServiceExt};
use rust_mc_status::{McClient, McRetryPolicy, PingRequestTower};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut svc = ServiceBuilder::new()
.buffer(64) // Clone + concurrent
.rate_limit(100, Duration::from_secs(1)) // 100 req/s
.retry(McRetryPolicy::new(3)) // 3 attempts on timeout/connection error
.service(McClient::new().into_service());
svc.ready().await?;
let resp = svc.call(PingRequestTower::java("mc.hypixel.net")).await?;
println!("{:.0} ms", resp.status.latency);
Ok(())
}
```
### Custom Tower Layer
Implement `tower::Layer` for logging, metrics, or any middleware:
```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use rust_mc_status::{McError, PingRequestTower, PingResponseTower};
#[derive(Clone)]
struct LogLayer;
impl<S> Layer<S> for LogLayer {
type Service = LogService<S>;
fn layer(&self, inner: S) -> LogService<S> { LogService { inner } }
}
#[derive(Clone)]
struct LogService<S> { inner: S }
impl<S> Service<PingRequestTower> for LogService<S>
where
S: Service<PingRequestTower, Response = PingResponseTower, Error = McError> + Send,
S::Future: Send + 'static,
{
type Response = PingResponseTower;
type Error = McError;
type Future = Pin<Box<dyn Future<Output = Result<PingResponseTower, McError>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), McError>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: PingRequestTower) -> Self::Future {
println!("โ {}", req.address);
let fut = self.inner.call(req);
Box::pin(async move {
let resp = fut.await?;
println!("{:.0} ms", resp.status.latency);
Ok(resp)
})
}
}
// Usage
let mut svc = ServiceBuilder::new()
.layer(LogLayer)
.service(McClient::new().into_service());
```
## SRV Record Lookup
When pinging a Java server **without** an explicit port, the library queries `_minecraft._tcp.{hostname}` for SRV records โ exactly as the official Minecraft client does. Results are cached for 5 minutes.
```rust
// SRV lookup performed automatically
let status = client.java("hypixel.net").await?;
// Explicit port โ SRV lookup skipped
let status = client.java("mc.hypixel.net:25565").await?;
```
## MOTD Formatting
```rust
use rust_mc_status::strip_formatting;
let raw = "ยงaHypixel ยงc[1.8/1.21]";
println!("{}", strip_formatting(raw)); // "Hypixel [1.8/1.21]"
// Or via the wrapper method
let status = client.java("mc.hypixel.net").await?;
println!("{}", status.motd_clean()); // same result
```
## Examples
| `basic_usage` | Core API: facade, typed status, per-request timeout, serialisation |
| `advanced_usage` | Batch queries, plugins, mods, favicon saving |
| `cache_management` | LRU cache stats, warm/cold timing, `clear_caches()` |
| `tower_usage` | Tower middleware: rate limiting, retries, buffer, custom Layer |
| `srv_lookup_example` | SRV record behaviour |
| `performance_test` | Benchmarking (run with `--release`) |
```bash
cargo run --example basic_usage
cargo run --example cache_management
cargo run --example tower_usage --features tower
cargo run --example performance_test --release
```
## API Reference
### `McClient` builder methods
| `new()` | Default settings (10 s timeout, 10 parallel, 1024 cache) |
| `with_timeout(Duration)` | Global request timeout |
| `with_max_parallel(usize)` | Max concurrent pings in `ping_many` |
| `with_cache_size(usize)` | LRU cache capacity for DNS and SRV |
| `into_service()` *(tower)* | Convert to `McService` for Tower middleware |
### Ping methods
| `client.java(addr)` | `JavaPingBuilder` | Supports `.timeout(d).await` |
| `client.bedrock(addr)` | `BedrockPingBuilder` | Supports `.timeout(d).await` |
| `client.server(addr, edition)` | `ServerPingBuilder` | Runtime edition; `.is_online().await` |
| `client.ping_many(&[ServerInfo])` | `Vec<(ServerInfo, Result<ServerStatus>)>` | Parallel batch |
| `ping_java(addr)` | `Result<JavaServerStatus>` | Global client, no setup needed |
| `ping_bedrock(addr)` | `Result<BedrockServerStatus>` | Global client, no setup needed |
### Tower types *(feature = "tower")*
| `McService` | `tower::Service<PingRequestTower>` โ wraps `McClient` |
| `McRetryPolicy` | Retries on `Timeout` / `ConnectionError`; configurable attempt count |
| `PingRequestTower` | Request type with `.java(addr)` / `.bedrock(addr)` constructors |
| `PingResponseTower` | Response containing `status: ServerStatus` |
### `JavaServerStatus` methods
`motd()`, `motd_clean()`, `version()`, `players_online()`, `players_max()`, `display_players()`, `favicon()`, `save_favicon(path)`, `ip()`, `port()`, `hostname()`, `latency_ms()`, `is_online()`, `raw()`
### `BedrockServerStatus` methods
`motd()`, `motd_clean()`, `motd2()`, `motd2_clean()`, `edition()`, `version()`, `players_online()`, `players_max()`, `display_players()`, `game_mode()`, `ip()`, `port()`, `hostname()`, `latency_ms()`, `is_online()`, `raw()`
### Utility functions
| `strip_formatting(s)` | Remove Minecraft `ยงX` codes, collapse whitespace |
| `truncate_str(s, n)` | Truncate to `n` Unicode characters (panic-safe) |
### Cache methods (all `async`)
`cache_stats().await`, `clear_caches().await`
## Error Handling
```rust
use rust_mc_status::McError;
match client.java("server.com").await {
Ok(s) => println!("online: {}", s.display_players()),
Err(McError::Timeout) => println!("timed out"),
Err(McError::DnsError(msg)) => println!("DNS: {}", msg),
Err(McError::ConnectionError(msg)) => println!("connection: {}", msg),
Err(e) => println!("error: {}", e),
}
```
## Performance Notes
- DNS and SRV results cached for 5 minutes with LRU eviction โ no memory leaks
- `tokio::sync::RwLock` allows concurrent readers with minimal contention
- `SmallVec` for player sample (โค 12), plugins (โค 8), and mods (โค 8) โ stack-allocated for common case
- Per-request `.timeout()` reduces tail latency in batch scenarios without touching the client
- `ping_bedrock` uses UDP; `ping_java` uses TCP with `TCP_NODELAY`
- Default build has zero tower/async-trait dependency โ only pulled in with `features = ["tower"]`
## License
MIT โ see [LICENSE](LICENSE).
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## Version
Current version: **3.0.0** (released 2026-07-01) โ see [CHANGELOG.md](CHANGELOG.md) for full history and migration guide.