@@ -51,248 +51,20 @@ def __init__(self, **data):
5151 super ().__init__ (** data )
5252
5353
54- # <<<<<<< main
55- class SimpleGitHubOAuthProvider (OAuthAuthorizationServerProvider ):
56- """Simple GitHub OAuth provider with essential functionality."""
57-
58- def __init__ (self , settings : ServerSettings ):
59- self .settings = settings
60- self .clients : dict [str , OAuthClientInformationFull ] = {}
61- self .auth_codes : dict [str , AuthorizationCode ] = {}
62- self .tokens : dict [str , AccessToken ] = {}
63- self .state_mapping : dict [str , dict [str , str ]] = {}
64- # Store GitHub tokens with MCP tokens using the format:
65- # {"mcp_token": "github_token"}
66- self .token_mapping : dict [str , str ] = {}
67-
68- async def get_client (self , client_id : str ) -> OAuthClientInformationFull | None :
69- """Get OAuth client information."""
70- return self .clients .get (client_id )
71-
72- async def register_client (self , client_info : OAuthClientInformationFull ):
73- """Register a new OAuth client."""
74- self .clients [client_info .client_id ] = client_info
75-
76- async def authorize (self , client : OAuthClientInformationFull , params : AuthorizationParams ) -> str :
77- """Generate an authorization URL for GitHub OAuth flow."""
78- state = params .state or secrets .token_hex (16 )
79-
80- # Store the state mapping
81- self .state_mapping [state ] = {
82- "redirect_uri" : str (params .redirect_uri ),
83- "code_challenge" : params .code_challenge ,
84- "redirect_uri_provided_explicitly" : str (params .redirect_uri_provided_explicitly ),
85- "client_id" : client .client_id ,
86- }
87-
88- # Build GitHub authorization URL
89- auth_url = (
90- f"{ self .settings .github_auth_url } "
91- f"?client_id={ self .settings .github_client_id } "
92- f"&redirect_uri={ self .settings .github_callback_path } "
93- f"&scope={ self .settings .github_scope } "
94- f"&state={ state } "
95- )
96-
97- return auth_url
98-
99- async def handle_github_callback (self , code : str , state : str ) -> str :
100- """Handle GitHub OAuth callback."""
101- state_data = self .state_mapping .get (state )
102- if not state_data :
103- raise HTTPException (400 , "Invalid state parameter" )
104-
105- redirect_uri = state_data ["redirect_uri" ]
106- code_challenge = state_data ["code_challenge" ]
107- redirect_uri_provided_explicitly = state_data ["redirect_uri_provided_explicitly" ] == "True"
108- client_id = state_data ["client_id" ]
109-
110- # Exchange code for token with GitHub
111- async with create_mcp_http_client () as client :
112- response = await client .post (
113- self .settings .github_token_url ,
114- data = {
115- "client_id" : self .settings .github_client_id ,
116- "client_secret" : self .settings .github_client_secret ,
117- "code" : code ,
118- "redirect_uri" : self .settings .github_callback_path ,
119- },
120- headers = {"Accept" : "application/json" },
121- )
122-
123- if response .status_code != 200 :
124- raise HTTPException (400 , "Failed to exchange code for token" )
125-
126- data = response .json ()
127-
128- if "error" in data :
129- raise HTTPException (400 , data .get ("error_description" , data ["error" ]))
130-
131- github_token = data ["access_token" ]
132-
133- # Create MCP authorization code
134- new_code = f"mcp_{ secrets .token_hex (16 )} "
135- auth_code = AuthorizationCode (
136- code = new_code ,
137- client_id = client_id ,
138- redirect_uri = AnyHttpUrl (redirect_uri ),
139- redirect_uri_provided_explicitly = redirect_uri_provided_explicitly ,
140- expires_at = time .time () + 300 ,
141- scopes = [self .settings .mcp_scope ],
142- code_challenge = code_challenge ,
143- )
144- self .auth_codes [new_code ] = auth_code
145-
146- # Store GitHub token - we'll map the MCP token to this later
147- self .tokens [github_token ] = AccessToken (
148- token = github_token ,
149- client_id = client_id ,
150- scopes = [self .settings .github_scope ],
151- expires_at = None ,
152- )
153-
154- del self .state_mapping [state ]
155- return construct_redirect_uri (redirect_uri , code = new_code , state = state )
156-
157- async def load_authorization_code (
158- self , client : OAuthClientInformationFull , authorization_code : str
159- ) -> AuthorizationCode | None :
160- """Load an authorization code."""
161- return self .auth_codes .get (authorization_code )
162-
163- async def exchange_authorization_code (
164- self , client : OAuthClientInformationFull , authorization_code : AuthorizationCode
165- ) -> OAuthToken :
166- """Exchange authorization code for tokens."""
167- if authorization_code .code not in self .auth_codes :
168- raise ValueError ("Invalid authorization code" )
169-
170- # Generate MCP access token
171- mcp_token = f"mcp_{ secrets .token_hex (32 )} "
172-
173- # Store MCP token
174- self .tokens [mcp_token ] = AccessToken (
175- token = mcp_token ,
176- client_id = client .client_id ,
177- scopes = authorization_code .scopes ,
178- expires_at = int (time .time ()) + 3600 ,
179- )
180-
181- # Find GitHub token for this client
182- github_token = next (
183- (
184- token
185- for token , data in self .tokens .items ()
186- # see https://github.blog/engineering/platform-security/behind-githubs-new-authentication-token-formats/
187- # which you get depends on your GH app setup.
188- if (token .startswith ("ghu_" ) or token .startswith ("gho_" )) and data .client_id == client .client_id
189- ),
190- None ,
191- )
192-
193- # Store mapping between MCP token and GitHub token
194- if github_token :
195- self .token_mapping [mcp_token ] = github_token
196-
197- del self .auth_codes [authorization_code .code ]
198-
199- return OAuthToken (
200- access_token = mcp_token ,
201- token_type = "Bearer" ,
202- expires_in = 3600 ,
203- scope = " " .join (authorization_code .scopes ),
204- )
205-
206- async def load_access_token (self , token : str ) -> AccessToken | None :
207- """Load and validate an access token."""
208- access_token = self .tokens .get (token )
209- if not access_token :
210- return None
211-
212- # Check if expired
213- if access_token .expires_at and access_token .expires_at < time .time ():
214- del self .tokens [token ]
215- return None
216-
217- return access_token
218-
219- async def load_refresh_token (self , client : OAuthClientInformationFull , refresh_token : str ) -> RefreshToken | None :
220- """Load a refresh token - not supported."""
221- return None
222-
223- async def exchange_refresh_token (
224- self ,
225- client : OAuthClientInformationFull ,
226- refresh_token : RefreshToken ,
227- scopes : list [str ],
228- ) -> OAuthToken :
229- """Exchange refresh token"""
230- raise NotImplementedError ("Not supported" )
231-
232- async def exchange_token (
233- self ,
234- client : OAuthClientInformationFull ,
235- subject_token : str ,
236- subject_token_type : str ,
237- actor_token : str | None ,
238- actor_token_type : str | None ,
239- scope : list [str ] | None ,
240- audience : str | None ,
241- resource : str | None ,
242- ) -> OAuthToken :
243- """Exchange an external token for an MCP access token."""
244- raise NotImplementedError ("Token exchange is not supported" )
245-
246- async def exchange_client_credentials (self , client : OAuthClientInformationFull , scopes : list [str ]) -> OAuthToken :
247- """Exchange client credentials for an access token."""
248- token = f"mcp_{ secrets .token_hex (32 )} "
249- self .tokens [token ] = AccessToken (
250- token = token ,
251- client_id = client .client_id ,
252- scopes = scopes ,
253- expires_at = int (time .time ()) + 3600 ,
254- )
255- return OAuthToken (
256- access_token = token ,
257- token_type = "Bearer" ,
258- expires_in = 3600 ,
259- scope = " " .join (scopes ),
260- )
261-
262- async def revoke_token (self , token : str , token_type_hint : str | None = None ) -> None :
263- """Revoke a token."""
264- if token in self .tokens :
265- del self .tokens [token ]
266-
267-
268- def create_simple_mcp_server (settings : ServerSettings ) -> FastMCP :
269- """Create a simple FastMCP server with GitHub OAuth."""
270- oauth_provider = SimpleGitHubOAuthProvider (settings )
54+ def create_resource_server (settings : ResourceServerSettings ) -> FastMCP :
55+ """
56+ Create MCP Resource Server with token introspection.
27157
272- auth_settings = AuthSettings (
273- issuer_url = settings .server_url ,
274- client_registration_options = ClientRegistrationOptions (
275- enabled = True ,
276- valid_scopes = [settings .mcp_scope ],
277- default_scopes = [settings .mcp_scope ],
278- ),
279- required_scopes = [settings .mcp_scope ],
280- # =======
281- # def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
282- # """
283- # Create MCP Resource Server with token introspection.
284-
285- # This server:
286- # 1. Provides protected resource metadata (RFC 9728)
287- # 2. Validates tokens via Authorization Server introspection
288- # 3. Serves MCP tools and resources
289- # """
290- # # Create token verifier for introspection with RFC 8707 resource validation
291- # token_verifier = IntrospectionTokenVerifier(
292- # introspection_endpoint=settings.auth_server_introspection_endpoint,
293- # server_url=str(settings.server_url),
294- # validate_resource=settings.oauth_strict, # Only validate when --oauth-strict is set
295- # >>>>>>> main
58+ This server:
59+ 1. Provides protected resource metadata (RFC 9728)
60+ 2. Validates tokens via Authorization Server introspection
61+ 3. Serves MCP tools and resources
62+ """
63+ # Create token verifier for introspection with RFC 8707 resource validation
64+ token_verifier = IntrospectionTokenVerifier (
65+ introspection_endpoint = settings .auth_server_introspection_endpoint ,
66+ server_url = str (settings .server_url ),
67+ validate_resource = settings .oauth_strict , # Only validate when --oauth-strict is set
29668 )
29769
29870 # Create FastMCP server as a Resource Server
0 commit comments