Skip to content
Open
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
19 changes: 17 additions & 2 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ use std::io::Read;
use std::path::PathBuf;
use tracing::warn;

fn generate_serve_secret_key() -> String {
use rand::distributions::{Alphanumeric, DistString};

format!(
"goose-acp-{}",
Alphanumeric.sample_string(&mut rand::thread_rng(), 32)
)
}

#[derive(Parser)]
#[command(name = "goose", author, version, display_name = "", about, long_about = None)]
pub struct Cli {
Expand Down Expand Up @@ -1086,13 +1095,19 @@ async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) ->
config_dir: Paths::config_dir(),
goose_platform: GoosePlatform::GooseCli,
}));
let router = create_router(server);
let secret_key =
std::env::var("GOOSE_SERVER__SECRET_KEY").unwrap_or_else(|_| generate_serve_secret_key());
Comment on lines +1098 to +1099
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty GOOSE_SERVER__SECRET_KEY values

Treating GOOSE_SERVER__SECRET_KEY as valid whenever the variable exists means GOOSE_SERVER__SECRET_KEY="" produces an empty shared secret, so /mcp-app-proxy and /mcp-app-guest auth can be satisfied with an empty secret parameter/body. In environments where this var is templated but left blank, any loopback client (including arbitrary web pages reaching localhost) can use the MCP app proxy endpoints without guessing a key; trim/validate and either fail startup or fall back to a generated secret when the value is empty.

Useful? React with 👍 / 👎.

let router = create_router(server, secret_key);

let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
info!("Starting ACP server on {}", addr);

let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, router).await?;
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await?;

Ok(())
}
Expand Down
25 changes: 25 additions & 0 deletions crates/goose-sdk/src/custom_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,31 @@ pub struct ReadResourceResponse {
pub result: serde_json::Value,
}

/// Call a tool from an extension.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/tool/call", response = GooseToolCallResponse)]
#[serde(rename_all = "camelCase")]
pub struct GooseToolCallRequest {
pub session_id: String,
pub name: String,
#[serde(default)]
pub arguments: serde_json::Value,
}

/// Tool call response.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
#[serde(rename_all = "camelCase")]
pub struct GooseToolCallResponse {
#[serde(default)]
pub content: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_content: Option<serde_json::Value>,
pub is_error: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "_meta")]
pub meta: Option<serde_json::Value>,
}

/// Update the working directory for a session.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/working_dir/update", response = EmptyResponse)]
Expand Down
5 changes: 5 additions & 0 deletions crates/goose/acp-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"requestType": "GetToolsRequest",
"responseType": "GetToolsResponse"
},
{
"method": "_goose/tool/call",
"requestType": "GooseToolCallRequest",
"responseType": "GooseToolCallResponse"
},
{
"method": "_goose/resource/read",
"requestType": "ReadResourceRequest",
Expand Down
59 changes: 59 additions & 0 deletions crates/goose/acp-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,48 @@
"x-side": "agent",
"x-method": "_goose/tools"
},
"GooseToolCallRequest": {
"type": "object",
"properties": {
"sessionId": {
"type": "string"
},
"name": {
"type": "string"
},
"arguments": {
"default": null
}
},
"required": [
"sessionId",
"name"
],
"description": "Call a tool from an extension.",
"x-side": "agent",
"x-method": "_goose/tool/call"
},
"GooseToolCallResponse": {
"type": "object",
"properties": {
"content": {
"type": "array",
"items": {},
"default": []
},
"structuredContent": {},
"isError": {
"type": "boolean"
},
"_meta": {}
},
"required": [
"isError"
],
"description": "Tool call response.",
"x-side": "agent",
"x-method": "_goose/tool/call"
},
"ReadResourceRequest": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1601,6 +1643,15 @@
"description": "Params for _goose/tools",
"title": "GetToolsRequest"
},
{
"allOf": [
{
"$ref": "#/$defs/GooseToolCallRequest"
}
],
"description": "Params for _goose/tool/call",
"title": "GooseToolCallRequest"
},
{
"allOf": [
{
Expand Down Expand Up @@ -2007,6 +2058,14 @@
],
"title": "GetToolsResponse"
},
{
"allOf": [
{
"$ref": "#/$defs/GooseToolCallResponse"
}
],
"title": "GooseToolCallResponse"
},
{
"allOf": [
{
Expand Down
Loading
Loading