Skip to content

Commit 04895cb

Browse files
committed
Refactor FastAPI server initialization in server.py; implement combined lifespan for MCP integration and enhance documentation for clarity
1 parent 2908ba3 commit 04895cb

File tree

1 file changed

+39
-25
lines changed

1 file changed

+39
-25
lines changed

src/sandbox_api_mcp_server/server.py

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -91,51 +91,65 @@ async def check_send(message):
9191

9292

9393
def 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

Comments
 (0)