upgrade rmcp crate (#7518)

This commit is contained in:
centdix
2026-01-07 17:02:51 +01:00
committed by GitHub
parent 6f53a2ab70
commit 65989430d3
10 changed files with 73 additions and 55 deletions

54
backend/Cargo.lock generated
View File

@@ -2656,6 +2656,16 @@ dependencies = [
"darling_macro 0.21.3",
]
[[package]]
name = "darling"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core 0.23.0",
"darling_macro 0.23.0",
]
[[package]]
name = "darling_core"
version = "0.13.4"
@@ -2712,6 +2722,19 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "darling_core"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn 2.0.111",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
@@ -2756,6 +2779,17 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "darling_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core 0.23.0",
"quote",
"syn 2.0.111",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@@ -9504,6 +9538,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
[[package]]
name = "path-clean"
version = "0.1.0"
@@ -10902,10 +10942,11 @@ dependencies = [
[[package]]
name = "rmcp"
version = "0.8.5"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5947688160b56fb6c827e3c20a72c90392a1d7e9dec74749197aa1780ac42ca"
checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa"
dependencies = [
"async-trait",
"base64 0.22.1",
"bytes",
"chrono",
@@ -10913,7 +10954,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"paste",
"pastey",
"pin-project-lite",
"rand 0.9.0",
"reqwest 0.12.24",
@@ -10933,11 +10974,11 @@ dependencies = [
[[package]]
name = "rmcp-macros"
version = "0.8.5"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01263441d3f8635c628e33856c468b96ebbce1af2d3699ea712ca71432d4ee7a"
checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59"
dependencies = [
"darling 0.21.3",
"darling 0.23.0",
"proc-macro2",
"quote",
"serde_json",
@@ -15867,7 +15908,6 @@ dependencies = [
"regex",
"reqwest 0.12.24",
"reqwest-middleware",
"rmcp",
"rust_decimal",
"serde",
"serde_json",

View File

@@ -40,7 +40,7 @@ mcp = ["dep:rmcp"]
python = []
[dependencies]
rmcp = { version = "0.8.1", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
rmcp = { version = "0.12.0", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
windmill-queue.workspace = true
windmill-common = { workspace = true, default-features = false }
windmill-audit.workspace = true

View File

@@ -19,10 +19,8 @@ use crate::oauth2_oss::SlackVerifier;
use crate::smtp_server_oss::SmtpServer;
#[cfg(feature = "mcp")]
use crate::mcp::{extract_and_store_workspace_id, setup_mcp_server, shutdown_mcp_server};
use crate::mcp::{extract_and_store_workspace_id, setup_mcp_server};
use crate::triggers::start_all_listeners;
#[cfg(feature = "mcp")]
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
use tower_http::catch_panic::CatchPanicLayer;
use crate::tracing_init::MyOnFailure;
@@ -400,14 +398,17 @@ pub async fn run_server(
// Setup MCP server
#[allow(unused_variables)]
let (mcp_router, mcp_session_manager) = {
let (mcp_router, mcp_cancellation_token) = {
#[cfg(feature = "mcp")]
if server_mode || mcp_mode {
let (mcp_router, mcp_session_manager) = setup_mcp_server().await?;
let (mcp_router, mcp_cancellation_token) = setup_mcp_server().await?;
let mcp_middleware = axum::middleware::from_fn(extract_and_store_workspace_id);
(mcp_router.layer(mcp_middleware), Some(mcp_session_manager))
(
mcp_router.layer(mcp_middleware),
Some(mcp_cancellation_token),
)
} else {
(Router::new(), Option::<Arc<LocalSessionManager>>::None)
(Router::new(), None)
}
#[cfg(not(feature = "mcp"))]
@@ -757,8 +758,8 @@ pub async fn run_server(
tracing::info!("Graceful shutdown of server");
#[cfg(feature = "mcp")]
if let Some(mcp_session_manager) = mcp_session_manager {
shutdown_mcp_server(mcp_session_manager).await;
if let Some(mcp_cancellation_token) = mcp_cancellation_token {
mcp_cancellation_token.cancel();
tracing::info!("MCP server shutdown");
}
});

View File

@@ -8,4 +8,4 @@ pub mod tools;
pub mod utils;
// Re-export main components
pub use server::{extract_and_store_workspace_id, setup_mcp_server, shutdown_mcp_server, list_tools_service};
pub use server::{extract_and_store_workspace_id, list_tools_service, setup_mcp_server};

View File

@@ -18,6 +18,7 @@ use rmcp::{
};
use serde_json::Value;
use tokio::try_join;
use tokio_util::sync::CancellationToken;
use windmill_common::db::UserDB;
use windmill_common::worker::to_raw_value;
use windmill_common::{utils::StripPath, DB};
@@ -47,7 +48,7 @@ use axum::{
extract::Path, http::Request, middleware::Next, response::Response, routing::get, Json, Router,
};
use rmcp::transport::streamable_http_server::{
session::local::LocalSessionManager, SessionManager, StreamableHttpService,
session::local::LocalSessionManager, StreamableHttpService,
};
use windmill_common::error::JsonResult;
@@ -126,6 +127,7 @@ impl Runner {
idempotent_hint: Some(false), // Are not guaranteed to be idempotent
open_world_hint: Some(true), // Can interact with external services
}),
meta: None,
})
}
}
@@ -471,7 +473,7 @@ impl ServerHandler for Runner {
);
}
Ok(ListToolsResult { tools, next_cursor: None })
Ok(ListToolsResult { tools, next_cursor: None, meta: None })
}
fn get_info(&self) -> ServerInfo {
@@ -498,7 +500,7 @@ impl ServerHandler for Runner {
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourcesResult, ErrorData> {
Ok(ListResourcesResult { resources: vec![], next_cursor: None })
Ok(ListResourcesResult { resources: vec![], next_cursor: None, meta: None })
}
async fn list_prompts(
@@ -530,11 +532,13 @@ pub async fn extract_and_store_workspace_id(
}
/// Setup the MCP server with HTTP transport
pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc<LocalSessionManager>)> {
pub async fn setup_mcp_server() -> anyhow::Result<(Router, CancellationToken)> {
let cancellation_token = CancellationToken::new();
let session_manager = Arc::new(LocalSessionManager::default());
let service_config = StreamableHttpServerConfig {
sse_keep_alive: Some(Duration::from_secs(15)),
stateful_mode: false,
cancellation_token: cancellation_token.clone(),
};
let service = StreamableHttpService::new(
|| Ok(Runner::new()),
@@ -543,34 +547,7 @@ pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc<LocalSessionManag
);
let router = axum::Router::new().nest_service("/", service);
Ok((router, session_manager))
}
/// Shutdown the MCP server gracefully by closing all active sessions
pub async fn shutdown_mcp_server(session_manager: Arc<LocalSessionManager>) {
let session_ids_to_close = {
let sessions_map = session_manager.sessions.read().await;
sessions_map.keys().cloned().collect::<Vec<_>>()
};
if !session_ids_to_close.is_empty() {
tracing::info!(
"Closing {} active MCP session(s)...",
session_ids_to_close.len()
);
let close_futures = session_ids_to_close
.iter()
.map(|session_id| {
let manager_clone = session_manager.clone();
async move {
if let Err(_) = manager_clone.close_session(session_id).await {
tracing::warn!("Error closing MCP session");
}
}
})
.collect::<Vec<_>>();
futures::future::join_all(close_futures).await;
}
Ok((router, cancellation_token))
}
/// HTTP handler to list MCP tools as JSON

View File

@@ -61,6 +61,7 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
output_schema: None,
icons: None,
annotations: Some(annotations),
meta: None,
}
}

View File

@@ -108,7 +108,7 @@ tempfile.workspace = true
systemstat.workspace = true
size.workspace = true
globset.workspace = true
rmcp = { version = "0.8.1", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
rmcp = { version = "0.12.0", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
opentelemetry-semantic-conventions = { workspace = true, optional = true }
opentelemetry-otlp = { workspace = true, optional = true }

View File

@@ -5,7 +5,7 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use serde_json::{json, Value};
use std::str::FromStr;
use rmcp::model::Tool as McpTool;
pub use rmcp::model::Tool as McpTool;
use rmcp::{
model::{
CallToolRequestParam, ClientCapabilities, ClientInfo, Implementation,

View File

@@ -57,7 +57,6 @@ windmill-parser-sql.workspace = true
windmill-parser-graphql.workspace = true
windmill-parser-php = { workspace = true, optional = true }
windmill-git-sync.workspace = true
rmcp = { version = "0.8.1", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
aws-sdk-bedrockruntime.workspace = true
aws-config.workspace = true
aws-credential-types.workspace = true

View File

@@ -20,7 +20,7 @@ use windmill_common::{
};
use windmill_common::{
flows::FlowModuleValue,
mcp_client::{McpClient, McpResource, McpToolSource},
mcp_client::{McpClient, McpResource, McpTool, McpToolSource},
};
use windmill_queue::{flow_status::get_step_of_flow_status, MiniPulledJob};
@@ -352,7 +352,7 @@ pub async fn cleanup_mcp_clients(mcp_clients: HashMap<String, Arc<McpClient>>) {
/// Convert raw MCP tools to Windmill Tool format with source tracking
fn convert_mcp_tools_to_windmill_tools(
mcp_tools: &[rmcp::model::Tool],
mcp_tools: &[McpTool],
resource_name: &str,
resource_path: &str,
) -> Result<Vec<Tool>, Error> {