Skip to content

Conversation

@4t145
Copy link
Collaborator

@4t145 4t145 commented May 26, 2025

  1. This allows users to use http service in different web framework as long as it support tower service, like axum, hyper, poem... Feature: Provide streamable http server and sse server as a tower Service #178
let service: StreamableHttpService<Calculator, LocalSessionManager> =
        StreamableHttpService::new(
            Calculator::default,
            Default::default(),
            StreamableHttpServerConfig {
                stateful_mode: true,
                sse_keep_alive: None,
            },
        );
let router = axum::Router::new().nest_service("/mcp", service);
  1. remove useless restriction.
  2. make serve_inner a sync function.
  3. support stateless streamable http server Implement Stateless Streamable HTTP Mode to avoid sticky session issue #212

Motivation and Context

How Has This Been Tested?

Breaking Changes

Remove old axum streamable http server.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

I am not going to refactor the sse server, I preffer to remain the exsisted interface.

@4t145 4t145 mentioned this pull request May 26, 2025
9 tasks
@4t145 4t145 force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596 Compare May 26, 2025 18:07
@4t145 4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145 4t145 force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72a Compare May 27, 2025 06:06
@4t145 4t145 requested review from Copilot and jokemanfire May 27, 2025 06:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the streamable HTTP server implementation to a Tower-compatible StreamableHttpService, replaces the axum-specific server, and updates examples, docs, and tests to use the new service. It also generalizes error bounds, makes serve_inner synchronous, and supports stateless mode.

  • Export StreamableHttpService from a new Tower module and remove the old axum server implementation.
  • Update examples (axum and hyper), README, and tests to nest and serve the new service under /mcp.
  • Widen transport error bounds (Send + Sync), remove unnecessary await on serve_inner, and reorganize common HTTP helpers.

Reviewed Changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
examples/servers/src/counter_streamhttp.rs Updated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rs Added hyper example using TowerToHyperService wrapper
examples/servers/README.md Documented axum and hyper streamable HTTP examples
examples/servers/Cargo.toml Reformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.js Updated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rs Switched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rs Added NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rs Removed entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rs Re-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rs Fixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rs Added Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rs Introduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rs Removed axum-specific helpers
crates/rmcp/src/transport/common.rs Switched from axum to sever_side_http module
crates/rmcp/src/transport.rs Generalized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rs Removed unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rs Sync-ified serve_inner calls
crates/rmcp/src/service.rs Removed async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.toml Bumped sse-stream version and added server-side-deps profile
Comments suppressed due to low confidence (1)

crates/rmcp/src/transport/common/sever_side_http.rs:1

  • The file sever_side_http.rs seems intended as server_side_http.rs. Renaming it will align with its purpose and avoid confusion.
use std::{convert::Infallible, fmt::Display, sync::Arc, time::Duration};

@4t145 4t145 changed the title draft: provide http server as tower service refactor: provide http server as tower service May 27, 2025
@ahmedhesham6
Copy link

Problem
When sending the “initialized” notification, the client currently only treats an Accepted response as success. In practice, the server may reply with an SSE envelope (e.g. Sse(None)), which gets treated as unexpected and causes the worker to quit:

ERROR ... worker quit with fatal: unexpected server response: expect accepted, got Sse(None), when process initialized notification response
ERROR ... client error: TransportError { error: TransportChannelClosed, context: "send initialized notification" }

Solution
Broaden expect_accepted so that it also accepts any Sse(..) variants. That way, harmless SSE frames won’t trigger a fatal error:

pub fn expect_accepted<E>(self) -> Result<(), StreamableHttpError<E>>
where
    E: std::error::Error + Send + Sync + 'static,
{
    match self {
        Self::Accepted    // normal ACK
        | Self::Sse(..)  // treat SSE frames as OK
            => Ok(()),
        got => Err(StreamableHttpError::UnexpectedServerResponse(
            format!("expect accepted, got {got:?}").into(),
        )),
    }
}

With this change, the client will continue normally upon receiving SSE messages instead of shutting down the worker.

@4t145
Copy link
Collaborator Author

4t145 commented May 27, 2025

@ahmedhesham6 That is a bug of server side implementation, I will fix this. Server should respond Accepted after received result or notification. Thank you for find out this.

@4t145
Copy link
Collaborator Author

4t145 commented May 28, 2025

@jokemanfire Could you please review this pr if you have spare time? Thanks a lot.

@4t145 4t145 merged commit 209be7b into modelcontextprotocol:main May 28, 2025
10 checks passed
@github-actions github-actions bot mentioned this pull request Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants