feat: Secret injection into the MCP configuration
This commit is contained in:
+3
-4
@@ -9,8 +9,7 @@ use anyhow::{Context, Result};
|
||||
use inquire::{validator::Validation, Text};
|
||||
use rust_embed::Embed;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ffi::OsStr;
|
||||
use std::{fs, fs::read_to_string, path::Path};
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
const DEFAULT_AGENT_NAME: &str = "rag";
|
||||
|
||||
@@ -68,7 +67,7 @@ impl Agent {
|
||||
|
||||
#[cfg(unix)]
|
||||
if is_script {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{fs, os::unix::fs::PermissionsExt};
|
||||
fs::set_permissions(&file_path, fs::Permissions::from_mode(0o755))?;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +103,7 @@ impl Agent {
|
||||
McpRegistry::reinit(registry, mcp_servers, abort_signal.clone()).await?;
|
||||
|
||||
if !new_mcp_registry.is_empty() {
|
||||
functions.append_mcp_meta_functions(new_mcp_registry.list_servers());
|
||||
functions.append_mcp_meta_functions(new_mcp_registry.list_started_servers());
|
||||
}
|
||||
|
||||
config.write().mcp_registry = Some(new_mcp_registry);
|
||||
|
||||
+23
-25
@@ -723,7 +723,7 @@ impl Config {
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn update(config: &GlobalConfig, data: &str) -> Result<()> {
|
||||
pub async fn update(config: &GlobalConfig, data: &str, abort_signal: AbortSignal) -> Result<()> {
|
||||
let parts: Vec<&str> = data.split_whitespace().collect();
|
||||
if parts.len() != 2 {
|
||||
bail!("Usage: .set <key> <value>. If value is null, unset key.");
|
||||
@@ -745,7 +745,21 @@ impl Config {
|
||||
}
|
||||
"use_mcp_servers" => {
|
||||
let value = parse_value(value)?;
|
||||
config.write().set_use_mcp_servers(value);
|
||||
config.write().set_use_mcp_servers(value.clone());
|
||||
config.write().functions.clear_mcp_meta_functions();
|
||||
let registry = config.write()
|
||||
.mcp_registry
|
||||
.take()
|
||||
.expect("MCP registry should be initialized");
|
||||
let new_mcp_registry =
|
||||
McpRegistry::reinit(registry, value, abort_signal.clone()).await?;
|
||||
|
||||
if !new_mcp_registry.is_empty() {
|
||||
config.write().functions
|
||||
.append_mcp_meta_functions(new_mcp_registry.list_started_servers());
|
||||
}
|
||||
|
||||
config.write().mcp_registry = Some(new_mcp_registry);
|
||||
}
|
||||
"max_output_tokens" => {
|
||||
let value = parse_value(value)?;
|
||||
@@ -1019,7 +1033,7 @@ impl Config {
|
||||
|
||||
if !new_mcp_registry.is_empty() {
|
||||
self.functions
|
||||
.append_mcp_meta_functions(new_mcp_registry.list_servers());
|
||||
.append_mcp_meta_functions(new_mcp_registry.list_started_servers());
|
||||
}
|
||||
|
||||
self.mcp_registry = Some(new_mcp_registry);
|
||||
@@ -2105,29 +2119,12 @@ impl Config {
|
||||
if prefix.is_empty() {
|
||||
values.push("all".to_string());
|
||||
}
|
||||
if let Some(registry) = &self.mcp_registry {
|
||||
values.extend(
|
||||
self.functions
|
||||
.declarations()
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
v.name.starts_with(MCP_INVOKE_META_FUNCTION_NAME_PREFIX)
|
||||
|| v.name.starts_with(MCP_LIST_META_FUNCTION_NAME_PREFIX)
|
||||
})
|
||||
.map(|v| {
|
||||
v.name
|
||||
.strip_prefix(
|
||||
format!("{MCP_LIST_META_FUNCTION_NAME_PREFIX}_").as_str(),
|
||||
)
|
||||
.or_else(|| {
|
||||
v.name.strip_prefix(
|
||||
format!("{MCP_INVOKE_META_FUNCTION_NAME_PREFIX}_")
|
||||
.as_str(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}),
|
||||
registry
|
||||
.list_configured_servers()
|
||||
);
|
||||
}
|
||||
values.extend(self.mapping_mcp_servers.keys().map(|v| v.to_string()));
|
||||
values
|
||||
.into_iter()
|
||||
@@ -2698,12 +2695,13 @@ impl Config {
|
||||
start_mcp_servers,
|
||||
self.use_mcp_servers.clone(),
|
||||
abort_signal.clone(),
|
||||
self,
|
||||
)
|
||||
.await?;
|
||||
match mcp_registry.is_empty() {
|
||||
false => {
|
||||
self.functions
|
||||
.append_mcp_meta_functions(mcp_registry.list_servers());
|
||||
.append_mcp_meta_functions(mcp_registry.list_started_servers());
|
||||
}
|
||||
_ => debug!(
|
||||
"Skipping global MCP functions registration since start_mcp_servers was 'false'"
|
||||
|
||||
+32
-6
@@ -14,8 +14,9 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc};
|
||||
use tokio::process::Command;
|
||||
use crate::vault::SECRET_RE;
|
||||
|
||||
pub const MCP_INVOKE_META_FUNCTION_NAME_PREFIX: &str = "mcp_invoke";
|
||||
pub const MCP_LIST_META_FUNCTION_NAME_PREFIX: &str = "mcp_list";
|
||||
@@ -57,6 +58,7 @@ impl McpRegistry {
|
||||
start_mcp_servers: bool,
|
||||
use_mcp_servers: Option<String>,
|
||||
abort_signal: AbortSignal,
|
||||
config: &Config,
|
||||
) -> Result<Self> {
|
||||
let mut registry = Self {
|
||||
log_path,
|
||||
@@ -83,7 +85,24 @@ impl McpRegistry {
|
||||
let content = tokio::fs::read_to_string(Config::mcp_config_file())
|
||||
.await
|
||||
.with_context(err)?;
|
||||
let config: McpServersConfig = serde_json::from_str(&content).with_context(err)?;
|
||||
let mut missing_secrets = vec![];
|
||||
let parsed_content = SECRET_RE.replace_all(&content, |caps: &fancy_regex::Captures<'_>| {
|
||||
let secret = config.vault
|
||||
.get_secret(&caps[1], false);
|
||||
match secret {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
missing_secrets.push(caps[1].to_string());
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !missing_secrets.is_empty() {
|
||||
return Err(anyhow!("MCP config file references secrets that are missing from the vault: {:?}", missing_secrets));
|
||||
}
|
||||
|
||||
let config: McpServersConfig = serde_json::from_str(&parsed_content).with_context(err)?;
|
||||
registry.config = Some(config);
|
||||
|
||||
if start_mcp_servers {
|
||||
@@ -124,13 +143,12 @@ impl McpRegistry {
|
||||
|
||||
async fn start_select_mcp_servers(&mut self, use_mcp_servers: Option<String>) -> Result<()> {
|
||||
if self.config.is_none() {
|
||||
debug!("MCP config is not present; assuming MCP servers are disabled globally. skipping MCP initialization");
|
||||
debug!("MCP config is not present; assuming MCP servers are disabled globally. Skipping MCP initialization");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("Starting selected MCP servers: {:?}", use_mcp_servers);
|
||||
|
||||
if let Some(servers) = use_mcp_servers {
|
||||
debug!("Starting selected MCP servers: {:?}", servers);
|
||||
let config = self
|
||||
.config
|
||||
.as_ref()
|
||||
@@ -231,10 +249,18 @@ impl McpRegistry {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn list_servers(&self) -> Vec<String> {
|
||||
pub fn list_started_servers(&self) -> Vec<String> {
|
||||
self.servers.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn list_configured_servers(&self) -> Vec<String> {
|
||||
if let Some(config) = &self.config {
|
||||
config.mcp_servers.keys().cloned().collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn catalog(&self) -> BoxFuture<'static, Result<Value>> {
|
||||
let servers: Vec<(String, Arc<ConnectedServer>)> = self
|
||||
.servers
|
||||
|
||||
+6
-6
@@ -184,12 +184,12 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 37]> = LazyLock::new(|| {
|
||||
"Delete roles, sessions, RAGs, or agents",
|
||||
AssertState::pass(),
|
||||
),
|
||||
ReplCommand::new(".exit", "Exit REPL", AssertState::pass()),
|
||||
ReplCommand::new(
|
||||
".vault",
|
||||
"View or modify the Loki vault",
|
||||
AssertState::pass(),
|
||||
),
|
||||
ReplCommand::new(".exit", "Exit REPL", AssertState::pass()),
|
||||
]
|
||||
});
|
||||
static COMMAND_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\s*(\.\S*)\s*").unwrap());
|
||||
@@ -663,7 +663,7 @@ pub async fn run_repl_command(
|
||||
}
|
||||
".set" => match args {
|
||||
Some(args) => {
|
||||
Config::update(config, args)?;
|
||||
Config::update(config, args, abort_signal).await?;
|
||||
}
|
||||
_ => {
|
||||
println!("Usage: .set <key> <value>...")
|
||||
@@ -708,7 +708,7 @@ pub async fn run_repl_command(
|
||||
config
|
||||
.write()
|
||||
.functions
|
||||
.append_mcp_meta_functions(registry.list_servers());
|
||||
.append_mcp_meta_functions(registry.list_started_servers());
|
||||
}
|
||||
config.write().mcp_registry = Some(registry);
|
||||
}
|
||||
@@ -730,7 +730,7 @@ pub async fn run_repl_command(
|
||||
config
|
||||
.write()
|
||||
.functions
|
||||
.append_mcp_meta_functions(registry.list_servers());
|
||||
.append_mcp_meta_functions(registry.list_started_servers());
|
||||
}
|
||||
config.write().mcp_registry = Some(registry);
|
||||
} else {
|
||||
@@ -757,7 +757,7 @@ pub async fn run_repl_command(
|
||||
config
|
||||
.write()
|
||||
.functions
|
||||
.append_mcp_meta_functions(registry.list_servers());
|
||||
.append_mcp_meta_functions(registry.list_started_servers());
|
||||
}
|
||||
config.write().mcp_registry = Some(registry);
|
||||
}
|
||||
@@ -782,7 +782,7 @@ pub async fn run_repl_command(
|
||||
}
|
||||
Some(("get", name)) => {
|
||||
if let Some(name) = name {
|
||||
config.read().vault.get_secret(name)?;
|
||||
config.read().vault.get_secret(name, true)?;
|
||||
} else {
|
||||
println!("Usage: .vault get <name>");
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,14 +1,18 @@
|
||||
mod utils;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
use crate::cli::Cli;
|
||||
use crate::config::Config;
|
||||
use crate::vault::utils::ensure_password_file_initialized;
|
||||
use anyhow::{Context, Result};
|
||||
use fancy_regex::Regex;
|
||||
use gman::providers::local::LocalProvider;
|
||||
use gman::providers::SecretProvider;
|
||||
use inquire::{required, Password, PasswordDisplayMode};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
pub static SECRET_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap());
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Vault {
|
||||
local_provider: LocalProvider,
|
||||
@@ -45,14 +49,17 @@ impl Vault {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_secret(&self, secret_name: &str) -> Result<()> {
|
||||
pub fn get_secret(&self, secret_name: &str, display_output: bool) -> Result<String> {
|
||||
let h = Handle::current();
|
||||
let secret = tokio::task::block_in_place(|| {
|
||||
h.block_on(self.local_provider.get_secret(secret_name))
|
||||
})?;
|
||||
println!("{}", secret);
|
||||
|
||||
Ok(())
|
||||
if display_output {
|
||||
println!("{}", secret);
|
||||
}
|
||||
|
||||
Ok(secret)
|
||||
}
|
||||
|
||||
pub fn update_secret(&self, secret_name: &str) -> Result<()> {
|
||||
@@ -105,7 +112,7 @@ impl Vault {
|
||||
}
|
||||
|
||||
if let Some(secret_name) = cli.get_secret {
|
||||
config.vault.get_secret(&secret_name)?;
|
||||
config.vault.get_secret(&secret_name, true)?;
|
||||
}
|
||||
|
||||
if let Some(secret_name) = cli.update_secret {
|
||||
|
||||
Reference in New Issue
Block a user