scanpy_mcp / scanpy_mcp.py
Paper2Agent's picture
Upload 5 files
ffcb052 verified
raw
history blame
3.16 kB
"""
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)
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> PlainTextResponse:
return PlainTextResponse("OK")
@mcp.custom_route("/", methods=["GET"])
async def index(request: Request) -> PlainTextResponse:
return PlainTextResponse("MCP is on https://Paper2Agent-scanpy-mcp.hf.space/mcp")
# Upload route
@mcp.custom_route("/upload", methods=["POST"])
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)