11"""Configuration management for CodeAlive MCP server."""
22
33import os
4+ import ipaddress
45from dataclasses import dataclass
56from typing import Optional
67from urllib .parse import urlsplit , urlunsplit
910REQUEST_TIMEOUT_SECONDS = 300.0
1011
1112
13+ def _is_loopback_host (host : str | None ) -> bool :
14+ if host is None :
15+ return False
16+ if host .lower () == "localhost" :
17+ return True
18+ try :
19+ return ipaddress .ip_address (host ).is_loopback
20+ except ValueError :
21+ return False
22+
23+
24+ def validate_oauth_urls (issuer_value : str , resource_value : str ) -> None :
25+ issuer = urlsplit (issuer_value )
26+ if (
27+ issuer .scheme != "https"
28+ or not issuer .netloc
29+ or issuer .username is not None
30+ or issuer .password is not None
31+ or issuer .path not in {"" , "/" }
32+ or issuer .query
33+ or issuer .fragment
34+ or not issuer_value .endswith ("/" )
35+ or (issuer .hostname or "" ).endswith ("." )
36+ ):
37+ raise ValueError ("CODEALIVE_OAUTH_ISSUER must be a canonical HTTPS origin" )
38+
39+ resource = urlsplit (resource_value )
40+ secure = resource .scheme == "https" or (
41+ resource .scheme == "http" and _is_loopback_host (resource .hostname )
42+ )
43+ if (
44+ not secure
45+ or not resource .netloc
46+ or resource .username is not None
47+ or resource .password is not None
48+ or resource .path in {"" , "/" }
49+ or resource .query
50+ or resource .fragment
51+ or resource_value .endswith ("/" )
52+ or (resource .hostname or "" ).endswith ("." )
53+ ):
54+ raise ValueError ("CODEALIVE_MCP_RESOURCE must be a canonical HTTPS URL with a path" )
55+
56+
57+ def _same_resource_identifier (left_value : str , right_value : str ) -> bool :
58+ left = urlsplit (left_value )
59+ right = urlsplit (right_value )
60+ if left .scheme .lower () != right .scheme .lower ():
61+ return False
62+ if left .netloc or right .netloc :
63+ left_port = left .port or (443 if left .scheme .lower () == "https" else 80 if left .scheme .lower () == "http" else None )
64+ right_port = right .port or (443 if right .scheme .lower () == "https" else 80 if right .scheme .lower () == "http" else None )
65+ return (
66+ left .hostname == right .hostname
67+ and left_port == right_port
68+ and left .username == right .username
69+ and left .password == right .password
70+ and left .path == right .path
71+ and left .query == right .query
72+ and left .fragment == right .fragment
73+ )
74+ return left .path == right .path and left .query == right .query and left .fragment == right .fragment
75+
76+
77+ def _is_absolute_resource_identifier (value : str ) -> bool :
78+ if not value or value != value .strip ():
79+ return False
80+ parsed = urlsplit (value )
81+ return bool (parsed .scheme ) and bool (parsed .netloc or parsed .path )
82+
83+
1284def normalize_base_url (base_url : Optional [str ]) -> str :
1385 """Normalize a CodeAlive base URL to the deployment origin.
1486
@@ -41,6 +113,26 @@ class Config:
41113 transport_mode : str = "stdio"
42114 verify_ssl : bool = True
43115 debug_mode : bool = False
116+ oauth_enabled : bool = False
117+ oauth_issuer : str = "https://auth.codealive.ai/"
118+ mcp_resource : str = "https://mcp.codealive.ai/api"
119+ tool_api_resource : str = "urn:codealive:tool-api"
120+ oauth_internal_client_id : str = "codealive-mcp"
121+ oauth_internal_client_secret : Optional [str ] = None
122+
123+ def __post_init__ (self ) -> None :
124+ if self .oauth_enabled :
125+ validate_oauth_urls (self .oauth_issuer , self .mcp_resource )
126+ if not _is_absolute_resource_identifier (self .tool_api_resource ):
127+ raise ValueError (
128+ "CODEALIVE_TOOL_API_RESOURCE must be an absolute resource identifier"
129+ )
130+ if _same_resource_identifier (self .mcp_resource , self .tool_api_resource ):
131+ raise ValueError (
132+ "CODEALIVE_MCP_RESOURCE and CODEALIVE_TOOL_API_RESOURCE must be distinct"
133+ )
134+ if not self .oauth_internal_client_id or not self .oauth_internal_client_id .strip ():
135+ raise ValueError ("CODEALIVE_OAUTH_INTERNAL_CLIENT_ID must not be empty" )
44136
45137 @classmethod
46138 def from_environment (cls ) -> "Config" :
@@ -51,4 +143,10 @@ def from_environment(cls) -> "Config":
51143 transport_mode = os .environ .get ("TRANSPORT_MODE" , "stdio" ),
52144 verify_ssl = not os .environ .get ("CODEALIVE_IGNORE_SSL" , "" ).lower () in ["true" , "1" , "yes" ],
53145 debug_mode = os .environ .get ("DEBUG_MODE" , "" ).lower () in ["true" , "1" , "yes" ],
146+ oauth_enabled = os .environ .get ("CODEALIVE_MCP_OAUTH_ENABLED" , "false" ).lower () in ["true" , "1" , "yes" ],
147+ oauth_issuer = os .environ .get ("CODEALIVE_OAUTH_ISSUER" , "https://auth.codealive.ai/" ),
148+ mcp_resource = os .environ .get ("CODEALIVE_MCP_RESOURCE" , "https://mcp.codealive.ai/api" ),
149+ tool_api_resource = os .environ .get ("CODEALIVE_TOOL_API_RESOURCE" , "urn:codealive:tool-api" ),
150+ oauth_internal_client_id = os .environ .get ("CODEALIVE_OAUTH_INTERNAL_CLIENT_ID" , "codealive-mcp" ),
151+ oauth_internal_client_secret = os .environ .get ("CODEALIVE_OAUTH_INTERNAL_CLIENT_SECRET" ),
54152 )
0 commit comments