Spaces:
Sleeping
Sleeping
| """ | |
| Model Context Protocol (MCP) for GLUE_Agent | |
| GLUE_Agent provides comprehensive multi-omics data integration tools for single-cell RNA-seq and ATAC-seq analysis. This framework enables preprocessing, model training, and visualization of integrated multi-modal datasets. | |
| This MCP Server contains tools extracted from the following tutorial files: | |
| 1. preprocessing | |
| - glue_preprocess_scrna: Preprocess scRNA-seq data with HVG selection, normalization, and PCA | |
| - glue_preprocess_scatac: Preprocess scATAC-seq data with LSI dimension reduction | |
| - glue_construct_regulatory_graph: Construct prior regulatory graph linking RNA and ATAC features | |
| 2. training | |
| - glue_configure_datasets: Configure RNA-seq and ATAC-seq datasets for GLUE model training | |
| - glue_train_model: Train GLUE model for multi-omics integration | |
| - glue_check_integration_consistency: Evaluate integration quality with consistency scores | |
| - glue_generate_embeddings: Generate cell and feature embeddings from trained GLUE model | |
| Remote endpoint layout | |
| ---------------------- | |
| GET / β health check (HF Spaces liveness probe) | |
| POST /upload β upload a local file to /data/inputs on the server | |
| GET /outputs/... β download a file previously written to /data/outputs | |
| /mcp β Streamable HTTP MCP endpoint (used by Claude Code) | |
| """ | |
| import os | |
| from pathlib import Path | |
| from fastapi import FastAPI, HTTPException, UploadFile | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastmcp import FastMCP | |
| from tools.preprocessing import preprocessing_mcp | |
| from tools.training import training_mcp | |
| # These env vars are set in the Dockerfile; both tool modules honour them too. | |
| INPUT_DIR = Path(os.getenv("PREPROCESSING_INPUT_DIR", "/data/inputs")) | |
| OUTPUT_DIR = Path(os.getenv("PREPROCESSING_OUTPUT_DIR", "/data/outputs")) | |
| INPUT_DIR.mkdir(parents=True, exist_ok=True) | |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) | |
| # --- MCP server ----------------------------------------------------------- | |
| mcp = FastMCP(name="GLUE_Agent") | |
| mcp.mount(preprocessing_mcp) | |
| mcp.mount(training_mcp) | |
| # Build the MCP HTTP sub-app first so we can forward its lifespan. | |
| mcp_http_app = mcp.http_app(path="/") | |
| # --- Outer FastAPI app ---------------------------------------------------- | |
| # The MCP sub-app's lifespan initialises the StreamableHTTPSessionManager | |
| # task group. Mounted sub-apps don't get their lifespans invoked by | |
| # Starlette, so we must propagate it to the parent app explicitly. | |
| app = FastAPI(title="GLUE_Agent MCP Server", lifespan=mcp_http_app.lifespan) | |
| _OAUTH_NOT_CONFIGURED = { | |
| "error": "unsupported_authorization_type", | |
| "error_description": "OAuth is not configured for this MCP server.", | |
| } | |
| async def health_check(): | |
| """Liveness probe for HF Spaces; also confirms the MCP endpoint path.""" | |
| return {"status": "ok", "mcp_endpoint": "/mcp"} | |
| # Claude Code currently probes OAuth discovery/registration endpoints for remote | |
| # HTTP MCP servers. If those endpoints return a generic 404 body (e.g. | |
| # {"detail":"Not Found"}), Claude tries to parse it as an OAuth error payload and | |
| # fails. Returning a well-formed OAuth error JSON avoids that failure mode. | |
| async def oauth_protected_resource(): | |
| return JSONResponse(status_code=404, content=_OAUTH_NOT_CONFIGURED) | |
| async def oauth_authorization_server(): | |
| return JSONResponse(status_code=404, content=_OAUTH_NOT_CONFIGURED) | |
| async def oauth_dynamic_client_register(): | |
| return JSONResponse(status_code=404, content=_OAUTH_NOT_CONFIGURED) | |
| async def upload_file(file: UploadFile): | |
| """ | |
| Upload a local file to the server's input directory. | |
| Usage (from launch_remote_mcp.sh CLAUDE.md template): | |
| curl -F "file=@/absolute/local/path" ${REMOTE_MCP_URL}/upload | |
| """ | |
| dest = INPUT_DIR / file.filename | |
| dest.parent.mkdir(parents=True, exist_ok=True) | |
| content = await file.read() | |
| dest.write_bytes(content) | |
| return {"filename": file.filename, "path": str(dest), "size": len(content)} | |
| async def download_output(name: str): | |
| """ | |
| Download a file previously written by an MCP tool. | |
| Usage (from launch_remote_mcp.sh CLAUDE.md template): | |
| wget ${REMOTE_MCP_URL}/outputs/<output_filename> | |
| """ | |
| # Resolve and guard against path-traversal attacks | |
| file_path = (OUTPUT_DIR / name).resolve() | |
| if not str(file_path).startswith(str(OUTPUT_DIR.resolve())): | |
| raise HTTPException(status_code=400, detail="Invalid file path") | |
| if not file_path.is_file(): | |
| raise HTTPException(status_code=404, detail=f"Output file not found: {name}") | |
| return FileResponse(str(file_path), filename=file_path.name) | |
| app.mount("/mcp", mcp_http_app) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| app, | |
| host="0.0.0.0", | |
| port=int(os.getenv("PORT", 7860)), | |
| ) | |