Skip to content

Commit 9d581ce

Browse files
fix: [BUG] Cursor has errors calling search tool (#78)
1 parent f588529 commit 9d581ce

21 files changed

Lines changed: 363 additions & 101 deletions

claude-output/claude_error_75.log

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Starting issue-fix mode at Sat Apr 5 18:02:54 UTC 2025
2+
Fetching issue #75 details
3+
Using repository: basicmachines-co/basic-memory
4+
Checking if phernandez is a member of organization basicmachines-co
5+
User phernandez is a member of organization basicmachines-co. Proceeding with Claude fix.
6+
Creating a new branch: fix-issue-75-20250405180254
7+
From https://github.com/basicmachines-co/basic-memory
8+
* branch main -> FETCH_HEAD
9+
Switched to a new branch 'fix-issue-75-20250405180254'
10+
branch 'fix-issue-75-20250405180254' set up to track 'origin/main'.
11+
Prompt saved to ./claude-output/claude_prompt_75.txt for debugging
12+
Running Claude to fix the issue...
13+
Committing changes...

claude-output/claude_output_75.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Let's summarize the changes we've made to fix issue #75:
2+
3+
1. We updated the `search_notes` tool in `/src/basic_memory/mcp/tools/search.py` to accept primitive types as parameters instead of a complex Pydantic `SearchQuery` object. This makes it easier for LLMs like Cursor to make proper tool calls.
4+
5+
2. We converted the internal implementation to create a SearchQuery object from the primitive parameters, maintaining backward compatibility with the existing API.
6+
7+
3. We updated tests in `/tests/mcp/test_tool_search.py` to use the new function signature with primitive parameters.
8+
9+
4. We updated code in `/src/basic_memory/mcp/tools/read_note.py` to use the new function signature when making calls to `search_notes`.
10+
11+
5. We updated code in `/src/basic_memory/mcp/prompts/search.py` to use the new function signature when making calls to `search_notes`.
12+
13+
These changes should make it easier for Cursor and other LLMs to use the search_notes tool by eliminating the complex Pydantic object parameter in favor of simple primitive parameters.
14+
15+
---SUMMARY---
16+
Fixed issue #75 where Cursor was having errors calling the search_notes tool. The problem was that the search_notes tool was expecting a complex Pydantic object (SearchQuery) as input, which was confusing Cursor.
17+
18+
Changes:
19+
1. Modified the search_notes tool to accept primitive types (strings, lists, etc.) as parameters instead of a complex Pydantic object
20+
2. Updated the implementation to create a SearchQuery object internally from these primitive parameters
21+
3. Updated all call sites in the codebase that were using the old function signature
22+
4. Updated tests to use the new function signature
23+
24+
The fix makes it easier for LLMs like Cursor to make proper calls to the search_notes tool, which will resolve the reported error messages:
25+
- "Parameter 'query' must be of type undefined, got object"
26+
- "Parameter 'query' must be of type undefined, got string"
27+
- "Invalid type for parameter 'query' in tool search_notes"
28+
29+
Files modified:
30+
- src/basic_memory/mcp/tools/search.py
31+
- src/basic_memory/mcp/tools/read_note.py
32+
- src/basic_memory/mcp/prompts/search.py
33+
- tests/mcp/test_tool_search.py
34+
- tests/mcp/test_tool_read_note.py
35+
---END SUMMARY---

claude-output/claude_prompt_75.txt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
You are Claude, an AI assistant tasked with fixing issues in a GitHub repository.
2+
3+
Issue #75: [BUG] Cursor has errors calling search tool
4+
5+
Issue Description:
6+
## Bug Description
7+
8+
9+
10+
> Cursor cannot figure out how to structure the parameters for that tool call. No matter what Cursor seems to try it gets the errors.
11+
>
12+
> ```Looking at the error messages more carefully:
13+
> - When I pass an object: "Parameter 'query' must be of type undefined, got object"
14+
> - When I pass a string: "Parameter 'query' must be of type undefined, got string"
15+
>
16+
>
17+
>
18+
> and then it reports: "Invalid type for parameter 'query' in tool search_notes"
19+
> Any chance you can give me some guidance with this?
20+
>
21+
22+
## Steps To Reproduce
23+
Steps to reproduce the behavior:
24+
25+
try using search tool in Cursor.
26+
27+
## Possible Solution
28+
29+
The tool args should probably be plain text and not json to make it easier to call.
30+
Additional Instructions from User Comment:
31+
let make a PR to implement option #1.
32+
Your task is to:
33+
1. Analyze the issue carefully to understand the problem
34+
2. Look through the repository to identify the relevant files that need to be modified
35+
3. Make precise changes to fix the issue
36+
4. Use the Edit tool to modify files directly when needed
37+
5. Be minimal in your changes - only modify what's necessary to fix the issue
38+
39+
After making changes, provide a summary of what you did in this format:
40+
41+
---SUMMARY---
42+
[Your detailed summary of changes, including which files were modified and how]
43+
---END SUMMARY---
44+
45+
Remember:
46+
- Be specific in your changes
47+
- Only modify files that are necessary to fix the issue
48+
- Follow existing code style and conventions
49+
- Make the minimal changes needed to resolve the issue

src/basic_memory/cli/commands/tool.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,29 @@
22

33
import asyncio
44
import sys
5-
from typing import Optional, List, Annotated
5+
from typing import Annotated, List, Optional
66

77
import typer
88
from loguru import logger
99
from rich import print as rprint
1010

1111
from basic_memory.cli.app import app
12-
from basic_memory.mcp.tools import build_context as mcp_build_context
13-
from basic_memory.mcp.tools import read_note as mcp_read_note
14-
from basic_memory.mcp.tools import recent_activity as mcp_recent_activity
15-
from basic_memory.mcp.tools import search_notes as mcp_search
16-
from basic_memory.mcp.tools import write_note as mcp_write_note
1712

1813
# Import prompts
1914
from basic_memory.mcp.prompts.continue_conversation import (
2015
continue_conversation as mcp_continue_conversation,
2116
)
22-
2317
from basic_memory.mcp.prompts.recent_activity import (
2418
recent_activity_prompt as recent_activity_prompt,
2519
)
26-
20+
from basic_memory.mcp.tools import build_context as mcp_build_context
21+
from basic_memory.mcp.tools import read_note as mcp_read_note
22+
from basic_memory.mcp.tools import recent_activity as mcp_recent_activity
23+
from basic_memory.mcp.tools import search_notes as mcp_search
24+
from basic_memory.mcp.tools import write_note as mcp_write_note
2725
from basic_memory.schemas.base import TimeFrame
2826
from basic_memory.schemas.memory import MemoryUrl
29-
from basic_memory.schemas.search import SearchQuery, SearchItemType
27+
from basic_memory.schemas.search import SearchItemType
3028

3129
tool_app = typer.Typer()
3230
app.add_typer(tool_app, name="tool", help="Access to MCP tools via CLI")
@@ -198,13 +196,28 @@ def search_notes(
198196
raise typer.Abort()
199197

200198
try:
201-
search_query = SearchQuery(
202-
permalink_match=query if permalink else None,
203-
text=query if not (permalink or title) else None,
204-
title=query if title else None,
205-
after_date=after_date,
199+
if permalink and title: # pragma: no cover
200+
typer.echo(
201+
"Use either --permalink or --title, not both. Exiting.",
202+
err=True,
203+
)
204+
raise typer.Exit(1)
205+
206+
# set search type
207+
search_type = ("permalink" if permalink else None,)
208+
search_type = ("permalink_match" if permalink and "*" in query else None,)
209+
search_type = ("title" if title else None,)
210+
search_type = "text" if search_type is None else search_type
211+
212+
results = asyncio.run(
213+
mcp_search(
214+
query,
215+
search_type=search_type,
216+
page=page,
217+
after_date=after_date,
218+
page_size=page_size,
219+
)
206220
)
207-
results = asyncio.run(mcp_search(query=search_query, page=page, page_size=page_size))
208221
# Use json module for more controlled serialization
209222
import json
210223

src/basic_memory/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ def get_process_name(): # pragma: no cover
234234
# Global flag to track if logging has been set up
235235
_LOGGING_SETUP = False
236236

237+
237238
def setup_basic_memory_logging(): # pragma: no cover
238239
"""Set up logging for basic-memory, ensuring it only happens once."""
239240
global _LOGGING_SETUP

src/basic_memory/mcp/prompts/continue_conversation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
"""
66

77
from textwrap import dedent
8-
from typing import Optional, Annotated
8+
from typing import Annotated, Optional
99

1010
from loguru import logger
1111
from pydantic import Field
1212

13-
from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
13+
from basic_memory.mcp.prompts.utils import PromptContext, PromptContextItem, format_prompt_context
1414
from basic_memory.mcp.server import mcp
1515
from basic_memory.mcp.tools.build_context import build_context
1616
from basic_memory.mcp.tools.recent_activity import recent_activity
1717
from basic_memory.mcp.tools.search import search_notes
1818
from basic_memory.schemas.base import TimeFrame
1919
from basic_memory.schemas.memory import GraphContext
20-
from basic_memory.schemas.search import SearchQuery, SearchItemType
20+
from basic_memory.schemas.search import SearchItemType
2121

2222

2323
@mcp.prompt(
@@ -48,7 +48,7 @@ async def continue_conversation(
4848
# If topic provided, search for it
4949
if topic:
5050
search_results = await search_notes(
51-
SearchQuery(text=topic, after_date=timeframe, types=[SearchItemType.ENTITY])
51+
query=topic, after_date=timeframe, entity_types=[SearchItemType.ENTITY]
5252
)
5353

5454
# Build context from results

src/basic_memory/mcp/prompts/search.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from basic_memory.mcp.server import mcp
1313
from basic_memory.mcp.tools.search import search_notes as search_tool
1414
from basic_memory.schemas.base import TimeFrame
15-
from basic_memory.schemas.search import SearchQuery, SearchResponse
15+
from basic_memory.schemas.search import SearchResponse
1616

1717

1818
@mcp.prompt(
@@ -40,7 +40,7 @@ async def search_prompt(
4040
"""
4141
logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
4242

43-
search_results = await search_tool(SearchQuery(text=query, after_date=timeframe))
43+
search_results = await search_tool(query=query, after_date=timeframe)
4444
return format_search_results(query, search_results, timeframe)
4545

4646

src/basic_memory/mcp/tools/read_note.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from basic_memory.mcp.tools.search import search_notes
1010
from basic_memory.mcp.tools.utils import call_get
1111
from basic_memory.schemas.memory import memory_url_path
12-
from basic_memory.schemas.search import SearchQuery
1312

1413

1514
@mcp.tool(
@@ -63,7 +62,7 @@ async def read_note(identifier: str, page: int = 1, page_size: int = 10) -> str:
6362

6463
# Fallback 1: Try title search via API
6564
logger.info(f"Search title for: {identifier}")
66-
title_results = await search_notes(SearchQuery(title=identifier))
65+
title_results = await search_notes(query=identifier, search_type="title")
6766

6867
if title_results and title_results.results:
6968
result = title_results.results[0] # Get the first/best match
@@ -87,7 +86,7 @@ async def read_note(identifier: str, page: int = 1, page_size: int = 10) -> str:
8786

8887
# Fallback 2: Text search as a last resort
8988
logger.info(f"Title search failed, trying text search for: {identifier}")
90-
text_results = await search_notes(SearchQuery(text=identifier))
89+
text_results = await search_notes(query=identifier, search_type="text")
9190

9291
# We didn't find a direct match, construct a helpful error message
9392
if not text_results or not text_results.results:

0 commit comments

Comments
 (0)