From 69bdf7b30a412d63c71a73ba9d114314f256f65b Mon Sep 17 00:00:00 2001 From: Chaim Date: Mon, 4 May 2026 06:33:01 +0000 Subject: [PATCH] fix(settings): harden PATCH/redeploy per code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add infisicalsdk dependency - Narrow update→create fallback to NotFound errors only (no silent swallow) - Truncate Coolify error response text to 200 chars - Add 60s cooldown to redeploy endpoint - Move httpx to top-level import --- mcp-server/pyproject.toml | 1 + web/app.py | 47 +++++++++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/mcp-server/pyproject.toml b/mcp-server/pyproject.toml index 2829d57..092713a 100644 --- a/mcp-server/pyproject.toml +++ b/mcp-server/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.30.0", "httpx>=0.27.0", + "infisicalsdk>=1.0.0", ] [build-system] diff --git a/web/app.py b/web/app.py index 7418dc5..2ea8000 100644 --- a/web/app.py +++ b/web/app.py @@ -26,6 +26,7 @@ from typing import Any from pydantic import BaseModel import asyncpg +import httpx from legal_mcp import config from legal_mcp.services import chunker, db, embeddings, extractor, git_sync, processor, proofreader, research_md @@ -2552,6 +2553,12 @@ async def api_post_agent_comment(case_number: str, req: AgentCommentRequest): # ── Settings: MCP Server Configuration ──────────────────────────── +# Module-level guard: minimum interval between redeploys (60 seconds). +# Prevents accidental double-clicks or automated retry loops from queueing +# multiple redundant Coolify builds. +_LAST_REDEPLOY_AT: float = 0.0 +_REDEPLOY_MIN_INTERVAL_SEC: float = 60.0 + def _infisical_client(): """Build Infisical SDK client, or return None if not configured.""" @@ -2699,10 +2706,12 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest): "false" if coerced is False else str(coerced) ) try: - # SDK pattern: try update, fall back to create if missing. - # NOTE: exact method may vary by infisical-python version. The - # canonical method is `update_secret_by_name`; if your version - # uses `secrets.update`, replace accordingly. + # SDK pattern: try update, fall back to create if the secret doesn't exist. + # The Infisical SDK's specific NotFound exception class isn't stable across + # versions, so we inspect the error string. Network/auth errors (which DON'T + # contain 'not found' / 404 / 'does not exist') are re-raised immediately + # so they reach the outer handler and surface as 502 with the real error, + # rather than being silently retried as a create-call. try: client.update_secret_by_name( project_id=project_id, @@ -2711,7 +2720,22 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest): secret_name=key, secret_value=str_value, ) - except Exception: + except Exception as update_err: + err_text = str(update_err).lower() + looks_like_missing = ( + "not found" in err_text + or "does not exist" in err_text + or "404" in err_text + ) + if not looks_like_missing: + logger.warning( + "infisical_update_failed key=%s err=%s — not retrying as create", + key, update_err, + ) + raise + logger.info( + "infisical_secret_missing key=%s — falling back to create", key, + ) client.create_secret_by_name( project_id=project_id, environment_slug=env, @@ -2740,7 +2764,14 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest): @app.post("/api/settings/mcp/env/redeploy") async def api_mcp_env_redeploy(): """Trigger Coolify redeploy of the legal-ai app.""" - import httpx + global _LAST_REDEPLOY_AT + now = time.time() + elapsed = now - _LAST_REDEPLOY_AT + if elapsed < _REDEPLOY_MIN_INTERVAL_SEC: + wait = int(_REDEPLOY_MIN_INTERVAL_SEC - elapsed) + raise HTTPException( + 429, f"Redeploy בהמתנה: נסה שוב בעוד {wait} שניות." + ) coolify_url = os.environ.get("COOLIFY_URL", "http://158.178.131.193:8000") coolify_token = os.environ.get("COOLIFY_API_TOKEN", "") app_uuid = os.environ.get("COOLIFY_APP_UUID", "gyjo0mtw2c42ej3xxvbz8zio") @@ -2757,8 +2788,9 @@ async def api_mcp_env_redeploy(): except Exception as e: raise HTTPException(502, f"Coolify unreachable: {e}") if resp.status_code >= 400: + body_preview = (resp.text or "")[:200] raise HTTPException( - 502, f"Coolify deploy failed: {resp.status_code} {resp.text}" + 502, f"Coolify deploy failed: {resp.status_code} — {body_preview}" ) data = resp.json() if resp.content else {} deployment_uuid = ( @@ -2766,6 +2798,7 @@ async def api_mcp_env_redeploy(): or (data.get("deployments") or [{}])[0].get("deployment_uuid") ) logger.info("mcp_env_redeploy triggered uuid=%s", deployment_uuid) + _LAST_REDEPLOY_AT = now return { "ok": True, "deployment_uuid": deployment_uuid,