goose emits a roots notification that 2026-07-28 removed, and rmcp does not stop it
Kind: idiosyncrasy · Status: current · Last verified: 2026-08-24 · Clients: goose, openai-codex · Revisions: 2026-07-28
notifications/roots/list_changed was removed in 2026-07-28 under SEP-2575. goose still sends it, and by default negotiates 2026-07-28.
The call site has no version check:
async fn do_update_working_dir(&self, new_dir: PathBuf) -> Result<(), Error> {
let client = self.client.lock().await;
let shared = client.service().shared_working_dir();
*shared.write().await = new_dir;
client.peer().notify_roots_list_changed().await?;
Ok(())
}
The default lifecycle prefers the revision that removed it:
ClientLifecycleMode::Auto {
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
legacy_version: Some(ProtocolVersion::LATEST),
}
The SDK does not filter it
goose declares rmcp = "3.0.0"; the lockfile resolves 3.1.4, which is what was read.
Peer::notify_roots_list_changed is generated by the method! macro's peer_not arm. That arm constructs the notification and sends it, with no version parameter in scope:
($(#[$meta:meta])* peer_not $method:ident $Not:ident) => {
pub async fn $method(&self) -> Result<(), ServiceError> {
self.send_notification(ClientNotification::$Not($Not {
method: Default::default(),
extensions: Default::default(),
}))
.await?;
Ok(())
}
};
send_notification pushes onto the peer sink channel and awaits the responder. It reads no protocol version.
Every V_2026_07_28 comparison in rmcp 3.1.4 sits somewhere else: server-side request handling (handler/server.rs), _meta key requirements (model/meta.rs), and lifecycle and version negotiation (service/client.rs:278,313). None is on the outbound client notification path. uses_legacy_lifecycle looks like a candidate but only selects initialize-versus-discover, and its callers are the server handler and the streamable HTTP server transport.
RootsListChangedNotification also remains a first-class type in the 3.1.4 model, undeprecated and unbounded by version.
So the notification reaches the wire. A server that negotiated 2026-07-28 with goose receives notifications/roots/list_changed whenever the session changes working directory, for a method that revision does not define.
Codex does not hit it
Codex CLI is the other Rust client on rmcp, at 3.1.3 rather than 3.1.4, and it is unaffected — because it does not implement roots at all. Its ClientHandler has no list_roots, roots is absent from its declared capabilities, and notify_roots_list_changed is never called. There is no working-directory change that could reach the notification path.
So the gap needs a caller, not just the SDK. A Rust client is exposed only once it opts into roots, which makes goose the case to watch rather than the first of many.
Scope
This is not goose-specific. rmcp is the official Rust SDK, and any Rust client that calls notify_roots_list_changed on a 2026-07-28 connection gets the same result. The SDK leaves version-appropriateness to the caller and no caller has a reason to expect that.
Practical impact is currently small, since roots is itself deprecated as of the same revision and few servers act on the notification. It matters more as a shape: the SDK exposes removed methods without guardrails, so the same pattern will recur for anything else 2026-07-28 dropped.
Sources
- crates/goose/src/agents/mcp_client.rs, commit
c5e5929a,crates/goose/src/agents/mcp_client.rs:721-726, 2026-08-23 - rmcp 3.1.4 source, service/client.rs and service.rs,
src/service/client.rs:1139-1161,1720, 2026-08-24 - codex-rs/rmcp-client/src/logging_client_handler.rs, the full ClientHandler impl, commit
b705b6b0,codex-rs/rmcp-client/src/logging_client_handler.rs, 2026-08-24 - 2026-07-28 changelog, major change 5, 2026-08-23