diff --git a/.release-manifest.json b/.release-manifest.json
index 97a0f63..a645da6 100644
--- a/.release-manifest.json
+++ b/.release-manifest.json
@@ -1,13 +1,15 @@
{
"crates/rust-mcp-sdk": "0.6.3",
"crates/rust-mcp-macros": "0.5.1",
- "crates/rust-mcp-transport": "0.5.1",
- "examples/hello-world-mcp-server": "0.1.31",
- "examples/hello-world-mcp-server-core": "0.1.22",
- "examples/simple-mcp-client": "0.1.31",
- "examples/simple-mcp-client-core": "0.1.31",
- "examples/hello-world-server-core-streamable-http": "0.1.22",
- "examples/hello-world-server-streamable-http": "0.1.31",
- "examples/simple-mcp-client-core-sse": "0.1.22",
- "examples/simple-mcp-client-sse": "0.1.22"
+ "crates/rust-mcp-transport": "0.5.0",
+ "examples/hello-world-mcp-server-stdio": "0.1.28",
+ "examples/hello-world-mcp-server-stdio-core": "0.1.19",
+ "examples/simple-mcp-client-stdio": "0.1.28",
+ "examples/simple-mcp-client-stdio-core": "0.1.28",
+ "examples/hello-world-server-streamable-http-core": "0.1.19",
+ "examples/hello-world-server-streamable-http": "0.1.28",
+ "examples/simple-mcp-client-sse-core": "0.1.19",
+ "examples/simple-mcp-client-sse": "0.1.19",
+ "examples/simple-mcp-client-streamable-http": "0.1.0",
+ "examples/simple-mcp-client-streamable-http-core": "0.1.0"
}
diff --git a/Cargo.lock b/Cargo.lock
index c10e354..c3c4462 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -257,10 +257,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
-version = "1.2.34"
+version = "1.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
+checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3"
dependencies = [
+ "find-msvc-tools",
"jobserver",
"libc",
"shlex",
@@ -381,9 +382,9 @@ checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
[[package]]
name = "deranged"
-version = "0.4.0"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
dependencies = [
"powerfmt",
]
@@ -451,6 +452,12 @@ dependencies = [
"instant",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650"
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -687,8 +694,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]]
-name = "hello-world-mcp-server"
-version = "0.1.31"
+name = "hello-world-mcp-server-stdio"
+version = "0.1.28"
dependencies = [
"async-trait",
"futures",
@@ -701,8 +708,8 @@ dependencies = [
]
[[package]]
-name = "hello-world-mcp-server-core"
-version = "0.1.22"
+name = "hello-world-mcp-server-stdio-core"
+version = "0.1.19"
dependencies = [
"async-trait",
"futures",
@@ -713,8 +720,8 @@ dependencies = [
]
[[package]]
-name = "hello-world-server-core-streamable-http"
-version = "0.1.22"
+name = "hello-world-server-streamable-http"
+version = "0.1.31"
dependencies = [
"async-trait",
"futures",
@@ -727,8 +734,8 @@ dependencies = [
]
[[package]]
-name = "hello-world-server-streamable-http"
-version = "0.1.31"
+name = "hello-world-server-streamable-http-core"
+version = "0.1.19"
dependencies = [
"async-trait",
"futures",
@@ -1684,6 +1691,7 @@ dependencies = [
"async-trait",
"axum",
"axum-server",
+ "base64 0.22.1",
"futures",
"hyper 1.7.0",
"reqwest",
@@ -1698,6 +1706,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"uuid",
+ "wiremock",
]
[[package]]
@@ -1903,8 +1912,8 @@ dependencies = [
]
[[package]]
-name = "simple-mcp-client"
-version = "0.1.31"
+name = "simple-mcp-client-sse"
+version = "0.1.22"
dependencies = [
"async-trait",
"colored",
@@ -1914,11 +1923,13 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
+ "tracing",
+ "tracing-subscriber",
]
[[package]]
-name = "simple-mcp-client-core"
-version = "0.1.31"
+name = "simple-mcp-client-sse-core"
+version = "0.1.19"
dependencies = [
"async-trait",
"colored",
@@ -1928,11 +1939,41 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
+ "tracing",
+ "tracing-subscriber",
]
[[package]]
-name = "simple-mcp-client-core-sse"
-version = "0.1.22"
+name = "simple-mcp-client-stdio"
+version = "0.1.28"
+dependencies = [
+ "async-trait",
+ "colored",
+ "futures",
+ "rust-mcp-sdk",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.16",
+ "tokio",
+]
+
+[[package]]
+name = "simple-mcp-client-stdio-core"
+version = "0.1.28"
+dependencies = [
+ "async-trait",
+ "colored",
+ "futures",
+ "rust-mcp-sdk",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.16",
+ "tokio",
+]
+
+[[package]]
+name = "simple-mcp-client-streamable-http"
+version = "0.1.0"
dependencies = [
"async-trait",
"colored",
@@ -1947,8 +1988,8 @@ dependencies = [
]
[[package]]
-name = "simple-mcp-client-sse"
-version = "0.1.22"
+name = "simple-mcp-client-streamable-http-core"
+version = "0.1.0"
dependencies = [
"async-trait",
"colored",
@@ -2088,12 +2129,11 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.41"
+version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+checksum = "8ca967379f9d8eb8058d86ed467d81d03e81acd45757e4ca341c24affbe8e8e3"
dependencies = [
"deranged",
- "itoa",
"num-conv",
"powerfmt",
"serde",
@@ -2103,15 +2143,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+checksum = "a9108bb380861b07264b950ded55a44a14a4adc68b9f5efd85aafc3aa4d40a68"
[[package]]
name = "time-macros"
-version = "0.2.22"
+version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+checksum = "7182799245a7264ce590b349d90338f1c1affad93d2639aed5f8f69c090b334c"
dependencies = [
"num-conv",
"time-core",
diff --git a/Cargo.toml b/Cargo.toml
index b4f7cca..711204d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,14 +4,17 @@ members = [
"crates/rust-mcp-macros",
"crates/rust-mcp-sdk",
"crates/rust-mcp-transport",
- "examples/simple-mcp-client",
- "examples/simple-mcp-client-core",
- "examples/hello-world-mcp-server",
- "examples/hello-world-mcp-server-core",
+ "examples/simple-mcp-client-stdio",
+ "examples/simple-mcp-client-stdio-core",
+ "examples/hello-world-mcp-server-stdio",
+ "examples/hello-world-mcp-server-stdio-core",
"examples/hello-world-server-streamable-http",
- "examples/hello-world-server-core-streamable-http",
+ "examples/hello-world-server-streamable-http-core",
"examples/simple-mcp-client-sse",
- "examples/simple-mcp-client-core-sse",
+ "examples/simple-mcp-client-sse-core",
+ "examples/simple-mcp-client-streamable-http",
+ "examples/simple-mcp-client-streamable-http-core",
+
]
[workspace.dependencies]
@@ -39,7 +42,7 @@ tracing-subscriber = { version = "0.3", features = [
"std",
"fmt",
] }
-
+base64 = "0.22"
axum = "0.8"
rustls = "0.23"
tokio-rustls = "0.26"
diff --git a/README.md b/README.md
index 1d334d6..c1e201c 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
[
](https://github.com/rust-mcp-stack/rust-mcp-sdk/actions/workflows/ci.yml)
[
-](examples/hello-world-mcp-server)
+](examples/hello-world-mcp-server-stdio)
A high-performance, asynchronous toolkit for building MCP servers and clients.
Focus on your app's logic while **rust-mcp-sdk** takes care of the rest!
@@ -32,13 +32,12 @@ This project supports following transports:
🚀 The **rust-mcp-sdk** includes a lightweight [Axum](https://github.com/tokio-rs/axum) based server that handles all core functionality seamlessly. Switching between `stdio` and `Streamable HTTP` is straightforward, requiring minimal code changes. The server is designed to efficiently handle multiple concurrent client connections and offers built-in support for SSL.
-
**MCP Streamable HTTP Support**
- ✅ Streamable HTTP Support for MCP Servers
- ✅ DNS Rebinding Protection
- ✅ Batch Messages
- ✅ Streaming & non-streaming JSON response
-- ⬜ Streamable HTTP Support for MCP Clients
+- ✅ Streamable HTTP Support for MCP Clients
- ⬜ Resumability
- ⬜ Authentication / Oauth
@@ -49,6 +48,7 @@ This project supports following transports:
- [MCP Server (stdio)](#mcp-server-stdio)
- [MCP Server (Streamable HTTP)](#mcp-server-streamable-http)
- [MCP Client (stdio)](#mcp-client-stdio)
+ - [MCP Client (Streamable HTTP)](#mcp-client_streamable-http))
- [MCP Client (sse)](#mcp-client-sse)
- [Getting Started](#getting-started)
- [HyperServerOptions](#hyperserveroptions)
@@ -110,7 +110,7 @@ async fn main() -> SdkResult<()> {
}
```
-See hello-world-mcp-server example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :
+See hello-world-mcp-server-stdio example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :

@@ -191,7 +191,8 @@ impl ServerHandler for MyServerHandler {
}
/// Handles requests to call a specific tool.
- async fn handle_call_tool_request( &self, request: CallToolRequest, runtime: Arc, ) -> Result {
+
+ async fn handle_call_tool_request( &self, request: CallToolRequest, runtime: Arc ) -> Result {
if request.tool_name() == SayHelloTool::tool_name() {
Ok( CallToolResult::text_content( vec![TextContent::from("Hello World!".to_string())] ))
@@ -205,7 +206,7 @@ impl ServerHandler for MyServerHandler {
---
-👉 For a more detailed example of a [Hello World MCP](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server) Server that supports multiple tools and provides more type-safe handling of `CallToolRequest`, check out: **[examples/hello-world-mcp-server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server)**
+👉 For a more detailed example of a [Hello World MCP](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio) Server that supports multiple tools and provides more type-safe handling of `CallToolRequest`, check out: **[examples/hello-world-mcp-server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server)**
See hello-world-server-streamable-http example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :
@@ -283,6 +284,8 @@ async fn main() -> SdkResult<()> {
println!("{}",result.content.first().unwrap().as_text_content()?.text);
+ client.shut_down().await?;
+
Ok(())
}
@@ -294,8 +297,82 @@ Here is the output :
> your results may vary slightly depending on the version of the MCP Server in use when you run it.
+### MCP Client (Streamable HTTP)
+```rs
+
+// STEP 1: Custom Handler to handle incoming MCP Messages
+pub struct MyClientHandler;
+
+#[async_trait]
+impl ClientHandler for MyClientHandler {
+ // To check out a list of all the methods in the trait that you can override, take a look at https://github.com/rust-mcp-stack/rust-mcp-sdk/blob/main/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs
+}
+
+#[tokio::main]
+async fn main() -> SdkResult<()> {
+
+ // Step2 : Define client details and capabilities
+ let client_details: InitializeRequestParams = InitializeRequestParams {
+ capabilities: ClientCapabilities::default(),
+ client_info: Implementation {
+ name: "simple-rust-mcp-client-sse".to_string(),
+ version: "0.1.0".to_string(),
+ title: Some("Simple Rust MCP Client (SSE)".to_string()),
+ },
+ protocol_version: LATEST_PROTOCOL_VERSION.into(),
+ };
+
+ // Step 3: Create transport options to connect to an MCP server via Streamable HTTP.
+ let transport_options = StreamableTransportOptions {
+ mcp_url: MCP_SERVER_URL.to_string(),
+ request_options: RequestOptions {
+ ..RequestOptions::default()
+ },
+ };
+
+ // STEP 4: instantiate the custom handler that is responsible for handling MCP messages
+ let handler = MyClientHandler {};
+
+ // STEP 5: create the client with transport options and the handler
+ let client = client_runtime::with_transport_options(client_details, transport_options, handler);
+
+ // STEP 6: start the MCP client
+ client.clone().start().await?;
+
+ // STEP 7: use client methods to communicate with the MCP Server as you wish
+
+ // Retrieve and display the list of tools available on the server
+ let server_version = client.server_version().unwrap();
+ let tools = client.list_tools(None).await?.tools;
+ println!("List of tools for {}@{}", server_version.name, server_version.version);
+
+ tools.iter().enumerate().for_each(|(tool_index, tool)| {
+ println!(" {}. {} : {}",
+ tool_index + 1,
+ tool.name,
+ tool.description.clone().unwrap_or_default()
+ );
+ });
+
+ println!("Call \"add\" tool with 100 and 28 ...");
+ // Create a `Map` to represent the tool parameters
+ let params = json!({"a": 100,"b": 28}).as_object().unwrap().clone();
+ let request = CallToolRequestParams { name: "add".to_string(),arguments: Some(params)};
+
+ // invoke the tool
+ let result = client.call_tool(request).await?;
+
+ println!("{}",result.content.first().unwrap().as_text_content()?.text);
+
+ client.shut_down().await?;
+
+ Ok(())
+```
+👉 see [examples/simple-mcp-client-streamable-http](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-streamable-http) for a complete working example.
+
+
### MCP Client (sse)
-Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost identical, with one exception at `step 3`. Instead of creating a `StdioTransport`, you simply create a `ClientSseTransport`. The rest of the code remains the same:
+Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost identical to the [stdio example](#mcp-client-stdio) , with one exception at `step 3`. Instead of creating a `StdioTransport`, you simply create a `ClientSseTransport`. The rest of the code remains the same:
```diff
- let transport = StdioTransport::create_with_server_launch(
@@ -306,6 +383,8 @@ Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost
+ let transport = ClientSseTransport::new(MCP_SERVER_URL, ClientSseTransportOptions::default())?;
```
+👉 see [examples/simple-mcp-client-sse](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-sse) for a complete working example.
+
## Getting Started
@@ -344,9 +423,15 @@ pub struct HyperServerOptions {
/// Hostname or IP address the server will bind to (default: "8080")
pub port: u16,
+ /// Optional thread-safe session id generator to generate unique session IDs.
+ pub session_id_generator: Option>>,
+
/// Optional custom path for the Streamable HTTP endpoint (default: `/mcp`)
pub custom_streamable_http_endpoint: Option,
+ /// Shared transport configuration used by the server
+ pub transport_options: Arc,
+
/// This setting only applies to streamable HTTP.
/// If true, the server will return JSON responses instead of starting an SSE stream.
/// This can be useful for simple request/response scenarios without streaming.
@@ -356,12 +441,6 @@ pub struct HyperServerOptions {
/// Interval between automatic ping messages sent to clients to detect disconnects
pub ping_interval: Duration,
- /// Shared transport configuration used by the server
- pub transport_options: Arc,
-
- /// Optional thread-safe session id generator to generate unique session IDs.
- pub session_id_generator: Option>,
-
/// Enables SSL/TLS if set to `true`
pub enable_ssl: bool,
@@ -373,17 +452,6 @@ pub struct HyperServerOptions {
/// Required if `enable_ssl` is `true`.
pub ssl_key_path: Option,
- /// If set to true, the SSE transport will also be supported for backward compatibility (default: true)
- pub sse_support: bool,
-
- /// Optional custom path for the Server-Sent Events (SSE) endpoint (default: `/sse`)
- /// Applicable only if sse_support is true
- pub custom_sse_endpoint: Option,
-
- /// Optional custom path for the MCP messages endpoint for sse (default: `/messages`)
- /// Applicable only if sse_support is true
- pub custom_messages_endpoint: Option,
-
/// List of allowed host header values for DNS rebinding protection.
/// If not specified, host validation is disabled.
pub allowed_hosts: Option>,
@@ -395,6 +463,17 @@ pub struct HyperServerOptions {
/// Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
/// Default is false for backwards compatibility.
pub dns_rebinding_protection: bool,
+
+ /// If set to true, the SSE transport will also be supported for backward compatibility (default: true)
+ pub sse_support: bool,
+
+ /// Optional custom path for the Server-Sent Events (SSE) endpoint (default: `/sse`)
+ /// Applicable only if sse_support is true
+ pub custom_sse_endpoint: Option,
+
+ /// Optional custom path for the MCP messages endpoint for sse (default: `/messages`)
+ /// Applicable only if sse_support is true
+ pub custom_messages_endpoint: Option,
}
```
@@ -416,9 +495,13 @@ The `rust-mcp-sdk` crate provides several features that can be enabled or disabl
- `server`: Activates MCP server capabilities in `rust-mcp-sdk`, providing modules and APIs for building and managing MCP servers.
- `client`: Activates MCP client capabilities, offering modules and APIs for client development and communicating with MCP servers.
-- `hyper-server`: This feature enables the **sse** transport for MCP servers, supporting multiple simultaneous client connections out of the box.
-- `ssl`: This feature enables TLS/SSL support for the **sse** transport when used with the `hyper-server`.
+- `hyper-server`: This feature is necessary to enable `Streamable HTTP` or `Server-Sent Events (SSE)` transports for MCP servers. It must be used alongside the server feature to support the required server functionalities.
+- `ssl`: This feature enables TLS/SSL support for the `Streamable HTTP` or `Server-Sent Events (SSE)` transport when used with the `hyper-server`.
- `macros`: Provides procedural macros for simplifying the creation and manipulation of MCP Tool structures.
+- `sse`: Enables support for the `Server-Sent Events (SSE)` transport.
+- `streamable-http`: Enables support for the `Streamable HTTP` transport.
+- `stdio`: Enables support for the `standard input/output (stdio)` transport.
+
- `tls-no-provider`: Enables TLS without a crypto provider. This is useful if you are already using a different crypto provider than the aws-lc default.
#### MCP Protocol Versions with Corresponding Features
@@ -450,9 +533,9 @@ If you only need the MCP Server functionality, you can disable the default featu
```toml
[dependencies]
-rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["server","macros"] }
+rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["server","macros","stdio"] }
```
-Optionally add `hyper-server` for **sse** transport, and `ssl` feature for tls/ssl support of the `hyper-server`
+Optionally add `hyper-server` and `streamable-http` for **Streamable HTTP** transport, and `ssl` feature for tls/ssl support of the `hyper-server`
@@ -465,7 +548,7 @@ Add the following to your Cargo.toml:
```toml
[dependencies]
-rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["client","2024_11_05"] }
+rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["client","2024_11_05","stdio"] }
```
@@ -478,10 +561,10 @@ Learn when to use the `mcp_*_handler` traits versus the lower-level `mcp_*_hand
[rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) provides two type of handler traits that you can chose from:
- **ServerHandler**: This is the recommended trait for your MCP project, offering a default implementation for all types of MCP messages. It includes predefined implementations within the trait, such as handling initialization or responding to ping requests, so you only need to override and customize the handler functions relevant to your specific needs.
- Refer to [examples/hello-world-mcp-server/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server/src/handler.rs) for an example.
+ Refer to [examples/hello-world-mcp-server-stdio/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio/src/handler.rs) for an example.
- **ServerHandlerCore**: If you need more control over MCP messages, consider using `ServerHandlerCore`. It offers three primary methods to manage the three MCP message types: `request`, `notification`, and `error`. While still providing type-safe objects in these methods, it allows you to determine how to handle each message based on its type and parameters.
- Refer to [examples/hello-world-mcp-server-core/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-core/src/handler.rs) for an example.
+ Refer to [examples/hello-world-mcp-server-stdio-core/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio-core/src/handler.rs) for an example.
---
@@ -510,7 +593,7 @@ Both functions create an MCP client instance.
-Check out the corresponding examples at: [examples/simple-mcp-client](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client) and [examples/simple-mcp-client-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-core).
+Check out the corresponding examples at: [examples/simple-mcp-client-stdio](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio) and [examples/simple-mcp-client-stdio-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio-core).
## Projects using Rust MCP SDK
diff --git a/crates/rust-mcp-sdk/Cargo.toml b/crates/rust-mcp-sdk/Cargo.toml
index 50d5e7f..99d6f86 100644
--- a/crates/rust-mcp-sdk/Cargo.toml
+++ b/crates/rust-mcp-sdk/Cargo.toml
@@ -24,15 +24,17 @@ futures = { workspace = true }
thiserror = { workspace = true }
axum = { workspace = true, optional = true }
-uuid = { workspace = true, features = ["v4"], optional = true }
+uuid = { workspace = true, features = ["v4"] }
tokio-stream = { workspace = true, optional = true }
axum-server = { version = "0.7", features = [], optional = true }
tracing.workspace = true
+base64.workspace = true
# rustls = { workspace = true, optional = true }
hyper = { version = "1.6.0", optional = true }
[dev-dependencies]
+wiremock = "0.5"
reqwest = { workspace = true, default-features = false, features = [
"stream",
"rustls-tls",
@@ -51,48 +53,55 @@ default = [
"client",
"server",
"macros",
+ "stdio",
+ "sse",
+ "streamable-http",
"hyper-server",
"ssl",
"2025_06_18",
] # All features enabled by default
-server = ["rust-mcp-transport/stdio"] # Server feature
-client = ["rust-mcp-transport/stdio", "rust-mcp-transport/sse"] # Client feature
-hyper-server = [
- "axum",
- "axum-server",
- "hyper",
- "server",
- "uuid",
- "tokio-stream",
- "rust-mcp-transport/sse",
-]
+
+sse = ["rust-mcp-transport/sse"]
+streamable-http = ["rust-mcp-transport/streamable-http"]
+stdio = ["rust-mcp-transport/stdio"]
+
+server = [] # Server feature
+client = [] # Client feature
+hyper-server = ["axum", "axum-server", "hyper", "server", "tokio-stream"]
ssl = ["axum-server/tls-rustls"]
tls-no-provider = ["axum-server/tls-rustls-no-provider"]
macros = ["rust-mcp-macros/sdk"]
-# enables mcp protocol version 2025_06_18
-2025_06_18 = [
+# enables mcp protocol version 2025-06-18
+2025-06-18 = [
"rust-mcp-schema/2025_06_18",
"rust-mcp-macros/2025_06_18",
"rust-mcp-transport/2025_06_18",
"rust-mcp-schema/schema_utils",
]
+# Alias: allow users to use underscores instead of hyphens
+2025_06_18 = ["2025-06-18"]
# enables mcp protocol version 2025_03_26
-2025_03_26 = [
+2025-03-26 = [
"rust-mcp-schema/2025_03_26",
"rust-mcp-macros/2025_03_26",
"rust-mcp-transport/2025_03_26",
"rust-mcp-schema/schema_utils",
]
+# Alias: allow users to use underscores instead of hyphens
+2025_03_26 = ["2025-03-26"]
+
# enables mcp protocol version 2024_11_05
-2024_11_05 = [
+2024-11-05 = [
"rust-mcp-schema/2024_11_05",
"rust-mcp-macros/2024_11_05",
"rust-mcp-transport/2024_11_05",
"rust-mcp-schema/schema_utils",
]
+# Alias: allow users to use underscores instead of hyphens
+2024_11_05 = ["2024-11-05"]
[lints]
workspace = true
diff --git a/crates/rust-mcp-sdk/README.md b/crates/rust-mcp-sdk/README.md
index cbe7318..8036022 100644
--- a/crates/rust-mcp-sdk/README.md
+++ b/crates/rust-mcp-sdk/README.md
@@ -9,7 +9,7 @@
[
](https://github.com/rust-mcp-stack/rust-mcp-sdk/actions/workflows/ci.yml)
[
-](examples/hello-world-mcp-server)
+](examples/hello-world-mcp-server-stdio)
A high-performance, asynchronous toolkit for building MCP servers and clients.
Focus on your app's logic while **rust-mcp-sdk** takes care of the rest!
@@ -32,13 +32,12 @@ This project supports following transports:
🚀 The **rust-mcp-sdk** includes a lightweight [Axum](https://github.com/tokio-rs/axum) based server that handles all core functionality seamlessly. Switching between `stdio` and `Streamable HTTP` is straightforward, requiring minimal code changes. The server is designed to efficiently handle multiple concurrent client connections and offers built-in support for SSL.
-
**MCP Streamable HTTP Support**
- ✅ Streamable HTTP Support for MCP Servers
- ✅ DNS Rebinding Protection
- ✅ Batch Messages
- ✅ Streaming & non-streaming JSON response
-- ⬜ Streamable HTTP Support for MCP Clients
+- ✅ Streamable HTTP Support for MCP Clients
- ⬜ Resumability
- ⬜ Authentication / Oauth
@@ -49,6 +48,7 @@ This project supports following transports:
- [MCP Server (stdio)](#mcp-server-stdio)
- [MCP Server (Streamable HTTP)](#mcp-server-streamable-http)
- [MCP Client (stdio)](#mcp-client-stdio)
+ - [MCP Client (Streamable HTTP)](#mcp-client_streamable-http))
- [MCP Client (sse)](#mcp-client-sse)
- [Getting Started](#getting-started)
- [HyperServerOptions](#hyperserveroptions)
@@ -110,7 +110,7 @@ async fn main() -> SdkResult<()> {
}
```
-See hello-world-mcp-server example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :
+See hello-world-mcp-server-stdio example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :

@@ -205,7 +205,7 @@ impl ServerHandler for MyServerHandler {
---
-👉 For a more detailed example of a [Hello World MCP](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server) Server that supports multiple tools and provides more type-safe handling of `CallToolRequest`, check out: **[examples/hello-world-mcp-server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server)**
+👉 For a more detailed example of a [Hello World MCP](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio) Server that supports multiple tools and provides more type-safe handling of `CallToolRequest`, check out: **[examples/hello-world-mcp-server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server)**
See hello-world-server-streamable-http example running in [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) :
@@ -283,6 +283,8 @@ async fn main() -> SdkResult<()> {
println!("{}",result.content.first().unwrap().as_text_content()?.text);
+ client.shut_down().await?;
+
Ok(())
}
@@ -294,8 +296,82 @@ Here is the output :
> your results may vary slightly depending on the version of the MCP Server in use when you run it.
+### MCP Client (Streamable HTTP)
+```rs
+
+// STEP 1: Custom Handler to handle incoming MCP Messages
+pub struct MyClientHandler;
+
+#[async_trait]
+impl ClientHandler for MyClientHandler {
+ // To check out a list of all the methods in the trait that you can override, take a look at https://github.com/rust-mcp-stack/rust-mcp-sdk/blob/main/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs
+}
+
+#[tokio::main]
+async fn main() -> SdkResult<()> {
+
+ // Step2 : Define client details and capabilities
+ let client_details: InitializeRequestParams = InitializeRequestParams {
+ capabilities: ClientCapabilities::default(),
+ client_info: Implementation {
+ name: "simple-rust-mcp-client-sse".to_string(),
+ version: "0.1.0".to_string(),
+ title: Some("Simple Rust MCP Client (SSE)".to_string()),
+ },
+ protocol_version: LATEST_PROTOCOL_VERSION.into(),
+ };
+
+ // Step 3: Create transport options to connect to an MCP server via Streamable HTTP.
+ let transport_options = StreamableTransportOptions {
+ mcp_url: MCP_SERVER_URL.to_string(),
+ request_options: RequestOptions {
+ ..RequestOptions::default()
+ },
+ };
+
+ // STEP 4: instantiate the custom handler that is responsible for handling MCP messages
+ let handler = MyClientHandler {};
+
+ // STEP 5: create the client with transport options and the handler
+ let client = client_runtime::with_transport_options(client_details, transport_options, handler);
+
+ // STEP 6: start the MCP client
+ client.clone().start().await?;
+
+ // STEP 7: use client methods to communicate with the MCP Server as you wish
+
+ // Retrieve and display the list of tools available on the server
+ let server_version = client.server_version().unwrap();
+ let tools = client.list_tools(None).await?.tools;
+ println!("List of tools for {}@{}", server_version.name, server_version.version);
+
+ tools.iter().enumerate().for_each(|(tool_index, tool)| {
+ println!(" {}. {} : {}",
+ tool_index + 1,
+ tool.name,
+ tool.description.clone().unwrap_or_default()
+ );
+ });
+
+ println!("Call \"add\" tool with 100 and 28 ...");
+ // Create a `Map` to represent the tool parameters
+ let params = json!({"a": 100,"b": 28}).as_object().unwrap().clone();
+ let request = CallToolRequestParams { name: "add".to_string(),arguments: Some(params)};
+
+ // invoke the tool
+ let result = client.call_tool(request).await?;
+
+ println!("{}",result.content.first().unwrap().as_text_content()?.text);
+
+ client.shut_down().await?;
+
+ Ok(())
+```
+👉 see [examples/simple-mcp-client-streamable-http](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-streamable-http) for a complete working example.
+
+
### MCP Client (sse)
-Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost identical, with one exception at `step 3`. Instead of creating a `StdioTransport`, you simply create a `ClientSseTransport`. The rest of the code remains the same:
+Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost identical to the [stdio example](#mcp-client-stdio) , with one exception at `step 3`. Instead of creating a `StdioTransport`, you simply create a `ClientSseTransport`. The rest of the code remains the same:
```diff
- let transport = StdioTransport::create_with_server_launch(
@@ -306,6 +382,8 @@ Creating an MCP client using the `rust-mcp-sdk` with the SSE transport is almost
+ let transport = ClientSseTransport::new(MCP_SERVER_URL, ClientSseTransportOptions::default())?;
```
+👉 see [examples/simple-mcp-client-sse](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-sse) for a complete working example.
+
## Getting Started
@@ -344,9 +422,15 @@ pub struct HyperServerOptions {
/// Hostname or IP address the server will bind to (default: "8080")
pub port: u16,
+ /// Optional thread-safe session id generator to generate unique session IDs.
+ pub session_id_generator: Option>>,
+
/// Optional custom path for the Streamable HTTP endpoint (default: `/mcp`)
pub custom_streamable_http_endpoint: Option,
+ /// Shared transport configuration used by the server
+ pub transport_options: Arc,
+
/// This setting only applies to streamable HTTP.
/// If true, the server will return JSON responses instead of starting an SSE stream.
/// This can be useful for simple request/response scenarios without streaming.
@@ -356,12 +440,6 @@ pub struct HyperServerOptions {
/// Interval between automatic ping messages sent to clients to detect disconnects
pub ping_interval: Duration,
- /// Shared transport configuration used by the server
- pub transport_options: Arc,
-
- /// Optional thread-safe session id generator to generate unique session IDs.
- pub session_id_generator: Option>,
-
/// Enables SSL/TLS if set to `true`
pub enable_ssl: bool,
@@ -373,17 +451,6 @@ pub struct HyperServerOptions {
/// Required if `enable_ssl` is `true`.
pub ssl_key_path: Option,
- /// If set to true, the SSE transport will also be supported for backward compatibility (default: true)
- pub sse_support: bool,
-
- /// Optional custom path for the Server-Sent Events (SSE) endpoint (default: `/sse`)
- /// Applicable only if sse_support is true
- pub custom_sse_endpoint: Option,
-
- /// Optional custom path for the MCP messages endpoint for sse (default: `/messages`)
- /// Applicable only if sse_support is true
- pub custom_messages_endpoint: Option,
-
/// List of allowed host header values for DNS rebinding protection.
/// If not specified, host validation is disabled.
pub allowed_hosts: Option>,
@@ -395,6 +462,17 @@ pub struct HyperServerOptions {
/// Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured).
/// Default is false for backwards compatibility.
pub dns_rebinding_protection: bool,
+
+ /// If set to true, the SSE transport will also be supported for backward compatibility (default: true)
+ pub sse_support: bool,
+
+ /// Optional custom path for the Server-Sent Events (SSE) endpoint (default: `/sse`)
+ /// Applicable only if sse_support is true
+ pub custom_sse_endpoint: Option,
+
+ /// Optional custom path for the MCP messages endpoint for sse (default: `/messages`)
+ /// Applicable only if sse_support is true
+ pub custom_messages_endpoint: Option,
}
```
@@ -416,9 +494,14 @@ The `rust-mcp-sdk` crate provides several features that can be enabled or disabl
- `server`: Activates MCP server capabilities in `rust-mcp-sdk`, providing modules and APIs for building and managing MCP servers.
- `client`: Activates MCP client capabilities, offering modules and APIs for client development and communicating with MCP servers.
-- `hyper-server`: This feature enables the **sse** transport for MCP servers, supporting multiple simultaneous client connections out of the box.
-- `ssl`: This feature enables TLS/SSL support for the **sse** transport when used with the `hyper-server`.
+- `hyper-server`: This feature is necessary to enable `Streamable HTTP` or `Server-Sent Events (SSE)` transports for MCP servers. It must be used alongside the server feature to support the required server functionalities.
+- `ssl`: This feature enables TLS/SSL support for the `Streamable HTTP` or `Server-Sent Events (SSE)` transport when used with the `hyper-server`.
- `macros`: Provides procedural macros for simplifying the creation and manipulation of MCP Tool structures.
+- `sse`: Enables support for the `Server-Sent Events (SSE)` transport.
+- `streamable-http`: Enables support for the `Streamable HTTP` transport.
+- `stdio`: Enables support for the `standard input/output (stdio)` transport.
+
+- `tls-no-provider`: Enables TLS without a crypto provider. This is useful if you are already using a different crypto provider than the aws-lc default.
#### MCP Protocol Versions with Corresponding Features
@@ -449,9 +532,9 @@ If you only need the MCP Server functionality, you can disable the default featu
```toml
[dependencies]
-rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["server","macros"] }
+rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["server","macros","stdio"] }
```
-Optionally add `hyper-server` for **sse** transport, and `ssl` feature for tls/ssl support of the `hyper-server`
+Optionally add `hyper-server` and `streamable-http` for **Streamable HTTP** transport, and `ssl` feature for tls/ssl support of the `hyper-server`
@@ -464,7 +547,7 @@ Add the following to your Cargo.toml:
```toml
[dependencies]
-rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["client","2024_11_05"] }
+rust-mcp-sdk = { version = "0.2.0", default-features = false, features = ["client","2024_11_05","stdio"] }
```
@@ -477,10 +560,10 @@ Learn when to use the `mcp_*_handler` traits versus the lower-level `mcp_*_hand
[rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) provides two type of handler traits that you can chose from:
- **ServerHandler**: This is the recommended trait for your MCP project, offering a default implementation for all types of MCP messages. It includes predefined implementations within the trait, such as handling initialization or responding to ping requests, so you only need to override and customize the handler functions relevant to your specific needs.
- Refer to [examples/hello-world-mcp-server/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server/src/handler.rs) for an example.
+ Refer to [examples/hello-world-mcp-server-stdio/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio/src/handler.rs) for an example.
- **ServerHandlerCore**: If you need more control over MCP messages, consider using `ServerHandlerCore`. It offers three primary methods to manage the three MCP message types: `request`, `notification`, and `error`. While still providing type-safe objects in these methods, it allows you to determine how to handle each message based on its type and parameters.
- Refer to [examples/hello-world-mcp-server-core/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-core/src/handler.rs) for an example.
+ Refer to [examples/hello-world-mcp-server-stdio-core/src/handler.rs](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio-core/src/handler.rs) for an example.
---
@@ -509,7 +592,7 @@ Both functions create an MCP client instance.
-Check out the corresponding examples at: [examples/simple-mcp-client](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client) and [examples/simple-mcp-client-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-core).
+Check out the corresponding examples at: [examples/simple-mcp-client-stdio](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio) and [examples/simple-mcp-client-stdio-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio-core).
## Projects using Rust MCP SDK
diff --git a/crates/rust-mcp-sdk/src/error.rs b/crates/rust-mcp-sdk/src/error.rs
index 3de8d98..3879526 100644
--- a/crates/rust-mcp-sdk/src/error.rs
+++ b/crates/rust-mcp-sdk/src/error.rs
@@ -11,25 +11,36 @@ pub type SdkResult = core::result::Result;
#[derive(Debug, Error)]
pub enum McpSdkError {
+ #[error("Transport error: {0}")]
+ Transport(#[from] TransportError),
+
+ #[error("I/O error: {0}")]
+ Io(#[from] std::io::Error),
+
#[error("{0}")]
RpcError(#[from] RpcError),
+
#[error("{0}")]
- IoError(#[from] std::io::Error),
- #[error("{0}")]
- TransportError(#[from] TransportError),
- #[error("{0}")]
- JoinError(#[from] JoinError),
- #[error("{0}")]
- AnyError(Box<(dyn std::error::Error + Send + Sync)>),
- #[error("{0}")]
- SdkError(#[from] crate::schema::schema_utils::SdkError),
+ Join(#[from] JoinError),
+
#[cfg(feature = "hyper-server")]
#[error("{0}")]
- TransportServerError(#[from] TransportServerError),
- #[error("Incompatible mcp protocol version: requested:{0} current:{1}")]
- IncompatibleProtocolVersion(String, String),
+ HyperServer(#[from] TransportServerError),
+
#[error("{0}")]
- ParseProtocolVersionError(#[from] ParseProtocolVersionError),
+ SdkError(#[from] crate::schema::schema_utils::SdkError),
+
+ #[error("Protocol error: {kind}")]
+ Protocol { kind: ProtocolErrorKind },
+}
+
+// Sub-enum for protocol-related errors
+#[derive(Debug, Error)]
+pub enum ProtocolErrorKind {
+ #[error("Incompatible protocol version: requested {requested}, current {current}")]
+ IncompatibleVersion { requested: String, current: String },
+ #[error("Failed to parse protocol version: {0}")]
+ ParseError(#[from] ParseProtocolVersionError),
}
impl McpSdkError {
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/app_state.rs b/crates/rust-mcp-sdk/src/hyper_servers/app_state.rs
index 0c1dcf3..ff6d5b2 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/app_state.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/app_state.rs
@@ -1,11 +1,9 @@
use std::{sync::Arc, time::Duration};
-use crate::schema::InitializeResult;
-use rust_mcp_transport::TransportOptions;
-
+use super::session_store::SessionStore;
use crate::mcp_traits::mcp_handler::McpServerHandler;
-
-use super::{session_store::SessionStore, IdGenerator};
+use crate::{id_generator::FastIdGenerator, mcp_traits::IdGenerator, schema::InitializeResult};
+use rust_mcp_transport::{SessionId, TransportOptions};
/// Application state struct for the Hyper server
///
@@ -14,7 +12,8 @@ use super::{session_store::SessionStore, IdGenerator};
#[derive(Clone)]
pub struct AppState {
pub session_store: Arc,
- pub id_generator: Arc,
+ pub id_generator: Arc>,
+ pub stream_id_gen: Arc,
pub server_details: Arc,
pub handler: Arc,
pub ping_interval: Duration,
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/routes/hyper_utils.rs b/crates/rust-mcp-sdk/src/hyper_servers/routes/hyper_utils.rs
index daf5d94..da69c67 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/routes/hyper_utils.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/routes/hyper_utils.rs
@@ -6,7 +6,7 @@ use crate::{
},
mcp_runtimes::server_runtime::DEFAULT_STREAM_ID,
mcp_server::{server_runtime, ServerRuntime},
- mcp_traits::mcp_handler::McpServerHandler,
+ mcp_traits::{mcp_handler::McpServerHandler, IdGenerator},
utils::validate_mcp_protocol_version,
};
@@ -22,13 +22,12 @@ use axum::{
};
use futures::stream;
use hyper::{header, HeaderMap, StatusCode};
-use rust_mcp_transport::{SessionId, SseTransport};
+use rust_mcp_transport::{
+ SessionId, SseTransport, StreamId, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_ID_HEADER,
+};
use std::{sync::Arc, time::Duration};
use tokio::io::{duplex, AsyncBufReadExt, BufReader};
-pub const MCP_SESSION_ID_HEADER: &str = "Mcp-Session-Id";
-pub const MCP_PROTOCOL_VERSION_HEADER: &str = "Mcp-Protocol-Version";
-
const DUPLEX_BUFFER_SIZE: usize = 8192;
async fn create_sse_stream(
@@ -41,11 +40,11 @@ async fn create_sse_stream(
let payload_string = payload.map(|p| p.to_string());
// TODO: this logic should be moved out after refactoing the mcp_stream.rs
- let result = payload_string
+ let payload_contains_request = payload_string
.as_ref()
.map(|json_str| contains_request(json_str))
.unwrap_or(Ok(false));
- let Ok(payload_contains_request) = result else {
+ let Ok(payload_contains_request) = payload_contains_request else {
return Ok((StatusCode::BAD_REQUEST, Json(SdkError::parse_error())).into_response());
};
@@ -54,18 +53,20 @@ async fn create_sse_stream(
// writable stream to deliver message to the client
let (write_tx, write_rx) = duplex(DUPLEX_BUFFER_SIZE);
- let transport = SseTransport::::new(
- read_rx,
- write_tx,
- read_tx,
- Arc::clone(&state.transport_options),
- )
- .map_err(|err| TransportServerError::TransportError(err.to_string()))?;
+ let transport = Arc::new(
+ SseTransport::::new(
+ read_rx,
+ write_tx,
+ read_tx,
+ Arc::clone(&state.transport_options),
+ )
+ .map_err(|err| TransportServerError::TransportError(err.to_string()))?,
+ );
- let stream_id = if standalone {
+ let stream_id: StreamId = if standalone {
DEFAULT_STREAM_ID.to_string()
} else {
- state.id_generator.generate()
+ state.stream_id_gen.generate()
};
let ping_interval = state.ping_interval;
let runtime_clone = Arc::clone(&runtime);
@@ -85,6 +86,7 @@ async fn create_sse_stream(
// Construct SSE stream
let reader = BufReader::new(write_rx);
+ // outgoing messages from server to the client
let message_stream = stream::unfold(reader, |mut reader| async move {
let mut line = String::new();
@@ -117,12 +119,12 @@ async fn create_sse_stream(
// TODO: this function will be removed after refactoring the readable stream of the transports
// so we would deserialize the string syncronousely and have more control over the flow
-// this function could potentially add a 20-250 ns overhead which could be avoided
+// this function may incur a slight runtime cost which could be avoided after refactoring
fn contains_request(json_str: &str) -> Result {
let value: serde_json::Value = serde_json::from_str(json_str)?;
match value {
serde_json::Value::Object(obj) => Ok(obj.contains_key("id") && obj.contains_key("method")),
- serde_json::Value::Array(arr) => Ok(arr.iter().all(|item| {
+ serde_json::Value::Array(arr) => Ok(arr.iter().any(|item| {
item.as_object()
.map(|obj| obj.contains_key("id") && obj.contains_key("method"))
.unwrap_or(false)
@@ -131,6 +133,19 @@ fn contains_request(json_str: &str) -> Result {
}
}
+fn is_result(json_str: &str) -> Result {
+ let value: serde_json::Value = serde_json::from_str(json_str)?;
+ match value {
+ serde_json::Value::Object(obj) => Ok(obj.contains_key("result")),
+ serde_json::Value::Array(arr) => Ok(arr.iter().all(|item| {
+ item.as_object()
+ .map(|obj| obj.contains_key("result"))
+ .unwrap_or(false)
+ })),
+ _ => Ok(false),
+ }
+}
+
pub async fn create_standalone_stream(
session_id: SessionId,
state: Arc,
@@ -224,7 +239,12 @@ async fn single_shot_stream(
tokio::spawn(async move {
match runtime_clone
- .start_stream(transport, &stream_id, ping_interval, payload_string)
+ .start_stream(
+ Arc::new(transport),
+ &stream_id,
+ ping_interval,
+ payload_string,
+ )
.await
{
Ok(_) => tracing::info!("stream {} exited gracefully.", &stream_id),
@@ -233,7 +253,6 @@ async fn single_shot_stream(
let _ = runtime.remove_transport(&stream_id).await;
});
- // Construct SSE stream
let mut reader = BufReader::new(write_rx);
let mut line = String::new();
let response = match reader.read_line(&mut line).await {
@@ -310,15 +329,34 @@ pub async fn process_incoming_message(
match state.session_store.get(&session_id).await {
Some(runtime) => {
let runtime = runtime.lock().await.to_owned();
-
- create_sse_stream(
- runtime.clone(),
- session_id.clone(),
- state.clone(),
- Some(payload),
- false,
- )
- .await
+ // when receiving a result in a streamable_http server, that means it was sent by the standalone sse transport
+ // it should be processed by the same transport , therefore no need to call create_sse_stream
+ let Ok(is_result) = is_result(payload) else {
+ return Ok((StatusCode::BAD_REQUEST, Json(SdkError::parse_error())).into_response());
+ };
+
+ if is_result {
+ match runtime
+ .consume_payload_string(DEFAULT_STREAM_ID, payload)
+ .await
+ {
+ Ok(()) => Ok((StatusCode::ACCEPTED, Json(())).into_response()),
+ Err(err) => Ok((
+ StatusCode::BAD_REQUEST,
+ Json(SdkError::internal_error().with_message(err.to_string().as_ref())),
+ )
+ .into_response()),
+ }
+ } else {
+ create_sse_stream(
+ runtime.clone(),
+ session_id.clone(),
+ state.clone(),
+ Some(payload),
+ false,
+ )
+ .await
+ }
}
None => {
let error = SdkError::session_not_found();
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/routes/sse_routes.rs b/crates/rust-mcp-sdk/src/hyper_servers/routes/sse_routes.rs
index a014e94..27a16b2 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/routes/sse_routes.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/routes/sse_routes.rs
@@ -1,3 +1,4 @@
+use crate::mcp_server::error::TransportServerError;
use crate::schema::schema_utils::ClientMessage;
use crate::{
hyper_servers::{
@@ -90,13 +91,17 @@ pub async fn handle_sse(
let (write_tx, write_rx) = duplex(DUPLEX_BUFFER_SIZE);
// create a transport for sending/receiving messages
- let transport = SseTransport::new(
+ let Ok(transport) = SseTransport::new(
read_rx,
write_tx,
read_tx,
Arc::clone(&state.transport_options),
- )
- .unwrap();
+ ) else {
+ return Err(TransportServerError::TransportError(
+ "Failed to create SSE transport".to_string(),
+ ));
+ };
+
let h: Arc = state.handler.clone();
// create a new server instance with unique session_id and
let server: Arc = server_runtime::create_server_instance(
@@ -115,7 +120,12 @@ pub async fn handle_sse(
// Start the server
tokio::spawn(async move {
match server
- .start_stream(transport, DEFAULT_STREAM_ID, state.ping_interval, None)
+ .start_stream(
+ Arc::new(transport),
+ DEFAULT_STREAM_ID,
+ state.ping_interval,
+ None,
+ )
.await
{
Ok(_) => tracing::info!("server {} exited gracefully.", session_id.to_owned()),
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/routes/streamable_http_routes.rs b/crates/rust-mcp-sdk/src/hyper_servers/routes/streamable_http_routes.rs
index 83cc372..00d46c0 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/routes/streamable_http_routes.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/routes/streamable_http_routes.rs
@@ -1,4 +1,4 @@
-use super::hyper_utils::{start_new_session, MCP_SESSION_ID_HEADER};
+use super::hyper_utils::start_new_session;
use crate::schema::schema_utils::SdkError;
use crate::{
error::McpSdkError,
@@ -14,6 +14,7 @@ use crate::{
},
utils::valid_initialize_method,
};
+use axum::routing::get;
use axum::{
extract::{Query, State},
middleware,
@@ -22,11 +23,9 @@ use axum::{
Json, Router,
};
use hyper::{HeaderMap, StatusCode};
-use rust_mcp_transport::SessionId;
+use rust_mcp_transport::{SessionId, MCP_SESSION_ID_HEADER};
use std::{collections::HashMap, sync::Arc};
-use axum::routing::get;
-
pub fn routes(state: Arc, streamable_http_endpoint: &str) -> Router> {
Router::new()
.route(streamable_http_endpoint, get(handle_streamable_http_get))
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/server.rs b/crates/rust-mcp-sdk/src/hyper_servers/server.rs
index f093da3..1c3b3cf 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/server.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/server.rs
@@ -1,6 +1,8 @@
use crate::{
- error::SdkResult, mcp_server::hyper_runtime::HyperRuntime,
- mcp_traits::mcp_handler::McpServerHandler,
+ error::SdkResult,
+ id_generator::{FastIdGenerator, UuidGenerator},
+ mcp_server::hyper_runtime::HyperRuntime,
+ mcp_traits::{mcp_handler::McpServerHandler, IdGenerator},
};
#[cfg(feature = "ssl")]
use axum_server::tls_rustls::RustlsConfig;
@@ -17,11 +19,11 @@ use super::{
app_state::AppState,
error::{TransportServerError, TransportServerResult},
routes::app_routes,
- IdGenerator, InMemorySessionStore, UuidGenerator,
+ InMemorySessionStore,
};
use crate::schema::InitializeResult;
use axum::Router;
-use rust_mcp_transport::TransportOptions;
+use rust_mcp_transport::{SessionId, TransportOptions};
// Default client ping interval (12 seconds)
const DEFAULT_CLIENT_PING_INTERVAL: Duration = Duration::from_secs(12);
@@ -43,7 +45,7 @@ pub struct HyperServerOptions {
pub port: u16,
/// Optional thread-safe session id generator to generate unique session IDs.
- pub session_id_generator: Option>,
+ pub session_id_generator: Option>>,
/// Optional custom path for the Streamable HTTP endpoint (default: `/mcp`)
pub custom_streamable_http_endpoint: Option,
@@ -258,6 +260,7 @@ impl HyperServer {
.session_id_generator
.take()
.map_or(Arc::new(UuidGenerator {}), |g| Arc::clone(&g)),
+ stream_id_gen: Arc::new(FastIdGenerator::new(Some("s_"))),
server_details: Arc::new(server_details),
handler,
ping_interval: server_options.ping_interval,
diff --git a/crates/rust-mcp-sdk/src/hyper_servers/session_store.rs b/crates/rust-mcp-sdk/src/hyper_servers/session_store.rs
index 95b2158..4384b1a 100644
--- a/crates/rust-mcp-sdk/src/hyper_servers/session_store.rs
+++ b/crates/rust-mcp-sdk/src/hyper_servers/session_store.rs
@@ -5,7 +5,6 @@ use async_trait::async_trait;
pub use in_memory::*;
use rust_mcp_transport::SessionId;
use tokio::sync::Mutex;
-use uuid::Uuid;
use crate::mcp_server::ServerRuntime;
@@ -46,26 +45,3 @@ pub trait SessionStore: Send + Sync {
async fn has(&self, session: &SessionId) -> bool;
}
-
-/// Trait for generating session identifiers
-///
-/// Implementors must be Send and Sync to support concurrent access.
-pub trait IdGenerator: Send + Sync {
- fn generate(&self) -> SessionId;
-}
-
-/// Struct implementing the IdGenerator trait using UUID v4
-///
-/// This is a simple wrapper around the uuid crate's Uuid::new_v4 function
-/// to generate unique session identifiers.
-pub struct UuidGenerator {}
-
-impl IdGenerator for UuidGenerator {
- /// Generates a new UUID v4-based session identifier
- ///
- /// # Returns
- /// * `SessionId` - A new UUID-based session identifier as a String
- fn generate(&self) -> SessionId {
- Uuid::new_v4().to_string()
- }
-}
diff --git a/crates/rust-mcp-sdk/src/id_generator.rs b/crates/rust-mcp-sdk/src/id_generator.rs
new file mode 100644
index 0000000..54f0e72
--- /dev/null
+++ b/crates/rust-mcp-sdk/src/id_generator.rs
@@ -0,0 +1,5 @@
+mod fast_id_generator;
+mod uuid_generator;
+pub use crate::mcp_traits::IdGenerator;
+pub use fast_id_generator::*;
+pub use uuid_generator::*;
diff --git a/crates/rust-mcp-sdk/src/id_generator/fast_id_generator.rs b/crates/rust-mcp-sdk/src/id_generator/fast_id_generator.rs
new file mode 100644
index 0000000..fc2e976
--- /dev/null
+++ b/crates/rust-mcp-sdk/src/id_generator/fast_id_generator.rs
@@ -0,0 +1,53 @@
+use crate::mcp_traits::IdGenerator;
+use base64::Engine;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+/// An [`IdGenerator`] implementation optimized for lightweight, locally-scoped identifiers.
+///
+/// This generator produces short, incrementing identifiers that are Base64-encoded.
+/// This makes it well-suited for cases such as `StreamId` generation, where:
+/// - IDs only need to be unique within a single process or session
+/// - Predictability is acceptable
+/// - Shorter, more human-readable identifiers are desirable
+///
+pub struct FastIdGenerator {
+ counter: AtomicU64,
+ ///Optional prefix for readability
+ prefix: &'static str,
+}
+
+impl FastIdGenerator {
+ /// Creates a new ID generator with an optional prefix.
+ ///
+ /// # Arguments
+ /// * `prefix` - A static string to prepend to IDs (e.g., "sid_").
+ pub fn new(prefix: Option<&'static str>) -> Self {
+ FastIdGenerator {
+ counter: AtomicU64::new(0),
+ prefix: prefix.unwrap_or_default(),
+ }
+ }
+}
+
+impl IdGenerator for FastIdGenerator
+where
+ T: From,
+{
+ /// Generates a new session ID as a short Base64-encoded string.
+ ///
+ /// Increments an internal counter atomically and encodes it in Base64 URL-safe format.
+ /// The resulting ID is prefixed (if provided) and typically 8–12 characters long.
+ ///
+ /// # Returns
+ /// * `SessionId` - A short, unique session ID (e.g., "sid_BBBB" or "BBBB").
+ fn generate(&self) -> T {
+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
+ let bytes = id.to_le_bytes();
+ let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
+ if self.prefix.is_empty() {
+ T::from(encoded)
+ } else {
+ T::from(format!("{}{}", self.prefix, encoded))
+ }
+ }
+}
diff --git a/crates/rust-mcp-sdk/src/id_generator/uuid_generator.rs b/crates/rust-mcp-sdk/src/id_generator/uuid_generator.rs
new file mode 100644
index 0000000..2f0dc21
--- /dev/null
+++ b/crates/rust-mcp-sdk/src/id_generator/uuid_generator.rs
@@ -0,0 +1,18 @@
+use crate::mcp_traits::IdGenerator;
+use uuid::Uuid;
+
+/// An [`IdGenerator`] implementation that uses UUID v4 to create unique identifiers.
+///
+/// This generator produces random UUIDs (version 4), which are highly unlikely
+/// to collide and difficult to predict. It is therefore well-suited for
+/// generating identifiers such as `SessionId` or other values where uniqueness is important.
+pub struct UuidGenerator;
+
+impl IdGenerator for UuidGenerator
+where
+ T: From,
+{
+ fn generate(&self) -> T {
+ T::from(Uuid::new_v4().to_string())
+ }
+}
diff --git a/crates/rust-mcp-sdk/src/lib.rs b/crates/rust-mcp-sdk/src/lib.rs
index 1ea23df..a33f889 100644
--- a/crates/rust-mcp-sdk/src/lib.rs
+++ b/crates/rust-mcp-sdk/src/lib.rs
@@ -21,7 +21,7 @@ pub mod mcp_client {
//! responding to ping requests, so you only need to override and customize the handler
//! functions relevant to your specific needs.
//!
- //! Refer to [examples/simple-mcp-client](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client) for an example.
+ //! Refer to [examples/simple-mcp-client-stdio](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio) for an example.
//!
//!
//! - **client_runtime_core**: If you need more control over MCP messages, consider using
@@ -30,7 +30,7 @@ pub mod mcp_client {
//! While still providing type-safe objects in these methods, it allows you to determine how to
//! handle each message based on its type and parameters.
//!
- //! Refer to [examples/simple-mcp-client-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-core) for an example.
+ //! Refer to [examples/simple-mcp-client-stdio-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/simple-mcp-client-stdio-core) for an example.
pub use super::mcp_handlers::mcp_client_handler::ClientHandler;
pub use super::mcp_handlers::mcp_client_handler_core::ClientHandlerCore;
pub use super::mcp_runtimes::client_runtime::mcp_client_runtime as client_runtime;
@@ -53,7 +53,7 @@ pub mod mcp_server {
//! responding to ping requests, so you only need to override and customize the handler
//! functions relevant to your specific needs.
//!
- //! Refer to [examples/hello-world-mcp-server](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server) for an example.
+ //! Refer to [examples/hello-world-mcp-server-stdio](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio) for an example.
//!
//!
//! - **server_runtime_core**: If you need more control over MCP messages, consider using
@@ -62,7 +62,7 @@ pub mod mcp_server {
//! While still providing type-safe objects in these methods, it allows you to determine how to
//! handle each message based on its type and parameters.
//!
- //! Refer to [examples/hello-world-mcp-server-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-core) for an example.
+ //! Refer to [examples/hello-world-mcp-server-stdio-core](https://github.com/rust-mcp-stack/rust-mcp-sdk/tree/main/examples/hello-world-mcp-server-stdio-core) for an example.
pub use super::mcp_handlers::mcp_server_handler::ServerHandler;
pub use super::mcp_handlers::mcp_server_handler_core::ServerHandlerCore;
@@ -93,4 +93,5 @@ pub mod macros {
pub use rust_mcp_macros::*;
}
+pub mod id_generator;
pub mod schema;
diff --git a/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime.rs b/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime.rs
index 7ee0815..2093dc3 100644
--- a/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime.rs
+++ b/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime.rs
@@ -1,12 +1,17 @@
pub mod mcp_client_runtime;
pub mod mcp_client_runtime_core;
-
+use crate::error::{McpSdkError, SdkResult};
+use crate::id_generator::FastIdGenerator;
+use crate::mcp_traits::mcp_client::McpClient;
+use crate::mcp_traits::mcp_handler::McpClientHandler;
+use crate::mcp_traits::IdGenerator;
+use crate::utils::ensure_server_protocole_compatibility;
use crate::{
mcp_traits::{RequestIdGen, RequestIdGenNumeric},
schema::{
schema_utils::{
- self, ClientMessage, ClientMessages, FromMessage, MessageFromClient, ServerMessage,
- ServerMessages,
+ self, ClientMessage, ClientMessages, FromMessage, McpMessage, MessageFromClient,
+ ServerMessage, ServerMessages,
},
InitializeRequest, InitializeRequestParams, InitializeResult, InitializedNotification,
RequestId, RpcError, ServerResult,
@@ -16,63 +21,100 @@ use async_trait::async_trait;
use futures::future::{join_all, try_join_all};
use futures::StreamExt;
-use rust_mcp_transport::{IoStream, McpDispatch, MessageDispatcher, Transport};
-use std::{
- sync::{Arc, RwLock},
- time::Duration,
-};
+#[cfg(feature = "streamable-http")]
+use rust_mcp_transport::{ClientStreamableTransport, StreamableTransportOptions};
+use rust_mcp_transport::{IoStream, SessionId, StreamId, Transport, TransportDispatcher};
+use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::io::{AsyncBufReadExt, BufReader};
-use tokio::sync::Mutex;
+use tokio::sync::{watch, Mutex};
-use crate::error::{McpSdkError, SdkResult};
-use crate::mcp_traits::mcp_client::McpClient;
-use crate::mcp_traits::mcp_handler::McpClientHandler;
-use crate::utils::ensure_server_protocole_compatibility;
+pub const DEFAULT_STREAM_ID: &str = "STANDALONE-STREAM";
+
+// Define a type alias for the TransportDispatcher trait object
+type TransportDispatcherType = dyn TransportDispatcher<
+ ServerMessages,
+ MessageFromClient,
+ ServerMessage,
+ ClientMessages,
+ ClientMessage,
+>;
+type TransportType = Arc;
pub struct ClientRuntime {
- // The transport interface for handling messages between client and server
- transport: Arc<
- dyn Transport<
- ServerMessages,
- MessageFromClient,
- ServerMessage,
- ClientMessages,
- ClientMessage,
- >,
- >,
+ // A thread-safe map storing transport types
+ transport_map: tokio::sync::RwLock>,
// The handler for processing MCP messages
handler: Box,
- // // Information about the server
+ // Information about the server
client_details: InitializeRequestParams,
- // Details about the connected server
- server_details: Arc>>,
handlers: Mutex>>>,
+ // Generator for unique request IDs
request_id_gen: Box,
+ // Generator for stream IDs
+ stream_id_gen: FastIdGenerator,
+ #[cfg(feature = "streamable-http")]
+ // Optional configuration for streamable transport
+ transport_options: Option,
+ // Flag indicating whether the client has been shut down
+ is_shut_down: Mutex,
+ // Session ID
+ session_id: tokio::sync::RwLock