From 7384a6c05aff1a8918ba03a06415f72d73972bc2 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Sun, 5 Jul 2026 02:54:15 +0530 Subject: [PATCH] Strip trailing slashes from PUBLIC_URL, CORS_ORIGINS, FRONTEND_BASE_URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A trailing slash on CORS_ORIGINS never matches a browser's Origin header (which never includes a path), silently breaking CORS with no error message beyond the browser's generic "No Access-Control-Allow-Origin header" — exactly what happened in production after switching domains. A trailing slash on PUBLIC_URL/FRONTEND_BASE_URL similarly produced double-slash URLs in API responses and reset-password links. All three are now normalized at config-load time so a .env typo can't cause this again. Co-Authored-By: Claude Sonnet 5 --- src/mt_odoo_mcp/config.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mt_odoo_mcp/config.py b/src/mt_odoo_mcp/config.py index 9483e3d..b1cd059 100644 --- a/src/mt_odoo_mcp/config.py +++ b/src/mt_odoo_mcp/config.py @@ -1,4 +1,5 @@ from __future__ import annotations +from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -45,9 +46,20 @@ class Settings(BaseSettings): admin_password: str = "change-me" admin_session_ttl_hours: int = 12 + @field_validator("public_url", "frontend_base_url", mode="after") + @classmethod + def _strip_trailing_slash(cls, v: str) -> str: + # Every call site concatenates a leading-slash path directly onto + # these (e.g. f"{public_url}/mcp/sse") — a trailing slash in .env + # would silently produce a double-slash URL. + return v.rstrip("/") + @property def cors_origins_list(self) -> list[str]: - return [o.strip() for o in self.cors_origins.split(",") if o.strip()] + # A trailing slash here would never match a browser's Origin header + # (which never includes a path/slash), silently breaking CORS — + # strip it defensively so a .env typo can't cause that again. + return [o.strip().rstrip("/") for o in self.cors_origins.split(",") if o.strip()] settings = Settings()