create-caspian-app 0.4.0-rc.0 → 0.4.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.py +75 -1
- package/package.json +1 -1
package/dist/main.py
CHANGED
|
@@ -19,6 +19,8 @@ from fastapi import (
|
|
|
19
19
|
)
|
|
20
20
|
from fastapi.responses import RedirectResponse, FileResponse, HTMLResponse
|
|
21
21
|
from starlette.datastructures import MutableHeaders
|
|
22
|
+
from starlette.middleware import Middleware
|
|
23
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
22
24
|
from starlette.middleware.sessions import SessionMiddleware
|
|
23
25
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
24
26
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
@@ -57,6 +59,78 @@ from casp.runtime_security import (
|
|
|
57
59
|
load_dotenv()
|
|
58
60
|
cfg = get_config()
|
|
59
61
|
|
|
62
|
+
# ====
|
|
63
|
+
# CORS configuration (shared .env convention, mirrors casp.rpc origin checks)
|
|
64
|
+
# ====
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _csv_env(name: str) -> list[str]:
|
|
68
|
+
return [item.strip() for item in os.getenv(name, "").split(",") if item.strip()]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _bool_env(name: str, default: bool = False) -> bool:
|
|
72
|
+
raw = os.getenv(name)
|
|
73
|
+
if raw is None:
|
|
74
|
+
return default
|
|
75
|
+
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _configured_cors_origins() -> list[str]:
|
|
79
|
+
"""Browser origins allowed to call the app, per the .env convention."""
|
|
80
|
+
origins: list[str] = []
|
|
81
|
+
for raw in (*_csv_env("CORS_ALLOWED_ORIGINS"), os.getenv("APP_BASE_URL", "")):
|
|
82
|
+
value = (raw or "").strip().rstrip("/")
|
|
83
|
+
if value and value not in origins:
|
|
84
|
+
origins.append(value)
|
|
85
|
+
return origins
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _build_mcp_cors_middleware() -> "Middleware":
|
|
89
|
+
"""Build the MCP CORS layer from .env, adding MCP-required headers.
|
|
90
|
+
|
|
91
|
+
Browser MCP clients (e.g. MCP Inspector "Direct") send an OPTIONS preflight
|
|
92
|
+
and rely on the mcp-session-id / mcp-protocol-version headers, which are not
|
|
93
|
+
in the generic CORS_ALLOWED_HEADERS list, so they are merged in here.
|
|
94
|
+
"""
|
|
95
|
+
origins = _configured_cors_origins()
|
|
96
|
+
allow_credentials = _bool_env("CORS_ALLOW_CREDENTIALS")
|
|
97
|
+
|
|
98
|
+
if not origins:
|
|
99
|
+
# The CORS spec forbids "*" together with credentials, so when no
|
|
100
|
+
# explicit origin is configured fall back to open + no credentials.
|
|
101
|
+
origins = ["*"]
|
|
102
|
+
allow_credentials = False
|
|
103
|
+
|
|
104
|
+
methods = _csv_env("CORS_ALLOWED_METHODS") or [
|
|
105
|
+
"GET", "POST", "DELETE", "OPTIONS"]
|
|
106
|
+
|
|
107
|
+
headers = _csv_env("CORS_ALLOWED_HEADERS")
|
|
108
|
+
for required in ("Content-Type", "Accept", "Authorization",
|
|
109
|
+
"mcp-session-id", "mcp-protocol-version"):
|
|
110
|
+
if required.lower() not in {h.lower() for h in headers}:
|
|
111
|
+
headers.append(required)
|
|
112
|
+
|
|
113
|
+
expose = _csv_env("CORS_EXPOSE_HEADERS")
|
|
114
|
+
for required in ("mcp-session-id", "mcp-protocol-version"):
|
|
115
|
+
if required.lower() not in {h.lower() for h in expose}:
|
|
116
|
+
expose.append(required)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
max_age = int(os.getenv("CORS_MAX_AGE", "600"))
|
|
120
|
+
except ValueError:
|
|
121
|
+
max_age = 600
|
|
122
|
+
|
|
123
|
+
return Middleware(
|
|
124
|
+
CORSMiddleware,
|
|
125
|
+
allow_origins=origins,
|
|
126
|
+
allow_credentials=allow_credentials,
|
|
127
|
+
allow_methods=methods,
|
|
128
|
+
allow_headers=headers,
|
|
129
|
+
expose_headers=expose,
|
|
130
|
+
max_age=max_age,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
60
134
|
# ====
|
|
61
135
|
# MCP SERVER (mounted into this app so one deploy serves web + MCP)
|
|
62
136
|
# ====
|
|
@@ -66,7 +140,7 @@ if cfg.mcp:
|
|
|
66
140
|
# caspian.config.json, so suppress the static "module not found" check.
|
|
67
141
|
from src.lib.mcp.mcp_server import mcp # type: ignore[import-not-found]
|
|
68
142
|
# Inner path "/" so the mount prefix below is the full endpoint path.
|
|
69
|
-
mcp_app = mcp.http_app(path="/")
|
|
143
|
+
mcp_app = mcp.http_app(path="/", middleware=[_build_mcp_cors_middleware()])
|
|
70
144
|
|
|
71
145
|
# ====
|
|
72
146
|
# AUTH CONFIGURATION (App behavior - customize here)
|