use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, Instant};
use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
use tokio_util::sync::CancellationToken;
use dashmap::DashMap;
use tokio::sync::RwLock;
use tokio::sync::{mpsc, watch};
type StatusTx = mpsc::UnboundedSender<String>;
type ServerTrust =
Arc<tokio::sync::RwLock<HashMap<String, (McpTrustLevel, Option<Vec<String>>, Vec<String>)>>>;
use rmcp::transport::auth::CredentialStore;
use crate::client::{McpClient, ToolRefreshEvent};
use crate::elicitation::ElicitationEvent;
use crate::embedding_guard::EmbeddingAnomalyGuard;
use crate::policy::PolicyEnforcer;
use crate::prober::DefaultMcpProber;
use crate::tool::{McpTool, ToolSecurityMeta};
use crate::trust_score::TrustScoreStore;
fn default_elicitation_timeout() -> u64 {
120
}
pub(crate) use zeph_config::McpTrustLevel;
const MAX_INJECTION_PENALTIES_PER_REGISTRATION: usize = 3;
#[non_exhaustive]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum McpTransport {
Stdio {
command: String,
args: Vec<String>,
env: HashMap<String, String>,
},
Http {
url: String,
#[serde(default)]
headers: HashMap<String, String>,
},
OAuth {
url: String,
scopes: Vec<String>,
callback_port: u16,
client_name: String,
},
}
#[allow(deprecated)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ServerEntry {
pub id: String,
pub transport: McpTransport,
pub timeout: Duration,
#[serde(default)]
pub trust_level: McpTrustLevel,
#[serde(default)]
pub tool_allowlist: Option<Vec<String>>,
#[serde(default)]
pub expected_tools: Vec<String>,
#[serde(default)]
pub roots: Vec<rmcp::model::Root>,
#[serde(default)]
pub tool_metadata: HashMap<String, ToolSecurityMeta>,
#[serde(default)]
pub elicitation_enabled: bool,
#[serde(default = "default_elicitation_timeout")]
pub elicitation_timeout_secs: u64,
#[serde(default)]
pub env_isolation: bool,
}
#[derive(Debug, Clone, Copy)]
struct IngestLimits {
description_bytes: usize,
instructions_bytes: usize,
}
struct ConnectOutput {
client_entry: Option<(String, McpClient)>,
tools_entry: Option<(String, Vec<McpTool>)>,
tools: Vec<McpTool>,
outcome: ServerConnectOutcome,
instructions: Option<(String, String)>,
}
#[derive(Debug, Clone)]
pub struct ServerConnectOutcome {
pub id: String,
pub connected: bool,
pub tool_count: usize,
pub error: String,
}
pub struct McpManager {
configs: Vec<ServerEntry>,
allowed_commands: Vec<String>,
clients: Arc<RwLock<HashMap<String, McpClient>>>,
connected_server_ids: SyncRwLock<HashSet<String>>,
enforcer: Arc<PolicyEnforcer>,
suppress_stderr: bool,
server_tools: Arc<RwLock<HashMap<String, Vec<McpTool>>>>,
refresh_tx: SyncMutex<Option<mpsc::Sender<ToolRefreshEvent>>>,
refresh_rx: SyncMutex<Option<mpsc::Receiver<ToolRefreshEvent>>>,
tools_watch_tx: watch::Sender<Vec<McpTool>>,
last_refresh: Arc<DashMap<String, Instant>>,
oauth_credentials: HashMap<String, Arc<dyn CredentialStore>>,
status_tx: Option<StatusTx>,
server_trust: ServerTrust,
prober: Option<DefaultMcpProber>,
trust_store: Option<Arc<TrustScoreStore>>,
embedding_guard: Option<EmbeddingAnomalyGuard>,
server_tool_metadata: Arc<HashMap<String, HashMap<String, ToolSecurityMeta>>>,
max_description_bytes: usize,
max_instructions_bytes: usize,
server_instructions: Arc<RwLock<HashMap<String, String>>>,
elicitation_tx: SyncMutex<Option<mpsc::Sender<ElicitationEvent>>>,
elicitation_rx: SyncMutex<Option<mpsc::Receiver<ElicitationEvent>>>,
server_elicitation: HashMap<String, bool>,
server_elicitation_timeout: HashMap<String, u64>,
add_remove_lock: tokio::sync::Mutex<()>,
shutdown_token: CancellationToken,
max_connect_attempts: u8,
startup_retry_backoff_ms: u64,
tool_timeout_secs: Option<u64>,
lock_tool_list: bool,
tool_list_locked: Arc<DashMap<String, ()>>,
}
impl std::fmt::Debug for McpManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("McpManager")
.field("server_count", &self.configs.len())
.finish_non_exhaustive()
}
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
struct IngestConfig<'a> {
server_id: &'a str,
trust_level: McpTrustLevel,
allowlist: Option<&'a [String]>,
expected_tools: &'a [String],
status_tx: Option<&'a StatusTx>,
max_description_bytes: usize,
tool_metadata: &'a HashMap<String, ToolSecurityMeta>,
}
mod builder;
mod call;
mod connect;
mod ingest;
mod retry;
mod server;
#[cfg(test)]
mod tests;