Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions crates/goose-mcp/src/computercontroller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use tokio::{process::Command, sync::mpsc};
use std::os::unix::fs::PermissionsExt;

use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
handler::{
require_str_parameter, require_u64_parameter, PromptError, ResourceError, ToolError,
},
protocol::ServerCapabilities,
};
use mcp_server::router::CapabilitiesBuilder;
Expand Down Expand Up @@ -595,11 +597,7 @@ impl ComputerControllerRouter {
}

async fn web_scrape(&self, params: Value) -> Result<Vec<Content>, ToolError> {
let url = params
.get("url")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'url' parameter".into()))?;

let url = require_str_parameter(&params, "url")?;
let save_as = params
.get("save_as")
.and_then(|v| v.as_str())
Expand Down Expand Up @@ -916,20 +914,9 @@ impl ComputerControllerRouter {
))])
}
"update_cell" => {
let row = params.get("row").and_then(|v| v.as_u64()).ok_or_else(|| {
ToolError::InvalidParameters("Missing 'row' parameter".into())
})?;

let col = params.get("col").and_then(|v| v.as_u64()).ok_or_else(|| {
ToolError::InvalidParameters("Missing 'col' parameter".into())
})?;

let value = params
.get("value")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidParameters("Missing 'value' parameter".into())
})?;
let row = require_u64_parameter(&params, "row")?;
let col = require_u64_parameter(&params, "col")?;
let value = require_str_parameter(&params, "value")?;

let worksheet_name = params
.get("worksheet")
Expand Down
9 changes: 2 additions & 7 deletions crates/goose-mcp/src/developer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use url::Url;

use include_dir::{include_dir, Dir};
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
handler::{require_str_parameter, PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
};

Expand Down Expand Up @@ -918,12 +918,7 @@ impl DeveloperRouter {
self.text_editor_view(&path, view_range).await
}
"write" => {
let file_text = params
.get("file_text")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidParameters("Missing 'file_text' parameter".into())
})?;
let file_text = require_str_parameter(&params, "file_text")?;

self.text_editor_write(&path, file_text).await
}
Expand Down
7 changes: 2 additions & 5 deletions crates/goose/src/agents/extension_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use axum::http::{HeaderMap, HeaderName};
use chrono::{DateTime, TimeZone, Utc};
use futures::stream::{FuturesUnordered, StreamExt};
use futures::{future, FutureExt};
use mcp_core::handler::require_str_parameter;
use mcp_core::{ToolCall, ToolError};
use rmcp::service::ClientInitializeError;
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
Expand Down Expand Up @@ -551,11 +552,7 @@ impl ExtensionManager {

// Function that gets executed for read_resource tool
pub async fn read_resource(&self, params: Value) -> Result<Vec<Content>, ToolError> {
let uri = params
.get("uri")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'uri' parameter".to_string()))?;

let uri = require_str_parameter(&params, "uri")?;
let extension_name = params.get("extension_name").and_then(|v| v.as_str());

// If extension name is provided, we can just look it up
Expand Down
31 changes: 30 additions & 1 deletion crates/mcp-core/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[allow(unused_imports)] // this is used in schema below
use serde_json::json;
use serde_json::{json, Value};
use thiserror::Error;

#[non_exhaustive]
Expand Down Expand Up @@ -35,3 +35,32 @@ pub enum PromptError {
#[error("Prompt not found: {0}")]
NotFound(String),
}

/// Helper function to require a string, returning a ToolError
pub fn require_str_parameter<'a>(
v: &'a serde_json::Value,
name: &str,
) -> Result<&'a str, ToolError> {
let v = v
.get(name)
.ok_or_else(|| ToolError::InvalidParameters(format!("The parameter {name} is required")))?;
match v.as_str() {
Some(r) => Ok(r),
None => Err(ToolError::InvalidParameters(format!(
"The parameter {name} must be a string"
))),
}
}

/// Helper function to require a u64, returning a ToolError
pub fn require_u64_parameter(v: &serde_json::Value, name: &str) -> Result<u64, ToolError> {
let v = v
.get(name)
.ok_or_else(|| ToolError::InvalidParameters(format!("The parameter {name} is required")))?;
match v.as_u64() {
Some(r) => Ok(r),
None => Err(ToolError::InvalidParameters(format!(
"The parameter {name} must be a number"
))),
}
}