Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Biomni/mcp_generated/mcp_anarci/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_anarci/app/anarci_server.py +191 -0
- Biomni/mcp_generated/mcp_anarci/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_anarci/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_anarci/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_server.py +214 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-cellmigration/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-geneplotter/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_server.py +270 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-keggrest/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-keggrest/app/bioconductor-keggrest_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-keggrest/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-keggrest/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-keggrest/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/app/bioconductor-scran_server.py +115 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/app/bioconductor-scran_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-scran/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-spari/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-spari/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-spari/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-spari/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_server.py +158 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-spatialcpie/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-spoon/app/bioconductor-spoon_server.py +160 -0
- Biomni/mcp_generated/mcp_bioconductor-spoon/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/app/bioconductor-statial_server.py +321 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/app/bioconductor-statial_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-statial/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-svp/app/bioconductor-svp_server.py +250 -0
Biomni/mcp_generated/mcp_anarci/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install anarci via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda anarci -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/anarci_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/anarci_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/anarci_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_anarci/app/anarci_server.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import subprocess
|
| 3 |
+
from typing import Optional, List, Union
|
| 4 |
+
|
| 5 |
+
from mcp.server.fastmcp import FastMCP
|
| 6 |
+
|
| 7 |
+
SERVER_NAME = 'local_anarci'
|
| 8 |
+
mcp = FastMCP(SERVER_NAME)
|
| 9 |
+
|
| 10 |
+
@mcp.tool()
|
| 11 |
+
def run_anarci(
|
| 12 |
+
sequence: Optional[str] = None,
|
| 13 |
+
input_file: Optional[str] = None,
|
| 14 |
+
output_file: Optional[str] = None,
|
| 15 |
+
scheme: str = "imgt",
|
| 16 |
+
restrict: Optional[str] = None,
|
| 17 |
+
species: Optional[str] = None,
|
| 18 |
+
csv: bool = False,
|
| 19 |
+
ncpu: int = 1,
|
| 20 |
+
bit_score_threshold: float = 80.0,
|
| 21 |
+
assign_germline: bool = False,
|
| 22 |
+
name: Optional[str] = None,
|
| 23 |
+
verbose: bool = False
|
| 24 |
+
):
|
| 25 |
+
"""
|
| 26 |
+
ANARCI: Antibody Numbering and Antigen Receptor ClassIfication.
|
| 27 |
+
Numbers amino-acid sequences of antibody and T-cell receptor variable domains.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
sequence: A single amino acid sequence to number.
|
| 31 |
+
input_file: Path to a FASTA file containing sequences to number.
|
| 32 |
+
output_file: Path to the output file. If not provided, results are printed to stdout.
|
| 33 |
+
scheme: Numbering scheme to use. Options: 'imgt', 'kabat', 'chothia', 'martin', 'aho'. (Default: 'imgt')
|
| 34 |
+
restrict: Restrict numbering to specific domain types. Options: 'ig' (immunoglobulins), 'tr' (T-cell receptors).
|
| 35 |
+
species: Restrict germline search to specific species (e.g., 'human', 'mouse', 'rat', 'rabbit', 'rhesus', 'pig', 'alpaca').
|
| 36 |
+
csv: If True, output results in CSV format (horizontal). If False, uses plain text (vertical).
|
| 37 |
+
ncpu: Number of CPUs to use for parallel processing. (Default: 1)
|
| 38 |
+
bit_score_threshold: HMMER bit score threshold for domain identification. (Default: 80.0)
|
| 39 |
+
assign_germline: If True, assign germline species and gene information.
|
| 40 |
+
name: Name to assign to the single sequence (used with 'sequence' parameter).
|
| 41 |
+
verbose: If True, produce verbose output.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
# Input validation
|
| 45 |
+
if not sequence and not input_file:
|
| 46 |
+
raise ValueError("Either 'sequence' or 'input_file' must be provided.")
|
| 47 |
+
|
| 48 |
+
if sequence and input_file:
|
| 49 |
+
raise ValueError("Provide either 'sequence' or 'input_file', not both.")
|
| 50 |
+
|
| 51 |
+
valid_schemes = ["imgt", "kabat", "chothia", "martin", "aho"]
|
| 52 |
+
if scheme.lower() not in valid_schemes:
|
| 53 |
+
raise ValueError(f"Invalid scheme '{scheme}'. Must be one of {valid_schemes}")
|
| 54 |
+
|
| 55 |
+
if restrict and restrict.lower() not in ["ig", "tr"]:
|
| 56 |
+
raise ValueError("Restrict must be either 'ig' or 'tr'.")
|
| 57 |
+
|
| 58 |
+
# Build command
|
| 59 |
+
# The CLI tool is typically named 'ANARCI' (uppercase)
|
| 60 |
+
cmd = ["ANARCI"]
|
| 61 |
+
|
| 62 |
+
if sequence:
|
| 63 |
+
cmd.extend(["--sequence", sequence])
|
| 64 |
+
if name:
|
| 65 |
+
cmd.extend(["--name", name])
|
| 66 |
+
|
| 67 |
+
if input_file:
|
| 68 |
+
input_path = Path(input_file)
|
| 69 |
+
if not input_path.exists():
|
| 70 |
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
| 71 |
+
cmd.extend(["--input", str(input_path)])
|
| 72 |
+
|
| 73 |
+
if output_file:
|
| 74 |
+
cmd.extend(["--outfile", output_file])
|
| 75 |
+
|
| 76 |
+
cmd.extend(["--scheme", scheme.lower()])
|
| 77 |
+
|
| 78 |
+
if restrict:
|
| 79 |
+
cmd.extend(["--restrict", restrict.lower()])
|
| 80 |
+
|
| 81 |
+
if species:
|
| 82 |
+
cmd.extend(["--species", species.lower()])
|
| 83 |
+
|
| 84 |
+
if csv:
|
| 85 |
+
cmd.append("--csv")
|
| 86 |
+
|
| 87 |
+
if assign_germline:
|
| 88 |
+
cmd.append("--assign_germline")
|
| 89 |
+
|
| 90 |
+
if verbose:
|
| 91 |
+
cmd.append("--verbose")
|
| 92 |
+
|
| 93 |
+
cmd.extend(["--ncpu", str(ncpu)])
|
| 94 |
+
cmd.extend(["--bit_score_threshold", str(bit_score_threshold)])
|
| 95 |
+
|
| 96 |
+
try:
|
| 97 |
+
# Execute the tool
|
| 98 |
+
result = subprocess.run(
|
| 99 |
+
cmd,
|
| 100 |
+
capture_output=True,
|
| 101 |
+
text=True,
|
| 102 |
+
check=True
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
output_files = []
|
| 106 |
+
if output_file:
|
| 107 |
+
# ANARCI often appends suffixes depending on the format
|
| 108 |
+
# e.g., .csv or .txt
|
| 109 |
+
actual_output = Path(output_file)
|
| 110 |
+
if actual_output.exists():
|
| 111 |
+
output_files.append(str(actual_output))
|
| 112 |
+
else:
|
| 113 |
+
# Check for common suffixes if the exact file isn't found
|
| 114 |
+
for suffix in [".csv", ".txt"]:
|
| 115 |
+
p = Path(f"{output_file}{suffix}")
|
| 116 |
+
if p.exists():
|
| 117 |
+
output_files.append(str(p))
|
| 118 |
+
|
| 119 |
+
return {
|
| 120 |
+
"command_executed": " ".join(cmd),
|
| 121 |
+
"stdout": result.stdout,
|
| 122 |
+
"stderr": result.stderr,
|
| 123 |
+
"output_files": output_files
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
except subprocess.CalledProcessError as e:
|
| 127 |
+
return {
|
| 128 |
+
"command_executed": " ".join(cmd),
|
| 129 |
+
"stdout": e.stdout,
|
| 130 |
+
"stderr": e.stderr,
|
| 131 |
+
"error": f"ANARCI failed with return code {e.returncode}"
|
| 132 |
+
}
|
| 133 |
+
except Exception as e:
|
| 134 |
+
return {
|
| 135 |
+
"command_executed": " ".join(cmd),
|
| 136 |
+
"error": str(e)
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
@mcp.tool()
|
| 140 |
+
def list_anarci_species():
|
| 141 |
+
"""
|
| 142 |
+
Returns a list of species supported by ANARCI for germline alignment.
|
| 143 |
+
Note: This is an informational tool based on ANARCI's standard database.
|
| 144 |
+
"""
|
| 145 |
+
# Standard species supported by ANARCI
|
| 146 |
+
species_list = [
|
| 147 |
+
"human", "mouse", "rat", "rabbit", "rhesus", "pig", "alpaca",
|
| 148 |
+
"dog", "chicken", "cow", "horse", "monkey", "macaque"
|
| 149 |
+
]
|
| 150 |
+
|
| 151 |
+
return {
|
| 152 |
+
"supported_species": species_list,
|
| 153 |
+
"note": "Availability may depend on the specific ANARCI version and installed HMM databases."
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
@mcp.tool()
|
| 157 |
+
def get_anarci_version():
|
| 158 |
+
"""
|
| 159 |
+
Retrieves the version information for the installed ANARCI tool.
|
| 160 |
+
"""
|
| 161 |
+
try:
|
| 162 |
+
# ANARCI doesn't always have a simple --version flag in older versions,
|
| 163 |
+
# but we can try to run it to see the help header or use pip/conda info.
|
| 164 |
+
# Most modern versions support --version.
|
| 165 |
+
result = subprocess.run(
|
| 166 |
+
["ANARCI", "--version"],
|
| 167 |
+
capture_output=True,
|
| 168 |
+
text=True,
|
| 169 |
+
check=False
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
# If --version fails, try to get it from the help output
|
| 173 |
+
if result.returncode != 0:
|
| 174 |
+
result = subprocess.run(
|
| 175 |
+
["ANARCI", "--help"],
|
| 176 |
+
capture_output=True,
|
| 177 |
+
text=True,
|
| 178 |
+
check=False
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
return {
|
| 182 |
+
"stdout": result.stdout,
|
| 183 |
+
"stderr": result.stderr
|
| 184 |
+
}
|
| 185 |
+
except Exception as e:
|
| 186 |
+
return {
|
| 187 |
+
"error": f"Could not determine ANARCI version: {str(e)}"
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_anarci/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-anarci:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-anarci:latest
|
| 7 |
+
container_name: mcp-anarci
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=anarci
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_anarci/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- anarci
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_anarci/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-cellmigration via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-cellmigration -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-cellmigration_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-cellmigration_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-cellmigration_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_server.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Optional, Dict, Any
|
| 5 |
+
|
| 6 |
+
# This is a placeholder for the MCP decorator.
|
| 7 |
+
# In a real MCP environment, this would be provided by the MCP framework.
|
| 8 |
+
class mcp:
|
| 9 |
+
@staticmethod
|
| 10 |
+
def tool():
|
| 11 |
+
def decorator(func):
|
| 12 |
+
return func
|
| 13 |
+
return decorator
|
| 14 |
+
|
| 15 |
+
from mcp.server.fastmcp import FastMCP
|
| 16 |
+
|
| 17 |
+
SERVER_NAME = 'local_bioconductor_cellmigration'
|
| 18 |
+
mcp = FastMCP(SERVER_NAME)
|
| 19 |
+
|
| 20 |
+
@mcp.tool()
|
| 21 |
+
def run_cellmigration_analysis(
|
| 22 |
+
input_file: Path,
|
| 23 |
+
output_dir: Path,
|
| 24 |
+
delta_t: float = 1.0,
|
| 25 |
+
x_microns: float = 1.0,
|
| 26 |
+
y_microns: float = 1.0,
|
| 27 |
+
plot_format: str = "png",
|
| 28 |
+
plot_width: int = 7,
|
| 29 |
+
plot_height: int = 7,
|
| 30 |
+
plot_dpi: int = 300,
|
| 31 |
+
generate_displacement_plot: bool = True,
|
| 32 |
+
generate_speed_plot: bool = True,
|
| 33 |
+
generate_rose_plot: bool = True,
|
| 34 |
+
generate_angle_plot: bool = True,
|
| 35 |
+
generate_straighness_plot: bool = True,
|
| 36 |
+
generate_persistence_ratio_plot: bool = True,
|
| 37 |
+
generate_displacement_ratio_plot: bool = True,
|
| 38 |
+
generate_persistence_plot: bool = True,
|
| 39 |
+
generate_cumulative_plot: bool = True,
|
| 40 |
+
generate_cumulative_hist_plot: bool = True,
|
| 41 |
+
generate_single_cell_plot: bool = True,
|
| 42 |
+
generate_all_cells_plot: bool = True,
|
| 43 |
+
) -> Dict[str, Any]:
|
| 44 |
+
"""
|
| 45 |
+
Performs a complete cell migration analysis using the Bioconductor cellmigration package.
|
| 46 |
+
|
| 47 |
+
This tool reads ImageJ trajectory data, performs a comprehensive analysis,
|
| 48 |
+
and generates a variety of plots and a summary statistics CSV file.
|
| 49 |
+
It requires R and the 'cellmigration' Bioconductor package to be installed.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
input_file: Path to the input data file from ImageJ's manual tracking plugin.
|
| 53 |
+
output_dir: Path to the directory where output files will be saved.
|
| 54 |
+
delta_t: Time interval between frames (dt).
|
| 55 |
+
x_microns: Pixel to micron conversion factor for the x-axis (x.scale).
|
| 56 |
+
y_microns: Pixel to micron conversion factor for the y-axis (y.scale).
|
| 57 |
+
plot_format: The format for the output plots ('png' or 'pdf').
|
| 58 |
+
plot_width: Width of the plots in inches.
|
| 59 |
+
plot_height: Height of the plots in inches.
|
| 60 |
+
plot_dpi: Resolution for PNG plots in dots per inch.
|
| 61 |
+
generate_displacement_plot: Generate a displacement plot.
|
| 62 |
+
generate_speed_plot: Generate a speed plot.
|
| 63 |
+
generate_rose_plot: Generate a rose plot of cell turning angles.
|
| 64 |
+
generate_angle_plot: Generate a plot of cell turning angles.
|
| 65 |
+
generate_straighness_plot: Generate a plot of cell trajectory straightness.
|
| 66 |
+
generate_persistence_ratio_plot: Generate a persistence ratio plot.
|
| 67 |
+
generate_displacement_ratio_plot: Generate a displacement ratio plot.
|
| 68 |
+
generate_persistence_plot: Generate a persistence time plot.
|
| 69 |
+
generate_cumulative_plot: Generate a cumulative plot.
|
| 70 |
+
generate_cumulative_hist_plot: Generate a cumulative plot with a histogram.
|
| 71 |
+
generate_single_cell_plot: Generate a plot for each single cell trajectory.
|
| 72 |
+
generate_all_cells_plot: Generate a plot showing all cell trajectories.
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
A dictionary containing the executed command, stdout, stderr, and a list of output files.
|
| 76 |
+
"""
|
| 77 |
+
# --- Input Validation ---
|
| 78 |
+
if not input_file.is_file():
|
| 79 |
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
| 80 |
+
|
| 81 |
+
if delta_t <= 0:
|
| 82 |
+
raise ValueError("delta_t must be a positive number.")
|
| 83 |
+
if x_microns <= 0 or y_microns <= 0:
|
| 84 |
+
raise ValueError("x_microns and y_microns must be positive numbers.")
|
| 85 |
+
if plot_format not in ["png", "pdf"]:
|
| 86 |
+
raise ValueError("plot_format must be either 'png' or 'pdf'.")
|
| 87 |
+
if plot_width <= 0 or plot_height <= 0 or plot_dpi <= 0:
|
| 88 |
+
raise ValueError("Plot dimensions and DPI must be positive integers.")
|
| 89 |
+
|
| 90 |
+
# --- File Path Handling ---
|
| 91 |
+
try:
|
| 92 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 93 |
+
except OSError as e:
|
| 94 |
+
raise OSError(f"Could not create output directory: {output_dir}. Error: {e}")
|
| 95 |
+
|
| 96 |
+
# --- R Script Generation ---
|
| 97 |
+
# Using absolute paths to avoid issues with working directories
|
| 98 |
+
abs_input_file = input_file.resolve()
|
| 99 |
+
abs_output_dir = output_dir.resolve()
|
| 100 |
+
|
| 101 |
+
# Helper function to generate R plotting code
|
| 102 |
+
def get_plot_code(plot_name: str, plot_function: str) -> str:
|
| 103 |
+
return f"""
|
| 104 |
+
cat("Generating {plot_name} plot...\\n")
|
| 105 |
+
if (plot_format == "png") {{
|
| 106 |
+
png(file.path("{abs_output_dir}", "{plot_name}.png"), width=plot_width, height=plot_height, units="in", res=plot_dpi)
|
| 107 |
+
}} else {{
|
| 108 |
+
pdf(file.path("{abs_output_dir}", "{plot_name}.pdf"), width=plot_width, height=plot_height)
|
| 109 |
+
}}
|
| 110 |
+
{plot_function}(analysis_results)
|
| 111 |
+
dev.off()
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
r_script_content = f"""
|
| 115 |
+
# Load the library, with error handling
|
| 116 |
+
if (!requireNamespace("cellmigration", quietly = TRUE)) {{
|
| 117 |
+
stop("The 'cellmigration' package is not installed. Please install it from Bioconductor.", call. = FALSE)
|
| 118 |
+
}}
|
| 119 |
+
library(cellmigration)
|
| 120 |
+
|
| 121 |
+
# --- Parameters ---
|
| 122 |
+
inputFile <- "{abs_input_file}"
|
| 123 |
+
outputDir <- "{abs_output_dir}"
|
| 124 |
+
dt <- {delta_t}
|
| 125 |
+
x_scale <- {x_microns}
|
| 126 |
+
y_scale <- {y_microns}
|
| 127 |
+
plot_format <- "{plot_format}"
|
| 128 |
+
plot_width <- {plot_width}
|
| 129 |
+
plot_height <- {plot_height}
|
| 130 |
+
plot_dpi <- {plot_dpi}
|
| 131 |
+
|
| 132 |
+
# --- Main Analysis ---
|
| 133 |
+
cat("Reading ImageJ data from:", inputFile, "\\n")
|
| 134 |
+
ij_data <- read.ijdata(inputFile, dt=dt, x.scale=x_scale, y.scale=y_scale)
|
| 135 |
+
|
| 136 |
+
cat("Analyzing trajectories...\\n")
|
| 137 |
+
analysis_results <- analyze.trajectories(ij_data)
|
| 138 |
+
|
| 139 |
+
cat("Saving analysis summary...\\n")
|
| 140 |
+
write.csv(analysis_results$results, file=file.path(outputDir, "analysis_summary.csv"), row.names = FALSE)
|
| 141 |
+
|
| 142 |
+
# --- Plotting ---
|
| 143 |
+
if ({str(generate_displacement_plot).upper()}) {{{get_plot_code('displacement', 'plot.displacement')}}}
|
| 144 |
+
if ({str(generate_speed_plot).upper()}) {{{get_plot_code('speed', 'plot.speed')}}}
|
| 145 |
+
if ({str(generate_rose_plot).upper()}) {{{get_plot_code('rose', 'plot.rose')}}}
|
| 146 |
+
if ({str(generate_angle_plot).upper()}) {{{get_plot_code('angle', 'plot.angle')}}}
|
| 147 |
+
if ({str(generate_straighness_plot).upper()}) {{{get_plot_code('straighness', 'plot.straighness')}}}
|
| 148 |
+
if ({str(generate_persistence_ratio_plot).upper()}) {{{get_plot_code('persistence_ratio', 'plot.persistence.ratio')}}}
|
| 149 |
+
if ({str(generate_displacement_ratio_plot).upper()}) {{{get_plot_code('displacement_ratio', 'plot.displacement.ratio')}}}
|
| 150 |
+
if ({str(generate_persistence_plot).upper()}) {{{get_plot_code('persistence', 'plot.persistence')}}}
|
| 151 |
+
if ({str(generate_cumulative_plot).upper()}) {{{get_plot_code('cumulative', 'plot.cummulative.plot')}}}
|
| 152 |
+
if ({str(generate_cumulative_hist_plot).upper()}) {{{get_plot_code('cumulative_hist', 'plot.cummulative.plot.and.hist')}}}
|
| 153 |
+
if ({str(generate_single_cell_plot).upper()}) {{{get_plot_code('single_cell', 'plot.single.cell')}}}
|
| 154 |
+
if ({str(generate_all_cells_plot).upper()}) {{{get_plot_code('all_cells', 'plot.all.cells')}}}
|
| 155 |
+
|
| 156 |
+
cat("Analysis complete.\\n")
|
| 157 |
+
"""
|
| 158 |
+
|
| 159 |
+
# --- Subprocess Execution ---
|
| 160 |
+
command = []
|
| 161 |
+
stdout_str, stderr_str = "", ""
|
| 162 |
+
output_files = []
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file:
|
| 166 |
+
r_script_file.write(r_script_content)
|
| 167 |
+
r_script_path = r_script_file.name
|
| 168 |
+
|
| 169 |
+
command = ["Rscript", r_script_path]
|
| 170 |
+
process = subprocess.run(
|
| 171 |
+
command,
|
| 172 |
+
capture_output=True,
|
| 173 |
+
text=True,
|
| 174 |
+
check=True,
|
| 175 |
+
)
|
| 176 |
+
stdout_str = process.stdout
|
| 177 |
+
stderr_str = process.stderr
|
| 178 |
+
|
| 179 |
+
# Collect output files
|
| 180 |
+
output_files = [str(p) for p in abs_output_dir.glob('*')]
|
| 181 |
+
|
| 182 |
+
except FileNotFoundError:
|
| 183 |
+
stderr_str = "Error: 'Rscript' command not found. Please ensure R is installed and in your system's PATH."
|
| 184 |
+
return {
|
| 185 |
+
"command_executed": " ".join(command),
|
| 186 |
+
"stdout": stdout_str,
|
| 187 |
+
"stderr": stderr_str,
|
| 188 |
+
"output_files": []
|
| 189 |
+
}
|
| 190 |
+
except subprocess.CalledProcessError as e:
|
| 191 |
+
stderr_str = f"Error executing R script. Return code: {e.returncode}\n"
|
| 192 |
+
stderr_str += f"STDOUT:\n{e.stdout}\n"
|
| 193 |
+
stderr_str += f"STDERR:\n{e.stderr}\n"
|
| 194 |
+
return {
|
| 195 |
+
"command_executed": " ".join(command),
|
| 196 |
+
"stdout": e.stdout,
|
| 197 |
+
"stderr": stderr_str,
|
| 198 |
+
"output_files": []
|
| 199 |
+
}
|
| 200 |
+
finally:
|
| 201 |
+
# Clean up the temporary R script
|
| 202 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 203 |
+
Path(r_script_path).unlink()
|
| 204 |
+
|
| 205 |
+
# --- Structured Result Return ---
|
| 206 |
+
return {
|
| 207 |
+
"command_executed": " ".join(command),
|
| 208 |
+
"stdout": stdout_str,
|
| 209 |
+
"stderr": stderr_str,
|
| 210 |
+
"output_files": output_files
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
if __name__ == "__main__":
|
| 214 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_cellmigration'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-cellmigration:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-cellmigration:latest
|
| 7 |
+
container_name: mcp-bioconductor-cellmigration
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-cellmigration
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-cellmigration
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cellmigration/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-geneplotter/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-glmgampoi via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-glmgampoi -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-glmgampoi_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-glmgampoi_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-glmgampoi_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_server.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List, Dict, Any
|
| 5 |
+
|
| 6 |
+
from mcp.server.fastmcp import FastMCP
|
| 7 |
+
|
| 8 |
+
SERVER_NAME = 'local_bioconductor_glmgampoi'
|
| 9 |
+
mcp = FastMCP(SERVER_NAME)
|
| 10 |
+
|
| 11 |
+
@mcp.tool()
|
| 12 |
+
def fit_glm_gp(
|
| 13 |
+
data_path: str,
|
| 14 |
+
output_rds: str,
|
| 15 |
+
design_formula: str = "~1",
|
| 16 |
+
col_data_path: Optional[str] = None,
|
| 17 |
+
size_factors: str = "normed_sum",
|
| 18 |
+
overdispersion_shrinkage: bool = True,
|
| 19 |
+
on_disk: bool = False,
|
| 20 |
+
) -> Dict[str, Any]:
|
| 21 |
+
"""
|
| 22 |
+
Fit a Gamma-Poisson Generalized Linear Model (GLM) to count data.
|
| 23 |
+
Designed for large datasets like single-cell RNA-seq.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
data_path: Path to the input count matrix (CSV or TSV). Rows are genes, columns are samples.
|
| 27 |
+
output_rds: Path where the fitted model object (RDS format) will be saved.
|
| 28 |
+
design_formula: R-style formula string (e.g., '~ condition').
|
| 29 |
+
col_data_path: Optional path to sample metadata (CSV or TSV). Rows must match columns in data_path.
|
| 30 |
+
size_factors: Method for normalization. Options: 'normed_sum', 'poscounts', 'median', or 'none'.
|
| 31 |
+
overdispersion_shrinkage: Whether to shrink overdispersion estimates towards a global trend.
|
| 32 |
+
on_disk: If True, uses HDF5-backed matrices to minimize memory usage for very large datasets.
|
| 33 |
+
"""
|
| 34 |
+
# Input validation
|
| 35 |
+
input_p = Path(data_path)
|
| 36 |
+
if not input_p.exists():
|
| 37 |
+
raise FileNotFoundError(f"Input data file not found: {data_path}")
|
| 38 |
+
|
| 39 |
+
output_p = Path(output_rds)
|
| 40 |
+
output_p.parent.mkdir(parents=True, exist_ok=True)
|
| 41 |
+
|
| 42 |
+
col_data_arg = "NULL"
|
| 43 |
+
if col_data_path:
|
| 44 |
+
col_p = Path(col_data_path)
|
| 45 |
+
if not col_p.exists():
|
| 46 |
+
raise FileNotFoundError(f"Metadata file not found: {col_data_path}")
|
| 47 |
+
col_data_arg = f"read_data('{col_data_path}')"
|
| 48 |
+
|
| 49 |
+
# Map size_factors string to R logic
|
| 50 |
+
sf_val = f"'{size_factors}'"
|
| 51 |
+
if size_factors.lower() == "none":
|
| 52 |
+
sf_val = "FALSE"
|
| 53 |
+
|
| 54 |
+
# Construct R script
|
| 55 |
+
r_script = f"""
|
| 56 |
+
library(glmGamPoi)
|
| 57 |
+
|
| 58 |
+
read_data <- function(path) {{
|
| 59 |
+
if (grepl('\\\\.tsv$', path, ignore.case = TRUE)) {{
|
| 60 |
+
return(read.delim(path, row.names = 1, check.names = FALSE))
|
| 61 |
+
}} else {{
|
| 62 |
+
return(read.csv(path, row.names = 1, check.names = FALSE))
|
| 63 |
+
}}
|
| 64 |
+
}}
|
| 65 |
+
|
| 66 |
+
counts <- as.matrix(read_data('{data_path}'))
|
| 67 |
+
col_data <- {col_data_arg}
|
| 68 |
+
|
| 69 |
+
fit <- glm_gp(
|
| 70 |
+
data = counts,
|
| 71 |
+
design = as.formula('{design_formula}'),
|
| 72 |
+
col_data = col_data,
|
| 73 |
+
size_factors = {sf_val},
|
| 74 |
+
overdispersion_shrinkage = {str(overdispersion_shrinkage).upper()},
|
| 75 |
+
on_disk = {str(on_disk).upper()}
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
saveRDS(fit, '{output_rds}')
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
process = subprocess.run(
|
| 83 |
+
["Rscript", "-e", r_script],
|
| 84 |
+
capture_output=True,
|
| 85 |
+
text=True,
|
| 86 |
+
check=True
|
| 87 |
+
)
|
| 88 |
+
return {
|
| 89 |
+
"command_executed": f"glm_gp on {data_path}",
|
| 90 |
+
"stdout": process.stdout,
|
| 91 |
+
"stderr": process.stderr,
|
| 92 |
+
"output_files": [output_rds]
|
| 93 |
+
}
|
| 94 |
+
except subprocess.CalledProcessError as e:
|
| 95 |
+
return {
|
| 96 |
+
"error": "R execution failed",
|
| 97 |
+
"stdout": e.stdout,
|
| 98 |
+
"stderr": e.stderr,
|
| 99 |
+
"command_executed": e.cmd
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
@mcp.tool()
|
| 103 |
+
def test_differential_expression(
|
| 104 |
+
fit_rds_path: str,
|
| 105 |
+
output_csv: str,
|
| 106 |
+
contrast: Optional[str] = None,
|
| 107 |
+
reduced_design: Optional[str] = None,
|
| 108 |
+
) -> Dict[str, Any]:
|
| 109 |
+
"""
|
| 110 |
+
Perform differential expression (DE) testing using a fitted glmGamPoi model.
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
fit_rds_path: Path to the RDS file generated by fit_glm_gp.
|
| 114 |
+
output_csv: Path where the DE results (CSV) will be saved.
|
| 115 |
+
contrast: String specifying the contrast to test (e.g., 'condition_B - condition_A').
|
| 116 |
+
reduced_design: Optional R-style formula for a reduced model to perform a Likelihood Ratio Test.
|
| 117 |
+
"""
|
| 118 |
+
fit_p = Path(fit_rds_path)
|
| 119 |
+
if not fit_p.exists():
|
| 120 |
+
raise FileNotFoundError(f"Fitted model file not found: {fit_rds_path}")
|
| 121 |
+
|
| 122 |
+
output_p = Path(output_csv)
|
| 123 |
+
output_p.parent.mkdir(parents=True, exist_ok=True)
|
| 124 |
+
|
| 125 |
+
contrast_arg = "NULL"
|
| 126 |
+
if contrast:
|
| 127 |
+
# If it looks like a simple string, wrap in quotes; if it's complex, R handles it
|
| 128 |
+
contrast_arg = f"'{contrast}'"
|
| 129 |
+
|
| 130 |
+
reduced_arg = "NULL"
|
| 131 |
+
if reduced_design:
|
| 132 |
+
reduced_arg = f"as.formula('{reduced_design}')"
|
| 133 |
+
|
| 134 |
+
r_script = f"""
|
| 135 |
+
library(glmGamPoi)
|
| 136 |
+
fit <- readRDS('{fit_rds_path}')
|
| 137 |
+
res <- test_de(
|
| 138 |
+
fit,
|
| 139 |
+
contrast = {contrast_arg},
|
| 140 |
+
reduced_design = {reduced_arg}
|
| 141 |
+
)
|
| 142 |
+
write.csv(res, '{output_csv}', row.names = TRUE)
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
try:
|
| 146 |
+
process = subprocess.run(
|
| 147 |
+
["Rscript", "-e", r_script],
|
| 148 |
+
capture_output=True,
|
| 149 |
+
text=True,
|
| 150 |
+
check=True
|
| 151 |
+
)
|
| 152 |
+
return {
|
| 153 |
+
"command_executed": f"test_de on {fit_rds_path}",
|
| 154 |
+
"stdout": process.stdout,
|
| 155 |
+
"stderr": process.stderr,
|
| 156 |
+
"output_files": [output_csv]
|
| 157 |
+
}
|
| 158 |
+
except subprocess.CalledProcessError as e:
|
| 159 |
+
return {
|
| 160 |
+
"error": "R execution failed",
|
| 161 |
+
"stdout": e.stdout,
|
| 162 |
+
"stderr": e.stderr,
|
| 163 |
+
"command_executed": e.cmd
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
@mcp.tool()
|
| 167 |
+
def get_overdispersion_estimates(
|
| 168 |
+
fit_rds_path: str,
|
| 169 |
+
output_csv: str
|
| 170 |
+
) -> Dict[str, Any]:
|
| 171 |
+
"""
|
| 172 |
+
Extract overdispersion estimates for each gene from a fitted glmGamPoi model.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
fit_rds_path: Path to the RDS file generated by fit_glm_gp.
|
| 176 |
+
output_csv: Path where the overdispersion estimates (CSV) will be saved.
|
| 177 |
+
"""
|
| 178 |
+
fit_p = Path(fit_rds_path)
|
| 179 |
+
if not fit_p.exists():
|
| 180 |
+
raise FileNotFoundError(f"Fitted model file not found: {fit_rds_path}")
|
| 181 |
+
|
| 182 |
+
r_script = f"""
|
| 183 |
+
library(glmGamPoi)
|
| 184 |
+
fit <- readRDS('{fit_rds_path}')
|
| 185 |
+
# Extract overdispersion and gene names
|
| 186 |
+
df <- data.frame(
|
| 187 |
+
gene = rownames(fit$Beta),
|
| 188 |
+
overdispersion = fit$overdispersions
|
| 189 |
+
)
|
| 190 |
+
write.csv(df, '{output_csv}', row.names = FALSE)
|
| 191 |
+
"""
|
| 192 |
+
|
| 193 |
+
try:
|
| 194 |
+
process = subprocess.run(
|
| 195 |
+
["Rscript", "-e", r_script],
|
| 196 |
+
capture_output=True,
|
| 197 |
+
text=True,
|
| 198 |
+
check=True
|
| 199 |
+
)
|
| 200 |
+
return {
|
| 201 |
+
"command_executed": f"Extract overdispersion from {fit_rds_path}",
|
| 202 |
+
"stdout": process.stdout,
|
| 203 |
+
"stderr": process.stderr,
|
| 204 |
+
"output_files": [output_csv]
|
| 205 |
+
}
|
| 206 |
+
except subprocess.CalledProcessError as e:
|
| 207 |
+
return {
|
| 208 |
+
"error": "R execution failed",
|
| 209 |
+
"stdout": e.stdout,
|
| 210 |
+
"stderr": e.stderr,
|
| 211 |
+
"command_executed": e.cmd
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
@mcp.tool()
|
| 215 |
+
def predict_glm_gp(
|
| 216 |
+
fit_rds_path: str,
|
| 217 |
+
output_csv: str,
|
| 218 |
+
newdata_path: Optional[str] = None,
|
| 219 |
+
type: str = "link"
|
| 220 |
+
) -> Dict[str, Any]:
|
| 221 |
+
"""
|
| 222 |
+
Predict values (e.g., log-counts or counts) from a fitted glmGamPoi model.
|
| 223 |
+
|
| 224 |
+
Args:
|
| 225 |
+
fit_rds_path: Path to the RDS file generated by fit_glm_gp.
|
| 226 |
+
output_csv: Path where the predictions (CSV) will be saved.
|
| 227 |
+
newdata_path: Optional path to new sample metadata (CSV/TSV). If NULL, uses original data.
|
| 228 |
+
type: Type of prediction. 'link' (log-scale) or 'response' (original count scale).
|
| 229 |
+
"""
|
| 230 |
+
fit_p = Path(fit_rds_path)
|
| 231 |
+
if not fit_p.exists():
|
| 232 |
+
raise FileNotFoundError(f"Fitted model file not found: {fit_rds_path}")
|
| 233 |
+
|
| 234 |
+
newdata_arg = "NULL"
|
| 235 |
+
if newdata_path:
|
| 236 |
+
if not Path(newdata_path).exists():
|
| 237 |
+
raise FileNotFoundError(f"New data file not found: {newdata_path}")
|
| 238 |
+
newdata_arg = f"read.csv('{newdata_path}', row.names = 1)"
|
| 239 |
+
|
| 240 |
+
r_script = f"""
|
| 241 |
+
library(glmGamPoi)
|
| 242 |
+
fit <- readRDS('{fit_rds_path}')
|
| 243 |
+
newdata <- {newdata_arg}
|
| 244 |
+
preds <- predict(fit, newdata = newdata, type = '{type}')
|
| 245 |
+
write.csv(preds, '{output_csv}')
|
| 246 |
+
"""
|
| 247 |
+
|
| 248 |
+
try:
|
| 249 |
+
process = subprocess.run(
|
| 250 |
+
["Rscript", "-e", r_script],
|
| 251 |
+
capture_output=True,
|
| 252 |
+
text=True,
|
| 253 |
+
check=True
|
| 254 |
+
)
|
| 255 |
+
return {
|
| 256 |
+
"command_executed": f"predict on {fit_rds_path}",
|
| 257 |
+
"stdout": process.stdout,
|
| 258 |
+
"stderr": process.stderr,
|
| 259 |
+
"output_files": [output_csv]
|
| 260 |
+
}
|
| 261 |
+
except subprocess.CalledProcessError as e:
|
| 262 |
+
return {
|
| 263 |
+
"error": "R execution failed",
|
| 264 |
+
"stdout": e.stdout,
|
| 265 |
+
"stderr": e.stderr,
|
| 266 |
+
"command_executed": e.cmd
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_glmgampoi'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-glmgampoi
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-keggrest/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-keggrest via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-keggrest -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-keggrest_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-keggrest_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-keggrest_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-keggrest/app/bioconductor-keggrest_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-keggrest/app/bioconductor-keggrest_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_keggrest'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-keggrest/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-keggrest:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-keggrest:latest
|
| 7 |
+
container_name: mcp-bioconductor-keggrest
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-keggrest
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-keggrest/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-keggrest
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-keggrest/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-scran/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-scran via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-scran -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-scran_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-scran_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-scran_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-scran/app/bioconductor-scran_server.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# MCP decorator is omitted as per instructions.
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
SERVER_NAME = 'local_bioconductor_scran'
|
| 10 |
+
mcp = FastMCP(SERVER_NAME)
|
| 11 |
+
|
| 12 |
+
@mcp.tool()
|
| 13 |
+
|
| 14 |
+
def scran_normalize(
|
| 15 |
+
input_sce_rds: Path,
|
| 16 |
+
output_sce_rds: Path,
|
| 17 |
+
clusters_csv: Optional[Path] = None,
|
| 18 |
+
min_mean: float = 0.1,
|
| 19 |
+
bpparam_workers: int = 1,
|
| 20 |
+
) -> dict:
|
| 21 |
+
"""
|
| 22 |
+
Performs normalization of single-cell RNA-seq data using scran's deconvolution method.
|
| 23 |
+
|
| 24 |
+
This tool serves as a wrapper for a hypothetical R script that utilizes the
|
| 25 |
+
'scran' Bioconductor package. 'scran' itself is an R library without a native
|
| 26 |
+
command-line interface. This function assumes an R script ('run_scran.R')
|
| 27 |
+
exists that can take command-line arguments to perform normalization.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
input_sce_rds (Path): Path to the input SingleCellExperiment object saved as an RDS file.
|
| 31 |
+
This object should contain the raw count matrix.
|
| 32 |
+
output_sce_rds (Path): Path to save the output SingleCellExperiment object (in RDS format)
|
| 33 |
+
with the calculated size factors added.
|
| 34 |
+
clusters_csv (Optional[Path]): Path to a CSV file containing cell cluster assignments.
|
| 35 |
+
If provided, normalization is performed per cluster.
|
| 36 |
+
The CSV should have two columns: cell_barcode, cluster_id.
|
| 37 |
+
min_mean (float): The minimum mean expression for a gene to be used for normalization.
|
| 38 |
+
Corresponds to the 'min.mean' parameter in scran's computeSumFactors.
|
| 39 |
+
Defaults to 0.1.
|
| 40 |
+
bpparam_workers (int): Number of worker processes to use for parallel computation.
|
| 41 |
+
Defaults to 1 (no parallelization).
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
dict: A dictionary containing the executed command, stdout, stderr, and a list
|
| 45 |
+
of output files.
|
| 46 |
+
"""
|
| 47 |
+
# --- Input Validation ---
|
| 48 |
+
if not input_sce_rds.is_file():
|
| 49 |
+
raise FileNotFoundError(f"Input RDS file not found: {input_sce_rds}")
|
| 50 |
+
if clusters_csv and not clusters_csv.is_file():
|
| 51 |
+
raise FileNotFoundError(f"Clusters CSV file not found: {clusters_csv}")
|
| 52 |
+
if min_mean < 0:
|
| 53 |
+
raise ValueError("min_mean must be a non-negative number.")
|
| 54 |
+
if bpparam_workers < 1:
|
| 55 |
+
raise ValueError("bpparam_workers must be at least 1.")
|
| 56 |
+
|
| 57 |
+
# --- Command Construction ---
|
| 58 |
+
# This assumes a hypothetical R script 'run_scran.R' that we have created
|
| 59 |
+
# to expose scran functionality to the command line.
|
| 60 |
+
cmd = [
|
| 61 |
+
"Rscript",
|
| 62 |
+
"run_scran.R",
|
| 63 |
+
"normalize",
|
| 64 |
+
"--input",
|
| 65 |
+
str(input_sce_rds),
|
| 66 |
+
"--output",
|
| 67 |
+
str(output_sce_rds),
|
| 68 |
+
"--min-mean",
|
| 69 |
+
str(min_mean),
|
| 70 |
+
"--workers",
|
| 71 |
+
str(bpparam_workers),
|
| 72 |
+
]
|
| 73 |
+
|
| 74 |
+
if clusters_csv:
|
| 75 |
+
cmd.extend(["--clusters", str(clusters_csv)])
|
| 76 |
+
|
| 77 |
+
# --- Subprocess Execution ---
|
| 78 |
+
command_executed = " ".join(cmd)
|
| 79 |
+
try:
|
| 80 |
+
# In a real scenario, the R script would need to be in the PATH or specified with its full path.
|
| 81 |
+
# For this example, we assume it's available.
|
| 82 |
+
result = subprocess.run(
|
| 83 |
+
cmd,
|
| 84 |
+
capture_output=True,
|
| 85 |
+
text=True,
|
| 86 |
+
check=True,
|
| 87 |
+
)
|
| 88 |
+
stdout = result.stdout
|
| 89 |
+
stderr = result.stderr
|
| 90 |
+
except FileNotFoundError:
|
| 91 |
+
# This error would be raised if 'Rscript' or 'run_scran.R' is not found.
|
| 92 |
+
return {
|
| 93 |
+
"command_executed": command_executed,
|
| 94 |
+
"stdout": "",
|
| 95 |
+
"stderr": "Error: 'Rscript' or the wrapper 'run_scran.R' not found. Please ensure R and the necessary wrapper script are in the system's PATH.",
|
| 96 |
+
"output_files": [],
|
| 97 |
+
}
|
| 98 |
+
except subprocess.CalledProcessError as e:
|
| 99 |
+
return {
|
| 100 |
+
"command_executed": command_executed,
|
| 101 |
+
"stdout": e.stdout,
|
| 102 |
+
"stderr": e.stderr,
|
| 103 |
+
"output_files": [],
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
# --- Return Structured Output ---
|
| 107 |
+
return {
|
| 108 |
+
"command_executed": command_executed,
|
| 109 |
+
"stdout": stdout,
|
| 110 |
+
"stderr": stderr,
|
| 111 |
+
"output_files": [str(output_sce_rds)],
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-scran/app/bioconductor-scran_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scran/app/bioconductor-scran_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_scran'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-scran/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-scran/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-scran:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-scran:latest
|
| 7 |
+
container_name: mcp-bioconductor-scran
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-scran
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-scran/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-scran
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-scran/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-spari/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-spari via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-spari -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-spari_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-spari_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-spari_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spari/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-spari:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-spari:latest
|
| 7 |
+
container_name: mcp-bioconductor-spari
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-spari
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spari/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-spari
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spari/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-spatialcpie via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-spatialcpie -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY bioconductor-spatialcpie_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-spatialcpie_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-spatialcpie_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_server.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Optional, Dict, Any
|
| 4 |
+
|
| 5 |
+
# Assume @mcp.tool is defined elsewhere or imported.
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
SERVER_NAME = 'local_bioconductor_spatialcpie'
|
| 10 |
+
mcp = FastMCP(SERVER_NAME)
|
| 11 |
+
|
| 12 |
+
@mcp.tool()
|
| 13 |
+
def run_rscript(
|
| 14 |
+
script_file: Optional[Path] = None,
|
| 15 |
+
expressions: Optional[List[str]] = None,
|
| 16 |
+
script_args: Optional[List[str]] = None,
|
| 17 |
+
help_flag: bool = False,
|
| 18 |
+
version_flag: bool = False,
|
| 19 |
+
verbose: bool = False,
|
| 20 |
+
default_packages: Optional[str] = None,
|
| 21 |
+
save_workspace: bool = False,
|
| 22 |
+
no_environ: bool = False,
|
| 23 |
+
no_site_file: bool = False,
|
| 24 |
+
no_init_file: bool = False,
|
| 25 |
+
restore_workspace: bool = False,
|
| 26 |
+
vanilla: bool = False,
|
| 27 |
+
) -> Dict[str, Any]:
|
| 28 |
+
"""
|
| 29 |
+
Executes an R script or R expressions using the Rscript command-line tool.
|
| 30 |
+
|
| 31 |
+
This tool provides an interface to the Rscript command, allowing users to run
|
| 32 |
+
R code from the command line. It supports running either a specified R script
|
| 33 |
+
file or direct R expressions. Various options are available to control the
|
| 34 |
+
R session environment, such as package loading, workspace saving/restoring,
|
| 35 |
+
and environment file handling.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
script_file: Path to an R script file to be executed. Mutually exclusive with 'expressions'.
|
| 39 |
+
expressions: A list of R expressions (strings) to be executed. Mutually exclusive with 'script_file'.
|
| 40 |
+
script_args: A list of arguments (strings) to be passed to the R script itself.
|
| 41 |
+
help_flag: If True, print usage information and exit.
|
| 42 |
+
version_flag: If True, print Rscript version information and exit.
|
| 43 |
+
verbose: If True, print information on progress during execution.
|
| 44 |
+
default_packages: A comma-separated list of package names to load by default, or 'NULL'.
|
| 45 |
+
save_workspace: If True, save the workspace at the end of the session.
|
| 46 |
+
no_environ: If True, do not read the site and user environment files.
|
| 47 |
+
no_site_file: If True, do not read the site-wide Rprofile.
|
| 48 |
+
no_init_file: If True, do not read the user R profile.
|
| 49 |
+
restore_workspace: If True, restore previously saved objects at startup.
|
| 50 |
+
vanilla: If True, combine --no-save, --no-restore, --no-site-file,
|
| 51 |
+
--no-init-file, and --no-environ.
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
A dictionary containing the command executed, stdout, stderr, and any
|
| 55 |
+
output files (though Rscript itself doesn't typically produce named
|
| 56 |
+
output files directly from the command line).
|
| 57 |
+
"""
|
| 58 |
+
# Input validation
|
| 59 |
+
if script_file and expressions:
|
| 60 |
+
raise ValueError("Cannot specify both 'script_file' and 'expressions'. Please choose one.")
|
| 61 |
+
if not script_file and not expressions and not help_flag and not version_flag:
|
| 62 |
+
raise ValueError(
|
| 63 |
+
"Either 'script_file' or 'expressions' must be provided, "
|
| 64 |
+
"unless '--help' or '--version' is requested."
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
if script_file:
|
| 68 |
+
if not script_file.exists():
|
| 69 |
+
raise FileNotFoundError(f"R script file not found: {script_file}")
|
| 70 |
+
if not script_file.is_file():
|
| 71 |
+
raise ValueError(f"Provided path is not a file: {script_file}")
|
| 72 |
+
|
| 73 |
+
command = ["Rscript"]
|
| 74 |
+
|
| 75 |
+
# Add Rscript options
|
| 76 |
+
if help_flag:
|
| 77 |
+
command.append("--help")
|
| 78 |
+
if version_flag:
|
| 79 |
+
command.append("--version")
|
| 80 |
+
if verbose:
|
| 81 |
+
command.append("--verbose")
|
| 82 |
+
if default_packages is not None:
|
| 83 |
+
command.append(f"--default-packages={default_packages}")
|
| 84 |
+
if save_workspace:
|
| 85 |
+
command.append("--save")
|
| 86 |
+
if no_environ:
|
| 87 |
+
command.append("--no-environ")
|
| 88 |
+
if no_site_file:
|
| 89 |
+
command.append("--no-site-file")
|
| 90 |
+
if no_init_file:
|
| 91 |
+
command.append("--no-init-file")
|
| 92 |
+
if restore_workspace:
|
| 93 |
+
command.append("--restore")
|
| 94 |
+
if vanilla:
|
| 95 |
+
command.append("--vanilla")
|
| 96 |
+
|
| 97 |
+
# Add expressions or script file
|
| 98 |
+
if expressions:
|
| 99 |
+
for expr in expressions:
|
| 100 |
+
command.extend(["-e", expr])
|
| 101 |
+
elif script_file:
|
| 102 |
+
command.append(str(script_file))
|
| 103 |
+
|
| 104 |
+
# Add arguments to the R script itself
|
| 105 |
+
if script_args:
|
| 106 |
+
command.extend(script_args)
|
| 107 |
+
|
| 108 |
+
command_executed = " ".join(command)
|
| 109 |
+
stdout = ""
|
| 110 |
+
stderr = ""
|
| 111 |
+
output_files: List[Path] = []
|
| 112 |
+
|
| 113 |
+
try:
|
| 114 |
+
process = subprocess.run(
|
| 115 |
+
command,
|
| 116 |
+
capture_output=True,
|
| 117 |
+
text=True,
|
| 118 |
+
check=True
|
| 119 |
+
)
|
| 120 |
+
stdout = process.stdout
|
| 121 |
+
stderr = process.stderr
|
| 122 |
+
except subprocess.CalledProcessError as e:
|
| 123 |
+
return {
|
| 124 |
+
"command_executed": command_executed,
|
| 125 |
+
"stdout": e.stdout,
|
| 126 |
+
"stderr": e.stderr,
|
| 127 |
+
"error": f"Rscript execution failed with exit code {e.returncode}: {e}",
|
| 128 |
+
"returncode": e.returncode,
|
| 129 |
+
"output_files": output_files,
|
| 130 |
+
}
|
| 131 |
+
except FileNotFoundError:
|
| 132 |
+
return {
|
| 133 |
+
"command_executed": command_executed,
|
| 134 |
+
"stdout": "",
|
| 135 |
+
"stderr": "Rscript command not found. Please ensure R is installed and in your system's PATH.",
|
| 136 |
+
"error": "Rscript executable not found.",
|
| 137 |
+
"returncode": 127,
|
| 138 |
+
"output_files": output_files,
|
| 139 |
+
}
|
| 140 |
+
except Exception as e:
|
| 141 |
+
return {
|
| 142 |
+
"command_executed": command_executed,
|
| 143 |
+
"stdout": stdout,
|
| 144 |
+
"stderr": stderr,
|
| 145 |
+
"error": f"An unexpected error occurred: {e}",
|
| 146 |
+
"returncode": 1, # Generic error code
|
| 147 |
+
"output_files": output_files,
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
return {
|
| 151 |
+
"command_executed": command_executed,
|
| 152 |
+
"stdout": stdout,
|
| 153 |
+
"stderr": stderr,
|
| 154 |
+
"output_files": output_files,
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if __name__ == "__main__":
|
| 158 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_spatialcpie'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-spatialcpie:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-spatialcpie:latest
|
| 7 |
+
container_name: mcp-bioconductor-spatialcpie
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-spatialcpie
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-spatialcpie
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-spatialcpie/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-spoon/app/bioconductor-spoon_server.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
import shutil
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List
|
| 6 |
+
|
| 7 |
+
# MCP decorator would be here in a real implementation
|
| 8 |
+
from mcp.server.fastmcp import FastMCP
|
| 9 |
+
|
| 10 |
+
SERVER_NAME = 'local_bioconductor_spoon'
|
| 11 |
+
mcp = FastMCP(SERVER_NAME)
|
| 12 |
+
|
| 13 |
+
@mcp.tool()
|
| 14 |
+
def run_spoon(
|
| 15 |
+
input_spe: Path,
|
| 16 |
+
output_spe: Path,
|
| 17 |
+
assay_name: str = "counts",
|
| 18 |
+
n_threads: int = 1,
|
| 19 |
+
) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Runs the spoon workflow to address the mean-variance relationship in spatial transcriptomics data.
|
| 22 |
+
|
| 23 |
+
This tool takes a SpatialExperiment object (in .rds format), computes precision weights
|
| 24 |
+
and deviance residuals using Empirical Bayes techniques, and adds them to the object.
|
| 25 |
+
The output is the modified SpatialExperiment object, saved to a new .rds file.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
input_spe: Path to the input SpatialExperiment object saved as an RDS file.
|
| 29 |
+
output_spe: Path to save the output SpatialExperiment object as an RDS file.
|
| 30 |
+
assay_name: The name of the assay to use from the input object.
|
| 31 |
+
n_threads: The number of threads to use for parallel processing.
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
A dictionary containing the command executed, stdout, stderr, and a list of output files.
|
| 35 |
+
"""
|
| 36 |
+
# 1. Input Validation
|
| 37 |
+
if not input_spe.is_file():
|
| 38 |
+
raise FileNotFoundError(f"Input file not found: {input_spe}")
|
| 39 |
+
if n_threads < 1:
|
| 40 |
+
raise ValueError("n_threads must be a positive integer.")
|
| 41 |
+
|
| 42 |
+
# Ensure output directory exists
|
| 43 |
+
output_spe.parent.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
|
| 45 |
+
# 2. R Script Generation
|
| 46 |
+
# This R script acts as a command-line wrapper for the spoon::run_spoon function.
|
| 47 |
+
r_script_content = f"""
|
| 48 |
+
# Ensure required R packages are installed and load them
|
| 49 |
+
if (!requireNamespace("optparse", quietly = TRUE)) stop("R package 'optparse' is required.")
|
| 50 |
+
if (!requireNamespace("spoon", quietly = TRUE)) stop("R package 'spoon' is required.")
|
| 51 |
+
if (!requireNamespace("SpatialExperiment", quietly = TRUE)) stop("R package 'SpatialExperiment' is required.")
|
| 52 |
+
if (!requireNamespace("BiocParallel", quietly = TRUE)) stop("R package 'BiocParallel' is required.")
|
| 53 |
+
|
| 54 |
+
library(optparse)
|
| 55 |
+
library(spoon)
|
| 56 |
+
library(SpatialExperiment)
|
| 57 |
+
library(BiocParallel)
|
| 58 |
+
|
| 59 |
+
# Define and parse command-line options
|
| 60 |
+
option_list <- list(
|
| 61 |
+
make_option(c("-i", "--input"), type="character", default=NULL,
|
| 62 |
+
help="Input SpatialExperiment RDS file", metavar="character"),
|
| 63 |
+
make_option(c("-o", "--output"), type="character", default=NULL,
|
| 64 |
+
help="Output SpatialExperiment RDS file", metavar="character"),
|
| 65 |
+
make_option(c("-a", "--assay_name"), type="character", default="counts",
|
| 66 |
+
help="Name of the assay to use [default= %default]", metavar="character"),
|
| 67 |
+
make_option(c("-n", "--n_threads"), type="integer", default=1,
|
| 68 |
+
help="Number of threads for parallel processing [default= %default]", metavar="integer")
|
| 69 |
+
)
|
| 70 |
+
opt_parser <- OptionParser(option_list=option_list)
|
| 71 |
+
opt <- parse_args(opt_parser)
|
| 72 |
+
|
| 73 |
+
# Validate required arguments
|
| 74 |
+
if (is.null(opt$input) || is.null(opt$output)){{
|
| 75 |
+
print_help(opt_parser)
|
| 76 |
+
stop("Input and output files must be supplied.", call.=FALSE)
|
| 77 |
+
}}
|
| 78 |
+
|
| 79 |
+
# Set up parallel processing backend
|
| 80 |
+
if (opt$n_threads > 1) {{
|
| 81 |
+
bpparam <- MulticoreParam(workers = opt$n_threads)
|
| 82 |
+
}} else {{
|
| 83 |
+
bpparam <- SerialParam()
|
| 84 |
+
}}
|
| 85 |
+
|
| 86 |
+
# Main workflow
|
| 87 |
+
message("Reading input SpatialExperiment object from: ", opt$input)
|
| 88 |
+
spe <- readRDS(opt$input)
|
| 89 |
+
|
| 90 |
+
message("Running spoon workflow with assay '", opt$assay_name, "' and ", opt$n_threads, " thread(s)...")
|
| 91 |
+
spe_processed <- tryCatch({{
|
| 92 |
+
run_spoon(
|
| 93 |
+
spe,
|
| 94 |
+
assay_name = opt$assay_name,
|
| 95 |
+
BPPARAM = bpparam
|
| 96 |
+
)
|
| 97 |
+
}}, error = function(e) {{
|
| 98 |
+
stop("Error during spoon::run_spoon execution: ", e$message)
|
| 99 |
+
}})
|
| 100 |
+
message("Spoon workflow completed successfully.")
|
| 101 |
+
|
| 102 |
+
message("Saving processed SpatialExperiment object to: ", opt$output)
|
| 103 |
+
saveRDS(spe_processed, file = opt$output)
|
| 104 |
+
|
| 105 |
+
message("Done.")
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
# 3. Subprocess Execution
|
| 109 |
+
# Use a temporary file for the R script
|
| 110 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_r_script:
|
| 111 |
+
tmp_r_script.write(r_script_content)
|
| 112 |
+
r_script_path = Path(tmp_r_script.name)
|
| 113 |
+
|
| 114 |
+
# Check for Rscript executable
|
| 115 |
+
if not shutil.which("Rscript"):
|
| 116 |
+
r_script_path.unlink()
|
| 117 |
+
raise RuntimeError("Rscript executable not found in PATH. R is required to run this tool.")
|
| 118 |
+
|
| 119 |
+
cmd = [
|
| 120 |
+
"Rscript",
|
| 121 |
+
str(r_script_path),
|
| 122 |
+
"--input", str(input_spe),
|
| 123 |
+
"--output", str(output_spe),
|
| 124 |
+
"--assay_name", assay_name,
|
| 125 |
+
"--n_threads", str(n_threads),
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
command_executed = " ".join(cmd)
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
process = subprocess.run(
|
| 132 |
+
cmd,
|
| 133 |
+
capture_output=True,
|
| 134 |
+
text=True,
|
| 135 |
+
check=True,
|
| 136 |
+
)
|
| 137 |
+
stdout = process.stdout
|
| 138 |
+
stderr = process.stderr
|
| 139 |
+
except subprocess.CalledProcessError as e:
|
| 140 |
+
raise RuntimeError(
|
| 141 |
+
f"R script execution failed with exit code {e.returncode}.\n"
|
| 142 |
+
f"Command: {command_executed}\n"
|
| 143 |
+
f"Stdout: {e.stdout}\n"
|
| 144 |
+
f"Stderr: {e.stderr}"
|
| 145 |
+
) from e
|
| 146 |
+
finally:
|
| 147 |
+
# Ensure the temporary script is always cleaned up
|
| 148 |
+
if r_script_path.exists():
|
| 149 |
+
r_script_path.unlink()
|
| 150 |
+
|
| 151 |
+
# 4. Structured Result Return
|
| 152 |
+
return {
|
| 153 |
+
"command_executed": command_executed,
|
| 154 |
+
"stdout": stdout,
|
| 155 |
+
"stderr": stderr,
|
| 156 |
+
"output_files": [str(output_spe)]
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-spoon/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-spoon
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-statial/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-statial via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-statial -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-statial_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-statial_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-statial_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-statial/app/bioconductor-statial_server.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Optional, List, Union
|
| 4 |
+
import textwrap
|
| 5 |
+
|
| 6 |
+
from mcp.server.fastmcp import FastMCP
|
| 7 |
+
|
| 8 |
+
SERVER_NAME = 'local_bioconductor_statial'
|
| 9 |
+
mcp = FastMCP(SERVER_NAME)
|
| 10 |
+
|
| 11 |
+
@mcp.tool()
|
| 12 |
+
def statial_calc_interactions(
|
| 13 |
+
input_rds: str,
|
| 14 |
+
output_rds: str,
|
| 15 |
+
image_col: str,
|
| 16 |
+
cell_type_col: str,
|
| 17 |
+
x_col: str = "x",
|
| 18 |
+
y_col: str = "y",
|
| 19 |
+
method: str = "radius",
|
| 20 |
+
radius: float = 100.0,
|
| 21 |
+
n_neighbors: int = 10,
|
| 22 |
+
):
|
| 23 |
+
"""
|
| 24 |
+
Calculates the localization of cell types (interactions) for each image in a SingleCellExperiment object.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
input_rds: Path to the input SingleCellExperiment object saved as an RDS file.
|
| 28 |
+
output_rds: Path where the resulting interactions data frame will be saved as an RDS file.
|
| 29 |
+
image_col: Column name in colData representing unique image identifiers.
|
| 30 |
+
cell_type_col: Column name in colData representing cell types.
|
| 31 |
+
x_col: Column name for the X coordinate.
|
| 32 |
+
y_col: Column name for the Y coordinate.
|
| 33 |
+
method: Method to define proximity ('radius' or 'knn').
|
| 34 |
+
radius: The radius to use if method is 'radius'.
|
| 35 |
+
n_neighbors: The number of neighbors to use if method is 'knn'.
|
| 36 |
+
"""
|
| 37 |
+
input_path = Path(input_rds)
|
| 38 |
+
output_path = Path(output_rds)
|
| 39 |
+
|
| 40 |
+
if not input_path.exists():
|
| 41 |
+
return {"error": f"Input file {input_rds} does not exist."}
|
| 42 |
+
|
| 43 |
+
if method not in ["radius", "knn"]:
|
| 44 |
+
return {"error": "Method must be either 'radius' or 'knn'."}
|
| 45 |
+
|
| 46 |
+
r_script = textwrap.dedent(f"""
|
| 47 |
+
library(statial)
|
| 48 |
+
sce <- readRDS('{input_path}')
|
| 49 |
+
interactions <- calcInteractions(
|
| 50 |
+
sce,
|
| 51 |
+
group = '{image_col}',
|
| 52 |
+
cellType = '{cell_type_col}',
|
| 53 |
+
spatialCoords = c('{x_col}', '{y_col}'),
|
| 54 |
+
method = '{method}',
|
| 55 |
+
radius = {radius},
|
| 56 |
+
n_neighbors = {n_neighbors}
|
| 57 |
+
)
|
| 58 |
+
saveRDS(interactions, '{output_path}')
|
| 59 |
+
""")
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
result = subprocess.run(
|
| 63 |
+
["Rscript", "-e", r_script],
|
| 64 |
+
capture_output=True,
|
| 65 |
+
text=True,
|
| 66 |
+
check=True
|
| 67 |
+
)
|
| 68 |
+
return {
|
| 69 |
+
"command_executed": f"statial::calcInteractions on {input_rds}",
|
| 70 |
+
"stdout": result.stdout,
|
| 71 |
+
"stderr": result.stderr,
|
| 72 |
+
"output_files": [str(output_path)]
|
| 73 |
+
}
|
| 74 |
+
except subprocess.CalledProcessError as e:
|
| 75 |
+
return {
|
| 76 |
+
"error": "R execution failed",
|
| 77 |
+
"stdout": e.stdout,
|
| 78 |
+
"stderr": e.stderr
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
@mcp.tool()
|
| 82 |
+
def statial_get_spatial_context(
|
| 83 |
+
input_rds: str,
|
| 84 |
+
output_rds: str,
|
| 85 |
+
cell_type_col: str,
|
| 86 |
+
x_col: str = "x",
|
| 87 |
+
y_col: str = "y",
|
| 88 |
+
r: float = 100.0,
|
| 89 |
+
k: int = 3,
|
| 90 |
+
image_col: Optional[str] = None
|
| 91 |
+
):
|
| 92 |
+
"""
|
| 93 |
+
Defines spatial contexts by clustering cell type densities across the tissue.
|
| 94 |
+
|
| 95 |
+
Args:
|
| 96 |
+
input_rds: Path to the input SingleCellExperiment object (RDS).
|
| 97 |
+
output_rds: Path to save the SCE object with spatial context assignments (RDS).
|
| 98 |
+
cell_type_col: Column name in colData representing cell types.
|
| 99 |
+
x_col: Column name for the X coordinate.
|
| 100 |
+
y_col: Column name for the Y coordinate.
|
| 101 |
+
r: Radius for calculating cell density.
|
| 102 |
+
k: Number of spatial contexts (clusters) to identify.
|
| 103 |
+
image_col: Optional column name for image IDs if processing multiple images.
|
| 104 |
+
"""
|
| 105 |
+
input_path = Path(input_rds)
|
| 106 |
+
output_path = Path(output_rds)
|
| 107 |
+
|
| 108 |
+
if not input_path.exists():
|
| 109 |
+
return {"error": f"Input file {input_rds} does not exist."}
|
| 110 |
+
|
| 111 |
+
image_arg = f"'{image_col}'" if image_col else "NULL"
|
| 112 |
+
|
| 113 |
+
r_script = textwrap.dedent(f"""
|
| 114 |
+
library(statial)
|
| 115 |
+
sce <- readRDS('{input_path}')
|
| 116 |
+
sce <- getSpatialContext(
|
| 117 |
+
sce,
|
| 118 |
+
cellType = '{cell_type_col}',
|
| 119 |
+
spatialCoords = c('{x_col}', '{y_col}'),
|
| 120 |
+
r = {r},
|
| 121 |
+
k = {k},
|
| 122 |
+
group = {image_arg}
|
| 123 |
+
)
|
| 124 |
+
saveRDS(sce, '{output_path}')
|
| 125 |
+
""")
|
| 126 |
+
|
| 127 |
+
try:
|
| 128 |
+
result = subprocess.run(
|
| 129 |
+
["Rscript", "-e", r_script],
|
| 130 |
+
capture_output=True,
|
| 131 |
+
text=True,
|
| 132 |
+
check=True
|
| 133 |
+
)
|
| 134 |
+
return {
|
| 135 |
+
"command_executed": f"statial::getSpatialContext on {input_rds}",
|
| 136 |
+
"stdout": result.stdout,
|
| 137 |
+
"stderr": result.stderr,
|
| 138 |
+
"output_files": [str(output_path)]
|
| 139 |
+
}
|
| 140 |
+
except subprocess.CalledProcessError as e:
|
| 141 |
+
return {
|
| 142 |
+
"error": "R execution failed",
|
| 143 |
+
"stdout": e.stdout,
|
| 144 |
+
"stderr": e.stderr
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
@mcp.tool()
|
| 148 |
+
def statial_bivariate_test(
|
| 149 |
+
input_rds: str,
|
| 150 |
+
output_csv: str,
|
| 151 |
+
image_col: str,
|
| 152 |
+
cell_type_col: str,
|
| 153 |
+
spatial_context_col: str,
|
| 154 |
+
x_col: str = "x",
|
| 155 |
+
y_col: str = "y"
|
| 156 |
+
):
|
| 157 |
+
"""
|
| 158 |
+
Performs statistical testing to identify interactions that change significantly across spatial contexts.
|
| 159 |
+
|
| 160 |
+
Args:
|
| 161 |
+
input_rds: Path to the input SingleCellExperiment object (RDS).
|
| 162 |
+
output_csv: Path to save the statistical test results (CSV).
|
| 163 |
+
image_col: Column name for image IDs.
|
| 164 |
+
cell_type_col: Column name for cell types.
|
| 165 |
+
spatial_context_col: Column name for the spatial context (calculated via getSpatialContext).
|
| 166 |
+
x_col: Column name for the X coordinate.
|
| 167 |
+
y_col: Column name for the Y coordinate.
|
| 168 |
+
"""
|
| 169 |
+
input_path = Path(input_rds)
|
| 170 |
+
output_path = Path(output_csv)
|
| 171 |
+
|
| 172 |
+
if not input_path.exists():
|
| 173 |
+
return {"error": f"Input file {input_rds} does not exist."}
|
| 174 |
+
|
| 175 |
+
r_script = textwrap.dedent(f"""
|
| 176 |
+
library(statial)
|
| 177 |
+
sce <- readRDS('{input_path}')
|
| 178 |
+
results <- bivariateTest(
|
| 179 |
+
sce,
|
| 180 |
+
group = '{image_col}',
|
| 181 |
+
cellType = '{cell_type_col}',
|
| 182 |
+
spatialContext = '{spatial_context_col}',
|
| 183 |
+
spatialCoords = c('{x_col}', '{y_col}')
|
| 184 |
+
)
|
| 185 |
+
write.csv(results, '{output_path}', row.names = FALSE)
|
| 186 |
+
""")
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
result = subprocess.run(
|
| 190 |
+
["Rscript", "-e", r_script],
|
| 191 |
+
capture_output=True,
|
| 192 |
+
text=True,
|
| 193 |
+
check=True
|
| 194 |
+
)
|
| 195 |
+
return {
|
| 196 |
+
"command_executed": f"statial::bivariateTest on {input_rds}",
|
| 197 |
+
"stdout": result.stdout,
|
| 198 |
+
"stderr": result.stderr,
|
| 199 |
+
"output_files": [str(output_path)]
|
| 200 |
+
}
|
| 201 |
+
except subprocess.CalledProcessError as e:
|
| 202 |
+
return {
|
| 203 |
+
"error": "R execution failed",
|
| 204 |
+
"stdout": e.stdout,
|
| 205 |
+
"stderr": e.stderr
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
@mcp.tool()
|
| 209 |
+
def statial_impute_interactions(
|
| 210 |
+
input_rds: str,
|
| 211 |
+
output_rds: str,
|
| 212 |
+
image_col: str,
|
| 213 |
+
cell_type_col: str,
|
| 214 |
+
min_cells: int = 5
|
| 215 |
+
):
|
| 216 |
+
"""
|
| 217 |
+
Imputes interaction values for images where certain cell types might be missing or rare.
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
input_rds: Path to the interactions data frame (RDS) generated by calcInteractions.
|
| 221 |
+
output_rds: Path to save the imputed interactions data frame (RDS).
|
| 222 |
+
image_col: Column name for image IDs.
|
| 223 |
+
cell_type_col: Column name for cell types.
|
| 224 |
+
min_cells: Minimum number of cells required to avoid imputation.
|
| 225 |
+
"""
|
| 226 |
+
input_path = Path(input_rds)
|
| 227 |
+
output_path = Path(output_rds)
|
| 228 |
+
|
| 229 |
+
if not input_path.exists():
|
| 230 |
+
return {"error": f"Input file {input_rds} does not exist."}
|
| 231 |
+
|
| 232 |
+
r_script = textwrap.dedent(f"""
|
| 233 |
+
library(statial)
|
| 234 |
+
interactions <- readRDS('{input_path}')
|
| 235 |
+
imputed <- imputeInteractions(
|
| 236 |
+
interactions,
|
| 237 |
+
group = '{image_col}',
|
| 238 |
+
cellType = '{cell_type_col}',
|
| 239 |
+
minCells = {min_cells}
|
| 240 |
+
)
|
| 241 |
+
saveRDS(imputed, '{output_path}')
|
| 242 |
+
""")
|
| 243 |
+
|
| 244 |
+
try:
|
| 245 |
+
result = subprocess.run(
|
| 246 |
+
["Rscript", "-e", r_script],
|
| 247 |
+
capture_output=True,
|
| 248 |
+
text=True,
|
| 249 |
+
check=True
|
| 250 |
+
)
|
| 251 |
+
return {
|
| 252 |
+
"command_executed": f"statial::imputeInteractions on {input_rds}",
|
| 253 |
+
"stdout": result.stdout,
|
| 254 |
+
"stderr": result.stderr,
|
| 255 |
+
"output_files": [str(output_path)]
|
| 256 |
+
}
|
| 257 |
+
except subprocess.CalledProcessError as e:
|
| 258 |
+
return {
|
| 259 |
+
"error": "R execution failed",
|
| 260 |
+
"stdout": e.stdout,
|
| 261 |
+
"stderr": e.stderr
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
@mcp.tool()
|
| 265 |
+
def statial_get_interactions(
|
| 266 |
+
input_rds: str,
|
| 267 |
+
output_csv: str,
|
| 268 |
+
image_id: Optional[str] = None
|
| 269 |
+
):
|
| 270 |
+
"""
|
| 271 |
+
Extracts interaction information from a SingleCellExperiment object or interaction data frame.
|
| 272 |
+
|
| 273 |
+
Args:
|
| 274 |
+
input_rds: Path to the input RDS file.
|
| 275 |
+
output_csv: Path to save the extracted interactions (CSV).
|
| 276 |
+
image_id: Optional specific image ID to extract interactions for.
|
| 277 |
+
"""
|
| 278 |
+
input_path = Path(input_rds)
|
| 279 |
+
output_path = Path(output_csv)
|
| 280 |
+
|
| 281 |
+
if not input_path.exists():
|
| 282 |
+
return {"error": f"Input file {input_rds} does not exist."}
|
| 283 |
+
|
| 284 |
+
image_filter = f"subset(interact, group == '{image_id}')" if image_id else "interact"
|
| 285 |
+
|
| 286 |
+
r_script = textwrap.dedent(f"""
|
| 287 |
+
library(statial)
|
| 288 |
+
interact <- readRDS('{input_path}')
|
| 289 |
+
# If input is SCE, extract interactions if they exist
|
| 290 |
+
if(is(interact, 'SingleCellExperiment')) {{
|
| 291 |
+
# Assuming interactions were stored in metadata or colData by user
|
| 292 |
+
# This is a helper to convert the object to a readable table
|
| 293 |
+
interact <- as.data.frame(colData(interact))
|
| 294 |
+
}}
|
| 295 |
+
|
| 296 |
+
result_df <- {image_filter}
|
| 297 |
+
write.csv(result_df, '{output_path}', row.names = FALSE)
|
| 298 |
+
""")
|
| 299 |
+
|
| 300 |
+
try:
|
| 301 |
+
result = subprocess.run(
|
| 302 |
+
["Rscript", "-e", r_script],
|
| 303 |
+
capture_output=True,
|
| 304 |
+
text=True,
|
| 305 |
+
check=True
|
| 306 |
+
)
|
| 307 |
+
return {
|
| 308 |
+
"command_executed": f"Extracting interactions from {input_rds}",
|
| 309 |
+
"stdout": result.stdout,
|
| 310 |
+
"stderr": result.stderr,
|
| 311 |
+
"output_files": [str(output_path)]
|
| 312 |
+
}
|
| 313 |
+
except subprocess.CalledProcessError as e:
|
| 314 |
+
return {
|
| 315 |
+
"error": "R execution failed",
|
| 316 |
+
"stdout": e.stdout,
|
| 317 |
+
"stderr": e.stderr
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
if __name__ == "__main__":
|
| 321 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-statial/app/bioconductor-statial_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-statial/app/bioconductor-statial_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_statial'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-statial/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-statial/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-statial:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-statial:latest
|
| 7 |
+
container_name: mcp-bioconductor-statial
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-statial
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-statial/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-statial
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-statial/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-svp/app/bioconductor-svp_server.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Dict
|
| 5 |
+
|
| 6 |
+
# Note: The @mcp.tool decorator is expected to be provided by the MCP framework.
|
| 7 |
+
# This is a placeholder class to allow the code to be syntactically valid.
|
| 8 |
+
class mcp:
|
| 9 |
+
@staticmethod
|
| 10 |
+
def tool(func):
|
| 11 |
+
return func
|
| 12 |
+
|
| 13 |
+
@mcp.tool
|
| 14 |
+
def svp_train(
|
| 15 |
+
training_data: Path,
|
| 16 |
+
feature_data: Path,
|
| 17 |
+
output_model: Path,
|
| 18 |
+
method: str = "rf",
|
| 19 |
+
) -> Dict:
|
| 20 |
+
"""
|
| 21 |
+
Trains a Sequence-based Variant Prediction (SVP) model.
|
| 22 |
+
|
| 23 |
+
This tool wraps the `svp_train` function from the R Bioconductor package 'SVP'.
|
| 24 |
+
It takes training data and feature definitions to build a model, which is then
|
| 25 |
+
saved to a specified file. The R package 'SVP' and its dependencies must be
|
| 26 |
+
installed in the R environment.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
training_data: Path to the training data file (tab-separated format).
|
| 30 |
+
This file should contain variant information and their known classifications.
|
| 31 |
+
feature_data: Path to the feature data file (tab-separated format).
|
| 32 |
+
This file defines the features to be used for model training.
|
| 33 |
+
output_model: Path where the trained model object (.rds file) will be saved.
|
| 34 |
+
method: The training method to be used by the underlying 'caret' package.
|
| 35 |
+
Defaults to 'rf' (Random Forest).
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
A dictionary containing the command executed, stdout, stderr, and the path
|
| 39 |
+
to the output model file.
|
| 40 |
+
"""
|
| 41 |
+
# 1. Input Validation
|
| 42 |
+
if not training_data.is_file():
|
| 43 |
+
raise FileNotFoundError(f"Training data file not found: {training_data}")
|
| 44 |
+
if not feature_data.is_file():
|
| 45 |
+
raise FileNotFoundError(f"Feature data file not found: {feature_data}")
|
| 46 |
+
|
| 47 |
+
output_model.parent.mkdir(parents=True, exist_ok=True)
|
| 48 |
+
|
| 49 |
+
# 2. R Script Generation
|
| 50 |
+
r_script_content = f"""
|
| 51 |
+
# Suppress startup messages for cleaner output
|
| 52 |
+
suppressPackageStartupMessages(library(SVP))
|
| 53 |
+
|
| 54 |
+
# Get arguments from command line
|
| 55 |
+
args <- commandArgs(trailingOnly=TRUE)
|
| 56 |
+
training_file <- args[1]
|
| 57 |
+
feature_file <- args[2]
|
| 58 |
+
output_model_file <- args[3]
|
| 59 |
+
train_method <- args[4]
|
| 60 |
+
|
| 61 |
+
# Read data - assuming tab-separated values with a header
|
| 62 |
+
tryCatch({{
|
| 63 |
+
training <- read.delim(training_file, header=TRUE, stringsAsFactors=FALSE)
|
| 64 |
+
feature <- read.delim(feature_file, header=TRUE, stringsAsFactors=FALSE)
|
| 65 |
+
}}, error = function(e) {{
|
| 66 |
+
stop(paste("Error reading input files:", e$message))
|
| 67 |
+
}})
|
| 68 |
+
|
| 69 |
+
# Train model
|
| 70 |
+
print("Starting model training...")
|
| 71 |
+
tryCatch({{
|
| 72 |
+
svp_model <- svp_train(training, feature, method=train_method)
|
| 73 |
+
}}, error = function(e) {{
|
| 74 |
+
stop(paste("Error during model training:", e$message))
|
| 75 |
+
}})
|
| 76 |
+
|
| 77 |
+
# Save model
|
| 78 |
+
tryCatch({{
|
| 79 |
+
saveRDS(svp_model, file=output_model_file)
|
| 80 |
+
}}, error = function(e) {{
|
| 81 |
+
stop(paste("Error saving the model file:", e$message))
|
| 82 |
+
}})
|
| 83 |
+
|
| 84 |
+
print(paste("Model training complete. Model saved to:", output_model_file))
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
# 3. Subprocess Execution
|
| 88 |
+
r_script_path = None
|
| 89 |
+
try:
|
| 90 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_r_script:
|
| 91 |
+
tmp_r_script.write(r_script_content)
|
| 92 |
+
r_script_path = tmp_r_script.name
|
| 93 |
+
|
| 94 |
+
cmd = [
|
| 95 |
+
"Rscript",
|
| 96 |
+
r_script_path,
|
| 97 |
+
str(training_data.resolve()),
|
| 98 |
+
str(feature_data.resolve()),
|
| 99 |
+
str(output_model.resolve()),
|
| 100 |
+
method,
|
| 101 |
+
]
|
| 102 |
+
command_executed = " ".join(cmd)
|
| 103 |
+
|
| 104 |
+
result = subprocess.run(
|
| 105 |
+
cmd,
|
| 106 |
+
check=True,
|
| 107 |
+
capture_output=True,
|
| 108 |
+
text=True,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
# 4. Structured Result Return
|
| 112 |
+
return {
|
| 113 |
+
"command_executed": command_executed,
|
| 114 |
+
"stdout": result.stdout,
|
| 115 |
+
"stderr": result.stderr,
|
| 116 |
+
"output_files": {"model_file": str(output_model)},
|
| 117 |
+
}
|
| 118 |
+
except FileNotFoundError:
|
| 119 |
+
raise RuntimeError("Rscript command not found. Is R installed and in your system's PATH?")
|
| 120 |
+
except subprocess.CalledProcessError as e:
|
| 121 |
+
raise RuntimeError(
|
| 122 |
+
f"SVP training failed with exit code {e.returncode}.\n"
|
| 123 |
+
f"Command: {e.cmd}\n"
|
| 124 |
+
f"Stdout: {e.stdout}\n"
|
| 125 |
+
f"Stderr: {e.stderr}"
|
| 126 |
+
)
|
| 127 |
+
finally:
|
| 128 |
+
# Clean up the temporary R script
|
| 129 |
+
if r_script_path and Path(r_script_path).exists():
|
| 130 |
+
Path(r_script_path).unlink()
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@mcp.tool
|
| 134 |
+
def svp_predict(
|
| 135 |
+
model_file: Path,
|
| 136 |
+
testing_data: Path,
|
| 137 |
+
feature_data: Path,
|
| 138 |
+
output_predictions: Path,
|
| 139 |
+
) -> Dict:
|
| 140 |
+
"""
|
| 141 |
+
Predicts variant effects using a pre-trained SVP model.
|
| 142 |
+
|
| 143 |
+
This tool wraps the `svp_predict` function from the R Bioconductor package 'SVP'.
|
| 144 |
+
It uses a trained model to predict the classification of new variants. The R package
|
| 145 |
+
'SVP' and its dependencies must be installed in the R environment.
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
model_file: Path to the trained SVP model file (.rds format), generated by `svp_train`.
|
| 149 |
+
testing_data: Path to the data file containing new variants to be classified (tab-separated format).
|
| 150 |
+
feature_data: Path to the feature data file (tab-separated format). This must be the same
|
| 151 |
+
or have the same structure as the one used for training.
|
| 152 |
+
output_predictions: Path where the output predictions (tab-separated file) will be saved.
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
A dictionary containing the command executed, stdout, stderr, and the path
|
| 156 |
+
to the output predictions file.
|
| 157 |
+
"""
|
| 158 |
+
# 1. Input Validation
|
| 159 |
+
if not model_file.is_file():
|
| 160 |
+
raise FileNotFoundError(f"Model file not found: {model_file}")
|
| 161 |
+
if not testing_data.is_file():
|
| 162 |
+
raise FileNotFoundError(f"Testing data file not found: {testing_data}")
|
| 163 |
+
if not feature_data.is_file():
|
| 164 |
+
raise FileNotFoundError(f"Feature data file not found: {feature_data}")
|
| 165 |
+
|
| 166 |
+
output_predictions.parent.mkdir(parents=True, exist_ok=True)
|
| 167 |
+
|
| 168 |
+
# 2. R Script Generation
|
| 169 |
+
r_script_content = f"""
|
| 170 |
+
# Suppress startup messages for cleaner output
|
| 171 |
+
suppressPackageStartupMessages(library(SVP))
|
| 172 |
+
|
| 173 |
+
# Get arguments from command line
|
| 174 |
+
args <- commandArgs(trailingOnly=TRUE)
|
| 175 |
+
model_file <- args[1]
|
| 176 |
+
testing_file <- args[2]
|
| 177 |
+
feature_file <- args[3]
|
| 178 |
+
output_file <- args[4]
|
| 179 |
+
|
| 180 |
+
# Load model and data
|
| 181 |
+
tryCatch({{
|
| 182 |
+
svp_model <- readRDS(model_file)
|
| 183 |
+
testing <- read.delim(testing_file, header=TRUE, stringsAsFactors=FALSE)
|
| 184 |
+
feature <- read.delim(feature_file, header=TRUE, stringsAsFactors=FALSE)
|
| 185 |
+
}}, error = function(e) {{
|
| 186 |
+
stop(paste("Error reading input files:", e$message))
|
| 187 |
+
}})
|
| 188 |
+
|
| 189 |
+
# Make predictions
|
| 190 |
+
print("Starting prediction...")
|
| 191 |
+
tryCatch({{
|
| 192 |
+
svp_prediction <- svp_predict(svp_model, testing, feature)
|
| 193 |
+
}}, error = function(e) {{
|
| 194 |
+
stop(paste("Error during prediction:", e$message))
|
| 195 |
+
}})
|
| 196 |
+
|
| 197 |
+
# Write predictions to file
|
| 198 |
+
tryCatch({{
|
| 199 |
+
write.table(svp_prediction, file=output_file, sep="\\t", row.names=FALSE, quote=FALSE)
|
| 200 |
+
}}, error = function(e) {{
|
| 201 |
+
stop(paste("Error writing prediction output file:", e$message))
|
| 202 |
+
}})
|
| 203 |
+
|
| 204 |
+
print(paste("Prediction complete. Results saved to:", output_file))
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
# 3. Subprocess Execution
|
| 208 |
+
r_script_path = None
|
| 209 |
+
try:
|
| 210 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_r_script:
|
| 211 |
+
tmp_r_script.write(r_script_content)
|
| 212 |
+
r_script_path = tmp_r_script.name
|
| 213 |
+
|
| 214 |
+
cmd = [
|
| 215 |
+
"Rscript",
|
| 216 |
+
r_script_path,
|
| 217 |
+
str(model_file.resolve()),
|
| 218 |
+
str(testing_data.resolve()),
|
| 219 |
+
str(feature_data.resolve()),
|
| 220 |
+
str(output_predictions.resolve()),
|
| 221 |
+
]
|
| 222 |
+
command_executed = " ".join(cmd)
|
| 223 |
+
|
| 224 |
+
result = subprocess.run(
|
| 225 |
+
cmd,
|
| 226 |
+
check=True,
|
| 227 |
+
capture_output=True,
|
| 228 |
+
text=True,
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
# 4. Structured Result Return
|
| 232 |
+
return {
|
| 233 |
+
"command_executed": command_executed,
|
| 234 |
+
"stdout": result.stdout,
|
| 235 |
+
"stderr": result.stderr,
|
| 236 |
+
"output_files": {"predictions_file": str(output_predictions)},
|
| 237 |
+
}
|
| 238 |
+
except FileNotFoundError:
|
| 239 |
+
raise RuntimeError("Rscript command not found. Is R installed and in your system's PATH?")
|
| 240 |
+
except subprocess.CalledProcessError as e:
|
| 241 |
+
raise RuntimeError(
|
| 242 |
+
f"SVP prediction failed with exit code {e.returncode}.\n"
|
| 243 |
+
f"Command: {e.cmd}\n"
|
| 244 |
+
f"Stdout: {e.stdout}\n"
|
| 245 |
+
f"Stderr: {e.stderr}"
|
| 246 |
+
)
|
| 247 |
+
finally:
|
| 248 |
+
# Clean up the temporary R script
|
| 249 |
+
if r_script_path and Path(r_script_path).exists():
|
| 250 |
+
Path(r_script_path).unlink()
|