You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## 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
ClosesMrLesk#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>
Copy file name to clipboardExpand all lines: README.en.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -267,7 +267,7 @@ You can run `backlog init` (even if you already initialized Backlog.md) and choo
267
267
268
268
</details>
269
269
270
-
Use the shared `backlog` server name everywhere – the MCP server auto-detects whether the current directory is initialized and falls back to `backlog://init-required` when needed.
270
+
Use the shared `backlog` server name everywhere. The server finds the active project from your client's MCP roots, and re-resolves when you switch workspace or worktree. Until it finds one, it serves `backlog://init-required`. A single user-scope server covers every repo.
271
271
272
272
### Manual config
273
273
@@ -285,7 +285,7 @@ Use the shared `backlog` server name everywhere – the MCP server auto-detects
285
285
}
286
286
```
287
287
288
-
If your IDE can't set the process working directory for MCP servers, set `BACKLOG_CWD` as shown above.
288
+
Set `BACKLOG_CWD` to pin the server to one project and stop workspace following. Use it to always target the same backlog, or when your client can't report MCP roots.
289
289
If your IDE supports custom args but not env vars, you can also use `["mcp", "start", "--cwd", "/absolute/path/to/your/project"]`.
title: Resolve MCP project root from client workspace roots
4
+
status: Done
5
+
assignee:
6
+
- '@ycaptain'
7
+
created_date: '2026-06-14 06:58'
8
+
updated_date: '2026-06-15 17:04'
9
+
labels:
10
+
- mcp
11
+
- bug
12
+
dependencies: []
13
+
priority: high
14
+
---
15
+
16
+
## Description
17
+
18
+
<!-- SECTION:DESCRIPTION:BEGIN -->
19
+
20
+
The MCP server resolves its project root once at startup and only consults MCP roots in fallback mode, so a shared/user-scope server (or one launched in the main checkout while working in a git worktree) writes tasks to the wrong backlog directory. See #558. Extend the request-scoped MCP roots discovery introduced in #608 (BACK-434) to the normal (initialized) startup path so the server follows the client's workspace roots, while preserving explicit pins (--cwd/BACKLOG_CWD) and the behavior of clients without the roots capability.
21
+
22
+
<!-- SECTION:DESCRIPTION:END -->
23
+
24
+
## Acceptance Criteria
25
+
26
+
<!-- AC:BEGIN -->
27
+
28
+
-[x]#1 An unpinned shared/user-scope server resolves the project from the client's workspace roots instead of a frozen startup directory, and re-resolves on roots/list_changed.
29
+
-[x]#2 An explicit --cwd/BACKLOG_CWD pin keeps a fixed root and never queries client roots.
30
+
-[x]#3 When the client workspace has no backlog project (empty/unusable roots, or a folder without a backlog), an unpinned launch-directory project is kept rather than dropped to init-required; pin with --cwd/BACKLOG_CWD to target a fixed global backlog.
31
+
-[x]#4 Clients without the roots capability keep the previous process.cwd() behavior, and the constraints from #570 are preserved (each root checked directly, multi-root selects the first advertised root that has a backlog config, single-flight).
32
+
-[x]#5 Tests cover the git-worktree case from #558, the shared-server case, pin precedence, the nested monorepo case, and the no-roots fallback; the full suite passes.
33
+
<!-- AC:END -->
34
+
35
+
## Implementation Plan
36
+
37
+
<!-- SECTION:PLAN:BEGIN -->
38
+
39
+
1. Thread a pinned flag from src/commands/mcp.ts into createMcpServer, derived from whether the directory came from --cwd/BACKLOG_CWD or process.cwd().
40
+
2. Enable the existing request-scoped roots discovery on the normal (initialized) startup path too — gated by pinned — reusing upgradeToProject/downgradeToFallback/resolveFromRoots from #608 rather than rewriting the resolver.
41
+
3. Track startupHasProject so a normal baseline keeps its project when the client workspace has no backlog (only a fallback/init-required baseline downgrades); make upgradeToProject's no-op short-circuit cover the normal baseline.
5. Cover cases in src/test/mcp-workspace-root.test.ts; update mcp-roots-discovery.test.ts for the confirm-once behavior; update the README MCP section.
44
+
<!-- SECTION:PLAN:END -->
45
+
46
+
## Implementation Notes
47
+
48
+
<!-- SECTION:NOTES:BEGIN -->
49
+
50
+
Minimal patch on top of #608's request-scoped roots discovery — upgradeToProject/downgradeToFallback/resolveFromRoots are kept intact rather than rewriting the resolver.
51
+
52
+
- src/mcp/server.ts: the normal (initialized) startup path now also calls enableRootsDiscovery unless pinned; a startupHasProject flag makes a normal baseline keep its project when the client workspace has no backlog (only a fallback baseline reverts to init-required); upgradeToProject's no-op short-circuit covers the normal baseline so a client root equal to the launch dir does not re-register.
53
+
- src/commands/mcp.ts threads pinned from --cwd/BACKLOG_CWD vs process.cwd().
54
+
- Scope: this PR intentionally does NOT drop an unrelated launch project (e.g. a global ~/.backlog) or add monorepo-nesting rules; those were left out to keep it a minimal bugfix within the existing architecture. Pin with --cwd/BACKLOG_CWD to fix a global backlog.
55
+
- Behavior change (reviewer note): the normal/initialized path now issues one roots/list request to follow the client workspace (#608 issued none in normal mode); a pin opts out entirely.
56
+
57
+
Full suite passes; bun run check . and bunx tsc --noEmit clean.
0 commit comments