Skip to content

fix(mcp): fix small note-tool defects from the v0.24.0 live run - #1705

Merged
phernandez merged 3 commits into
mainfrom
fix/1634-note-tool-defects
Oct 9, 2026
Merged

phernandez merged 3 commits into
mainfrom
fix/1634-note-tool-defects

Conversation

@phernandez

@phernandez phernandez commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Fixes #1634.

Summary

1. Out-of-range line read

  • slice_note_content (markdown/sections.py) returns a NoteSliceError when a line range starts after the last line. The message is start_line 500 is past the end of the document (17 lines). Before this change it clamped only the end, which produced the inverted range "Lines 500-17".
  • The API turns this error into a 404 with that message as the detail, the same way it handles other slice failures. read_note (text and JSON), cat with max_tokens, and the CLI all report the message as an error.
  • cat slices plain line ranges on the client side, so that path raises the same message.
  • Line 1 of an empty document can still be read.

2. Markdown links that climb out of the project

  • New helper climbs_out_of_project(target, source_path) in markdown/path_links.py. It is true for a path target where resolve_project_path returns None.
  • Relation rows are built against the note's own path at four points: accepted write, move, the batch indexer, and the EntityService graph publish. All four now drop these targets instead of storing an unresolved links_to row that can never resolve.
  • Canonical Markdown is unchanged. Relations are derived state.
  • The rule applies to every path-shaped target, including wikilinks spelled [[../../x.md]]. feat(core): index Markdown links to project files #1514 already resolves those with the same rule.

3. write_note conflict names the wrong note after a move

  • The service outcome AlreadyExists now carries the NoteLocation of the note found at the path. On a lost create race (a 409 from create), it looks up the note that won the path.
  • NoteAlreadyExists in schemas/v2/note_write.py gains external_id: str | None = None and permalink: str | None = None. The change is additive: no field is renamed or removed, and a payload with only file_path still validates.
  • The tool now reports the stored permalink of the note at the path. In a workspace context it qualifies that permalink the same way the success path does. The JSON payload adds external_id and sets file_path.
  • If the server cannot name the note (for example, an older server), the suggested edit_note/read_note identifier falls back to the file path, not the permalink computed from the request.

4. recent_activity

  • The tool description, docstring, and prompt no longer say "all projects" for a bare call. A bare call uses the active project, then the default project. Discovery across projects happens only when neither resolves. The prompt header now reads "the default project".
  • When a tool returns an empty list, FastMCP sends no content blocks at all, so a text-only client got nothing. A new EmptyListResultMiddleware (mcp/empty_results.py) adds a single [] text block in that case: the same JSON shape a non-empty list renders as, so clients that parse the text (joined or not) still get valid JSON. Structured content stays {"result": []}. The middleware applies to every tool, so tail gets the same fix.
  • The server instructions now say find needs project when no project-qualified path is given, alongside grep and tail.

5. Local authorship keys

Already resolved on main by 91d79bd. This PR does not touch it.

Behavior changes worth noting

  • Two existing tests pinned the old behavior and are updated:
    • read_note(start_line=1000) used to return empty content. It now raises the past-end error.
    • The MCP conflict payload used to have file_path: None. It now holds the existing note's path.
  • The already_exists HTTP payload now includes external_id and permalink. The cloud consumes this schema, and the addition is backward compatible.

Verification

  • just fast-check: lint, format, and typecheck all clean.
  • BASIC_MEMORY_TESTMON_SELECT_FLAGS="--import-mode=importlib" just fast-test <touched test paths>: passed.
  • just test-sqlite:
    • unit: 7962 passed, 58 skipped
    • integration: 688 passed, 15 skipped
  • just man-regen: all pages already match their source.

🤖 Generated with Claude Code

https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea

Fixes four defects reported in #1634.

1. A line read that starts past the end of the document now fails with
   "start_line N is past the end of the document (M lines)" instead of
   rendering an inverted "Lines 500-17" range. The slicer returns the error
   (the API answers 404 with that detail, like other slice failures), and
   cat's client-side range path raises the same message.

2. A path link that climbs past the project root is no longer stored as a
   permanently unresolved relation. Relation rows are built at write, move,
   and index time against the note's own path, and a path target that
   resolve_project_path cannot place in the project is dropped there.
   Relations are derived state; canonical Markdown is unchanged.

3. A write_note conflict names the note that owns the path. The API's
   already_exists outcome gains optional external_id and permalink fields
   (additive; older payloads still validate). The tool reports that stored
   permalink, external_id, and file_path instead of the permalink computed
   from the request, which after a move named the moved note.

4. recent_activity: the tool and prompt text no longer promise all
   projects when a bare call uses the default project; an empty list result
   now carries a "[]" text block plus a sentence, so text-only clients see
   an answer while structured content stays {"result": []}; the server
   instructions say find needs a project or a project-qualified path.

Item 5 (local authorship keys) was already resolved by 91d79bd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea
Signed-off-by: phernandez <paul@basicmachines.co>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T03:31:56.629248Z 04d4684 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

A second prose block broke clients that join text blocks and parse the
result as JSON. "[]" alone is explicit for text-only clients and keeps
the joined text valid JSON.

Refs #1634

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea
Signed-off-by: phernandez <paul@basicmachines.co>

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

Copy link
Copy Markdown

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: f65fceaf33

ℹ️ 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".

Comment thread src/basic_memory/mcp/prompts/recent_activity.py Outdated
A bare prompt call can resolve to the default project or, with no
default, to every project. The header claimed "the default project" in
both cases. It now names a project only when the caller passed one; the
tool's own summary states which scope applied.

Refs #1634

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez
phernandez merged commit 861ed7d into main Oct 9, 2026
34 checks passed
@phernandez
phernandez deleted the fix/1634-note-tool-defects branch October 9, 2026 03:51
phernandez added a commit that referenced this pull request Oct 9, 2026
Entries for #1697, #1698, #1699, #1700, #1701, #1702, #1704 and #1705,
which landed without changelog edits so the parallel PRs would not
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea
Signed-off-by: phernandez <paul@basicmachines.co>
phernandez added a commit that referenced this pull request Oct 9, 2026
Entries for #1697, #1698, #1699, #1700, #1701, #1702, #1704 and #1705,
which landed without changelog edits so the parallel PRs would not
conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APFUk2bjEwMptMQqpRhjea
Signed-off-by: phernandez <paul@basicmachines.co>
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.

Small note-tool defects from the v0.24.0 live run

1 participant