Skip to content

Commit 88f908e

Browse files
authored
feat: introduce health check handler support (#135)
* feat: health check handler * introduce healthcheck support * introduce healthcheck support * chore: clippy * chore: add missing arg when auth is disabled * chore: update readme * chore: cargo update
1 parent 45f1305 commit 88f908e

14 files changed

Lines changed: 288 additions & 30 deletions

File tree

Cargo.lock

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This SDK fully implements the latest MCP protocol version ([2025-11-25](https://
3232
- ✅ MCP [Tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) support
3333
- ✅ Batch Messages
3434
- ✅ Streaming & non-streaming JSON response
35+
- ✅ HTTP Health Checks (for load balancers & container orchestration)
3536
- ✅ OAuth Authentication for MCP Servers
3637
-[Remote Oauth Provider](crates/rust-mcp-sdk/src/auth/auth_provider/remote_auth_provider.rs) (for any provider with DCR support)
3738
-**Keycloak** Provider (via [rust-mcp-extra](crates/rust-mcp-extra/README.md#keycloak))
@@ -67,6 +68,7 @@ This SDK fully implements the latest MCP protocol version ([2025-11-25](https://
6768
- [Handler Traits](#handlers-traits)
6869
- [Choosing Between **ServerHandler** and **ServerHandlerCore**](#choosing-between-serverhandler-and-serverhandlercore)
6970
- [Choosing Between **ClientHandler** and **ClientHandlerCore**](#choosing-between-clienthandler-and-clienthandlercore)
71+
- [Health Check Endpoint](#health-check-endpoint)
7072
- [Projects using Rust MCP SDK](#projects-using-rust-mcp-sdk)
7173
- [Contributing](#contributing)
7274
- [Development](#development)
@@ -576,6 +578,28 @@ Both functions create an MCP client instance.
576578

577579
Check out the corresponding examples at: [examples/simple-mcp-client-stdio.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/crates/rust-mcp-sdk/examples/simple-mcp-client-stdio.rs) and [examples/simple-mcp-client-stdio-core.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/crates/rust-mcp-sdk/examples/simple-mcp-client-stdio-core.rs).
578580

581+
## Health Check Endpoint
582+
583+
While not part of the official MCP spec, `rust-mcp-sdk` provides an optional HTTP health check endpoint. This is a practical quality-of-life feature, specifically useful when your MCP server is:
584+
- Exposed behind load balancers or reverse proxies (e.g., NGINX, HAProxy, Cloudflare).
585+
- Running in container orchestration environments (e.g., Kubernetes, Docker Swarm, AWS ECS).
586+
587+
The health check endpoint is disabled by default. You can enable it and optionally provide your own custom handler (to return specific metrics or metadata) via `HyperServerOptions`:
588+
589+
```rs
590+
let server = hyper_server::create_server(
591+
server_details,
592+
handler.to_mcp_server_handler(),
593+
HyperServerOptions {
594+
host: "127.0.0.1".into(),
595+
health_endpoint: Some("/health".into()), // enables the endpoint
596+
health_handler: Some(Arc::new(CustomHealth {})), // optional: overrides default 200 OK
597+
..Default::default()
598+
},
599+
);
600+
```
601+
602+
👉 See the [streamable_http_healthcheck.rs](crates/rust-mcp-sdk/examples/streamable_http_healthcheck.rs) example for a complete implementation demonstrating a custom JSON health handler.
579603

580604
## Projects using Rust MCP SDK
581605

crates/rust-mcp-sdk/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This SDK fully implements the latest MCP protocol version ([2025-11-25](https://
3232
- ✅ MCP [Tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) support
3333
- ✅ Batch Messages
3434
- ✅ Streaming & non-streaming JSON response
35+
- ✅ HTTP Health Checks (for load balancers & container orchestration)
3536
- ✅ OAuth Authentication for MCP Servers
3637
-[Remote Oauth Provider](crates/rust-mcp-sdk/src/auth/auth_provider/remote_auth_provider.rs) (for any provider with DCR support)
3738
-**Keycloak** Provider (via [rust-mcp-extra](crates/rust-mcp-extra/README.md#keycloak))
@@ -67,6 +68,7 @@ This SDK fully implements the latest MCP protocol version ([2025-11-25](https://
6768
- [Handler Traits](#handlers-traits)
6869
- [Choosing Between **ServerHandler** and **ServerHandlerCore**](#choosing-between-serverhandler-and-serverhandlercore)
6970
- [Choosing Between **ClientHandler** and **ClientHandlerCore**](#choosing-between-clienthandler-and-clienthandlercore)
71+
- [Health Check Endpoint](#health-check-endpoint)
7072
- [Projects using Rust MCP SDK](#projects-using-rust-mcp-sdk)
7173
- [Contributing](#contributing)
7274
- [Development](#development)
@@ -576,6 +578,28 @@ Both functions create an MCP client instance.
576578

577579
Check out the corresponding examples at: [examples/simple-mcp-client-stdio.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/crates/rust-mcp-sdk/examples/simple-mcp-client-stdio.rs) and [examples/simple-mcp-client-stdio-core.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/crates/rust-mcp-sdk/examples/simple-mcp-client-stdio-core.rs).
578580

581+
## Health Check Endpoint
582+
583+
While not part of the official MCP spec, `rust-mcp-sdk` provides an optional HTTP health check endpoint. This is a practical quality-of-life feature, specifically useful when your MCP server is:
584+
- Exposed behind load balancers or reverse proxies (e.g., NGINX, HAProxy, Cloudflare).
585+
- Running in container orchestration environments (e.g., Kubernetes, Docker Swarm, AWS ECS).
586+
587+
The health check endpoint is disabled by default. You can enable it and optionally provide your own custom handler (to return specific metrics or metadata) via `HyperServerOptions`:
588+
589+
```rs
590+
let server = hyper_server::create_server(
591+
server_details,
592+
handler.to_mcp_server_handler(),
593+
HyperServerOptions {
594+
host: "127.0.0.1".into(),
595+
health_endpoint: Some("/health".into()), // enables the endpoint
596+
health_handler: Some(Arc::new(CustomHealth {})), // optional: overrides default 200 OK
597+
..Default::default()
598+
},
599+
);
600+
```
601+
602+
👉 See the [streamable_http_healthcheck.rs](crates/rust-mcp-sdk/examples/streamable_http_healthcheck.rs) example for a complete implementation demonstrating a custom JSON health handler.
579603

580604
## Projects using Rust MCP SDK
581605

crates/rust-mcp-sdk/examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ This folder contains a variety of example programs demonstrating how to use the
1111
- **[Streamable HTTP Examples](#%EF%B8%8F-mcp-servers-examples-streamable-http)**
1212
- quick-start-streamable-http
1313
- hello-world-server-streamable-http
14-
- hello-world-server-streamable-http-core
14+
- hello-world-server-streamable-http-core
15+
- streamable_http_healthcheck
1516
- **[Oauth Example](#%EF%B8%8F-mcp-server---oauth-example)**
1617
- mcp-server-oauth-remote
1718
- **MCP Client**
@@ -58,6 +59,7 @@ Minimal quick-start example demonstrating the fastest way to set up a basic MCP
5859
- [quick-start-streamable-http.rs](quick-start-streamable-http.rs)
5960
- [hello-world-server-streamable-http.rs](hello-world-server-streamable-http.rs)
6061
- [hello-world-server-streamable-http-core.rs](hello-world-server-streamable-http-core.rs)
62+
- [streamable_http_healthcheck.rs](streamable_http_healthcheck.rs)
6163

6264
**Start the server:**
6365
_for instance, start the `hello-world-server-streamable-http`_

crates/rust-mcp-sdk/examples/quick-start-streamable-http.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async fn main() -> SdkResult<()> {
8585
HyperServerOptions {
8686
host: "127.0.0.1".to_string(),
8787
event_store: Some(std::sync::Arc::new(InMemoryEventStore::default())), // enable resumability
88+
health_endpoint: Some("/health".into()), // enable health check endpoint
8889
..Default::default()
8990
},
9091
);
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//! Example custom health check handler.
2+
//!
3+
//! This demonstrates how to implement `HealthHandler` to override the default
4+
//! `/health` endpoint behavior. Instead of returning the minimal static `200 OK`,
5+
//! this handler returns a JSON payload containing basic service metadata.
6+
7+
pub mod common;
8+
9+
use crate::common::{initialize_tracing, ExampleServerHandler};
10+
use rust_mcp_schema::ServerCapabilitiesResources;
11+
use rust_mcp_sdk::mcp_http::{self, GenericBodyExt};
12+
use rust_mcp_sdk::schema::{
13+
Implementation, InitializeResult, ProtocolVersion, ServerCapabilities, ServerCapabilitiesTools,
14+
};
15+
use rust_mcp_sdk::{
16+
error::SdkResult,
17+
event_store::InMemoryEventStore,
18+
mcp_icon,
19+
mcp_server::{hyper_server, HyperServerOptions, ToMcpServerHandler},
20+
task_store::InMemoryTaskStore,
21+
};
22+
use serde_json::Map;
23+
use std::sync::Arc;
24+
25+
/// Custom health check handler.
26+
///
27+
/// Use this with `HyperServerOptions.health_handler` to override the default
28+
/// health endpoint behavior and return a custom response.
29+
struct CustomHealth {}
30+
impl mcp_http::HealthHandler for CustomHealth {
31+
fn call(
32+
&self,
33+
_req: mcp_http::http::Request<&str>,
34+
) -> mcp_http::http::Response<mcp_http::GenericBody> {
35+
let status = serde_json::json!({
36+
"status":"ok",
37+
"server": env!("CARGO_PKG_NAME"),
38+
"version":env!("CARGO_PKG_VERSION")
39+
});
40+
mcp_http::GenericBody::from_value(&status).into_json_response(http::StatusCode::OK, None)
41+
}
42+
}
43+
44+
#[tokio::main]
45+
async fn main() -> SdkResult<()> {
46+
// Set up the tracing subscriber for logging
47+
initialize_tracing();
48+
49+
// STEP 1: Define server details and capabilities
50+
let server_details = InitializeResult {
51+
// server name and version
52+
server_info: Implementation {
53+
name: "Hello World MCP Server Streamable Http/SSE".into(),
54+
version: "0.1.0".into(),
55+
title: Some("Hello World MCP Streamable Http/SSE".into()),
56+
description: Some("test server, by Rust MCP SDK".into()),
57+
icons: vec![mcp_icon!(
58+
src = "https://raw.githubusercontent.com/rust-mcp-stack/rust-mcp-sdk/main/assets/rust-mcp-icon.png",
59+
mime_type = "image/png",
60+
sizes = ["128x128"],
61+
theme = "dark"
62+
)],
63+
website_url: Some("https://github.com/rust-mcp-stack/rust-mcp-sdk".into()),
64+
},
65+
capabilities: ServerCapabilities {
66+
// indicates that server support mcp tools
67+
tools: Some(ServerCapabilitiesTools { list_changed: None }),
68+
resources: Some(ServerCapabilitiesResources{ list_changed: None, subscribe: None }),
69+
completions:Some(Map::new()),
70+
..Default::default() // Using default values for other fields
71+
},
72+
meta: None,
73+
instructions: Some("server instructions...".into()),
74+
protocol_version: ProtocolVersion::V2025_11_25.into(),
75+
};
76+
77+
// STEP 2: instantiate our custom handler for handling MCP messages
78+
let handler = ExampleServerHandler {};
79+
80+
// STEP 3: instantiate HyperServer, providing `server_details` , `handler` and HyperServerOptions
81+
let server = hyper_server::create_server(
82+
server_details,
83+
handler.to_mcp_server_handler(),
84+
HyperServerOptions {
85+
host: "127.0.0.1".into(),
86+
event_store: Some(Arc::new(InMemoryEventStore::default())), // enable resumability
87+
task_store: Some(Arc::new(InMemoryTaskStore::new(None))),
88+
client_task_store: Some(Arc::new(InMemoryTaskStore::new(None))),
89+
health_endpoint: Some("/health".into()), // enable health check endpoint
90+
health_handler: Some(Arc::new(CustomHealth {})), // use a custom health check handler
91+
..Default::default()
92+
},
93+
);
94+
95+
// STEP 4: Start the server
96+
server.start().await?;
97+
98+
Ok(())
99+
}

crates/rust-mcp-sdk/src/hyper_servers/routes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(feature = "auth")]
22
pub mod auth_routes;
33
pub mod fallback_routes;
4+
pub mod health_check_route;
45
#[cfg(feature = "sse")]
56
pub mod messages_routes;
67
#[cfg(feature = "sse")]
@@ -44,6 +45,11 @@ pub fn app_routes(
4445
server_options.streamable_http_endpoint(),
4546
));
4647

48+
// mount health check if enabled
49+
if let Some(health_check_endpoint) = server_options.health_endpoint.as_ref() {
50+
router = router.merge(health_check_route::routes(health_check_endpoint));
51+
}
52+
4753
#[cfg(feature = "sse")]
4854
{
4955
router = router
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::hyper_servers::error::TransportServerResult;
2+
use crate::mcp_http::McpHttpHandler;
3+
use crate::{mcp_http::McpAppState, utils::remove_query_and_hash};
4+
use axum::response::IntoResponse;
5+
use axum::routing::get;
6+
use axum::Extension;
7+
use axum::Router;
8+
use http::{HeaderMap, Method, Uri};
9+
use std::sync::Arc;
10+
11+
pub fn routes(health_check_endpoint: &str) -> Router<Arc<McpAppState>> {
12+
Router::new().route(
13+
remove_query_and_hash(health_check_endpoint).as_str(),
14+
get(handle_health_check),
15+
)
16+
}
17+
18+
// The health check endpoint is **not** part of the official MCP spec but is added
19+
// as a practical quality-of-life feature specifically useful when:
20+
// • The server is exposed behind load balancers / reverse proxies (nginx, traefik, haproxy, cloudflare, etc.)
21+
// • The service is running in container orchestration (Kubernetes, Docker Swarm, ECS…)
22+
//
23+
// Many load balancers and proxies periodically send health check requests to determine
24+
// if a backend is still alive.
25+
//
26+
// Custom path can be set in HyperServerOptions.
27+
pub async fn handle_health_check(
28+
headers: HeaderMap,
29+
uri: Uri,
30+
Extension(http_handler): Extension<Arc<McpHttpHandler>>,
31+
) -> TransportServerResult<impl IntoResponse> {
32+
let request = McpHttpHandler::create_request(Method::GET, uri, headers, None);
33+
let generic_res = http_handler.handle_health(request).await?;
34+
let (parts, body) = generic_res.into_parts();
35+
let resp = axum::response::Response::from_parts(parts, axum::body::Body::new(body));
36+
Ok(resp)
37+
}

0 commit comments

Comments
 (0)