refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

refactor: provide http server as tower service - #228

Merged
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server
May 28, 2025
Merged

refactor: provide http server as tower service#228
4t145 merged 4 commits into
modelcontextprotocol:mainfrom
4t145:stateless-streamable-http-server

Conversation

@4t145

@4t1454t145 commented May 26, 2025

Copy link
Copy Markdown
Contributor
  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.

@4t1454t145 mentioned this pull request May 26, 2025
9 tasks
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 0c2a48a to 3ad5596CompareMay 26, 2025 18:07
@4t145
4t145 marked this pull request as ready for review May 27, 2025 04:12
@4t145
4t145force-pushed the stateless-streamable-http-server branch from 3ca60cd to 59ba72aCompareMay 27, 2025 06:06
@4t145
4t145 requested review from Copilot and jokemanfireMay 27, 2025 06:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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
FileDescription
examples/servers/src/counter_streamhttp.rsUpdated to use StreamableHttpService with axum and manual serve
examples/servers/src/counter_hyper_streamable_http.rsAdded hyper example using TowerToHyperService wrapper
examples/servers/README.mdDocumented axum and hyper streamable HTTP examples
examples/servers/Cargo.tomlReformatted dependency lists and added hyper example deps
crates/rmcp/tests/test_with_js/streamable_client.jsUpdated client URL path to include /mcp/
crates/rmcp/tests/test_with_js.rsSwitched tests to use StreamableHttpService and axum server
crates/rmcp/src/transport/streamable_http_server/session/never.rsAdded NeverSessionManager stub for unsupported session
crates/rmcp/src/transport/streamable_http_server/axum.rsRemoved entire axum-based server implementation
crates/rmcp/src/transport/streamable_http_server.rsRe-exported tower-based service and session types
crates/rmcp/src/transport/sse_server.rsFixed import path and removed extra .await in serve_directly
crates/rmcp/src/transport/sink_stream.rsAdded Sync bound on transport error types
crates/rmcp/src/transport/common/sever_side_http.rsIntroduced common server-side HTTP helpers (new module)
crates/rmcp/src/transport/common/axum.rsRemoved axum-specific helpers
crates/rmcp/src/transport/common.rsSwitched from axum to sever_side_http module
crates/rmcp/src/transport.rsGeneralized Transport trait bounds and re-exported new service
crates/rmcp/src/service/server.rsRemoved unnecessary From<std::io::Error> bound and sync-ified
crates/rmcp/src/service/client.rsSync-ified serve_inner calls
crates/rmcp/src/service.rsRemoved async on serve_directly(_with_ct) and serve_inner
crates/rmcp/Cargo.tomlBumped 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};

Comment threadcrates/rmcp/src/transport/common.rs Outdated
Comment threadcrates/rmcp/src/transport/streamable_http_server/session/never.rs Outdated
@4t1454t145 changed the title draft: provide http server as tower servicerefactor: provide http server as tower serviceMay 27, 2025
@ahmedhesham6

Copy link
Copy Markdown

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:

pubfnexpect_accepted<E>(self) -> Result<(),StreamableHttpError<E>>whereE: std::error::Error + Send + Sync + 'static,{matchself{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

4t145 commented May 27, 2025

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
ContributorAuthor

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

@4t145
4t145 merged commit 209be7b into modelcontextprotocol:mainMay 28, 2025
@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
)
* refactor: streamable http server as tower service
Sign up for freeto 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.

4 participants

@4t145@ahmedhesham6@jokemanfire