Skip to content

Authentication Issue with Combined FastAPI/FastMCP App #2817

@kyzer-davis

Description

@kyzer-davis

Description

Observing authentication issues with a fastapi/fastmcp combined app when the authentication is nested a bit deeper in the app.

  • The MRE below has two FastAPI routes. One is protected with BearerAuth while the other is open.
  • The MCP http app is created from the Fastapi app and all 3 routes (/protected, /open, /mcp) are mounted in the combined app.
  • I have included middleware (auth_logger) for tracking the Authorization: bearer test header from inbound API > MCP > Route.

The two problems I observe are as follows:

  1. With auth=DebugTokenVerifier() set, the intialize always fails. Logging shows the Auth header gets to the combined API App, but FastMCP rejects with 401 before the MCP logging middlware logs the header.
    INFO:auth_logger:Authorization Header (API): test
    [01/08/26 21:58:47] INFO     Auth error returned: invalid_token (status=401)    middleware.py:92
  2. With the previous item disabled: intialize and list tools both work but when attempting to use the protected tool; the call fails although the Auth header is passed from API > MCP > Route (as confirmed by the open test)
    # test_endpoint_auth_protected_get MCP Tool Call
    INFO:auth_logger:Authorization Header (API): test
    DEBUG:mcp.server.streamable_http_manager:Session already exists, handling request directly
    INFO:mcp.server.lowlevel.server:Processing request of type CallToolRequest
    DEBUG:mcp.server.lowlevel.server:Dispatching request of type CallToolRequest
    INFO:auth_logger:Authorization Header (MCP): test
    INFO:httpx:HTTP Request: GET http://fastapi/protected "HTTP/1.1 401 Unauthorized"
    [01/08/26 22:00:00] Error calling tool 'test_endpoint_auth_protected_get'  
    
    # test_endpoint_open_open_get MCP Tool Call
    INFO:auth_logger:Authorization Header (API): test
    DEBUG:mcp.server.streamable_http_manager:Session already exists, handling request directly
    INFO:mcp.server.lowlevel.server:Processing request of type CallToolRequest
    DEBUG:mcp.server.lowlevel.server:Dispatching request of type CallToolRequest
    INFO:auth_logger:Authorization Header (MCP): test
    INFO:auth_logger:Authorization Header (ROUTE): test
    INFO:httpx:HTTP Request: GET http://fastapi/open "HTTP/1.1 200 OK"

Sample of CURL to protected route to verify it is working as expected with and without the auth header.

curl -X GET http://127.0.0.1:8000/protected -H "Authorization: Bearer test"
{"message":"Auth is working!"}

curl -X GET http://127.0.0.1:8000/protected
{"detail":"Not authenticated"}

CURL Commands (I have been testing with MCP Inspector for the second tool call error) but these help for quick illustrations like the previous example.

curl -X GET http://127.0.0.1:8000/protected -H "Authorization: Bearer test"
curl -X GET http://127.0.0.1:8000/open -H "Authorization: Bearer test"

curl -s -D - -X POST \
  -H "Authorization: Bearer test" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": {
        "name": "curl-test",
        "version": "1.0.0"
      }
    },
    "id": 1
  }' \
  "http://127.0.0.1:8000/mcp"

Example Code

from fastmcp import FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.server.dependencies import get_http_headers
from fastmcp.server.auth.providers.debug import DebugTokenVerifier

from fastapi import Depends, FastAPI
from fastapi.security import HTTPBearer

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("auth_logger")


# API application
api_app = FastAPI(title="API")


@api_app.get("/protected", dependencies=[Depends(HTTPBearer())])
async def test_endpoint_auth(request: Request):
    logger.info(
        f"Authorization Header (ROUTE): {request.headers.get('authorization', 'No Authorization Header')}"
    )
    return {"message": "Auth is working!"}


@api_app.get("/open")
async def test_endpoint_open(request: Request):
    logger.info(
        f"Authorization Header (ROUTE): {request.headers.get('authorization', 'No Authorization Header')}"
    )
    return {"message": "API is working!"}


# MCP integration
mcp = FastMCP.from_fastapi(
    app=api_app,
    name="MCP",
    httpx_client_kwargs={
        "headers": {
            "Authorization": f"Bearer asdf", # Added while testing, does not do anything that I can see
        }
    },
    auth=DebugTokenVerifier(), # Disable this line to test issue number 2
)


class McpLoggerMiddleware(Middleware):
    async def on_message(self, context: MiddlewareContext, call_next):
        headers = get_http_headers()
        auth_header = headers.get("authorization", "")
        logger.info(f"Authorization Header (MCP): {auth_header}")
        return await call_next(context)


mcp.add_middleware(McpLoggerMiddleware())

mcp_app = mcp.http_app(path="/mcp")


# Combined application
app = FastAPI(
    title="API with MCP",
    routes=[
        *mcp_app.routes,  # MCP routes
        *api_app.routes,  # Original API routes
    ],
    lifespan=mcp_app.lifespan,
)


class ApiLoggerMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        auth_header = request.headers.get("authorization", "No Authorization Header")
        logger.info(f"Authorization Header (API): {auth_header}")
        response = await call_next(request)
        return response


app.add_middleware(ApiLoggerMiddleware)

Version Information

FastMCP version:                                                                      2.14.2
MCP version:                                                                          1.25.0
Python version:                                                                       3.12.3
Platform:                       Linux-6.6.87.2-microsoft-standard-WSL2-x86_64-with-glibc2.39
FastMCP root path: /home/kydavis/scratchpad/fastmcp-issue/.venv/lib/python3.12/site-packages

Metadata

Metadata

Assignees

No one assigned

    Labels

    authRelated to authentication (Bearer, JWT, OAuth, WorkOS) for client or server.bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.httpRelated to HTTP transport, networking, or web server functionality.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions