Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/basic_memory/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ async def lifespan(app: FastAPI): # pragma: no cover
# Legacy web app proxy paths (compat with /proxy/projects/projects)
app.include_router(v2_project, prefix="/proxy/projects")

# Legacy v1 compat: older CLI versions call GET /projects/projects
app.include_router(v2_project, prefix="/projects")

# V2 routers are the only public API surface


Expand Down
21 changes: 10 additions & 11 deletions src/basic_memory/cli/commands/mcp.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
"""MCP server command with streamable HTTP transport."""

import os
import typer
from typing import Optional

import typer
from loguru import logger

from basic_memory.cli.app import app
from basic_memory.config import ConfigManager, init_mcp_logging

# Import mcp instance (has lifespan that handles initialization and file sync)
from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover

# Import mcp tools to register them
import basic_memory.mcp.tools # noqa: F401 # pragma: no cover

# Import prompts to register them
import basic_memory.mcp.prompts # noqa: F401 # pragma: no cover
from loguru import logger


@app.command()
def mcp(
Expand Down Expand Up @@ -47,6 +39,13 @@ def mcp(
# Even when cloud_mode_enabled is True, stdio MCP runs locally and needs local API access.
os.environ["BASIC_MEMORY_FORCE_LOCAL"] = "true"

# Deferred imports to avoid heavy startup cost for unrelated CLI commands
from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover

# Import mcp tools/prompts to register them with the server
import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
import basic_memory.mcp.prompts # noqa: F401 # pragma: no cover

# Initialize logging for MCP (file only, stdout breaks protocol)
init_mcp_logging()

Expand Down
35 changes: 21 additions & 14 deletions src/basic_memory/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
"""Main CLI entry point for basic-memory.""" # pragma: no cover

import sys

from basic_memory.cli.app import app # pragma: no cover

# Register commands
from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
cloud,
db,
doctor,
import_chatgpt,
import_claude_conversations,
import_claude_projects,
import_memory_json,
mcp,
project,
status,
tool,
)

def _version_flag_present(argv: list[str]) -> bool:
return any(flag in argv for flag in ("--version", "-v"))


if not _version_flag_present(sys.argv[1:]):
Comment on lines +8 to +12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Don’t treat every -v as a global version flag

The new _version_flag_present gate treats any -v anywhere in argv as --version, which skips importing all subcommands. That breaks normal invocations like bm status -v or bm project -v where -v is a per-command verbose flag (see status.py/project.py options), because the command modules never register and Typer will report “No such command.” This regression only happens when a subcommand uses -v, so the fix should scope the version check to top-level flags (e.g., only when -v/--version appears before any subcommand).

Useful? React with 👍 / 👎.

# Register commands only when not short-circuiting for --version
from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
cloud,
db,
doctor,
import_chatgpt,
import_claude_conversations,
import_claude_projects,
import_memory_json,
mcp,
project,
status,
tool,
)
# Re-apply warning filter AFTER all imports
# (authlib adds a DeprecationWarning filter that overrides ours)
import warnings # pragma: no cover
Expand Down
23 changes: 23 additions & 0 deletions tests/api/v2/test_project_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,26 @@ async def test_resolve_project_empty_identifier(client: AsyncClient, v2_projects
response = await client.post(f"{v2_projects_url}/resolve", json=resolve_data)

assert response.status_code == 422 # Validation error


# --- Legacy v1 compatibility tests ---


@pytest.mark.asyncio
async def test_legacy_v1_list_projects_endpoint(client: AsyncClient, test_project: Project):
"""Test that the legacy /projects/projects endpoint still works for older CLI versions.

This endpoint was removed when we migrated to v2 but older versions of
basic-memory-cloud CLI still call it for `bm project list`.
"""
# The legacy v1 endpoint was at /projects/projects
response = await client.get("/projects/projects/")

assert response.status_code == 200
data = response.json()
assert "projects" in data
assert "default_project" in data

# Verify the test project is in the list
project_names = [p["name"] for p in data["projects"]]
assert test_project.name in project_names
Loading