Spaces:
Running
Running
""" | |
Model Context Protocol (MCP) for scanpy | |
Scanpy is a scalable toolkit for analyzing single-cell gene expression data built jointly with anndata. | |
It provides preprocessing, visualization, clustering, pseudotime and trajectory inference, differential expression testing, and integration of heterogeneous datasets. | |
This codebase focuses on fundamental single-cell RNA sequencing analysis workflows including quality control, normalization, dimensionality reduction, and clustering. | |
This MCP Server contains the tools extracted from the following tutorials: | |
1. clustering | |
- quality_control: Calculate and visualize QC metrics, filter cells and genes, detect doublets | |
- normalize_data: Normalize count data with median total counts and log transformation | |
- select_features: Identify highly variable genes for feature selection | |
- reduce_dimensionality: Perform PCA analysis and variance visualization | |
- build_neighborhood_graph: Construct nearest neighbor graph and UMAP embedding | |
- cluster_cells: Perform Leiden clustering with visualization | |
- annotate_cell_types: Multi-resolution clustering, marker gene analysis, and differential expression | |
""" | |
import sys | |
from pathlib import Path | |
from fastmcp import FastMCP | |
from starlette.requests import Request | |
from starlette.responses import PlainTextResponse, JSONResponse | |
import os | |
from fastapi.staticfiles import StaticFiles | |
import uuid | |
import os | |
# Import the MCP tools from the tools folder | |
from tools.clustering import clustering_mcp | |
# Define the MCP server | |
mcp = FastMCP(name = "scanpy") | |
# Mount the tools | |
mcp.mount(clustering_mcp) | |
# Use absolute directory for uploads | |
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
UPLOAD_DIR = os.path.join(BASE_DIR, "/data/upload") | |
os.makedirs(UPLOAD_DIR, exist_ok=True) | |
async def health_check(request: Request) -> PlainTextResponse: | |
return PlainTextResponse("OK") | |
async def index(request: Request) -> PlainTextResponse: | |
return PlainTextResponse("MCP is on https://Paper2Agent-scanpy-mcp.hf.space/mcp") | |
# Upload route | |
async def upload(request: Request): | |
form = await request.form() | |
up = form.get("file") | |
if up is None: | |
return JSONResponse({"error": "missing form field 'file'"}, status_code=400) | |
# Generate a safe filename | |
orig = getattr(up, "filename", "") or "" | |
ext = os.path.splitext(orig)[1] | |
name = f"{uuid.uuid4().hex}{ext}" | |
dst = os.path.join(UPLOAD_DIR, name) | |
# up is a Starlette UploadFile-like object | |
with open(dst, "wb") as out: | |
out.write(await up.read()) | |
# Return only the absolute local path | |
abs_path = os.path.abspath(dst) | |
return JSONResponse({"path": abs_path}) | |
app = mcp.http_app(path="/mcp") | |
# Saved uploaded input files | |
app.mount("/files", StaticFiles(directory=UPLOAD_DIR), name="files") | |
# Saved output files | |
app.mount("/outputs", StaticFiles(directory="/data/tmp_outputs"), name="outputs") | |
# Run the MCP server | |
if __name__ == "__main__": | |
mcp.run(transport="http", host="127.0.0.1", port=8003) |