@@ -91,51 +91,65 @@ async def check_send(message):
9191
9292
9393def run ():
94- app = FastAPI (title = "SandboxApiMCP" , lifespan = lifespan )
95- app .include_router (get_sandbox_api_router ())
96- app .add_middleware (
97- CORSMiddleware ,
98- allow_origins = ["*" ],
99- allow_credentials = True ,
100- allow_methods = ["*" ],
101- allow_headers = ["*" ],
102- )
103- app .add_middleware (ProxyHeadersMiddleware )
104- app .add_middleware (SecurityHeadersMiddleware )
94+ """
95+ Run the FastAPI server with MCP integration.
96+
97+ IMPORTANT: This uses a combined lifespan approach because http_app() requires
98+ its lifespan to be run to initialize the task group. Simply mounting http_app
99+ on a FastAPI app will NOT work - you MUST combine the lifespans.
105100
106- # Get port from environment or use default
101+ See: https://gofastmcp.com/integrations/asgi#asgi-starlette-fastmcp
102+ """
107103 port = int (os .getenv ("PORT" , 9100 ))
108104
109- # Define route maps to exclude health_check endpoint from MCP tools
105+ # Step 1: Create temporary FastAPI app for MCP conversion
106+ temp_app = FastAPI (title = "SandboxApiMCP" )
107+ temp_app .include_router (get_sandbox_api_router ())
108+
110109 route_maps = [
111110 # Exclude health endpoint from MCP tools
112111 RouteMap (
113112 methods = ["GET" ],
114113 pattern = r".*/health$" ,
115114 mcp_type = MCPType .EXCLUDE , # Exclude from MCP
116115 ),
117- # Map all other endpoints to tools (default behavior)
118116 ]
119117
120- # Convert FastAPI app to MCP server using from_fastapi
121- # This will expose FastAPI endpoints as MCP tools
118+ # Step 2: Convert to MCP and get http_app
122119 mcp = FastMCP .from_fastapi (
123- app = app ,
120+ app = temp_app ,
124121 name = "Neo4j Sandbox API MCP Server" ,
125122 route_maps = route_maps ,
126123 )
124+ http_app = mcp .http_app ()
127125
128- # Mount MCP transports - support both for maximum compatibility
129- sse_app = mcp .sse_app ()
130- app .mount ("/sse" , sse_app )
126+ # Step 3: Create combined lifespan
127+ @asynccontextmanager
128+ async def combined_lifespan (app : FastAPI ):
129+ # Initialize JWKS public key
130+ app .state .jwks_public_key = await fetch_jwks_public_key (Auth0Settings ().auth0_jwks_url )
131+ # Run MCP app lifespan (required for task group initialization)
132+ async with http_app .lifespan (app ):
133+ yield
134+
135+ # Step 4: Create final FastAPI app with combined lifespan
136+ app = FastAPI (title = "SandboxApiMCP" , lifespan = combined_lifespan )
137+ app .include_router (get_sandbox_api_router ())
138+ app .add_middleware (
139+ CORSMiddleware ,
140+ allow_origins = ["*" ],
141+ allow_credentials = True ,
142+ allow_methods = ["*" ],
143+ allow_headers = ["*" ],
144+ )
145+ app .add_middleware (ProxyHeadersMiddleware )
146+ app .add_middleware (SecurityHeadersMiddleware )
131147
132- # HTTP transport for modern clients (recommended for production)
133- http_app = mcp .http_app ()
148+ # Step 5: Mount HTTP app at root
134149 app .mount ("" , http_app )
135150
136- logger .info ("MCP server available at multiple transports:" )
137- logger .info (" - /sse (SSE transport - legacy, backward compatible)" )
138- logger .info (" - /mcp (Streamable HTTP transport - modern, recommended)" )
151+ logger .info ("MCP server available at:" )
152+ logger .info (" - /mcp (HTTP transport)" )
139153
140154 # Authentication Note:
141155 # - FastAPI routes use Depends(verify_auth) which handles both:
0 commit comments