Skip to content

Commit 9c367cd

Browse files
committed
Refactor out Route generation for metadata endpoints
Refactors the `auth_metadata_mount` function into it's component parts, so that consumer that need more flexibility can use lower level constructs. Namely, if you need to manually build the various OAuth metadata endpoints because your clients are non-standards compliant.
1 parent 121c656 commit 9c367cd

File tree

5 files changed

+1039
-42
lines changed

5 files changed

+1039
-42
lines changed

README.md

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ if __name__ == "__main__":
181181
import os
182182
from fastmcp import FastMCP, Context
183183
from 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)
209209
def 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-authorization-server
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-authorization-server/api
269+
/.well-known/oauth-authorization-server/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

250387
1. **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)
252389
3. **Set MCP server dependencies** to allow delegated access
253390
4. **Create client secret identity** for secure authentication
254391

255-
256392
## Overview
257393

258394
This workspace contains multiple Python packages that provide various Keycard functionality:
@@ -314,6 +450,7 @@ pip install ./packages/mcp-fastmcp
314450
## Documentation
315451

316452
Comprehensive 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

Comments
 (0)