1use crate::agents::extension::PlatformExtensionContext;
2use crate::agents::mcp_client::{Error, McpClientTrait};
3use crate::config::get_extension_by_name;
4use anyhow::Result;
5use async_trait::async_trait;
6use indoc::indoc;
7use rmcp::model::{
8 CallToolResult, Content, ErrorCode, ErrorData, GetPromptResult, Implementation,
9 InitializeResult, JsonObject, ListPromptsResult, ListResourcesResult, ListToolsResult,
10 ProtocolVersion, ReadResourceResult, ServerCapabilities, ServerNotification, Tool,
11 ToolAnnotations, ToolsCapability,
12};
13use schemars::{schema_for, JsonSchema};
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::sync::Arc;
17use tokio::sync::mpsc;
18use tokio_util::sync::CancellationToken;
19use tracing::error;
20
21pub static EXTENSION_NAME: &str = "Extension Manager";
22#[derive(Debug, thiserror::Error)]
25pub enum ExtensionManagerToolError {
26 #[error("Unknown tool: {tool_name}")]
27 UnknownTool { tool_name: String },
28
29 #[error("Extension manager not available")]
30 ManagerUnavailable,
31
32 #[error("Missing required parameter: {param_name}")]
33 MissingParameter { param_name: String },
34
35 #[error("Invalid action: {action}. Must be 'enable' or 'disable'")]
36 InvalidAction { action: String },
37
38 #[error("Extension operation failed: {message}")]
39 OperationFailed { message: String },
40
41 #[error("Failed to deserialize parameters: {0}")]
42 DeserializationError(#[from] serde_json::Error),
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
46#[serde(rename_all = "lowercase")]
47pub enum ManageExtensionAction {
48 Enable,
49 Disable,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
53pub struct ManageExtensionsParams {
54 pub action: ManageExtensionAction,
55 pub extension_name: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
59pub struct ReadResourceParams {
60 pub uri: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub extension_name: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
66pub struct ListResourcesParams {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub extension_name: Option<String>,
69}
70
71pub const READ_RESOURCE_TOOL_NAME: &str = "read_resource";
72pub const LIST_RESOURCES_TOOL_NAME: &str = "list_resources";
73pub const SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME: &str = "search_available_extensions";
74pub const MANAGE_EXTENSIONS_TOOL_NAME: &str = "manage_extensions";
75pub const MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE: &str = "extensionmanager__manage_extensions";
76
77pub struct ExtensionManagerClient {
78 info: InitializeResult,
79 #[allow(dead_code)]
80 context: PlatformExtensionContext,
81}
82
83impl ExtensionManagerClient {
84 pub fn new(context: PlatformExtensionContext) -> Result<Self> {
85 let info = InitializeResult {
86 protocol_version: ProtocolVersion::V_2025_03_26,
87 capabilities: ServerCapabilities {
88 tools: Some(ToolsCapability {
89 list_changed: Some(false),
90 }),
91 resources: None,
92 prompts: None,
93 completions: None,
94 experimental: None,
95 logging: None,
96 },
97 server_info: Implementation {
98 name: EXTENSION_NAME.to_string(),
99 title: Some(EXTENSION_NAME.to_string()),
100 version: "1.0.0".to_string(),
101 icons: None,
102 website_url: None,
103 },
104 instructions: Some(indoc! {r#"
105 Extension Management
106
107 Use these tools to discover, enable, and disable extensions, as well as review resources.
108
109 Available tools:
110 - search_available_extensions: Find extensions available to enable/disable
111 - manage_extensions: Enable or disable extensions
112 - list_resources: List resources from extensions
113 - read_resource: Read specific resources from extensions
114
115 Use search_available_extensions when you need to find what extensions are available.
116 Use manage_extensions to enable or disable specific extensions by name.
117 Use list_resources and read_resource to work with extension data and resources.
118 "#}.to_string()),
119 };
120
121 Ok(Self { info, context })
122 }
123
124 async fn handle_search_available_extensions(
125 &self,
126 ) -> Result<Vec<Content>, ExtensionManagerToolError> {
127 if let Some(weak_ref) = &self.context.extension_manager {
128 if let Some(extension_manager) = weak_ref.upgrade() {
129 match extension_manager.search_available_extensions().await {
130 Ok(content) => Ok(content),
131 Err(e) => Err(ExtensionManagerToolError::OperationFailed {
132 message: format!("Failed to search available extensions: {}", e.message),
133 }),
134 }
135 } else {
136 Err(ExtensionManagerToolError::ManagerUnavailable)
137 }
138 } else {
139 Err(ExtensionManagerToolError::ManagerUnavailable)
140 }
141 }
142
143 async fn handle_manage_extensions(
144 &self,
145 arguments: Option<JsonObject>,
146 ) -> Result<Vec<Content>, ExtensionManagerToolError> {
147 let arguments = arguments.ok_or(ExtensionManagerToolError::MissingParameter {
148 param_name: "arguments".to_string(),
149 })?;
150
151 let params: ManageExtensionsParams =
152 serde_json::from_value(serde_json::Value::Object(arguments))?;
153
154 match self
155 .manage_extensions_impl(params.action, params.extension_name)
156 .await
157 {
158 Ok(content) => Ok(content),
159 Err(error_data) => Err(ExtensionManagerToolError::OperationFailed {
160 message: error_data.message.to_string(),
161 }),
162 }
163 }
164
165 async fn manage_extensions_impl(
166 &self,
167 action: ManageExtensionAction,
168 extension_name: String,
169 ) -> Result<Vec<Content>, ErrorData> {
170 let extension_manager = self
171 .context
172 .extension_manager
173 .as_ref()
174 .and_then(|weak| weak.upgrade())
175 .ok_or_else(|| {
176 ErrorData::new(
177 ErrorCode::INTERNAL_ERROR,
178 "Extension manager is no longer available".to_string(),
179 None,
180 )
181 })?;
182
183 if action == ManageExtensionAction::Disable {
184 return extension_manager
185 .remove_extension(&extension_name)
186 .await
187 .map(|_| {
188 vec![Content::text(format!(
189 "The extension '{}' has been disabled successfully",
190 extension_name
191 ))]
192 })
193 .map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
194 }
195
196 let config = match get_extension_by_name(&extension_name) {
197 Some(config) => config,
198 None => {
199 return Err(ErrorData::new(
200 ErrorCode::RESOURCE_NOT_FOUND,
201 format!(
202 "Extension '{}' not found. Please check the extension name and try again.",
203 extension_name
204 ),
205 None,
206 ));
207 }
208 };
209
210 extension_manager
211 .add_extension(config)
212 .await
213 .map(|_| {
214 vec![Content::text(format!(
215 "The extension '{}' has been installed successfully",
216 extension_name
217 ))]
218 })
219 .map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None))
220 }
221
222 async fn handle_list_resources(
223 &self,
224 arguments: Option<JsonObject>,
225 ) -> Result<Vec<Content>, ExtensionManagerToolError> {
226 if let Some(weak_ref) = &self.context.extension_manager {
227 if let Some(extension_manager) = weak_ref.upgrade() {
228 let params = arguments
229 .map(serde_json::Value::Object)
230 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
231
232 match extension_manager
233 .list_resources(params, tokio_util::sync::CancellationToken::default())
234 .await
235 {
236 Ok(content) => Ok(content),
237 Err(e) => Err(ExtensionManagerToolError::OperationFailed {
238 message: format!("Failed to list resources: {}", e.message),
239 }),
240 }
241 } else {
242 Err(ExtensionManagerToolError::ManagerUnavailable)
243 }
244 } else {
245 Err(ExtensionManagerToolError::ManagerUnavailable)
246 }
247 }
248
249 async fn handle_read_resource(
250 &self,
251 arguments: Option<JsonObject>,
252 ) -> Result<Vec<Content>, ExtensionManagerToolError> {
253 if let Some(weak_ref) = &self.context.extension_manager {
254 if let Some(extension_manager) = weak_ref.upgrade() {
255 let params = arguments
256 .map(serde_json::Value::Object)
257 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
258
259 match extension_manager
260 .read_resource_tool(params, tokio_util::sync::CancellationToken::default())
261 .await
262 {
263 Ok(content) => Ok(content),
264 Err(e) => Err(ExtensionManagerToolError::OperationFailed {
265 message: format!("Failed to read resource: {}", e.message),
266 }),
267 }
268 } else {
269 Err(ExtensionManagerToolError::ManagerUnavailable)
270 }
271 } else {
272 Err(ExtensionManagerToolError::ManagerUnavailable)
273 }
274 }
275
276 #[allow(clippy::too_many_lines)]
277 async fn get_tools(&self) -> Vec<Tool> {
278 let mut tools = vec![
279 Tool::new(
280 SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME.to_string(),
281 "Searches for additional extensions available to help complete tasks.
282 Use this tool when you're unable to find a specific feature or functionality you need to complete your task, or when standard approaches aren't working.
283 These extensions might provide the exact tools needed to solve your problem.
284 If you find a relevant one, consider using your tools to enable it.".to_string(),
285 Arc::new(
286 serde_json::json!({
287 "type": "object",
288 "required": [],
289 "properties": {}
290 })
291 .as_object()
292 .expect("Schema must be an object")
293 .clone()
294 ),
295 ).annotate(ToolAnnotations {
296 title: Some("Discover extensions".to_string()),
297 read_only_hint: Some(true),
298 destructive_hint: Some(false),
299 idempotent_hint: Some(false),
300 open_world_hint: Some(false),
301 }),
302 Tool::new(
303 MANAGE_EXTENSIONS_TOOL_NAME.to_string(),
304 "Tool to manage extensions and tools in aster context.
305 Enable or disable extensions to help complete tasks.
306 Enable or disable an extension by providing the extension name.
307 ".to_string(),
308 Arc::new(
309 serde_json::to_value(schema_for!(ManageExtensionsParams))
310 .expect("Failed to serialize schema")
311 .as_object()
312 .expect("Schema must be an object")
313 .clone()
314 ),
315 ).annotate(ToolAnnotations {
316 title: Some("Enable or disable an extension".to_string()),
317 read_only_hint: Some(false),
318 destructive_hint: Some(false),
319 idempotent_hint: Some(false),
320 open_world_hint: Some(false),
321 }),
322 ];
323
324 if let Some(weak_ref) = &self.context.extension_manager {
326 if let Some(extension_manager) = weak_ref.upgrade() {
327 if extension_manager.supports_resources().await {
328 tools.extend([
329 Tool::new(
330 LIST_RESOURCES_TOOL_NAME.to_string(),
331 indoc! {r#"
332 List resources from an extension(s).
333
334 Resources allow extensions to share data that provide context to LLMs, such as
335 files, database schemas, or application-specific information. This tool lists resources
336 in the provided extension, and returns a list for the user to browse. If no extension
337 is provided, the tool will search all extensions for the resource.
338 "#}.to_string(),
339 Arc::new(
340 serde_json::to_value(schema_for!(ListResourcesParams))
341 .expect("Failed to serialize schema")
342 .as_object()
343 .expect("Schema must be an object")
344 .clone()
345 ),
346 ).annotate(ToolAnnotations {
347 title: Some("List resources".to_string()),
348 read_only_hint: Some(true),
349 destructive_hint: Some(false),
350 idempotent_hint: Some(false),
351 open_world_hint: Some(false),
352 }),
353 Tool::new(
354 READ_RESOURCE_TOOL_NAME.to_string(),
355 indoc! {r#"
356 Read a resource from an extension.
357
358 Resources allow extensions to share data that provide context to LLMs, such as
359 files, database schemas, or application-specific information. This tool searches for the
360 resource URI in the provided extension, and reads in the resource content. If no extension
361 is provided, the tool will search all extensions for the resource.
362 "#}.to_string(),
363 Arc::new(
364 serde_json::to_value(schema_for!(ReadResourceParams))
365 .expect("Failed to serialize schema")
366 .as_object()
367 .expect("Schema must be an object")
368 .clone()
369 ),
370 ).annotate(ToolAnnotations {
371 title: Some("Read a resource".to_string()),
372 read_only_hint: Some(true),
373 destructive_hint: Some(false),
374 idempotent_hint: Some(false),
375 open_world_hint: Some(false),
376 }),
377 ]);
378 }
379 }
380 }
381
382 tools
383 }
384}
385
386#[async_trait]
387impl McpClientTrait for ExtensionManagerClient {
388 async fn list_resources(
389 &self,
390 _next_cursor: Option<String>,
391 _cancellation_token: CancellationToken,
392 ) -> Result<ListResourcesResult, Error> {
393 Err(Error::TransportClosed)
394 }
395
396 async fn read_resource(
397 &self,
398 _uri: &str,
399 _cancellation_token: CancellationToken,
400 ) -> Result<ReadResourceResult, Error> {
401 Err(Error::TransportClosed)
403 }
404
405 async fn list_tools(
406 &self,
407 _next_cursor: Option<String>,
408 _cancellation_token: CancellationToken,
409 ) -> Result<ListToolsResult, Error> {
410 Ok(ListToolsResult {
411 tools: self.get_tools().await,
412 next_cursor: None,
413 meta: None,
414 })
415 }
416
417 async fn call_tool(
418 &self,
419 name: &str,
420 arguments: Option<JsonObject>,
421 _cancellation_token: CancellationToken,
422 ) -> Result<CallToolResult, Error> {
423 let result = match name {
424 SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME => {
425 self.handle_search_available_extensions().await
426 }
427 MANAGE_EXTENSIONS_TOOL_NAME => self.handle_manage_extensions(arguments).await,
428 LIST_RESOURCES_TOOL_NAME => self.handle_list_resources(arguments).await,
429 READ_RESOURCE_TOOL_NAME => self.handle_read_resource(arguments).await,
430 _ => Err(ExtensionManagerToolError::UnknownTool {
431 tool_name: name.to_string(),
432 }),
433 };
434
435 match result {
436 Ok(content) => Ok(CallToolResult::success(content)),
437 Err(error) => {
438 error!("Extension manager tool '{}' failed: {}", name, error);
440
441 Ok(CallToolResult {
443 content: vec![Content::text(error.to_string())],
444 is_error: Some(true), structured_content: None,
446 meta: None,
447 })
448 }
449 }
450 }
451
452 async fn list_prompts(
453 &self,
454 _next_cursor: Option<String>,
455 _cancellation_token: CancellationToken,
456 ) -> Result<ListPromptsResult, Error> {
457 Err(Error::TransportClosed)
458 }
459
460 async fn get_prompt(
461 &self,
462 _name: &str,
463 _arguments: Value,
464 _cancellation_token: CancellationToken,
465 ) -> Result<GetPromptResult, Error> {
466 Err(Error::TransportClosed)
467 }
468
469 async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
470 mpsc::channel(1).1
471 }
472
473 fn get_info(&self) -> Option<&InitializeResult> {
474 Some(&self.info)
475 }
476}