context_server_tool.rs

  1use std::sync::Arc;
  2
  3use anyhow::{Result, anyhow, bail};
  4use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
  5use context_server::{ContextServerId, types};
  6use gpui::{AnyWindowHandle, App, Entity, Task};
  7use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
  8use project::{Project, context_server_store::ContextServerStore};
  9use ui::IconName;
 10
 11pub struct ContextServerTool {
 12    store: Entity<ContextServerStore>,
 13    server_id: ContextServerId,
 14    tool: types::Tool,
 15}
 16
 17impl ContextServerTool {
 18    pub fn new(
 19        store: Entity<ContextServerStore>,
 20        server_id: ContextServerId,
 21        tool: types::Tool,
 22    ) -> Self {
 23        Self {
 24            store,
 25            server_id,
 26            tool,
 27        }
 28    }
 29}
 30
 31impl Tool for ContextServerTool {
 32    fn name(&self) -> String {
 33        self.tool.name.clone()
 34    }
 35
 36    fn description(&self) -> String {
 37        self.tool.description.clone().unwrap_or_default()
 38    }
 39
 40    fn icon(&self) -> IconName {
 41        IconName::Cog
 42    }
 43
 44    fn source(&self) -> ToolSource {
 45        ToolSource::ContextServer {
 46            id: self.server_id.clone().0.into(),
 47        }
 48    }
 49
 50    fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
 51        true
 52    }
 53
 54    fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
 55        let mut schema = self.tool.input_schema.clone();
 56        assistant_tool::adapt_schema_to_format(&mut schema, format)?;
 57        Ok(match schema {
 58            serde_json::Value::Null => {
 59                serde_json::json!({ "type": "object", "properties": [] })
 60            }
 61            serde_json::Value::Object(map) if map.is_empty() => {
 62                serde_json::json!({ "type": "object", "properties": [] })
 63            }
 64            _ => schema,
 65        })
 66    }
 67
 68    fn ui_text(&self, _input: &serde_json::Value) -> String {
 69        format!("Run MCP tool `{}`", self.tool.name)
 70    }
 71
 72    fn run(
 73        self: Arc<Self>,
 74        input: serde_json::Value,
 75        _messages: &[LanguageModelRequestMessage],
 76        _project: Entity<Project>,
 77        _action_log: Entity<ActionLog>,
 78        _window: Option<AnyWindowHandle>,
 79        cx: &mut App,
 80    ) -> ToolResult {
 81        if let Some(server) = self.store.read(cx).get_running_server(&self.server_id) {
 82            let tool_name = self.tool.name.clone();
 83            let server_clone = server.clone();
 84            let input_clone = input.clone();
 85
 86            cx.spawn(async move |_cx| {
 87                let Some(protocol) = server_clone.client() else {
 88                    bail!("Context server not initialized");
 89                };
 90
 91                let arguments = if let serde_json::Value::Object(map) = input_clone {
 92                    Some(map.into_iter().collect())
 93                } else {
 94                    None
 95                };
 96
 97                log::trace!(
 98                    "Running tool: {} with arguments: {:?}",
 99                    tool_name,
100                    arguments
101                );
102                let response = protocol.run_tool(tool_name, arguments).await?;
103
104                let mut result = String::new();
105                for content in response.content {
106                    match content {
107                        types::ToolResponseContent::Text { text } => {
108                            result.push_str(&text);
109                        }
110                        types::ToolResponseContent::Image { .. } => {
111                            log::warn!("Ignoring image content from tool response");
112                        }
113                        types::ToolResponseContent::Resource { .. } => {
114                            log::warn!("Ignoring resource content from tool response");
115                        }
116                    }
117                }
118                Ok(result)
119            })
120            .into()
121        } else {
122            Task::ready(Err(anyhow!("Context server not found"))).into()
123        }
124    }
125}