Skip to content

fix(mcp): add project detection from memory:// URLs in edit_note and delete_note#668

Merged
phernandez merged 2 commits intomainfrom
fix/664-detect-project-url-prefix
Mar 16, 2026
Merged

fix(mcp): add project detection from memory:// URLs in edit_note and delete_note#668
phernandez merged 2 commits intomainfrom
fix/664-detect-project-url-prefix

Conversation

@phernandez
Copy link
Member

Summary

  • edit_note and delete_note were missing detect_project_from_url_prefix() that other tools (read_note, build_context, search_notes, read_content) already use
  • This caused incorrect cloud routing when using memory:// URLs without an explicit project parameter in multi-project setups
  • Added the same 4-line detection pattern before get_project_client() in both tools

Fixes #664

Test plan

  • just fast-check passes (lint, typecheck, impacted tests)
  • pytest tests/mcp/test_tool_edit_note.py tests/mcp/test_tool_delete_note.py -v passes
  • Verify multi-project routing works with memory://project-name/note URLs in edit_note and delete_note

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

Claude finished @phernandez's task in 1m 42s —— View job


PR Review

  • Read changed files
  • Review against team checklist
  • Post final review

Summary

This is a clean, minimal fix that closes a real routing gap. edit_note and delete_note were the only write-path tools accepting memory:// URLs that lacked project auto-detection — now they're consistent with all read-path tools.


Code Quality & Standards ✅

The implementation is correct and follows the established pattern exactly:

# delete_note.py:227-230 / edit_note.py:264-267
if project is None:
    detected = detect_project_from_url_prefix(identifier, ConfigManager().config)
    if detected:
        project = detected

Placement (before get_project_client()) is correct. Imports are correct. The 4-line block is byte-for-byte identical to read_note, build_context, search_notes, and read_content. No dead code, no commented blocks, line length fine.


Testing & Documentation ⚠️ One Gap

Missing tests for the new code paths. CLAUDE.md is explicit: "Coverage must stay at 100%". The new if project is None / detect / if detected block in both tools adds uncovered lines unless existing tests happen to call these tools with a memory://project-name/... URL and project=None. A quick grep confirms no such tests exist yet in test_tool_edit_note.py or test_tool_delete_note.py.

For consistency with test_project_context.py and the existing coverage standard, two small tests are needed — one per tool:

# tests/mcp/test_tool_edit_note.py
async def test_edit_note_detects_project_from_memory_url(...):
    result = await edit_note(
        identifier="memory://test-project/some-note",
        operation="append",
        content="extra",
        project=None,   # must be None to trigger detection
    )
    # assert it routed to test-project, not the default

# tests/mcp/test_tool_delete_note.py
async def test_delete_note_detects_project_from_memory_url(...):
    result = await delete_note(
        identifier="memory://test-project/some-note",
        project=None,
    )
    # assert routing was correct (False is fine if note doesn't exist)

Fix this →


Basic Memory Architecture ⚠️ move_note Also Affected

move_note.py accepts memory:// URLs in its identifier parameter (per its own docstring) but also lacks detect_project_from_url_prefix. This PR doesn't address it, which is fine for scope — but worth a follow-up issue so the routing gap is fully closed across all write tools.


Checklist

Item Status
Follows Basic Memory coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code
Unit tests for new code paths ⚠️ Missing
100% test coverage maintained ⚠️ At risk
No hardcoded secrets or credentials
Input validation for MCP tools
Proper error handling and logging
No emoji/unicode in console output

Verdict: Logically correct and architecturally sound. The only ask before merge is adding tests for the two new detection branches to maintain the 100% coverage requirement. The move_note gap is a good candidate for a follow-up issue.

Copy link

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

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

ℹ️ 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 on lines +227 to +228
if project is None:
detected = detect_project_from_url_prefix(identifier, ConfigManager().config)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Limit auto project routing to memory:// identifiers

This new routing block runs for every identifier, but detect_project_from_url_prefix() also matches plain path/permalink inputs (not just memory://...), so non-URL deletes now get silently re-routed when their first segment equals another project name. In a multi-project setup, a call like delete_note("research/archive") with project=None can now delete from project research instead of the default project where that path was intended, which is a destructive misroute; please gate this override to actual memory:// identifiers before setting project (and apply the same fix to the identical block in edit_note).

Useful? React with 👍 / 👎.

phernandez added a commit that referenced this pull request Mar 14, 2026
Address code review feedback on PR #668:

1. Gate detect_project_from_url_prefix() to only trigger for memory://
   identifiers. Plain paths like "research/note" could previously misroute
   to a project named "research" — now only explicit memory:// URLs
   trigger project detection for these destructive/mutating tools.

2. Add 6 tests covering:
   - Detection works with memory:// URLs (edit_note + delete_note)
   - Detection is NOT called for plain path identifiers
   - Detection is NOT called when project is explicitly provided

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

Claude finished @phernandez's task in 1m 3s —— View job


PR Review

  • Read changed files
  • Check detect_project_from_url_prefix implementation for Codex's concern
  • Review tests
  • Post final review

Summary

This PR adds detect_project_from_url_prefix() to edit_note and delete_note, making all write-path MCP tools consistent with the read-path tools. The latest commit (c553019) also addressed the two gaps raised in the previous review (missing tests) and the Codex concern (plain-path misrouting).


Code Quality & Standards ✅

Both implementations are correct and consistent with each other and with the rest of the codebase:

delete_note.py:231-234

if project is None and identifier.strip().startswith("memory://"):
    detected = detect_project_from_url_prefix(identifier, ConfigManager().config)
    if detected:
        project = detected

edit_note.py:268-271 — identical guard pattern.

The memory:// prefix check directly addresses the Codex P1 concern: plain paths like "research/note" are now explicitly excluded from detection. The detect_project_from_url_prefix function itself handles both formats internally, but the callers gate on startswith("memory://") first, making the guard unambiguous.

Comments follow the literate-programming style from CLAUDE.md (Trigger / Why / Outcome). Line lengths fine. No dead code.


Testing & Documentation ✅

The updated test files cover three scenarios per tool (6 new tests total):

Test What it covers
test_*_detects_project_from_memory_url Happy path: memory://project/path with project=None routes correctly
test_*_skips_detection_for_plain_path Guard: plain "test/note" does not call detect_project_from_url_prefix
test_*_skips_detection_when_project_provided Guard: explicit project= skips detection entirely

The mock-based skip tests (assert_not_called) are the right pattern here — they verify the conditional guard without relying on project name collisions. Coverage for the new branches is complete.


Checklist

Item Status
Follows Basic Memory coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code
Unit tests for new code paths
100% test coverage maintained
No hardcoded secrets or credentials
Input validation for MCP tools
Proper error handling and logging
No emoji/unicode in console output
Codex P1 concern (plain-path misrouting) addressed

Verdict: Ready to merge. All previous review gaps are resolved. The move_note gap mentioned in the prior review remains a valid follow-up issue, but it's out of scope for this PR.

@phernandez
Copy link
Member Author

Addressed review feedback in c553019:

  1. Gated detection to memory:// prefix onlydetect_project_from_url_prefix() now only triggers when the identifier starts with memory://. Plain paths like research/archive will no longer misroute to a project named research.

  2. Added 6 tests covering:

    • Detection works with memory:// URLs (edit_note + delete_note)
    • Detection is NOT called for plain path identifiers
    • Detection is NOT called when project is explicitly provided

phernandez and others added 2 commits March 15, 2026 18:43
…delete_note

edit_note and delete_note were missing detect_project_from_url_prefix()
which other tools (read_note, build_context, search_notes, read_content)
already use. This caused incorrect cloud routing when using memory://
URLs without an explicit project parameter in multi-project setups.

Fixes #664

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Address code review feedback on PR #668:

1. Gate detect_project_from_url_prefix() to only trigger for memory://
   identifiers. Plain paths like "research/note" could previously misroute
   to a project named "research" — now only explicit memory:// URLs
   trigger project detection for these destructive/mutating tools.

2. Add 6 tests covering:
   - Detection works with memory:// URLs (edit_note + delete_note)
   - Detection is NOT called for plain path identifiers
   - Detection is NOT called when project is explicitly provided

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez phernandez force-pushed the fix/664-detect-project-url-prefix branch from c553019 to 15154bf Compare March 15, 2026 23:45
@github-actions
Copy link
Contributor

github-actions bot commented Mar 15, 2026

Claude finished @phernandez's task in 1m 10s —— View job


PR Review

  • Read changed files
  • Review against team checklist
  • Post final review

Summary

This PR adds detect_project_from_url_prefix() to edit_note and delete_note, closing the write-path routing gap introduced when these tools were initially implemented without the detection pattern that the read-path tools already had. The latest commit also addressed the Codex P1 concern (plain-path misrouting) by gating detection on startswith("memory://").


Code Quality & Standards ✅

Both detection blocks are correct and consistent:

delete_note.py:231-234

if project is None and identifier.strip().startswith("memory://"):
    detected = detect_project_from_url_prefix(identifier, ConfigManager().config)
    if detected:
        project = detected

edit_note.py:268-271 — identical guard pattern.

Comments follow the literate-programming style from CLAUDE.md (Trigger / Why / Outcome). Placement (before get_project_client()) is correct. Imports are correct. Line lengths fine. No dead code.

The startswith("memory://") gate correctly addresses the Codex P1 concern: plain paths like "research/archive" are excluded from detection, preventing silent misrouting in multi-project setups.


Testing & Documentation ✅

6 new tests cover all three conditional branches per tool:

Test Coverage
test_*_detects_project_from_memory_url Happy path: memory://project/path routes correctly
test_*_skips_detection_for_plain_path Guard: plain path does not call detect_project_from_url_prefix
test_*_skips_detection_when_project_provided Guard: explicit project= skips detection entirely

The mock-based assert_not_called() tests are the right pattern for verifying conditional guards. The happy-path test in edit_note includes the stronger assertion f"project: {test_project.name}" in result, confirming routing went to the correct project. All new branches are covered — 100% coverage standard is maintained.


Checklist

Item Status
Follows Basic Memory coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code
Unit tests for new code paths
100% test coverage maintained
No hardcoded secrets or credentials
Input validation for MCP tools
Proper error handling and logging
No emoji/unicode in console output
Codex P1 concern (plain-path misrouting) addressed

Verdict: Ready to merge. All review gaps from prior rounds are resolved. The move_note routing gap (no detect_project_from_url_prefix) noted in the first review remains a valid follow-up issue — it's out of scope here but worth tracking.

@phernandez phernandez merged commit 1a6a655 into main Mar 16, 2026
25 checks passed
@phernandez phernandez deleted the fix/664-detect-project-url-prefix branch March 16, 2026 00:14
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.

[BUG]

1 participant