use crate::mcp::auth::{McpAuthProvider, McpAuthRequest, McpCredential};
use crate::mcp::elicitation::UrlElicitationHandler;
use crate::mcp::form_elicitation::FormElicitationHandler;
use crate::mcp::http::HttpTransport;
use crate::mcp::result::map_tool_call_result;
use crate::mcp::transport::{McpConnection, McpEndpoint, McpTransport};
use crate::{EgressService, McpToolCallResult, McpToolDefinition};
use anyhow::Result;
use everruns_contracts::ToolResultImage;
use everruns_contracts::tool_types::ToolResult;
use serde_json::Value;
use std::sync::Arc;
pub struct McpClient {
http: Arc<HttpTransport>,
#[cfg(feature = "mcp-stdio")]
stdio: Arc<crate::mcp::stdio::StdioTransport>,
auth: Arc<dyn McpAuthProvider>,
}
impl McpClient {
pub fn new(egress: Arc<dyn EgressService>, auth: Arc<dyn McpAuthProvider>) -> Self {
Self::with_http(Arc::new(HttpTransport::new(egress)), auth)
}
pub fn with_url_elicitation(
egress: Arc<dyn EgressService>,
auth: Arc<dyn McpAuthProvider>,
elicitation: Arc<dyn UrlElicitationHandler>,
) -> Self {
Self::with_http(
Arc::new(HttpTransport::new(egress).with_elicitation_handler(elicitation)),
auth,
)
}
pub fn with_url_and_form_elicitation(
egress: Arc<dyn EgressService>,
auth: Arc<dyn McpAuthProvider>,
url: Arc<dyn UrlElicitationHandler>,
form: Arc<dyn FormElicitationHandler>,
) -> Self {
Self::with_http(
Arc::new(
HttpTransport::new(egress)
.with_elicitation_handler(url)
.with_form_elicitation_handler(form),
),
auth,
)
}
pub fn with_http(http: Arc<HttpTransport>, auth: Arc<dyn McpAuthProvider>) -> Self {
Self {
http,
#[cfg(feature = "mcp-stdio")]
stdio: Arc::new(crate::mcp::stdio::StdioTransport),
auth,
}
}
fn transport_for(&self, connection: &McpConnection) -> &dyn McpTransport {
match &connection.endpoint {
McpEndpoint::Http { .. } => self.http.as_ref(),
#[cfg(feature = "mcp-stdio")]
McpEndpoint::Stdio { .. } => self.stdio.as_ref(),
}
}
async fn credential(&self, connection: &McpConnection) -> Result<Option<McpCredential>> {
self.auth
.authorization(&McpAuthRequest {
server_name: &connection.name,
auth_mode: connection.auth_mode.clone(),
oauth_provider_id: connection.oauth_provider_id.as_deref(),
})
.await
}
pub async fn discover(&self, connection: &McpConnection) -> Result<Vec<McpToolDefinition>> {
let credential = self.credential(connection).await?;
self.transport_for(connection)
.list_tools(connection, credential.as_ref())
.await
}
pub async fn call(
&self,
connection: &McpConnection,
tool_name: &str,
arguments: Value,
) -> Result<McpToolCallResult> {
let credential = self.credential(connection).await?;
self.transport_for(connection)
.call_tool(connection, tool_name, arguments, credential.as_ref())
.await
}
pub async fn call_as_tool_result(
&self,
connection: &McpConnection,
tool_call_id: impl Into<String>,
tool_name: &str,
arguments: Value,
) -> Result<ToolResult> {
let result = self.call(connection, tool_name, arguments).await?;
let (json_result, images) = map_tool_call_result(&result);
Ok(tool_result(tool_call_id.into(), json_result, images))
}
}
pub(crate) fn tool_result(
tool_call_id: String,
result: Value,
images: Vec<ToolResultImage>,
) -> ToolResult {
ToolResult {
tool_call_id,
result: Some(result),
images: if images.is_empty() {
None
} else {
Some(images)
},
error: None,
connection_required: None,
raw_output: None,
}
}