Skip to content

Commit 00c8633

Browse files
authored
feat: add watch to mcp process (#83)
1 parent 617e60b commit 00c8633

6 files changed

Lines changed: 92 additions & 24 deletions

File tree

src/basic_memory/api/app.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
11
"""FastAPI application for basic-memory knowledge graph API."""
22

3+
import asyncio
34
from contextlib import asynccontextmanager
45

56
from fastapi import FastAPI, HTTPException
67
from fastapi.exception_handlers import http_exception_handler
78
from loguru import logger
89

910
from basic_memory import db
10-
from basic_memory.config import config as app_config
11-
from basic_memory.api.routers import knowledge, search, memory, resource, project_info
11+
from basic_memory.api.routers import knowledge, memory, project_info, resource, search
12+
from basic_memory.config import config as project_config
13+
from basic_memory.config import config_manager
14+
from basic_memory.sync import SyncService, WatchService
15+
16+
17+
async def run_background_sync(sync_service: SyncService, watch_service: WatchService): # pragma: no cover
18+
logger.info(f"Starting watch service to sync file changes in dir: {project_config.home}")
19+
# full sync
20+
await sync_service.sync(project_config.home, show_progress=False)
21+
22+
# watch changes
23+
await watch_service.run()
1224

1325

1426
@asynccontextmanager
1527
async def lifespan(app: FastAPI): # pragma: no cover
1628
"""Lifecycle manager for the FastAPI app."""
17-
await db.run_migrations(app_config)
29+
await db.run_migrations(project_config)
30+
31+
# app config
32+
basic_memory_config = config_manager.load_config()
33+
logger.info(f"Sync changes enabled: {basic_memory_config.sync_changes}")
34+
logger.info(f"Update permalinks on move enabled: {basic_memory_config.update_permalinks_on_move}")
35+
36+
watch_task = None
37+
if basic_memory_config.sync_changes:
38+
# import after migrations have run
39+
from basic_memory.cli.commands.sync import get_sync_service
40+
41+
sync_service = await get_sync_service()
42+
watch_service = WatchService(
43+
sync_service=sync_service,
44+
file_service=sync_service.entity_service.file_service,
45+
config=project_config,
46+
)
47+
watch_task = asyncio.create_task(run_background_sync(sync_service, watch_service))
48+
else:
49+
logger.info("Sync changes disabled. Skipping watch service.")
50+
51+
52+
# proceed with startup
1853
yield
54+
1955
logger.info("Shutting down Basic Memory API")
56+
if watch_task:
57+
watch_task.cancel()
58+
2059
await db.shutdown_db()
2160

2261

src/basic_memory/cli/app.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import asyncio
21
from typing import Optional
32

43
import typer
54

6-
from basic_memory import db
7-
from basic_memory.config import config
8-
95

106
def version_callback(value: bool) -> None:
117
"""Show version and exit."""
@@ -58,9 +54,6 @@ def app_callback(
5854
config = new_config
5955

6056

61-
# Run database migrations
62-
asyncio.run(db.run_migrations(config))
63-
6457
# Register sub-command groups
6558
import_app = typer.Typer(help="Import data from various sources")
6659
app.add_typer(import_app, name="import")

src/basic_memory/cli/commands/mcp.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import basic_memory
66
from basic_memory.cli.app import app
7-
from basic_memory.config import config
7+
from basic_memory.config import config, config_manager
88

99
# Import mcp instance
1010
from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
@@ -19,8 +19,15 @@ def mcp(): # pragma: no cover
1919
home_dir = config.home
2020
project_name = config.project
2121

22+
# app config
23+
basic_memory_config = config_manager.load_config()
24+
2225
logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
2326
logger.info(f"Project: {project_name}")
2427
logger.info(f"Project directory: {home_dir}")
28+
logger.info(f"Sync changes enabled: {basic_memory_config.sync_changes}")
29+
logger.info(
30+
f"Update permalinks on move enabled: {basic_memory_config.update_permalinks_on_move}"
31+
)
2532

2633
mcp_server.run()

src/basic_memory/cli/main.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
"""Main CLI entry point for basic-memory.""" # pragma: no cover
22

3-
from basic_memory.cli.app import app # pragma: no cover
3+
import asyncio
4+
45
import typer
56

7+
from basic_memory.cli.app import app # pragma: no cover
8+
69
# Register commands
710
from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
8-
status,
9-
sync,
1011
db,
11-
import_memory_json,
12-
mcp,
12+
import_chatgpt,
1313
import_claude_conversations,
1414
import_claude_projects,
15-
import_chatgpt,
16-
tool,
15+
import_memory_json,
16+
mcp,
1717
project,
18+
status,
19+
sync,
20+
tool,
1821
)
22+
from basic_memory.config import config
23+
from basic_memory.db import run_migrations as db_run_migrations
1924

2025

2126
# Version command
@@ -55,4 +60,8 @@ def main(
5560

5661

5762
if __name__ == "__main__": # pragma: no cover
63+
# Run database migrations
64+
asyncio.run(db_run_migrations(config))
65+
66+
# start the app
5867
app()

src/basic_memory/config.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ class ProjectConfig(BaseSettings):
3838
default=500, description="Milliseconds to wait after changes before syncing", gt=0
3939
)
4040

41-
log_level: str = "DEBUG"
42-
41+
# update permalinks on move
4342
update_permalinks_on_move: bool = Field(
4443
default=False,
4544
description="Whether to update permalinks when files are moved or renamed. default (False)",
@@ -82,6 +81,18 @@ class BasicMemoryConfig(BaseSettings):
8281
description="Name of the default project to use",
8382
)
8483

84+
log_level: str = "INFO"
85+
86+
update_permalinks_on_move: bool = Field(
87+
default=False,
88+
description="Whether to update permalinks when files are moved or renamed. default (False)",
89+
)
90+
91+
sync_changes: bool = Field(
92+
default=True,
93+
description="Whether to sync changes in real time. default (True)",
94+
)
95+
8596
model_config = SettingsConfigDict(
8697
env_prefix="BASIC_MEMORY_",
8798
extra="ignore",
@@ -199,9 +210,14 @@ def get_project_config(project_name: Optional[str] = None) -> ProjectConfig:
199210
"BASIC_MEMORY_PROJECT", project_name or config_manager.default_project
200211
)
201212

213+
update_permalinks_on_move = config_manager.load_config().update_permalinks_on_move
202214
try:
203215
project_path = config_manager.get_project_path(actual_project_name)
204-
return ProjectConfig(home=project_path, project=actual_project_name)
216+
return ProjectConfig(
217+
home=project_path,
218+
project=actual_project_name,
219+
update_permalinks_on_move=update_permalinks_on_move,
220+
)
205221
except ValueError: # pragma: no cover
206222
logger.warning(f"Project '{actual_project_name}' not found, using default")
207223
project_path = config_manager.get_project_path(config_manager.default_project)
@@ -230,8 +246,10 @@ def get_process_name(): # pragma: no cover
230246
return "sync"
231247
elif "mcp" in sys.argv:
232248
return "mcp"
233-
else:
249+
elif "cli" in sys.argv:
234250
return "cli"
251+
else:
252+
return "api"
235253

236254

237255
process_name = get_process_name()
@@ -251,7 +269,7 @@ def setup_basic_memory_logging(): # pragma: no cover
251269
setup_logging(
252270
env=config.env,
253271
home_dir=user_home, # Use user home for logs
254-
log_level=config.log_level,
272+
log_level=config_manager.load_config().log_level,
255273
log_file=f"{DATA_DIR_NAME}/basic-memory-{process_name}.log",
256274
console=False,
257275
)

src/basic_memory/db.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ async def engine_session_factory(
146146
_session_maker = None
147147

148148

149-
async def run_migrations(app_config: ProjectConfig, database_type=DatabaseType.FILESYSTEM):
149+
async def run_migrations(
150+
app_config: ProjectConfig, database_type=DatabaseType.FILESYSTEM
151+
): # pragma: no cover
150152
"""Run any pending alembic migrations."""
151153
logger.info("Running database migrations...")
152154
try:

0 commit comments

Comments
 (0)