from __future__ import annotations from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastmcp import FastMCP from mcp.server.sse import SseServerTransport from starlette.responses import Response from starlette.applications import Starlette import uvicorn from starlette.routing import Mount, Route from core.config import get_settings from mcp_server.router import register_tools settings = get_settings() mcp = FastMCP("RecruitmentMCP") register_tools(mcp) transport = SseServerTransport("/messages/") async def sse_endpoint(request): async with transport.connect_sse(request.scope, request.receive, request._send) as streams: await mcp._mcp_server.run( streams[0], streams[1], mcp._mcp_server.create_initialization_options(), ) # Return a concrete response after client disconnect to avoid NoneType route errors. return Response(status_code=204) mcp_app = Starlette( routes=[ Route("/sse", endpoint=sse_endpoint), Mount("/messages/", app=transport.handle_post_message), ] ) app = FastAPI(title="Recruitment MCP Server") app.mount("/mcp-server", mcp_app) for target in (app, mcp_app): target.add_middleware( CORSMiddleware, allow_origins=settings.cors_origin_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/mcp-health") def mcp_health() -> dict: return { "ok": True, "mcp_endpoint": "/mcp-server/sse", "tools": [ "ingest_resume_pdf", "semantic_candidate_search", "candidate_metadata_query", "compute_job_match_score", "get_policy_info", "manage_application_status", "manage_interview_records", "generate_job_posting", "bulk_ingest_reference_resumes", ], } if __name__ == "__main__": uvicorn.run("mcp_server.server:app", host=settings.host, port=9000, reload=False)