Skip to content

Commit e41579f

Browse files
committed
Merge branch 'main' of github.com:basicmachines-co/basic-memory
2 parents 56e5cc0 + 2b7008d commit e41579f

File tree

4 files changed

+61
-74
lines changed

4 files changed

+61
-74
lines changed

src/basic_memory/mcp/tools/chatgpt_tools.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from basic_memory.mcp.tools.search import search_notes
1515
from basic_memory.mcp.tools.read_note import read_note
1616
from basic_memory.schemas.search import SearchResponse
17+
from basic_memory.config import ConfigManager
1718

1819

1920
def _format_search_results_for_chatgpt(results: SearchResponse) -> List[Dict[str, Any]]:
@@ -90,10 +91,14 @@ async def search(
9091
logger.info(f"ChatGPT search request: query='{query}'")
9192

9293
try:
94+
# ChatGPT tools don't expose project parameter, so use default project
95+
config = ConfigManager().config
96+
default_project = config.default_project
97+
9398
# Call underlying search_notes with sensible defaults for ChatGPT
9499
results = await search_notes.fn(
95100
query=query,
96-
project=None, # Let project resolution happen automatically
101+
project=default_project, # Use default project for ChatGPT
97102
page=1,
98103
page_size=10, # Reasonable default for ChatGPT consumption
99104
search_type="text", # Default to full-text search
@@ -149,10 +154,14 @@ async def fetch(
149154
logger.info(f"ChatGPT fetch request: id='{id}'")
150155

151156
try:
157+
# ChatGPT tools don't expose project parameter, so use default project
158+
config = ConfigManager().config
159+
default_project = config.default_project
160+
152161
# Call underlying read_note function
153162
content = await read_note.fn(
154163
identifier=id,
155-
project=None, # Let project resolution happen automatically
164+
project=default_project, # Use default project for ChatGPT
156165
page=1,
157166
page_size=10, # Default pagination
158167
context=context,

src/basic_memory/mcp/tools/view_note.py

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ async def view_note(
2222
) -> str:
2323
"""View a markdown note as a formatted artifact.
2424
25-
This tool reads a note using the same logic as read_note but displays the content
26-
as a markdown artifact for better viewing experience in Claude Desktop.
25+
This tool reads a note using the same logic as read_note but instructs Claude
26+
to display the content as a markdown artifact in the Claude Desktop app.
2727
Project parameter optional with server resolution.
2828
29-
After calling this tool, create an artifact using the returned content to display
30-
the note in a readable format. The tool returns the note content that should be
31-
used to create a markdown artifact.
32-
3329
Args:
3430
identifier: The title or permalink of the note to view
3531
project: Project name to read from. Optional - server will resolve using hierarchy.
@@ -39,7 +35,7 @@ async def view_note(
3935
context: Optional FastMCP context for performance caching.
4036
4137
Returns:
42-
The note content as a markdown artifact with a confirmation message.
38+
Instructions for Claude to create a markdown artifact with the note content.
4339
4440
Examples:
4541
# View a note by title
@@ -66,26 +62,16 @@ async def view_note(
6662

6763
# Check if this is an error message (note not found)
6864
if "# Note Not Found" in content:
69-
return content # Return error message directly instead of creating artifact
70-
71-
# Extract title from content if possible
72-
title = identifier
73-
lines = content.split("\n")
74-
for line in lines:
75-
if line.startswith("title:"):
76-
# Extract title from frontmatter
77-
title = line.split("title:", 1)[1].strip().strip('"').strip("'")
78-
break
79-
elif line.startswith("# "):
80-
# Extract title from first heading
81-
title = line[2:].strip()
82-
break
83-
84-
# Return the artifact XML as part of the response
85-
artifact = f'<artifact identifier="note-{hash(identifier) & 0x7FFFFFFF}" type="text/markdown" title="{title}">\n{content}\n</artifact>'
65+
return content # Return error message directly
8666

67+
# Return instructions for Claude to create an artifact
8768
return dedent(f"""
88-
<instructions>
89-
Create an artifact using the returned content to display the note in a readable format.
90-
</instructions>
91-
{artifact}\n\n✅ Note displayed as artifact: **{title}**""")
69+
Note retrieved: "{identifier}"
70+
71+
Display this note as a markdown artifact for the user.
72+
73+
Content:
74+
---
75+
{content}
76+
---
77+
""").strip()

test-int/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def app_config(config_home, tmp_path, monkeypatch) -> BasicMemoryConfig:
125125
env="test",
126126
projects=projects,
127127
default_project="test-project",
128-
default_project_mode=True,
128+
default_project_mode=False, # Match real-world usage - tools must pass explicit project
129129
update_permalinks_on_move=True,
130130
cloud_mode=False, # Explicitly disable cloud mode
131131
)

tests/mcp/test_tool_view_note.py

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,16 @@ async def test_view_note_basic_functionality(app, test_project):
4444
# View the note
4545
result = await view_note.fn("Test View Note", project=test_project.name)
4646

47-
# Should contain artifact XML
48-
assert '<artifact identifier="note-' in result
49-
assert 'type="text/markdown"' in result
50-
assert 'title="Test View Note"' in result
51-
assert "</artifact>" in result
47+
# Should contain note retrieval message
48+
assert 'Note retrieved: "Test View Note"' in result
49+
assert "Display this note as a markdown artifact for the user" in result
50+
assert "Content:" in result
51+
assert "---" in result
5252

53-
# Should contain the note content within the artifact
53+
# Should contain the note content
5454
assert "# Test View Note" in result
5555
assert "This is test content for viewing." in result
5656

57-
# Should have confirmation message
58-
assert "✅ Note displayed as artifact" in result
59-
6057

6158
@pytest.mark.asyncio
6259
async def test_view_note_with_frontmatter_title(app, test_project):
@@ -80,9 +77,9 @@ async def test_view_note_with_frontmatter_title(app, test_project):
8077
# View the note
8178
result = await view_note.fn("Frontmatter Title", project=test_project.name)
8279

83-
# Should extract title from frontmatter
84-
assert 'title="Frontmatter Title"' in result
85-
assert "✅ Note displayed as artifact: **Frontmatter Title**" in result
80+
# Should show title in retrieval message
81+
assert 'Note retrieved: "Frontmatter Title"' in result
82+
assert "Display this note as a markdown artifact for the user" in result
8683

8784

8885
@pytest.mark.asyncio
@@ -98,9 +95,9 @@ async def test_view_note_with_heading_title(app, test_project):
9895
# View the note
9996
result = await view_note.fn("Heading Title", project=test_project.name)
10097

101-
# Should extract title from heading
102-
assert 'title="Heading Title"' in result
103-
assert "✅ Note displayed as artifact: **Heading Title**" in result
98+
# Should show title in retrieval message
99+
assert 'Note retrieved: "Heading Title"' in result
100+
assert "Display this note as a markdown artifact for the user" in result
104101

105102

106103
@pytest.mark.asyncio
@@ -119,7 +116,7 @@ async def test_view_note_unicode_content(app, test_project):
119116
assert "🚀" in result
120117
assert "🎉" in result
121118
assert "♠♣♥♦" in result
122-
assert '<artifact identifier="note-' in result
119+
assert 'Note retrieved: "Unicode Test 🚀"' in result
123120

124121

125122
@pytest.mark.asyncio
@@ -136,9 +133,9 @@ async def test_view_note_by_permalink(app, test_project):
136133
result = await view_note.fn("test/permalink-test", project=test_project.name)
137134

138135
# Should work with permalink
139-
assert '<artifact identifier="note-' in result
136+
assert 'Note retrieved: "test/permalink-test"' in result
140137
assert "Content for permalink test." in result
141-
assert "✅ Note displayed as artifact" in result
138+
assert "Display this note as a markdown artifact for the user" in result
142139

143140

144141
@pytest.mark.asyncio
@@ -155,9 +152,9 @@ async def test_view_note_with_memory_url(app, test_project):
155152
result = await view_note.fn("memory://test/memory-url-test", project=test_project.name)
156153

157154
# Should work with memory:// URL
158-
assert '<artifact identifier="note-' in result
155+
assert 'Note retrieved: "memory://test/memory-url-test"' in result
159156
assert "Testing memory:// URL handling in view_note" in result
160-
assert "✅ Note displayed as artifact" in result
157+
assert "Display this note as a markdown artifact for the user" in result
161158

162159

163160
@pytest.mark.asyncio
@@ -166,10 +163,10 @@ async def test_view_note_not_found(app, test_project):
166163
# Try to view non-existent note
167164
result = await view_note.fn("NonExistent Note", project=test_project.name)
168165

169-
# Should return error message without artifact
166+
# Should return error message without artifact instructions
170167
assert "# Note Not Found" in result
171168
assert "NonExistent Note" in result
172-
assert "<artifact" not in result # No artifact for errors
169+
assert "Display this note as a markdown artifact" not in result # No artifact for errors
173170
assert "Check Identifier Type" in result
174171
assert "Search Instead" in result
175172

@@ -188,9 +185,9 @@ async def test_view_note_pagination(app, test_project):
188185
result = await view_note.fn("Pagination Test", page=1, page_size=5, project=test_project.name)
189186

190187
# Should work with pagination
191-
assert '<artifact identifier="note-' in result
188+
assert 'Note retrieved: "Pagination Test"' in result
192189
assert "Content for pagination test." in result
193-
assert "✅ Note displayed as artifact" in result
190+
assert "Display this note as a markdown artifact for the user" in result
194191

195192

196193
@pytest.mark.asyncio
@@ -207,14 +204,14 @@ async def test_view_note_project_parameter(app, test_project):
207204
result = await view_note.fn("Project Test", project=test_project.name)
208205

209206
# Should work with project parameter
210-
assert '<artifact identifier="note-' in result
207+
assert 'Note retrieved: "Project Test"' in result
211208
assert "Content for project test." in result
212-
assert "✅ Note displayed as artifact" in result
209+
assert "Display this note as a markdown artifact for the user" in result
213210

214211

215212
@pytest.mark.asyncio
216213
async def test_view_note_artifact_identifier_unique(app, test_project):
217-
"""Test that different notes get different artifact identifiers."""
214+
"""Test that different notes are retrieved correctly with unique identifiers."""
218215
# Create two notes
219216
await write_note.fn(
220217
project=test_project.name, title="Note One", folder="test", content="Content one"
@@ -227,15 +224,11 @@ async def test_view_note_artifact_identifier_unique(app, test_project):
227224
result1 = await view_note.fn("Note One", project=test_project.name)
228225
result2 = await view_note.fn("Note Two", project=test_project.name)
229226

230-
# Should have different artifact identifiers
231-
import re
232-
233-
id1_match = re.search(r'identifier="(note-\d+)"', result1)
234-
id2_match = re.search(r'identifier="(note-\d+)"', result2)
235-
236-
assert id1_match is not None
237-
assert id2_match is not None
238-
assert id1_match.group(1) != id2_match.group(1)
227+
# Should have different note identifiers in retrieval messages
228+
assert 'Note retrieved: "Note One"' in result1
229+
assert 'Note retrieved: "Note Two"' in result2
230+
assert "Content one" in result1
231+
assert "Content two" in result2
239232

240233

241234
@pytest.mark.asyncio
@@ -252,9 +245,9 @@ async def test_view_note_fallback_identifier_as_title(app, test_project):
252245
# View the note
253246
result = await view_note.fn("Simple Note", project=test_project.name)
254247

255-
# Should use identifier as fallback title
256-
assert 'title="Simple Note"' in result
257-
assert "✅ Note displayed as artifact: **Simple Note**" in result
248+
# Should use identifier as title in retrieval message
249+
assert 'Note retrieved: "Simple Note"' in result
250+
assert "Display this note as a markdown artifact for the user" in result
258251

259252

260253
@pytest.mark.asyncio
@@ -282,8 +275,7 @@ async def test_view_note_direct_success(app, test_project, mock_call_get):
282275
mock_call_get.assert_called_once()
283276
assert "test/test-note" in mock_call_get.call_args[0][1]
284277

285-
# Verify result contains artifact
286-
assert '<artifact identifier="note-' in result
287-
assert 'title="Test Note"' in result
278+
# Verify result contains note content
279+
assert 'Note retrieved: "test/test-note"' in result
280+
assert "Display this note as a markdown artifact for the user" in result
288281
assert "This is a test note." in result
289-
assert "✅ Note displayed as artifact: **Test Note**" in result

0 commit comments

Comments
 (0)