diff --git a/src/basic_memory/mcp/project_context.py b/src/basic_memory/mcp/project_context.py index 02f56134..a81b3fac 100644 --- a/src/basic_memory/mcp/project_context.py +++ b/src/basic_memory/mcp/project_context.py @@ -40,6 +40,29 @@ def set_workspace_provider(provider: Callable[[], Awaitable[list[WorkspaceInfo]] _workspace_provider = provider +async def _resolve_default_project_from_api() -> Optional[str]: + """Query the projects API for the default project. + + Used as a fallback when ConfigManager has no local config (cloud mode). + """ + from basic_memory.mcp.async_client import get_client + + try: + async with get_client() as client: + response = await client.get("/v2/projects/") + if response.status_code == 200: + project_list = ProjectList.model_validate(response.json()) + if project_list.default_project: + return project_list.default_project + # Fallback: find project with is_default=True + for p in project_list.projects: + if p.is_default: + return p.name + except Exception: + pass + return None + + async def resolve_project_parameter( project: Optional[str] = None, allow_discovery: bool = False, @@ -66,11 +89,16 @@ async def resolve_project_parameter( Returns: Resolved project name or None if no resolution possible """ - # Load config for any values not explicitly provided + # Load config for any values not explicitly provided. + # ConfigManager reads from the local config file, which doesn't exist in cloud mode. + # When it returns None, fall back to querying the projects API for the is_default flag. if default_project is None: config = ConfigManager().config default_project = config.default_project + if default_project is None: + default_project = await _resolve_default_project_from_api() + # Create resolver with configuration and resolve resolver = ProjectResolver.from_env( default_project=default_project, diff --git a/tests/mcp/test_tool_recent_activity.py b/tests/mcp/test_tool_recent_activity.py index f9194071..8ef7b786 100644 --- a/tests/mcp/test_tool_recent_activity.py +++ b/tests/mcp/test_tool_recent_activity.py @@ -128,69 +128,16 @@ async def test_recent_activity_type_invalid(client, test_project, test_graph): @pytest.mark.asyncio -async def test_recent_activity_discovery_mode(client, test_project, test_graph, config_manager): - """Test that recent_activity discovery mode works without project parameter.""" - # Clear default_project to test discovery mode - cfg = config_manager.load_config() - cfg.default_project = None - config_manager.save_config(cfg) - - # Test discovery mode (no project parameter) +async def test_recent_activity_uses_default_project(client, test_project, test_graph): + """When no project parameter is given, recent_activity uses the default project.""" + # Call without explicit project — should resolve to the default result = await recent_activity() assert result is not None assert isinstance(result, str) - # Check that we get a formatted summary - assert "Recent Activity Summary" in result - assert "Most Active Project:" in result or "Other Active Projects:" in result - assert "Summary:" in result - assert "active projects" in result - - # Should contain project discovery guidance - assert "Suggested project:" in result or "Multiple active projects" in result - assert "Session reminder:" in result - - -@pytest.mark.asyncio -async def test_recent_activity_discovery_mode_no_activity(client, test_project, config_manager): - """If there is no activity in any project, discovery mode should say so.""" - # Clear default_project to test discovery mode - cfg = config_manager.load_config() - cfg.default_project = None - config_manager.save_config(cfg) - - result = await recent_activity() - assert "Recent Activity Summary" in result - assert "No recent activity found in any project." in result - - -@pytest.mark.asyncio -async def test_recent_activity_discovery_mode_multiple_active_projects( - app, client, test_project, tmp_path_factory, config_manager -): - """Discovery mode should use the multi-project guidance when multiple projects have activity.""" - # Clear default_project to test discovery mode - cfg = config_manager.load_config() - cfg.default_project = None - config_manager.save_config(cfg) - - from basic_memory.mcp.tools import create_memory_project, write_note - - second_root = tmp_path_factory.mktemp("second-project-home") - - result = await create_memory_project( - project_name="second-project", - project_path=str(second_root), - set_default=False, - ) - assert result.startswith("✓") - - await write_note(project=test_project.name, title="One", directory="notes", content="one") - await write_note(project="second-project", title="Two", directory="notes", content="two") - - out = await recent_activity() - assert "Recent Activity Summary" in out - assert "or would you prefer a different project" in out + # Should return project-specific output for the default project + assert "Recent Activity:" in result + assert "Activity Summary:" in result def test_recent_activity_format_relative_time_and_truncate_helpers():