33This module provides a single canonical implementation of project resolution
44logic, eliminating duplicated decision trees across the codebase.
55
6- The resolution follows a three-tier hierarchy:
7- 1. Constrained mode: BASIC_MEMORY_MCP_PROJECT env var (highest priority)
8- 2. Explicit parameter: Project passed directly to operation
9- 3. Default project: Used when default_project_mode=true (lowest priority)
6+ The resolution follows a unified linear priority chain that works
7+ identically in both local and cloud modes:
108
11- In cloud mode, project is required unless discovery mode is explicitly allowed.
9+ 1. ENV_CONSTRAINT: BASIC_MEMORY_MCP_PROJECT env var (highest priority)
10+ 2. EXPLICIT: Project passed directly to operation
11+ 3. DEFAULT: Default project when default_project_mode=true
12+ 4. Fallback: cloud → CLOUD_DISCOVERY or ValueError; local → NONE
1213"""
1314
1415import os
@@ -68,7 +69,7 @@ class ProjectResolver:
6869 used by MCP tools, API routes, and CLI commands.
6970
7071 Args:
71- cloud_mode: Whether running in cloud mode (project required)
72+ cloud_mode: Whether running in cloud mode
7273 default_project_mode: Whether to use default project when not specified
7374 default_project: The default project name
7475 constrained_project: Optional env-constrained project override
@@ -110,13 +111,13 @@ def resolve(
110111 project : Optional [str ] = None ,
111112 allow_discovery : bool = False ,
112113 ) -> ResolvedProject :
113- """Resolve project using the three-tier hierarchy .
114+ """Resolve project using a unified linear priority chain .
114115
115- Resolution order:
116- 1. Cloud mode check (project required unless discovery allowed )
117- 2. Constrained project from env var (highest priority in local mode)
118- 3. Explicit project parameter
119- 4. Default project if default_project_mode=true
116+ The same resolution order applies in both local and cloud modes :
117+ 1. ENV_CONSTRAINT — BASIC_MEMORY_MCP_PROJECT env var (highest priority )
118+ 2. EXPLICIT — project parameter passed directly
119+ 3. DEFAULT — default project when default_project_mode=true
120+ 4. Fallback — cloud: CLOUD_DISCOVERY or ValueError; local: NONE
120121
121122 Args:
122123 project: Optional explicit project parameter
@@ -127,31 +128,10 @@ def resolve(
127128 ResolvedProject with project name, resolution mode, and reason
128129
129130 Raises:
130- ValueError: If in cloud mode and no project specified (unless discovery allowed)
131+ ValueError: If in cloud mode and no project could be resolved
132+ (unless allow_discovery=True)
131133 """
132- # --- Cloud Mode Handling ---
133- # In cloud mode, project is required unless discovery is explicitly allowed
134- if self .cloud_mode :
135- if project :
136- logger .debug (f"Cloud mode: using explicit project '{ project } '" )
137- return ResolvedProject (
138- project = project ,
139- mode = ResolutionMode .CLOUD_EXPLICIT ,
140- reason = f"Explicit project in cloud mode: { project } " ,
141- )
142- elif allow_discovery :
143- logger .debug ("Cloud mode: discovery mode allowed, no project required" )
144- return ResolvedProject (
145- project = None ,
146- mode = ResolutionMode .CLOUD_DISCOVERY ,
147- reason = "Discovery mode enabled in cloud" ,
148- )
149- else :
150- raise ValueError ("No project specified. Project is required for cloud mode." )
151-
152- # --- Local Mode: Three-Tier Hierarchy ---
153-
154- # Priority 1: CLI constraint overrides everything
134+ # --- Priority 1: ENV constraint overrides everything ---
155135 if self .constrained_project :
156136 logger .debug (f"Using CLI constrained project: { self .constrained_project } " )
157137 return ResolvedProject (
@@ -160,16 +140,17 @@ def resolve(
160140 reason = f"Environment constraint: BASIC_MEMORY_MCP_PROJECT={ self .constrained_project } " ,
161141 )
162142
163- # Priority 2: Explicit project parameter
143+ # --- Priority 2: Explicit project parameter ---
164144 if project :
145+ mode = ResolutionMode .CLOUD_EXPLICIT if self .cloud_mode else ResolutionMode .EXPLICIT
165146 logger .debug (f"Using explicit project parameter: { project } " )
166147 return ResolvedProject (
167148 project = project ,
168- mode = ResolutionMode . EXPLICIT ,
149+ mode = mode ,
169150 reason = f"Explicit parameter: { project } " ,
170151 )
171152
172- # Priority 3: Default project mode
153+ # --- Priority 3: Default project mode ---
173154 if self .default_project_mode and self .default_project :
174155 logger .debug (f"Using default project from config: { self .default_project } " )
175156 return ResolvedProject (
@@ -178,12 +159,23 @@ def resolve(
178159 reason = f"Default project mode: { self .default_project } " ,
179160 )
180161
181- # No resolution possible
162+ # --- Fallback: mode-dependent behavior ---
163+ if self .cloud_mode :
164+ if allow_discovery :
165+ logger .debug ("Cloud mode: discovery mode allowed, no project required" )
166+ return ResolvedProject (
167+ project = None ,
168+ mode = ResolutionMode .CLOUD_DISCOVERY ,
169+ reason = "Discovery mode enabled in cloud" ,
170+ )
171+ raise ValueError ("No project specified. Project is required for cloud mode." )
172+
173+ # Local mode: no resolution possible
182174 logger .debug ("No project resolution possible" )
183175 return ResolvedProject (
184176 project = None ,
185177 mode = ResolutionMode .NONE ,
186- reason = "No project specified and default_project_mode is disabled " ,
178+ reason = "No project specified and no default project configured " ,
187179 )
188180
189181 def require_project (
0 commit comments