mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
feat: env and secrets configuration for mcp server (#565)
This commit is contained in:
@@ -88,12 +88,16 @@ impl Capabilities {
|
||||
// TODO IMPORTANT need to ensure this times out if the system command is broken!
|
||||
pub async fn add_system(&mut self, config: SystemConfig) -> SystemResult<()> {
|
||||
let mut client: McpClient = match config {
|
||||
SystemConfig::Sse { ref uri } => {
|
||||
let transport = SseTransport::new(uri);
|
||||
SystemConfig::Sse { ref uri, ref envs } => {
|
||||
let transport = SseTransport::new(uri, envs.get_env());
|
||||
McpClient::new(transport.start().await?)
|
||||
}
|
||||
SystemConfig::Stdio { ref cmd, ref args } => {
|
||||
let transport = StdioTransport::new(cmd, args.to_vec());
|
||||
SystemConfig::Stdio {
|
||||
ref cmd,
|
||||
ref args,
|
||||
ref envs,
|
||||
} => {
|
||||
let transport = StdioTransport::new(cmd, args.to_vec(), envs.get_env());
|
||||
McpClient::new(transport.start().await?)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mcp_client::client::Error as ClientError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -15,25 +17,63 @@ pub enum SystemError {
|
||||
|
||||
pub type SystemResult<T> = Result<T, SystemError>;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct Envs {
|
||||
/// A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host
|
||||
#[serde(default)]
|
||||
#[serde(flatten)]
|
||||
map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Envs {
|
||||
pub fn new(map: HashMap<String, String>) -> Self {
|
||||
Self { map }
|
||||
}
|
||||
|
||||
pub fn default() -> Self {
|
||||
Self::new(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn get_env(&self) -> HashMap<String, String> {
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the different types of MCP systems that can be added to the manager
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SystemConfig {
|
||||
/// Server-sent events client with a URI endpoint
|
||||
Sse { uri: String },
|
||||
Sse {
|
||||
uri: String,
|
||||
#[serde(default)]
|
||||
envs: Envs,
|
||||
},
|
||||
/// Standard I/O client with command and arguments
|
||||
Stdio { cmd: String, args: Vec<String> },
|
||||
Stdio {
|
||||
cmd: String,
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
envs: Envs,
|
||||
},
|
||||
}
|
||||
|
||||
impl SystemConfig {
|
||||
pub fn sse<S: Into<String>>(uri: S) -> Self {
|
||||
Self::Sse { uri: uri.into() }
|
||||
Self::Sse {
|
||||
uri: uri.into(),
|
||||
envs: Envs::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stdio<S: Into<String>>(cmd: S) -> Self {
|
||||
Self::Stdio {
|
||||
cmd: cmd.into(),
|
||||
args: vec![],
|
||||
envs: Envs::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +83,9 @@ impl SystemConfig {
|
||||
S: Into<String>,
|
||||
{
|
||||
match self {
|
||||
Self::Stdio { cmd, .. } => Self::Stdio {
|
||||
Self::Stdio { cmd, envs, .. } => Self::Stdio {
|
||||
cmd,
|
||||
envs,
|
||||
args: args.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
other => other,
|
||||
@@ -55,8 +96,8 @@ impl SystemConfig {
|
||||
impl std::fmt::Display for SystemConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SystemConfig::Sse { uri } => write!(f, "SSE({})", uri),
|
||||
SystemConfig::Stdio { cmd, args } => write!(f, "Stdio({} {})", cmd, args.join(" ")),
|
||||
SystemConfig::Sse { uri, .. } => write!(f, "SSE({})", uri),
|
||||
SystemConfig::Stdio { cmd, args, .. } => write!(f, "Stdio({} {})", cmd, args.join(" ")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use mcp_client::{
|
||||
};
|
||||
use rand::Rng;
|
||||
use rand::SeedableRng;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -122,7 +122,7 @@ async fn create_stdio_client(
|
||||
_name: &str,
|
||||
_version: &str,
|
||||
) -> Result<McpClient, Box<dyn std::error::Error>> {
|
||||
let transport = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()]);
|
||||
let transport = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()], HashMap::new());
|
||||
Ok(McpClient::new(transport.start().await?))
|
||||
}
|
||||
|
||||
@@ -130,6 +130,6 @@ async fn create_sse_client(
|
||||
_name: &str,
|
||||
_version: &str,
|
||||
) -> Result<McpClient, Box<dyn std::error::Error>> {
|
||||
let transport = SseTransport::new("http://localhost:8000/sse");
|
||||
let transport = SseTransport::new("http://localhost:8000/sse", HashMap::new());
|
||||
Ok(McpClient::new(transport.start().await?))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient};
|
||||
use mcp_client::transport::{SseTransport, Transport};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
@@ -16,7 +17,7 @@ async fn main() -> Result<()> {
|
||||
.init();
|
||||
|
||||
// Create the base transport
|
||||
let transport = SseTransport::new("http://localhost:8000/sse");
|
||||
let transport = SseTransport::new("http://localhost:8000/sse", HashMap::new());
|
||||
|
||||
// Start transport
|
||||
let handle = transport.start().await?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use mcp_client::client::{ClientCapabilities, ClientInfo, Error as ClientError, McpClient};
|
||||
use mcp_client::transport::{StdioTransport, Transport};
|
||||
@@ -15,7 +17,7 @@ async fn main() -> Result<(), ClientError> {
|
||||
.init();
|
||||
|
||||
// 1) Create the transport
|
||||
let transport = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()]);
|
||||
let transport = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()], HashMap::new());
|
||||
|
||||
// 2) Start the transport to get a handle
|
||||
let transport_handle = transport.start().await?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
// This example shows how to use the mcp-client crate to interact with a server that has a simple counter tool.
|
||||
// The server is started by running `cargo run -p mcp-server` in the root of the mcp-server crate.
|
||||
use anyhow::Result;
|
||||
@@ -23,6 +25,7 @@ async fn main() -> Result<(), ClientError> {
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
HashMap::new(),
|
||||
);
|
||||
|
||||
// Start the transport to get a handle
|
||||
|
||||
@@ -4,6 +4,7 @@ use eventsource_client::{Client, SSE};
|
||||
use futures::TryStreamExt;
|
||||
use mcp_core::protocol::{JsonRpcMessage, JsonRpcRequest};
|
||||
use reqwest::Client as HttpClient;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio::time::{timeout, Duration};
|
||||
@@ -205,13 +206,15 @@ impl SseActor {
|
||||
#[derive(Clone)]
|
||||
pub struct SseTransport {
|
||||
sse_url: String,
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// The SSE transport spawns an `SseActor` on `start()`.
|
||||
impl SseTransport {
|
||||
pub fn new<S: Into<String>>(sse_url: S) -> Self {
|
||||
pub fn new<S: Into<String>>(sse_url: S, env: HashMap<String, String>) -> Self {
|
||||
Self {
|
||||
sse_url: sse_url.into(),
|
||||
env: env,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +241,11 @@ impl SseTransport {
|
||||
#[async_trait]
|
||||
impl Transport for SseTransport {
|
||||
async fn start(&self) -> Result<TransportHandle, Error> {
|
||||
// Set environment variables
|
||||
for (key, value) in &self.env {
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
|
||||
// Create a channel for outgoing TransportMessages
|
||||
let (tx, rx) = mpsc::channel(32);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
|
||||
|
||||
@@ -103,18 +104,25 @@ impl StdioActor {
|
||||
pub struct StdioTransport {
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl StdioTransport {
|
||||
pub fn new<S: Into<String>>(command: S, args: Vec<String>) -> Self {
|
||||
pub fn new<S: Into<String>>(
|
||||
command: S,
|
||||
args: Vec<String>,
|
||||
env: HashMap<String, String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
command: command.into(),
|
||||
args,
|
||||
env: env,
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_process(&self) -> Result<(Child, ChildStdin, ChildStdout), Error> {
|
||||
let mut process = Command::new(&self.command)
|
||||
.envs(&self.env)
|
||||
.args(&self.args)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
|
||||
Reference in New Issue
Block a user