Skip to content

Commit 18cb5eb

Browse files
jope-bmclaude
authored andcommitted
feat(mcp): expose note external_id in list_directory and recent_activity output
Web-app deep links are built from a note's external_id, and the hosted cloud MCP appends a link template to these tools' results that agents fill with that id on request. Neither tool rendered the id in its text output, so the template referenced a value the model could not see (basicmachines-co/basic-memory-cloud#1437 review). list_directory file rows gain a trailing "| id: <uuid>" column; recent_activity entity rows gain "[id: <uuid>]". Both values were already present in the underlying API payloads (DirectoryNode, EntitySummary) - this only surfaces them in the rendered text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Joe P <joe@basicmemory.com>
1 parent 39fe75f commit 18cb5eb

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/basic_memory/mcp/tools/list_directory.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ async def list_directory(
162162
file_line += f" | {title}"
163163
if date_str:
164164
file_line += f" | {date_str}"
165+
# Web-app deep links are built from the note's external_id; hosted
166+
# MCP appends a link template that references it, so the id must be
167+
# visible in the listing the agent actually reads.
168+
external_id = node.get("external_id")
169+
if external_id:
170+
file_line += f" | id: {external_id}"
165171

166172
output_lines.append(file_line)
167173

src/basic_memory/mcp/tools/recent_activity.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,9 @@ def _format_project_output(
516516
folder_path = str(PurePosixPath(entity.file_path).parent)
517517
if folder_path and folder_path != ".":
518518
folder = f" ({folder_path})"
519-
lines.append(f" • {title}{folder}")
519+
# external_id makes rows linkable: hosted MCP appends a web-app
520+
# link template that the agent fills with this id on request.
521+
lines.append(f" • {title}{folder} [id: {entity.external_id}]")
520522

521523
# Show observations (categorized insights)
522524
if observations:

tests/mcp/test_tool_list_directory.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,21 @@ async def test_list_directory_shows_file_metadata(client, test_graph, test_proje
221221
lines = result.split("\n")
222222
file_lines = [line for line in lines if "📄" in line]
223223
assert len(file_lines) == 5 # All 5 files from test_graph
224+
225+
226+
@pytest.mark.asyncio
227+
async def test_list_directory_file_rows_include_external_id(client, test_graph, test_project):
228+
"""File rows carry the note external_id.
229+
230+
Web-app deep links are built from this id; the hosted MCP link template
231+
tells agents to substitute it, so it must be visible in the text output.
232+
"""
233+
import re
234+
235+
result = await list_directory(project=test_project.name, dir_name="/test")
236+
237+
uuid_pattern = r"\| id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
238+
file_lines = [line for line in result.splitlines() if line.startswith("📄")]
239+
assert file_lines, f"no file rows in: {result!r}"
240+
for line in file_lines:
241+
assert re.search(uuid_pattern, line), f"file row missing external_id: {line!r}"

tests/mcp/test_tool_recent_activity.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,3 +611,23 @@ def test_format_project_output_no_more_pages():
611611
)
612612
assert "1 items found." in out
613613
assert "Use page=" not in out
614+
615+
616+
@pytest.mark.asyncio
617+
async def test_recent_activity_entity_rows_include_external_id(client, test_graph, test_project):
618+
"""Entity rows carry the note external_id for web-app link building.
619+
620+
The hosted MCP link template tells agents to substitute this id, so it
621+
must be visible in the text output they read.
622+
"""
623+
import re
624+
625+
result = await recent_activity(project=test_project.name, timeframe="30d")
626+
627+
assert "Recent Notes & Documents" in result
628+
uuid_pattern = r"\[id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\]"
629+
entity_lines = [line for line in result.splitlines() if line.strip().startswith("•")]
630+
assert entity_lines, f"no entity rows in: {result!r}"
631+
assert any(re.search(uuid_pattern, line) for line in entity_lines), (
632+
f"entity rows missing external_id: {entity_lines!r}"
633+
)

0 commit comments

Comments
 (0)