mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
856737fe3c
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
88 lines
2.7 KiB
Python
88 lines
2.7 KiB
Python
"""
|
|
Basic Memory FastMCP server.
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastmcp import FastMCP
|
|
from loguru import logger
|
|
|
|
from basic_memory import db
|
|
from basic_memory.config import ConfigManager
|
|
from basic_memory.services.initialization import initialize_app, initialize_file_sync
|
|
from basic_memory.telemetry import show_notice_if_needed, track_app_started
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastMCP):
|
|
"""Lifecycle manager for the MCP server.
|
|
|
|
Handles:
|
|
- Database initialization and migrations
|
|
- Telemetry notice and tracking
|
|
- File sync in background (if enabled and not in cloud mode)
|
|
- Proper cleanup on shutdown
|
|
"""
|
|
app_config = ConfigManager().config
|
|
logger.info("Starting Basic Memory MCP server")
|
|
|
|
# Show telemetry notice (first run only) and track startup
|
|
show_notice_if_needed()
|
|
track_app_started("mcp")
|
|
|
|
# Track if we created the engine (vs test fixtures providing it)
|
|
# This prevents disposing an engine provided by test fixtures when
|
|
# multiple Client connections are made in the same test
|
|
engine_was_none = db._engine is None
|
|
|
|
# Initialize app (runs migrations, reconciles projects)
|
|
await initialize_app(app_config)
|
|
|
|
# Start file sync as background task (if enabled and not in cloud mode)
|
|
sync_task = None
|
|
is_test_env = (
|
|
app_config.env == "test"
|
|
or os.getenv("BASIC_MEMORY_ENV", "").lower() == "test"
|
|
or os.getenv("PYTEST_CURRENT_TEST") is not None
|
|
)
|
|
if is_test_env:
|
|
logger.info("Test environment detected - skipping local file sync")
|
|
elif app_config.sync_changes and not app_config.cloud_mode_enabled:
|
|
logger.info("Starting file sync in background")
|
|
|
|
async def _file_sync_runner() -> None:
|
|
await initialize_file_sync(app_config)
|
|
|
|
sync_task = asyncio.create_task(_file_sync_runner())
|
|
elif app_config.cloud_mode_enabled:
|
|
logger.info("Cloud mode enabled - skipping local file sync")
|
|
else:
|
|
logger.info("Sync changes disabled - skipping file sync")
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
# Shutdown
|
|
logger.info("Shutting down Basic Memory MCP server")
|
|
if sync_task:
|
|
sync_task.cancel()
|
|
try:
|
|
await sync_task
|
|
except asyncio.CancelledError:
|
|
logger.info("File sync task cancelled")
|
|
|
|
# Only shutdown DB if we created it (not if test fixture provided it)
|
|
if engine_was_none:
|
|
await db.shutdown_db()
|
|
logger.info("Database connections closed")
|
|
else:
|
|
logger.debug("Skipping DB shutdown - engine provided externally")
|
|
|
|
|
|
mcp = FastMCP(
|
|
name="Basic Memory",
|
|
lifespan=lifespan,
|
|
)
|