|
1 | 1 | """Search tools for Basic Memory MCP server.""" |
2 | 2 |
|
| 3 | +from typing import Optional, List |
3 | 4 | from loguru import logger |
4 | 5 |
|
5 | 6 | from basic_memory.mcp.server import mcp |
6 | 7 | from basic_memory.mcp.tools.utils import call_post |
7 | | -from basic_memory.schemas.search import SearchQuery, SearchResponse |
| 8 | +from basic_memory.schemas.search import SearchQuery, SearchResponse, SearchItemType |
8 | 9 | from basic_memory.mcp.async_client import client |
9 | 10 |
|
10 | 11 |
|
11 | 12 | @mcp.tool( |
12 | 13 | description="Search across all content in the knowledge base.", |
13 | 14 | ) |
14 | | -async def search_notes(query: SearchQuery, page: int = 1, page_size: int = 10) -> SearchResponse: |
| 15 | +async def search_notes( |
| 16 | + query: str, |
| 17 | + page: int = 1, |
| 18 | + page_size: int = 10, |
| 19 | + search_type: str = "text", |
| 20 | + types: Optional[List[str]] = None, |
| 21 | + entity_types: Optional[List[str]] = None, |
| 22 | + after_date: Optional[str] = None, |
| 23 | +) -> SearchResponse: |
15 | 24 | """Search across all content in the knowledge base. |
16 | 25 |
|
17 | 26 | This tool searches the knowledge base using full-text search, pattern matching, |
18 | 27 | or exact permalink lookup. It supports filtering by content type, entity type, |
19 | 28 | and date. |
20 | 29 |
|
21 | 30 | Args: |
22 | | - query: SearchQuery object with search parameters including: |
23 | | - - text: Full-text search (e.g., "project planning") |
24 | | - Supports boolean operators: AND, OR, NOT and parentheses for grouping |
25 | | - - title: Search only in titles (e.g., "Meeting notes") |
26 | | - - permalink: Exact permalink match (e.g., "docs/meeting-notes") |
27 | | - - permalink_match: Pattern matching for permalinks (e.g., "docs/*-notes") |
28 | | - - types: Optional list of content types to search (e.g., ["entity", "observation"]) |
29 | | - - entity_types: Optional list of entity types to filter by (e.g., ["note", "person"]) |
30 | | - - after_date: Optional date filter for recent content (e.g., "1 week", "2d") |
| 31 | + query: The search query string |
31 | 32 | page: The page number of results to return (default 1) |
32 | 33 | page_size: The number of results to return per page (default 10) |
| 34 | + search_type: Type of search to perform, one of: "text", "title", "permalink", "permalink_match" (default: "text") |
| 35 | + types: Optional list of content types to search (e.g., ["entity", "observation"]) |
| 36 | + entity_types: Optional list of entity types to filter by (e.g., ["note", "person"]) |
| 37 | + after_date: Optional date filter for recent content (e.g., "1 week", "2d") |
33 | 38 |
|
34 | 39 | Returns: |
35 | 40 | SearchResponse with results and pagination info |
36 | 41 |
|
37 | 42 | Examples: |
38 | 43 | # Basic text search |
39 | | - results = await search_notes(SearchQuery(text="project planning")) |
| 44 | + results = await search_notes("project planning") |
40 | 45 |
|
41 | 46 | # Boolean AND search (both terms must be present) |
42 | | - results = await search_notes(SearchQuery(text="project AND planning")) |
| 47 | + results = await search_notes("project AND planning") |
43 | 48 |
|
44 | 49 | # Boolean OR search (either term can be present) |
45 | | - results = await search_notes(SearchQuery(text="project OR meeting")) |
| 50 | + results = await search_notes("project OR meeting") |
46 | 51 |
|
47 | 52 | # Boolean NOT search (exclude terms) |
48 | | - results = await search_notes(SearchQuery(text="project NOT meeting")) |
| 53 | + results = await search_notes("project NOT meeting") |
49 | 54 |
|
50 | 55 | # Boolean search with grouping |
51 | | - results = await search_notes(SearchQuery(text="(project OR planning) AND notes")) |
| 56 | + results = await search_notes("(project OR planning) AND notes") |
52 | 57 |
|
53 | 58 | # Search with type filter |
54 | | - results = await search_notes(SearchQuery( |
55 | | - text="meeting notes", |
| 59 | + results = await search_notes( |
| 60 | + query="meeting notes", |
56 | 61 | types=["entity"], |
57 | | - )) |
| 62 | + ) |
58 | 63 |
|
59 | 64 | # Search for recent content |
60 | | - results = await search_notes(SearchQuery( |
61 | | - text="bug report", |
| 65 | + results = await search_notes( |
| 66 | + query="bug report", |
62 | 67 | after_date="1 week" |
63 | | - )) |
| 68 | + ) |
64 | 69 |
|
65 | 70 | # Pattern matching on permalinks |
66 | | - results = await search_notes(SearchQuery( |
67 | | - permalink_match="docs/meeting-*" |
68 | | - )) |
| 71 | + results = await search_notes( |
| 72 | + query="docs/meeting-*", |
| 73 | + search_type="permalink_match" |
| 74 | + ) |
69 | 75 | """ |
70 | | - logger.info(f"Searching for {query}") |
| 76 | + # Create a SearchQuery object based on the parameters |
| 77 | + search_query = SearchQuery() |
| 78 | + |
| 79 | + # Set the appropriate search field based on search_type |
| 80 | + if search_type == "text": |
| 81 | + search_query.text = query |
| 82 | + elif search_type == "title": |
| 83 | + search_query.title = query |
| 84 | + elif search_type == "permalink": |
| 85 | + search_query.permalink = query |
| 86 | + elif search_type == "permalink_match": |
| 87 | + search_query.permalink_match = query |
| 88 | + else: |
| 89 | + search_query.text = query # Default to text search |
| 90 | + |
| 91 | + # Add optional filters if provided |
| 92 | + if types: |
| 93 | + search_query.types = [SearchItemType(t) for t in types] |
| 94 | + if entity_types: |
| 95 | + search_query.entity_types = entity_types |
| 96 | + if after_date: |
| 97 | + search_query.after_date = after_date |
| 98 | + |
| 99 | + logger.info(f"Searching for {search_query}") |
71 | 100 | response = await call_post( |
72 | 101 | client, |
73 | 102 | "/search/", |
74 | | - json=query.model_dump(), |
| 103 | + json=search_query.model_dump(), |
75 | 104 | params={"page": page, "page_size": page_size}, |
76 | 105 | ) |
77 | 106 | return SearchResponse.model_validate(response.json()) |
0 commit comments