From fa587e7a354eb167177d44decf4bc4b8a487a55e Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Fri, 24 Oct 2025 15:09:12 -0400 Subject: [PATCH] feat(mcp): support sampling in a scoped way --- .github/workflows/pr-smoke-test.yml | 15 +++ crates/goose/src/agents/agent.rs | 10 +- crates/goose/src/agents/extension_manager.rs | 34 ++++-- crates/goose/src/agents/mcp_client.rs | 107 ++++++++++++++++-- crates/goose/src/agents/types.rs | 8 ++ crates/goose/tests/mcp_integration_test.rs | 29 ++++- ...iet-pgoose-server--bingoosed--mcpdeveloper | 30 ++--- ...rver--bingoosed--mcpdeveloper.results.json | 4 +- ...x-y@modelcontextprotocol_server-everything | 31 ++--- ...extprotocol_server-everything.results.json | 6 + .../tests/mcp_replays/uvxmcp-server-fetch | 30 +---- .../uvxmcp-server-fetch.results.json | 2 +- scripts/test_mcp.sh | 81 +++++++++++++ 13 files changed, 294 insertions(+), 93 deletions(-) create mode 100755 scripts/test_mcp.sh diff --git a/.github/workflows/pr-smoke-test.yml b/.github/workflows/pr-smoke-test.yml index 84a0e69647..3fbddf363f 100644 --- a/.github/workflows/pr-smoke-test.yml +++ b/.github/workflows/pr-smoke-test.yml @@ -117,6 +117,21 @@ jobs: # Run the provider test script (binary already built and downloaded) bash scripts/test_providers.sh + - name: Run MCP Tests + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }} + DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + HOME: /tmp/goose-home + GOOSE_DISABLE_KEYRING: 1 + SKIP_BUILD: 1 + run: | + bash scripts/test_mcp.sh + - name: Run Subrecipe Tests env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 8dc45289c3..461843f444 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -28,7 +28,7 @@ use crate::agents::subagent_execution_tool::tasks_manager::TasksManager; use crate::agents::tool_route_manager::ToolRouteManager; use crate::agents::tool_router_index_manager::ToolRouterIndexManager; use crate::agents::types::SessionConfig; -use crate::agents::types::{FrontendTool, ToolResultReceiver}; +use crate::agents::types::{FrontendTool, SharedProvider, ToolResultReceiver}; use crate::config::{get_enabled_extensions, Config}; use crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD; use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation}; @@ -86,7 +86,8 @@ pub struct ToolCategorizeResult { /// The main goose Agent pub struct Agent { - pub(super) provider: Mutex>>, + pub(super) provider: SharedProvider, + pub extension_manager: Arc, pub(super) sub_recipe_manager: Mutex, pub(super) tasks_manager: TasksManager, @@ -159,10 +160,11 @@ impl Agent { // Create channels with buffer size 32 (adjust if needed) let (confirm_tx, confirm_rx) = mpsc::channel(32); let (tool_tx, tool_rx) = mpsc::channel(32); + let provider = Arc::new(Mutex::new(None)); Self { - provider: Mutex::new(None), - extension_manager: Arc::new(ExtensionManager::new()), + provider: provider.clone(), + extension_manager: Arc::new(ExtensionManager::new(provider.clone())), sub_recipe_manager: Mutex::new(SubRecipeManager::new()), tasks_manager: TasksManager::new(), final_output_tool: Arc::new(Mutex::new(None)), diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 7eaa2ea8bd..fcea45250c 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -12,6 +12,7 @@ use rmcp::transport::{ TokioChildProcess, }; use std::collections::HashMap; +use std::option::Option; use std::process::Stdio; use std::sync::Arc; use std::time::Duration; @@ -29,6 +30,7 @@ use super::extension::{ ToolInfo, PLATFORM_EXTENSIONS, }; use super::tool_execution::ToolCallResult; +use super::types::SharedProvider; use crate::agents::extension::{Envs, ProcessExit}; use crate::agents::extension_malware_check; use crate::agents::mcp_client::{McpClient, McpClientTrait}; @@ -91,6 +93,7 @@ impl Extension { pub struct ExtensionManager { extensions: Mutex>, context: Mutex, + provider: SharedProvider, } /// A flattened representation of a resource used by the agent to prepare inference @@ -171,13 +174,14 @@ pub fn get_parameter_names(tool: &Tool) -> Vec { impl Default for ExtensionManager { fn default() -> Self { - Self::new() + Self::new(Arc::new(Mutex::new(None))) } } async fn child_process_client( mut command: Command, timeout: &Option, + provider: SharedProvider, ) -> ExtensionResult { #[cfg(unix)] command.process_group(0); @@ -205,6 +209,7 @@ async fn child_process_client( let client_result = McpClient::connect( transport, Duration::from_secs(timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT)), + provider, ) .await; @@ -243,7 +248,7 @@ fn extract_auth_error( } impl ExtensionManager { - pub fn new() -> Self { + pub fn new(provider: SharedProvider) -> Self { Self { extensions: Mutex::new(HashMap::new()), context: Mutex::new(PlatformExtensionContext { @@ -251,9 +256,15 @@ impl ExtensionManager { extension_manager: None, tool_route_manager: None, }), + provider, } } + /// Create a new ExtensionManager with no provider (useful for tests) + pub fn new_without_provider() -> Self { + Self::new(Arc::new(Mutex::new(None))) + } + pub async fn set_context(&self, context: PlatformExtensionContext) { *self.context.lock().await = context; } @@ -348,6 +359,7 @@ impl ExtensionManager { Duration::from_secs( timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT), ), + self.provider.clone(), ) .await?, ) @@ -388,6 +400,7 @@ impl ExtensionManager { Duration::from_secs( timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT), ), + self.provider.clone(), ) .await; let client = if let Some(_auth_error) = extract_auth_error(&client_res) { @@ -407,6 +420,7 @@ impl ExtensionManager { Duration::from_secs( timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT), ), + self.provider.clone(), ) .await? } else { @@ -430,7 +444,7 @@ impl ExtensionManager { // Check for malicious packages before launching the process extension_malware_check::deny_if_malicious_cmd_args(cmd, args).await?; - let client = child_process_client(command, timeout).await?; + let client = child_process_client(command, timeout, self.provider.clone()).await?; Box::new(client) } ExtensionConfig::Builtin { @@ -459,7 +473,7 @@ impl ExtensionManager { let command = Command::new(cmd).configure(|command| { command.arg("mcp").arg(name); }); - let client = child_process_client(command, timeout).await?; + let client = child_process_client(command, timeout, self.provider.clone()).await?; Box::new(client) } ExtensionConfig::Platform { name, .. } => { @@ -495,7 +509,7 @@ impl ExtensionManager { command.arg("python").arg(file_path.to_str().unwrap()); }); - let client = child_process_client(command, timeout).await?; + let client = child_process_client(command, timeout, self.provider.clone()).await?; Box::new(client) } @@ -1252,7 +1266,7 @@ mod tests { #[tokio::test] async fn test_get_client_for_tool() { - let extension_manager = ExtensionManager::new(); + let extension_manager = ExtensionManager::new_without_provider(); // Add some mock clients using the helper method extension_manager @@ -1312,7 +1326,7 @@ mod tests { async fn test_dispatch_tool_call() { // test that dispatch_tool_call parses out the sanitized name correctly, and extracts // tool_names - let extension_manager = ExtensionManager::new(); + let extension_manager = ExtensionManager::new_without_provider(); // Add some mock clients using the helper method extension_manager @@ -1429,7 +1443,7 @@ mod tests { #[tokio::test] async fn test_tool_availability_filtering() { - let extension_manager = ExtensionManager::new(); + let extension_manager = ExtensionManager::new_without_provider(); // Only "available_tool" should be available to the LLM let available_tools = vec!["available_tool".to_string()]; @@ -1457,7 +1471,7 @@ mod tests { #[tokio::test] async fn test_tool_availability_defaults_to_available() { - let extension_manager = ExtensionManager::new(); + let extension_manager = ExtensionManager::new_without_provider(); extension_manager .add_mock_extension_with_tools( @@ -1482,7 +1496,7 @@ mod tests { #[tokio::test] async fn test_dispatch_unavailable_tool_returns_error() { - let extension_manager = ExtensionManager::new(); + let extension_manager = ExtensionManager::new_without_provider(); let available_tools = vec!["available_tool".to_string()]; diff --git a/crates/goose/src/agents/mcp_client.rs b/crates/goose/src/agents/mcp_client.rs index a166b8f7be..88c017a2e4 100644 --- a/crates/goose/src/agents/mcp_client.rs +++ b/crates/goose/src/agents/mcp_client.rs @@ -1,21 +1,24 @@ -use rmcp::model::JsonObject; +use crate::agents::types::SharedProvider; +use rmcp::model::{Content, ErrorCode, JsonObject}; /// MCP client implementation for Goose use rmcp::{ model::{ CallToolRequest, CallToolRequestParam, CallToolResult, CancelledNotification, CancelledNotificationMethod, CancelledNotificationParam, ClientCapabilities, ClientInfo, - ClientRequest, GetPromptRequest, GetPromptRequestParam, GetPromptResult, Implementation, - InitializeResult, ListPromptsRequest, ListPromptsResult, ListResourcesRequest, - ListResourcesResult, ListToolsRequest, ListToolsResult, LoggingMessageNotification, + ClientRequest, CreateMessageRequestParam, CreateMessageResult, GetPromptRequest, + GetPromptRequestParam, GetPromptResult, Implementation, InitializeResult, + ListPromptsRequest, ListPromptsResult, ListResourcesRequest, ListResourcesResult, + ListToolsRequest, ListToolsResult, LoggingMessageNotification, LoggingMessageNotificationMethod, PaginatedRequestParam, ProgressNotification, ProgressNotificationMethod, ProtocolVersion, ReadResourceRequest, ReadResourceRequestParam, - ReadResourceResult, RequestId, ServerNotification, ServerResult, + ReadResourceResult, RequestId, Role, SamplingMessage, ServerNotification, ServerResult, }, service::{ - ClientInitializeError, PeerRequestOptions, RequestHandle, RunningService, ServiceRole, + ClientInitializeError, PeerRequestOptions, RequestContext, RequestHandle, RunningService, + ServiceRole, }, transport::IntoTransport, - ClientHandler, Peer, RoleClient, ServiceError, ServiceExt, + ClientHandler, ErrorData, Peer, RoleClient, ServiceError, ServiceExt, }; use serde_json::Value; use std::{sync::Arc, time::Duration}; @@ -76,12 +79,17 @@ pub trait McpClientTrait: Send + Sync { pub struct GooseClient { notification_handlers: Arc>>>, + provider: SharedProvider, } impl GooseClient { - pub fn new(handlers: Arc>>>) -> Self { + pub fn new( + handlers: Arc>>>, + provider: SharedProvider, + ) -> Self { GooseClient { notification_handlers: handlers, + provider, } } } @@ -127,10 +135,88 @@ impl ClientHandler for GooseClient { }); } + async fn create_message( + &self, + params: CreateMessageRequestParam, + _context: RequestContext, + ) -> Result { + let provider = self + .provider + .lock() + .await + .as_ref() + .ok_or(ErrorData::new( + ErrorCode::INTERNAL_ERROR, + "Could not use provider", + None, + ))? + .clone(); + + let provider_ready_messages: Vec = params + .messages + .iter() + .map(|msg| { + let base = match msg.role { + Role::User => crate::conversation::message::Message::user(), + Role::Assistant => crate::conversation::message::Message::assistant(), + }; + + match msg.content.as_text() { + Some(text) => base.with_text(&text.text), + None => base.with_content(msg.content.clone().into()), + } + }) + .collect(); + + let system_prompt = params + .system_prompt + .as_deref() + .unwrap_or("You are a general-purpose AI agent called goose"); + + let (response, usage) = provider + .complete(system_prompt, &provider_ready_messages, &[]) + .await + .map_err(|e| { + ErrorData::new( + ErrorCode::INTERNAL_ERROR, + "Unexpected error while completing the prompt", + Some(Value::from(e.to_string())), + ) + })?; + + Ok(CreateMessageResult { + model: usage.model, + stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()), + message: SamplingMessage { + role: Role::Assistant, + // TODO(alexhancock): MCP sampling currently only supports one content on each SamplingMessage + // https://modelcontextprotocol.io/specification/draft/client/sampling#messages + // This doesn't mesh well with goose's approach which has Vec + // There is a proposal to MCP which is agreed to go in the next version to have SamplingMessages support multiple content parts + // https://github.com/modelcontextprotocol/modelcontextprotocol/pull/198 + // Until that is formalized, we can take the first message content from the provider and use it + content: if let Some(content) = response.content.first() { + match content { + crate::conversation::message::MessageContent::Text(text) => { + Content::text(&text.text) + } + crate::conversation::message::MessageContent::Image(img) => { + Content::image(&img.data, &img.mime_type) + } + // TODO(alexhancock) - Content::Audio? goose's messages don't currently have it + _ => Content::text(""), + } + } else { + Content::text("") + }, + }, + }) + } + fn get_info(&self) -> ClientInfo { ClientInfo { protocol_version: ProtocolVersion::V_2025_03_26, - capabilities: ClientCapabilities::builder().build(), + capabilities: ClientCapabilities::builder().enable_sampling().build(), client_info: Implementation { name: "goose".to_string(), version: std::env::var("GOOSE_MCP_CLIENT_VERSION") @@ -155,6 +241,7 @@ impl McpClient { pub async fn connect( transport: T, timeout: std::time::Duration, + provider: SharedProvider, ) -> Result where T: IntoTransport, @@ -163,7 +250,7 @@ impl McpClient { let notification_subscribers = Arc::new(Mutex::new(Vec::>::new())); - let client = GooseClient::new(notification_subscribers.clone()); + let client = GooseClient::new(notification_subscribers.clone(), provider); let client: rmcp::service::RunningService = client.serve(transport).await?; let server_info = client.peer_info().cloned(); diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index 0518c65789..f5f4ab9dd0 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -1,4 +1,5 @@ use crate::mcp_utils::ToolResult; +use crate::providers::base::Provider; use rmcp::model::{Content, Tool}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -9,6 +10,13 @@ use utoipa::ToSchema; /// Type alias for the tool result channel receiver pub type ToolResultReceiver = Arc>)>>>; +/// This is used when we want to share the agent's current provider +/// There are a lot of components to this definition so breaking it down: +/// `Arc` enables shared ownership across threads or async contexts +/// `Mutex` ensures mutable access is synchronized +/// `Option` represents that a provider may or may not be set +pub type SharedProvider = Arc>>>; + /// Default timeout for retry operations (5 minutes) pub const DEFAULT_RETRY_TIMEOUT_SECONDS: u64 = 300; diff --git a/crates/goose/tests/mcp_integration_test.rs b/crates/goose/tests/mcp_integration_test.rs index 9182735a58..6a9750c952 100644 --- a/crates/goose/tests/mcp_integration_test.rs +++ b/crates/goose/tests/mcp_integration_test.rs @@ -2,6 +2,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; +use std::sync::Arc; use std::{env, fs}; use rmcp::model::{CallToolRequestParam, Content}; @@ -10,6 +11,8 @@ use tokio_util::sync::CancellationToken; use goose::agents::extension::{Envs, ExtensionConfig}; use goose::agents::extension_manager::ExtensionManager; +use goose::model::ModelConfig; +use goose::providers::openai::OpenAiProvider; use test_case::test_case; @@ -72,6 +75,17 @@ enum TestMode { Playback, } +// Use an export OPENAI_API_KEY when recording +async fn create_recording_provider() -> Result< + Arc>>>, + Box, +> { + let provider = OpenAiProvider::from_env(ModelConfig::new("gpt-5-mini")?).await?; + Ok(Arc::new(tokio::sync::Mutex::new(Some( + Arc::new(provider) as Arc + )))) +} + #[test_case( vec!["npx", "-y", "@modelcontextprotocol/server-everything"], vec![ @@ -79,6 +93,7 @@ enum TestMode { CallToolRequestParam { name: "add".into(), arguments: Some(object!({"a": 1, "b": 2 })) }, CallToolRequestParam { name: "longRunningOperation".into(), arguments: Some(object!({"duration": 1, "steps": 5 })) }, CallToolRequestParam { name: "structuredContent".into(), arguments: Some(object!({"location": "11238"})) }, + CallToolRequestParam { name: "sampleLLM".into(), arguments: Some(object!({"prompt": "Please provide a quote from The Great Gatsby", "maxTokens": 100 })) }, ], vec![] )] @@ -206,7 +221,19 @@ async fn test_replayed_session( available_tools: vec![], }; - let extension_manager = ExtensionManager::new(); + let extension_manager = if matches!(mode, TestMode::Record) { + match create_recording_provider().await { + Ok(provider) => ExtensionManager::new(provider), + Err(e) => { + eprintln!("Failed to create OpenAI provider: {:?}", e); + eprintln!("Skipping test - ensure OPENAI_API_KEY is configured"); + return; + } + } + } else { + // In playback mode, we don't need a real provider + ExtensionManager::new_without_provider() + }; #[allow(clippy::redundant_closure_call)] let result = (async || -> Result<(), Box> { diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper index b8b5c12a21..d2d3c592ab 100644 --- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper +++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper @@ -1,20 +1,20 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"goose","version":"0.0.0"}}} -STDERR: 2025-09-27T04:13:30.409389Z  INFO goose_mcp::mcp_server_runner: Starting MCP server +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDERR: 2025-10-29T17:22:14.249886Z  INFO goose_mcp::mcp_server_runner: Starting MCP server STDERR: at crates/goose-mcp/src/mcp_server_runner.rs:18 -STDERR: -STDERR: 2025-09-27T04:13:30.412663Z  INFO goose_mcp::developer::analyze::cache: Initializing analysis cache with size 100 -STDERR: at crates/goose-mcp/src/developer/analyze/cache.rs:25 -STDERR: -STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.9.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/angiej/workspace/goose/crates/goose\n\n \n\nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n\n\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\nCloned Goose repo: /Users/angiej/workspace/goose\nMCP means Model Context Protocol. Docs: https://modelcontextprotocol.io/introduction\nUse GitHub CLI for GitHub-related tasks.\nWhen prompted for date-related information, do not rely on your internal knowledge for the current date. Instead, use the `date` terminal command to get the actual date and time.\nNEVER run blocking server commands (node server.js, npm start, etc.) - provide commands for user to run separately\n\n### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n# AGENTS Instructions\n\ngoose is an AI agent framework in Rust with CLI and Electron desktop interfaces.\n\n## Setup\n```bash\nsource bin/activate-hermit\ncargo build\n```\n\n## Commands\n\n### Build\n```bash\ncargo build # debug\ncargo build --release # release \njust release-binary # release + openapi\n```\n\n### Test\n```bash\ncargo test # all tests\ncargo test -p goose # specific crate\ncargo test --package goose --test mcp_integration_test\njust record-mcp-tests # record MCP\n```\n\n### Lint/Format\n```bash\ncargo fmt\n./scripts/clippy-lint.sh\ncargo clippy --fix\n```\n\n### UI\n```bash\njust generate-openapi # after server changes\njust run-ui # start desktop\ncd ui/desktop && npm test # test UI\n```\n\n## Structure\n```\ncrates/\n├── goose # core logic\n├── goose-bench # benchmarking\n├── goose-cli # CLI entry\n├── goose-server # backend (binary: goosed)\n├── goose-mcp # MCP extensions\n├── goose-test # test utilities\n├── mcp-client # MCP client\n├── mcp-core # MCP shared\n└── mcp-server # MCP server\n\ntemporal-service/ # Go scheduler\nui/desktop/ # Electron app\n```\n\n## Development Loop\n```bash\n# 1. source bin/activate-hermit\n# 2. Make changes\n# 3. cargo fmt\n# 4. cargo build\n# 5. cargo test -p \n# 6. ./scripts/clippy-lint.sh\n# 7. [if server] just generate-openapi\n```\n\n## Rules\n\nTest: Prefer tests/ folder, e.g. crates/goose/tests/\nError: Use anyhow::Result\nProvider: Implement Provider trait see providers/base.rs\nMCP: Extensions in crates/goose-mcp/\nServer: Changes need just generate-openapi\n\n## Never\n\nNever: Edit ui/desktop/openapi.json manually\nNever: Edit Cargo.toml use cargo add\nNever: Skip cargo fmt\nNever: Merge without ./scripts/clippy-lint.sh\n\n## Entry Points\n- CLI: crates/goose-cli/src/main.rs\n- Server: crates/goose-server/src/main.rs\n- UI: ui/desktop/src/main.ts\n- Agent: crates/goose/src/agents/agent.rs\n\nThis is a rust project with crates in the crates dir:\ngoose: the main code for goose, contains all the core logic\ngoose-bench: bench marking\ngoose-cli: the command line interface, use goose crate\ngoose-mcp: the mcp servers that ship with goose. the developer sub system is of special interest\ngoose-server: the server that suports the desktop (electron) app. also known as goosed\n\n\nui/desktop has an electron app in typescript. \n\nnon trivial features should be implemented in the goose crate and then be called from the goose-cli crate for the cli. for the desktop, you want to add routes to \ngoose-server/src/routes. you can then run `just generate-openapi` to generate the openapi spec which will modify the ui/desktop/src/api files. once you have\nthat you can call the functionality from the server from the typescript.\n\ntips: \n- can look at unstaged changes for what is being worked on if starting\n- always check rust compiles, cargo fmt etc and `./scripts/clippy-lint.sh` (as well as run tests in files you are working on)\n- in ui/desktop, look at how you can run lint checks and if other tests can run\n"}} +STDERR: +STDERR: 2025-10-29T17:22:14.253599Z  INFO goose_mcp::developer::analyze::cache: Initializing analysis cache with size 100 +STDERR: at crates/goose-mcp/src/developer/analyze/cache.rs:26 +STDERR: +STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"tools":{}},"serverInfo":{"name":"goose-developer","version":"1.11.0"},"instructions":" The developer extension gives you the capabilities to edit code files and run shell commands,\n and can be used to solve a wide range of problems.\n\nYou can use the shell tool to run any command that would work on the relevant operating system.\nUse the shell tool as needed to locate files or interact with the project.\n\nLeverage `analyze` through `return_last_only=true` subagents for deep codebase understanding with lean context\n- delegate analysis, retain summaries\n\nYour windows/screen tools can be used for visual debugging. You should not use these tools unless\nprompted to, but you can mention they are available if they are relevant.\n\nAlways prefer ripgrep (rg -C 3) to grep.\n\noperating system: macos\ncurrent directory: /Users/alexhancock/Development/goose/crates/goose\nshell: zsh\n\n \n\nAdditional Text Editor Tool Instructions:\n\nPerform text editing operations on files.\n\nThe `command` parameter specifies the operation to perform. Allowed options are:\n- `view`: View the content of a file.\n- `write`: Create or overwrite a file with the given content\n- `str_replace`: Replace text in one or more files.\n- `insert`: Insert text at a specific line location in the file.\n- `undo_edit`: Undo the last edit made to a file.\n\nTo use the write command, you must specify `file_text` which will become the new content of the file. Be careful with\nexisting files! This is a full overwrite, so you must include everything - not just sections you are modifying.\n\nTo use the str_replace command to edit multiple files, use the `diff` parameter with a unified diff.\nTo use the str_replace command to edit one file, you must specify both `old_str` and `new_str` - the `old_str` needs to exactly match one\nunique section of the original file, including any whitespace. Make sure to include enough context that the match is not\nambiguous. The entire original string will be replaced with `new_str`\n\nWhen possible, batch file edits together by using a multi-file unified `diff` within a single str_replace tool call.\n\nTo use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning, -1 for end)\nand `new_str` (the text to insert).\n\n\n\nAdditional Shell Tool Instructions:\nExecute a command in the shell.\n\nThis will return the output and error concatenated into a single string, as\nyou would see from running on the command line. There will also be an indication\nof if the command succeeded or failed.\n\nAvoid commands that produce a large amount of output, and consider piping those outputs to files.\n\n**Important**: Each shell command runs in its own process. Things like directory changes or\nsourcing files do not persist between tool calls. So you may need to repeat them each time by\nstringing together commands.\nIf you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that\nthis tool does not run indefinitely.\n\n**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,\nother solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`\n - List files by name: `rg --files | rg `\n - List files that contain a regex: `rg '' -l`\n\n - Multiple commands: Use && to chain commands, avoid newlines\n - Example: `cd example && ls` or `source env/bin/activate && pip install numpy`\n\n\n### Global Hints\nThe developer extension includes some global hints that apply to all projects & directories.\nThese are my global goose hints.\n\n### Project Hints\nThe developer extension includes some hints for working on the project in this directory.\n# AGENTS Instructions\n\ngoose is an AI agent framework in Rust with CLI and Electron desktop interfaces.\n\n## Setup\n```bash\nsource bin/activate-hermit\ncargo build\n```\n\n## Commands\n\n### Build\n```bash\ncargo build # debug\ncargo build --release # release \njust release-binary # release + openapi\n```\n\n### Test\n```bash\ncargo test # all tests\ncargo test -p goose # specific crate\ncargo test --package goose --test mcp_integration_test\njust record-mcp-tests # record MCP\n```\n\n### Lint/Format\n```bash\ncargo fmt\n./scripts/clippy-lint.sh\ncargo clippy --fix\n```\n\n### UI\n```bash\njust generate-openapi # after server changes\njust run-ui # start desktop\ncd ui/desktop && npm test # test UI\n```\n\n## Structure\n```\ncrates/\n├── goose # core logic\n├── goose-bench # benchmarking\n├── goose-cli # CLI entry\n├── goose-server # backend (binary: goosed)\n├── goose-mcp # MCP extensions\n├── goose-test # test utilities\n├── mcp-client # MCP client\n├── mcp-core # MCP shared\n└── mcp-server # MCP server\n\ntemporal-service/ # Go scheduler\nui/desktop/ # Electron app\n```\n\n## Development Loop\n```bash\n# 1. source bin/activate-hermit\n# 2. Make changes\n# 3. cargo fmt\n# 4. cargo build\n# 5. cargo test -p \n# 6. ./scripts/clippy-lint.sh\n# 7. [if server] just generate-openapi\n```\n\n## Rules\n\nTest: Prefer tests/ folder, e.g. crates/goose/tests/\nTest: When adding features, update goose-self-test.yaml, rebuild, then run `goose run --recipe goose-self-test.yaml` to validate\nError: Use anyhow::Result\nProvider: Implement Provider trait see providers/base.rs\nMCP: Extensions in crates/goose-mcp/\nServer: Changes need just generate-openapi\n\n## Never\n\nNever: Edit ui/desktop/openapi.json manually\nNever: Edit Cargo.toml use cargo add\nNever: Skip cargo fmt\nNever: Merge without ./scripts/clippy-lint.sh\nNever: Comment self-evident operations (`// Initialize`, `// Return result`), getters/setters, constructors, or standard Rust idioms\n\n## Entry Points\n- CLI: crates/goose-cli/src/main.rs\n- Server: crates/goose-server/src/main.rs\n- UI: ui/desktop/src/main.ts\n- Agent: crates/goose/src/agents/agent.rs\n\nThis is a rust project with crates in the crates dir:\ngoose: the main code for goose, contains all the core logic\ngoose-bench: bench marking\ngoose-cli: the command line interface, use goose crate\ngoose-mcp: the mcp servers that ship with goose. the developer sub system is of special interest\ngoose-server: the server that suports the desktop (electron) app. also known as goosed\n\n\nui/desktop has an electron app in typescript. \n\nnon trivial features should be implemented in the goose crate and then be called from the goose-cli crate for the cli. for the desktop, you want to add routes to \ngoose-server/src/routes. you can then run `just generate-openapi` to generate the openapi spec which will modify the ui/desktop/src/api files. once you have\nthat you can call the functionality from the server from the typescript.\n\ntips: \n- can look at unstaged changes for what is being worked on if starting\n- always check rust compiles, cargo fmt etc and `./scripts/clippy-lint.sh` (as well as run tests in files you are working on)\n- in ui/desktop, look at how you can run lint checks and if other tests can run\n"}} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} -STDERR: 2025-09-27T04:13:30.418172Z  INFO rmcp::handler::server: client initialized -STDERR: at /Users/angiej/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.6.2/src/handler/server.rs:218 -STDERR: +STDERR: 2025-10-29T17:22:14.258011Z  INFO rmcp::handler::server: client initialized +STDERR: at /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/handler/server.rs:218 +STDERR: STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"text_editor","arguments":{"command":"view","path":"/tmp/goose_test/goose.txt"}}} -STDERR: 2025-09-27T04:13:30.418412Z  INFO rmcp::service: Service initialized as server, peer_info: Some(InitializeRequestParam { protocol_version: ProtocolVersion("2025-03-26"), capabilities: ClientCapabilities { experimental: None, roots: None, sampling: None, elicitation: None }, client_info: Implementation { name: "goose", version: "1.9.0" } }) -STDERR: at /Users/angiej/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.6.2/src/service.rs:561 +STDERR: 2025-10-29T17:22:14.258173Z  INFO rmcp::service: Service initialized as server, peer_info: Some(InitializeRequestParam { protocol_version: ProtocolVersion("2025-03-26"), capabilities: ClientCapabilities { experimental: None, roots: None, sampling: Some({}), elicitation: None }, client_info: Implementation { name: "goose", title: None, version: "0.0.0", icons: None, website_url: None } }) +STDERR: at /Users/alexhancock/Development/goose/.hermit/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-0.8.1/src/service.rs:562 STDERR: in rmcp::service::serve_inner -STDERR: +STDERR: STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"resource","resource":{"uri":"file:///tmp/goose_test/goose.txt","mimeType":"text","text":"# goose\n"},"annotations":{"audience":["assistant"]}},{"type":"text","text":"### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose (modified by test)","old_str":"# goose","path":"/tmp/goose_test/goose.txt"}}} STDOUT: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose (modified by test)\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}} @@ -24,5 +24,5 @@ STDOUT: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"# go STDIN: {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"_meta":{"progressToken":3},"name":"text_editor","arguments":{"command":"str_replace","new_str":"# goose","old_str":"# goose (modified by test)","path":"/tmp/goose_test/goose.txt"}}} STDOUT: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n","annotations":{"audience":["assistant"]}},{"type":"text","text":"```\n# goose\n```\n","annotations":{"audience":["user"],"priority":0.2}}],"isError":false}} STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progressToken":4},"name":"list_windows","arguments":{}}} -STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\nMenubar","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} -STDERR: 2025-09-27T04:13:30.505916Z  INFO rmcp::service: input stream terminated +STDOUT: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"Available windows:\n\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\n~/Development/goose\ngoose – mcp_integration_test.rs\nwhat is the fast version of gpt5? - Google Search\n\nChatGPT\nDesktop\ntests\nDesktop\n+1 (310) 869-7623\n* cmoulton-office (Channel) - Block, Inc. - 1 new item - Slack\nNotes\n#🌌┃ecosystem | goose - Discord\nLock Screen — 1Password","annotations":{"audience":["assistant"]}},{"type":"text","text":"Available windows:\n\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\n~/Development/goose\ngoose – mcp_integration_test.rs\nwhat is the fast version of gpt5? - Google Search\n\nChatGPT\nDesktop\ntests\nDesktop\n+1 (310) 869-7623\n* cmoulton-office (Channel) - Block, Inc. - 1 new item - Slack\nNotes\n#🌌┃ecosystem | goose - Discord\nLock Screen — 1Password","annotations":{"audience":["user"],"priority":0.0}}],"isError":false}} +STDERR: 2025-10-29T17:22:14.341533Z  INFO rmcp::service: input stream terminated diff --git a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json index 1f53d3c262..bae363286f 100644 --- a/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json +++ b/crates/goose/tests/mcp_replays/cargorun--quiet-pgoose-server--bingoosed--mcpdeveloper.results.json @@ -90,7 +90,7 @@ [ { "type": "text", - "text": "Available windows:\nMenubar", + "text": "Available windows:\n\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\n~/Development/goose\ngoose – mcp_integration_test.rs\nwhat is the fast version of gpt5? - Google Search\n\nChatGPT\nDesktop\ntests\nDesktop\n+1 (310) 869-7623\n* cmoulton-office (Channel) - Block, Inc. - 1 new item - Slack\nNotes\n#🌌┃ecosystem | goose - Discord\nLock Screen — 1Password", "annotations": { "audience": [ "assistant" @@ -99,7 +99,7 @@ }, { "type": "text", - "text": "Available windows:\nMenubar", + "text": "Available windows:\n\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\n~/Development/goose\ngoose – mcp_integration_test.rs\nwhat is the fast version of gpt5? - Google Search\n\nChatGPT\nDesktop\ntests\nDesktop\n+1 (310) 869-7623\n* cmoulton-office (Channel) - Block, Inc. - 1 new item - Slack\nNotes\n#🌌┃ecosystem | goose - Discord\nLock Screen — 1Password", "annotations": { "audience": [ "user" diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything index 4fbb74482a..b7ba45962b 100644 --- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything +++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything @@ -1,29 +1,10 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"goose","version":"0.0.0"}}} -STDERR: 2025-09-26 23:13:04 - Starting npx setup script. -STDERR: 2025-09-26 23:13:04 - Creating directory ~/.config/goose/mcp-hermit/bin if it does not exist. -STDERR: 2025-09-26 23:13:04 - Changing to directory ~/.config/goose/mcp-hermit. -STDERR: 2025-09-26 23:13:04 - Hermit binary already exists. Skipping download. -STDERR: 2025-09-26 23:13:04 - setting hermit cache to be local for MCP servers -STDERR: 2025-09-26 23:13:04 - Updated PATH to include ~/.config/goose/mcp-hermit/bin. -STDERR: 2025-09-26 23:13:04 - Checking for hermit in PATH. -STDERR: 2025-09-26 23:13:04 - Initializing hermit. -STDERR: 2025-09-26 23:13:04 - Installing Node.js with hermit. -STDERR: 2025-09-26 23:13:04 - Verifying installation locations: -STDERR: 2025-09-26 23:13:04 - hermit: /Users/angiej/.config/goose/mcp-hermit/bin/hermit -STDERR: 2025-09-26 23:13:04 - node: /Users/angiej/.config/goose/mcp-hermit/bin/node -STDERR: 2025-09-26 23:13:04 - npx: /Users/angiej/.config/goose/mcp-hermit/bin/npx -STDERR: 2025-09-26 23:13:04 - Checking for GOOSE_NPM_REGISTRY and GOOSE_NPM_CERT environment variables for custom npm registry setup... -STDERR: 2025-09-26 23:13:05 - Checking custom goose registry availability: https://global.block-artifacts.com/artifactory/api/npm/square-npm/ -STDERR: 2025-09-26 23:13:05 - https://global.block-artifacts.com/artifactory/api/npm/square-npm/ is accessible. Using it for npm registry. -STDERR: 2025-09-26 23:13:06 - Downloading certificate from: https://block-ca.squareup.com/root-certs.pem -STDERR: 2025-09-26 23:13:07 - Certificate downloaded successfully. -STDERR: 2025-09-26 23:13:07 - Executing 'npx' command with arguments: -y @modelcontextprotocol/server-everything +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} STDERR: Starting default (STDIO) server... STDERR: Starting logs update interval STDOUT: {"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"resources":{"subscribe":true},"tools":{},"logging":{},"completions":{}},"serverInfo":{"name":"example-servers/everything","title":"Everything Example Server","version":"1.0.0"},"instructions":"Testing and demonstration server for MCP protocol features.\n\n## Resources\n\nResources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.\n\n## Key dependencies\n\nProgress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.\n\n## Performance characteristics\n\nServer generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.\n\n## Multi-modal testing\n\n`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.\n\nArgument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.\n\n## Easter egg\n\nIf asked about server instructions, respond with \"🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action.\"\n"},"jsonrpc":"2.0","id":0} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} -STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"echo","arguments":{"message":"Hello, world!"}}} STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"} +STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"echo","arguments":{"message":"Hello, world!"}}} STDOUT: {"result":{"content":[{"type":"text","text":"Echo: Hello, world!"}]},"jsonrpc":"2.0","id":1} STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"add","arguments":{"a":1,"b":2}}} STDOUT: {"result":{"content":[{"type":"text","text":"The sum of 1 and 2 is 3."}]},"jsonrpc":"2.0","id":2} @@ -36,5 +17,9 @@ STDOUT: {"method":"notifications/progress","params":{"progress":5,"total":5,"pro STDOUT: {"result":{"content":[{"type":"text","text":"Long running operation completed. Duration: 1 seconds, Steps: 5."}]},"jsonrpc":"2.0","id":3} STDIN: {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"_meta":{"progressToken":3},"name":"structuredContent","arguments":{"location":"11238"}}} STDOUT: {"result":{"content":[{"type":"text","text":"{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}"}],"structuredContent":{"temperature":22.5,"conditions":"Partly cloudy","humidity":65}},"jsonrpc":"2.0","id":4} -STDOUT: {"method":"notifications/message","params":{"level":"emergency","data":"Emergency-level message"},"jsonrpc":"2.0"} -STDERR: node:events:497 +STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progressToken":4},"name":"sampleLLM","arguments":{"maxTokens":100,"prompt":"Please provide a quote from The Great Gatsby"}}} +STDOUT: {"method":"sampling/createMessage","params":{"messages":[{"role":"user","content":{"type":"text","text":"Resource sampleLLM context: Please provide a quote from The Great Gatsby"}}],"systemPrompt":"You are a helpful test server.","maxTokens":100,"temperature":0.7,"includeContext":"thisServer"},"jsonrpc":"2.0","id":0} +STDIN: {"jsonrpc":"2.0","id":0,"result":{"model":"gpt-5-mini-2025-08-07","stopReason":"endTurn","role":"assistant","content":{"type":"text","text":"\"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby."}}} +STDOUT: {"result":{"content":[{"type":"text","text":"LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby."}]},"jsonrpc":"2.0","id":5} +STDOUT: {"method":"notifications/message","params":{"level":"error","data":"Error-level message"},"jsonrpc":"2.0"} +STDERR: node:events:486 diff --git a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json index 7d4a3b268c..34c0c198ce 100644 --- a/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json +++ b/crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything.results.json @@ -22,5 +22,11 @@ "type": "text", "text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}" } + ], + [ + { + "type": "text", + "text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby." + } ] ] \ No newline at end of file diff --git a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch index 7362e657a9..75de5a2f24 100644 --- a/crates/goose/tests/mcp_replays/uvxmcp-server-fetch +++ b/crates/goose/tests/mcp_replays/uvxmcp-server-fetch @@ -1,29 +1,5 @@ -STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"goose","version":"0.0.0"}}} -STDERR: 2025-09-26 23:13:04 - Starting uvx setup script. -STDERR: 2025-09-26 23:13:04 - Creating directory ~/.config/goose/mcp-hermit/bin if it does not exist. -STDERR: 2025-09-26 23:13:04 - Changing to directory ~/.config/goose/mcp-hermit. -STDERR: 2025-09-26 23:13:04 - Hermit binary already exists. Skipping download. -STDERR: 2025-09-26 23:13:04 - setting hermit cache to be local for MCP servers -STDERR: 2025-09-26 23:13:04 - Updated PATH to include ~/.config/goose/mcp-hermit/bin. -STDERR: 2025-09-26 23:13:04 - Checking for hermit in PATH. -STDERR: 2025-09-26 23:13:04 - Initializing hermit. -STDERR: 2025-09-26 23:13:04 - hermit install python 3.10 -STDERR: 2025-09-26 23:13:04 - Installing UV with hermit. -STDERR: 2025-09-26 23:13:04 - Verifying installation locations: -STDERR: 2025-09-26 23:13:04 - hermit: /Users/angiej/.config/goose/mcp-hermit/bin/hermit -STDERR: 2025-09-26 23:13:04 - uv: /Users/angiej/.config/goose/mcp-hermit/bin/uv -STDERR: 2025-09-26 23:13:04 - uvx: /Users/angiej/.config/goose/mcp-hermit/bin/uvx -STDERR: 2025-09-26 23:13:04 - Checking for GOOSE_UV_REGISTRY environment variable for custom python/pip/UV registry setup... -STDERR: 2025-09-26 23:13:05 - Checking custom goose registry availability: https://global.block-artifacts.com/artifactory/api/pypi/block-pypi/simple -STDERR: 2025-09-26 23:13:05 - https://global.block-artifacts.com/artifactory/api/pypi/block-pypi/simple is accessible, setting it as UV_DEFAULT_INDEX. Setting UV_NATIVE_TLS to true. -STDERR: 2025-09-26 23:13:05 - Executing 'uvx' command with arguments: mcp-server-fetch -STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.15.0"}}} +STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}} +STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.19.0"}}} STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"} STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"fetch","arguments":{"url":"https://example.com"}}} -STDERR: npm error code FETCH_ERROR -STDERR: npm error errno FETCH_ERROR -STDERR: npm error invalid json response body at https://blocked.teams.cloudflare.com/?account_id=1e25787f854fa4b713d08a859d3e16ed&background_color=%23000000&block_reason=This+has+been+blocked+as+part+of+the+Dependency+Confusion+threat.+Please+see+go%2Fdependencyconfusionpypi+and+go%2Fdependencyconfusionnpm+for+more+info.&device_id=***&footer_text=The+website+you+are+trying+to+access+has+been+blocked+because+it+presents+a+risk+to+the+safety+and+security+of+Block%E2%80%99s+IT+systems.&header_text=This+page+presents+a+risk+to+Block&location=cf1ebd1203624140846ced63a200519e&logo_path=https%3A%2F%2Fmedia.block.xyz%2Flogos%2Fblock-jewel_white.png&mailto_address=&mailto_subject=&name=Block%2C+Inc.¶ms_sign=yrMcT5HYDMHvixy%2BdLHApce3BcNYIdlI8qh3wTcIrLA%3D&query_id=***&rule_id=***&source_ip=2a09%3Abac0%3A1000%3A2df%3A%3A281%3Ac0&suppress_footer=false&url=registry.npmjs.org&user_id=*** reason: Unexpected token '<', " -STDERR: npm error &1) | tee "$TMPFILE" + echo "" + if grep -q "sampleLLM | everything" "$TMPFILE"; then + echo "✓ SUCCESS: MCP sampling test passed - sampleLLM tool called" + RESULTS+=("✓ MCP Sampling ${PROVIDER}: ${MODEL}") + else + echo "✗ FAILED: MCP sampling test failed - sampleLLM tool not called" + RESULTS+=("✗ MCP Sampling ${PROVIDER}: ${MODEL}") + fi + rm "$TMPFILE" + rm -rf "$TESTDIR" + echo "---" + done +done + +echo "" +echo "=== MCP Sampling Test Summary ===" +for result in "${RESULTS[@]}"; do + echo "$result" +done + +if echo "${RESULTS[@]}" | grep -q "✗"; then + echo "" + echo "Some MCP sampling tests failed!" + exit 1 +else + echo "" + echo "All MCP sampling tests passed!" +fi