use std::sync::Arc; use anyhow::Context; use anyhow::Result; use arc_swap::ArcSwap; use codex_protocol::mcp::Resource; use codex_protocol::mcp::ResourceContent; use rmcp::model::PaginatedRequestParams; use rmcp::model::ReadResourceRequestParams; use crate::McpConnectionManager; /// One page of resources returned by an MCP server. #[derive(Clone, Debug, PartialEq)] pub struct McpResourcePage { /// Resources advertised on this page. pub resources: Vec, /// Opaque cursor to supply when requesting the next page. pub next_cursor: Option, } /// Contents returned after reading one MCP resource. #[derive(Clone, Debug, PartialEq)] pub struct McpResourceReadResult { /// Text or blob content returned for the requested resource. pub contents: Vec, } /// Session-scoped access to MCP resources through the currently installed manager. /// /// The client retains the manager's shared publication handle rather than a manager /// snapshot, so calls automatically use replacements installed during startup and refresh. #[derive(Clone)] pub struct McpResourceClient { manager: Arc>, } impl std::fmt::Debug for McpResourceClient { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter .debug_struct("McpResourceClient") .finish_non_exhaustive() } } impl McpResourceClient { /// Creates a resource client backed by the session's replaceable MCP manager. pub fn new(manager: Arc>) -> Self { Self { manager } } /// Returns whether the current manager contains the named server. /// /// This does not wait for server startup or imply that startup succeeded. pub async fn has_server(&self, server: &str) -> bool { self.manager.load_full().contains_server(server) } /// Lists one resource page from the named server. pub async fn list_resources( &self, server: &str, cursor: Option, ) -> Result { let params = cursor.map(|cursor| PaginatedRequestParams::default().with_cursor(Some(cursor))); let result = self .manager .load_full() .list_resources(server, params) .await?; let resources = result .resources .into_iter() .map(resource_from_rmcp) .collect::>>()?; Ok(McpResourcePage { resources, next_cursor: result.next_cursor, }) } /// Reads one resource from the named server. pub async fn read_resource(&self, server: &str, uri: &str) -> Result { let result = self .manager .load_full() .read_resource(server, ReadResourceRequestParams::new(uri.to_string())) .await?; let contents = result .contents .into_iter() .map(resource_content_from_rmcp) .collect::>>()?; Ok(McpResourceReadResult { contents }) } } fn resource_from_rmcp(resource: rmcp::model::Resource) -> Result { let value = serde_json::to_value(resource).context("failed to serialize MCP resource")?; Resource::from_mcp_value(value).context("failed to convert MCP resource") } fn resource_content_from_rmcp(content: rmcp::model::ResourceContents) -> Result { let value = serde_json::to_value(content).context("failed to serialize MCP resource content")?; serde_json::from_value(value).context("failed to convert MCP resource content") }