Paper2Agent/scanpy_mcp
0
1"""2Model Context Protocol (MCP) for scanpy3 4Scanpy is a scalable toolkit for analyzing single-cell gene expression data built jointly with anndata. 5It provides preprocessing, visualization, clustering, pseudotime and trajectory inference, differential expression testing, and integration of heterogeneous datasets.6This codebase focuses on fundamental single-cell RNA sequencing analysis workflows including quality control, normalization, dimensionality reduction, and clustering.7 8This MCP Server contains the tools extracted from the following tutorials:91. clustering10 - quality_control: Calculate and visualize QC metrics, filter cells and genes, detect doublets11 - normalize_data: Normalize count data with median total counts and log transformation12 - select_features: Identify highly variable genes for feature selection13 - reduce_dimensionality: Perform PCA analysis and variance visualization14 - build_neighborhood_graph: Construct nearest neighbor graph and UMAP embedding15 - cluster_cells: Perform Leiden clustering with visualization16 - annotate_cell_types: Multi-resolution clustering, marker gene analysis, and differential expression17"""18 19import sys20from pathlib import Path21from fastmcp import FastMCP22from starlette.requests import Request23from starlette.responses import PlainTextResponse, JSONResponse24import os25from fastapi.staticfiles import StaticFiles26import uuid27import os28 29 30# Import the MCP tools from the tools folder31from tools.clustering import clustering_mcp32 33# Define the MCP server34mcp = FastMCP(name = "scanpy")35 36# Mount the tools37mcp.mount(clustering_mcp)38 39# Use absolute directory for uploads40BASE_DIR = os.path.dirname(os.path.abspath(__file__))41UPLOAD_DIR = os.path.join(BASE_DIR, "/data/upload")42os.makedirs(UPLOAD_DIR, exist_ok=True)43 44@mcp.custom_route("/health", methods=["GET"])45async def health_check(request: Request) -> PlainTextResponse:46 return PlainTextResponse("OK")47 48 49@mcp.custom_route("/", methods=["GET"])50async def index(request: Request) -> PlainTextResponse:51 return PlainTextResponse("MCP is on https://Paper2Agent-scanpy-mcp.hf.space/mcp")52 53# Upload route54@mcp.custom_route("/upload", methods=["POST"])55async def upload(request: Request):56 form = await request.form()57 up = form.get("file")58 if up is None:59 return JSONResponse({"error": "missing form field 'file'"}, status_code=400)60 61 # Generate a safe filename62 orig = getattr(up, "filename", "") or ""63 ext = os.path.splitext(orig)[1]64 name = f"{uuid.uuid4().hex}{ext}"65 dst = os.path.join(UPLOAD_DIR, name)66 67 # up is a Starlette UploadFile-like object68 with open(dst, "wb") as out:69 out.write(await up.read())70 71 # Return only the absolute local path72 abs_path = os.path.abspath(dst)73 return JSONResponse({"path": abs_path})74 75app = mcp.http_app(path="/mcp")76# Saved uploaded input files77app.mount("/files", StaticFiles(directory=UPLOAD_DIR), name="files")78# Saved output files79app.mount("/outputs", StaticFiles(directory="/data/tmp_outputs"), name="outputs")80 81# Run the MCP server82if __name__ == "__main__":83 mcp.run(transport="http", host="127.0.0.1", port=8003)