mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 16:02:23 +00:00
* feat: allow private MCP server URLs * docs: remove private MCP server URL doc * fix: apply MCP URL opt-in to OAuth handlers * fix: update EE ref for MCP OAuth redirects * fix: preserve MCP OAuth client timeout * chore: update ee-repo-ref to 481ea7f28dc5af6b72390c82f494f34cb9809546 This commit updates the EE repository reference after PR #608 was merged in windmill-ee-private. Previous ee-repo-ref: 6c7da03fb994be23ed6aca59bece94d257a641b5 New ee-repo-ref: 481ea7f28dc5af6b72390c82f494f34cb9809546 Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
//! Windmill MCP (Model Context Protocol) implementation
|
|
//!
|
|
//! This crate provides:
|
|
//! - MCP client for connecting to external MCP servers (used by AI agents)
|
|
//! - Common types and utilities for MCP implementations
|
|
//! - MCP server types (when `server` feature is enabled)
|
|
//! - OAuth support (when `auth` feature is enabled)
|
|
|
|
// Common types and utilities module
|
|
pub mod common;
|
|
|
|
// Client module
|
|
pub mod client;
|
|
|
|
// Re-export common types at crate root for convenience
|
|
pub use common::{
|
|
convert_schema_to_schema_type, is_resource_allowed, parse_mcp_scopes, transform_hub_path,
|
|
transform_path, FlowInfo, HubResponse, HubScriptInfo, ItemSchema, McpScopeConfig, ResourceInfo,
|
|
ResourceType, SchemaType, ScriptInfo, ToolableItem, WorkspaceId,
|
|
};
|
|
|
|
// Re-export client types at crate root for backward compatibility
|
|
pub use client::{McpClient, McpResource, McpToolSource};
|
|
|
|
// Re-export rmcp types for client usage
|
|
pub use rmcp::model::Tool as McpTool;
|
|
|
|
// Server module (when server feature is enabled)
|
|
#[cfg(feature = "server")]
|
|
pub mod server;
|
|
|
|
// MCP OAuth client registration (when auth feature is enabled)
|
|
#[cfg(feature = "auth")]
|
|
pub mod client_registration;
|
|
|
|
// Re-export rmcp auth types when auth feature is enabled
|
|
#[cfg(feature = "auth")]
|
|
pub mod oauth {
|
|
//! Re-exports of rmcp auth and oauth2 types for MCP OAuth implementations
|
|
|
|
use std::time::Duration;
|
|
|
|
pub use rmcp::transport::auth::AuthorizationManager;
|
|
|
|
const DEFAULT_OAUTH_HTTP_TIMEOUT: Duration = Duration::from_secs(30);
|
|
|
|
pub fn no_redirect_http_client() -> Result<reqwest::Client, reqwest::Error> {
|
|
no_redirect_http_client_with_timeout(DEFAULT_OAUTH_HTTP_TIMEOUT)
|
|
}
|
|
|
|
pub(crate) fn no_redirect_http_client_with_timeout(
|
|
timeout: Duration,
|
|
) -> Result<reqwest::Client, reqwest::Error> {
|
|
reqwest::Client::builder()
|
|
.timeout(timeout)
|
|
.redirect(reqwest::redirect::Policy::none())
|
|
.build()
|
|
}
|
|
|
|
// Re-export oauth2 types needed for MCP OAuth flow
|
|
pub use oauth2::{
|
|
basic::BasicClient, AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge,
|
|
RedirectUrl, Scope, TokenUrl,
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::{
|
|
io::Read,
|
|
net::TcpListener,
|
|
thread,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
#[tokio::test]
|
|
async fn no_redirect_http_client_times_out_stalled_responses() {
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
|
|
let handle = thread::spawn(move || {
|
|
if let Ok((mut stream, _)) = listener.accept() {
|
|
let _ = stream.set_read_timeout(Some(Duration::from_millis(200)));
|
|
let mut buffer = [0; 1024];
|
|
let _ = stream.read(&mut buffer);
|
|
thread::sleep(Duration::from_millis(300));
|
|
}
|
|
});
|
|
|
|
let client = no_redirect_http_client_with_timeout(Duration::from_millis(50)).unwrap();
|
|
let started = Instant::now();
|
|
let err = client
|
|
.get(format!("http://{addr}/stall"))
|
|
.send()
|
|
.await
|
|
.expect_err("stalled response should time out");
|
|
|
|
assert!(err.is_timeout(), "expected timeout error, got: {err}");
|
|
assert!(
|
|
started.elapsed() < Duration::from_secs(2),
|
|
"stalled request should fail promptly"
|
|
);
|
|
|
|
handle.join().unwrap();
|
|
}
|
|
}
|
|
}
|