diff --git a/CHANGELOG.md b/CHANGELOG.md index 893431cf87..0386e08fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.546.1](https://github.com/windmill-labs/windmill/compare/v1.546.0...v1.546.1) (2025-09-23) + + +### Bug Fixes + +* **mcp:** use stateless mode for openai sdk compatibility ([#6656](https://github.com/windmill-labs/windmill/issues/6656)) ([389b692](https://github.com/windmill-labs/windmill/commit/389b692523507a28916e96b481c60f3c49cd31da)) + ## [1.546.0](https://github.com/windmill-labs/windmill/compare/v1.545.0...v1.546.0) (2025-09-23) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index af91b00806..4419859554 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -10856,9 +10856,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.2.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37f2048a81a7ff7e8ef6bc5abced70c3d9114c8f03d85d7aaaafd9fd04f12e9e" +checksum = "41ab0892f4938752b34ae47cb53910b1b0921e55e77ddb6e44df666cab17939f" dependencies = [ "base64 0.22.1", "bytes", @@ -10871,7 +10871,7 @@ dependencies = [ "pin-project-lite", "rand 0.9.0", "rmcp-macros", - "schemars 0.8.22", + "schemars 1.0.4", "serde", "serde_json", "sse-stream", @@ -10886,11 +10886,11 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.2.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72398e694b9f6dbb5de960cf158c8699e6a1854cb5bbaac7de0646b2005763c4" +checksum = "1827cd98dab34cade0513243c6fe0351f0f0b2c9d6825460bcf45b42804bdda0" dependencies = [ - "darling 0.20.11", + "darling 0.21.3", "proc-macro2", "quote", "serde_json", @@ -11431,9 +11431,8 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ - "chrono", "dyn-clone", - "schemars_derive", + "schemars_derive 0.8.22", "serde", "serde_json", ] @@ -11456,8 +11455,10 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ + "chrono", "dyn-clone", "ref-cast", + "schemars_derive 1.0.4", "serde", "serde_json", ] @@ -11474,6 +11475,18 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "schemars_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.106", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -13291,9 +13304,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -15122,7 +15135,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15182,7 +15195,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "argon2", @@ -15302,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.546.0" +version = "1.546.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15317,7 +15330,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.546.0" +version = "1.546.1" dependencies = [ "chrono", "serde", @@ -15330,7 +15343,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "axum", @@ -15349,7 +15362,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "async-recursion", @@ -15433,7 +15446,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.546.0" +version = "1.546.1" dependencies = [ "regex", "serde", @@ -15448,7 +15461,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "bytes", @@ -15472,7 +15485,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.546.0" +version = "1.546.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15484,7 +15497,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.546.0" +version = "1.546.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15493,7 +15506,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "lazy_static", @@ -15505,7 +15518,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "serde_json", @@ -15517,7 +15530,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "gosyn", @@ -15529,7 +15542,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "lazy_static", @@ -15541,7 +15554,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "serde_json", @@ -15553,7 +15566,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "nu-parser", @@ -15564,7 +15577,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15575,7 +15588,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15587,7 +15600,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "async-recursion", @@ -15610,7 +15623,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "lazy_static", @@ -15624,7 +15637,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15641,7 +15654,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "lazy_static", @@ -15655,7 +15668,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "lazy_static", @@ -15673,7 +15686,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15698,7 +15711,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "serde_json", @@ -15708,7 +15721,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "async-recursion", @@ -15741,7 +15754,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.546.0" +version = "1.546.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15751,7 +15764,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.546.0" +version = "1.546.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2be65f2153..4e8811a52c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.546.0" +version = "1.546.1" authors.workspace = true edition.workspace = true @@ -34,7 +34,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.546.0" +version = "1.546.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 60d79e3b31..76d05a7188 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -40,7 +40,7 @@ mcp = ["dep:rmcp"] python = [] [dependencies] -rmcp = { version = "0.2.1", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true } +rmcp = { version = "0.6.4", 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 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 430a860ad1..3c2d957947 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.546.0 + version: 1.546.1 title: Windmill API contact: diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs index f24026c580..9cf3c9af2b 100644 --- a/backend/windmill-api/src/folders.rs +++ b/backend/windmill-api/src/folders.rs @@ -361,6 +361,11 @@ async fn update_folder( ); } if let Some(extra_perms) = ng.extra_perms { + if !extra_perms.is_object() { + return Err(windmill_common::error::Error::BadRequest(format!( + "extra_perms must be an object, received {}", extra_perms.to_string() + ))); + } sqlb.set( "extra_perms", "?".bind(&serde_json::to_string(&extra_perms).map_err(to_anyhow)?), diff --git a/backend/windmill-api/src/mcp/server.rs b/backend/windmill-api/src/mcp/server.rs index e51d008e3c..fa8644b5b8 100644 --- a/backend/windmill-api/src/mcp/server.rs +++ b/backend/windmill-api/src/mcp/server.rs @@ -4,16 +4,17 @@ //! specification. This is a thin orchestration layer that delegates to the appropriate //! modules for tool management, database operations, and schema transformation. -use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; +use std::{borrow::Cow, time::Duration}; use axum::body::to_bytes; use rmcp::{ handler::server::ServerHandler, model::*, service::{RequestContext, RoleServer}, - Error, + transport::StreamableHttpServerConfig, + ErrorData, }; use serde_json::Value; use tokio::try_join; @@ -26,35 +27,29 @@ use crate::jobs::{ run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, }; +use super::tools::endpoint_tools::{ + all_endpoint_tools, call_endpoint_tool, endpoint_tools_to_mcp_tools, EndpointTool, +}; use super::utils::{ database::{ - check_scopes, get_items, get_resources_types, get_scripts_from_hub, get_item_schema, get_hub_script_schema + check_scopes, get_hub_script_schema, get_item_schema, get_items, get_resources_types, + get_scripts_from_hub, + }, + models::{ + FlowInfo, ResourceInfo, ResourceType, SchemaType, ScriptInfo, ToolableItem, WorkspaceId, }, - models::{ScriptInfo, FlowInfo, ResourceInfo, ResourceType, SchemaType, ToolableItem, WorkspaceId}, schema::transform_schema_for_resources, transform::{reverse_transform, reverse_transform_key}, }; -use super::tools::{ - endpoint_tools::{all_endpoint_tools, endpoint_tools_to_mcp_tools, call_endpoint_tool, EndpointTool}, -}; use axum::{ - extract::Path, - http::Request, - middleware::Next, - response::Response, - routing::get, - Json, - Router, + 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, SessionManager, StreamableHttpService, }; use windmill_common::error::JsonResult; - /// MCP Server Runner - implements the core MCP protocol handlers #[derive(Clone)] pub struct Runner {} @@ -72,7 +67,7 @@ impl Runner { workspace_id: &str, resources_cache: &mut HashMap>, resources_types: &Vec, - ) -> Result { + ) -> Result { let is_hub = item.is_hub(); let path = item.get_path_or_id(); let item_type = item.item_type(); @@ -120,52 +115,57 @@ impl Runner { name: Cow::Owned(path), description: Some(Cow::Owned(description)), input_schema: Arc::new(input_schema_map), + title: Some(item.get_summary().to_string()), + output_schema: None, + icons: None, annotations: Some(ToolAnnotations { title: Some(item.get_summary().to_string()), - read_only_hint: Some(false), // Can modify environment + read_only_hint: Some(false), // Can modify environment destructive_hint: Some(true), // Can potentially be destructive idempotent_hint: Some(false), // Are not guaranteed to be idempotent - open_world_hint: Some(true), // Can interact with external services + open_world_hint: Some(true), // Can interact with external services }), }) } } - impl ServerHandler for Runner { /// Handles the `CallTool` request from the MCP client async fn call_tool( &self, request: CallToolRequestParam, context: RequestContext, - ) -> Result { + ) -> Result { let http_parts = context .extensions .get::() .ok_or_else(|| { tracing::error!("http::request::Parts not found"); - Error::internal_error("http::request::Parts not found", None) + ErrorData::internal_error("http::request::Parts not found", None) })?; let authed = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("ApiAuthed Axum extension not found"); - Error::internal_error("ApiAuthed Axum extension not found", None) + ErrorData::internal_error("ApiAuthed Axum extension not found", None) })?; check_scopes(authed)?; let db = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("DB Axum extension not found"); - Error::internal_error("DB Axum extension not found", None) + ErrorData::internal_error("DB Axum extension not found", None) })?; let user_db = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("UserDB Axum extension not found"); - Error::internal_error("UserDB Axum extension not found", None) + ErrorData::internal_error("UserDB Axum extension not found", None) })?; - + let args = request.arguments.map(Value::Object).ok_or_else(|| { - Error::invalid_params("Missing arguments for tool", Some(request.name.clone().into())) + ErrorData::invalid_params( + "Missing arguments for tool", + Some(request.name.clone().into()), + ) })?; let workspace_id = http_parts @@ -173,7 +173,7 @@ impl ServerHandler for Runner { .get::() .ok_or_else(|| { tracing::error!("WorkspaceId not found"); - Error::internal_error("WorkspaceId not found", None) + ErrorData::internal_error("WorkspaceId not found", None) }) .map(|w_id| w_id.0.clone())?; @@ -182,18 +182,20 @@ impl ServerHandler for Runner { for endpoint_tool in endpoint_tools { if endpoint_tool.name.as_ref() == request.name { // This is an endpoint tool, forward to the actual HTTP endpoint - let result = call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed).await?; + let result = + call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed) + .await?; return Ok(CallToolResult::success(vec![Content::text( - serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string()) + serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string()), )])); } } // Continue with script/flow logic let (tool_type, path, is_hub) = reverse_transform(&request.name).map_err(|e| { - Error::internal_error(format!("Failed to reverse transform path: {}", e), None) + ErrorData::internal_error(format!("Failed to reverse transform path: {}", e), None) })?; - + let item_schema = if is_hub { get_hub_script_schema(&format!("hub/{}", path), db).await? } else { @@ -259,14 +261,20 @@ impl ServerHandler for Runner { let body_bytes = to_bytes(response.into_body(), usize::MAX) .await .map_err(|e| { - Error::internal_error(format!("Failed to read response body: {}", e), None) + ErrorData::internal_error( + format!("Failed to read response body: {}", e), + None, + ) })?; let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { - Error::internal_error(format!("Failed to decode response body: {}", e), None) + ErrorData::internal_error( + format!("Failed to decode response body: {}", e), + None, + ) })?; Ok(CallToolResult::success(vec![Content::text(body_str)])) } - Err(e) => Err(Error::internal_error( + Err(e) => Err(ErrorData::internal_error( format!("Failed to run script: {}", e), None, )), @@ -278,30 +286,30 @@ impl ServerHandler for Runner { &self, _request: Option, mut _context: RequestContext, - ) -> Result { + ) -> Result { let http_parts = _context .extensions .get::() .ok_or_else(|| { tracing::error!("http::request::Parts not found"); - Error::internal_error("http::request::Parts not found", None) + ErrorData::internal_error("http::request::Parts not found", None) })?; let authed = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("ApiAuthed Axum extension not found"); - Error::internal_error("ApiAuthed Axum extension not found", None) + ErrorData::internal_error("ApiAuthed Axum extension not found", None) })?; check_scopes(authed)?; let db = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("DB Axum extension not found"); - Error::internal_error("DB Axum extension not found", None) + ErrorData::internal_error("DB Axum extension not found", None) })?; let user_db = http_parts.extensions.get::().ok_or_else(|| { tracing::error!("UserDB Axum extension not found"); - Error::internal_error("UserDB Axum extension not found", None) + ErrorData::internal_error("UserDB Axum extension not found", None) })?; let workspace_id = http_parts @@ -309,7 +317,7 @@ impl ServerHandler for Runner { .get::() .ok_or_else(|| { tracing::error!("WorkspaceId not found"); - Error::internal_error("WorkspaceId not found", None) + ErrorData::internal_error("WorkspaceId not found", None) }) .map(|w_id| w_id.0.clone())?; @@ -319,10 +327,18 @@ impl ServerHandler for Runner { .iter() .find(|scope| scope.starts_with("mcp:") && !scope.contains("hub")) }); - let hub_scope = scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub"))); + let hub_scope = + scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub"))); let (scope_type, scope_path) = owned_scope.map_or(("all", None), |scope| { let parts = scope.split(":").collect::>(); - (parts[1], if parts.len() == 3 { Some(parts[2]) } else { None }) + ( + parts[1], + if parts.len() == 3 { + Some(parts[2]) + } else { + None + }, + ) }); let scope_integrations = hub_scope.and_then(|scope| { let parts = scope.split(":").collect::>(); @@ -341,8 +357,14 @@ impl ServerHandler for Runner { "script", scope_path.as_deref(), ); - let flows_fn = - get_items::(user_db, authed, &workspace_id, scope_type, "flow", scope_path.as_deref()); + let flows_fn = get_items::( + user_db, + authed, + &workspace_id, + scope_type, + "flow", + scope_path.as_deref(), + ); let resources_types_fn = get_resources_types(user_db, authed, &workspace_id); let hub_scripts_fn = get_scripts_from_hub(db, scope_integrations.as_deref()); let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() { @@ -410,7 +432,7 @@ impl ServerHandler for Runner { fn get_info(&self) -> ServerInfo { ServerInfo { - protocol_version: Default::default(), + protocol_version: ProtocolVersion::default(), capabilities: ServerCapabilities::builder() .enable_tools() .enable_tool_list_changed() @@ -424,7 +446,7 @@ impl ServerHandler for Runner { &self, _request: InitializeRequestParam, _context: RequestContext, - ) -> Result { + ) -> Result { Ok(self.get_info()) } @@ -432,7 +454,7 @@ impl ServerHandler for Runner { &self, _request: Option, _context: RequestContext, - ) -> Result { + ) -> Result { Ok(ListResourcesResult { resources: vec![], next_cursor: None }) } @@ -440,7 +462,7 @@ impl ServerHandler for Runner { &self, _request: Option, _context: RequestContext, - ) -> Result { + ) -> Result { Ok(ListPromptsResult::default()) } @@ -448,7 +470,7 @@ impl ServerHandler for Runner { &self, _request: Option, _context: RequestContext, - ) -> Result { + ) -> Result { Ok(ListResourceTemplatesResult::default()) } } @@ -467,7 +489,10 @@ 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)> { let session_manager = Arc::new(LocalSessionManager::default()); - let service_config = Default::default(); + let service_config = StreamableHttpServerConfig { + sse_keep_alive: Some(Duration::from_secs(15)), + stateful_mode: false, + }; let service = StreamableHttpService::new( || Ok(Runner::new()), session_manager.clone(), @@ -513,6 +538,5 @@ async fn list_mcp_tools_handler() -> JsonResult> { /// Creates a router service for listing MCP tools pub fn list_tools_service() -> Router { - Router::new() - .route("/", get(list_mcp_tools_handler)) -} \ No newline at end of file + Router::new().route("/", get(list_mcp_tools_handler)) +} diff --git a/backend/windmill-api/src/mcp/tools/endpoint_tools.rs b/backend/windmill-api/src/mcp/tools/endpoint_tools.rs index 1b8a7f0a66..624ca29c7b 100644 --- a/backend/windmill-api/src/mcp/tools/endpoint_tools.rs +++ b/backend/windmill-api/src/mcp/tools/endpoint_tools.rs @@ -3,16 +3,16 @@ //! Contains the auto-generated endpoint tools and utilities for converting //! them to MCP tools and handling HTTP calls to Windmill API endpoints. -use rmcp::{model::Tool, Error}; +use crate::db::ApiAuthed; +use rmcp::{model::Tool, ErrorData}; use std::sync::Arc; use windmill_common::auth::create_jwt_token; use windmill_common::db::Authed; use windmill_common::BASE_URL; -use crate::db::ApiAuthed; // Import the auto-generated tools use super::auto_generated_endpoints; -pub use auto_generated_endpoints::{EndpointTool, all_tools}; +pub use auto_generated_endpoints::{all_tools, EndpointTool}; /// Get all available endpoint tools pub fn all_endpoint_tools() -> Vec { @@ -21,25 +21,28 @@ pub fn all_endpoint_tools() -> Vec { /// Convert endpoint tools to MCP tools pub fn endpoint_tools_to_mcp_tools(endpoint_tools: Vec) -> Vec { - endpoint_tools.into_iter().map(|tool| endpoint_tool_to_mcp_tool(&tool)).collect() + endpoint_tools + .into_iter() + .map(|tool| endpoint_tool_to_mcp_tool(&tool)) + .collect() } /// Convert a single endpoint tool to MCP tool pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool { let mut combined_properties = serde_json::Map::new(); let mut combined_required = Vec::new(); - + // Combine all parameter schemas let schemas = [ &tool.path_params_schema, - &tool.query_params_schema, + &tool.query_params_schema, &tool.body_schema, ]; - + for schema in schemas.iter().filter_map(|s| s.as_ref()) { merge_schema_into(&mut combined_properties, &mut combined_required, schema); } - + let combined_schema = serde_json::json!({ "type": "object", "properties": combined_properties, @@ -47,7 +50,7 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool { }); let description = format!("{}. {}", tool.description, tool.instructions); - + // Create annotations based on HTTP method and endpoint characteristics let annotations = create_endpoint_annotations(tool); @@ -55,6 +58,9 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool { name: tool.name.clone(), description: Some(description.into()), input_schema: Arc::new(combined_schema.as_object().unwrap().clone()), + title: Some(tool.name.to_string()), + output_schema: None, + icons: None, annotations: Some(annotations), } } @@ -62,15 +68,15 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool { /// Create appropriate annotations for endpoint tools based on HTTP method fn create_endpoint_annotations(tool: &EndpointTool) -> rmcp::model::ToolAnnotations { let method = tool.method.as_ref(); - + // Determine characteristics based on HTTP method let (read_only, destructive, idempotent, open_world) = match method { - "GET" => (true, false, true, true), // Read-only, safe, idempotent - "POST" => (false, true, false, true), // Can modify, potentially destructive, not idempotent - "PUT" => (false, false, true, true), // Can modify, typically idempotent updates - "DELETE" => (false, true, true, true), // Destructive but idempotent + "GET" => (true, false, true, true), // Read-only, safe, idempotent + "POST" => (false, true, false, true), // Can modify, potentially destructive, not idempotent + "PUT" => (false, false, true, true), // Can modify, typically idempotent updates + "DELETE" => (false, true, true, true), // Destructive but idempotent "PATCH" => (false, false, false, true), // Partial updates, not guaranteed idempotent - _ => (false, true, false, true), // Default: assume can modify and be destructive + _ => (false, true, false, true), // Default: assume can modify and be destructive }; rmcp::model::ToolAnnotations { @@ -93,7 +99,7 @@ fn merge_schema_into( combined_properties.insert(key.clone(), value.clone()); } } - + if let Some(required) = schema.get("required").and_then(|r| r.as_array()) { for req in required.iter().filter_map(|r| r.as_str()) { combined_required.push(req.to_string()); @@ -107,34 +113,52 @@ pub async fn call_endpoint_tool( args: serde_json::Value, workspace_id: &str, api_authed: &ApiAuthed, -) -> Result { +) -> Result { let args_map = match &args { serde_json::Value::Object(map) => map, - _ => return Err(Error::invalid_params("Arguments must be an object", Some(tool.name.clone().into()))), + _ => { + return Err(ErrorData::invalid_params( + "Arguments must be an object", + Some(tool.name.clone().into()), + )) + } }; // Build URL with path substitutions - let path_template = substitute_path_params(&tool.path, workspace_id, args_map, &tool.path_params_schema)?; + let path_template = + substitute_path_params(&tool.path, workspace_id, args_map, &tool.path_params_schema)?; let query_string = build_query_string(args_map, &tool.query_params_schema); - let full_url = format!("{}/api{}{}", BASE_URL.read().await, path_template, query_string); + let full_url = format!( + "{}/api{}{}", + BASE_URL.read().await, + path_template, + query_string + ); // Prepare request body let body_json = build_request_body(&tool.method, args_map, &tool.body_schema); // Create and execute request - let response = create_http_request(&tool.method, &full_url, workspace_id, api_authed, body_json).await?; - + let response = + create_http_request(&tool.method, &full_url, workspace_id, api_authed, body_json).await?; + let status = response.status(); let response_text = response.text().await.map_err(|e| { - Error::internal_error(format!("Failed to read response text: {}", e), None) + ErrorData::internal_error(format!("Failed to read response text: {}", e), None) })?; if status.is_success() { - Ok(serde_json::from_str(&response_text).unwrap_or_else(|_| serde_json::Value::String(response_text))) + Ok(serde_json::from_str(&response_text) + .unwrap_or_else(|_| serde_json::Value::String(response_text))) } else { - Err(Error::internal_error( - format!("HTTP {} {}: {}", status.as_u16(), status.canonical_reason().unwrap_or(""), response_text), - None + Err(ErrorData::internal_error( + format!( + "HTTP {} {}: {}", + status.as_u16(), + status.canonical_reason().unwrap_or(""), + response_text + ), + None, )) } } @@ -145,9 +169,9 @@ fn substitute_path_params( workspace_id: &str, args_map: &serde_json::Map, path_schema: &Option, -) -> Result { +) -> Result { let mut path_template = path.replace("{workspace}", workspace_id); - + if let Some(schema) = path_schema { if let Some(props) = schema.get("properties").and_then(|p| p.as_object()) { for (param_name, _) in props { @@ -157,19 +181,19 @@ fn substitute_path_params( if let Some(str_val) = param_value.as_str() { path_template = path_template.replace(&placeholder, str_val); } - }, + } None => { tracing::warn!("Missing required path parameter: {}", param_name); - return Err(Error::invalid_params( + return Err(ErrorData::invalid_params( format!("Missing required path parameter: {}", param_name), - None + None, )); } } } } } - + Ok(path_template) } @@ -178,25 +202,31 @@ fn build_query_string( args_map: &serde_json::Map, query_schema: &Option, ) -> String { - let Some(schema) = query_schema else { return String::new() }; - let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else { return String::new() }; - + let Some(schema) = query_schema else { + return String::new(); + }; + let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else { + return String::new(); + }; + let query_params: Vec = props .keys() .filter_map(|param_name| { - args_map.get(param_name) + args_map + .get(param_name) .filter(|v| !v.is_null()) .map(|value| { let value_str = value.to_string(); let str_val = value_str.trim_matches('"'); - format!("{}={}", - urlencoding::encode(param_name), + format!( + "{}={}", + urlencoding::encode(param_name), urlencoding::encode(str_val) ) }) }) .collect(); - + if query_params.is_empty() { String::new() } else { @@ -213,18 +243,19 @@ fn build_request_body( if method == "GET" { return None; } - + let schema = body_schema.as_ref()?; let props = schema.get("properties")?.as_object()?; - + let body_map: serde_json::Map = props .keys() .filter_map(|param_name| { - args_map.get(param_name) + args_map + .get(param_name) .map(|value| (param_name.clone(), value.clone())) }) .collect(); - + if body_map.is_empty() { None } else { @@ -239,7 +270,7 @@ async fn create_http_request( workspace_id: &str, api_authed: &ApiAuthed, body_json: Option, -) -> Result { +) -> Result { let client = &crate::HTTP_CLIENT; let mut request_builder = match method { "GET" => client.get(url), @@ -247,16 +278,19 @@ async fn create_http_request( "PUT" => client.put(url), "DELETE" => client.delete(url), "PATCH" => client.patch(url), - _ => return Err(Error::invalid_params( - format!("Unsupported HTTP method: {}", method), - None - )), + _ => { + return Err(ErrorData::invalid_params( + format!("Unsupported HTTP method: {}", method), + None, + )) + } }; // Add authorization header let authed = Authed::from(api_authed.clone()); - let token = create_jwt_token(authed, workspace_id, 3600, None, None, None, None).await - .map_err(|e| Error::internal_error(e.to_string(), None))?; + let token = create_jwt_token(authed, workspace_id, 3600, None, None, None, None) + .await + .map_err(|e| ErrorData::internal_error(e.to_string(), None))?; request_builder = request_builder.header("Authorization", format!("Bearer {}", token)); // Add body if present @@ -266,7 +300,8 @@ async fn create_http_request( .json(&body); } - request_builder.send().await.map_err(|e| { - Error::internal_error(format!("Failed to execute request: {}", e), None) - }) -} \ No newline at end of file + request_builder + .send() + .await + .map_err(|e| ErrorData::internal_error(format!("Failed to execute request: {}", e), None)) +} diff --git a/backend/windmill-api/src/mcp/utils/database.rs b/backend/windmill-api/src/mcp/utils/database.rs index 0a0ea15ca6..39aac1ae6e 100644 --- a/backend/windmill-api/src/mcp/utils/database.rs +++ b/backend/windmill-api/src/mcp/utils/database.rs @@ -3,28 +3,32 @@ //! Contains all database query functions and database-related utilities //! used by the MCP server implementation. -use rmcp::Error; +use rmcp::ErrorData; use sql_builder::prelude::*; use windmill_common::db::UserDB; use windmill_common::scripts::{get_full_hub_script_by_path, Schema}; use windmill_common::utils::{query_elems_from_hub, StripPath}; use windmill_common::{DB, HUB_BASE_URL}; +use super::models::*; use crate::db::ApiAuthed; use crate::HTTP_CLIENT; -use super::models::*; /// Check if the user has proper MCP scopes -pub fn check_scopes(authed: &ApiAuthed) -> Result<(), Error> { +pub fn check_scopes(authed: &ApiAuthed) -> Result<(), ErrorData> { let scopes = authed.scopes.as_ref(); if scopes.is_none() - || scopes - .unwrap() - .iter() - .all(|scope| !scope.starts_with("mcp:all") && !scope.starts_with("mcp:favorites") && !scope.starts_with("mcp:hub:")) + || scopes.unwrap().iter().all(|scope| { + !scope.starts_with("mcp:all") + && !scope.starts_with("mcp:favorites") + && !scope.starts_with("mcp:hub:") + }) { tracing::error!("Unauthorized: missing mcp scope"); - return Err(Error::internal_error("Unauthorized: missing mcp scope".to_string(), None)); + return Err(ErrorData::internal_error( + "Unauthorized: missing mcp scope".to_string(), + None, + )); } Ok(()) } @@ -36,7 +40,7 @@ pub async fn get_item_schema( authed: &ApiAuthed, workspace_id: &str, item_type: &str, -) -> Result, Error> { +) -> Result, ErrorData> { let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); sqlb.fields(&["o.schema"]); sqlb.and_where("o.path = ?".bind(&path)); @@ -45,23 +49,23 @@ pub async fn get_item_schema( sqlb.and_where("o.draft_only IS NOT TRUE"); let sql = sqlb.sql().map_err(|_e| { tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) + ErrorData::internal_error("failed to build sql", None) })?; let mut tx = user_db .clone() .begin(authed) .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?; let item = sqlx::query_as::<_, ItemSchema>(&sql) .fetch_one(&mut *tx) .await .map_err(|_e| { tracing::error!("failed to fetch item schema: {}", _e); - Error::internal_error("failed to fetch item schema", None) + ErrorData::internal_error("failed to fetch item schema", None) })?; tx.commit() .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?; Ok(item.schema) } @@ -70,29 +74,29 @@ pub async fn get_resources_types( user_db: &UserDB, authed: &ApiAuthed, workspace_id: &str, -) -> Result, Error> { +) -> Result, ErrorData> { let mut sqlb = SqlBuilder::select_from("resource_type as o"); sqlb.fields(&["o.name", "o.description"]); sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); let sql = sqlb.sql().map_err(|_e| { tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) + ErrorData::internal_error("failed to build sql", None) })?; let mut tx = user_db .clone() .begin(authed) .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?; let rows = sqlx::query_as::<_, ResourceType>(&sql) .fetch_all(&mut *tx) .await .map_err(|_e| { tracing::error!("Failed to fetch resource types: {}", _e); - Error::internal_error("failed to fetch resource types", None) + ErrorData::internal_error("failed to fetch resource types", None) })?; tx.commit() .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?; Ok(rows) } @@ -102,30 +106,30 @@ pub async fn get_resources( authed: &ApiAuthed, workspace_id: &str, resource_type: &str, -) -> Result, Error> { +) -> Result, ErrorData> { let mut sqlb = SqlBuilder::select_from("resource as o"); sqlb.fields(&["o.path", "o.description", "o.resource_type"]); sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); sqlb.and_where("o.resource_type = ?".bind(&resource_type)); let sql = sqlb.sql().map_err(|_e| { tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) + ErrorData::internal_error("failed to build sql", None) })?; let mut tx = user_db .clone() .begin(authed) .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?; let rows = sqlx::query_as::<_, ResourceInfo>(&sql) .fetch_all(&mut *tx) .await .map_err(|_e| { tracing::error!("Failed to fetch resources: {}", _e); - Error::internal_error("failed to fetch resources", None) + ErrorData::internal_error("failed to fetch resources", None) })?; tx.commit() .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?; Ok(rows) } @@ -138,7 +142,7 @@ pub async fn get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen scope_type: &str, item_type: &str, scope_path: Option<&str>, -) -> Result, Error> { +) -> Result, ErrorData> { let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); let fields = vec!["o.path", "o.summary", "o.description", "o.schema"]; sqlb.fields(&fields); @@ -157,9 +161,15 @@ pub async fn get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen // scope path is always a folder path, format is f/my_folder/* if let Some(scope_path) = scope_path { - if scope_path.split("/").count() != 3 || !scope_path.starts_with("f/") || !scope_path.ends_with("/*") { - return Err(Error::internal_error( - format!("Invalid folder format: {}, expected format is f/my_folder/*", scope_path), + if scope_path.split("/").count() != 3 + || !scope_path.starts_with("f/") + || !scope_path.ends_with("/*") + { + return Err(ErrorData::internal_error( + format!( + "Invalid folder format: {}, expected format is f/my_folder/*", + scope_path + ), None, )); } @@ -177,23 +187,23 @@ pub async fn get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen .limit(100); let sql = sqlb.sql().map_err(|_e| { tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) + ErrorData::internal_error("failed to build sql", None) })?; let mut tx = user_db .clone() .begin(authed) .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?; let rows = sqlx::query_as::<_, T>(&sql) .fetch_all(&mut *tx) .await .map_err(|_e| { tracing::error!("Failed to fetch {}: {}", item_type, _e); - Error::internal_error(format!("failed to fetch {}", item_type), None) + ErrorData::internal_error(format!("failed to fetch {}", item_type), None) })?; tx.commit() .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + .map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?; Ok(rows) } @@ -201,7 +211,7 @@ pub async fn get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen pub async fn get_scripts_from_hub( db: &DB, scope_integrations: Option<&str>, -) -> Result, Error> { +) -> Result, ErrorData> { let query_params = Some(vec![ ("limit", "100".to_string()), ("with_schema", "true".to_string()), @@ -213,34 +223,34 @@ pub async fn get_scripts_from_hub( .await .map_err(|e| { tracing::error!("Failed to get items from hub: {}", e); - Error::internal_error(format!("Failed to get items from hub: {}", e), None) + ErrorData::internal_error(format!("Failed to get items from hub: {}", e), None) })?; - + use axum::body::to_bytes; let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| { tracing::error!("Failed to read response body: {}", e); - Error::internal_error(format!("Failed to read response body: {}", e), None) + ErrorData::internal_error(format!("Failed to read response body: {}", e), None) })?; let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { tracing::error!("Failed to decode response body: {}", e); - Error::internal_error(format!("Failed to decode response body: {}", e), None) + ErrorData::internal_error(format!("Failed to decode response body: {}", e), None) })?; let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| { tracing::error!("Failed to parse hub response: {}", e); - Error::internal_error(format!("Failed to parse hub response: {}", e), None) + ErrorData::internal_error(format!("Failed to parse hub response: {}", e), None) })?; Ok(hub_response.asks) } /// Get the schema for a Hub script -pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result, Error> { +pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result, ErrorData> { let strip_path = StripPath(path.to_string()); let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db)) .await .map_err(|e| { tracing::error!("Failed to get hub script: {}", e); - Error::internal_error(format!("Failed to get hub script: {}", e), None) + ErrorData::internal_error(format!("Failed to get hub script: {}", e), None) })?; match serde_json::from_str::(res.schema.get()) { Ok(schema) => Ok(Some(schema)), @@ -249,4 +259,4 @@ pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result Ok(None) } } -} \ No newline at end of file +} diff --git a/backend/windmill-api/src/mcp/utils/schema.rs b/backend/windmill-api/src/mcp/utils/schema.rs index b11f2f83ab..0de27a677f 100644 --- a/backend/windmill-api/src/mcp/utils/schema.rs +++ b/backend/windmill-api/src/mcp/utils/schema.rs @@ -3,16 +3,16 @@ //! Contains functions for transforming Windmill schemas into MCP-compatible formats, //! including resource enrichment and schema conversion utilities. -use rmcp::Error; +use rmcp::ErrorData; use serde_json::Value; use std::collections::HashMap; use windmill_common::db::UserDB; use windmill_common::scripts::Schema; -use crate::db::ApiAuthed; -use super::models::{SchemaType, ResourceInfo, ResourceType}; use super::database::get_resources; +use super::models::{ResourceInfo, ResourceType, SchemaType}; use super::transform::apply_key_transformation; +use crate::db::ApiAuthed; /// Convert a Windmill Schema to a SchemaType pub fn convert_schema_to_schema_type(schema: Option) -> SchemaType { @@ -35,7 +35,7 @@ pub async fn transform_schema_for_resources( w_id: &str, resources_cache: &mut HashMap>, resources_types: &Vec, -) -> Result { +) -> Result { let mut schema_obj: SchemaType = schema.clone(); // replace invalid char in property key with underscore @@ -71,24 +71,15 @@ pub async fn transform_schema_for_resources( let resource_type_obj = resource_type.cloned(); if !resources_cache.contains_key(&resource_type_key) { - let available_resources = get_resources( - user_db, - authed, - &w_id, - &resource_type_key, - ) - .await; + let available_resources = + get_resources(user_db, authed, &w_id, &resource_type_key).await; match available_resources { Ok(cache_data) => { - resources_cache - .insert(resource_type_key.clone(), cache_data); + resources_cache.insert(resource_type_key.clone(), cache_data); } Err(e) => { - tracing::error!( - "Failed to fetch resource cache data: {}", - e - ); + tracing::error!("Failed to fetch resource cache data: {}", e); continue; // Skip this property if fetching failed } } @@ -111,24 +102,16 @@ pub async fn transform_schema_for_resources( ), None => "An object parameter.".to_string() }; - prop_map.insert( - "type".to_string(), - Value::String("string".to_string()), - ); - prop_map.insert( - "description".to_string(), - Value::String(description), - ); + prop_map + .insert("type".to_string(), Value::String("string".to_string())); + prop_map.insert("description".to_string(), Value::String(description)); if resources_count > 0 { let resources_description = resource_cache .iter() .map(|resource| { format!( "{}: $res:{}", - resource - .description - .as_deref() - .unwrap_or("No title"), + resource.description.as_deref().unwrap_or("No title"), resource.path ) }) @@ -157,4 +140,4 @@ pub async fn transform_schema_for_resources( } Ok(schema_obj) -} \ No newline at end of file +} diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 9c9f4dfdbe..617d849b16 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.546.0"; +export const VERSION = "v1.546.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 4580fe0927..30fad224ee 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.546.0"; +export const VERSION = "1.546.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a7c5d8854d..5f2f72761b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.546.0", + "version": "1.546.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.546.0", + "version": "1.546.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 87d968cf51..4a8441970f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.546.0", + "version": "1.546.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 72d3a150a3..47bc3d54a1 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.546.0" -wmill_pg = ">=1.546.0" +wmill = ">=1.546.1" +wmill_pg = ">=1.546.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index e014505c1e..37d235675c 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.546.0 + version: 1.546.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index d857d69ec6..8fa3a67310 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.546.0' + ModuleVersion = '1.546.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index fa909a1618..44e4dac416 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.546.0" +version = "1.546.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 8f5bfee297..811d109887 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.546.0" +version = "1.546.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index fac5926171..d02e7a16e1 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.546.0", + "version": "1.546.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 9269dadb53..f4a3fe75d4 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.546.0", + "version": "1.546.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 8267df0b9e..078cbb62ee 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.546.0 +1.546.1