Skip to content

Commit 1d5babc

Browse files
lucarligclaude
andcommitted
refactor(fast-time): use rmcp SDK for Streamable HTTP transport and tools
Replace the hand-rolled MCP layer with the official Rust SDK (rmcp): - /mcp Streamable HTTP transport is now served by rmcp's StreamableHttpService + LocalSessionManager, removing the bespoke session map, idle eviction, and JSON-RPC dispatch (streamable_http.rs). - Tools are declared once via #[tool] macros with schemas derived from their parameter types, removing the hand-written tool schemas and dispatch (mcp.rs). - The legacy HTTP+SSE transport remains a hand-rolled shim (documented exception) because rmcp does not reproduce the bespoke ContextForge legacy-SSE semantics (endpoint event, sessionId/session_id aliases, 202/404/410 codes). It delegates tools/list and tools/call back to the rmcp server so schemas and logic never drift. - serverInfo is built explicitly so /mcp reports the server identity rather than rmcp's own crate name. Behavior parity (DST conversion, echo delay limit, protocol-version echo) is covered by unit tests; both transports verified end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
1 parent f56eec4 commit 1d5babc

10 files changed

Lines changed: 520 additions & 885 deletions

File tree

Cargo.lock

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

mcp-servers/rust/fast-time-server/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ tokio-stream = "0.1.17"
1616
# Web framework
1717
axum.workspace = true
1818

19+
# MCP SDK (official Rust SDK) — drives the /mcp Streamable HTTP transport and
20+
# tool schemas. The legacy SSE transport is a hand-rolled shim (see transports/sse.rs).
21+
rmcp = { workspace = true, features = ["server", "macros", "transport-streamable-http-server"] }
22+
1923
# Serialization
2024
serde.workspace = true
2125
serde_json.workspace = true

mcp-servers/rust/fast-time-server/src/app.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
use axum::Router;
55
use axum::serve::ListenerExt;
6+
use rmcp::transport::streamable_http_server::StreamableHttpService;
7+
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
68
use serde_json::json;
79
use std::env;
810
use tracing::info;
@@ -11,7 +13,8 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
1113

1214
use crate::config::{APP_NAME, APP_VERSION, DEFAULT_BIND_ADDRESS, MCP_PROTOCOL_VERSION};
1315
use crate::rest;
14-
use crate::transports::{sse, streamable_http};
16+
use crate::server::FastTimeServer;
17+
use crate::transports::sse;
1518

1619
pub async fn run() -> anyhow::Result<()> {
1720
init_logging();
@@ -67,18 +70,23 @@ fn init_logging() {
6770
}
6871

6972
fn router() -> Router {
73+
// Modern Streamable HTTP transport, served entirely by the rmcp SDK.
74+
let mcp = StreamableHttpService::new(
75+
|| Ok(FastTimeServer::new()),
76+
LocalSessionManager::default().into(),
77+
Default::default(),
78+
);
79+
7080
Router::new()
7181
.route("/health", axum::routing::get(health_handler))
7282
.route("/version", axum::routing::get(version_handler))
7383
.route("/api/echo", axum::routing::post(rest::echo_handler))
7484
.route("/api/time", axum::routing::get(rest::time_handler))
75-
.route(
76-
"/mcp",
77-
axum::routing::post(streamable_http::handler).delete(streamable_http::delete_handler),
78-
)
85+
// Legacy HTTP+SSE transport — hand-rolled shim (see transports/sse.rs).
7986
.route("/sse", axum::routing::get(sse::handler))
8087
.route("/messages", axum::routing::post(sse::message_handler))
8188
.route("/message", axum::routing::post(sse::message_handler))
89+
.nest_service("/mcp", mcp)
8290
}
8391

8492
async fn health_handler() -> axum::Json<serde_json::Value> {
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
// Copyright 2026
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::time::Duration;
5-
64
pub(crate) const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0:9080";
75
pub(crate) const APP_NAME: &str = "fast-time-server";
86
pub(crate) const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
7+
/// Latest protocol version advertised by the legacy SSE shim and `/version`.
8+
/// The `/mcp` Streamable HTTP transport negotiates independently inside rmcp.
99
pub(crate) const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
10-
/// Protocol versions echoed back during initialize negotiation; anything else
11-
/// falls back to `MCP_PROTOCOL_VERSION`.
10+
/// Protocol versions echoed back during legacy SSE initialize negotiation;
11+
/// anything else falls back to `MCP_PROTOCOL_VERSION`.
1212
pub(crate) const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
1313
"2024-11-05",
1414
"2025-03-26",
1515
"2025-06-18",
1616
MCP_PROTOCOL_VERSION,
1717
];
18-
pub(crate) const SESSION_HEADER: &str = "mcp-session-id";
18+
/// Cap on concurrent legacy SSE sessions.
1919
pub(crate) const MAX_ACTIVE_SESSIONS: usize = 10_000;
20-
/// Idle lifetime for streamable-HTTP sessions before they are evicted.
21-
pub(crate) const SESSION_IDLE_TTL: Duration = Duration::from_secs(300);
2220
pub(crate) const SSE_CHANNEL_CAPACITY: usize = 64;
2321
pub(crate) const MAX_DELAY_MS: u64 = 60_000;

mcp-servers/rust/fast-time-server/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
mod app;
77
mod config;
88
mod delay;
9-
mod mcp;
109
mod rest;
10+
mod server;
1111
mod time;
1212
mod transports;
1313

0 commit comments

Comments
 (0)