55 python -m mcp_simple_auth.server --port=8001
66"""
77
8- import asyncio
98import logging
109from typing import Any , Literal
1110
1211import click
1312import httpx
1413from pydantic import AnyHttpUrl
1514from pydantic_settings import BaseSettings , SettingsConfigDict
16- from starlette .applications import Starlette
17- from starlette .routing import Mount , Route
18- from uvicorn import Config , Server
1915
20- from mcp .server .auth .handlers .metadata import MetadataHandler
2116from mcp .server .auth .middleware .auth_context import get_access_token
22- from mcp .server .auth .routes import cors_middleware
2317from mcp .server .auth .settings import AuthSettings
2418from mcp .server .fastmcp .server import FastMCP
25- from mcp .shared .auth import OAuthMetadata
2619
2720from .token_verifier import IntrospectionTokenVerifier
2821
@@ -40,10 +33,9 @@ class ResourceServerSettings(BaseSettings):
4033 host : str = "localhost"
4134 port : int = 8001
4235 server_url : AnyHttpUrl = AnyHttpUrl ("http://localhost:8001" )
43- transport : Literal ["sse" , "streamable-http" ] = "streamable-http"
4436
4537 # Authorization Server settings
46- auth_server_url : AnyHttpUrl = AnyHttpUrl ("http://localhost:8001 " )
38+ auth_server_url : AnyHttpUrl = AnyHttpUrl ("http://localhost:9000 " )
4739 auth_server_introspection_endpoint : str = f"{ API_ENDPOINT } /oauth2/@me"
4840 auth_server_discord_user_endpoint : str = f"{ API_ENDPOINT } /users/@me"
4941
@@ -55,7 +47,7 @@ def __init__(self, **data):
5547 super ().__init__ (** data )
5648
5749
58- def create_resource_server (settings : ResourceServerSettings ) -> Starlette :
50+ def create_resource_server (settings : ResourceServerSettings ) -> FastMCP :
5951 """
6052 Create MCP Resource Server.
6153 """
@@ -67,8 +59,10 @@ def create_resource_server(settings: ResourceServerSettings) -> Starlette:
6759 )
6860
6961 # Create FastMCP server as a Resource Server
70- resource_server = FastMCP (
62+ app = FastMCP (
7163 name = "MCP Resource Server" ,
64+ host = settings .host ,
65+ port = settings .port ,
7266 debug = True ,
7367 token_verifier = token_verifier ,
7468 auth = AuthSettings (
@@ -99,14 +93,14 @@ async def get_discord_user_data() -> dict[str, Any]:
9993
10094 return response .json ()
10195
102- @resource_server .tool ()
96+ @app .tool ()
10397 async def get_user_profile () -> dict [str , Any ]:
10498 """
10599 Get the authenticated user's Discord profile information.
106100 """
107101 return await get_discord_user_data ()
108102
109- @resource_server .tool ()
103+ @app .tool ()
110104 async def get_user_info () -> dict [str , Any ]:
111105 """
112106 Get information about the currently authenticated user.
@@ -127,53 +121,12 @@ async def get_user_info() -> dict[str, Any]:
127121 "authorization_server" : str (settings .auth_server_url ),
128122 }
129123
130- # Create Starlette app to mount the MCP server and host RFC8414
131- # metadata to jump to Discord's authorization server
132- app = Starlette (
133- debug = True ,
134- routes = [
135- Route (
136- "/.well-known/oauth-authorization-server" ,
137- endpoint = cors_middleware (
138- MetadataHandler (metadata = OAuthMetadata (
139- issuer = settings .server_url ,
140- authorization_endpoint = AnyHttpUrl (f"{ API_ENDPOINT } /oauth2/authorize" ),
141- token_endpoint = AnyHttpUrl (f"{ API_ENDPOINT } /oauth2/token" ),
142- token_endpoint_auth_methods_supported = ["client_secret_basic" ],
143- response_types_supported = ["code" ],
144- grant_types_supported = ["client_credentials" ],
145- scopes_supported = ["identify" ]
146- )).handle ,
147- ["GET" , "OPTIONS" ],
148- ),
149- methods = ["GET" , "OPTIONS" ],
150- ),
151- Mount (
152- "/" ,
153- app = resource_server .streamable_http_app () if settings .transport == "streamable-http" else resource_server .sse_app ()
154- ),
155- ],
156- lifespan = lambda app : resource_server .session_manager .run (),
157- )
158-
159124 return app
160125
161126
162- async def run_server (settings : ResourceServerSettings ):
163- mcp_server = create_resource_server (settings )
164- config = Config (
165- mcp_server ,
166- host = settings .host ,
167- port = settings .port ,
168- log_level = "info" ,
169- )
170- server = Server (config )
171- await server .serve ()
172-
173-
174127@click .command ()
175128@click .option ("--port" , default = 8001 , help = "Port to listen on" )
176- @click .option ("--auth-server" , default = "http://localhost:8001 " , help = "Authorization Server URL" )
129+ @click .option ("--auth-server" , default = "http://localhost:9000 " , help = "Authorization Server URL" )
177130@click .option (
178131 "--transport" ,
179132 default = "streamable-http" ,
@@ -200,14 +153,15 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
200153 auth_server_url = auth_server_url ,
201154 auth_server_introspection_endpoint = f"{ API_ENDPOINT } /oauth2/@me" ,
202155 auth_server_discord_user_endpoint = f"{ API_ENDPOINT } /users/@me" ,
203- transport = transport ,
204156 )
205157 except ValueError as e :
206158 logger .error (f"Configuration error: { e } " )
207159 logger .error ("Make sure to provide a valid Authorization Server URL" )
208160 return 1
209161
210162 try :
163+ mcp_server = create_resource_server (settings )
164+
211165 logger .info ("=" * 80 )
212166 logger .info ("📦 MCP RESOURCE SERVER" )
213167 logger .info ("=" * 80 )
@@ -228,7 +182,7 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
228182 logger .info ("=" * 80 )
229183
230184 # Run the server - this should block and keep running
231- asyncio .run (run_server ( settings ) )
185+ mcp_server .run (transport = transport )
232186 logger .info ("Server stopped" )
233187 return 0
234188 except Exception as e :
0 commit comments