Detailed changes
@@ -2253,6 +2253,23 @@
"ssh_connections": [],
// Whether to read ~/.ssh/config for ssh connection sources.
"read_ssh_config": true,
+ // Default timeout in milliseconds for all context server tool calls.
+ // Individual servers can override this in their configuration.
+ // Examples:
+ // "context_servers": {
+ // "my-stdio-server": {
+ // "command": "/path/to/server",
+ // "args": ["--flag"],
+ // "timeout": 120000 // Override: 2 minutes for this server
+ // },
+ // "my-http-server": {
+ // "url": "https://example.com/mcp",
+ // "headers": { "Authorization": "Bearer token" },
+ // "timeout": 90000 // Override: 90 seconds for this server
+ // }
+ // }
+ // Default: 60000 (60 seconds)
+ "context_server_timeout": 60000,
// Configures context servers for use by the agent.
"context_servers": {},
// Configures agent servers available in the agent panel.
@@ -286,6 +286,7 @@ impl AgentConnection for AcpConnection {
project::context_server_store::ContextServerConfiguration::Http {
url,
headers,
+ timeout: _,
} => Some(acp::McpServer::Http(
acp::McpServerHttp::new(id.0.to_string(), url.to_string()).headers(
headers
@@ -172,6 +172,7 @@ impl ConfigurationSource {
enabled: true,
url,
headers: auth,
+ timeout: None,
},
)
})
@@ -411,6 +412,7 @@ impl ConfigureContextServerModal {
enabled: _,
url,
headers,
+ timeout: _,
} => Some(ConfigurationTarget::ExistingHttp {
id: server_id,
url,
@@ -10,6 +10,7 @@ use collections::HashMap;
use http_client::HttpClient;
use std::path::Path;
use std::sync::Arc;
+use std::time::Duration;
use std::{fmt::Display, path::PathBuf};
use anyhow::Result;
@@ -39,6 +40,7 @@ pub struct ContextServer {
id: ContextServerId,
client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
configuration: ContextServerTransport,
+ request_timeout: Option<Duration>,
}
impl ContextServer {
@@ -54,6 +56,7 @@ impl ContextServer {
command,
working_directory.map(|directory| directory.to_path_buf()),
),
+ request_timeout: None, // Stdio handles timeout through command
}
}
@@ -63,6 +66,7 @@ impl ContextServer {
headers: HashMap<String, String>,
http_client: Arc<dyn HttpClient>,
executor: gpui::BackgroundExecutor,
+ request_timeout: Option<Duration>,
) -> Result<Self> {
let transport = match endpoint.scheme() {
"http" | "https" => {
@@ -73,14 +77,23 @@ impl ContextServer {
}
_ => anyhow::bail!("unsupported MCP url scheme {}", endpoint.scheme()),
};
- Ok(Self::new(id, transport))
+ Ok(Self::new_with_timeout(id, transport, request_timeout))
}
pub fn new(id: ContextServerId, transport: Arc<dyn crate::transport::Transport>) -> Self {
+ Self::new_with_timeout(id, transport, None)
+ }
+
+ pub fn new_with_timeout(
+ id: ContextServerId,
+ transport: Arc<dyn crate::transport::Transport>,
+ request_timeout: Option<Duration>,
+ ) -> Self {
Self {
id,
client: RwLock::new(None),
configuration: ContextServerTransport::Custom(transport),
+ request_timeout,
}
}
@@ -113,7 +126,7 @@ impl ContextServer {
client::ContextServerId(self.id.0.clone()),
self.id().0,
transport.clone(),
- None,
+ self.request_timeout,
cx.clone(),
)?,
})
@@ -2,6 +2,7 @@ pub mod extension;
pub mod registry;
use std::sync::Arc;
+use std::time::Duration;
use anyhow::{Context as _, Result};
use collections::{HashMap, HashSet};
@@ -102,6 +103,7 @@ pub enum ContextServerConfiguration {
Http {
url: url::Url,
headers: HashMap<String, String>,
+ timeout: Option<u64>,
},
}
@@ -151,9 +153,14 @@ impl ContextServerConfiguration {
enabled: _,
url,
headers: auth,
+ timeout,
} => {
let url = url::Url::parse(&url).log_err()?;
- Some(ContextServerConfiguration::Http { url, headers: auth })
+ Some(ContextServerConfiguration::Http {
+ url,
+ headers: auth,
+ timeout,
+ })
}
}
}
@@ -482,18 +489,31 @@ impl ContextServerStore {
configuration: Arc<ContextServerConfiguration>,
cx: &mut Context<Self>,
) -> Result<Arc<ContextServer>> {
+ // Get global timeout from settings
+ let global_timeout = ProjectSettings::get_global(cx).context_server_timeout;
+
if let Some(factory) = self.context_server_factory.as_ref() {
return Ok(factory(id, configuration));
}
match configuration.as_ref() {
- ContextServerConfiguration::Http { url, headers } => Ok(Arc::new(ContextServer::http(
- id,
+ ContextServerConfiguration::Http {
url,
- headers.clone(),
- cx.http_client(),
- cx.background_executor().clone(),
- )?)),
+ headers,
+ timeout,
+ } => {
+ // Apply timeout precedence for HTTP servers: per-server > global
+ let resolved_timeout = timeout.unwrap_or(global_timeout);
+
+ Ok(Arc::new(ContextServer::http(
+ id,
+ url,
+ headers.clone(),
+ cx.http_client(),
+ cx.background_executor().clone(),
+ Some(Duration::from_millis(resolved_timeout)),
+ )?))
+ }
_ => {
let root_path = self
.project
@@ -511,9 +531,16 @@ impl ContextServerStore {
})
})
});
+
+ // Apply timeout precedence for stdio servers: per-server > global
+ let mut command_with_timeout = configuration.command().unwrap().clone();
+ if command_with_timeout.timeout.is_none() {
+ command_with_timeout.timeout = Some(global_timeout);
+ }
+
Ok(Arc::new(ContextServer::stdio(
id,
- configuration.command().unwrap().clone(),
+ command_with_timeout,
root_path,
)))
}
@@ -1257,6 +1284,7 @@ mod tests {
enabled: true,
url: server_url.to_string(),
headers: Default::default(),
+ timeout: None,
},
)],
)
@@ -59,6 +59,9 @@ pub struct ProjectSettings {
/// Settings for context servers used for AI-related features.
pub context_servers: HashMap<Arc<str>, ContextServerSettings>,
+ /// Default timeout for context server requests in milliseconds.
+ pub context_server_timeout: u64,
+
/// Configuration for Diagnostics-related features.
pub diagnostics: DiagnosticsSettings,
@@ -141,6 +144,8 @@ pub enum ContextServerSettings {
/// Optional authentication configuration for the remote server.
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
headers: HashMap<String, String>,
+ /// Timeout for tool calls in milliseconds.
+ timeout: Option<u64>,
},
Extension {
/// Whether the context server is enabled.
@@ -167,10 +172,12 @@ impl From<settings::ContextServerSettingsContent> for ContextServerSettings {
enabled,
url,
headers,
+ timeout,
} => ContextServerSettings::Http {
enabled,
url,
headers,
+ timeout,
},
}
}
@@ -188,10 +195,12 @@ impl Into<settings::ContextServerSettingsContent> for ContextServerSettings {
enabled,
url,
headers,
+ timeout,
} => settings::ContextServerSettingsContent::Http {
enabled,
url,
headers,
+ timeout,
},
}
}
@@ -560,6 +569,7 @@ impl Settings for ProjectSettings {
.into_iter()
.map(|(key, value)| (key, value.into()))
.collect(),
+ context_server_timeout: project.context_server_timeout.unwrap_or(60000),
lsp: project
.lsp
.clone()
@@ -41,6 +41,12 @@ pub struct ProjectSettingsContent {
#[serde(default)]
pub context_servers: HashMap<Arc<str>, ContextServerSettingsContent>,
+ /// Default timeout in milliseconds for context server tool calls.
+ /// Can be overridden per-server in context_servers configuration.
+ ///
+ /// Default: 60000 (60 seconds)
+ pub context_server_timeout: Option<u64>,
+
/// Configuration for how direnv configuration should be loaded
pub load_direnv: Option<DirenvSettings>,
@@ -215,6 +221,8 @@ pub enum ContextServerSettingsContent {
/// Optional headers to send.
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
headers: HashMap<String, String>,
+ /// Timeout for tool calls in milliseconds. Defaults to global context_server_timeout if not specified.
+ timeout: Option<u64>,
},
Extension {
/// Whether the context server is enabled.
@@ -402,6 +402,7 @@ impl VsCodeSettings {
terminal: None,
dap: Default::default(),
context_servers: self.context_servers(),
+ context_server_timeout: None,
load_direnv: None,
slash_commands: None,
git_hosting_providers: None,