Files
windmill/backend/windmill-mcp/src/lib.rs
centdixandwindmill-internal-app[bot] 3bc5800197 feat: allow private MCP server URLs (#9470)
* 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>
2026-06-08 14:46:47 +00:00

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();
}
}
}