Skip to content

feat: MCP Roots support#7790

Merged
alexhancock merged 1 commit intomainfrom
alexhancock/issue-6556-mcp-roots
Mar 11, 2026
Merged

feat: MCP Roots support#7790
alexhancock merged 1 commit intomainfrom
alexhancock/issue-6556-mcp-roots

Conversation

@alexhancock
Copy link
Copy Markdown
Collaborator

@alexhancock alexhancock commented Mar 10, 2026

fixes #6556

needs modelcontextprotocol/rust-sdk#742

demo

demo of it with a server that has a tool which lists roots from client and returns them from the tool

Screenshot 2026-03-10 at 1 11 29 PM

@alexhancock alexhancock requested review from DOsinga and jamadeo March 10, 2026 17:12
Cargo.toml Outdated

[patch.crates-io]
v8 = { path = "vendor/v8" }
rmcp = { path = "../rust-sdk/crates/rmcp" }
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

will update this when modelcontextprotocol/rust-sdk#742 merges and releases

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d68b492bc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Cargo.toml Outdated

[patch.crates-io]
v8 = { path = "vendor/v8" }
rmcp = { path = "../rust-sdk/crates/rmcp" }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Badge Remove machine-local rmcp patch override

The new [patch.crates-io] entry hard-pins rmcp to ../rust-sdk/crates/rmcp, which is outside this repository and not present in a standard checkout. That makes dependency resolution fail for contributors/CI that only have this repo, so the project cannot build or test without an extra unpublished sibling directory.

Useful? React with 👍 / 👎.

let timeout_duration =
Duration::from_secs(timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT));

let roots_dir = std::env::current_dir().unwrap_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use session working dir for streamable HTTP roots

This sets MCP roots for streamable HTTP extensions from std::env::current_dir() instead of the per-session working_dir passed into ExtensionManager::add_extension. In sessions started with a different workspace path, servers that call roots/list will be given the wrong directory and may operate on the wrong files or fail to find project data.

Useful? React with 👍 / 👎.

}

fn working_dir_roots(dir: &std::path::Path) -> ListRootsResult {
let uri = format!("file://{}", dir.display());
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 Build roots URI with proper file-URL encoding

Constructing the root URI via string formatting (file://{dir.display()}) does not perform file-URL normalization/escaping, so paths with spaces, #, non-UTF-8 bytes, or Windows separators produce invalid or ambiguous URIs. MCP servers consuming roots/list can misparse or reject these roots; use a file-URL constructor instead of raw formatting.

Useful? React with 👍 / 👎.

@DOsinga
Copy link
Copy Markdown
Collaborator

DOsinga commented Mar 10, 2026

the codex comments seem worth paying attention to, especially the one about the current folder

and there's the thing, we talked about this before, but should the working_dir be the root for the MCP servers? that means that technically they shouldn't have access to anything outside the working_dir. our developer extension definitely does that and would then ignore the root. ¯_(ツ)_/¯

@alexhancock alexhancock force-pushed the alexhancock/issue-6556-mcp-roots branch from 3d68b49 to b5cda10 Compare March 11, 2026 14:06
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5cda109bc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +277 to +279
let roots_dir = effective_working_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
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 Derive roots path from validated working directory

When working_dir is provided but does not exist, this function logs a warning and skips command.current_dir(dir), so the MCP process runs in the parent process directory; however, roots_dir is still taken from the invalid effective_working_dir. In that scenario, roots/list returns a non-existent root that does not match the server's actual cwd, and root-aware MCP servers can fail file operations even though the extension started successfully.

Useful? React with 👍 / 👎.

@alexhancock alexhancock force-pushed the alexhancock/issue-6556-mcp-roots branch from b5cda10 to 3d64f80 Compare March 11, 2026 14:37
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d64f80e33

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

&self,
_context: RequestContext<RoleClient>,
) -> impl std::future::Future<Output = Result<ListRootsResult, ErrorData>> + Send + '_ {
std::future::ready(Ok(working_dir_roots(&self.working_dir)))
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 Update roots after working directory changes

list_roots always returns the working_dir captured when the MCP client is first connected, so roots become stale if a session's working directory is changed later (for example via ACP working_dir/update, which updates session state without recreating extension clients). In that scenario, root-aware MCP servers receive an outdated root and can resolve files against the wrong project until the extension is reconnected.

Useful? React with 👍 / 👎.

@alexhancock alexhancock force-pushed the alexhancock/issue-6556-mcp-roots branch from 3d64f80 to 255956e Compare March 11, 2026 14:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 255956e5d1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

let timeout_duration =
Duration::from_secs(timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT));

let roots_dir = std::env::current_dir().unwrap_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pass session working dir to streamable HTTP MCP clients

ExtensionManager::add_extension receives a per-session working_dir, but create_streamable_http_client always derives roots_dir from std::env::current_dir() before calling McpClient::connect. In sessions where the workspace differs from the process cwd, roots/list will advertise the wrong directory to streamable HTTP servers, so root-aware tools can read/write against the wrong project tree.

Useful? React with 👍 / 👎.

Comment on lines +277 to +279
let roots_dir = effective_working_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
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 Derive roots from the validated child-process working dir

child_process_client validates effective_working_dir before calling command.current_dir, but roots_dir is still taken from effective_working_dir even when that validation fails. If working_dir (or GOOSE_WORKING_DIR) points to a missing/non-directory path, the MCP process runs with a different cwd while roots/list advertises the invalid path, which can break root-based file operations.

Useful? React with 👍 / 👎.

}

fn working_dir_roots(dir: &std::path::Path) -> ListRootsResult {
let uri = format!("file://{}", dir.display());
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 Build roots URI via proper file-URL conversion

working_dir_roots creates the root URI with raw string formatting (file://{dir.display()}), which skips file-URL escaping/normalization. Paths containing spaces, #, non-UTF8 bytes, Windows separators, or relative segments can produce malformed or ambiguous URIs, and MCP servers that parse roots as URLs may reject or misresolve them.

Useful? React with 👍 / 👎.

@alexhancock alexhancock force-pushed the alexhancock/issue-6556-mcp-roots branch 2 times, most recently from 06f1483 to 997499c Compare March 11, 2026 16:39
@alexhancock alexhancock force-pushed the alexhancock/issue-6556-mcp-roots branch from 997499c to 49492a4 Compare March 11, 2026 17:15
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49492a4695

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1181 to +1185
if let Some(session) = self.sessions.lock().await.get(&req.session_id) {
session
.agent
.extension_manager
.update_working_dir(&path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drop sessions lock before awaiting roots update

on_update_working_dir borrows a session from self.sessions.lock().await.get(...) and then awaits extension_manager.update_working_dir, which keeps the sessions mutex held for the entire async update path. If any extension is slow or blocked while handling roots/list_changed, other ACP requests that need session lookup will block behind this lock, making the agent appear hung after a working-dir change.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

unclear to me how to test this in an ACP client, but if anything is bad in the UX we can followup.

@codefromthecrypt do you know how to trigger the working dir change in an example ACP client?

Comment on lines +851 to +853
let extensions = self.extensions.lock().await;
for (name, ext) in extensions.iter() {
if let Err(e) = ext.client.update_working_dir(new_dir.to_path_buf()).await {
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 Release extensions mutex before notifying MCP clients

ExtensionManager::update_working_dir holds self.extensions.lock() while awaiting each ext.client.update_working_dir(...) call. Because those calls perform network I/O (notify_roots_list_changed), one slow extension can block the extensions map lock and stall concurrent extension operations (tool listing/calls, add/remove) until the await completes.

Useful? React with 👍 / 👎.

@alexhancock alexhancock added this pull request to the merge queue Mar 11, 2026
Merged via the queue into main with commit f2de8b5 Mar 11, 2026
22 checks passed
@alexhancock alexhancock deleted the alexhancock/issue-6556-mcp-roots branch March 11, 2026 19:14
lifeizhou-ap added a commit that referenced this pull request Mar 12, 2026
* main: (270 commits)
  test(acp): align provider and server test parity (#7822)
  fix(acp): register MCP extensions when resuming a session (#7806)
  fix(goose): load .gitignore in prompt_manager for hint file filtering (#7795)
  fix: remap max_completion_tokens to max_tokens for OpenAI-compatible providers (#7765)
  fix(openai): preserve Responses API tool call/output linkage (#7759)
  chore(deps): bump @hono/node-server from 1.19.9 to 1.19.11 in /evals/open-model-gym/mcp-harness (#7687)
  fix: return ContextLengthExceeded when prompt exceeds effective KV cache size (#7815)
  feat: MCP Roots support (#7790)
  fix(google): use `includeThoughts/part.thought` for thinking handling (#7593)
  refactor: simplify tokenizer initialization — remove unnecessary Result wrapper (#7744)
  Fix model selector showing wrong model in tabs (#7784)
  Stop collecting goosed stderr after startup (#7814)
  fix: avoid word splitting by space for windows shell commands (#7781) (#7810)
  Simplify and make it not break on linux (#7813)
  Add preferred microphone selection  (#7805)
  Remove dependency on posthog-rs (#7811)
  feat: load hints in nested subdirs (#7772)
  feat(acp): add read tool and delegate filesystem I/O to ACP clients (#7668)
  Support secret interpolation in streamable HTTP extension URLs (#7782)
  More logging for command injection classifier model training (#7779)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Full Support for MCP Roots Protocol in Goose

2 participants