codex_mcp/
resource_client.rs1use std::sync::Arc;
2use std::sync::Weak;
3
4use anyhow::Context;
5use anyhow::Result;
6use codex_protocol::mcp::Resource;
7use codex_protocol::mcp::ResourceContent;
8use rmcp::model::PaginatedRequestParams;
9use rmcp::model::ReadResourceRequestParams;
10
11use crate::McpRuntime;
12use crate::binding_clients::McpBindingClientIdentity;
13use crate::binding_clients::McpBindingClients;
14use crate::connection_manager::McpConnectionManager;
15
16#[derive(Clone, Debug, PartialEq)]
18pub struct McpResourcePage {
19 pub resources: Vec<Resource>,
21 pub next_cursor: Option<String>,
23}
24
25#[derive(Clone, Debug, PartialEq)]
27pub struct McpResourceReadResult {
28 pub contents: Vec<ResourceContent>,
30}
31
32#[derive(Clone)]
34pub struct McpResourceClient {
35 source: McpResourceSource,
36}
37
38#[derive(Clone)]
40pub struct McpResourceClientCacheKey(McpResourceClientCacheKeyInner);
41
42#[derive(Clone)]
43enum McpResourceClientCacheKeyInner {
44 Latest(Weak<McpConnectionManager>),
45 Exact(McpBindingClientIdentity),
46}
47
48#[derive(Clone)]
49enum McpResourceSource {
50 Latest(Arc<McpRuntime>),
51 Exact(Arc<McpBindingClients>),
52}
53
54impl PartialEq for McpResourceClientCacheKey {
55 fn eq(&self, other: &Self) -> bool {
56 match (&self.0, &other.0) {
57 (
58 McpResourceClientCacheKeyInner::Latest(left),
59 McpResourceClientCacheKeyInner::Latest(right),
60 ) => left.ptr_eq(right),
61 (
62 McpResourceClientCacheKeyInner::Exact(left),
63 McpResourceClientCacheKeyInner::Exact(right),
64 ) => left == right,
65 (
66 McpResourceClientCacheKeyInner::Latest(_),
67 McpResourceClientCacheKeyInner::Exact(_),
68 )
69 | (
70 McpResourceClientCacheKeyInner::Exact(_),
71 McpResourceClientCacheKeyInner::Latest(_),
72 ) => false,
73 }
74 }
75}
76
77impl Eq for McpResourceClientCacheKey {}
78
79impl std::fmt::Debug for McpResourceClient {
80 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 formatter
82 .debug_struct("McpResourceClient")
83 .finish_non_exhaustive()
84 }
85}
86
87impl McpResourceClient {
88 pub fn new(runtime: Arc<McpRuntime>) -> Self {
90 Self {
91 source: McpResourceSource::Latest(runtime),
92 }
93 }
94
95 pub(crate) fn for_binding(clients: Arc<McpBindingClients>) -> Self {
96 Self {
97 source: McpResourceSource::Exact(clients),
98 }
99 }
100
101 pub fn cache_key(&self) -> McpResourceClientCacheKey {
103 let key = match &self.source {
104 McpResourceSource::Latest(runtime) => {
105 McpResourceClientCacheKeyInner::Latest(Arc::downgrade(&runtime.snapshot()))
106 }
107 McpResourceSource::Exact(clients) => {
108 McpResourceClientCacheKeyInner::Exact(clients.identity())
109 }
110 };
111 McpResourceClientCacheKey(key)
112 }
113
114 pub async fn has_server(&self, server: &str) -> bool {
118 match &self.source {
119 McpResourceSource::Latest(runtime) => runtime.snapshot().contains_server(server),
120 McpResourceSource::Exact(clients) => clients.contains_server(server),
121 }
122 }
123
124 pub async fn list_resources(
126 &self,
127 server: &str,
128 cursor: Option<String>,
129 ) -> Result<McpResourcePage> {
130 let params =
131 cursor.map(|cursor| PaginatedRequestParams::default().with_cursor(Some(cursor)));
132 let result = match &self.source {
133 McpResourceSource::Latest(runtime) => {
134 runtime.snapshot().list_resources(server, params).await
135 }
136 McpResourceSource::Exact(clients) => clients.list_resources(server, params).await,
137 }?;
138 let resources = result
139 .resources
140 .into_iter()
141 .map(resource_from_rmcp)
142 .collect::<Result<Vec<_>>>()?;
143 Ok(McpResourcePage {
144 resources,
145 next_cursor: result.next_cursor,
146 })
147 }
148
149 pub async fn read_resource(&self, server: &str, uri: &str) -> Result<McpResourceReadResult> {
151 let params = ReadResourceRequestParams::new(uri.to_string());
152 let result = match &self.source {
153 McpResourceSource::Latest(runtime) => {
154 runtime.snapshot().read_resource(server, params).await
155 }
156 McpResourceSource::Exact(clients) => clients.read_resource(server, params).await,
157 }?;
158 let contents = result
159 .contents
160 .into_iter()
161 .map(resource_content_from_rmcp)
162 .collect::<Result<Vec<_>>>()?;
163 Ok(McpResourceReadResult { contents })
164 }
165}
166
167fn resource_from_rmcp(resource: rmcp::model::Resource) -> Result<Resource> {
168 let value = serde_json::to_value(resource).context("failed to serialize MCP resource")?;
169 Resource::from_mcp_value(value).context("failed to convert MCP resource")
170}
171
172fn resource_content_from_rmcp(content: rmcp::model::ResourceContents) -> Result<ResourceContent> {
173 let value =
174 serde_json::to_value(content).context("failed to serialize MCP resource content")?;
175 serde_json::from_value(value).context("failed to convert MCP resource content")
176}