feat: MCP support for agentic CLI providers (#6972)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Adrian Cole
2026-02-12 07:37:52 +08:00
committed by GitHub
parent c6e27b8f84
commit 8d59c2dced
57 changed files with 1196 additions and 254 deletions
@@ -1,7 +1,9 @@
// Extension data management for sessions
// Provides a simple way to store extension-specific data with versioned keys
use crate::config::base::Config;
use crate::config::ExtensionConfig;
use crate::session::SessionManager;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -111,12 +113,81 @@ impl EnabledExtensionsState {
pub fn new(extensions: Vec<ExtensionConfig>) -> Self {
Self { extensions }
}
pub fn extensions_or_default(
extension_data: Option<&ExtensionData>,
config: &Config,
) -> Vec<ExtensionConfig> {
extension_data
.and_then(Self::from_extension_data)
.map(|state| state.extensions)
.unwrap_or_else(|| {
crate::config::extensions::get_enabled_extensions_with_config(config)
})
}
pub async fn for_session(
session_manager: &SessionManager,
session_id: &str,
config: &Config,
) -> Vec<ExtensionConfig> {
let session = session_manager.get_session(session_id, false).await.ok();
Self::extensions_or_default(session.as_ref().map(|s| &s.extension_data), config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use tempfile::NamedTempFile;
use test_case::test_case;
fn test_config() -> Config {
let config_file = NamedTempFile::new().unwrap();
let secrets_file = NamedTempFile::new().unwrap();
Config::new_with_file_secrets(config_file.path(), secrets_file.path()).unwrap()
}
fn test_extension() -> ExtensionConfig {
ExtensionConfig::Builtin {
name: "developer".into(),
description: "dev".into(),
display_name: None,
timeout: None,
bundled: None,
available_tools: vec![],
}
}
fn extension_data_with(extensions: Vec<ExtensionConfig>) -> ExtensionData {
let mut data = ExtensionData::new();
EnabledExtensionsState::new(extensions)
.to_extension_data(&mut data)
.unwrap();
data
}
#[test_case(
Some(extension_data_with(vec![test_extension()])),
Some(vec![test_extension()])
; "prefers_session_data"
)]
#[test_case(None, None ; "no_session_falls_back_to_config")]
#[test_case(Some(ExtensionData::default()), None ; "empty_session_data_falls_back_to_config")]
fn test_extensions_or_default(
extension_data: Option<ExtensionData>,
expected: Option<Vec<ExtensionConfig>>,
) {
let config = test_config();
let expected = expected.unwrap_or_else(|| {
crate::config::extensions::get_enabled_extensions_with_config(&config)
});
assert_eq!(
EnabledExtensionsState::extensions_or_default(extension_data.as_ref(), &config),
expected,
);
}
#[test]
fn test_extension_data_basic_operations() {