Files
ysj7705andClaude Opus 5.5 916d149ab5 Gitea MCP OAuth 브리지 (DCR, Gitea 로그인)
Gitea에 동적 클라이언트 등록이 없어 MCP 클라이언트가 URL만으로 OAuth 연결을
못 하는 문제를 이 브리지가 인가 서버 역할을 대신해 해결한다. G3X의
plane-mcp-oauth-bridge(upstream_oauth 모드)에서 가져와 Gitea 전용으로 정리했다:
Plane PAT 연결 페이지·워크스페이스 헤더·bridge_mode 분기를 없앴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:20:02 +09:00

67 lines
2.0 KiB
Python

from __future__ import annotations
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
def parse_allowed_mcp_clients(raw: str) -> dict[str, set[str]]:
out: dict[str, set[str]] = {}
for part in raw.split(","):
part = part.strip()
if not part:
continue
client_id, _, redirect_uri = part.partition("|")
client_id = client_id.strip()
redirect_uri = redirect_uri.strip()
if not client_id or not redirect_uri:
continue
out.setdefault(client_id, set()).add(redirect_uri)
return out
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
public_base_url: str = "http://127.0.0.1:48080"
listen_host: str = "0.0.0.0"
listen_port: int = 48080
upstream_mcp_url: str = "http://gitea-mcp:8080/mcp"
oidc_issuer: str = ""
oidc_client_id: str = ""
oidc_client_secret: str = ""
oidc_scopes: str = (
"openid profile email write:repository write:issue write:user"
" write:organization write:notification write:package write:misc"
)
allowed_mcp_clients: str = ""
token_encryption_key: str = ""
data_dir: str = "./data"
dcr_enabled: bool = True
dcr_max_clients: int = 2000
# Forward bearer tokens the bridge did not issue (e.g. Gitea PATs) unchanged.
passthrough_unknown_tokens: bool = True
# Optional Host header for the upstream request (gitea-mcp rejects other hosts).
upstream_host_header: str = ""
@property
def issuer(self) -> str:
return self.public_base_url.rstrip("/")
@property
def mcp_resource_url(self) -> str:
return f"{self.issuer}/mcp"
@property
def allowed_clients(self) -> dict[str, set[str]]:
return parse_allowed_mcp_clients(self.allowed_mcp_clients)
@property
def session_secret(self) -> bytes:
return self.token_encryption_key.encode("utf-8")
@lru_cache
def get_settings() -> Settings:
return Settings()