The _wrap_well_known_endpoint function assumed all route endpoints are regular
request handlers (async def handler(request) -> Response). However, the MCP
SDK's cors_middleware wraps handlers with CORSMiddleware, which is an ASGI app
expecting (scope, receive, send). When the wrapper called
`await endpoint(request)` on a CORSMiddleware instance, it passed only 1
argument instead of the required 3 ASGI args.
The fix detects whether the endpoint is a regular handler function or an ASGI
app (using the same inspect check as Starlette's Route constructor), and uses
the appropriate calling convention:
- Regular handlers: called as `await endpoint(request)` (existing behavior)
- ASGI apps: invoked via the ASGI interface `await endpoint(scope, receive, send)`
with response capture to apply cache-busting headers
https://claude.ai/code/session_011S5zFTWRfKBJBUEanrhvQg
This commit implements a new feature that allows Gmail attachments to be
served via HTTP URLs instead of returning base64-encoded data in the tool
response. This avoids consuming LLM context window space and token budgets
for large attachments.
Architecture:
-------------
The implementation works in both stdio and streamable-http transport modes:
1. Temp File Storage (core/attachment_storage.py):
- New AttachmentStorage class manages temporary file storage in ./tmp/attachments/
- Uses UUID-based file IDs to prevent guessing/unauthorized access
- Tracks metadata: filename, mime type, size, creation/expiration times
- Files expire after 1 hour (configurable) with automatic cleanup support
- Handles base64 decoding and file writing
2. HTTP Route Handlers:
- Added /attachments/{file_id} route to main FastMCP server (streamable-http mode)
- Added same route to MinimalOAuthServer (stdio mode)
- Both routes serve files with proper Content-Type headers via FileResponse
- Returns 404 for expired or missing attachments
3. Modified get_gmail_attachment_content():
- Now saves attachments to temp storage and returns HTTP URL
- Attempts to fetch filename/mimeType from message metadata (best effort)
- Handles stateless mode gracefully (skips file saving, shows preview)
- Falls back to base64 preview if file saving fails
- URL generation respects WORKSPACE_EXTERNAL_URL for reverse proxy setups
Key Features:
-------------
- Works in both stdio and streamable-http modes (uses existing HTTP servers)
- Respects stateless mode (no file writes when WORKSPACE_MCP_STATELESS_MODE=true)
- Secure: UUID-based file IDs prevent unauthorized access
- Automatic expiration: Files cleaned up after 1 hour
- Reverse proxy support: Uses WORKSPACE_EXTERNAL_URL if configured
- Graceful degradation: Falls back to preview if storage fails
Benefits:
---------
- Avoids context window bloat: Large attachments don't consume LLM tokens
- Better performance: Clients can stream/download files directly
- More efficient: No need to decode base64 in client applications
- Works across network boundaries: URLs accessible from any client
The feature maintains backward compatibility - if file saving fails or stateless
mode is enabled, the function falls back to showing a base64 preview.