44This server handles OAuth flows, client registration, and token issuance.
55Can be replaced with enterprise authorization servers like Auth0, Entra ID, etc.
66
7+ NOTE: this is a simplified example for demonstration purposes.
8+ This is not a production-ready implementation.
9+
710Usage:
811 python -m mcp_simple_auth.auth_server --port=9000
912"""
1316import time
1417
1518import click
16- from pydantic import AnyHttpUrl
17- from pydantic_settings import SettingsConfigDict
19+ from pydantic import AnyHttpUrl , BaseModel
1820from starlette .applications import Starlette
1921from starlette .exceptions import HTTPException
2022from starlette .requests import Request
3032logger = logging .getLogger (__name__ )
3133
3234
33- class AuthServerSettings (GitHubOAuthSettings ):
35+ class AuthServerSettings (BaseModel ):
3436 """Settings for the Authorization Server."""
3537
36- model_config = SettingsConfigDict (env_prefix = "MCP_" )
37-
3838 # Server settings
3939 host : str = "localhost"
4040 port : int = 9000
4141 server_url : AnyHttpUrl = AnyHttpUrl ("http://localhost:9000" )
4242 github_callback_path : str = "http://localhost:9000/github/callback"
4343
44- def __init__ (self , ** data ):
45- """Initialize settings with values from environment variables."""
46- super ().__init__ (** data )
47-
4844
4945class GitHubProxyAuthProvider (GitHubOAuthProvider ):
5046 """
@@ -56,22 +52,22 @@ class GitHubProxyAuthProvider(GitHubOAuthProvider):
5652 3. Maps MCP tokens to GitHub tokens for API access
5753 """
5854
59- def __init__ (self , settings : AuthServerSettings ):
60- super ().__init__ (settings , settings . github_callback_path )
55+ def __init__ (self , github_settings : GitHubOAuthSettings , github_callback_path : str ):
56+ super ().__init__ (github_settings , github_callback_path )
6157
6258
63- def create_authorization_server (settings : AuthServerSettings ) -> Starlette :
59+ def create_authorization_server (server_settings : AuthServerSettings , github_settings : GitHubOAuthSettings ) -> Starlette :
6460 """Create the Authorization Server application."""
65- oauth_provider = GitHubProxyAuthProvider (settings )
61+ oauth_provider = GitHubProxyAuthProvider (github_settings , server_settings . github_callback_path )
6662
6763 auth_settings = AuthSettings (
68- issuer_url = settings .server_url ,
64+ issuer_url = server_settings .server_url ,
6965 client_registration_options = ClientRegistrationOptions (
7066 enabled = True ,
71- valid_scopes = [settings .mcp_scope ],
72- default_scopes = [settings .mcp_scope ],
67+ valid_scopes = [github_settings .mcp_scope ],
68+ default_scopes = [github_settings .mcp_scope ],
7369 ),
74- required_scopes = [settings .mcp_scope ],
70+ required_scopes = [github_settings .mcp_scope ],
7571 authorization_servers = None ,
7672 )
7773
@@ -93,20 +89,8 @@ async def github_callback_handler(request: Request) -> Response:
9389 if not code or not state :
9490 raise HTTPException (400 , "Missing code or state parameter" )
9591
96- try :
97- redirect_uri = await oauth_provider .handle_github_callback (code , state )
98- return RedirectResponse (url = redirect_uri , status_code = 302 )
99- except HTTPException :
100- raise
101- except Exception as e :
102- logger .error ("Unexpected error" , exc_info = e )
103- return JSONResponse (
104- status_code = 500 ,
105- content = {
106- "error" : "server_error" ,
107- "error_description" : "Unexpected error" ,
108- },
109- )
92+ redirect_uri = await oauth_provider .handle_github_callback (code , state )
93+ return RedirectResponse (url = redirect_uri , status_code = 302 )
11094
11195 routes .append (Route ("/github/callback" , endpoint = github_callback_handler , methods = ["GET" ]))
11296
@@ -118,32 +102,27 @@ async def introspect_handler(request: Request) -> Response:
118102 Resource Servers call this endpoint to validate tokens without
119103 needing direct access to token storage.
120104 """
121- try :
122- form = await request .form ()
123- token = form .get ("token" )
124- if not token or not isinstance (token , str ):
125- return JSONResponse ({"active" : False }, status_code = 400 )
126-
127- # Look up token in provider
128- access_token = await oauth_provider .load_access_token (token )
129- if not access_token :
130- return JSONResponse ({"active" : False })
131-
132- # Return token info for Resource Server
133- return JSONResponse (
134- {
135- "active" : True ,
136- "client_id" : access_token .client_id ,
137- "scope" : " " .join (access_token .scopes ),
138- "exp" : access_token .expires_at ,
139- "iat" : int (time .time ()),
140- "token_type" : "Bearer" ,
141- }
142- )
143-
144- except Exception as e :
145- logger .exception ("Token introspection error" )
146- return JSONResponse ({"active" : False , "error" : str (e )}, status_code = 500 )
105+ form = await request .form ()
106+ token = form .get ("token" )
107+ if not token or not isinstance (token , str ):
108+ return JSONResponse ({"active" : False }, status_code = 400 )
109+
110+ # Look up token in provider
111+ access_token = await oauth_provider .load_access_token (token )
112+ if not access_token :
113+ return JSONResponse ({"active" : False })
114+
115+ # Return token info for Resource Server
116+ return JSONResponse (
117+ {
118+ "active" : True ,
119+ "client_id" : access_token .client_id ,
120+ "scope" : " " .join (access_token .scopes ),
121+ "exp" : access_token .expires_at ,
122+ "iat" : int (time .time ()),
123+ "token_type" : "Bearer" ,
124+ }
125+ )
147126
148127 routes .append (
149128 Route (
@@ -161,28 +140,16 @@ async def github_user_handler(request: Request) -> Response:
161140 Resource Servers call this with MCP tokens to get GitHub user data
162141 without exposing GitHub tokens to clients.
163142 """
164- try :
165- # Extract Bearer token
166- auth_header = request .headers .get ("authorization" , "" )
167- if not auth_header .startswith ("Bearer " ):
168- return JSONResponse ({"error" : "unauthorized" }, status_code = 401 )
169-
170- mcp_token = auth_header [7 :]
171-
172- # Get GitHub user info using the provider method
173- try :
174- user_info = await oauth_provider .get_github_user_info (mcp_token )
175- return JSONResponse (user_info )
176- except ValueError as e :
177- if "No GitHub token found" in str (e ):
178- return JSONResponse ({"error" : "no_github_token" }, status_code = 404 )
179- elif "GitHub API error" in str (e ):
180- return JSONResponse ({"error" : "github_api_error" }, status_code = 502 )
181- raise
182-
183- except Exception as e :
184- logger .exception ("GitHub user info error" )
185- return JSONResponse ({"error" : str (e )}, status_code = 500 )
143+ # Extract Bearer token
144+ auth_header = request .headers .get ("authorization" , "" )
145+ if not auth_header .startswith ("Bearer " ):
146+ return JSONResponse ({"error" : "unauthorized" }, status_code = 401 )
147+
148+ mcp_token = auth_header [7 :]
149+
150+ # Get GitHub user info using the provider method
151+ user_info = await oauth_provider .get_github_user_info (mcp_token )
152+ return JSONResponse (user_info )
186153
187154 routes .append (
188155 Route (
@@ -192,36 +159,36 @@ async def github_user_handler(request: Request) -> Response:
192159 )
193160 )
194161
195- return Starlette (debug = True , routes = routes )
162+ return Starlette (routes = routes )
196163
197164
198- async def run_server (settings : AuthServerSettings ):
165+ async def run_server (server_settings : AuthServerSettings , github_settings : GitHubOAuthSettings ):
199166 """Run the Authorization Server."""
200- auth_server = create_authorization_server (settings )
167+ auth_server = create_authorization_server (server_settings , github_settings )
201168
202169 config = Config (
203170 auth_server ,
204- host = settings .host ,
205- port = settings .port ,
171+ host = server_settings .host ,
172+ port = server_settings .port ,
206173 log_level = "info" ,
207174 )
208175 server = Server (config )
209176
210177 logger .info ("=" * 80 )
211178 logger .info ("MCP AUTHORIZATION SERVER" )
212179 logger .info ("=" * 80 )
213- logger .info (f"Server URL: { settings .server_url } " )
180+ logger .info (f"Server URL: { server_settings .server_url } " )
214181 logger .info ("Endpoints:" )
215- logger .info (f" - OAuth Metadata: { settings .server_url } /.well-known/oauth-authorization-server" )
216- logger .info (f" - Client Registration: { settings .server_url } /register" )
217- logger .info (f" - Authorization: { settings .server_url } /authorize" )
218- logger .info (f" - Token Exchange: { settings .server_url } /token" )
219- logger .info (f" - Token Introspection: { settings .server_url } /introspect" )
220- logger .info (f" - GitHub Callback: { settings .server_url } /github/callback" )
221- logger .info (f" - GitHub User Proxy: { settings .server_url } /github/user" )
182+ logger .info (f" - OAuth Metadata: { server_settings .server_url } /.well-known/oauth-authorization-server" )
183+ logger .info (f" - Client Registration: { server_settings .server_url } /register" )
184+ logger .info (f" - Authorization: { server_settings .server_url } /authorize" )
185+ logger .info (f" - Token Exchange: { server_settings .server_url } /token" )
186+ logger .info (f" - Token Introspection: { server_settings .server_url } /introspect" )
187+ logger .info (f" - GitHub Callback: { server_settings .server_url } /github/callback" )
188+ logger .info (f" - GitHub User Proxy: { server_settings .server_url } /github/user" )
222189 logger .info ("" )
223190 logger .info ("Resource Servers should use /introspect to validate tokens" )
224- logger .info ("Configure GitHub App callback URL: " + settings .github_callback_path )
191+ logger .info ("Configure GitHub App callback URL: " + server_settings .github_callback_path )
225192 logger .info ("=" * 80 )
226193
227194 await server .serve ()
@@ -242,16 +209,23 @@ def main(port: int, host: str) -> int:
242209 """
243210 logging .basicConfig (level = logging .INFO )
244211
245- try :
246- settings = AuthServerSettings (host = host , port = port )
247- except ValueError as e :
248- logger .error ("Failed to load settings. Make sure environment variables are set:" )
249- logger .error (" MCP_GITHUB_CLIENT_ID=<your-client-id>" )
250- logger .error (" MCP_GITHUB_CLIENT_SECRET=<your-client-secret>" )
251- logger .error (f"Error: { e } " )
252- return 1
212+ # Load GitHub settings from environment variables
213+ github_settings = GitHubOAuthSettings ()
214+
215+ # Validate required fields
216+ if not github_settings .github_client_id or not github_settings .github_client_secret :
217+ raise ValueError ("GitHub credentials not provided" )
218+
219+ # Create server settings
220+ server_url = f"http://{ host } :{ port } "
221+ server_settings = AuthServerSettings (
222+ host = host ,
223+ port = port ,
224+ server_url = AnyHttpUrl (server_url ),
225+ github_callback_path = f"{ server_url } /github/callback" ,
226+ )
253227
254- asyncio .run (run_server (settings ))
228+ asyncio .run (run_server (server_settings , github_settings ))
255229 return 0
256230
257231
0 commit comments