Read paths from an interactive & login shell (#5774)
This commit is contained in:
Generated
-1
@@ -2787,7 +2787,6 @@ dependencies = [
|
|||||||
"docx-rs",
|
"docx-rs",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
"glob",
|
"glob",
|
||||||
"goose",
|
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ use anyhow::Result;
|
|||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
use goose::config::{Config, ExtensionConfig};
|
use goose::config::{Config, ExtensionConfig};
|
||||||
|
use goose_mcp::mcp_server_runner::{serve, McpCommand};
|
||||||
|
use goose_mcp::{
|
||||||
|
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::commands::acp::run_acp_agent;
|
use crate::commands::acp::run_acp_agent;
|
||||||
use crate::commands::bench::agent_generator;
|
use crate::commands::bench::agent_generator;
|
||||||
@@ -420,7 +424,10 @@ enum Command {
|
|||||||
|
|
||||||
/// Manage system prompts and behaviors
|
/// Manage system prompts and behaviors
|
||||||
#[command(about = "Run one of the mcp servers bundled with goose")]
|
#[command(about = "Run one of the mcp servers bundled with goose")]
|
||||||
Mcp { name: String },
|
Mcp {
|
||||||
|
#[arg(value_parser = clap::value_parser!(McpCommand))]
|
||||||
|
server: McpCommand,
|
||||||
|
},
|
||||||
|
|
||||||
/// Run goose as an ACP (Agent Client Protocol) agent
|
/// Run goose as an ACP (Agent Client Protocol) agent
|
||||||
#[command(about = "Run goose as an ACP agent server on stdio")]
|
#[command(about = "Run goose as an ACP agent server on stdio")]
|
||||||
@@ -877,15 +884,18 @@ pub async fn cli() -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Command::Configure {}) => {
|
Some(Command::Configure {}) => handle_configure().await?,
|
||||||
handle_configure().await?;
|
Some(Command::Info { verbose }) => handle_info(verbose)?,
|
||||||
}
|
Some(Command::Mcp { server }) => {
|
||||||
Some(Command::Info { verbose }) => {
|
let name = server.name();
|
||||||
handle_info(verbose)?;
|
|
||||||
}
|
|
||||||
Some(Command::Mcp { name }) => {
|
|
||||||
crate::logging::setup_logging(Some(&format!("mcp-{name}")), None)?;
|
crate::logging::setup_logging(Some(&format!("mcp-{name}")), None)?;
|
||||||
goose_mcp::mcp_server_runner::run_mcp_server(&name).await?;
|
match server {
|
||||||
|
McpCommand::AutoVisualiser => serve(AutoVisualiserRouter::new()).await?,
|
||||||
|
McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?,
|
||||||
|
McpCommand::Memory => serve(MemoryServer::new()).await?,
|
||||||
|
McpCommand::Tutorial => serve(TutorialServer::new()).await?,
|
||||||
|
McpCommand::Developer => serve(DeveloperServer::new()).await?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(Command::Acp {}) => {
|
Some(Command::Acp {}) => {
|
||||||
run_acp_agent().await?;
|
run_acp_agent().await?;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ description.workspace = true
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
goose = { path = "../goose" }
|
|
||||||
rmcp = { version = "0.8.1", features = ["server", "client", "transport-io", "macros"] }
|
rmcp = { version = "0.8.1", features = ["server", "client", "transport-io", "macros"] }
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.94"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -77,13 +76,13 @@ libc = "0.2"
|
|||||||
# ~1000 downloads). Pinned to exact version to prevent supply chain attacks.
|
# ~1000 downloads). Pinned to exact version to prevent supply chain attacks.
|
||||||
mpatch = "=0.2.0"
|
mpatch = "=0.2.0"
|
||||||
tokio-util = "0.7.16"
|
tokio-util = "0.7.16"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = "3.0.0"
|
serial_test = "3.0.0"
|
||||||
sysinfo = "0.32.1"
|
sysinfo = "0.32.1"
|
||||||
temp-env = "0.3.6"
|
temp-env = "0.3.6"
|
||||||
clap = { version = "4", features = ["derive"] }
|
|
||||||
colored = "2"
|
colored = "2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ use crate::developer::analyze::types::{
|
|||||||
AnalysisMode, AnalysisResult, CallChain, EntryType, FocusedAnalysisData,
|
AnalysisMode, AnalysisResult, CallChain, EntryType, FocusedAnalysisData,
|
||||||
};
|
};
|
||||||
use crate::developer::lang;
|
use crate::developer::lang;
|
||||||
use goose::utils::safe_truncate;
|
|
||||||
use rmcp::model::{Content, Role};
|
use rmcp::model::{Content, Role};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn safe_truncate(s: &str, max_chars: usize) -> String {
|
||||||
|
if s.chars().count() <= max_chars {
|
||||||
|
s.to_string()
|
||||||
|
} else {
|
||||||
|
let truncated: String = s.chars().take(max_chars.saturating_sub(3)).collect();
|
||||||
|
format!("{}...", truncated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Formatter;
|
pub struct Formatter;
|
||||||
|
|
||||||
impl Formatter {
|
impl Formatter {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod analyze;
|
pub mod analyze;
|
||||||
mod editor_models;
|
mod editor_models;
|
||||||
mod lang;
|
mod lang;
|
||||||
|
pub mod paths;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod text_editor;
|
mod text_editor;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
static SHELL_PATH_DIRS: OnceCell<Result<Vec<PathBuf>, anyhow::Error>> = OnceCell::const_new();
|
||||||
|
|
||||||
|
pub async fn get_shell_path_dirs() -> Result<&'static Vec<PathBuf>> {
|
||||||
|
let result = SHELL_PATH_DIRS
|
||||||
|
.get_or_init(|| async {
|
||||||
|
get_shell_path_async()
|
||||||
|
.await
|
||||||
|
.map(|path| env::split_paths(&path).collect())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(dirs) => Ok(dirs),
|
||||||
|
Err(e) => Err(anyhow::anyhow!(
|
||||||
|
"Failed to get shell PATH directories: {}",
|
||||||
|
e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_shell_path_async() -> Result<String> {
|
||||||
|
let shell = env::var("SHELL").unwrap_or_else(|_| {
|
||||||
|
if cfg!(windows) {
|
||||||
|
"cmd".to_string()
|
||||||
|
} else {
|
||||||
|
"/bin/bash".to_string()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if cfg!(windows) {
|
||||||
|
get_windows_path_async(&shell).await
|
||||||
|
} else {
|
||||||
|
get_unix_path_async(&shell).await
|
||||||
|
}
|
||||||
|
.or_else(|e| {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to get PATH from shell ({}), falling back to current PATH",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
env::var("PATH").map_err(|_| anyhow::anyhow!("No PATH variable available"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_unix_path_async(shell: &str) -> Result<String> {
|
||||||
|
let output = Command::new(shell)
|
||||||
|
.args(["-l", "-i", "-c", "echo $PATH"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to execute shell command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(anyhow::anyhow!("Shell command failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = String::from_utf8(output.stdout)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))?
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if path.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("Shell returned empty PATH"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_windows_path_async(shell: &str) -> Result<String> {
|
||||||
|
let shell_name = std::path::Path::new(shell)
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("cmd");
|
||||||
|
|
||||||
|
let output = match shell_name {
|
||||||
|
"pwsh" | "powershell" => {
|
||||||
|
Command::new(shell)
|
||||||
|
.args(["-NoLogo", "-Command", "$env:PATH"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Command::new(shell)
|
||||||
|
.args(["/c", "echo %PATH%"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = output.map_err(|e| anyhow::anyhow!("Failed to execute shell command: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(anyhow::anyhow!("Shell command failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = String::from_utf8(output.stdout)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))?
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if path.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("Shell returned empty PATH"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
@@ -17,6 +18,8 @@ use rmcp::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
env::join_paths,
|
||||||
|
ffi::OsString,
|
||||||
future::Future,
|
future::Future,
|
||||||
io::Cursor,
|
io::Cursor,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@@ -31,11 +34,11 @@ use tokio::{
|
|||||||
use tokio_stream::{wrappers::SplitStream, StreamExt as _};
|
use tokio_stream::{wrappers::SplitStream, StreamExt as _};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::developer::{paths::get_shell_path_dirs, shell::ShellConfig};
|
||||||
|
|
||||||
use super::analyze::{types::AnalyzeParams, CodeAnalyzer};
|
use super::analyze::{types::AnalyzeParams, CodeAnalyzer};
|
||||||
use super::editor_models::{create_editor_model, EditorModel};
|
use super::editor_models::{create_editor_model, EditorModel};
|
||||||
use super::shell::{
|
use super::shell::{configure_shell_command, expand_path, is_absolute_path, kill_process_group};
|
||||||
configure_shell_command, expand_path, get_shell_config, is_absolute_path, kill_process_group,
|
|
||||||
};
|
|
||||||
use super::text_editor::{
|
use super::text_editor::{
|
||||||
text_editor_insert, text_editor_replace, text_editor_undo, text_editor_view, text_editor_write,
|
text_editor_insert, text_editor_replace, text_editor_undo, text_editor_view, text_editor_write,
|
||||||
};
|
};
|
||||||
@@ -179,6 +182,8 @@ pub struct DeveloperServer {
|
|||||||
pub running_processes: Arc<RwLock<HashMap<String, CancellationToken>>>,
|
pub running_processes: Arc<RwLock<HashMap<String, CancellationToken>>>,
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
running_processes: Arc<RwLock<HashMap<String, CancellationToken>>>,
|
running_processes: Arc<RwLock<HashMap<String, CancellationToken>>>,
|
||||||
|
bash_env_file: Option<PathBuf>,
|
||||||
|
extend_path_with_shell: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tool_handler(router = self.tool_router)]
|
#[tool_handler(router = self.tool_router)]
|
||||||
@@ -549,9 +554,21 @@ impl DeveloperServer {
|
|||||||
prompts: load_prompt_files(),
|
prompts: load_prompt_files(),
|
||||||
code_analyzer: CodeAnalyzer::new(),
|
code_analyzer: CodeAnalyzer::new(),
|
||||||
running_processes: Arc::new(RwLock::new(HashMap::new())),
|
running_processes: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
extend_path_with_shell: false,
|
||||||
|
bash_env_file: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extend_path_with_shell(mut self, value: bool) -> Self {
|
||||||
|
self.extend_path_with_shell = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bash_env_file(mut self, value: Option<PathBuf>) -> Self {
|
||||||
|
self.bash_env_file = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// List all available windows that can be used with screen_capture.
|
/// List all available windows that can be used with screen_capture.
|
||||||
/// Returns a list of window titles that can be used with the window_title parameter
|
/// Returns a list of window titles that can be used with the window_title parameter
|
||||||
/// of the screen_capture tool.
|
/// of the screen_capture tool.
|
||||||
@@ -942,10 +959,34 @@ impl DeveloperServer {
|
|||||||
peer: &rmcp::service::Peer<RoleServer>,
|
peer: &rmcp::service::Peer<RoleServer>,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
) -> Result<String, ErrorData> {
|
) -> Result<String, ErrorData> {
|
||||||
// Get platform-specific shell configuration
|
let mut shell_config = ShellConfig::default();
|
||||||
let shell_config = get_shell_config();
|
let shell_name = std::path::Path::new(&shell_config.executable)
|
||||||
|
.file_name()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("bash");
|
||||||
|
|
||||||
let mut child = configure_shell_command(&shell_config, command)
|
if let Some(ref env_file) = self.bash_env_file {
|
||||||
|
if shell_name == "bash" {
|
||||||
|
shell_config.envs.push((
|
||||||
|
OsString::from("BASH_ENV"),
|
||||||
|
env_file.clone().into_os_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = configure_shell_command(&shell_config, command);
|
||||||
|
|
||||||
|
if self.extend_path_with_shell {
|
||||||
|
if let Err(e) = get_shell_path_dirs()
|
||||||
|
.await
|
||||||
|
.and_then(|dirs| join_paths(dirs).map_err(|e| anyhow!(e)))
|
||||||
|
.map(|path| command.env("PATH", path))
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to extend PATH with shell directories: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child = command
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None))?;
|
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None))?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::{env, ffi::OsString, process::Stdio};
|
use std::{env, ffi::OsString, process::Stdio};
|
||||||
|
|
||||||
use goose::config::paths::Paths;
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[allow(unused_imports)] // False positive: trait is used for process_group method
|
#[allow(unused_imports)] // False positive: trait is used for process_group method
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
@@ -22,24 +21,10 @@ impl Default for ShellConfig {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
let shell = env::var("SHELL").unwrap_or_else(|_| "bash".to_string());
|
let shell = env::var("SHELL").unwrap_or_else(|_| "bash".to_string());
|
||||||
// Get just the shell name from the path (e.g., /bin/zsh -> zsh)
|
|
||||||
let shell_name = std::path::Path::new(&shell)
|
|
||||||
.file_name()
|
|
||||||
.and_then(|s| s.to_str())
|
|
||||||
.unwrap_or("bash");
|
|
||||||
|
|
||||||
// Configure environment based on shell type
|
|
||||||
let envs = if shell_name == "bash" {
|
|
||||||
let bash_env = Paths::config_dir().join(".bash_env").into_os_string();
|
|
||||||
vec![(OsString::from("BASH_ENV"), bash_env)]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
executable: shell,
|
executable: shell,
|
||||||
args: vec!["-c".to_string()], // -c is standard across bash/zsh/fish
|
args: vec!["-c".to_string()], // -c is standard across bash/zsh/fish
|
||||||
envs,
|
envs: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,10 +67,6 @@ impl ShellConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shell_config() -> ShellConfig {
|
|
||||||
ShellConfig::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand_path(path_str: &str) -> String {
|
pub fn expand_path(path_str: &str) -> String {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
// Expand Windows environment variables (%VAR%)
|
// Expand Windows environment variables (%VAR%)
|
||||||
|
|||||||
@@ -1,37 +1,45 @@
|
|||||||
use crate::{
|
use std::str::FromStr;
|
||||||
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
|
||||||
};
|
use anyhow::Result;
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use rmcp::{transport::stdio, ServiceExt};
|
use rmcp::{transport::stdio, ServiceExt};
|
||||||
|
|
||||||
/// Run an MCP server by name
|
#[derive(Clone, Debug)]
|
||||||
///
|
pub enum McpCommand {
|
||||||
/// This function handles the common logic for starting MCP servers.
|
AutoVisualiser,
|
||||||
/// The caller is responsible for setting up logging before calling this function.
|
ComputerController,
|
||||||
pub async fn run_mcp_server(name: &str) -> Result<()> {
|
Developer,
|
||||||
if name == "googledrive" || name == "google_drive" {
|
Memory,
|
||||||
return Err(anyhow!(
|
Tutorial,
|
||||||
"the built-in Google Drive extension has been removed"
|
}
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("Starting MCP server");
|
impl FromStr for McpCommand {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
match name.to_lowercase().replace(' ', "").as_str() {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
"autovisualiser" => serve_and_wait(AutoVisualiserRouter::new()).await,
|
match s.to_lowercase().replace(' ', "").as_str() {
|
||||||
"computercontroller" => serve_and_wait(ComputerControllerServer::new()).await,
|
"autovisualiser" => Ok(McpCommand::AutoVisualiser),
|
||||||
"developer" => serve_and_wait(DeveloperServer::new()).await,
|
"computercontroller" => Ok(McpCommand::ComputerController),
|
||||||
"memory" => serve_and_wait(MemoryServer::new()).await,
|
"developer" => Ok(McpCommand::Developer),
|
||||||
"tutorial" => serve_and_wait(TutorialServer::new()).await,
|
"memory" => Ok(McpCommand::Memory),
|
||||||
_ => {
|
"tutorial" => Ok(McpCommand::Tutorial),
|
||||||
tracing::warn!("Unknown MCP server name: {}", name);
|
_ => Err(format!("Invalid command: {}", s)),
|
||||||
Err(anyhow!("Unknown MCP server name: {}", name))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to run any MCP server with common error handling
|
impl McpCommand {
|
||||||
async fn serve_and_wait<S>(server: S) -> Result<()>
|
pub fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
McpCommand::AutoVisualiser => "autovisualiser",
|
||||||
|
McpCommand::ComputerController => "computercontroller",
|
||||||
|
McpCommand::Developer => "developer",
|
||||||
|
McpCommand::Memory => "memory",
|
||||||
|
McpCommand::Tutorial => "tutorial",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve<S>(server: S) -> Result<()>
|
||||||
where
|
where
|
||||||
S: rmcp::ServerHandler,
|
S: rmcp::ServerHandler,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ mod routes;
|
|||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use goose::config::paths::Paths;
|
||||||
|
use goose_mcp::{
|
||||||
|
mcp_server_runner::{serve, McpCommand},
|
||||||
|
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
@@ -22,8 +27,8 @@ enum Commands {
|
|||||||
Agent,
|
Agent,
|
||||||
/// Run the MCP server
|
/// Run the MCP server
|
||||||
Mcp {
|
Mcp {
|
||||||
/// Name of the MCP server type
|
#[arg(value_parser = clap::value_parser!(McpCommand))]
|
||||||
name: String,
|
server: McpCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,13 +36,27 @@ enum Commands {
|
|||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match &cli.command {
|
match cli.command {
|
||||||
Commands::Agent => {
|
Commands::Agent => {
|
||||||
commands::agent::run().await?;
|
commands::agent::run().await?;
|
||||||
}
|
}
|
||||||
Commands::Mcp { name } => {
|
Commands::Mcp { server } => {
|
||||||
logging::setup_logging(Some(&format!("mcp-{name}")))?;
|
logging::setup_logging(Some(&format!("mcp-{}", server.name())))?;
|
||||||
goose_mcp::mcp_server_runner::run_mcp_server(name).await?;
|
match server {
|
||||||
|
McpCommand::AutoVisualiser => serve(AutoVisualiserRouter::new()).await?,
|
||||||
|
McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?,
|
||||||
|
McpCommand::Memory => serve(MemoryServer::new()).await?,
|
||||||
|
McpCommand::Tutorial => serve(TutorialServer::new()).await?,
|
||||||
|
McpCommand::Developer => {
|
||||||
|
let bash_env = Paths::config_dir().join(".bash_env");
|
||||||
|
serve(
|
||||||
|
DeveloperServer::new()
|
||||||
|
.extend_path_with_shell(true)
|
||||||
|
.bash_env_file(Some(bash_env)),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user