1use serde_json::{Map, Value};
4
5use crate::error::{Error, Result};
6use crate::mcp::common::McpClient;
7use crate::mcp::http::{call_tool_http, connect_streamable, fetch_mcp_tools_http};
8use crate::mcp::sse::{call_tool_sse, connect_sse, fetch_mcp_tools_sse};
9use crate::oauth::OAuthReady;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TransportMode {
13 Auto,
14 Sse,
15 Streamable,
16}
17
18impl TransportMode {
19 pub fn parse(s: &str) -> Result<Self> {
20 match s {
21 "auto" => Ok(Self::Auto),
22 "sse" => Ok(Self::Sse),
23 "streamable" => Ok(Self::Streamable),
24 other => Err(Error::usage(format!(
25 "invalid --transport {other:?}; expected auto|sse|streamable"
26 ))),
27 }
28 }
29}
30
31pub async fn fetch_mcp_tools(
32 url: &str,
33 auth_headers: &[(String, String)],
34 cache_key: &str,
35 ttl: u64,
36 refresh: bool,
37 transport: TransportMode,
38 oauth: Option<&OAuthReady>,
39) -> Result<Vec<Value>> {
40 match transport {
41 TransportMode::Streamable => {
42 fetch_mcp_tools_http(url, auth_headers, cache_key, ttl, refresh, oauth).await
43 }
44 TransportMode::Sse => {
45 fetch_mcp_tools_sse(url, auth_headers, cache_key, ttl, refresh, oauth).await
46 }
47 TransportMode::Auto => {
48 match fetch_mcp_tools_http(url, auth_headers, cache_key, ttl, refresh, oauth).await {
49 Ok(tools) => Ok(tools),
50 Err(streamable_err) => {
51 tracing::debug!("streamable HTTP failed, trying SSE: {streamable_err}");
52 fetch_mcp_tools_sse(url, auth_headers, cache_key, ttl, refresh, oauth)
53 .await
54 .map_err(|sse_err| {
55 Error::runtime(format!(
56 "MCP HTTP failed (streamable: {streamable_err}; sse: {sse_err})"
57 ))
58 })
59 }
60 }
61 }
62 }
63}
64
65pub async fn call_tool(
66 url: &str,
67 auth_headers: &[(String, String)],
68 tool_name: &str,
69 arguments: Map<String, Value>,
70 full_envelope: bool,
71 transport: TransportMode,
72 oauth: Option<&OAuthReady>,
73) -> Result<Value> {
74 match transport {
75 TransportMode::Streamable => {
76 call_tool_http(
77 url,
78 auth_headers,
79 tool_name,
80 arguments,
81 full_envelope,
82 oauth,
83 )
84 .await
85 }
86 TransportMode::Sse => {
87 call_tool_sse(
88 url,
89 auth_headers,
90 tool_name,
91 arguments,
92 full_envelope,
93 oauth,
94 )
95 .await
96 }
97 TransportMode::Auto => {
98 match call_tool_http(
99 url,
100 auth_headers,
101 tool_name,
102 arguments.clone(),
103 full_envelope,
104 oauth,
105 )
106 .await
107 {
108 Ok(v) => Ok(v),
109 Err(streamable_err) => {
110 tracing::debug!("streamable call failed, trying SSE: {streamable_err}");
111 call_tool_sse(
112 url,
113 auth_headers,
114 tool_name,
115 arguments,
116 full_envelope,
117 oauth,
118 )
119 .await
120 .map_err(|sse_err| {
121 Error::runtime(format!(
122 "MCP call failed (streamable: {streamable_err}; sse: {sse_err})"
123 ))
124 })
125 }
126 }
127 }
128 }
129}
130
131pub async fn connect_http(
133 url: &str,
134 auth_headers: &[(String, String)],
135 transport: TransportMode,
136 oauth: Option<&OAuthReady>,
137) -> Result<McpClient> {
138 match transport {
139 TransportMode::Streamable => connect_streamable(url, auth_headers, oauth).await,
140 TransportMode::Sse => connect_sse(url, auth_headers, oauth).await,
141 TransportMode::Auto => match connect_streamable(url, auth_headers, oauth).await {
142 Ok(c) => Ok(c),
143 Err(streamable_err) => {
144 tracing::debug!("streamable connect failed, trying SSE: {streamable_err}");
145 connect_sse(url, auth_headers, oauth)
146 .await
147 .map_err(|sse_err| {
148 Error::runtime(format!(
149 "MCP connect failed (streamable: {streamable_err}; sse: {sse_err})"
150 ))
151 })
152 }
153 },
154 }
155}