basic-memory 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

Files changed (89) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +23 -1
  4. basic_memory/alembic/migrations.py +4 -9
  5. basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
  6. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  7. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +106 -0
  8. basic_memory/api/app.py +9 -10
  9. basic_memory/api/routers/__init__.py +2 -1
  10. basic_memory/api/routers/knowledge_router.py +31 -5
  11. basic_memory/api/routers/memory_router.py +18 -17
  12. basic_memory/api/routers/project_info_router.py +275 -0
  13. basic_memory/api/routers/resource_router.py +105 -4
  14. basic_memory/api/routers/search_router.py +22 -4
  15. basic_memory/cli/app.py +54 -5
  16. basic_memory/cli/commands/__init__.py +15 -2
  17. basic_memory/cli/commands/db.py +9 -13
  18. basic_memory/cli/commands/import_chatgpt.py +26 -30
  19. basic_memory/cli/commands/import_claude_conversations.py +27 -29
  20. basic_memory/cli/commands/import_claude_projects.py +29 -31
  21. basic_memory/cli/commands/import_memory_json.py +26 -28
  22. basic_memory/cli/commands/mcp.py +7 -1
  23. basic_memory/cli/commands/project.py +119 -0
  24. basic_memory/cli/commands/project_info.py +167 -0
  25. basic_memory/cli/commands/status.py +14 -28
  26. basic_memory/cli/commands/sync.py +63 -22
  27. basic_memory/cli/commands/tool.py +253 -0
  28. basic_memory/cli/main.py +39 -1
  29. basic_memory/config.py +166 -4
  30. basic_memory/db.py +19 -4
  31. basic_memory/deps.py +10 -3
  32. basic_memory/file_utils.py +37 -19
  33. basic_memory/markdown/entity_parser.py +3 -3
  34. basic_memory/markdown/utils.py +5 -0
  35. basic_memory/mcp/async_client.py +1 -1
  36. basic_memory/mcp/main.py +24 -0
  37. basic_memory/mcp/prompts/__init__.py +19 -0
  38. basic_memory/mcp/prompts/ai_assistant_guide.py +26 -0
  39. basic_memory/mcp/prompts/continue_conversation.py +111 -0
  40. basic_memory/mcp/prompts/recent_activity.py +88 -0
  41. basic_memory/mcp/prompts/search.py +182 -0
  42. basic_memory/mcp/prompts/utils.py +155 -0
  43. basic_memory/mcp/server.py +2 -6
  44. basic_memory/mcp/tools/__init__.py +12 -21
  45. basic_memory/mcp/tools/build_context.py +85 -0
  46. basic_memory/mcp/tools/canvas.py +97 -0
  47. basic_memory/mcp/tools/delete_note.py +28 -0
  48. basic_memory/mcp/tools/project_info.py +51 -0
  49. basic_memory/mcp/tools/read_content.py +229 -0
  50. basic_memory/mcp/tools/read_note.py +190 -0
  51. basic_memory/mcp/tools/recent_activity.py +100 -0
  52. basic_memory/mcp/tools/search.py +56 -17
  53. basic_memory/mcp/tools/utils.py +245 -16
  54. basic_memory/mcp/tools/write_note.py +124 -0
  55. basic_memory/models/knowledge.py +27 -11
  56. basic_memory/models/search.py +2 -1
  57. basic_memory/repository/entity_repository.py +3 -2
  58. basic_memory/repository/project_info_repository.py +9 -0
  59. basic_memory/repository/repository.py +24 -7
  60. basic_memory/repository/search_repository.py +47 -14
  61. basic_memory/schemas/__init__.py +10 -9
  62. basic_memory/schemas/base.py +4 -1
  63. basic_memory/schemas/memory.py +14 -4
  64. basic_memory/schemas/project_info.py +96 -0
  65. basic_memory/schemas/search.py +29 -33
  66. basic_memory/services/context_service.py +3 -3
  67. basic_memory/services/entity_service.py +26 -13
  68. basic_memory/services/file_service.py +145 -26
  69. basic_memory/services/link_resolver.py +9 -46
  70. basic_memory/services/search_service.py +95 -22
  71. basic_memory/sync/__init__.py +3 -2
  72. basic_memory/sync/sync_service.py +523 -117
  73. basic_memory/sync/watch_service.py +258 -132
  74. basic_memory/utils.py +51 -36
  75. basic_memory-0.9.0.dist-info/METADATA +736 -0
  76. basic_memory-0.9.0.dist-info/RECORD +99 -0
  77. basic_memory/alembic/README +0 -1
  78. basic_memory/cli/commands/tools.py +0 -157
  79. basic_memory/mcp/tools/knowledge.py +0 -68
  80. basic_memory/mcp/tools/memory.py +0 -170
  81. basic_memory/mcp/tools/notes.py +0 -202
  82. basic_memory/schemas/discovery.py +0 -28
  83. basic_memory/sync/file_change_scanner.py +0 -158
  84. basic_memory/sync/utils.py +0 -31
  85. basic_memory-0.7.0.dist-info/METADATA +0 -378
  86. basic_memory-0.7.0.dist-info/RECORD +0 -82
  87. {basic_memory-0.7.0.dist-info → basic_memory-0.9.0.dist-info}/WHEEL +0 -0
  88. {basic_memory-0.7.0.dist-info → basic_memory-0.9.0.dist-info}/entry_points.txt +0 -0
  89. {basic_memory-0.7.0.dist-info → basic_memory-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,182 @@
1
+ """Search prompts for Basic Memory MCP server.
2
+
3
+ These prompts help users search and explore their knowledge base.
4
+ """
5
+
6
+ from textwrap import dedent
7
+ from typing import Annotated, Optional
8
+
9
+ from loguru import logger
10
+ from pydantic import Field
11
+
12
+ from basic_memory.mcp.server import mcp
13
+ from basic_memory.mcp.tools.search import search as search_tool
14
+ from basic_memory.schemas.base import TimeFrame
15
+ from basic_memory.schemas.search import SearchQuery, SearchResponse
16
+
17
+
18
+ @mcp.prompt(
19
+ name="Search Knowledge Base",
20
+ description="Search across all content in basic-memory",
21
+ )
22
+ async def search_prompt(
23
+ query: str,
24
+ timeframe: Annotated[
25
+ Optional[TimeFrame],
26
+ Field(description="How far back to search (e.g. '1d', '1 week')"),
27
+ ] = None,
28
+ ) -> str:
29
+ """Search across all content in basic-memory.
30
+
31
+ This prompt helps search for content in the knowledge base and
32
+ provides helpful context about the results.
33
+
34
+ Args:
35
+ query: The search text to look for
36
+ timeframe: Optional timeframe to limit results (e.g. '1d', '1 week')
37
+
38
+ Returns:
39
+ Formatted search results with context
40
+ """
41
+ logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
42
+
43
+ search_results = await search_tool(SearchQuery(text=query, after_date=timeframe))
44
+ return format_search_results(query, search_results, timeframe)
45
+
46
+
47
+ def format_search_results(
48
+ query: str, results: SearchResponse, timeframe: Optional[TimeFrame] = None
49
+ ) -> str:
50
+ """Format search results into a helpful summary.
51
+
52
+ Args:
53
+ query: The search query
54
+ results: Search results object
55
+ timeframe: How far back results were searched
56
+
57
+ Returns:
58
+ Formatted search results summary
59
+ """
60
+ if not results.results:
61
+ return dedent(f"""
62
+ # Search Results for: "{query}"
63
+
64
+ I couldn't find any results for this query.
65
+
66
+ ## Opportunity to Capture Knowledge!
67
+
68
+ This is an excellent opportunity to create new knowledge on this topic. Consider:
69
+
70
+ ```python
71
+ await write_note(
72
+ title="{query.capitalize()}",
73
+ content=f'''
74
+ # {query.capitalize()}
75
+
76
+ ## Overview
77
+ [Summary of what we've discussed about {query}]
78
+
79
+ ## Observations
80
+ - [category] [First observation about {query}]
81
+ - [category] [Second observation about {query}]
82
+
83
+ ## Relations
84
+ - relates_to [[Other Relevant Topic]]
85
+ '''
86
+ )
87
+ ```
88
+
89
+ ## Other Suggestions
90
+ - Try a different search term
91
+ - Broaden your search criteria
92
+ - Check recent activity with `recent_activity(timeframe="1w")`
93
+ """)
94
+
95
+ # Start building our summary with header
96
+ time_info = f" (after {timeframe})" if timeframe else ""
97
+ summary = dedent(f"""
98
+ # Search Results for: "{query}"{time_info}
99
+
100
+ This is a memory search session.
101
+ Please use the available basic-memory tools to gather relevant context before responding.
102
+ I found {len(results.results)} results that match your query.
103
+
104
+ Here are the most relevant results:
105
+ """)
106
+
107
+ # Add each search result
108
+ for i, result in enumerate(results.results[:5]): # Limit to top 5 results
109
+ summary += dedent(f"""
110
+ ## {i + 1}. {result.title}
111
+ - **Type**: {result.type.value}
112
+ """)
113
+
114
+ # Add creation date if available in metadata
115
+ if result.metadata and "created_at" in result.metadata:
116
+ created_at = result.metadata["created_at"]
117
+ if hasattr(created_at, "strftime"):
118
+ summary += (
119
+ f"- **Created**: {created_at.strftime('%Y-%m-%d %H:%M')}\n" # pragma: no cover
120
+ )
121
+ elif isinstance(created_at, str):
122
+ summary += f"- **Created**: {created_at}\n"
123
+
124
+ # Add score and excerpt
125
+ summary += f"- **Relevance Score**: {result.score:.2f}\n"
126
+
127
+ # Add excerpt if available in metadata
128
+ if result.content:
129
+ summary += f"- **Excerpt**:\n{result.content}\n"
130
+
131
+ # Add permalink for retrieving content
132
+ if result.permalink:
133
+ summary += dedent(f"""
134
+ You can view this content with: `read_note("{result.permalink}")`
135
+ Or explore its context with: `build_context("memory://{result.permalink}")`
136
+ """)
137
+ else:
138
+ summary += dedent(f"""
139
+ You can view this file with: `read_file("{result.file_path}")`
140
+ """) # pragma: no cover
141
+
142
+ # Add next steps with strong write encouragement
143
+ summary += dedent(f"""
144
+ ## Next Steps
145
+
146
+ You can:
147
+ - Refine your search: `search("{query} AND additional_term")`
148
+ - Exclude terms: `search("{query} NOT exclude_term")`
149
+ - View more results: `search("{query}", after_date=None)`
150
+ - Check recent activity: `recent_activity()`
151
+
152
+ ## Synthesize and Capture Knowledge
153
+
154
+ Consider creating a new note that synthesizes what you've learned:
155
+
156
+ ```python
157
+ await write_note(
158
+ title="Synthesis of {query.capitalize()} Information",
159
+ content='''
160
+ # Synthesis of {query.capitalize()} Information
161
+
162
+ ## Overview
163
+ [Synthesis of the search results and your conversation]
164
+
165
+ ## Key Insights
166
+ [Summary of main points learned from these results]
167
+
168
+ ## Observations
169
+ - [insight] [Important observation from search results]
170
+ - [connection] [How this connects to other topics]
171
+
172
+ ## Relations
173
+ - relates_to [[{results.results[0].title if results.results else "Related Topic"}]]
174
+ - extends [[Another Relevant Topic]]
175
+ '''
176
+ )
177
+ ```
178
+
179
+ Remember that capturing synthesized knowledge is one of the most valuable features of Basic Memory.
180
+ """)
181
+
182
+ return summary
@@ -0,0 +1,155 @@
1
+ """Utility functions for formatting prompt responses.
2
+
3
+ These utilities help format data from various tools into consistent,
4
+ user-friendly markdown summaries.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from textwrap import dedent
9
+ from typing import List
10
+
11
+ from basic_memory.schemas.base import TimeFrame
12
+ from basic_memory.schemas.memory import (
13
+ normalize_memory_url,
14
+ EntitySummary,
15
+ RelationSummary,
16
+ ObservationSummary,
17
+ )
18
+
19
+
20
+ @dataclass
21
+ class PromptContextItem:
22
+ primary_results: List[EntitySummary]
23
+ related_results: List[EntitySummary | RelationSummary | ObservationSummary]
24
+
25
+
26
+ @dataclass
27
+ class PromptContext:
28
+ timeframe: TimeFrame
29
+ topic: str
30
+ results: List[PromptContextItem]
31
+
32
+
33
+ def format_prompt_context(context: PromptContext) -> str:
34
+ """Format continuation context into a helpful summary.
35
+ Returns:
36
+ Formatted continuation summary
37
+ """
38
+ if not context.results:
39
+ return dedent(f"""
40
+ # Continuing conversation on: {context.topic}
41
+
42
+ This is a memory retrieval session.
43
+ The supplied query did not return any information specifically on this topic.
44
+
45
+ ## Opportunity to Capture New Knowledge!
46
+
47
+ This is an excellent chance to start documenting this topic:
48
+
49
+ ```python
50
+ await write_note(
51
+ title="{context.topic}",
52
+ content=f'''
53
+ # {context.topic}
54
+
55
+ ## Overview
56
+ [Summary of what we know about {context.topic}]
57
+
58
+ ## Key Points
59
+ [Main aspects or components of {context.topic}]
60
+
61
+ ## Observations
62
+ - [category] [First important observation about {context.topic}]
63
+ - [category] [Second observation about {context.topic}]
64
+
65
+ ## Relations
66
+ - relates_to [[Related Topic]]
67
+ - part_of [[Broader Context]]
68
+ '''
69
+ )
70
+ ```
71
+
72
+ ## Other Options
73
+
74
+ Please use the available basic-memory tools to gather relevant context before responding.
75
+ You can also:
76
+ - Try a different search term
77
+ - Check recent activity with `recent_activity(timeframe="1w")`
78
+ """)
79
+
80
+ # Start building our summary with header - add knowledge capture emphasis
81
+ summary = dedent(f"""
82
+ # Continuing conversation on: {context.topic}
83
+
84
+ This is a memory retrieval session.
85
+
86
+ Please use the available basic-memory tools to gather relevant context before responding.
87
+ Start by executing one of the suggested commands below to retrieve content.
88
+
89
+ Here's what I found from previous conversations:
90
+
91
+ > **Knowledge Capture Recommendation:** As you continue this conversation, actively look for opportunities to record new information, decisions, or insights that emerge. Use `write_note()` to document important context.
92
+ """)
93
+
94
+ # Track what we've added to avoid duplicates
95
+ added_permalinks = set()
96
+ sections = []
97
+
98
+ # Process each context
99
+ for context in context.results: # pyright: ignore
100
+ for primary in context.primary_results: # pyright: ignore
101
+ if primary.permalink not in added_permalinks:
102
+ primary_permalink = primary.permalink
103
+
104
+ added_permalinks.add(primary_permalink)
105
+
106
+ memory_url = normalize_memory_url(primary_permalink)
107
+ section = dedent(f"""
108
+ --- {memory_url}
109
+
110
+ ## {primary.title}
111
+ - **Type**: {primary.type}
112
+ """)
113
+
114
+ # Add creation date
115
+ section += f"- **Created**: {primary.created_at.strftime('%Y-%m-%d %H:%M')}\n"
116
+
117
+ # Add content snippet
118
+ if hasattr(primary, "content") and primary.content: # pyright: ignore
119
+ content = primary.content or "" # pyright: ignore
120
+ if content:
121
+ section += f"\n**Excerpt**:\n{content}\n"
122
+
123
+ section += dedent(f"""
124
+
125
+ You can read this document with: `read_note("{primary_permalink}")`
126
+ """)
127
+ sections.append(section)
128
+
129
+ if context.related_results: # pyright: ignore
130
+ section += dedent( # pyright: ignore
131
+ """
132
+ ## Related Context
133
+ """
134
+ )
135
+
136
+ for related in context.related_results: # pyright: ignore
137
+ section_content = dedent(f"""
138
+ - type: **{related.type}**
139
+ - title: {related.title}
140
+ """)
141
+ if related.permalink:
142
+ section_content += (
143
+ f'You can view this document with: `read_note("{related.permalink}")`'
144
+ )
145
+ else:
146
+ section_content += (
147
+ f'You can view this file with: `read_file("{related.file_path}")`'
148
+ )
149
+
150
+ section += section_content
151
+ sections.append(section)
152
+
153
+ # Add all sections
154
+ summary += "\n".join(sections)
155
+ return summary
@@ -1,15 +1,11 @@
1
1
  """Enhanced FastMCP server instance for Basic Memory."""
2
2
 
3
3
  from mcp.server.fastmcp import FastMCP
4
-
5
- from basic_memory.utils import setup_logging
4
+ from mcp.server.fastmcp.utilities.logging import configure_logging
6
5
 
7
6
  # mcp console logging
8
- # configure_logging(level='INFO')
9
-
7
+ configure_logging(level="INFO")
10
8
 
11
- # start our out file logging
12
- setup_logging(log_file=".basic-memory/basic-memory.log")
13
9
 
14
10
  # Create the shared server instance
15
11
  mcp = FastMCP("Basic Memory")
@@ -6,31 +6,22 @@ all tools with the MCP server.
6
6
  """
7
7
 
8
8
  # Import tools to register them with MCP
9
- from basic_memory.mcp.tools.memory import build_context, recent_activity
10
-
11
- # from basic_memory.mcp.tools.ai_edit import ai_edit
12
- from basic_memory.mcp.tools.notes import read_note, write_note
9
+ from basic_memory.mcp.tools.delete_note import delete_note
10
+ from basic_memory.mcp.tools.read_content import read_content
11
+ from basic_memory.mcp.tools.build_context import build_context
12
+ from basic_memory.mcp.tools.recent_activity import recent_activity
13
+ from basic_memory.mcp.tools.read_note import read_note
14
+ from basic_memory.mcp.tools.write_note import write_note
13
15
  from basic_memory.mcp.tools.search import search
14
-
15
- from basic_memory.mcp.tools.knowledge import (
16
- delete_entities,
17
- get_entity,
18
- get_entities,
19
- )
16
+ from basic_memory.mcp.tools.canvas import canvas
20
17
 
21
18
  __all__ = [
22
- # Knowledge graph tools
23
- "delete_entities",
24
- "get_entity",
25
- "get_entities",
26
- # Search tools
27
- "search",
28
- # memory tools
29
19
  "build_context",
30
- "recent_activity",
31
- # notes
20
+ "canvas",
21
+ "delete_note",
22
+ "read_content",
32
23
  "read_note",
24
+ "recent_activity",
25
+ "search",
33
26
  "write_note",
34
- # file edit
35
- # "ai_edit",
36
27
  ]
@@ -0,0 +1,85 @@
1
+ """Build context tool for Basic Memory MCP server."""
2
+
3
+ from typing import Optional
4
+
5
+ from loguru import logger
6
+
7
+ from basic_memory.mcp.async_client import client
8
+ from basic_memory.mcp.server import mcp
9
+ from basic_memory.mcp.tools.utils import call_get
10
+ from basic_memory.schemas.base import TimeFrame
11
+ from basic_memory.schemas.memory import (
12
+ GraphContext,
13
+ MemoryUrl,
14
+ memory_url_path,
15
+ normalize_memory_url,
16
+ )
17
+
18
+
19
+ @mcp.tool(
20
+ description="""Build context from a memory:// URI to continue conversations naturally.
21
+
22
+ Use this to follow up on previous discussions or explore related topics.
23
+ Timeframes support natural language like:
24
+ - "2 days ago"
25
+ - "last week"
26
+ - "today"
27
+ - "3 months ago"
28
+ Or standard formats like "7d", "24h"
29
+ """,
30
+ )
31
+ async def build_context(
32
+ url: MemoryUrl,
33
+ depth: Optional[int] = 1,
34
+ timeframe: Optional[TimeFrame] = "7d",
35
+ page: int = 1,
36
+ page_size: int = 10,
37
+ max_related: int = 10,
38
+ ) -> GraphContext:
39
+ """Get context needed to continue a discussion.
40
+
41
+ This tool enables natural continuation of discussions by loading relevant context
42
+ from memory:// URIs. It uses pattern matching to find relevant content and builds
43
+ a rich context graph of related information.
44
+
45
+ Args:
46
+ url: memory:// URI pointing to discussion content (e.g. memory://specs/search)
47
+ depth: How many relation hops to traverse (1-3 recommended for performance)
48
+ timeframe: How far back to look. Supports natural language like "2 days ago", "last week"
49
+ page: Page number of results to return (default: 1)
50
+ page_size: Number of results to return per page (default: 10)
51
+ max_related: Maximum number of related results to return (default: 10)
52
+
53
+ Returns:
54
+ GraphContext containing:
55
+ - primary_results: Content matching the memory:// URI
56
+ - related_results: Connected content via relations
57
+ - metadata: Context building details
58
+
59
+ Examples:
60
+ # Continue a specific discussion
61
+ build_context("memory://specs/search")
62
+
63
+ # Get deeper context about a component
64
+ build_context("memory://components/memory-service", depth=2)
65
+
66
+ # Look at recent changes to a specification
67
+ build_context("memory://specs/document-format", timeframe="today")
68
+
69
+ # Research the history of a feature
70
+ build_context("memory://features/knowledge-graph", timeframe="3 months ago")
71
+ """
72
+ logger.info(f"Building context from {url}")
73
+ url = normalize_memory_url(url)
74
+ response = await call_get(
75
+ client,
76
+ f"/memory/{memory_url_path(url)}",
77
+ params={
78
+ "depth": depth,
79
+ "timeframe": timeframe,
80
+ "page": page,
81
+ "page_size": page_size,
82
+ "max_related": max_related,
83
+ },
84
+ )
85
+ return GraphContext.model_validate(response.json())
@@ -0,0 +1,97 @@
1
+ """Canvas creation tool for Basic Memory MCP server.
2
+
3
+ This tool creates Obsidian canvas files (.canvas) using the JSON Canvas 1.0 spec.
4
+ """
5
+
6
+ import json
7
+ from typing import Dict, List, Any
8
+
9
+ from loguru import logger
10
+
11
+ from basic_memory.mcp.async_client import client
12
+ from basic_memory.mcp.server import mcp
13
+ from basic_memory.mcp.tools.utils import call_put
14
+
15
+
16
+ @mcp.tool(
17
+ description="Create an Obsidian canvas file to visualize concepts and connections.",
18
+ )
19
+ async def canvas(
20
+ nodes: List[Dict[str, Any]],
21
+ edges: List[Dict[str, Any]],
22
+ title: str,
23
+ folder: str,
24
+ ) -> str:
25
+ """Create an Obsidian canvas file with the provided nodes and edges.
26
+
27
+ This tool creates a .canvas file compatible with Obsidian's Canvas feature,
28
+ allowing visualization of relationships between concepts or documents.
29
+
30
+ For the full JSON Canvas 1.0 specification, see the 'spec://canvas' resource.
31
+
32
+ Args:
33
+ nodes: List of node objects following JSON Canvas 1.0 spec
34
+ edges: List of edge objects following JSON Canvas 1.0 spec
35
+ title: The title of the canvas (will be saved as title.canvas)
36
+ folder: The folder where the file should be saved
37
+
38
+ Returns:
39
+ A summary of the created canvas file
40
+
41
+ Important Notes:
42
+ - When referencing files, use the exact file path as shown in Obsidian
43
+ Example: "folder/Document Name.md" (not permalink format)
44
+ - For file nodes, the "file" attribute must reference an existing file
45
+ - Nodes require id, type, x, y, width, height properties
46
+ - Edges require id, fromNode, toNode properties
47
+ - Position nodes in a logical layout (x,y coordinates in pixels)
48
+ - Use color attributes ("1"-"6" or hex) for visual organization
49
+
50
+ Basic Structure:
51
+ ```json
52
+ {
53
+ "nodes": [
54
+ {
55
+ "id": "node1",
56
+ "type": "file", // Options: "file", "text", "link", "group"
57
+ "file": "folder/Document.md",
58
+ "x": 0,
59
+ "y": 0,
60
+ "width": 400,
61
+ "height": 300
62
+ }
63
+ ],
64
+ "edges": [
65
+ {
66
+ "id": "edge1",
67
+ "fromNode": "node1",
68
+ "toNode": "node2",
69
+ "label": "connects to"
70
+ }
71
+ ]
72
+ }
73
+ ```
74
+ """
75
+ # Ensure path has .canvas extension
76
+ file_title = title if title.endswith(".canvas") else f"{title}.canvas"
77
+ file_path = f"{folder}/{file_title}"
78
+
79
+ # Create canvas data structure
80
+ canvas_data = {"nodes": nodes, "edges": edges}
81
+
82
+ # Convert to JSON
83
+ canvas_json = json.dumps(canvas_data, indent=2)
84
+
85
+ # Write the file using the resource API
86
+ logger.info(f"Creating canvas file: {file_path}")
87
+ response = await call_put(client, f"/resource/{file_path}", json=canvas_json)
88
+
89
+ # Parse response
90
+ result = response.json()
91
+ logger.debug(result)
92
+
93
+ # Build summary
94
+ action = "Created" if response.status_code == 201 else "Updated"
95
+ summary = [f"# {action}: {file_path}", "\nThe canvas is ready to open in Obsidian."]
96
+
97
+ return "\n".join(summary)
@@ -0,0 +1,28 @@
1
+ from basic_memory.mcp.tools.utils import call_delete
2
+
3
+
4
+ from basic_memory.mcp.server import mcp
5
+ from basic_memory.mcp.async_client import client
6
+ from basic_memory.schemas import DeleteEntitiesResponse
7
+
8
+
9
+ @mcp.tool(description="Delete a note by title or permalink")
10
+ async def delete_note(identifier: str) -> bool:
11
+ """Delete a note from the knowledge base.
12
+
13
+ Args:
14
+ identifier: Note title or permalink
15
+
16
+ Returns:
17
+ True if note was deleted, False otherwise
18
+
19
+ Examples:
20
+ # Delete by title
21
+ delete_note("Meeting Notes: Project Planning")
22
+
23
+ # Delete by permalink
24
+ delete_note("notes/project-planning")
25
+ """
26
+ response = await call_delete(client, f"/knowledge/entities/{identifier}")
27
+ result = DeleteEntitiesResponse.model_validate(response.json())
28
+ return result.deleted
@@ -0,0 +1,51 @@
1
+ """Project info tool for Basic Memory MCP server."""
2
+
3
+ from loguru import logger
4
+
5
+ from basic_memory.mcp.async_client import client
6
+ from basic_memory.mcp.server import mcp
7
+ from basic_memory.mcp.tools.utils import call_get
8
+ from basic_memory.schemas import ProjectInfoResponse
9
+
10
+
11
+ @mcp.tool(
12
+ description="Get information and statistics about the current Basic Memory project.",
13
+ )
14
+ async def project_info() -> ProjectInfoResponse:
15
+ """Get comprehensive information about the current Basic Memory project.
16
+
17
+ This tool provides detailed statistics and status information about your
18
+ Basic Memory project, including:
19
+
20
+ - Project configuration
21
+ - Entity, observation, and relation counts
22
+ - Graph metrics (most connected entities, isolated entities)
23
+ - Recent activity and growth over time
24
+ - System status (database, watch service, version)
25
+
26
+ Use this tool to:
27
+ - Verify your Basic Memory installation is working correctly
28
+ - Get insights into your knowledge base structure
29
+ - Monitor growth and activity over time
30
+ - Identify potential issues like unresolved relations
31
+
32
+ Returns:
33
+ Detailed project information and statistics
34
+
35
+ Examples:
36
+ # Get information about the current project
37
+ info = await project_info()
38
+
39
+ # Check entity counts
40
+ print(f"Total entities: {info.statistics.total_entities}")
41
+
42
+ # Check system status
43
+ print(f"Basic Memory version: {info.system.version}")
44
+ """
45
+ logger.info("Getting project info")
46
+
47
+ # Call the API endpoint
48
+ response = await call_get(client, "/stats/project-info")
49
+
50
+ # Convert response to ProjectInfoResponse
51
+ return ProjectInfoResponse.model_validate(response.json())