Compare commits

...

1 Commits

Author SHA1 Message Date
claude[bot] acdab2ecfc fix(mcp): apply workspace-canonical prefix in resolve_project_and_path fallback
When build_context is called with project_id for a project in an organization
workspace (e.g. "big-team/main"), the fallback path in resolve_project_and_path
was only prepending the project permalink ("main/tests/*") and ignoring the
workspace slug. Notes in organization workspaces are stored with the full
workspace-prefixed permalink ("big-team/main/tests/test"), so the GLOB query
never matched.

Fix by calling _canonical_memory_path_for_workspace in the fallback when a
workspace context is active. This produces the correct prefix for both
organization workspaces ("big-team/main/tests/*") and personal workspaces
("main/tests/*"), consistent with how the existing workspace_context and
cached_project paths already handle URLs that begin with the qualified prefix.

Add tests for:
- build_context skips URL detection when project_id is provided
- build_context correctly resolves workspace-prefixed paths in org workspaces

Closes #799

Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-05-08 02:30:13 +00:00
2 changed files with 84 additions and 7 deletions
+21 -7
View File
@@ -1163,13 +1163,27 @@ async def resolve_project_and_path(
resolved_path = normalized_path
if include_project:
# Trigger: project-prefixed permalinks are enabled and the path lacks a prefix
# Why: ensure memory URL lookups align with canonical permalinks
# Outcome: prefix the path with the active project's permalink
project_prefix = active_project.permalink
if resolved_path != project_prefix and not resolved_path.startswith(
f"{project_prefix}/"
):
resolved_path = f"{project_prefix}/{resolved_path}"
# Why: ensure memory URL lookups align with canonical permalinks for both
# local and workspace-routed projects; organization workspaces use a
# workspace-slug prefix (e.g. "big-team/main/tests/*") while personal
# workspaces and pure-local projects use just the project permalink
# Outcome: prefix the path using the workspace-canonical form when a
# workspace context is active; fall back to project-only prefix otherwise
fallback_workspace_ctx = current_workspace_permalink_context()
if fallback_workspace_ctx:
resolved_path = _canonical_memory_path_for_workspace(
workspace_slug=fallback_workspace_ctx.workspace_slug,
workspace_type=fallback_workspace_ctx.workspace_type,
project_permalink=active_project.permalink,
remainder=normalized_path,
include_project=True,
)
else:
project_prefix = active_project.permalink
if resolved_path != project_prefix and not resolved_path.startswith(
f"{project_prefix}/"
):
resolved_path = f"{project_prefix}/{resolved_path}"
return active_project, resolved_path, True
+63
View File
@@ -5,6 +5,7 @@ import pytest
from mcp.server.fastmcp.exceptions import ToolError
from basic_memory.mcp.tools import build_context
from basic_memory.mcp.tools.write_note import write_note
@pytest.mark.asyncio
@@ -224,3 +225,65 @@ async def test_build_context_markdown_not_found(client, test_project):
assert isinstance(result, str)
assert "No results found" in result
assert test_project.name in result
@pytest.mark.asyncio
async def test_build_context_skips_url_detection_when_project_id_provided(
monkeypatch, app, test_project, test_graph
):
"""project_id is authoritative, so memory URL discovery must not run first."""
async def fail_if_called(*args, **kwargs):
raise AssertionError("project_id routing should bypass URL discovery")
import importlib
bc_module = importlib.import_module("basic_memory.mcp.tools.build_context")
monkeypatch.setattr(bc_module, "detect_project_from_memory_url_prefix", fail_if_called)
result = await build_context(
url=f"memory://{test_project.name}/test/root",
project_id=test_project.external_id,
)
assert isinstance(result, dict)
@pytest.mark.asyncio
async def test_build_context_project_id_org_workspace_routing(app, test_project):
"""build_context must resolve the correct workspace-prefixed path when project_id is used
inside an organization workspace context.
Before the fix, the fallback in resolve_project_and_path only prepended the project
permalink (e.g. "test-project/tests/*") and ignored the workspace slug, so notes stored
as "team-paul/test-project/tests/<slug>" were never found.
"""
from basic_memory.workspace_context import workspace_permalink_context
workspace_slug = "team-paul"
# Write a note inside the org workspace context so its permalink is
# "team-paul/test-project/workspace-tests/team-note".
with workspace_permalink_context(workspace_slug, "organization"):
await write_note(
project=test_project.name,
title="Team Note",
directory="workspace-tests",
content="# Team Note\n\nContent written in org workspace context.",
)
# build_context with project_id inside the same workspace context should find the note.
with workspace_permalink_context(workspace_slug, "organization"):
result = await build_context(
url="memory://workspace-tests/*",
project_id=test_project.external_id,
)
assert isinstance(result, dict)
assert result["metadata"]["primary_count"] >= 1, (
"build_context should find the note written under the org workspace context"
)
permalinks = [r["primary_result"]["permalink"] for r in result["results"]]
assert any("team-paul" in p for p in permalinks), (
f"Expected workspace-prefixed permalink; got {permalinks}"
)