Skip to main content

objectiveai_sdk/cli/command/agents/mcp/
mod.rs

1//! `agents mcp` — query a live agent's aggregated MCP surface over its
2//! per-`response_id` listener socket. Sub-groups:
3//!
4//! - `resources` — `list`, `read`.
5//! - `servers` — `list`.
6//! - `tools` — `call`, `list`.
7
8use crate::cli::command::CommandRequest;
9
10pub mod resources;
11pub mod servers;
12pub mod tools;
13
14#[derive(clap::Subcommand)]
15pub enum Command {
16    /// Query a live agent's MCP resources — `list`, `read` — over its
17    /// per-`response_id` listener socket.
18    Resources {
19        #[command(subcommand)]
20        command: resources::Command,
21    },
22    /// List a live agent's connected MCP servers and their metadata.
23    Servers {
24        #[command(subcommand)]
25        command: servers::Command,
26    },
27    /// Query a live agent's MCP tools — `call`, `list` — over its
28    /// per-`response_id` listener socket.
29    Tools {
30        #[command(subcommand)]
31        command: tools::Command,
32    },
33}
34
35#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
36#[serde(untagged)]
37#[schemars(rename = "cli.command.agents.mcp.Request")]
38pub enum Request {
39    #[schemars(title = "Resources")]
40    Resources(resources::Request),
41    #[schemars(title = "Servers")]
42    Servers(servers::Request),
43    #[schemars(title = "Tools")]
44    Tools(tools::Request),
45}
46
47// Exempt from json-schema coverage: tier aggregate (see the root
48// `ResponseItem` in command.rs - TS7056).
49#[objectiveai_sdk_macros::json_schema_ignore]
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
51#[schemars(rename = "cli.command.agents.mcp.ResponseItem")]
52#[serde(untagged)]
53pub enum ResponseItem {
54    #[schemars(title = "Resources")]
55    Resources(resources::ResponseItem),
56    #[schemars(title = "Servers")]
57    Servers(servers::ResponseItem),
58    #[schemars(title = "Tools")]
59    Tools(tools::ResponseItem),
60}
61
62#[cfg(feature = "mcp")]
63impl crate::cli::command::CommandResponse for ResponseItem {
64    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
65        match self {
66            ResponseItem::Resources(v) => v.into_mcp(),
67            ResponseItem::Servers(v) => v.into_mcp(),
68            ResponseItem::Tools(v) => v.into_mcp(),
69        }
70    }
71}
72
73impl TryFrom<Command> for Request {
74    type Error = crate::cli::command::FromArgsError;
75    fn try_from(command: Command) -> Result<Self, Self::Error> {
76        match command {
77            Command::Resources { command } => {
78                Ok(Request::Resources(resources::Request::try_from(command)?))
79            }
80            Command::Servers { command } => {
81                Ok(Request::Servers(servers::Request::try_from(command)?))
82            }
83            Command::Tools { command } => {
84                Ok(Request::Tools(tools::Request::try_from(command)?))
85            }
86        }
87    }
88}
89
90impl CommandRequest for Request {
91    fn request_base(&self) -> &crate::cli::command::RequestBase {
92        match self {
93            Request::Resources(inner) => inner.request_base(),
94            Request::Servers(inner) => inner.request_base(),
95            Request::Tools(inner) => inner.request_base(),
96        }
97    }
98
99    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
100        match self {
101            Request::Resources(inner) => inner.request_base_mut(),
102            Request::Servers(inner) => inner.request_base_mut(),
103            Request::Tools(inner) => inner.request_base_mut(),
104        }
105    }
106}
107
108#[cfg(feature = "cli-executor")]
109pub async fn execute<E: crate::cli::command::CommandExecutor>(
110    executor: &E,
111    request: Request,
112    agent_arguments: Option<&crate::cli::command::AgentArguments>,
113) -> Result<
114    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
115    E::Error,
116> {
117    use futures::StreamExt;
118    let stream: std::pin::Pin<
119        Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>,
120    > = match request {
121        Request::Resources(req) => {
122            let inner = resources::execute(executor, req, agent_arguments).await?;
123            Box::pin(inner.map(|r| r.map(ResponseItem::Resources)))
124        }
125        Request::Servers(req) => {
126            let inner = servers::execute(executor, req, agent_arguments).await?;
127            Box::pin(inner.map(|r| r.map(ResponseItem::Servers)))
128        }
129        Request::Tools(req) => {
130            let inner = tools::execute(executor, req, agent_arguments).await?;
131            Box::pin(inner.map(|r| r.map(ResponseItem::Tools)))
132        }
133    };
134    Ok(stream)
135}
136
137#[cfg(feature = "cli-executor")]
138pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
139    executor: &E,
140    request: Request,
141    transform: crate::cli::command::Transform,
142    agent_arguments: Option<&crate::cli::command::AgentArguments>,
143) -> Result<
144    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
145    E::Error,
146> {
147    let stream: std::pin::Pin<
148        Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>,
149    > = match request {
150        Request::Resources(req) => {
151            let inner = resources::execute_transform(executor, req, transform, agent_arguments).await?;
152            Box::pin(inner)
153        }
154        Request::Servers(req) => {
155            let inner = servers::execute_transform(executor, req, transform, agent_arguments).await?;
156            Box::pin(inner)
157        }
158        Request::Tools(req) => {
159            let inner = tools::execute_transform(executor, req, transform, agent_arguments).await?;
160            Box::pin(inner)
161        }
162    };
163    Ok(stream)
164}