feat(mcp): serve notes as MCP resources via their memory:// URLs - #1394
Conversation
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5e4f8d727
ℹ️ 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".
Codex review of #1394, first pass, all three findings: - The template treated its first segment as a project unconditionally, so an unprefixed permalink like memory://docs/roadmap failed or read the wrong project. Reading now goes through resolve_project_and_path — the same routing the tools use — so a project prefix routes to the project and anything else resolves in the active/default project. - A note whose canonical permalink ends in /info was unreachable served (the {workspace}/{project}/info template wins that shape): project_info now falls back to the note when the workspace/project route does not resolve, so the extensionless permalink reads whichever template wins. - ToolError no longer collapses to 'No note': only a confirmed not-found gets that message; auth, server, and transport failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ff3f0e125
ℹ️ 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".
Codex review of #1394, second pass: - The client was opened for the default project before routing, so a URI naming a cloud-mode project resolved entities on the default backend. A first segment that names a configured project now opens that project's own client (its transport, auth, and workspace), with errors surfacing rather than falling back; an unconfigured segment uses the default client and resolve_project_and_path's active-project fallback, as before. - Nothing reserves 'man' as a project name: when no manual page matches, memory://man/<path> now falls through to a note in a project really named man, and when both miss, the manual's hint is the error. - Entity resolution is strict for resources: a resources/read returns the addressed document or an error, never the fuzzy-search guess the tools use for suggestions — writing the route test surfaced that a miss could fuzzy-match an unrelated note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a260d1ba73
ℹ️ 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".
…auses Codex review of #1394, third pass: - With permalinks_include_project=False the first URI segment is a directory even when it collides with a configured project's name — the active project owns unprefixed permalinks, so pre-routing now happens only when the config says memory URLs carry a project prefix. - Not-found is now its own type (NoteNotFoundError): the manual-namespace and /info-shape fallbacks swap in their own error only when the note is confirmed missing; an operational note failure (auth, server, transport) keeps its cause through both dispatchers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5e5c95859
ℹ️ 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".
Codex review of #1394, fourth pass: the not-found mapping covered the whole routing-and-read block, so a stale configured project whose backend answers 'Project not found' was reported as a missing note — and the man/info fallbacks would treat it as a confirmed miss. The mapping now wraps only the strict entity-resolve call; routing and content-read failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fa4ded9178
ℹ️ 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".
…lient Codex review of #1394, fifth pass: the pre-routing helper only inspected local config.projects, so a workspace-qualified URI like memory://personal/main/docs/report opened the default project's client and resolved on the wrong transport. Routing now uses the same detect_project_from_memory_url_prefix the tools call before creating their client — covering configured local projects and workspace routes alike — still gated on permalinks_include_project, and with detection failures surfacing through the existing error mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 436834130e
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6ad6433b7
ℹ️ 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".
Codex review of #1394, seventh pass: the permalinks_include_project gate skipped prefix detection entirely, so with prefixes disabled a workspace-qualified URI opened the default project's transport. Cloud permalinks stay workspace-qualified regardless of that flag (see test_team_workspace_write_stores_complete_permalink_when_project_prefixes_disabled), so detection now always runs and the flag drops only a bare configured-local-project match — the directory-collision case it was added for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7361bf6f82
ℹ️ 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".
Codex review of #1394, eighth pass: response.json() and model_validate both raise ValueError, so a reachable info route answering with a malformed payload fell into the note fallback and could serve unrelated markdown in place of project statistics. Payload failures now raise a ResourceError naming the route; the fallback triggers only for routing failures, as intended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58b799d8e0
ℹ️ 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".
Codex review of #1394, ninth pass: forced-local transports (streamable-http, sse) surface an unknown compound workspace/project route as ToolError rather than ValueError/RuntimeError, so the /info note fallback never ran and an extensionless info-note URI was unreadable on those transports. A ToolError naming a missing route now enters the fallback; every other ToolError keeps its cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
The memory://{workspace}/{project}/info resource returned a Pydantic
model, which FastMCP's resource runtime rejects (contents must be str,
bytes, or list[ResourceContent]) — every served read failed; its tests
only ever called the handler directly, so nothing noticed. Return the
validated response as JSON text and parse it in the direct-call tests.
Found by the note-resource tests, which read through a real session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp
Signed-off-by: phernandez <paul@basicmachines.co>
Basic Memory hands out memory:// URLs everywhere — pages, prompts,
handoffs — but only the guide, the manual, and project info answered
resources/read; a note URL returned Unknown resource. Close the gap:
- memory://{project}/{path*} returns the note's raw markdown exactly as
it sits on disk, frontmatter included. The identifier may be a
permalink, a title, or a file path (resolved through the same
knowledge/resolve endpoint the tools use, so there is no direct file
access and no traversal surface).
- Overlaps are answered, not fought over: template precedence between
overlapping matches is not guaranteed, so memory://man/... delegates
to the manual and {workspace}/{project}/info-shaped URIs are served by
project_info first, falling back to a genuine note named .../info when
no such workspace project exists.
- Unknown notes raise a ResourceError pointing at search_notes; unknown
projects surface the routing error; binary files are steered to the
read_content tool (only text is byte-exact).
- Server instructions note that any memory://<project>/<path> URL reads
as a resource.
Tests exercise the served path through a real client session (a live
Context, as production has), plus the man and info overlaps, the
info-named-note fallback, error branches, and the binary steer; the new
module is at 100% coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp
Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, first pass, all three findings: - The template treated its first segment as a project unconditionally, so an unprefixed permalink like memory://docs/roadmap failed or read the wrong project. Reading now goes through resolve_project_and_path — the same routing the tools use — so a project prefix routes to the project and anything else resolves in the active/default project. - A note whose canonical permalink ends in /info was unreachable served (the {workspace}/{project}/info template wins that shape): project_info now falls back to the note when the workspace/project route does not resolve, so the extensionless permalink reads whichever template wins. - ToolError no longer collapses to 'No note': only a confirmed not-found gets that message; auth, server, and transport failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, second pass: - The client was opened for the default project before routing, so a URI naming a cloud-mode project resolved entities on the default backend. A first segment that names a configured project now opens that project's own client (its transport, auth, and workspace), with errors surfacing rather than falling back; an unconfigured segment uses the default client and resolve_project_and_path's active-project fallback, as before. - Nothing reserves 'man' as a project name: when no manual page matches, memory://man/<path> now falls through to a note in a project really named man, and when both miss, the manual's hint is the error. - Entity resolution is strict for resources: a resources/read returns the addressed document or an error, never the fuzzy-search guess the tools use for suggestions — writing the route test surfaced that a miss could fuzzy-match an unrelated note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
…auses Codex review of #1394, third pass: - With permalinks_include_project=False the first URI segment is a directory even when it collides with a configured project's name — the active project owns unprefixed permalinks, so pre-routing now happens only when the config says memory URLs carry a project prefix. - Not-found is now its own type (NoteNotFoundError): the manual-namespace and /info-shape fallbacks swap in their own error only when the note is confirmed missing; an operational note failure (auth, server, transport) keeps its cause through both dispatchers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, fourth pass: the not-found mapping covered the whole routing-and-read block, so a stale configured project whose backend answers 'Project not found' was reported as a missing note — and the man/info fallbacks would treat it as a confirmed miss. The mapping now wraps only the strict entity-resolve call; routing and content-read failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
…lient Codex review of #1394, fifth pass: the pre-routing helper only inspected local config.projects, so a workspace-qualified URI like memory://personal/main/docs/report opened the default project's client and resolved on the wrong transport. Routing now uses the same detect_project_from_memory_url_prefix the tools call before creating their client — covering configured local projects and workspace routes alike — still gated on permalinks_include_project, and with detection failures surfacing through the existing error mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
The resource tests lived in tests/mcp with an in-memory client and a few monkeypatched paths; nothing in test-int drove resources/list and resources/read through the full MCP Client -> server -> FastAPI -> database flow. Cover the manual (listing, index, page spellings), a note read back at its project-prefixed, unprefixed, and file-path URIs, the workspace info template, and a miss surfacing as a missing-note error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, seventh pass: the permalinks_include_project gate skipped prefix detection entirely, so with prefixes disabled a workspace-qualified URI opened the default project's transport. Cloud permalinks stay workspace-qualified regardless of that flag (see test_team_workspace_write_stores_complete_permalink_when_project_prefixes_disabled), so detection now always runs and the flag drops only a bare configured-local-project match — the directory-collision case it was added for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, eighth pass: response.json() and model_validate both raise ValueError, so a reachable info route answering with a malformed payload fell into the note fallback and could serve unrelated markdown in place of project statistics. Payload failures now raise a ResourceError naming the route; the fallback triggers only for routing failures, as intended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, ninth pass: forced-local transports (streamable-http, sse) surface an unknown compound workspace/project route as ToolError rather than ValueError/RuntimeError, so the /info note fallback never ran and an extensionless info-note URI was unreadable on those transports. A ToolError naming a missing route now enters the fallback; every other ToolError keeps its cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
a3ff0c3 to
e7ec6d4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7ec6d49ca
ℹ️ 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".
Codex review of #1394, tenth pass: the man template registers before the notes template and wins the tie for memory://man/..., so the note fallback sitting in note_resource never ran on the served path — a project genuinely named man was unreachable through resources/read. manual_page now owns the fallback (confirmed note miss restores the manual's index hint; operational failures keep their cause) and the notes handler simply delegates, keeping both templates' answers identical whichever one matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7312652431
ℹ️ 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".
Codex review of #1394, first pass, all three findings: - The template treated its first segment as a project unconditionally, so an unprefixed permalink like memory://docs/roadmap failed or read the wrong project. Reading now goes through resolve_project_and_path — the same routing the tools use — so a project prefix routes to the project and anything else resolves in the active/default project. - A note whose canonical permalink ends in /info was unreachable served (the {workspace}/{project}/info template wins that shape): project_info now falls back to the note when the workspace/project route does not resolve, so the extensionless permalink reads whichever template wins. - ToolError no longer collapses to 'No note': only a confirmed not-found gets that message; auth, server, and transport failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, second pass: - The client was opened for the default project before routing, so a URI naming a cloud-mode project resolved entities on the default backend. A first segment that names a configured project now opens that project's own client (its transport, auth, and workspace), with errors surfacing rather than falling back; an unconfigured segment uses the default client and resolve_project_and_path's active-project fallback, as before. - Nothing reserves 'man' as a project name: when no manual page matches, memory://man/<path> now falls through to a note in a project really named man, and when both miss, the manual's hint is the error. - Entity resolution is strict for resources: a resources/read returns the addressed document or an error, never the fuzzy-search guess the tools use for suggestions — writing the route test surfaced that a miss could fuzzy-match an unrelated note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
…auses Codex review of #1394, third pass: - With permalinks_include_project=False the first URI segment is a directory even when it collides with a configured project's name — the active project owns unprefixed permalinks, so pre-routing now happens only when the config says memory URLs carry a project prefix. - Not-found is now its own type (NoteNotFoundError): the manual-namespace and /info-shape fallbacks swap in their own error only when the note is confirmed missing; an operational note failure (auth, server, transport) keeps its cause through both dispatchers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, fourth pass: the not-found mapping covered the whole routing-and-read block, so a stale configured project whose backend answers 'Project not found' was reported as a missing note — and the man/info fallbacks would treat it as a confirmed miss. The mapping now wraps only the strict entity-resolve call; routing and content-read failures keep their actionable cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
…lient Codex review of #1394, fifth pass: the pre-routing helper only inspected local config.projects, so a workspace-qualified URI like memory://personal/main/docs/report opened the default project's client and resolved on the wrong transport. Routing now uses the same detect_project_from_memory_url_prefix the tools call before creating their client — covering configured local projects and workspace routes alike — still gated on permalinks_include_project, and with detection failures surfacing through the existing error mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, seventh pass: the permalinks_include_project gate skipped prefix detection entirely, so with prefixes disabled a workspace-qualified URI opened the default project's transport. Cloud permalinks stay workspace-qualified regardless of that flag (see test_team_workspace_write_stores_complete_permalink_when_project_prefixes_disabled), so detection now always runs and the flag drops only a bare configured-local-project match — the directory-collision case it was added for. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, eighth pass: response.json() and model_validate both raise ValueError, so a reachable info route answering with a malformed payload fell into the note fallback and could serve unrelated markdown in place of project statistics. Payload failures now raise a ResourceError naming the route; the fallback triggers only for routing failures, as intended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Codex review of #1394, ninth pass: forced-local transports (streamable-http, sse) surface an unknown compound workspace/project route as ToolError rather than ValueError/RuntimeError, so the /info note fallback never ran and an extensionless info-note URI was unreadable on those transports. A ToolError naming a missing route now enters the fallback; every other ToolError keeps its cause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp Signed-off-by: phernandez <paul@basicmachines.co>
Follow-up to #1389/#1393 (#610 line of work).
What
Every
memory://URL Basic Memory hands out now answers the standard MCPresources/read. New resource template:The identifier may be a permalink, a title, or a file path — resolved through the same
knowledge/resolveendpoint the tools use, so there is no direct file access and no traversal surface.Why
We advertise
memory://URLs everywhere — the assistant guide, manual pages, conversation handoffs — and some of them were resources (ai_assistant_guide,memory://man, project info) while note URLs returnedUnknown resource. A client that tried the standard MCP way to read the very URI we handed it got an error. Same papercut class as thesearch(3)alias finding on #1389: the URI invites a read we didn't answer.Overlaps are answered, not fought over
FastMCP's precedence between overlapping template matches is version-sorted, not specificity-sorted — effectively undefined for unversioned templates. So the notes handler behaves correctly whichever template wins:
memory://man/...→ delegates to the manual page handler (identical bytes either way)memory://{ws}/{proj}/info→ served byproject_infofirst; a genuine note named.../infois still read when no such workspace project exists (test covers both directions)Error behavior
ResourceErrorpointing atsearch_notesResourceErrorsteering to theread_contenttool; only text comes back byte-exactFound and fixed along the way
The new tests read resources through a real client session instead of calling handlers directly — and that immediately exposed a pre-existing break: the
memory://{workspace}/{project}/inforesource returned a Pydantic model, which FastMCP's resource runtime rejects (contents must be str, bytes, or list[ResourceContent]), so every served read of project info failed on main. It now returns the validated response as JSON text; its direct-call tests parse the JSON.Tests
tests/mcp/test_note_resources.pyreads through a real in-memory client session (liveContext, exactly as production) rather thanmcp.read_resource()— that distinction caught asession_idexplosion during development. Module at 100% coverage.Not in this PR
memory://workspace/project/path) — the first segment is the project; a qualified fallback is a follow-up.resources/subscribefor live note updates — the story resources unlock that tools can't; future slice.🤖 Generated with Claude Code
https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp