@@ -181,7 +181,7 @@ if __name__ == "__main__":
181181import os
182182from fastmcp import FastMCP, Context
183183from keycardai.mcp.integrations.fastmcp import (
184- AuthProvider,
184+ AuthProvider,
185185 AccessContext,
186186 ClientSecret
187187)
@@ -209,11 +209,11 @@ mcp = FastMCP("My Secure FastMCP Server", auth=auth)
209209def call_external_api (ctx : Context, query : str ) -> str :
210210 # Get access context to check token exchange status
211211 access_context: AccessContext = ctx.get_state(" keycardai" )
212-
212+
213213 # Check for errors before accessing token
214214 if access_context.has_errors():
215215 return f " Error: Failed to obtain access token - { access_context.get_errors()} "
216-
216+
217217 # Access delegated token through context namespace
218218 token = access_context.access(" https://api.example.com" ).access_token
219219 # Use token to call external API
@@ -239,6 +239,143 @@ Configure the remote MCP in your AI client, like [Cursor](https://cursor.com/?fr
239239
240240### 🎉 Your MCP server is now protected with Keycard authentication! 🎉
241241
242+ ## Using FastAPI
243+
244+ Mounting a FastMCP server into a larger FastAPI service introduces a few
245+ gotchas, particularly related to the various OAuth metadata endpoints.
246+
247+ ### Standards Compliant Approach
248+
249+ > [ !NOTE]
250+ > Most MCP clients expect standards-compliance. Follow this approach if you're
251+ > using those clients or the official MCP SDKs.
252+
253+ The OAuth spec declares that your metadata must be exposed at the root of your
254+ service.
255+
256+ ```
257+ /.well-known/oauth-protected-resource
258+ ```
259+
260+ This causes a problem when you're mounting multiple APIs or MCP servers to a
261+ common FastAPI service. Each API or MCP Server will potentially have their own
262+ OAuth metadata.
263+
264+ The OAuth spec defines that the metadata for each individual service should be
265+ exposed as an extension to the base ` well-known ` URI. For example:
266+
267+ ```
268+ /.well-known/oauth-protected-resource/api
269+ /.well-known/oauth-protected-resource/mcp-server/mcp
270+ ```
271+
272+ To ensure FastMCP and FastAPI produce this, you need to ensure your routing is
273+ defined in a specific way:
274+
275+ ``` python
276+ from fastmcp import FastMCP
277+ from fastapi import FastAPI
278+
279+ mcp = FastMCP(" MCP Server" )
280+ mcp_app = mcp.http_app() # DO NOT specify a path here
281+
282+ app = FastAPI(title = " API" , lifespan = mcp_app.lifespan)
283+
284+ # You MUST mount the MCP's `http_app` to the full path for FastMCP to expose the
285+ # OAuth metadata correctly.
286+ app.mount(" /mcp-server/mcp" , mcp_app)
287+ ```
288+
289+ ### Custom, Non Standards Compliant, Approach
290+
291+ > ![ WARNING]
292+ > ** This is not advised.** Only follow this if you know for sure you need
293+ > flexibility outside of what the spec requires.
294+
295+ If you've built custom clients or need to mount the metadata at a different, non
296+ standards compliant, location, you can do that manually.
297+
298+ #### Mounting at a Custom Root
299+
300+ ``` python
301+ from fastmcp import FastMCP
302+ from fastapi import FastAPI
303+ from keycardai.mcp.server.routers.metadata import well_known_metadata_mount
304+
305+ auth_provider = AuthProvider(
306+ zone_id = " your-zone-id" , # Get this from keycard.ai
307+ mcp_server_name = " My Secure FastMCP Server" ,
308+ mcp_base_url = " http://127.0.0.1:8000/"
309+ )
310+
311+ auth = auth_provider.get_remote_auth_provider()
312+
313+ mcp = FastMCP(" MCP Server" , auth = auth)
314+ mcp_app = mcp.http_app()
315+
316+ app = FastAPI(title = " API" , lifespan = mcp_app.lifespan)
317+
318+ app.mount(
319+ " /custom-well-known" ,
320+ well_known_metadata_mount(issuer = auth.zone_url),
321+ )
322+ ```
323+
324+ which will produce the following endpoints
325+
326+ ```
327+ /custom-well-known/oauth-protected-resource
328+ /custom-well-known/oauth-authorization-server
329+ ```
330+
331+ #### Mounting at a Specific URI
332+
333+ If you need even more control, you can mount the individual routes at a specific
334+ URI.
335+
336+ ``` python
337+ from fastmcp import FastMCP
338+ from fastapi import FastAPI
339+ from keycardai.mcp.server.routers.metadata import (
340+ well_known_authorization_server_route,
341+ well_known_protected_resource_route,
342+ )
343+
344+ auth_provider = AuthProvider(
345+ zone_id = " your-zone-id" , # Get this from keycard.ai
346+ mcp_server_name = " My Secure FastMCP Server" ,
347+ mcp_base_url = " http://127.0.0.1:8000/"
348+ )
349+
350+ auth = auth_provider.get_remote_auth_provider()
351+
352+ mcp = FastMCP(" MCP Server" , auth = auth)
353+ mcp_app = mcp.http_app()
354+
355+ app = FastAPI(title = " API" , lifespan = mcp_app.lifespan)
356+
357+ app.router.routes.append(
358+ well_known_protected_resource_route(
359+ path = " /my/custom/path/to/well-known/oauth-protected-resource" ,
360+ issuer = auth.zone_url,
361+ )
362+ )
363+
364+ app.router.routes.append(
365+ well_known_authorization_server_route(
366+ path = " /my/custom/path/to/well-known/oauth-authorization-server" ,
367+ issuer = auth.zone_url,
368+ )
369+ )
370+ ```
371+
372+ which will produce the following endpoints
373+
374+ ```
375+ /my/custom/path/to/well-known/oauth-protected-resource
376+ /my/custom/path/to/well-known/oauth-authorization-server
377+ ```
378+
242379## Features
243380
244381### Delegated Access
@@ -248,11 +385,10 @@ Keycard allows MCP servers to access other resources on behalf of users with aut
248385#### Setup Protected Resources
249386
2503871 . ** Configure credential provider** (e.g., Google Workspace)
251- 2 . ** Configure protected resource** (e.g., Google Drive API)
388+ 2 . ** Configure protected resource** (e.g., Google Drive API)
2523893 . ** Set MCP server dependencies** to allow delegated access
2533904 . ** Create client secret identity** for secure authentication
254391
255-
256392## Overview
257393
258394This workspace contains multiple Python packages that provide various Keycard functionality:
@@ -314,6 +450,7 @@ pip install ./packages/mcp-fastmcp
314450## Documentation
315451
316452Comprehensive documentation is available at our [ documentation site] ( https://docs.keycard.ai ) , including:
453+
317454- API reference for all packages
318455- Usage examples and tutorials
319456- Integration guides
@@ -381,4 +518,4 @@ For questions, issues, or support:
381518
382519- GitHub Issues: [ https://github.com/keycardai/python-sdk/issues ] ( https://github.com/keycardai/python-sdk/issues )
383520- Documentation: [ https://docs.keycardai.com ] ( https://docs.keycard.ai/ )
384- 521+
0 commit comments