llm_agent/mcp/
types.rs

1use crate::errors::BoxedError;
2use futures::future::BoxFuture;
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6/// Either a fixed MCP configuration or a resolver that derives parameters from
7/// the agent context.
8#[derive(Clone)]
9#[allow(clippy::type_complexity)]
10pub enum MCPInit<TCtx>
11where
12    TCtx: Send + Sync + 'static,
13{
14    Params(MCPParams),
15    Func(Arc<dyn Fn(&TCtx) -> Result<MCPParams, BoxedError> + Send + Sync>),
16    AsyncFunc(
17        Arc<dyn Fn(&TCtx) -> BoxFuture<'static, Result<MCPParams, BoxedError>> + Send + Sync>,
18    ),
19}
20
21impl<TCtx> MCPInit<TCtx>
22where
23    TCtx: Send + Sync + 'static,
24{
25    /// Returns an init that always yields the supplied parameters.
26    #[must_use]
27    pub fn from_params(params: MCPParams) -> Self {
28        Self::Params(params)
29    }
30
31    /// Returns an init backed by the provided synchronous resolver function.
32    pub fn from_fn<F>(func: F) -> Self
33    where
34        F: Fn(&TCtx) -> Result<MCPParams, BoxedError> + Send + Sync + 'static,
35    {
36        Self::Func(Arc::new(func))
37    }
38
39    /// Convenience helper to build an init from an async closure.
40    pub fn from_async_fn<F, Fut>(func: F) -> Self
41    where
42        F: Fn(&TCtx) -> Fut + Send + Sync + 'static,
43        Fut: std::future::Future<Output = Result<MCPParams, BoxedError>> + Send + 'static,
44    {
45        Self::AsyncFunc(Arc::new(move |ctx| Box::pin(func(ctx))))
46    }
47
48    /// Resolve parameters for the supplied context.
49    pub(crate) async fn resolve(&self, context: &TCtx) -> Result<MCPParams, BoxedError> {
50        match self {
51            Self::Params(params) => Ok(params.clone()),
52            Self::Func(func) => func(context),
53            Self::AsyncFunc(func) => func(context).await,
54        }
55    }
56}
57
58impl<TCtx> From<MCPParams> for MCPInit<TCtx>
59where
60    TCtx: Send + Sync + 'static,
61{
62    fn from(value: MCPParams) -> Self {
63        Self::from_params(value)
64    }
65}
66
67impl<TCtx, F> From<F> for MCPInit<TCtx>
68where
69    TCtx: Send + Sync + 'static,
70    F: Fn(&TCtx) -> Result<MCPParams, BoxedError> + Send + Sync + 'static,
71{
72    fn from(value: F) -> Self {
73        Self::from_fn(value)
74    }
75}
76
77// For async resolvers use `MCPInit::from_async_fn` explicitly to preserve
78// clarity.
79
80/// `MCPParams` describes how to reach an MCP server.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(tag = "type", rename_all = "kebab-case")]
83pub enum MCPParams {
84    Stdio(MCPStdioParams),
85    StreamableHttp(MCPStreamableHTTPParams),
86}
87
88/// Launches a local MCP server via stdio.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct MCPStdioParams {
91    /// Executable to spawn (e.g. "uvx").
92    pub command: String,
93    /// Optional arguments passed to the command.
94    #[serde(default, skip_serializing_if = "Vec::is_empty")]
95    pub args: Vec<String>,
96}
97
98/// Connects to a remote MCP server exposing the streamable HTTP transport.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct MCPStreamableHTTPParams {
101    /// Base URL for the MCP server.
102    pub url: String,
103    /// Authorization header value when required. OAuth flows are not automated,
104    /// so supply a token directly.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub authorization: Option<String>,
107}