Repository navigation
feat(SESF-86): take the current project from the client's MCP roots - #98
Conversation
Claude Desktop Code-tab sessions connect before they enter the project, so their X-Project-Root header is empty and the read tools refused calls that named no project. A spike showed that Claude Code (CLI 2.1.312 and the Desktop Code tab 2.1.309) answers roots/list with the project folder, both at connect and at call time, and re-initializes after a server restart. Codex 0.163.0 declares no roots. - http_server: run stateful, streamed MCP sessions so a tool call can ask the client for its roots. - client_roots: ask roots/list once per MCP session on the tool call's stream (5 s cap), keep the first file:// root, forget on notifications/roots/list_changed. - tools: scope order is project_root, then client roots, then the header, then refusal; the Scope line says "from client roots". - hooks/memory-session-start.py: carry Mcp-Session-Id, read SSE replies, delete the session after each call. It still works against a stateless server, so install the hook before restarting. - Docs: CLAUDE.md, AGENTS.md, README and Overview.
|
@coderabbitai review |
✅ Action performed
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
| Layer / File(s) | Summary |
|---|---|
Client-root scope resolution client_roots.py, tools.py, tests/test_client_roots.py, AGENTS.md, CLAUDE.md |
The server validates and caches the first local MCP root per session. Search and statistics tools use it before X-Project-Root when project_root is absent. |
Stateful streamed MCP server http_server.py, tests/test_client_roots.py |
The server registers root-change handling and uses stateful streamed sessions instead of stateless JSON responses. |
Session-aware memory hook hooks/memory-session-start.py, tests/test_memory_session_start.py |
The hook retains session IDs, sends session headers, parses JSON or SSE responses, and deletes sessions after tool calls. |
Scope and session guidance README.md, AGENTS.md, CLAUDE.md |
The documentation describes MCP-root scope selection, header fallback, and streamed session behavior. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant MemoryHook
participant HTTPServer
participant MCPClient
MemoryHook->>HTTPServer: initialize stateful MCP session
HTTPServer-->>MemoryHook: return session ID and streamed response
MemoryHook->>HTTPServer: send tool call with session ID
HTTPServer->>MCPClient: request client roots when scope is needed
MCPClient-->>HTTPServer: return first local file root
HTTPServer-->>MemoryHook: return SSE tool result
MemoryHook->>HTTPServer: close session
Possibly related PRs
- lbruton/SessionFlow#93: Both changes address Desktop project-scope handling.
- lbruton/SessionFlow#87: This change preserves the header fallback introduced there.
- lbruton/SessionFlow#78: This change updates the session-start hook’s MCP contract.
Merge Risk: 
Minimal · previous review c0886
-
The supplied evidence does not establish a blocker to merging. Client compatibility remains worth checking during rollout.
Pre-merge checks: 
4
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Roots arrive through streams of light
Sessions stay through day and night
Scope checks roots, then headers fall
SSE carries answers to all
Closed sessions leave no trace behind
Comment @coderabbitai help to get the list of available commands.
Up to standards ✅🟢 Issues
|
| Category | Results |
|---|---|
| CodeStyle | 1 minor |
🟢 Metrics 110 complexity · 1 duplication
Metric Results Complexity 110 Duplication 1
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c0886e85c0
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A stateful session task keeps the context of its initialize request, so the ProjectMiddleware ContextVar held the first request's header. The scoped tools and cleanup_sessions now read the header from server.request_context.request and fall back to the ContextVar only when there is no HTTP request (Codex review).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59b5f4f625
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sessions whose client exits without a DELETE are freed after 30 minutes. The value matches the mcp 1.30 default, but the documented expiry no longer depends on it (Codex review).
SESF-86. The read tools take the current project from the client's MCP roots when the call names none.
Why
Claude Desktop Code-tab sessions connect before they enter the project, so their
X-Project-Rootheader is empty and SESF-85 refuses their unscoped calls. A spike against a throwaway stateful server showed:roots/listanswerlistChangedlistChangedBoth Claude Code clients re-initialized by themselves after a server restart.
Changes
http_server.py:StreamableHTTPSessionManager(stateless=False, json_response=False). The roots request travels on the tool call's response stream, which needs stateful, streamed sessions.client_roots.py(new): asksroots/listonce per MCP session (5 s cap), keeps the firstfile://root, and forgets every answer onnotifications/roots/list_changed. Clients that declare no roots are not asked.tools.py: scope order isproject_root, then client roots, then the header, then refusal. TheScope:line saysfrom client roots.hooks/memory-session-start.py: carriesMcp-Session-Id, reads SSE replies and deletes its session after each call. It still works against a stateless server.Deploy order
~/.claude/hooks/and~/.codex/hooks/. The old hook breaks against the stateful server; the new one works against both.launchctl kickstart -k.Tests
tests/test_client_roots.py: URI parsing, caching, timeout, errors,list_changed, and source order over a real in-memory MCP session.tests/test_memory_session_start.py: a stateful mode in the fake server, plus an end-to-end run of the hook against the SDK's stateful manager under uvicorn.ruff check .clean; 846 passed, 120 skipped.Desktop chat (
mcp-remotebridge) and OpenCode are untested: OpenCode's API key has expired.Summary by CodeRabbit