diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index a2d8521..a7480ad 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -7375,9 +7375,19 @@ def chmod_best_effort(path, mode): def resolve_project_id(args): + # Project-scoped tools resolve their target before issuing an HTTP + # request. Reload auth here as well as in request_json so a long-running + # MCP worker observes `nap project attach` even when it started before a + # project was selected. + if AUTH_PATH.exists() or not DEFAULT_PROJECT_ID: + refresh_public_auth_state() project_id = DEFAULT_PROJECT_ID if not project_id: - raise RuntimeError("No project configured. Run nap project create or call nap_project_create first.") + raise SafeToolError( + "project_not_configured", + "No project is selected in this directory. Run `nap project attach` " + "for an existing project or `nap project create` for a new one.", + ) return project_id diff --git a/tests/test_live_auth_state_reload.py b/tests/test_live_auth_state_reload.py index b8b55fe..b0672bd 100644 --- a/tests/test_live_auth_state_reload.py +++ b/tests/test_live_auth_state_reload.py @@ -46,12 +46,29 @@ def run(): requests = [] mod.load_public_auth_file = lambda: dict(current) mod.gateway_is_unlocked = lambda: False + mod.AUTH_PATH = pathlib.Path(__file__) + + # The MCP worker may have started before `nap project attach`, leaving + # its project selection empty until another process replaces auth.json. + mod.DEFAULT_PROJECT_ID = None + assert mod.resolve_project_id({}) == "project-1" + + current = {} + mod.DEFAULT_PROJECT_ID = "stale-project" + try: + mod.resolve_project_id({}) + except mod.SafeToolError as exc: + assert exc.code == "project_not_configured" + assert "nap project attach" in exc.safe_message + else: + raise AssertionError("missing project selection must return an actionable safe error") def open_success(request, timeout=None): requests.append(request) return Response() mod.api_urlopen = open_success + current = auth("fresh-access", "fresh-refresh") mod.TOKEN = "stale-access" mod.REFRESH_TOKEN = "stale-refresh" result = mod.request_json("GET", "/v1/projects/project-1")