-
Notifications
You must be signed in to change notification settings - Fork 6k
feat: acp methods for config extensions #9581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
19daa9a
aef792a
e74af4c
d3253a0
9d26cb3
adf02fd
8df3c22
5debef9
a0563fe
d9c0bfa
2ddb22c
eee54ee
6150a4c
9e79da0
03bf425
575baf1
c1caaf2
8176800
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| use agent_client_protocol::schema::McpServer; | ||
| use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse}; | ||
| use schemars::JsonSchema; | ||
| use serde::{Deserialize, Serialize}; | ||
|
|
@@ -147,30 +148,104 @@ pub struct DeleteSessionRequest { | |
| pub session_id: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] | ||
| #[serde(tag = "type", rename_all = "snake_case")] | ||
| pub enum GooseExtension { | ||
| Builtin { | ||
| name: String, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| description: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| display_name: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| timeout: Option<u64>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| bundled: Option<bool>, | ||
| }, | ||
| Platform { | ||
| name: String, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| description: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| display_name: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| bundled: Option<bool>, | ||
| }, | ||
| Mcp { | ||
| server: McpServer, | ||
| #[serde(default, rename = "envKeys", skip_serializing_if = "Vec::is_empty")] | ||
| env_keys: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| description: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| timeout: Option<u64>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| socket: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| bundled: Option<bool>, | ||
| }, | ||
| } | ||
|
|
||
| impl Default for GooseExtension { | ||
| fn default() -> Self { | ||
| Self::Builtin { | ||
| name: String::new(), | ||
| description: None, | ||
| display_name: None, | ||
| timeout: None, | ||
| bundled: None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct GooseExtensionEntry { | ||
| pub extension: GooseExtension, | ||
| pub enabled: bool, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| pub config_key: Option<String>, | ||
| } | ||
|
|
||
| /// List Goose-owned extension definitions available to configure or enable. | ||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] | ||
| #[request( | ||
| method = "_goose/unstable/extensions/available", | ||
| response = GetAvailableExtensionsResponse | ||
| )] | ||
| pub struct GetAvailableExtensionsRequest {} | ||
|
|
||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct GetAvailableExtensionsResponse { | ||
| pub extensions: Vec<GooseExtension>, | ||
| } | ||
|
|
||
| /// List configured extensions and any warnings. | ||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] | ||
| #[request(method = "_goose/unstable/config/extensions/list", response = GetExtensionsResponse)] | ||
| pub struct GetExtensionsRequest {} | ||
| #[request( | ||
| method = "_goose/unstable/config/extensions/list", | ||
| response = GetConfigExtensionsResponse | ||
| )] | ||
| pub struct GetConfigExtensionsRequest {} | ||
|
|
||
| /// List configured extensions and any warnings. | ||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] | ||
| pub struct GetExtensionsResponse { | ||
| /// Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details. | ||
| pub extensions: Vec<serde_json::Value>, | ||
| pub struct GetConfigExtensionsResponse { | ||
| pub extensions: Vec<GooseExtensionEntry>, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the text UI opens the extensions manager, it still filters Useful? React with 👍 / 👎. |
||
| #[serde(default)] | ||
| pub warnings: Vec<String>, | ||
| } | ||
|
|
||
| pub type GetExtensionsRequest = GetConfigExtensionsRequest; | ||
| pub type GetExtensionsResponse = GetConfigExtensionsResponse; | ||
|
|
||
| /// Persist a new extension to the user's global goose config. | ||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] | ||
| #[request(method = "_goose/unstable/config/extensions/add", response = EmptyResponse)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct AddConfigExtensionRequest { | ||
| pub name: String, | ||
| /// Extension configuration. Must be a JSON object matching one of the | ||
| /// `ExtensionConfig` variants (e.g. `stdio`, `streamable_http`, `builtin`). | ||
| /// `name` and `enabled` are injected server-side. | ||
| #[serde(default)] | ||
| pub extension_config: serde_json::Value, | ||
| pub extension: GooseExtension, | ||
| #[serde(default)] | ||
|
Comment on lines
247
to
249
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Changing Useful? React with 👍 / 👎. |
||
| pub enabled: bool, | ||
| } | ||
|
|
@@ -183,11 +258,14 @@ pub struct RemoveConfigExtensionRequest { | |
| pub config_key: String, | ||
| } | ||
|
|
||
| /// Toggle the `enabled` flag for a persisted extension in the user's global goose config. | ||
| /// Set the `enabled` flag for a persisted extension in the user's global goose config. | ||
| #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] | ||
| #[request(method = "_goose/unstable/config/extensions/toggle", response = EmptyResponse)] | ||
| #[request( | ||
| method = "_goose/unstable/config/extensions/set-enabled", | ||
| response = EmptyResponse | ||
| )] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct ToggleConfigExtensionRequest { | ||
| pub struct SetConfigExtensionEnabledRequest { | ||
| pub config_key: String, | ||
| pub enabled: bool, | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an ACP/SDK client follows the generated
AddConfigExtensionRequest_unstabletype,server: McpServeradmitsMcpServerSse, but the config-add conversion rejects that exact variant withinvalid_paramsingoose_extension_to_config(crates/goose/src/acp/server/extensions.rs:290-292). In that scenario the typed request passes the published schema/Zod validation but can never be persisted, so clients can build a seemingly valid_goose/unstable/config/extensions/addpayload that always fails; use a Goose-specific MCP server union that excludes SSE or make the schema reflect the rejection.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because we reuse the standard ACP
McpServer, which includes SSE. Goose config persistence intentionally no longer supports SSE and rejects it at conversion time.