basic-memory 0.7.0__py3-none-any.whl → 0.16.1__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 (150) hide show
  1. basic_memory/__init__.py +5 -1
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +27 -3
  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/5fe1ab1ccebe_add_projects_table.py +108 -0
  7. basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
  8. basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
  9. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
  10. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  11. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
  12. basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
  13. basic_memory/api/app.py +64 -18
  14. basic_memory/api/routers/__init__.py +4 -1
  15. basic_memory/api/routers/directory_router.py +84 -0
  16. basic_memory/api/routers/importer_router.py +152 -0
  17. basic_memory/api/routers/knowledge_router.py +166 -21
  18. basic_memory/api/routers/management_router.py +80 -0
  19. basic_memory/api/routers/memory_router.py +9 -64
  20. basic_memory/api/routers/project_router.py +406 -0
  21. basic_memory/api/routers/prompt_router.py +260 -0
  22. basic_memory/api/routers/resource_router.py +119 -4
  23. basic_memory/api/routers/search_router.py +5 -5
  24. basic_memory/api/routers/utils.py +130 -0
  25. basic_memory/api/template_loader.py +292 -0
  26. basic_memory/cli/app.py +43 -9
  27. basic_memory/cli/auth.py +277 -0
  28. basic_memory/cli/commands/__init__.py +13 -2
  29. basic_memory/cli/commands/cloud/__init__.py +6 -0
  30. basic_memory/cli/commands/cloud/api_client.py +112 -0
  31. basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
  32. basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
  33. basic_memory/cli/commands/cloud/core_commands.py +195 -0
  34. basic_memory/cli/commands/cloud/rclone_commands.py +301 -0
  35. basic_memory/cli/commands/cloud/rclone_config.py +110 -0
  36. basic_memory/cli/commands/cloud/rclone_installer.py +249 -0
  37. basic_memory/cli/commands/cloud/upload.py +233 -0
  38. basic_memory/cli/commands/cloud/upload_command.py +124 -0
  39. basic_memory/cli/commands/command_utils.py +51 -0
  40. basic_memory/cli/commands/db.py +28 -12
  41. basic_memory/cli/commands/import_chatgpt.py +40 -220
  42. basic_memory/cli/commands/import_claude_conversations.py +41 -168
  43. basic_memory/cli/commands/import_claude_projects.py +46 -157
  44. basic_memory/cli/commands/import_memory_json.py +48 -108
  45. basic_memory/cli/commands/mcp.py +84 -10
  46. basic_memory/cli/commands/project.py +876 -0
  47. basic_memory/cli/commands/status.py +50 -33
  48. basic_memory/cli/commands/tool.py +341 -0
  49. basic_memory/cli/main.py +8 -7
  50. basic_memory/config.py +477 -23
  51. basic_memory/db.py +168 -17
  52. basic_memory/deps.py +251 -25
  53. basic_memory/file_utils.py +113 -58
  54. basic_memory/ignore_utils.py +297 -0
  55. basic_memory/importers/__init__.py +27 -0
  56. basic_memory/importers/base.py +79 -0
  57. basic_memory/importers/chatgpt_importer.py +232 -0
  58. basic_memory/importers/claude_conversations_importer.py +177 -0
  59. basic_memory/importers/claude_projects_importer.py +148 -0
  60. basic_memory/importers/memory_json_importer.py +108 -0
  61. basic_memory/importers/utils.py +58 -0
  62. basic_memory/markdown/entity_parser.py +143 -23
  63. basic_memory/markdown/markdown_processor.py +3 -3
  64. basic_memory/markdown/plugins.py +39 -21
  65. basic_memory/markdown/schemas.py +1 -1
  66. basic_memory/markdown/utils.py +28 -13
  67. basic_memory/mcp/async_client.py +134 -4
  68. basic_memory/mcp/project_context.py +141 -0
  69. basic_memory/mcp/prompts/__init__.py +19 -0
  70. basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
  71. basic_memory/mcp/prompts/continue_conversation.py +62 -0
  72. basic_memory/mcp/prompts/recent_activity.py +188 -0
  73. basic_memory/mcp/prompts/search.py +57 -0
  74. basic_memory/mcp/prompts/utils.py +162 -0
  75. basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
  76. basic_memory/mcp/resources/project_info.py +71 -0
  77. basic_memory/mcp/server.py +7 -13
  78. basic_memory/mcp/tools/__init__.py +33 -21
  79. basic_memory/mcp/tools/build_context.py +120 -0
  80. basic_memory/mcp/tools/canvas.py +130 -0
  81. basic_memory/mcp/tools/chatgpt_tools.py +187 -0
  82. basic_memory/mcp/tools/delete_note.py +225 -0
  83. basic_memory/mcp/tools/edit_note.py +320 -0
  84. basic_memory/mcp/tools/list_directory.py +167 -0
  85. basic_memory/mcp/tools/move_note.py +545 -0
  86. basic_memory/mcp/tools/project_management.py +200 -0
  87. basic_memory/mcp/tools/read_content.py +271 -0
  88. basic_memory/mcp/tools/read_note.py +255 -0
  89. basic_memory/mcp/tools/recent_activity.py +534 -0
  90. basic_memory/mcp/tools/search.py +369 -23
  91. basic_memory/mcp/tools/utils.py +374 -16
  92. basic_memory/mcp/tools/view_note.py +77 -0
  93. basic_memory/mcp/tools/write_note.py +207 -0
  94. basic_memory/models/__init__.py +3 -2
  95. basic_memory/models/knowledge.py +67 -15
  96. basic_memory/models/project.py +87 -0
  97. basic_memory/models/search.py +10 -6
  98. basic_memory/repository/__init__.py +2 -0
  99. basic_memory/repository/entity_repository.py +229 -7
  100. basic_memory/repository/observation_repository.py +35 -3
  101. basic_memory/repository/project_info_repository.py +10 -0
  102. basic_memory/repository/project_repository.py +103 -0
  103. basic_memory/repository/relation_repository.py +21 -2
  104. basic_memory/repository/repository.py +147 -29
  105. basic_memory/repository/search_repository.py +411 -62
  106. basic_memory/schemas/__init__.py +22 -9
  107. basic_memory/schemas/base.py +97 -8
  108. basic_memory/schemas/cloud.py +50 -0
  109. basic_memory/schemas/directory.py +30 -0
  110. basic_memory/schemas/importer.py +35 -0
  111. basic_memory/schemas/memory.py +187 -25
  112. basic_memory/schemas/project_info.py +211 -0
  113. basic_memory/schemas/prompt.py +90 -0
  114. basic_memory/schemas/request.py +56 -2
  115. basic_memory/schemas/response.py +1 -1
  116. basic_memory/schemas/search.py +31 -35
  117. basic_memory/schemas/sync_report.py +72 -0
  118. basic_memory/services/__init__.py +2 -1
  119. basic_memory/services/context_service.py +241 -104
  120. basic_memory/services/directory_service.py +295 -0
  121. basic_memory/services/entity_service.py +590 -60
  122. basic_memory/services/exceptions.py +21 -0
  123. basic_memory/services/file_service.py +284 -30
  124. basic_memory/services/initialization.py +191 -0
  125. basic_memory/services/link_resolver.py +49 -56
  126. basic_memory/services/project_service.py +863 -0
  127. basic_memory/services/search_service.py +168 -32
  128. basic_memory/sync/__init__.py +3 -2
  129. basic_memory/sync/background_sync.py +26 -0
  130. basic_memory/sync/sync_service.py +1180 -109
  131. basic_memory/sync/watch_service.py +412 -135
  132. basic_memory/templates/prompts/continue_conversation.hbs +110 -0
  133. basic_memory/templates/prompts/search.hbs +101 -0
  134. basic_memory/utils.py +383 -51
  135. basic_memory-0.16.1.dist-info/METADATA +493 -0
  136. basic_memory-0.16.1.dist-info/RECORD +148 -0
  137. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
  138. basic_memory/alembic/README +0 -1
  139. basic_memory/cli/commands/sync.py +0 -206
  140. basic_memory/cli/commands/tools.py +0 -157
  141. basic_memory/mcp/tools/knowledge.py +0 -68
  142. basic_memory/mcp/tools/memory.py +0 -170
  143. basic_memory/mcp/tools/notes.py +0 -202
  144. basic_memory/schemas/discovery.py +0 -28
  145. basic_memory/sync/file_change_scanner.py +0 -158
  146. basic_memory/sync/utils.py +0 -31
  147. basic_memory-0.7.0.dist-info/METADATA +0 -378
  148. basic_memory-0.7.0.dist-info/RECORD +0 -82
  149. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
  150. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,142 +3,23 @@
3
3
  import asyncio
4
4
  import json
5
5
  from pathlib import Path
6
- from typing import Dict, Any, Annotated, Optional
6
+ from typing import Annotated
7
7
 
8
- import logfire
9
8
  import typer
9
+ from basic_memory.cli.app import claude_app
10
+ from basic_memory.config import get_project_config
11
+ from basic_memory.importers.claude_projects_importer import ClaudeProjectsImporter
12
+ from basic_memory.markdown import EntityParser, MarkdownProcessor
10
13
  from loguru import logger
11
14
  from rich.console import Console
12
15
  from rich.panel import Panel
13
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14
-
15
- from basic_memory.cli.app import claude_app
16
- from basic_memory.config import config
17
- from basic_memory.markdown import EntityParser, MarkdownProcessor
18
- from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
19
16
 
20
17
  console = Console()
21
18
 
22
19
 
23
- def clean_filename(text: str) -> str:
24
- """Convert text to safe filename."""
25
- clean = "".join(c if c.isalnum() else "-" for c in text.lower()).strip("-")
26
- return clean
27
-
28
-
29
- def format_project_markdown(project: Dict[str, Any], doc: Dict[str, Any]) -> EntityMarkdown:
30
- """Format a project document as a Basic Memory entity."""
31
-
32
- # Extract timestamps
33
- created_at = doc.get("created_at") or project["created_at"]
34
- modified_at = project["updated_at"]
35
-
36
- # Generate clean names for organization
37
- project_dir = clean_filename(project["name"])
38
- doc_file = clean_filename(doc["filename"])
39
-
40
- # Create entity
41
- entity = EntityMarkdown(
42
- frontmatter=EntityFrontmatter(
43
- metadata={
44
- "type": "project_doc",
45
- "title": doc["filename"],
46
- "created": created_at,
47
- "modified": modified_at,
48
- "permalink": f"{project_dir}/docs/{doc_file}",
49
- "project_name": project["name"],
50
- "project_uuid": project["uuid"],
51
- "doc_uuid": doc["uuid"],
52
- }
53
- ),
54
- content=doc["content"],
55
- )
56
-
57
- return entity
58
-
59
-
60
- def format_prompt_markdown(project: Dict[str, Any]) -> Optional[EntityMarkdown]:
61
- """Format project prompt template as a Basic Memory entity."""
62
-
63
- if not project.get("prompt_template"):
64
- return None
65
-
66
- # Extract timestamps
67
- created_at = project["created_at"]
68
- modified_at = project["updated_at"]
69
-
70
- # Generate clean project directory name
71
- project_dir = clean_filename(project["name"])
72
-
73
- # Create entity
74
- entity = EntityMarkdown(
75
- frontmatter=EntityFrontmatter(
76
- metadata={
77
- "type": "prompt_template",
78
- "title": f"Prompt Template: {project['name']}",
79
- "created": created_at,
80
- "modified": modified_at,
81
- "permalink": f"{project_dir}/prompt-template",
82
- "project_name": project["name"],
83
- "project_uuid": project["uuid"],
84
- }
85
- ),
86
- content=f"# Prompt Template: {project['name']}\n\n{project['prompt_template']}",
87
- )
88
-
89
- return entity
90
-
91
-
92
- async def process_projects_json(
93
- json_path: Path, base_path: Path, markdown_processor: MarkdownProcessor
94
- ) -> Dict[str, int]:
95
- """Import project data from Claude.ai projects.json format."""
96
-
97
- with Progress(
98
- SpinnerColumn(),
99
- TextColumn("[progress.description]{task.description}"),
100
- BarColumn(),
101
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
102
- console=console,
103
- ) as progress:
104
- read_task = progress.add_task("Reading project data...", total=None)
105
-
106
- # Read project data
107
- data = json.loads(json_path.read_text())
108
- progress.update(read_task, total=len(data))
109
-
110
- # Track import counts
111
- docs_imported = 0
112
- prompts_imported = 0
113
-
114
- # Process each project
115
- for project in data:
116
- project_dir = clean_filename(project["name"])
117
-
118
- # Create project directories
119
- docs_dir = base_path / project_dir / "docs"
120
- docs_dir.mkdir(parents=True, exist_ok=True)
121
-
122
- # Import prompt template if it exists
123
- if prompt_entity := format_prompt_markdown(project):
124
- file_path = base_path / f"{prompt_entity.frontmatter.metadata['permalink']}.md"
125
- await markdown_processor.write_file(file_path, prompt_entity)
126
- prompts_imported += 1
127
-
128
- # Import project documents
129
- for doc in project.get("docs", []):
130
- entity = format_project_markdown(project, doc)
131
- file_path = base_path / f"{entity.frontmatter.metadata['permalink']}.md"
132
- await markdown_processor.write_file(file_path, entity)
133
- docs_imported += 1
134
-
135
- progress.update(read_task, advance=1)
136
-
137
- return {"documents": docs_imported, "prompts": prompts_imported}
138
-
139
-
140
20
  async def get_markdown_processor() -> MarkdownProcessor:
141
21
  """Get MarkdownProcessor instance."""
22
+ config = get_project_config()
142
23
  entity_parser = EntityParser(config.home)
143
24
  return MarkdownProcessor(entity_parser)
144
25
 
@@ -161,36 +42,44 @@ def import_projects(
161
42
 
162
43
  After importing, run 'basic-memory sync' to index the new files.
163
44
  """
164
- with logfire.span("import claude projects"): # pyright: ignore [reportGeneralTypeIssues]
165
- try:
166
- if projects_json:
167
- if not projects_json.exists():
168
- typer.echo(f"Error: File not found: {projects_json}", err=True)
169
- raise typer.Exit(1)
170
-
171
- # Get markdown processor
172
- markdown_processor = asyncio.run(get_markdown_processor())
173
-
174
- # Process the file
175
- base_path = config.home / base_folder if base_folder else config.home
176
- console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
177
- results = asyncio.run(
178
- process_projects_json(projects_json, base_path, markdown_processor)
179
- )
180
-
181
- # Show results
182
- console.print(
183
- Panel(
184
- f"[green]Import complete![/green]\n\n"
185
- f"Imported {results['documents']} project documents\n"
186
- f"Imported {results['prompts']} prompt templates",
187
- expand=False,
188
- )
189
- )
190
-
191
- console.print("\nRun 'basic-memory sync' to index the new files.")
192
-
193
- except Exception as e:
194
- logger.error("Import failed")
195
- typer.echo(f"Error during import: {e}", err=True)
45
+ config = get_project_config()
46
+ try:
47
+ if not projects_json.exists():
48
+ typer.echo(f"Error: File not found: {projects_json}", err=True)
196
49
  raise typer.Exit(1)
50
+
51
+ # Get markdown processor
52
+ markdown_processor = asyncio.run(get_markdown_processor())
53
+
54
+ # Create the importer
55
+ importer = ClaudeProjectsImporter(config.home, markdown_processor)
56
+
57
+ # Process the file
58
+ base_path = config.home / base_folder if base_folder else config.home
59
+ console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
60
+
61
+ # Run the import
62
+ with projects_json.open("r", encoding="utf-8") as file:
63
+ json_data = json.load(file)
64
+ result = asyncio.run(importer.import_data(json_data, base_folder))
65
+
66
+ if not result.success: # pragma: no cover
67
+ typer.echo(f"Error during import: {result.error_message}", err=True)
68
+ raise typer.Exit(1)
69
+
70
+ # Show results
71
+ console.print(
72
+ Panel(
73
+ f"[green]Import complete![/green]\n\n"
74
+ f"Imported {result.documents} project documents\n"
75
+ f"Imported {result.prompts} prompt templates",
76
+ expand=False,
77
+ )
78
+ )
79
+
80
+ console.print("\nRun 'basic-memory sync' to index the new files.")
81
+
82
+ except Exception as e:
83
+ logger.error("Import failed")
84
+ typer.echo(f"Error during import: {e}", err=True)
85
+ raise typer.Exit(1)
@@ -3,97 +3,23 @@
3
3
  import asyncio
4
4
  import json
5
5
  from pathlib import Path
6
- from typing import Dict, Any, List, Annotated
6
+ from typing import Annotated
7
7
 
8
- import logfire
9
8
  import typer
9
+ from basic_memory.cli.app import import_app
10
+ from basic_memory.config import get_project_config
11
+ from basic_memory.importers.memory_json_importer import MemoryJsonImporter
12
+ from basic_memory.markdown import EntityParser, MarkdownProcessor
10
13
  from loguru import logger
11
14
  from rich.console import Console
12
15
  from rich.panel import Panel
13
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14
-
15
- from basic_memory.cli.app import import_app
16
- from basic_memory.config import config
17
- from basic_memory.markdown import EntityParser, MarkdownProcessor
18
- from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter, Observation, Relation
19
16
 
20
17
  console = Console()
21
18
 
22
19
 
23
- async def process_memory_json(
24
- json_path: Path, base_path: Path, markdown_processor: MarkdownProcessor
25
- ):
26
- """Import entities from memory.json using markdown processor."""
27
-
28
- # First pass - collect all relations by source entity
29
- entity_relations: Dict[str, List[Relation]] = {}
30
- entities: Dict[str, Dict[str, Any]] = {}
31
-
32
- with Progress(
33
- SpinnerColumn(),
34
- TextColumn("[progress.description]{task.description}"),
35
- BarColumn(),
36
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
37
- console=console,
38
- ) as progress:
39
- read_task = progress.add_task("Reading memory.json...", total=None)
40
-
41
- # First pass - collect entities and relations
42
- with open(json_path) as f:
43
- lines = f.readlines()
44
- progress.update(read_task, total=len(lines))
45
-
46
- for line in lines:
47
- data = json.loads(line)
48
- if data["type"] == "entity":
49
- entities[data["name"]] = data
50
- elif data["type"] == "relation":
51
- # Store relation with its source entity
52
- source = data.get("from") or data.get("from_id")
53
- if source not in entity_relations:
54
- entity_relations[source] = []
55
- entity_relations[source].append(
56
- Relation(
57
- type=data.get("relationType") or data.get("relation_type"),
58
- target=data.get("to") or data.get("to_id"),
59
- )
60
- )
61
- progress.update(read_task, advance=1)
62
-
63
- # Second pass - create and write entities
64
- write_task = progress.add_task("Creating entities...", total=len(entities))
65
-
66
- entities_created = 0
67
- for name, entity_data in entities.items():
68
- entity = EntityMarkdown(
69
- frontmatter=EntityFrontmatter(
70
- metadata={
71
- "type": entity_data["entityType"],
72
- "title": name,
73
- "permalink": f"{entity_data['entityType']}/{name}",
74
- }
75
- ),
76
- content=f"# {name}\n",
77
- observations=[Observation(content=obs) for obs in entity_data["observations"]],
78
- relations=entity_relations.get(
79
- name, []
80
- ), # Add any relations where this entity is the source
81
- )
82
-
83
- # Let markdown processor handle writing
84
- file_path = base_path / f"{entity_data['entityType']}/{name}.md"
85
- await markdown_processor.write_file(file_path, entity)
86
- entities_created += 1
87
- progress.update(write_task, advance=1)
88
-
89
- return {
90
- "entities": entities_created,
91
- "relations": sum(len(rels) for rels in entity_relations.values()),
92
- }
93
-
94
-
95
20
  async def get_markdown_processor() -> MarkdownProcessor:
96
21
  """Get MarkdownProcessor instance."""
22
+ config = get_project_config()
97
23
  entity_parser = EntityParser(config.home)
98
24
  return MarkdownProcessor(entity_parser)
99
25
 
@@ -103,6 +29,9 @@ def memory_json(
103
29
  json_path: Annotated[Path, typer.Argument(..., help="Path to memory.json file")] = Path(
104
30
  "memory.json"
105
31
  ),
32
+ destination_folder: Annotated[
33
+ str, typer.Option(help="Optional destination folder within the project")
34
+ ] = "",
106
35
  ):
107
36
  """Import entities and relations from a memory.json file.
108
37
 
@@ -110,37 +39,48 @@ def memory_json(
110
39
  1. Read entities and relations from the JSON file
111
40
  2. Create markdown files for each entity
112
41
  3. Include outgoing relations in each entity's markdown
113
-
114
- After importing, run 'basic-memory sync' to index the new files.
115
42
  """
116
43
 
117
- with logfire.span("import memory_json"): # pyright: ignore [reportGeneralTypeIssues]
118
- if not json_path.exists():
119
- typer.echo(f"Error: File not found: {json_path}", err=True)
120
- raise typer.Exit(1)
44
+ if not json_path.exists():
45
+ typer.echo(f"Error: File not found: {json_path}", err=True)
46
+ raise typer.Exit(1)
121
47
 
122
- try:
123
- # Get markdown processor
124
- markdown_processor = asyncio.run(get_markdown_processor())
125
-
126
- # Process the file
127
- base_path = config.home
128
- console.print(f"\nImporting from {json_path}...writing to {base_path}")
129
- results = asyncio.run(process_memory_json(json_path, base_path, markdown_processor))
130
-
131
- # Show results
132
- console.print(
133
- Panel(
134
- f"[green]Import complete![/green]\n\n"
135
- f"Created {results['entities']} entities\n"
136
- f"Added {results['relations']} relations",
137
- expand=False,
138
- )
139
- )
48
+ config = get_project_config()
49
+ try:
50
+ # Get markdown processor
51
+ markdown_processor = asyncio.run(get_markdown_processor())
52
+
53
+ # Create the importer
54
+ importer = MemoryJsonImporter(config.home, markdown_processor)
140
55
 
141
- console.print("\nRun 'basic-memory sync' to index the new files.")
56
+ # Process the file
57
+ base_path = config.home if not destination_folder else config.home / destination_folder
58
+ console.print(f"\nImporting from {json_path}...writing to {base_path}")
142
59
 
143
- except Exception as e:
144
- logger.error("Import failed")
145
- typer.echo(f"Error during import: {e}", err=True)
60
+ # Run the import for json log format
61
+ file_data = []
62
+ with json_path.open("r", encoding="utf-8") as file:
63
+ for line in file:
64
+ json_data = json.loads(line)
65
+ file_data.append(json_data)
66
+ result = asyncio.run(importer.import_data(file_data, destination_folder))
67
+
68
+ if not result.success: # pragma: no cover
69
+ typer.echo(f"Error during import: {result.error_message}", err=True)
146
70
  raise typer.Exit(1)
71
+
72
+ # Show results
73
+ console.print(
74
+ Panel(
75
+ f"[green]Import complete![/green]\n\n"
76
+ f"Created {result.entities} entities\n"
77
+ f"Added {result.relations} relations\n"
78
+ f"Skipped {result.skipped_entities} entities\n",
79
+ expand=False,
80
+ )
81
+ )
82
+
83
+ except Exception as e:
84
+ logger.error("Import failed")
85
+ typer.echo(f"Error during import: {e}", err=True)
86
+ raise typer.Exit(1)
@@ -1,8 +1,12 @@
1
- """MCP server command."""
1
+ """MCP server command with streamable HTTP transport."""
2
+
3
+ import asyncio
4
+ import os
5
+ import typer
6
+ from typing import Optional
2
7
 
3
- from loguru import logger
4
8
  from basic_memory.cli.app import app
5
- from basic_memory.config import config
9
+ from basic_memory.config import ConfigManager
6
10
 
7
11
  # Import mcp instance
8
12
  from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
@@ -10,11 +14,81 @@ from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
10
14
  # Import mcp tools to register them
11
15
  import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
12
16
 
17
+ # Import prompts to register them
18
+ import basic_memory.mcp.prompts # noqa: F401 # pragma: no cover
19
+ from loguru import logger
20
+ import threading
21
+ from basic_memory.services.initialization import initialize_file_sync
22
+
23
+ config = ConfigManager().config
24
+
25
+ if not config.cloud_mode_enabled:
26
+
27
+ @app.command()
28
+ def mcp(
29
+ transport: str = typer.Option(
30
+ "stdio", help="Transport type: stdio, streamable-http, or sse"
31
+ ),
32
+ host: str = typer.Option(
33
+ "0.0.0.0", help="Host for HTTP transports (use 0.0.0.0 to allow external connections)"
34
+ ),
35
+ port: int = typer.Option(8000, help="Port for HTTP transports"),
36
+ path: str = typer.Option("/mcp", help="Path prefix for streamable-http transport"),
37
+ project: Optional[str] = typer.Option(None, help="Restrict MCP server to single project"),
38
+ ): # pragma: no cover
39
+ """Run the MCP server with configurable transport options.
40
+
41
+ This command starts an MCP server using one of three transport options:
42
+
43
+ - stdio: Standard I/O (good for local usage)
44
+ - streamable-http: Recommended for web deployments (default)
45
+ - sse: Server-Sent Events (for compatibility with existing clients)
46
+ """
47
+
48
+ # Validate and set project constraint if specified
49
+ if project:
50
+ config_manager = ConfigManager()
51
+ project_name, _ = config_manager.get_project(project)
52
+ if not project_name:
53
+ typer.echo(f"No project found named: {project}", err=True)
54
+ raise typer.Exit(1)
55
+
56
+ # Set env var with validated project name
57
+ os.environ["BASIC_MEMORY_MCP_PROJECT"] = project_name
58
+ logger.info(f"MCP server constrained to project: {project_name}")
59
+
60
+ app_config = ConfigManager().config
61
+
62
+ def run_file_sync():
63
+ """Run file sync in a separate thread with its own event loop."""
64
+ loop = asyncio.new_event_loop()
65
+ asyncio.set_event_loop(loop)
66
+ try:
67
+ loop.run_until_complete(initialize_file_sync(app_config))
68
+ except Exception as e:
69
+ logger.error(f"File sync error: {e}", err=True)
70
+ finally:
71
+ loop.close()
72
+
73
+ logger.info(f"Sync changes enabled: {app_config.sync_changes}")
74
+ if app_config.sync_changes:
75
+ # Start the sync thread
76
+ sync_thread = threading.Thread(target=run_file_sync, daemon=True)
77
+ sync_thread.start()
78
+ logger.info("Started file sync in background")
79
+
80
+ # Now run the MCP server (blocks)
81
+ logger.info(f"Starting MCP server with {transport.upper()} transport")
13
82
 
14
- @app.command()
15
- def mcp(): # pragma: no cover
16
- """Run the MCP server for Claude Desktop integration."""
17
- home_dir = config.home
18
- logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
19
- logger.info(f"Home directory: {home_dir}")
20
- mcp_server.run()
83
+ if transport == "stdio":
84
+ mcp_server.run(
85
+ transport=transport,
86
+ )
87
+ elif transport == "streamable-http" or transport == "sse":
88
+ mcp_server.run(
89
+ transport=transport,
90
+ host=host,
91
+ port=port,
92
+ path=path,
93
+ log_level="INFO",
94
+ )