BACK-508 - Resolve MCP project root from client workspace roots#687
Merged
Conversation
a75fa5d to
e81a54a
Compare
26074a8 to
855c315
Compare
The MCP server froze its project root at startup and only consulted client MCP roots in fallback (uninitialized) mode, so a shared/user-scope server -- or one launched in the main checkout while working in a git worktree -- wrote tasks to the wrong backlog directory. Extend the request-scoped roots discovery from MrLesk#608 (BACK-434) to the normal (initialized) startup path: unless pinned, the server now follows the client's workspace roots and re-resolves on roots/list_changed. A startupHasProject flag makes a launch-directory project return to itself when the client workspace has no backlog (rather than dropping to init-required); a fallback baseline still reverts to init-required. An explicit --cwd/BACKLOG_CWD pin opts out of roots entirely. The existing upgradeToProject/downgradeToFallback/resolveFromRoots and the MrLesk#570 constraints are kept intact. Behavior change: the normal/initialized path now issues one roots/list request to follow the client workspace (MrLesk#608 issued none in normal mode); a pin opts out. Closes MrLesk#558.
855c315 to
3b175ae
Compare
Owner
|
Thanks @ycaptain |
kuwork
added a commit
to kuwork/Backlog.md
that referenced
this pull request
Jun 23, 2026
…sk#687) ## Summary The MCP server chose its project root once at startup and only looked at the client's MCP roots when it had no project of its own (fallback mode). A server shared across projects, or one started in the main checkout while the active workspace is a git worktree, therefore kept writing tasks to whatever directory it launched in (MrLesk#558). This makes an already-initialized server follow the client workspace as well, reusing the request-scoped roots discovery that MrLesk#608 added for fallback mode. On its first request the server asks the client for its roots and switches to the matching project, then re-checks on every `roots/list_changed`. If the workspace has no backlog of its own, the server stays on its launch directory instead of dropping to "init required". Passing `--cwd` or `BACKLOG_CWD` pins the root and turns roots discovery off, which is what a deliberately shared server wants. ## Behavior change An initialized, unpinned server now sends one `roots/list` request on its first handled request, where before it sent none. If the client's root matches the launch directory, nothing is re-registered. Clients that do not advertise the roots capability keep the old `process.cwd()` behavior. A pinned server sends no roots requests at all. ## Implementation notes * `src/commands/mcp.ts` marks the root as pinned when it comes from `--cwd` or `BACKLOG_CWD`, and unpinned when it is only `process.cwd()`. * `src/mcp/server.ts` adds a `startupHasProject` flag so an initialized server returns to its own project when the client offers nothing usable, while a fallback server still reverts to init-required. * `upgradeToProject`, `downgradeToFallback` and `resolveFromRoots` are unchanged, as are the root checks from MrLesk#570: each root is checked on its own, no walking up the tree, one resolution in flight at a time. * README now documents workspace following and the `--cwd` / `BACKLOG_CWD` pin. ## Related Tasks Closes MrLesk#558. Task `backlog/tasks/back-558 - Resolve-MCP-project-root-from-client-workspace-roots.md` (BACK-558), acceptance criteria and DoD checked. ## Validation * `bun test src/test/mcp-workspace-root.test.ts src/test/mcp-roots-discovery.test.ts` (15 pass): following the client workspace from an unrelated launch directory, a pin that sends zero roots requests, a monorepo package kept, the launch project kept when the workspace has no backlog, the return to the launch project after the workspace loses its backlog, and the worktree case from MrLesk#558. * `bunx tsc --noEmit` * `bun run check .` --------- Co-authored-by: Alex Gavrilescu <leskcorp@gmail.com>
yaleh
pushed a commit
to yaleh/epicd
that referenced
this pull request
Jul 7, 2026
…sk#687) ## Summary The MCP server chose its project root once at startup and only looked at the client's MCP roots when it had no project of its own (fallback mode). A server shared across projects, or one started in the main checkout while the active workspace is a git worktree, therefore kept writing tasks to whatever directory it launched in (MrLesk#558). This makes an already-initialized server follow the client workspace as well, reusing the request-scoped roots discovery that MrLesk#608 added for fallback mode. On its first request the server asks the client for its roots and switches to the matching project, then re-checks on every `roots/list_changed`. If the workspace has no backlog of its own, the server stays on its launch directory instead of dropping to "init required". Passing `--cwd` or `BACKLOG_CWD` pins the root and turns roots discovery off, which is what a deliberately shared server wants. ## Behavior change An initialized, unpinned server now sends one `roots/list` request on its first handled request, where before it sent none. If the client's root matches the launch directory, nothing is re-registered. Clients that do not advertise the roots capability keep the old `process.cwd()` behavior. A pinned server sends no roots requests at all. ## Implementation notes * `src/commands/mcp.ts` marks the root as pinned when it comes from `--cwd` or `BACKLOG_CWD`, and unpinned when it is only `process.cwd()`. * `src/mcp/server.ts` adds a `startupHasProject` flag so an initialized server returns to its own project when the client offers nothing usable, while a fallback server still reverts to init-required. * `upgradeToProject`, `downgradeToFallback` and `resolveFromRoots` are unchanged, as are the root checks from MrLesk#570: each root is checked on its own, no walking up the tree, one resolution in flight at a time. * README now documents workspace following and the `--cwd` / `BACKLOG_CWD` pin. ## Related Tasks Closes MrLesk#558. Task `backlog/tasks/back-558 - Resolve-MCP-project-root-from-client-workspace-roots.md` (BACK-558), acceptance criteria and DoD checked. ## Validation * `bun test src/test/mcp-workspace-root.test.ts src/test/mcp-roots-discovery.test.ts` (15 pass): following the client workspace from an unrelated launch directory, a pin that sends zero roots requests, a monorepo package kept, the launch project kept when the workspace has no backlog, the return to the launch project after the workspace loses its backlog, and the worktree case from MrLesk#558. * `bunx tsc --noEmit` * `bun run check .` --------- Co-authored-by: Alex Gavrilescu <leskcorp@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The MCP server chose its project root once at startup and only looked at the client's MCP roots when it had no project of its own (fallback mode). A server shared across projects, or one started in the main checkout while the active workspace is a git worktree, therefore kept writing tasks to whatever directory it launched in (#558).
This makes an already-initialized server follow the client workspace as well, reusing the request-scoped roots discovery that #608 added for fallback mode. On its first request the server asks the client for its roots and switches to the matching project, then re-checks on every
roots/list_changed. If the workspace has no backlog of its own, the server stays on its launch directory instead of dropping to "init required". Passing--cwdorBACKLOG_CWDpins the root and turns roots discovery off, which is what a deliberately shared server wants.Behavior change
An initialized, unpinned server now sends one
roots/listrequest on its first handled request, where before it sent none. If the client's root matches the launch directory, nothing is re-registered. Clients that do not advertise the roots capability keep the oldprocess.cwd()behavior. A pinned server sends no roots requests at all.Implementation notes
src/commands/mcp.tsmarks the root as pinned when it comes from--cwdorBACKLOG_CWD, and unpinned when it is onlyprocess.cwd().src/mcp/server.tsadds astartupHasProjectflag so an initialized server returns to its own project when the client offers nothing usable, while a fallback server still reverts to init-required.upgradeToProject,downgradeToFallbackandresolveFromRootsare unchanged, as are the root checks from BACK-406 - Use MCP roots for project root discovery when cwd fails #570: each root is checked on its own, no walking up the tree, one resolution in flight at a time.--cwd/BACKLOG_CWDpin.Related Tasks
Closes #558. Task
backlog/tasks/back-558 - Resolve-MCP-project-root-from-client-workspace-roots.md(BACK-558), acceptance criteria and DoD checked.Validation
bun test src/test/mcp-workspace-root.test.ts src/test/mcp-roots-discovery.test.ts(15 pass): following the client workspace from an unrelated launch directory, a pin that sends zero roots requests, a monorepo package kept, the launch project kept when the workspace has no backlog, the return to the launch project after the workspace loses its backlog, and the worktree case from [Bug]: MCP server writes to main repo instead of git worktree #558.bunx tsc --noEmitbun run check .