Skip to content

Commit 840bae6

Browse files
authored
Merge pull request #44 from keycardai/matte/greater-metadata-control
feat(keycardai-mcp): Add greater control over OAuth metadata location
2 parents 0e5d93e + 90f4366 commit 840bae6

File tree

5 files changed

+1038
-42
lines changed

5 files changed

+1038
-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-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

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)