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_abundancebin/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_server.py +136 -0
- Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_abundancebin/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_abundancebin/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_abundancebin/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_abundancebin/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_anarci/app/anarci_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_anarci/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_server.cpython-311.pyc +0 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py +144 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-biostrings/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_server.py +303 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-despace/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py +688 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-geomxtools/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-glmgampoi/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-infercnv/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_server.py +298 -0
- Biomni/mcp_generated/mcp_bioconductor-infercnv/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-infercnv/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-infercnv/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py +195 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py +340 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-preprocesscore/requirements.txt +2 -0
Biomni/mcp_generated/mcp_abundancebin/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 abundancebin via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda abundancebin -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/abundancebin_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/abundancebin_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/abundancebin_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_server.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# Assume @mcp.tool() is defined in the execution environment.
|
| 7 |
+
# No import is needed for the final code.
|
| 8 |
+
|
| 9 |
+
# Set up logging
|
| 10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
from mcp.server.fastmcp import FastMCP
|
| 14 |
+
|
| 15 |
+
SERVER_NAME = 'local_abundancebin'
|
| 16 |
+
mcp = FastMCP(SERVER_NAME)
|
| 17 |
+
|
| 18 |
+
@mcp.tool()
|
| 19 |
+
def abundancebin(
|
| 20 |
+
input_file: Path,
|
| 21 |
+
kmer_len: int = 20,
|
| 22 |
+
output: Optional[Path] = None,
|
| 23 |
+
exclude: Optional[int] = None,
|
| 24 |
+
exclude_max: Optional[int] = None,
|
| 25 |
+
output_fasta: bool = False,
|
| 26 |
+
bin_num: Optional[int] = None,
|
| 27 |
+
recursive_classification: bool = False,
|
| 28 |
+
):
|
| 29 |
+
"""
|
| 30 |
+
Performs abundance-based binning on a given input FASTA/FASTQ file.
|
| 31 |
+
|
| 32 |
+
This tool uses k-mer frequency to classify sequences into bins. It can either
|
| 33 |
+
classify into a specified number of bins or use a recursive classification approach.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
input_file: Path to the input FASTA/FASTQ file.
|
| 37 |
+
kmer_len: The length of the k-mer to use for composition analysis (default: 20).
|
| 38 |
+
output: Path to the output log file. If not provided, defaults to '<input_file>.log'.
|
| 39 |
+
exclude: Exclude contigs with coverage lower than this count.
|
| 40 |
+
exclude_max: Exclude contigs with coverage higher than this count.
|
| 41 |
+
output_fasta: If True, output binned sequences into separate FASTA files.
|
| 42 |
+
bin_num: The specific number of bins to classify sequences into.
|
| 43 |
+
recursive_classification: If True, undergo recursive classification instead of specifying a bin number.
|
| 44 |
+
This is mutually exclusive with 'bin_num'.
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
A dictionary containing the execution details and paths to output files.
|
| 48 |
+
"""
|
| 49 |
+
# 1. Input Validation
|
| 50 |
+
if not input_file.is_file():
|
| 51 |
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
| 52 |
+
|
| 53 |
+
if bin_num is not None and recursive_classification:
|
| 54 |
+
raise ValueError("Parameters 'bin_num' and 'recursive_classification' are mutually exclusive. Please provide only one.")
|
| 55 |
+
|
| 56 |
+
if kmer_len <= 0:
|
| 57 |
+
raise ValueError("kmer_len must be a positive integer.")
|
| 58 |
+
|
| 59 |
+
# 2. Command Construction
|
| 60 |
+
# Determine the output file path based on the tool's default behavior
|
| 61 |
+
if output:
|
| 62 |
+
output_path = output
|
| 63 |
+
else:
|
| 64 |
+
output_path = input_file.with_suffix(".log")
|
| 65 |
+
|
| 66 |
+
# Ensure the output directory exists
|
| 67 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 68 |
+
|
| 69 |
+
cmd = [
|
| 70 |
+
"abundancebin",
|
| 71 |
+
"-input", str(input_file),
|
| 72 |
+
"-kmer_len", str(kmer_len),
|
| 73 |
+
"-output", str(output_path)
|
| 74 |
+
]
|
| 75 |
+
|
| 76 |
+
if exclude is not None:
|
| 77 |
+
cmd.extend(["-exclude", str(exclude)])
|
| 78 |
+
|
| 79 |
+
if exclude_max is not None:
|
| 80 |
+
cmd.extend(["-exclude_max", str(exclude_max)])
|
| 81 |
+
|
| 82 |
+
if output_fasta:
|
| 83 |
+
cmd.append("-OUTPUT_FASTA")
|
| 84 |
+
|
| 85 |
+
if bin_num is not None:
|
| 86 |
+
cmd.extend(["-bin_num", str(bin_num)])
|
| 87 |
+
|
| 88 |
+
if recursive_classification:
|
| 89 |
+
cmd.append("-RECURSIVE_CLASSIFICATION")
|
| 90 |
+
|
| 91 |
+
command_executed = " ".join(cmd)
|
| 92 |
+
logger.info(f"Executing command: {command_executed}")
|
| 93 |
+
|
| 94 |
+
# 3. Subprocess Execution and Error Handling
|
| 95 |
+
try:
|
| 96 |
+
result = subprocess.run(
|
| 97 |
+
cmd,
|
| 98 |
+
capture_output=True,
|
| 99 |
+
text=True,
|
| 100 |
+
check=True
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# The tool might generate more files if -OUTPUT_FASTA is used.
|
| 104 |
+
# For simplicity, we return the main log file. The user can infer others.
|
| 105 |
+
output_files = [str(output_path)] if output_path.exists() else []
|
| 106 |
+
|
| 107 |
+
# 4. Structured Result Return (Success)
|
| 108 |
+
return {
|
| 109 |
+
"command_executed": command_executed,
|
| 110 |
+
"stdout": result.stdout,
|
| 111 |
+
"stderr": result.stderr,
|
| 112 |
+
"output_files": output_files
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
except FileNotFoundError:
|
| 116 |
+
error_message = "abundancebin command not found. Please ensure the tool is installed and in your system's PATH."
|
| 117 |
+
logger.error(error_message)
|
| 118 |
+
# Re-raising is often better to signal a fatal environment error.
|
| 119 |
+
raise RuntimeError(error_message) from None
|
| 120 |
+
|
| 121 |
+
except subprocess.CalledProcessError as e:
|
| 122 |
+
logger.error(f"abundancebin failed with exit code {e.returncode}")
|
| 123 |
+
logger.error(f"Stderr: {e.stderr}")
|
| 124 |
+
logger.error(f"Stdout: {e.stdout}")
|
| 125 |
+
|
| 126 |
+
# 4. Structured Result Return (Failure)
|
| 127 |
+
return {
|
| 128 |
+
"command_executed": command_executed,
|
| 129 |
+
"stdout": e.stdout,
|
| 130 |
+
"stderr": e.stderr,
|
| 131 |
+
"return_code": e.returncode,
|
| 132 |
+
"output_files": []
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
if __name__ == "__main__":
|
| 136 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_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_abundancebin/app/abundancebin_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_abundancebin'
|
| 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_abundancebin/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_abundancebin/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-abundancebin:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-abundancebin:latest
|
| 7 |
+
container_name: mcp-abundancebin
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=abundancebin
|
| 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_abundancebin/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- abundancebin
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_abundancebin/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_anarci/app/anarci_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_anarci/app/anarci_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_anarci'
|
| 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_anarci/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_server.cpython-311.pyc
ADDED
|
Binary file (39 kB). View file
|
|
|
Biomni/mcp_generated/mcp_bioconductor-biostrings/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-biostrings via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-biostrings -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-biostrings_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-biostrings_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-biostrings_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List, Dict, Any
|
| 5 |
+
|
| 6 |
+
# Mock the decorator for standalone execution
|
| 7 |
+
class mcp:
|
| 8 |
+
@staticmethod
|
| 9 |
+
def tool():
|
| 10 |
+
def decorator(func):
|
| 11 |
+
return func
|
| 12 |
+
return decorator
|
| 13 |
+
|
| 14 |
+
from mcp.server.fastmcp import FastMCP
|
| 15 |
+
|
| 16 |
+
SERVER_NAME = 'local_bioconductor_biostrings'
|
| 17 |
+
mcp = FastMCP(SERVER_NAME)
|
| 18 |
+
|
| 19 |
+
@mcp.tool()
|
| 20 |
+
def rscript(
|
| 21 |
+
script_file: Optional[Path] = None,
|
| 22 |
+
expressions: Optional[List[str]] = None,
|
| 23 |
+
script_args: Optional[List[str]] = None,
|
| 24 |
+
verbose: bool = False,
|
| 25 |
+
default_packages: Optional[str] = None,
|
| 26 |
+
vanilla: bool = False,
|
| 27 |
+
save: bool = False,
|
| 28 |
+
no_environ: bool = False,
|
| 29 |
+
no_site_file: bool = False,
|
| 30 |
+
no_init_file: bool = False,
|
| 31 |
+
restore: bool = False,
|
| 32 |
+
) -> Dict[str, Any]:
|
| 33 |
+
"""
|
| 34 |
+
Executes an R script or R expressions using the Rscript command-line tool.
|
| 35 |
+
|
| 36 |
+
This tool serves as a wrapper for Rscript, allowing for the execution of R code
|
| 37 |
+
from a file or directly from string expressions. It mirrors the functionality
|
| 38 |
+
provided by `Rscript --help`.
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
script_file: Path to the R script file to be executed. Mutually exclusive with 'expressions'.
|
| 42 |
+
expressions: A list of R expressions to be executed. Mutually exclusive with 'script_file'.
|
| 43 |
+
script_args: A list of arguments to be passed to the R script itself.
|
| 44 |
+
verbose: If True, enables verbose output, printing information on progress.
|
| 45 |
+
default_packages: A comma-separated string of package names to be loaded by default.
|
| 46 |
+
vanilla: If True, combines --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ.
|
| 47 |
+
If set, it overrides the individual flags (save, restore, etc.).
|
| 48 |
+
save: If True, the workspace is saved at the end of the session. Ignored if 'vanilla' is True.
|
| 49 |
+
no_environ: If True, site and user environment files are not read. Ignored if 'vanilla' is True.
|
| 50 |
+
no_site_file: If True, the site-wide Rprofile is not read. Ignored if 'vanilla' is True.
|
| 51 |
+
no_init_file: If True, the user's R profile is not read. Ignored if 'vanilla' is True.
|
| 52 |
+
restore: If True, previously saved objects are restored at startup. Ignored if 'vanilla' is True.
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
A dictionary containing the executed command, stdout, stderr, and a list of output files (always empty).
|
| 56 |
+
"""
|
| 57 |
+
# 1. Input Validation
|
| 58 |
+
if not script_file and not expressions:
|
| 59 |
+
raise ValueError("Either 'script_file' or 'expressions' must be provided.")
|
| 60 |
+
if script_file and expressions:
|
| 61 |
+
raise ValueError("'script_file' and 'expressions' are mutually exclusive and cannot be used together.")
|
| 62 |
+
if script_file and not script_file.is_file():
|
| 63 |
+
raise FileNotFoundError(f"The specified script file does not exist: {script_file}")
|
| 64 |
+
if expressions and not expressions:
|
| 65 |
+
raise ValueError("'expressions' list cannot be empty if provided.")
|
| 66 |
+
|
| 67 |
+
# 2. Command Construction
|
| 68 |
+
cmd = ["Rscript"]
|
| 69 |
+
|
| 70 |
+
if verbose:
|
| 71 |
+
cmd.append("--verbose")
|
| 72 |
+
|
| 73 |
+
if default_packages:
|
| 74 |
+
cmd.extend(["--default-packages", default_packages])
|
| 75 |
+
|
| 76 |
+
if vanilla:
|
| 77 |
+
cmd.append("--vanilla")
|
| 78 |
+
else:
|
| 79 |
+
# These options are combined and handled by the --vanilla flag in Rscript
|
| 80 |
+
if save:
|
| 81 |
+
cmd.append("--save")
|
| 82 |
+
if no_environ:
|
| 83 |
+
cmd.append("--no-environ")
|
| 84 |
+
if no_site_file:
|
| 85 |
+
cmd.append("--no-site-file")
|
| 86 |
+
if no_init_file:
|
| 87 |
+
cmd.append("--no-init-file")
|
| 88 |
+
if restore:
|
| 89 |
+
cmd.append("--restore")
|
| 90 |
+
|
| 91 |
+
# Add expressions or script file to the command
|
| 92 |
+
if expressions:
|
| 93 |
+
for expr in expressions:
|
| 94 |
+
cmd.extend(["-e", expr])
|
| 95 |
+
elif script_file:
|
| 96 |
+
cmd.append(str(script_file))
|
| 97 |
+
|
| 98 |
+
# Add arguments for the R script itself
|
| 99 |
+
if script_args:
|
| 100 |
+
cmd.extend(script_args)
|
| 101 |
+
|
| 102 |
+
command_str = " ".join(cmd)
|
| 103 |
+
logging.info(f"Executing command: {command_str}")
|
| 104 |
+
|
| 105 |
+
# 3. Subprocess Execution and Error Handling
|
| 106 |
+
try:
|
| 107 |
+
result = subprocess.run(
|
| 108 |
+
cmd,
|
| 109 |
+
capture_output=True,
|
| 110 |
+
text=True,
|
| 111 |
+
check=True,
|
| 112 |
+
)
|
| 113 |
+
stdout = result.stdout
|
| 114 |
+
stderr = result.stderr
|
| 115 |
+
except FileNotFoundError:
|
| 116 |
+
# This error occurs if 'Rscript' is not in the system's PATH
|
| 117 |
+
return {
|
| 118 |
+
"command_executed": command_str,
|
| 119 |
+
"stdout": "",
|
| 120 |
+
"stderr": "Error: 'Rscript' command not found. Please ensure R is installed and accessible in your system's PATH.",
|
| 121 |
+
"output_files": []
|
| 122 |
+
}
|
| 123 |
+
except subprocess.CalledProcessError as e:
|
| 124 |
+
# This error occurs if the R script itself fails (non-zero exit code)
|
| 125 |
+
logging.error(f"Rscript execution failed with return code {e.returncode}")
|
| 126 |
+
return {
|
| 127 |
+
"command_executed": command_str,
|
| 128 |
+
"stdout": e.stdout,
|
| 129 |
+
"stderr": e.stderr,
|
| 130 |
+
"output_files": []
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# 4. Structured Result Return
|
| 134 |
+
# Rscript itself does not have a dedicated output file parameter. Any files
|
| 135 |
+
# created are determined by the R code within the script.
|
| 136 |
+
return {
|
| 137 |
+
"command_executed": command_str,
|
| 138 |
+
"stdout": stdout,
|
| 139 |
+
"stderr": stderr,
|
| 140 |
+
"output_files": []
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if __name__ == "__main__":
|
| 144 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_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-biostrings/app/bioconductor-biostrings_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_biostrings'
|
| 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-biostrings/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-biostrings:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-biostrings:latest
|
| 7 |
+
container_name: mcp-bioconductor-biostrings
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-biostrings
|
| 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-biostrings/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-biostrings
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-biostrings/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-despace/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-despace via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-despace -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-despace_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-despace_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-despace_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_server.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# MCP is a placeholder for the Model Context Protocol library.
|
| 7 |
+
# In a real environment, this would be: from mcp import tool
|
| 8 |
+
class mcp:
|
| 9 |
+
@staticmethod
|
| 10 |
+
def tool():
|
| 11 |
+
def decorator(func):
|
| 12 |
+
return func
|
| 13 |
+
return decorator
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 16 |
+
|
| 17 |
+
from mcp.server.fastmcp import FastMCP
|
| 18 |
+
|
| 19 |
+
SERVER_NAME = 'local_bioconductor_despace'
|
| 20 |
+
mcp = FastMCP(SERVER_NAME)
|
| 21 |
+
|
| 22 |
+
@mcp.tool()
|
| 23 |
+
def cluster_scrnaseq(
|
| 24 |
+
sc_object_path: Path,
|
| 25 |
+
output_rds_path: Path,
|
| 26 |
+
s_topics: int = 10,
|
| 27 |
+
n_top_genes: int = 2000,
|
| 28 |
+
) -> dict:
|
| 29 |
+
"""
|
| 30 |
+
Performs clustering on single-cell RNA-seq data as a preprocessing step for DeSpace.
|
| 31 |
+
|
| 32 |
+
This tool wraps the `cluster_scRNAseq` function from the DeSpace R package. It takes a
|
| 33 |
+
Seurat object, identifies highly variable genes, performs dimensionality reduction,
|
| 34 |
+
and identifies clusters based on topic modeling.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
sc_object_path: Path to the input single-cell Seurat object (.rds file).
|
| 38 |
+
output_rds_path: Path to save the clustered single-cell Seurat object (.rds file).
|
| 39 |
+
s_topics: The number of topics (S) for topic modeling, representing putative cell types.
|
| 40 |
+
n_top_genes: Number of highly variable genes to use for clustering.
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
A dictionary containing the executed command, stdout, stderr, and the path to the output file.
|
| 44 |
+
"""
|
| 45 |
+
# --- Input Validation ---
|
| 46 |
+
if not sc_object_path.is_file():
|
| 47 |
+
raise FileNotFoundError(f"Input single-cell object not found: {sc_object_path}")
|
| 48 |
+
if not output_rds_path.parent.exists():
|
| 49 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 50 |
+
logging.info(f"Created output directory: {output_rds_path.parent}")
|
| 51 |
+
|
| 52 |
+
if s_topics <= 0:
|
| 53 |
+
raise ValueError("s_topics must be a positive integer.")
|
| 54 |
+
if n_top_genes <= 0:
|
| 55 |
+
raise ValueError("n_top_genes must be a positive integer.")
|
| 56 |
+
|
| 57 |
+
# --- Command Construction ---
|
| 58 |
+
# This assumes a wrapper R script 'cluster_scrnaseq.R' is in the system's PATH.
|
| 59 |
+
cmd = [
|
| 60 |
+
"Rscript", "cluster_scrnaseq.R",
|
| 61 |
+
"--sc_object_path", str(sc_object_path),
|
| 62 |
+
"--output_rds_path", str(output_rds_path),
|
| 63 |
+
"--s_topics", str(s_topics),
|
| 64 |
+
"--n_top_genes", str(n_top_genes),
|
| 65 |
+
]
|
| 66 |
+
command_executed = " ".join(cmd)
|
| 67 |
+
logging.info(f"Executing command: {command_executed}")
|
| 68 |
+
|
| 69 |
+
# --- Subprocess Execution ---
|
| 70 |
+
try:
|
| 71 |
+
process = subprocess.run(
|
| 72 |
+
cmd,
|
| 73 |
+
check=True,
|
| 74 |
+
capture_output=True,
|
| 75 |
+
text=True,
|
| 76 |
+
)
|
| 77 |
+
stdout = process.stdout
|
| 78 |
+
stderr = process.stderr
|
| 79 |
+
logging.info("Single-cell clustering completed successfully.")
|
| 80 |
+
except FileNotFoundError:
|
| 81 |
+
err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH."
|
| 82 |
+
logging.error(err_msg)
|
| 83 |
+
return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []}
|
| 84 |
+
except subprocess.CalledProcessError as e:
|
| 85 |
+
logging.error(f"Single-cell clustering failed with exit code {e.returncode}.")
|
| 86 |
+
return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []}
|
| 87 |
+
|
| 88 |
+
# --- Structured Result Return ---
|
| 89 |
+
return {
|
| 90 |
+
"command_executed": command_executed,
|
| 91 |
+
"stdout": stdout,
|
| 92 |
+
"stderr": stderr,
|
| 93 |
+
"output_files": [str(output_rds_path)]
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
@mcp.tool()
|
| 97 |
+
def cluster_sp_data(
|
| 98 |
+
sp_object_path: Path,
|
| 99 |
+
output_rds_path: Path,
|
| 100 |
+
resolution: float = 0.8,
|
| 101 |
+
) -> dict:
|
| 102 |
+
"""
|
| 103 |
+
Performs clustering on spatial transcriptomics data as a preprocessing step for DeSpace.
|
| 104 |
+
|
| 105 |
+
This tool wraps the `cluster_sp_data` function from the DeSpace R package. It takes a
|
| 106 |
+
spatial Seurat object, normalizes the data, finds variable features, and performs
|
| 107 |
+
graph-based clustering.
|
| 108 |
+
|
| 109 |
+
Args:
|
| 110 |
+
sp_object_path: Path to the input spatial Seurat object (.rds file).
|
| 111 |
+
output_rds_path: Path to save the clustered spatial Seurat object (.rds file).
|
| 112 |
+
resolution: Clustering resolution for the Louvain algorithm.
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
A dictionary containing the executed command, stdout, stderr, and the path to the output file.
|
| 116 |
+
"""
|
| 117 |
+
# --- Input Validation ---
|
| 118 |
+
if not sp_object_path.is_file():
|
| 119 |
+
raise FileNotFoundError(f"Input spatial object not found: {sp_object_path}")
|
| 120 |
+
if not output_rds_path.parent.exists():
|
| 121 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 122 |
+
logging.info(f"Created output directory: {output_rds_path.parent}")
|
| 123 |
+
|
| 124 |
+
if resolution <= 0.0:
|
| 125 |
+
raise ValueError("resolution must be a positive float.")
|
| 126 |
+
|
| 127 |
+
# --- Command Construction ---
|
| 128 |
+
# This assumes a wrapper R script 'cluster_sp_data.R' is in the system's PATH.
|
| 129 |
+
cmd = [
|
| 130 |
+
"Rscript", "cluster_sp_data.R",
|
| 131 |
+
"--sp_object_path", str(sp_object_path),
|
| 132 |
+
"--output_rds_path", str(output_rds_path),
|
| 133 |
+
"--resolution", str(resolution),
|
| 134 |
+
]
|
| 135 |
+
command_executed = " ".join(cmd)
|
| 136 |
+
logging.info(f"Executing command: {command_executed}")
|
| 137 |
+
|
| 138 |
+
# --- Subprocess Execution ---
|
| 139 |
+
try:
|
| 140 |
+
process = subprocess.run(
|
| 141 |
+
cmd,
|
| 142 |
+
check=True,
|
| 143 |
+
capture_output=True,
|
| 144 |
+
text=True,
|
| 145 |
+
)
|
| 146 |
+
stdout = process.stdout
|
| 147 |
+
stderr = process.stderr
|
| 148 |
+
logging.info("Spatial data clustering completed successfully.")
|
| 149 |
+
except FileNotFoundError:
|
| 150 |
+
err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH."
|
| 151 |
+
logging.error(err_msg)
|
| 152 |
+
return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []}
|
| 153 |
+
except subprocess.CalledProcessError as e:
|
| 154 |
+
logging.error(f"Spatial data clustering failed with exit code {e.returncode}.")
|
| 155 |
+
return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []}
|
| 156 |
+
|
| 157 |
+
# --- Structured Result Return ---
|
| 158 |
+
return {
|
| 159 |
+
"command_executed": command_executed,
|
| 160 |
+
"stdout": stdout,
|
| 161 |
+
"stderr": stderr,
|
| 162 |
+
"output_files": [str(output_rds_path)]
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
@mcp.tool()
|
| 166 |
+
def run_despace(
|
| 167 |
+
sc_object_path: Path,
|
| 168 |
+
sp_object_path: Path,
|
| 169 |
+
output_rds_path: Path,
|
| 170 |
+
sc_assay: str = "RNA",
|
| 171 |
+
sc_slot: str = "counts",
|
| 172 |
+
sp_assay: str = "Spatial",
|
| 173 |
+
sp_slot: str = "counts",
|
| 174 |
+
num_markers_sc: int = 10,
|
| 175 |
+
num_markers_sp: int = 10,
|
| 176 |
+
s_topics: int = 10,
|
| 177 |
+
n_top_genes: int = 2000,
|
| 178 |
+
resolution: float = 0.8,
|
| 179 |
+
sample_id: Optional[str] = None,
|
| 180 |
+
num_threads: int = 1,
|
| 181 |
+
save_model: bool = False,
|
| 182 |
+
model_path: Optional[Path] = None,
|
| 183 |
+
) -> dict:
|
| 184 |
+
"""
|
| 185 |
+
Runs the main DeSpace algorithm to integrate single-cell and spatial transcriptomics data.
|
| 186 |
+
|
| 187 |
+
This tool wraps the `DeSpace` R function. It can perform clustering internally if not
|
| 188 |
+
already present in the input objects, or it can use pre-computed clusters. The main
|
| 189 |
+
output is a Seurat object with cell-type deconvolution results.
|
| 190 |
+
|
| 191 |
+
Args:
|
| 192 |
+
sc_object_path: Path to the single-cell Seurat object (.rds file).
|
| 193 |
+
sp_object_path: Path to the spatial transcriptomics Seurat object (.rds file).
|
| 194 |
+
output_rds_path: Path to save the resulting Seurat object with DeSpace results.
|
| 195 |
+
sc_assay: Assay to use from the single-cell Seurat object.
|
| 196 |
+
sc_slot: Slot to use from the single-cell assay (e.g., 'counts', 'data').
|
| 197 |
+
sp_assay: Assay to use from the spatial Seurat object.
|
| 198 |
+
sp_slot: Slot to use from the spatial assay (e.g., 'counts', 'data').
|
| 199 |
+
num_markers_sc: Number of markers to use for each single-cell cluster.
|
| 200 |
+
num_markers_sp: Number of markers to use for each spatial cluster.
|
| 201 |
+
s_topics: The number of topics (S) for topic modeling, used if sc-data is not pre-clustered.
|
| 202 |
+
n_top_genes: Number of highly variable genes, used if sc-data is not pre-clustered.
|
| 203 |
+
resolution: Clustering resolution, used if sp-data is not pre-clustered.
|
| 204 |
+
sample_id: Optional identifier for the sample, used for saving the model.
|
| 205 |
+
num_threads: Number of parallel threads to use.
|
| 206 |
+
save_model: If True, save the trained DeSpace model.
|
| 207 |
+
model_path: Path to save the DeSpace model file. Required if save_model is True.
|
| 208 |
+
|
| 209 |
+
Returns:
|
| 210 |
+
A dictionary containing the executed command, stdout, stderr, and a list of output files.
|
| 211 |
+
"""
|
| 212 |
+
# --- Input Validation ---
|
| 213 |
+
if not sc_object_path.is_file():
|
| 214 |
+
raise FileNotFoundError(f"Single-cell input file not found: {sc_object_path}")
|
| 215 |
+
if not sp_object_path.is_file():
|
| 216 |
+
raise FileNotFoundError(f"Spatial input file not found: {sp_object_path}")
|
| 217 |
+
|
| 218 |
+
if not output_rds_path.parent.exists():
|
| 219 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 220 |
+
logging.info(f"Created output directory: {output_rds_path.parent}")
|
| 221 |
+
|
| 222 |
+
if num_markers_sc <= 0:
|
| 223 |
+
raise ValueError("num_markers_sc must be a positive integer.")
|
| 224 |
+
if num_markers_sp <= 0:
|
| 225 |
+
raise ValueError("num_markers_sp must be a positive integer.")
|
| 226 |
+
if s_topics <= 0:
|
| 227 |
+
raise ValueError("s_topics must be a positive integer.")
|
| 228 |
+
if n_top_genes <= 0:
|
| 229 |
+
raise ValueError("n_top_genes must be a positive integer.")
|
| 230 |
+
if resolution <= 0.0:
|
| 231 |
+
raise ValueError("resolution must be a positive float.")
|
| 232 |
+
if num_threads <= 0:
|
| 233 |
+
raise ValueError("num_threads must be a positive integer.")
|
| 234 |
+
|
| 235 |
+
if save_model:
|
| 236 |
+
if model_path is None:
|
| 237 |
+
raise ValueError("model_path must be provided when save_model is True.")
|
| 238 |
+
if not model_path.parent.exists():
|
| 239 |
+
model_path.parent.mkdir(parents=True, exist_ok=True)
|
| 240 |
+
logging.info(f"Created model output directory: {model_path.parent}")
|
| 241 |
+
|
| 242 |
+
# --- Command Construction ---
|
| 243 |
+
# This assumes a wrapper R script 'run_despace.R' is in the system's PATH.
|
| 244 |
+
cmd = [
|
| 245 |
+
"Rscript", "run_despace.R",
|
| 246 |
+
"--sc_object_path", str(sc_object_path),
|
| 247 |
+
"--sp_object_path", str(sp_object_path),
|
| 248 |
+
"--output_rds_path", str(output_rds_path),
|
| 249 |
+
"--sc_assay", sc_assay,
|
| 250 |
+
"--sc_slot", sc_slot,
|
| 251 |
+
"--sp_assay", sp_assay,
|
| 252 |
+
"--sp_slot", sp_slot,
|
| 253 |
+
"--num_markers_sc", str(num_markers_sc),
|
| 254 |
+
"--num_markers_sp", str(num_markers_sp),
|
| 255 |
+
"--s_topics", str(s_topics),
|
| 256 |
+
"--n_top_genes", str(n_top_genes),
|
| 257 |
+
"--resolution", str(resolution),
|
| 258 |
+
"--num_threads", str(num_threads),
|
| 259 |
+
]
|
| 260 |
+
|
| 261 |
+
if sample_id:
|
| 262 |
+
cmd.extend(["--sample_id", sample_id])
|
| 263 |
+
|
| 264 |
+
if save_model and model_path:
|
| 265 |
+
cmd.append("--save_model")
|
| 266 |
+
cmd.extend(["--model_path", str(model_path)])
|
| 267 |
+
|
| 268 |
+
command_executed = " ".join(cmd)
|
| 269 |
+
logging.info(f"Executing command: {command_executed}")
|
| 270 |
+
|
| 271 |
+
# --- Subprocess Execution ---
|
| 272 |
+
try:
|
| 273 |
+
process = subprocess.run(
|
| 274 |
+
cmd,
|
| 275 |
+
check=True,
|
| 276 |
+
capture_output=True,
|
| 277 |
+
text=True,
|
| 278 |
+
)
|
| 279 |
+
stdout = process.stdout
|
| 280 |
+
stderr = process.stderr
|
| 281 |
+
logging.info("DeSpace execution completed successfully.")
|
| 282 |
+
except FileNotFoundError:
|
| 283 |
+
err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH."
|
| 284 |
+
logging.error(err_msg)
|
| 285 |
+
return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []}
|
| 286 |
+
except subprocess.CalledProcessError as e:
|
| 287 |
+
logging.error(f"DeSpace execution failed with exit code {e.returncode}.")
|
| 288 |
+
return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []}
|
| 289 |
+
|
| 290 |
+
# --- Structured Result Return ---
|
| 291 |
+
output_files: List[str] = [str(output_rds_path)]
|
| 292 |
+
if save_model and model_path:
|
| 293 |
+
output_files.append(str(model_path))
|
| 294 |
+
|
| 295 |
+
return {
|
| 296 |
+
"command_executed": command_executed,
|
| 297 |
+
"stdout": stdout,
|
| 298 |
+
"stderr": stderr,
|
| 299 |
+
"output_files": output_files
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
if __name__ == "__main__":
|
| 303 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_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-despace/app/bioconductor-despace_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_despace'
|
| 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-despace/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-despace/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-despace:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-despace:latest
|
| 7 |
+
container_name: mcp-bioconductor-despace
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-despace
|
| 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-despace/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-despace
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-despace/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-geomxtools/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-geomxtools via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-geomxtools -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-geomxtools_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-geomxtools_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-geomxtools_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py
ADDED
|
@@ -0,0 +1,688 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# In a real MCP environment, this would be imported.
|
| 7 |
+
# from mcp import tool as mcp_tool
|
| 8 |
+
# For this exercise, we assume the decorator @mcp.tool() is available.
|
| 9 |
+
class mcp:
|
| 10 |
+
@staticmethod
|
| 11 |
+
def tool():
|
| 12 |
+
def decorator(func):
|
| 13 |
+
return func
|
| 14 |
+
return decorator
|
| 15 |
+
|
| 16 |
+
from mcp.server.fastmcp import FastMCP
|
| 17 |
+
|
| 18 |
+
SERVER_NAME = 'local_bioconductor_geomxtools'
|
| 19 |
+
mcp = FastMCP(SERVER_NAME)
|
| 20 |
+
|
| 21 |
+
@mcp.tool()
|
| 22 |
+
def read_nanostring_geomx_set(
|
| 23 |
+
dcc_files_dir: Path,
|
| 24 |
+
pkc_files: List[Path],
|
| 25 |
+
pheno_data_file: Path,
|
| 26 |
+
output_rds_path: Path,
|
| 27 |
+
pheno_data_sheet: Optional[str] = None,
|
| 28 |
+
pheno_data_dcc_col_name: str = "Sample_ID",
|
| 29 |
+
protocol_data_col_names: Optional[List[str]] = None,
|
| 30 |
+
experiment_data_col_names: Optional[List[str]] = None,
|
| 31 |
+
) -> dict:
|
| 32 |
+
"""
|
| 33 |
+
Reads NanoString GeoMx files (DCC, PKC, annotation) and creates a NanoStringGeomxSet object.
|
| 34 |
+
|
| 35 |
+
This tool is a wrapper around the `readNanoStringGeoMxSet` function from the
|
| 36 |
+
R/Bioconductor package `GeomxTools`. It processes raw data into a structured
|
| 37 |
+
R object for downstream analysis.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
dcc_files_dir: Path to the directory containing DCC files.
|
| 41 |
+
pkc_files: A list of paths to PKC files.
|
| 42 |
+
pheno_data_file: Path to the sample annotation file (e.g., an Excel file).
|
| 43 |
+
output_rds_path: Path for the output RDS file which will contain the NanoStringGeomxSet object.
|
| 44 |
+
pheno_data_sheet: Optional name of the worksheet in the Excel annotation file.
|
| 45 |
+
pheno_data_dcc_col_name: Column name in the annotation file that matches DCC file names.
|
| 46 |
+
protocol_data_col_names: Optional list of column names in annotation to be added to protocolData.
|
| 47 |
+
experiment_data_col_names: Optional list of column names in annotation to be added to experimentData.
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 51 |
+
"""
|
| 52 |
+
# --- Input Validation ---
|
| 53 |
+
if not dcc_files_dir.is_dir():
|
| 54 |
+
raise ValueError(f"DCC files directory not found: {dcc_files_dir}")
|
| 55 |
+
if not pkc_files:
|
| 56 |
+
raise ValueError("At least one PKC file must be provided.")
|
| 57 |
+
for pkc_file in pkc_files:
|
| 58 |
+
if not pkc_file.is_file():
|
| 59 |
+
raise ValueError(f"PKC file not found: {pkc_file}")
|
| 60 |
+
if not pheno_data_file.is_file():
|
| 61 |
+
raise ValueError(f"Phenotype data file not found: {pheno_data_file}")
|
| 62 |
+
|
| 63 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 64 |
+
|
| 65 |
+
# --- R Script Generation ---
|
| 66 |
+
# Safely create R vectors from Python lists
|
| 67 |
+
r_protocol_cols = f'c({", ".join(f"{col}" for col in protocol_data_col_names)})' if protocol_data_col_names else "NULL"
|
| 68 |
+
r_experiment_cols = f'c({", ".join(f"{col}" for col in experiment_data_col_names)})' if experiment_data_col_names else "NULL"
|
| 69 |
+
r_pheno_sheet = f'"{pheno_data_sheet}"' if pheno_data_sheet else "NULL"
|
| 70 |
+
pkc_files_r_vector = f'c({", ".join(f"{str(p)}" for p in pkc_files)})'
|
| 71 |
+
|
| 72 |
+
r_script_content = f"""
|
| 73 |
+
library(GeomxTools)
|
| 74 |
+
|
| 75 |
+
tryCatch({{
|
| 76 |
+
dcc_dir <- "{dcc_files_dir}"
|
| 77 |
+
pkc_files_vec <- {pkc_files_r_vector}
|
| 78 |
+
pheno_file <- "{pheno_data_file}"
|
| 79 |
+
output_path <- "{output_rds_path}"
|
| 80 |
+
pheno_dcc_col <- "{pheno_data_dcc_col_name}"
|
| 81 |
+
|
| 82 |
+
dcc_files <- list.files(dcc_dir, pattern = "\\\\.dcc$", full.names = TRUE, recursive = TRUE)
|
| 83 |
+
if (length(dcc_files) == 0) {{
|
| 84 |
+
stop("No .dcc files found in the specified directory.")
|
| 85 |
+
}}
|
| 86 |
+
|
| 87 |
+
geomx_data <- readNanoStringGeoMxSet(
|
| 88 |
+
dccFiles = dcc_files,
|
| 89 |
+
pkcFiles = pkc_files_vec,
|
| 90 |
+
phenoDataFile = pheno_file,
|
| 91 |
+
phenoDataSheet = {r_pheno_sheet},
|
| 92 |
+
phenoDataDccColName = pheno_dcc_col,
|
| 93 |
+
protocolDataColNames = {r_protocol_cols},
|
| 94 |
+
experimentDataColNames = {r_experiment_cols}
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
saveRDS(geomx_data, file = output_path)
|
| 98 |
+
cat("Successfully created NanoStringGeomxSet object and saved to", output_path, "\\n")
|
| 99 |
+
}}, error = function(e) {{
|
| 100 |
+
message("R script failed with error: ", e$message)
|
| 101 |
+
quit(status = 1)
|
| 102 |
+
}})
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
# --- Subprocess Execution ---
|
| 106 |
+
command_executed = ""
|
| 107 |
+
try:
|
| 108 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 109 |
+
r_script_file.write(r_script_content)
|
| 110 |
+
r_script_path = r_script_file.name
|
| 111 |
+
|
| 112 |
+
cmd = ["Rscript", r_script_path]
|
| 113 |
+
command_executed = " ".join(cmd)
|
| 114 |
+
|
| 115 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 116 |
+
|
| 117 |
+
return {
|
| 118 |
+
"command_executed": command_executed,
|
| 119 |
+
"stdout": process.stdout,
|
| 120 |
+
"stderr": process.stderr,
|
| 121 |
+
"output_files": [str(output_rds_path)]
|
| 122 |
+
}
|
| 123 |
+
except FileNotFoundError:
|
| 124 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 125 |
+
except subprocess.CalledProcessError as e:
|
| 126 |
+
return {
|
| 127 |
+
"command_executed": command_executed,
|
| 128 |
+
"stdout": e.stdout,
|
| 129 |
+
"stderr": e.stderr,
|
| 130 |
+
"error": "R script execution failed.",
|
| 131 |
+
"return_code": e.returncode,
|
| 132 |
+
"output_files": []
|
| 133 |
+
}
|
| 134 |
+
finally:
|
| 135 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 136 |
+
Path(r_script_path).unlink()
|
| 137 |
+
|
| 138 |
+
@mcp.tool()
|
| 139 |
+
def set_segment_qc_flags(
|
| 140 |
+
input_rds_path: Path,
|
| 141 |
+
output_rds_path: Path,
|
| 142 |
+
min_segment_reads: Optional[int] = 1000,
|
| 143 |
+
percent_aligned: Optional[float] = 80,
|
| 144 |
+
percent_saturation: Optional[float] = 50,
|
| 145 |
+
min_negative_count: Optional[int] = 10,
|
| 146 |
+
max_ntc_count: Optional[int] = 1000,
|
| 147 |
+
min_nuclei: Optional[int] = 200,
|
| 148 |
+
min_area: Optional[int] = 16000,
|
| 149 |
+
) -> dict:
|
| 150 |
+
"""
|
| 151 |
+
Sets segment QC flags in a NanoStringGeomxSet object based on specified cutoffs.
|
| 152 |
+
|
| 153 |
+
Args:
|
| 154 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 155 |
+
output_rds_path: Path for the output RDS file with QC flags applied.
|
| 156 |
+
min_segment_reads: Minimum number of reads in a segment.
|
| 157 |
+
percent_aligned: Minimum percentage of reads aligned.
|
| 158 |
+
percent_saturation: Minimum percentage of reads saturated.
|
| 159 |
+
min_negative_count: Minimum negative probe counts.
|
| 160 |
+
max_ntc_count: Maximum counts observed in NTC wells.
|
| 161 |
+
min_nuclei: Minimum number of nuclei in a segment.
|
| 162 |
+
min_area: Minimum area of a segment.
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 166 |
+
"""
|
| 167 |
+
# --- Input Validation ---
|
| 168 |
+
if not input_rds_path.is_file():
|
| 169 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 170 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 171 |
+
|
| 172 |
+
# --- R Script Generation ---
|
| 173 |
+
r_script_content = f"""
|
| 174 |
+
library(GeomxTools)
|
| 175 |
+
|
| 176 |
+
tryCatch({{
|
| 177 |
+
input_rds <- "{input_rds_path}"
|
| 178 |
+
output_rds <- "{output_rds_path}"
|
| 179 |
+
|
| 180 |
+
geomx_data <- readRDS(input_rds)
|
| 181 |
+
|
| 182 |
+
qc_cutoffs <- list()
|
| 183 |
+
if (!is.null({min_segment_reads or 'NULL'})) {{ qc_cutoffs$minSegmentReads <- {min_segment_reads} }}
|
| 184 |
+
if (!is.null({percent_aligned or 'NULL'})) {{ qc_cutoffs$percentAligned <- {percent_aligned} }}
|
| 185 |
+
if (!is.null({percent_saturation or 'NULL'})) {{ qc_cutoffs$percentSaturation <- {percent_saturation} }}
|
| 186 |
+
if (!is.null({min_negative_count or 'NULL'})) {{ qc_cutoffs$minNegativeCount <- {min_negative_count} }}
|
| 187 |
+
if (!is.null({max_ntc_count or 'NULL'})) {{ qc_cutoffs$maxNTCCount <- {max_ntc_count} }}
|
| 188 |
+
if (!is.null({min_nuclei or 'NULL'})) {{ qc_cutoffs$minNuclei <- {min_nuclei} }}
|
| 189 |
+
if (!is.null({min_area or 'NULL'})) {{ qc_cutoffs$minArea <- {min_area} }}
|
| 190 |
+
|
| 191 |
+
geomx_data_qc <- setSegmentQCFlags(geomx_data, qcCutoffs = qc_cutoffs)
|
| 192 |
+
|
| 193 |
+
saveRDS(geomx_data_qc, file = output_rds)
|
| 194 |
+
cat("Successfully applied segment QC flags and saved to", output_rds, "\\n")
|
| 195 |
+
}}, error = function(e) {{
|
| 196 |
+
message("R script failed with error: ", e$message)
|
| 197 |
+
quit(status = 1)
|
| 198 |
+
}})
|
| 199 |
+
"""
|
| 200 |
+
|
| 201 |
+
# --- Subprocess Execution ---
|
| 202 |
+
command_executed = ""
|
| 203 |
+
try:
|
| 204 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 205 |
+
r_script_file.write(r_script_content)
|
| 206 |
+
r_script_path = r_script_file.name
|
| 207 |
+
|
| 208 |
+
cmd = ["Rscript", r_script_path]
|
| 209 |
+
command_executed = " ".join(cmd)
|
| 210 |
+
|
| 211 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 212 |
+
|
| 213 |
+
return {
|
| 214 |
+
"command_executed": command_executed,
|
| 215 |
+
"stdout": process.stdout,
|
| 216 |
+
"stderr": process.stderr,
|
| 217 |
+
"output_files": [str(output_rds_path)]
|
| 218 |
+
}
|
| 219 |
+
except FileNotFoundError:
|
| 220 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 221 |
+
except subprocess.CalledProcessError as e:
|
| 222 |
+
return {
|
| 223 |
+
"command_executed": command_executed,
|
| 224 |
+
"stdout": e.stdout,
|
| 225 |
+
"stderr": e.stderr,
|
| 226 |
+
"error": "R script execution failed.",
|
| 227 |
+
"return_code": e.returncode,
|
| 228 |
+
"output_files": []
|
| 229 |
+
}
|
| 230 |
+
finally:
|
| 231 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 232 |
+
Path(r_script_path).unlink()
|
| 233 |
+
|
| 234 |
+
@mcp.tool()
|
| 235 |
+
def set_bioprobe_qc_flags(
|
| 236 |
+
input_rds_path: Path,
|
| 237 |
+
output_rds_path: Path,
|
| 238 |
+
min_probe_ratio: float = 0.1,
|
| 239 |
+
percent_fail_grubbs: float = 20,
|
| 240 |
+
remove_local_outliers: bool = True,
|
| 241 |
+
) -> dict:
|
| 242 |
+
"""
|
| 243 |
+
Sets probe QC flags in a NanoStringGeomxSet object.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 247 |
+
output_rds_path: Path for the output RDS file with QC flags applied.
|
| 248 |
+
min_probe_ratio: Minimum ratio of probes to the geometric mean of all probes.
|
| 249 |
+
percent_fail_grubbs: Percentage of segments that must fail Grubbs test for a probe to be flagged.
|
| 250 |
+
remove_local_outliers: If TRUE, local outliers will be removed.
|
| 251 |
+
|
| 252 |
+
Returns:
|
| 253 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 254 |
+
"""
|
| 255 |
+
# --- Input Validation ---
|
| 256 |
+
if not input_rds_path.is_file():
|
| 257 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 258 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 259 |
+
|
| 260 |
+
# --- R Script Generation ---
|
| 261 |
+
r_script_content = f"""
|
| 262 |
+
library(GeomxTools)
|
| 263 |
+
|
| 264 |
+
tryCatch({{
|
| 265 |
+
input_rds <- "{input_rds_path}"
|
| 266 |
+
output_rds <- "{output_rds_path}"
|
| 267 |
+
|
| 268 |
+
geomx_data <- readRDS(input_rds)
|
| 269 |
+
|
| 270 |
+
qc_cutoffs <- list(
|
| 271 |
+
minProbeRatio = {min_probe_ratio},
|
| 272 |
+
percentFailGrubbs = {percent_fail_grubbs}
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
geomx_data_qc <- setBioProbeQCFlags(
|
| 276 |
+
geomx_data,
|
| 277 |
+
qcCutoffs = qc_cutoffs,
|
| 278 |
+
removeLocalOutliers = {str(remove_local_outliers).upper()}
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
saveRDS(geomx_data_qc, file = output_rds)
|
| 282 |
+
cat("Successfully applied bioprobe QC flags and saved to", output_rds, "\\n")
|
| 283 |
+
}}, error = function(e) {{
|
| 284 |
+
message("R script failed with error: ", e$message)
|
| 285 |
+
quit(status = 1)
|
| 286 |
+
}})
|
| 287 |
+
"""
|
| 288 |
+
|
| 289 |
+
# --- Subprocess Execution ---
|
| 290 |
+
command_executed = ""
|
| 291 |
+
try:
|
| 292 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 293 |
+
r_script_file.write(r_script_content)
|
| 294 |
+
r_script_path = r_script_file.name
|
| 295 |
+
|
| 296 |
+
cmd = ["Rscript", r_script_path]
|
| 297 |
+
command_executed = " ".join(cmd)
|
| 298 |
+
|
| 299 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 300 |
+
|
| 301 |
+
return {
|
| 302 |
+
"command_executed": command_executed,
|
| 303 |
+
"stdout": process.stdout,
|
| 304 |
+
"stderr": process.stderr,
|
| 305 |
+
"output_files": [str(output_rds_path)]
|
| 306 |
+
}
|
| 307 |
+
except FileNotFoundError:
|
| 308 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 309 |
+
except subprocess.CalledProcessError as e:
|
| 310 |
+
return {
|
| 311 |
+
"command_executed": command_executed,
|
| 312 |
+
"stdout": e.stdout,
|
| 313 |
+
"stderr": e.stderr,
|
| 314 |
+
"error": "R script execution failed.",
|
| 315 |
+
"return_code": e.returncode,
|
| 316 |
+
"output_files": []
|
| 317 |
+
}
|
| 318 |
+
finally:
|
| 319 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 320 |
+
Path(r_script_path).unlink()
|
| 321 |
+
|
| 322 |
+
@mcp.tool()
|
| 323 |
+
def subset_geomx_set(
|
| 324 |
+
input_rds_path: Path,
|
| 325 |
+
output_rds_path: Path,
|
| 326 |
+
subset_by_pdata: bool = True,
|
| 327 |
+
subset_column: Optional[str] = None,
|
| 328 |
+
subset_values: Optional[List[str]] = None,
|
| 329 |
+
) -> dict:
|
| 330 |
+
"""
|
| 331 |
+
Subsets a NanoStringGeomxSet object based on phenotype (pData) or feature (fData) annotations.
|
| 332 |
+
|
| 333 |
+
Args:
|
| 334 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 335 |
+
output_rds_path: Path for the output subsetted RDS file.
|
| 336 |
+
subset_by_pdata: If True, subset by sample annotations (pData). If False, subset by feature annotations (fData).
|
| 337 |
+
subset_column: The column name in pData or fData to use for subsetting.
|
| 338 |
+
subset_values: A list of values to keep from the subset_column.
|
| 339 |
+
|
| 340 |
+
Returns:
|
| 341 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 342 |
+
"""
|
| 343 |
+
# --- Input Validation ---
|
| 344 |
+
if not input_rds_path.is_file():
|
| 345 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 346 |
+
if not subset_column or not subset_values:
|
| 347 |
+
raise ValueError("subset_column and subset_values must be provided for subsetting.")
|
| 348 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 349 |
+
|
| 350 |
+
# --- R Script Generation ---
|
| 351 |
+
subset_values_r = f'c({", ".join(f"{v}" for v in subset_values)})'
|
| 352 |
+
|
| 353 |
+
r_script_content = f"""
|
| 354 |
+
library(GeomxTools)
|
| 355 |
+
library(Biobase)
|
| 356 |
+
|
| 357 |
+
tryCatch({{
|
| 358 |
+
object <- readRDS("{input_rds_path}")
|
| 359 |
+
subset_col <- "{subset_column}"
|
| 360 |
+
subset_vals <- {subset_values_r}
|
| 361 |
+
|
| 362 |
+
if ({str(subset_by_pdata).upper()}) {{
|
| 363 |
+
logic <- pData(object)[[subset_col]] %in% subset_vals
|
| 364 |
+
logic[is.na(logic)] <- FALSE
|
| 365 |
+
subset_object <- object[, logic]
|
| 366 |
+
}} else {{
|
| 367 |
+
logic <- fData(object)[[subset_col]] %in% subset_vals
|
| 368 |
+
logic[is.na(logic)] <- FALSE
|
| 369 |
+
subset_object <- object[logic, ]
|
| 370 |
+
}}
|
| 371 |
+
|
| 372 |
+
saveRDS(subset_object, file = "{output_rds_path}")
|
| 373 |
+
cat("Successfully subset object and saved to", "{output_rds_path}", "\\n")
|
| 374 |
+
}}, error = function(e) {{
|
| 375 |
+
message("R script failed with error: ", e$message)
|
| 376 |
+
quit(status = 1)
|
| 377 |
+
}})
|
| 378 |
+
"""
|
| 379 |
+
|
| 380 |
+
# --- Subprocess Execution ---
|
| 381 |
+
command_executed = ""
|
| 382 |
+
try:
|
| 383 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 384 |
+
r_script_file.write(r_script_content)
|
| 385 |
+
r_script_path = r_script_file.name
|
| 386 |
+
|
| 387 |
+
cmd = ["Rscript", r_script_path]
|
| 388 |
+
command_executed = " ".join(cmd)
|
| 389 |
+
|
| 390 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 391 |
+
|
| 392 |
+
return {
|
| 393 |
+
"command_executed": command_executed,
|
| 394 |
+
"stdout": process.stdout,
|
| 395 |
+
"stderr": process.stderr,
|
| 396 |
+
"output_files": [str(output_rds_path)]
|
| 397 |
+
}
|
| 398 |
+
except FileNotFoundError:
|
| 399 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 400 |
+
except subprocess.CalledProcessError as e:
|
| 401 |
+
return {
|
| 402 |
+
"command_executed": command_executed,
|
| 403 |
+
"stdout": e.stdout,
|
| 404 |
+
"stderr": e.stderr,
|
| 405 |
+
"error": "R script execution failed.",
|
| 406 |
+
"return_code": e.returncode,
|
| 407 |
+
"output_files": []
|
| 408 |
+
}
|
| 409 |
+
finally:
|
| 410 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 411 |
+
Path(r_script_path).unlink()
|
| 412 |
+
|
| 413 |
+
@mcp.tool()
|
| 414 |
+
def aggregate_counts(
|
| 415 |
+
input_rds_path: Path,
|
| 416 |
+
output_rds_path: Path,
|
| 417 |
+
elt: str = "exprs",
|
| 418 |
+
) -> dict:
|
| 419 |
+
"""
|
| 420 |
+
Aggregates probe-level counts to the target level in a NanoStringGeomxSet object.
|
| 421 |
+
|
| 422 |
+
Args:
|
| 423 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 424 |
+
output_rds_path: Path for the output RDS file with aggregated counts.
|
| 425 |
+
elt: The name of the assay data element to aggregate.
|
| 426 |
+
|
| 427 |
+
Returns:
|
| 428 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 429 |
+
"""
|
| 430 |
+
# --- Input Validation ---
|
| 431 |
+
if not input_rds_path.is_file():
|
| 432 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 433 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 434 |
+
|
| 435 |
+
# --- R Script Generation ---
|
| 436 |
+
r_script_content = f"""
|
| 437 |
+
library(GeomxTools)
|
| 438 |
+
|
| 439 |
+
tryCatch({{
|
| 440 |
+
geomx_data <- readRDS("{input_rds_path}")
|
| 441 |
+
aggregated_data <- aggregateCounts(geomx_data, elt = "{elt}")
|
| 442 |
+
|
| 443 |
+
saveRDS(aggregated_data, file = "{output_rds_path}")
|
| 444 |
+
cat("Successfully aggregated counts and saved to", "{output_rds_path}", "\\n")
|
| 445 |
+
}}, error = function(e) {{
|
| 446 |
+
message("R script failed with error: ", e$message)
|
| 447 |
+
quit(status = 1)
|
| 448 |
+
}})
|
| 449 |
+
"""
|
| 450 |
+
|
| 451 |
+
# --- Subprocess Execution ---
|
| 452 |
+
command_executed = ""
|
| 453 |
+
try:
|
| 454 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 455 |
+
r_script_file.write(r_script_content)
|
| 456 |
+
r_script_path = r_script_file.name
|
| 457 |
+
|
| 458 |
+
cmd = ["Rscript", r_script_path]
|
| 459 |
+
command_executed = " ".join(cmd)
|
| 460 |
+
|
| 461 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 462 |
+
|
| 463 |
+
return {
|
| 464 |
+
"command_executed": command_executed,
|
| 465 |
+
"stdout": process.stdout,
|
| 466 |
+
"stderr": process.stderr,
|
| 467 |
+
"output_files": [str(output_rds_path)]
|
| 468 |
+
}
|
| 469 |
+
except FileNotFoundError:
|
| 470 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 471 |
+
except subprocess.CalledProcessError as e:
|
| 472 |
+
return {
|
| 473 |
+
"command_executed": command_executed,
|
| 474 |
+
"stdout": e.stdout,
|
| 475 |
+
"stderr": e.stderr,
|
| 476 |
+
"error": "R script execution failed.",
|
| 477 |
+
"return_code": e.returncode,
|
| 478 |
+
"output_files": []
|
| 479 |
+
}
|
| 480 |
+
finally:
|
| 481 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 482 |
+
Path(r_script_path).unlink()
|
| 483 |
+
|
| 484 |
+
@mcp.tool()
|
| 485 |
+
def normalize_geomx(
|
| 486 |
+
input_rds_path: Path,
|
| 487 |
+
output_rds_path: Path,
|
| 488 |
+
norm_method: str,
|
| 489 |
+
from_elt: str = "exprs",
|
| 490 |
+
to_elt: str = "exprs_norm",
|
| 491 |
+
housekeepers: Optional[List[str]] = None,
|
| 492 |
+
) -> dict:
|
| 493 |
+
"""
|
| 494 |
+
Normalizes the count data in a NanoStringGeomxSet object.
|
| 495 |
+
|
| 496 |
+
Args:
|
| 497 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 498 |
+
output_rds_path: Path for the output RDS file with normalized data.
|
| 499 |
+
norm_method: Normalization method. Must be one of 'quant', 'neg', 'hk'.
|
| 500 |
+
from_elt: The assay data element to use for normalization.
|
| 501 |
+
to_elt: The name of the new assay data element to store normalized values.
|
| 502 |
+
housekeepers: A list of housekeeper gene names, required if norm_method is 'hk'.
|
| 503 |
+
|
| 504 |
+
Returns:
|
| 505 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file.
|
| 506 |
+
"""
|
| 507 |
+
# --- Input Validation ---
|
| 508 |
+
if not input_rds_path.is_file():
|
| 509 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 510 |
+
|
| 511 |
+
valid_methods = ["quant", "neg", "hk"]
|
| 512 |
+
if norm_method not in valid_methods:
|
| 513 |
+
raise ValueError(f"Invalid norm_method '{norm_method}'. Must be one of {valid_methods}.")
|
| 514 |
+
|
| 515 |
+
if norm_method == "hk" and not housekeepers:
|
| 516 |
+
raise ValueError("Housekeeper genes must be provided for 'hk' normalization.")
|
| 517 |
+
|
| 518 |
+
output_rds_path.parent.mkdir(parents=True, exist_ok=True)
|
| 519 |
+
|
| 520 |
+
housekeepers_r = f'c({", ".join(f"{hk}" for hk in housekeepers)})' if housekeepers else "NULL"
|
| 521 |
+
|
| 522 |
+
# --- R Script Generation ---
|
| 523 |
+
r_script_content = f"""
|
| 524 |
+
library(GeomxTools)
|
| 525 |
+
|
| 526 |
+
tryCatch({{
|
| 527 |
+
geomx_data <- readRDS("{input_rds_path}")
|
| 528 |
+
|
| 529 |
+
normalized_data <- normalize(
|
| 530 |
+
geomx_data,
|
| 531 |
+
norm.method = "{norm_method}",
|
| 532 |
+
fromElt = "{from_elt}",
|
| 533 |
+
toElt = "{to_elt}",
|
| 534 |
+
housekeepers = {housekeepers_r}
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
saveRDS(normalized_data, file = "{output_rds_path}")
|
| 538 |
+
cat("Successfully normalized data and saved to", "{output_rds_path}", "\\n")
|
| 539 |
+
}}, error = function(e) {{
|
| 540 |
+
message("R script failed with error: ", e$message)
|
| 541 |
+
quit(status = 1)
|
| 542 |
+
}})
|
| 543 |
+
"""
|
| 544 |
+
|
| 545 |
+
# --- Subprocess Execution ---
|
| 546 |
+
command_executed = ""
|
| 547 |
+
try:
|
| 548 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 549 |
+
r_script_file.write(r_script_content)
|
| 550 |
+
r_script_path = r_script_file.name
|
| 551 |
+
|
| 552 |
+
cmd = ["Rscript", r_script_path]
|
| 553 |
+
command_executed = " ".join(cmd)
|
| 554 |
+
|
| 555 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 556 |
+
|
| 557 |
+
return {
|
| 558 |
+
"command_executed": command_executed,
|
| 559 |
+
"stdout": process.stdout,
|
| 560 |
+
"stderr": process.stderr,
|
| 561 |
+
"output_files": [str(output_rds_path)]
|
| 562 |
+
}
|
| 563 |
+
except FileNotFoundError:
|
| 564 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 565 |
+
except subprocess.CalledProcessError as e:
|
| 566 |
+
return {
|
| 567 |
+
"command_executed": command_executed,
|
| 568 |
+
"stdout": e.stdout,
|
| 569 |
+
"stderr": e.stderr,
|
| 570 |
+
"error": "R script execution failed.",
|
| 571 |
+
"return_code": e.returncode,
|
| 572 |
+
"output_files": []
|
| 573 |
+
}
|
| 574 |
+
finally:
|
| 575 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 576 |
+
Path(r_script_path).unlink()
|
| 577 |
+
|
| 578 |
+
@mcp.tool()
|
| 579 |
+
def mixed_model_de(
|
| 580 |
+
input_rds_path: Path,
|
| 581 |
+
output_csv_path: Path,
|
| 582 |
+
elt: str,
|
| 583 |
+
model_formula_fixed: str,
|
| 584 |
+
group_var: str,
|
| 585 |
+
model_formula_random: Optional[str] = None,
|
| 586 |
+
contrasts: Optional[List[str]] = None,
|
| 587 |
+
n_cores: int = 1,
|
| 588 |
+
) -> dict:
|
| 589 |
+
"""
|
| 590 |
+
Performs differential expression analysis using a linear mixed model.
|
| 591 |
+
|
| 592 |
+
Args:
|
| 593 |
+
input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object.
|
| 594 |
+
output_csv_path: Path for the output CSV file with DE results.
|
| 595 |
+
elt: The assay data element to use for the analysis (e.g., 'exprs_norm').
|
| 596 |
+
model_formula_fixed: The fixed effects part of the model formula (e.g., 'region + diseaseStatus').
|
| 597 |
+
group_var: The main variable of interest for testing (e.g., 'diseaseStatus').
|
| 598 |
+
model_formula_random: The random effects part of the model formula (e.g., '(1|slideName)').
|
| 599 |
+
contrasts: A list of contrasts to test (e.g., ['diseaseA - diseaseB', 'diseaseC - diseaseB']).
|
| 600 |
+
n_cores: Number of cores to use for parallel processing.
|
| 601 |
+
|
| 602 |
+
Returns:
|
| 603 |
+
A dictionary containing the execution command, stdout, stderr, and the path to the output CSV file.
|
| 604 |
+
"""
|
| 605 |
+
# --- Input Validation ---
|
| 606 |
+
if not input_rds_path.is_file():
|
| 607 |
+
raise ValueError(f"Input RDS file not found: {input_rds_path}")
|
| 608 |
+
if n_cores < 1:
|
| 609 |
+
raise ValueError("n_cores must be at least 1.")
|
| 610 |
+
output_csv_path.parent.mkdir(parents=True, exist_ok=True)
|
| 611 |
+
|
| 612 |
+
# --- R Script Generation ---
|
| 613 |
+
formula_str = f"~ {model_formula_fixed}"
|
| 614 |
+
if model_formula_random:
|
| 615 |
+
formula_str += f" + {model_formula_random}"
|
| 616 |
+
|
| 617 |
+
contrasts_r = "NULL"
|
| 618 |
+
if contrasts:
|
| 619 |
+
contrasts_r = f'c({", ".join(f"{c}" for c in contrasts)})'
|
| 620 |
+
|
| 621 |
+
r_script_content = f"""
|
| 622 |
+
library(GeomxTools)
|
| 623 |
+
library(limma)
|
| 624 |
+
library(Biobase)
|
| 625 |
+
|
| 626 |
+
tryCatch({{
|
| 627 |
+
object <- readRDS("{input_rds_path}")
|
| 628 |
+
|
| 629 |
+
model_formula <- as.formula("{formula_str}")
|
| 630 |
+
|
| 631 |
+
contrast_matrix <- NULL
|
| 632 |
+
if (!is.null({contrasts_r})) {{
|
| 633 |
+
contrast_matrix <- makeContrasts(contrasts = {contrasts_r}, levels = unique(pData(object)[["{group_var}"]]))
|
| 634 |
+
}}
|
| 635 |
+
|
| 636 |
+
results <- mixedModelDE(
|
| 637 |
+
object,
|
| 638 |
+
elt = "{elt}",
|
| 639 |
+
modelFormula = model_formula,
|
| 640 |
+
groupVar = "{group_var}",
|
| 641 |
+
nCores = {n_cores},
|
| 642 |
+
multiCore = {str(n_cores > 1).upper()},
|
| 643 |
+
contrasts = contrast_matrix
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
write.csv(results, file = "{output_csv_path}", row.names = FALSE)
|
| 647 |
+
cat("Successfully performed DE analysis and saved results to", "{output_csv_path}", "\\n")
|
| 648 |
+
}}, error = function(e) {{
|
| 649 |
+
message("R script failed with error: ", e$message)
|
| 650 |
+
quit(status = 1)
|
| 651 |
+
}})
|
| 652 |
+
"""
|
| 653 |
+
|
| 654 |
+
# --- Subprocess Execution ---
|
| 655 |
+
command_executed = ""
|
| 656 |
+
try:
|
| 657 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file:
|
| 658 |
+
r_script_file.write(r_script_content)
|
| 659 |
+
r_script_path = r_script_file.name
|
| 660 |
+
|
| 661 |
+
cmd = ["Rscript", r_script_path]
|
| 662 |
+
command_executed = " ".join(cmd)
|
| 663 |
+
|
| 664 |
+
process = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 665 |
+
|
| 666 |
+
return {
|
| 667 |
+
"command_executed": command_executed,
|
| 668 |
+
"stdout": process.stdout,
|
| 669 |
+
"stderr": process.stderr,
|
| 670 |
+
"output_files": [str(output_csv_path)]
|
| 671 |
+
}
|
| 672 |
+
except FileNotFoundError:
|
| 673 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 674 |
+
except subprocess.CalledProcessError as e:
|
| 675 |
+
return {
|
| 676 |
+
"command_executed": command_executed,
|
| 677 |
+
"stdout": e.stdout,
|
| 678 |
+
"stderr": e.stderr,
|
| 679 |
+
"error": "R script execution failed.",
|
| 680 |
+
"return_code": e.returncode,
|
| 681 |
+
"output_files": []
|
| 682 |
+
}
|
| 683 |
+
finally:
|
| 684 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 685 |
+
Path(r_script_path).unlink()
|
| 686 |
+
|
| 687 |
+
if __name__ == "__main__":
|
| 688 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_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-geomxtools/app/bioconductor-geomxtools_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_geomxtools'
|
| 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-geomxtools/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-geomxtools:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-geomxtools:latest
|
| 7 |
+
container_name: mcp-bioconductor-geomxtools
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-geomxtools
|
| 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-geomxtools/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-geomxtools
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-geomxtools/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-glmgampoi/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-glmgampoi:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-glmgampoi:latest
|
| 7 |
+
container_name: mcp-bioconductor-glmgampoi
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-glmgampoi
|
| 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-glmgampoi/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-infercnv/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-infercnv via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-infercnv -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-infercnv_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-infercnv_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-infercnv_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_server.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
import tempfile
|
| 6 |
+
import shlex
|
| 7 |
+
|
| 8 |
+
from mcp.server.fastmcp import FastMCP
|
| 9 |
+
|
| 10 |
+
SERVER_NAME = 'local_bioconductor_infercnv'
|
| 11 |
+
mcp = FastMCP(SERVER_NAME)
|
| 12 |
+
|
| 13 |
+
@mcp.tool()
|
| 14 |
+
def infercnv_run(
|
| 15 |
+
raw_counts_matrix: str,
|
| 16 |
+
annotations_file: str,
|
| 17 |
+
gene_order_file: str,
|
| 18 |
+
out_dir: str,
|
| 19 |
+
ref_group_names: Optional[List[str]] = None,
|
| 20 |
+
cutoff: float = 1.0,
|
| 21 |
+
min_cells_per_gene: int = 3,
|
| 22 |
+
cluster_by_groups: bool = True,
|
| 23 |
+
denoise: bool = False,
|
| 24 |
+
hmm: bool = False,
|
| 25 |
+
hmm_type: str = "i6",
|
| 26 |
+
analysis_mode: str = "samples",
|
| 27 |
+
num_threads: int = 1,
|
| 28 |
+
plot_steps: bool = False,
|
| 29 |
+
no_plot: bool = False,
|
| 30 |
+
window_length: int = 101,
|
| 31 |
+
max_centered_threshold: float = 3.0,
|
| 32 |
+
leiden_resolution: float = 0.05,
|
| 33 |
+
):
|
| 34 |
+
"""
|
| 35 |
+
Run the full InferCNV pipeline to identify somatic copy number alterations in single-cell RNA-seq data.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
raw_counts_matrix: Path to the matrix of gene expression counts (genes as rows, cells as columns).
|
| 39 |
+
annotations_file: Path to the cell annotations file (cell name and group).
|
| 40 |
+
gene_order_file: Path to the gene positions file (gene, chromosome, start, stop).
|
| 41 |
+
out_dir: Directory to save the output files.
|
| 42 |
+
ref_group_names: List of group names to use as reference (normal) cells. If None, all cells are used.
|
| 43 |
+
cutoff: Threshold for gene expression. Use 1.0 for Smart-seq2 and 0.1 for 10x Genomics.
|
| 44 |
+
min_cells_per_gene: Minimum number of cells a gene must be expressed in to be kept.
|
| 45 |
+
cluster_by_groups: Whether to cluster cells by their annotation groups.
|
| 46 |
+
denoise: Whether to apply denoising filters.
|
| 47 |
+
hmm: Whether to run the Hidden Markov Model (HMM) to predict CNV states.
|
| 48 |
+
hmm_type: Type of HMM to use ('i6' for 6-state model, 'i3' for 3-state model).
|
| 49 |
+
analysis_mode: Analysis mode ('samples' or 'subclusters').
|
| 50 |
+
num_threads: Number of CPU threads to use for parallel processing.
|
| 51 |
+
plot_steps: Whether to generate plots for every intermediate step.
|
| 52 |
+
no_plot: If True, skips the final heatmap generation.
|
| 53 |
+
window_length: Length of the moving average window for smoothing.
|
| 54 |
+
max_centered_threshold: Maximum value for centering the expression data.
|
| 55 |
+
leiden_resolution: Resolution for Leiden clustering if analysis_mode is 'subclusters'.
|
| 56 |
+
"""
|
| 57 |
+
# Input validation
|
| 58 |
+
raw_path = Path(raw_counts_matrix)
|
| 59 |
+
ann_path = Path(annotations_file)
|
| 60 |
+
gene_path = Path(gene_order_file)
|
| 61 |
+
out_path = Path(out_dir)
|
| 62 |
+
|
| 63 |
+
if not raw_path.exists():
|
| 64 |
+
return {"error": f"Raw counts matrix not found: {raw_counts_matrix}"}
|
| 65 |
+
if not ann_path.exists():
|
| 66 |
+
return {"error": f"Annotations file not found: {annotations_file}"}
|
| 67 |
+
if not gene_path.exists():
|
| 68 |
+
return {"error": f"Gene order file not found: {gene_order_file}"}
|
| 69 |
+
|
| 70 |
+
if hmm_type not in ["i6", "i3"]:
|
| 71 |
+
return {"error": "hmm_type must be either 'i6' or 'i3'"}
|
| 72 |
+
|
| 73 |
+
if analysis_mode not in ["samples", "subclusters"]:
|
| 74 |
+
return {"error": "analysis_mode must be either 'samples' or 'subclusters'"}
|
| 75 |
+
|
| 76 |
+
os.makedirs(out_path, exist_ok=True)
|
| 77 |
+
|
| 78 |
+
# Prepare R vector for reference groups
|
| 79 |
+
if ref_group_names:
|
| 80 |
+
ref_groups_r = "c(" + ", ".join([f"'{g}'" for g in ref_group_names]) + ")"
|
| 81 |
+
else:
|
| 82 |
+
ref_groups_r = "NULL"
|
| 83 |
+
|
| 84 |
+
# Construct R script
|
| 85 |
+
r_script_content = f"""
|
| 86 |
+
library(infercnv)
|
| 87 |
+
|
| 88 |
+
# Create InferCNV Object
|
| 89 |
+
infercnv_obj = CreateInfercnvObject(
|
| 90 |
+
raw_counts_matrix = "{raw_path.absolute()}",
|
| 91 |
+
gene_order_file = "{gene_path.absolute()}",
|
| 92 |
+
annotations_file = "{ann_path.absolute()}",
|
| 93 |
+
ref_group_names = {ref_groups_r}
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
# Run InferCNV Pipeline
|
| 97 |
+
infercnv_obj = infercnv::run(
|
| 98 |
+
infercnv_obj,
|
| 99 |
+
cutoff = {cutoff},
|
| 100 |
+
min_cells_per_gene = {min_cells_per_gene},
|
| 101 |
+
out_dir = "{out_path.absolute()}",
|
| 102 |
+
cluster_by_groups = {str(cluster_by_groups).upper()},
|
| 103 |
+
denoise = {str(denoise).upper()},
|
| 104 |
+
HMM = {str(hmm).upper()},
|
| 105 |
+
HMM_type = "{hmm_type}",
|
| 106 |
+
analysis_mode = "{analysis_mode}",
|
| 107 |
+
num_threads = {num_threads},
|
| 108 |
+
plot_steps = {str(plot_steps).upper()},
|
| 109 |
+
no_plot = {str(no_plot).upper()},
|
| 110 |
+
window_length = {window_length},
|
| 111 |
+
max_centered_threshold = {max_centered_threshold},
|
| 112 |
+
leiden_resolution = {leiden_resolution}
|
| 113 |
+
)
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
try:
|
| 117 |
+
with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp:
|
| 118 |
+
tmp.write(r_script_content)
|
| 119 |
+
tmp_path = tmp.name
|
| 120 |
+
|
| 121 |
+
cmd = ["Rscript", tmp_path]
|
| 122 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 123 |
+
|
| 124 |
+
# Cleanup temp file
|
| 125 |
+
os.unlink(tmp_path)
|
| 126 |
+
|
| 127 |
+
# Identify output files
|
| 128 |
+
output_files = [str(f) for f in out_path.glob("*") if f.is_file()]
|
| 129 |
+
|
| 130 |
+
return {
|
| 131 |
+
"command_executed": " ".join(cmd),
|
| 132 |
+
"stdout": result.stdout,
|
| 133 |
+
"stderr": result.stderr,
|
| 134 |
+
"output_files": output_files,
|
| 135 |
+
"status": "success"
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
except subprocess.CalledProcessError as e:
|
| 139 |
+
if os.path.exists(tmp_path):
|
| 140 |
+
os.unlink(tmp_path)
|
| 141 |
+
return {
|
| 142 |
+
"command_executed": " ".join(e.cmd),
|
| 143 |
+
"stdout": e.stdout,
|
| 144 |
+
"stderr": e.stderr,
|
| 145 |
+
"error": "InferCNV execution failed."
|
| 146 |
+
}
|
| 147 |
+
except Exception as e:
|
| 148 |
+
return {"error": str(e)}
|
| 149 |
+
|
| 150 |
+
@mcp.tool()
|
| 151 |
+
def infercnv_plot(
|
| 152 |
+
infercnv_obj_path: str,
|
| 153 |
+
out_dir: str,
|
| 154 |
+
output_filename: str = "infercnv_plot",
|
| 155 |
+
color_safe_pal: bool = False,
|
| 156 |
+
title: str = "InferCNV Heatmap",
|
| 157 |
+
cluster_by_groups: bool = True,
|
| 158 |
+
x_center: float = 1.0,
|
| 159 |
+
x_range: Optional[float] = None,
|
| 160 |
+
custom_color_pal: Optional[List[str]] = None,
|
| 161 |
+
):
|
| 162 |
+
"""
|
| 163 |
+
Generate or regenerate plots from a saved InferCNV object.
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
infercnv_obj_path: Path to the saved .rds or .obj InferCNV object.
|
| 167 |
+
out_dir: Directory to save the plot.
|
| 168 |
+
output_filename: Name of the output plot file (without extension).
|
| 169 |
+
color_safe_pal: Use a color-blind safe palette.
|
| 170 |
+
title: Title of the plot.
|
| 171 |
+
cluster_by_groups: Whether to cluster by groups in the plot.
|
| 172 |
+
x_center: Value to center the color scale on (usually 1.0).
|
| 173 |
+
x_range: Range of values to display (e.g., 0.1 means 0.9 to 1.1).
|
| 174 |
+
custom_color_pal: Optional list of colors for the heatmap palette.
|
| 175 |
+
"""
|
| 176 |
+
obj_path = Path(infercnv_obj_path)
|
| 177 |
+
out_path = Path(out_dir)
|
| 178 |
+
|
| 179 |
+
if not obj_path.exists():
|
| 180 |
+
return {"error": f"InferCNV object not found: {infercnv_obj_path}"}
|
| 181 |
+
|
| 182 |
+
os.makedirs(out_path, exist_ok=True)
|
| 183 |
+
|
| 184 |
+
x_range_r = f"{x_range}" if x_range is not None else "NULL"
|
| 185 |
+
color_pal_r = "NULL"
|
| 186 |
+
if custom_color_pal:
|
| 187 |
+
color_pal_r = "c(" + ", ".join([f"'{c}'" for c in custom_color_pal]) + ")"
|
| 188 |
+
|
| 189 |
+
r_script_content = f"""
|
| 190 |
+
library(infercnv)
|
| 191 |
+
infercnv_obj = readRDS("{obj_path.absolute()}")
|
| 192 |
+
|
| 193 |
+
plot_cnv(
|
| 194 |
+
infercnv_obj,
|
| 195 |
+
out_dir = "{out_path.absolute()}",
|
| 196 |
+
output_filename = "{output_filename}",
|
| 197 |
+
color_safe_pal = {str(color_safe_pal).upper()},
|
| 198 |
+
title = "{title}",
|
| 199 |
+
cluster_by_groups = {str(cluster_by_groups).upper()},
|
| 200 |
+
x_center = {x_center},
|
| 201 |
+
x_range = {x_range_r},
|
| 202 |
+
custom_color_pal = {color_pal_r}
|
| 203 |
+
)
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
try:
|
| 207 |
+
with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp:
|
| 208 |
+
tmp.write(r_script_content)
|
| 209 |
+
tmp_path = tmp.name
|
| 210 |
+
|
| 211 |
+
cmd = ["Rscript", tmp_path]
|
| 212 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 213 |
+
os.unlink(tmp_path)
|
| 214 |
+
|
| 215 |
+
return {
|
| 216 |
+
"command_executed": " ".join(cmd),
|
| 217 |
+
"stdout": result.stdout,
|
| 218 |
+
"stderr": result.stderr,
|
| 219 |
+
"output_files": [str(f) for f in out_path.glob(f"{output_filename}*")],
|
| 220 |
+
"status": "success"
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
except subprocess.CalledProcessError as e:
|
| 224 |
+
if os.path.exists(tmp_path):
|
| 225 |
+
os.unlink(tmp_path)
|
| 226 |
+
return {
|
| 227 |
+
"command_executed": " ".join(e.cmd),
|
| 228 |
+
"stdout": e.stdout,
|
| 229 |
+
"stderr": e.stderr,
|
| 230 |
+
"error": "InferCNV plotting failed."
|
| 231 |
+
}
|
| 232 |
+
except Exception as e:
|
| 233 |
+
return {"error": str(e)}
|
| 234 |
+
|
| 235 |
+
@mcp.tool()
|
| 236 |
+
def infercnv_filter_genes(
|
| 237 |
+
raw_counts_matrix: str,
|
| 238 |
+
gene_order_file: str,
|
| 239 |
+
output_matrix_path: str,
|
| 240 |
+
min_cells_per_gene: int = 3,
|
| 241 |
+
):
|
| 242 |
+
"""
|
| 243 |
+
Pre-filter a counts matrix to remove genes expressed in fewer than a threshold number of cells.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
raw_counts_matrix: Path to the input counts matrix.
|
| 247 |
+
gene_order_file: Path to the gene order file.
|
| 248 |
+
output_matrix_path: Path to save the filtered matrix.
|
| 249 |
+
min_cells_per_gene: Minimum number of cells a gene must be expressed in.
|
| 250 |
+
"""
|
| 251 |
+
raw_path = Path(raw_counts_matrix)
|
| 252 |
+
gene_path = Path(gene_order_file)
|
| 253 |
+
out_path = Path(output_matrix_path)
|
| 254 |
+
|
| 255 |
+
if not raw_path.exists():
|
| 256 |
+
return {"error": f"Input matrix not found: {raw_counts_matrix}"}
|
| 257 |
+
|
| 258 |
+
r_script_content = f"""
|
| 259 |
+
library(infercnv)
|
| 260 |
+
# Load data
|
| 261 |
+
counts = read.table("{raw_path.absolute()}", header=TRUE, row.names=1, check.names=FALSE)
|
| 262 |
+
# Filter
|
| 263 |
+
gene_counts = rowSums(counts > 0)
|
| 264 |
+
filtered_counts = counts[gene_counts >= {min_cells_per_gene}, ]
|
| 265 |
+
# Save
|
| 266 |
+
write.table(filtered_counts, file="{out_path.absolute()}", quote=FALSE, sep='\\t')
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
try:
|
| 270 |
+
with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp:
|
| 271 |
+
tmp.write(r_script_content)
|
| 272 |
+
tmp_path = tmp.name
|
| 273 |
+
|
| 274 |
+
cmd = ["Rscript", tmp_path]
|
| 275 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 276 |
+
os.unlink(tmp_path)
|
| 277 |
+
|
| 278 |
+
return {
|
| 279 |
+
"command_executed": " ".join(cmd),
|
| 280 |
+
"stdout": result.stdout,
|
| 281 |
+
"stderr": result.stderr,
|
| 282 |
+
"output_files": [str(out_path)],
|
| 283 |
+
"status": "success"
|
| 284 |
+
}
|
| 285 |
+
except subprocess.CalledProcessError as e:
|
| 286 |
+
if os.path.exists(tmp_path):
|
| 287 |
+
os.unlink(tmp_path)
|
| 288 |
+
return {
|
| 289 |
+
"command_executed": " ".join(e.cmd),
|
| 290 |
+
"stdout": e.stdout,
|
| 291 |
+
"stderr": e.stderr,
|
| 292 |
+
"error": "Filtering failed."
|
| 293 |
+
}
|
| 294 |
+
except Exception as e:
|
| 295 |
+
return {"error": str(e)}
|
| 296 |
+
|
| 297 |
+
if __name__ == "__main__":
|
| 298 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-infercnv/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-infercnv:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-infercnv:latest
|
| 7 |
+
container_name: mcp-bioconductor-infercnv
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-infercnv
|
| 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-infercnv/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-infercnv
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-infercnv/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/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-org.hs.eg.db via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-org.hs.eg.db -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-org.hs.eg.db_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-org.hs.eg.db_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-org.hs.eg.db_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import json
|
| 3 |
+
import tempfile
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Optional, Dict, Any
|
| 6 |
+
|
| 7 |
+
def run_r_command(script: str) -> Dict[str, Any]:
|
| 8 |
+
"""
|
| 9 |
+
Helper function to execute R code and capture output.
|
| 10 |
+
Uses jsonlite in R to return structured data.
|
| 11 |
+
"""
|
| 12 |
+
# Wrap the script to load the library and output JSON
|
| 13 |
+
full_script = f"""
|
| 14 |
+
suppressPackageStartupMessages(library(org.hs.eg.db))
|
| 15 |
+
suppressPackageStartupMessages(library(jsonlite))
|
| 16 |
+
|
| 17 |
+
tryCatch({{
|
| 18 |
+
{script}
|
| 19 |
+
}}, error = function(e) {{
|
| 20 |
+
write(paste("ERROR:", e$message), stderr())
|
| 21 |
+
q(status = 1)
|
| 22 |
+
}})
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
|
| 26 |
+
tmp.write(full_script)
|
| 27 |
+
tmp_path = tmp.name
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
process = subprocess.run(
|
| 31 |
+
["Rscript", tmp_path],
|
| 32 |
+
capture_output=True,
|
| 33 |
+
text=True,
|
| 34 |
+
check=True
|
| 35 |
+
)
|
| 36 |
+
return {
|
| 37 |
+
"stdout": process.stdout,
|
| 38 |
+
"stderr": process.stderr,
|
| 39 |
+
"command_executed": f"Rscript {tmp_path}"
|
| 40 |
+
}
|
| 41 |
+
except subprocess.CalledProcessError as e:
|
| 42 |
+
return {
|
| 43 |
+
"error": e.stderr or e.stdout,
|
| 44 |
+
"command_executed": f"Rscript {tmp_path}",
|
| 45 |
+
"stdout": e.stdout,
|
| 46 |
+
"stderr": e.stderr
|
| 47 |
+
}
|
| 48 |
+
finally:
|
| 49 |
+
if Path(tmp_path).exists():
|
| 50 |
+
Path(tmp_path).unlink()
|
| 51 |
+
|
| 52 |
+
from mcp.server.fastmcp import FastMCP
|
| 53 |
+
|
| 54 |
+
SERVER_NAME = 'local_bioconductor_org_hs_eg_db'
|
| 55 |
+
mcp = FastMCP(SERVER_NAME)
|
| 56 |
+
|
| 57 |
+
@mcp.tool()
|
| 58 |
+
def org_hs_eg_db_select(
|
| 59 |
+
keys: List[str],
|
| 60 |
+
columns: List[str],
|
| 61 |
+
keytype: str = "ENTREZID",
|
| 62 |
+
) -> Dict[str, Any]:
|
| 63 |
+
"""
|
| 64 |
+
Retrieve annotations for the specified keys from the Human (org.hs.eg.db) database.
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
keys: A list of identifiers to look up (e.g., ["7157", "4312"] or ["TP53", "BRCA1"]).
|
| 68 |
+
columns: The types of data to retrieve (e.g., ["SYMBOL", "GENENAME", "ENSEMBL"]).
|
| 69 |
+
keytype: The type of the input keys (e.g., "ENTREZID", "SYMBOL", "ENSEMBL").
|
| 70 |
+
"""
|
| 71 |
+
# Validation
|
| 72 |
+
if not keys:
|
| 73 |
+
return {"error": "At least one key must be provided."}
|
| 74 |
+
if not columns:
|
| 75 |
+
return {"error": "At least one column must be provided."}
|
| 76 |
+
|
| 77 |
+
# Format R vectors
|
| 78 |
+
r_keys = 'c("' + '","'.join(keys) + '")'
|
| 79 |
+
r_cols = 'c("' + '","'.join(columns) + '")'
|
| 80 |
+
|
| 81 |
+
script = f"""
|
| 82 |
+
res <- select(org.hs.eg.db, keys = {r_keys}, columns = {r_cols}, keytype = "{keytype}")
|
| 83 |
+
cat(toJSON(res, pretty = TRUE))
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
result = run_r_command(script)
|
| 87 |
+
return result
|
| 88 |
+
|
| 89 |
+
@mcp.tool()
|
| 90 |
+
def org_hs_eg_db_keytypes() -> Dict[str, Any]:
|
| 91 |
+
"""
|
| 92 |
+
List all available types of identifiers (keytypes) that can be used as input for queries.
|
| 93 |
+
Common types include ENTREZID, SYMBOL, ENSEMBL, and UNIPROT.
|
| 94 |
+
"""
|
| 95 |
+
script = """
|
| 96 |
+
res <- keytypes(org.hs.eg.db)
|
| 97 |
+
cat(toJSON(res))
|
| 98 |
+
"""
|
| 99 |
+
return run_r_command(script)
|
| 100 |
+
|
| 101 |
+
@mcp.tool()
|
| 102 |
+
def org_hs_eg_db_columns() -> Dict[str, Any]:
|
| 103 |
+
"""
|
| 104 |
+
List all available annotation columns that can be retrieved from the database.
|
| 105 |
+
"""
|
| 106 |
+
script = """
|
| 107 |
+
res <- columns(org.hs.eg.db)
|
| 108 |
+
cat(toJSON(res))
|
| 109 |
+
"""
|
| 110 |
+
return run_r_command(script)
|
| 111 |
+
|
| 112 |
+
@mcp.tool()
|
| 113 |
+
def org_hs_eg_db_map_symbol_to_entrez(
|
| 114 |
+
symbols: List[str]
|
| 115 |
+
) -> Dict[str, Any]:
|
| 116 |
+
"""
|
| 117 |
+
A convenience tool to quickly map Human Gene Symbols to Entrez IDs.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
symbols: List of gene symbols (e.g., ["TP53", "APOE"]).
|
| 121 |
+
"""
|
| 122 |
+
if not symbols:
|
| 123 |
+
return {"error": "No symbols provided."}
|
| 124 |
+
|
| 125 |
+
r_keys = 'c("' + '","'.join(symbols) + '")'
|
| 126 |
+
script = f"""
|
| 127 |
+
res <- select(org.hs.eg.db, keys = {r_keys}, columns = c("ENTREZID"), keytype = "SYMBOL")
|
| 128 |
+
cat(toJSON(res, pretty = TRUE))
|
| 129 |
+
"""
|
| 130 |
+
return run_r_command(script)
|
| 131 |
+
|
| 132 |
+
@mcp.tool()
|
| 133 |
+
def org_hs_eg_db_get_keys(
|
| 134 |
+
keytype: str = "SYMBOL",
|
| 135 |
+
pattern: str = "",
|
| 136 |
+
limit: int = 100
|
| 137 |
+
) -> Dict[str, Any]:
|
| 138 |
+
"""
|
| 139 |
+
Retrieve a list of all valid keys of a specific type, optionally filtered by a pattern.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
keytype: The type of keys to retrieve (e.g., "SYMBOL", "ENSEMBL").
|
| 143 |
+
pattern: A string pattern to filter keys (uses grep-style matching).
|
| 144 |
+
limit: Maximum number of keys to return to prevent overwhelming output.
|
| 145 |
+
"""
|
| 146 |
+
if limit <= 0:
|
| 147 |
+
limit = 100
|
| 148 |
+
|
| 149 |
+
script = f"""
|
| 150 |
+
all_keys <- keys(org.hs.eg.db, keytype = "{keytype}")
|
| 151 |
+
if ("{pattern}" != "") {{
|
| 152 |
+
all_keys <- all_keys[grep("{pattern}", all_keys)]
|
| 153 |
+
}}
|
| 154 |
+
res <- head(all_keys, {limit})
|
| 155 |
+
cat(toJSON(res))
|
| 156 |
+
"""
|
| 157 |
+
return run_r_command(script)
|
| 158 |
+
|
| 159 |
+
@mcp.tool()
|
| 160 |
+
def org_hs_eg_db_metadata() -> Dict[str, Any]:
|
| 161 |
+
"""
|
| 162 |
+
Get metadata about the org.hs.eg.db package, including version, organism, and data sources.
|
| 163 |
+
"""
|
| 164 |
+
script = """
|
| 165 |
+
res <- metadata(org.hs.eg.db)
|
| 166 |
+
cat(toJSON(res, pretty = TRUE))
|
| 167 |
+
"""
|
| 168 |
+
return run_r_command(script)
|
| 169 |
+
|
| 170 |
+
@mcp.tool()
|
| 171 |
+
def org_hs_eg_db_map_ids(
|
| 172 |
+
ids: List[str],
|
| 173 |
+
from_type: str,
|
| 174 |
+
to_type: str
|
| 175 |
+
) -> Dict[str, Any]:
|
| 176 |
+
"""
|
| 177 |
+
Generic tool to map identifiers from one type to another.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
ids: List of input identifiers.
|
| 181 |
+
from_type: The source identifier type (e.g., "ENSEMBL").
|
| 182 |
+
to_type: The target identifier type (e.g., "SYMBOL").
|
| 183 |
+
"""
|
| 184 |
+
if not ids:
|
| 185 |
+
return {"error": "No IDs provided."}
|
| 186 |
+
|
| 187 |
+
r_keys = 'c("' + '","'.join(ids) + '")'
|
| 188 |
+
script = f"""
|
| 189 |
+
res <- select(org.hs.eg.db, keys = {r_keys}, columns = c("{to_type}"), keytype = "{from_type}")
|
| 190 |
+
cat(toJSON(res, pretty = TRUE))
|
| 191 |
+
"""
|
| 192 |
+
return run_r_command(script)
|
| 193 |
+
|
| 194 |
+
if __name__ == "__main__":
|
| 195 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_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-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_org_hs_eg_db'
|
| 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-org.hs.eg.db/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-org.hs.eg.db:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-org.hs.eg.db:latest
|
| 7 |
+
container_name: mcp-bioconductor-org.hs.eg.db
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-org.hs.eg.db
|
| 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-org.hs.eg.db/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-org.hs.eg.db
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-preprocesscore/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-preprocesscore via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-preprocesscore -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-preprocesscore_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-preprocesscore_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-preprocesscore_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
import tempfile
|
| 6 |
+
|
| 7 |
+
def run_r_command(script_content: str):
|
| 8 |
+
"""Helper to execute R code and handle errors."""
|
| 9 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
|
| 10 |
+
tmp.write("library(preprocessCore)\n")
|
| 11 |
+
tmp.write(script_content)
|
| 12 |
+
tmp_path = tmp.name
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
result = subprocess.run(
|
| 16 |
+
["Rscript", tmp_path],
|
| 17 |
+
capture_output=True,
|
| 18 |
+
text=True,
|
| 19 |
+
check=True
|
| 20 |
+
)
|
| 21 |
+
return result.stdout, result.stderr
|
| 22 |
+
except subprocess.CalledProcessError as e:
|
| 23 |
+
raise RuntimeError(f"R execution failed: {e.stderr}\nStdout: {e.stdout}")
|
| 24 |
+
finally:
|
| 25 |
+
if os.path.exists(tmp_path):
|
| 26 |
+
os.remove(tmp_path)
|
| 27 |
+
|
| 28 |
+
from mcp.server.fastmcp import FastMCP
|
| 29 |
+
|
| 30 |
+
SERVER_NAME = 'local_bioconductor_preprocesscore'
|
| 31 |
+
mcp = FastMCP(SERVER_NAME)
|
| 32 |
+
|
| 33 |
+
@mcp.tool()
|
| 34 |
+
def preprocesscore_normalize_quantiles(
|
| 35 |
+
input_file: str,
|
| 36 |
+
output_file: str,
|
| 37 |
+
keep_names: bool = True,
|
| 38 |
+
sep: str = ",",
|
| 39 |
+
header: bool = True
|
| 40 |
+
):
|
| 41 |
+
"""
|
| 42 |
+
Perform Quantile Normalization on a numeric matrix.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
input_file: Path to the input CSV/TSV file containing the matrix.
|
| 46 |
+
output_file: Path where the normalized matrix will be saved.
|
| 47 |
+
keep_names: Whether to preserve row and column names in the output.
|
| 48 |
+
sep: Delimiter used in the input file (e.g., ',' or '\t').
|
| 49 |
+
header: Whether the input file has a header row.
|
| 50 |
+
"""
|
| 51 |
+
input_path = Path(input_file)
|
| 52 |
+
output_path = Path(output_file)
|
| 53 |
+
|
| 54 |
+
if not input_path.exists():
|
| 55 |
+
return {"error": f"Input file {input_file} not found."}
|
| 56 |
+
|
| 57 |
+
r_script = f"""
|
| 58 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 59 |
+
normalized_data <- normalize.quantiles(data, keep.names={str(keep_names).upper()})
|
| 60 |
+
write.table(normalized_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE)
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
stdout, stderr = run_r_command(r_script)
|
| 65 |
+
return {
|
| 66 |
+
"command_executed": "normalize.quantiles",
|
| 67 |
+
"stdout": stdout,
|
| 68 |
+
"stderr": stderr,
|
| 69 |
+
"output_files": [str(output_path)]
|
| 70 |
+
}
|
| 71 |
+
except Exception as e:
|
| 72 |
+
return {"error": str(e)}
|
| 73 |
+
|
| 74 |
+
@mcp.tool()
|
| 75 |
+
def preprocesscore_normalize_quantiles_robust(
|
| 76 |
+
input_file: str,
|
| 77 |
+
output_file: str,
|
| 78 |
+
remove_extreme: str = "both",
|
| 79 |
+
n_remove: int = 1,
|
| 80 |
+
use_log2: bool = False,
|
| 81 |
+
sep: str = ",",
|
| 82 |
+
header: bool = True
|
| 83 |
+
):
|
| 84 |
+
"""
|
| 85 |
+
Perform Robust Quantile Normalization.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
input_file: Path to the input CSV/TSV file.
|
| 89 |
+
output_file: Path for the output file.
|
| 90 |
+
remove_extreme: How to remove outliers: 'none', 'left', 'right', or 'both'.
|
| 91 |
+
n_remove: Number of extreme values to remove.
|
| 92 |
+
use_log2: Whether to apply log2 transformation before normalization.
|
| 93 |
+
sep: Delimiter used in the input file.
|
| 94 |
+
header: Whether the input file has a header row.
|
| 95 |
+
"""
|
| 96 |
+
input_path = Path(input_file)
|
| 97 |
+
output_path = Path(output_file)
|
| 98 |
+
|
| 99 |
+
if not input_path.exists():
|
| 100 |
+
return {"error": f"Input file {input_file} not found."}
|
| 101 |
+
|
| 102 |
+
if remove_extreme not in ["none", "left", "right", "both"]:
|
| 103 |
+
return {"error": "remove_extreme must be one of: none, left, right, both"}
|
| 104 |
+
|
| 105 |
+
r_script = f"""
|
| 106 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 107 |
+
normalized_data <- normalize.quantiles.robust(
|
| 108 |
+
data,
|
| 109 |
+
remove.extreme="{remove_extreme}",
|
| 110 |
+
n.remove={n_remove},
|
| 111 |
+
use.log2={str(use_log2).upper()}
|
| 112 |
+
)
|
| 113 |
+
write.table(normalized_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE)
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
try:
|
| 117 |
+
stdout, stderr = run_r_command(r_script)
|
| 118 |
+
return {
|
| 119 |
+
"command_executed": "normalize.quantiles.robust",
|
| 120 |
+
"stdout": stdout,
|
| 121 |
+
"stderr": stderr,
|
| 122 |
+
"output_files": [str(output_path)]
|
| 123 |
+
}
|
| 124 |
+
except Exception as e:
|
| 125 |
+
return {"error": str(e)}
|
| 126 |
+
|
| 127 |
+
@mcp.tool()
|
| 128 |
+
def preprocesscore_background_correct(
|
| 129 |
+
input_file: str,
|
| 130 |
+
output_file: str,
|
| 131 |
+
method: str = "rma",
|
| 132 |
+
sep: str = ",",
|
| 133 |
+
header: bool = True
|
| 134 |
+
):
|
| 135 |
+
"""
|
| 136 |
+
Perform background correction on a matrix of intensities.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
input_file: Path to the input CSV/TSV file.
|
| 140 |
+
output_file: Path for the output file.
|
| 141 |
+
method: Correction method (typically 'rma').
|
| 142 |
+
sep: Delimiter used in the input file.
|
| 143 |
+
header: Whether the input file has a header row.
|
| 144 |
+
"""
|
| 145 |
+
input_path = Path(input_file)
|
| 146 |
+
output_path = Path(output_file)
|
| 147 |
+
|
| 148 |
+
if not input_path.exists():
|
| 149 |
+
return {"error": f"Input file {input_file} not found."}
|
| 150 |
+
|
| 151 |
+
r_script = f"""
|
| 152 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 153 |
+
corrected_data <- background.correct(data, method="{method}")
|
| 154 |
+
write.table(corrected_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE)
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
stdout, stderr = run_r_command(r_script)
|
| 159 |
+
return {
|
| 160 |
+
"command_executed": f"background.correct(method='{method}')",
|
| 161 |
+
"stdout": stdout,
|
| 162 |
+
"stderr": stderr,
|
| 163 |
+
"output_files": [str(output_path)]
|
| 164 |
+
}
|
| 165 |
+
except Exception as e:
|
| 166 |
+
return {"error": str(e)}
|
| 167 |
+
|
| 168 |
+
@mcp.tool()
|
| 169 |
+
def preprocesscore_sub_col_summarize_median_polish(
|
| 170 |
+
input_file: str,
|
| 171 |
+
output_file: str,
|
| 172 |
+
group_labels: List[int],
|
| 173 |
+
sep: str = ",",
|
| 174 |
+
header: bool = True
|
| 175 |
+
):
|
| 176 |
+
"""
|
| 177 |
+
Summarize columns of a matrix using Median Polish based on group labels.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
input_file: Path to the input CSV/TSV file.
|
| 181 |
+
output_file: Path for the output file.
|
| 182 |
+
group_labels: A list of integers representing the group for each column (e.g., [1, 1, 2, 2]).
|
| 183 |
+
sep: Delimiter used in the input file.
|
| 184 |
+
header: Whether the input file has a header row.
|
| 185 |
+
"""
|
| 186 |
+
input_path = Path(input_file)
|
| 187 |
+
output_path = Path(output_file)
|
| 188 |
+
|
| 189 |
+
if not input_path.exists():
|
| 190 |
+
return {"error": f"Input file {input_file} not found."}
|
| 191 |
+
|
| 192 |
+
# Convert Python list to R vector string
|
| 193 |
+
r_groups = f"c({','.join(map(str, group_labels))})"
|
| 194 |
+
|
| 195 |
+
r_script = f"""
|
| 196 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 197 |
+
groups <- as.integer({r_groups})
|
| 198 |
+
summarized <- subColSummarizeMedianPolish(data, groups)
|
| 199 |
+
write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE)
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
try:
|
| 203 |
+
stdout, stderr = run_r_command(r_script)
|
| 204 |
+
return {
|
| 205 |
+
"command_executed": "subColSummarizeMedianPolish",
|
| 206 |
+
"stdout": stdout,
|
| 207 |
+
"stderr": stderr,
|
| 208 |
+
"output_files": [str(output_path)]
|
| 209 |
+
}
|
| 210 |
+
except Exception as e:
|
| 211 |
+
return {"error": str(e)}
|
| 212 |
+
|
| 213 |
+
@mcp.tool()
|
| 214 |
+
def preprocesscore_sub_col_summarize_log_avg(
|
| 215 |
+
input_file: str,
|
| 216 |
+
output_file: str,
|
| 217 |
+
group_labels: List[int],
|
| 218 |
+
sep: str = ",",
|
| 219 |
+
header: bool = True
|
| 220 |
+
):
|
| 221 |
+
"""
|
| 222 |
+
Summarize columns of a matrix using Log-Average based on group labels.
|
| 223 |
+
|
| 224 |
+
Args:
|
| 225 |
+
input_file: Path to the input CSV/TSV file.
|
| 226 |
+
output_file: Path for the output file.
|
| 227 |
+
group_labels: A list of integers representing the group for each column.
|
| 228 |
+
"""
|
| 229 |
+
input_path = Path(input_file)
|
| 230 |
+
output_path = Path(output_file)
|
| 231 |
+
|
| 232 |
+
if not input_path.exists():
|
| 233 |
+
return {"error": f"Input file {input_file} not found."}
|
| 234 |
+
|
| 235 |
+
r_groups = f"c({','.join(map(str, group_labels))})"
|
| 236 |
+
|
| 237 |
+
r_script = f"""
|
| 238 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 239 |
+
groups <- as.integer({r_groups})
|
| 240 |
+
summarized <- subColSummarizeLogAvg(data, groups)
|
| 241 |
+
write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE)
|
| 242 |
+
"""
|
| 243 |
+
|
| 244 |
+
try:
|
| 245 |
+
stdout, stderr = run_r_command(r_script)
|
| 246 |
+
return {
|
| 247 |
+
"command_executed": "subColSummarizeLogAvg",
|
| 248 |
+
"stdout": stdout,
|
| 249 |
+
"stderr": stderr,
|
| 250 |
+
"output_files": [str(output_path)]
|
| 251 |
+
}
|
| 252 |
+
except Exception as e:
|
| 253 |
+
return {"error": str(e)}
|
| 254 |
+
|
| 255 |
+
@mcp.tool()
|
| 256 |
+
def preprocesscore_sub_col_summarize_log_median(
|
| 257 |
+
input_file: str,
|
| 258 |
+
output_file: str,
|
| 259 |
+
group_labels: List[int],
|
| 260 |
+
sep: str = ",",
|
| 261 |
+
header: bool = True
|
| 262 |
+
):
|
| 263 |
+
"""
|
| 264 |
+
Summarize columns of a matrix using Log-Median based on group labels.
|
| 265 |
+
|
| 266 |
+
Args:
|
| 267 |
+
input_file: Path to the input CSV/TSV file.
|
| 268 |
+
output_file: Path for the output file.
|
| 269 |
+
group_labels: A list of integers representing the group for each column.
|
| 270 |
+
"""
|
| 271 |
+
input_path = Path(input_file)
|
| 272 |
+
output_path = Path(output_file)
|
| 273 |
+
|
| 274 |
+
if not input_path.exists():
|
| 275 |
+
return {"error": f"Input file {input_file} not found."}
|
| 276 |
+
|
| 277 |
+
r_groups = f"c({','.join(map(str, group_labels))})"
|
| 278 |
+
|
| 279 |
+
r_script = f"""
|
| 280 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 281 |
+
groups <- as.integer({r_groups})
|
| 282 |
+
summarized <- subColSummarizeLogMedian(data, groups)
|
| 283 |
+
write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE)
|
| 284 |
+
"""
|
| 285 |
+
|
| 286 |
+
try:
|
| 287 |
+
stdout, stderr = run_r_command(r_script)
|
| 288 |
+
return {
|
| 289 |
+
"command_executed": "subColSummarizeLogMedian",
|
| 290 |
+
"stdout": stdout,
|
| 291 |
+
"stderr": stderr,
|
| 292 |
+
"output_files": [str(output_path)]
|
| 293 |
+
}
|
| 294 |
+
except Exception as e:
|
| 295 |
+
return {"error": str(e)}
|
| 296 |
+
|
| 297 |
+
@mcp.tool()
|
| 298 |
+
def preprocesscore_sub_col_summarize_biweight_midavg(
|
| 299 |
+
input_file: str,
|
| 300 |
+
output_file: str,
|
| 301 |
+
group_labels: List[int],
|
| 302 |
+
sep: str = ",",
|
| 303 |
+
header: bool = True
|
| 304 |
+
):
|
| 305 |
+
"""
|
| 306 |
+
Summarize columns of a matrix using Biweight Mid-average based on group labels.
|
| 307 |
+
|
| 308 |
+
Args:
|
| 309 |
+
input_file: Path to the input CSV/TSV file.
|
| 310 |
+
output_file: Path for the output file.
|
| 311 |
+
group_labels: A list of integers representing the group for each column.
|
| 312 |
+
"""
|
| 313 |
+
input_path = Path(input_file)
|
| 314 |
+
output_path = Path(output_file)
|
| 315 |
+
|
| 316 |
+
if not input_path.exists():
|
| 317 |
+
return {"error": f"Input file {input_file} not found."}
|
| 318 |
+
|
| 319 |
+
r_groups = f"c({','.join(map(str, group_labels))})"
|
| 320 |
+
|
| 321 |
+
r_script = f"""
|
| 322 |
+
data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}"))
|
| 323 |
+
groups <- as.integer({r_groups})
|
| 324 |
+
summarized <- subColSummarizeBiweightMidavg(data, groups)
|
| 325 |
+
write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE)
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
try:
|
| 329 |
+
stdout, stderr = run_r_command(r_script)
|
| 330 |
+
return {
|
| 331 |
+
"command_executed": "subColSummarizeBiweightMidavg",
|
| 332 |
+
"stdout": stdout,
|
| 333 |
+
"stderr": stderr,
|
| 334 |
+
"output_files": [str(output_path)]
|
| 335 |
+
}
|
| 336 |
+
except Exception as e:
|
| 337 |
+
return {"error": str(e)}
|
| 338 |
+
|
| 339 |
+
if __name__ == "__main__":
|
| 340 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_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-preprocesscore/app/bioconductor-preprocesscore_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_preprocesscore'
|
| 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-preprocesscore/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-preprocesscore/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-preprocesscore:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-preprocesscore:latest
|
| 7 |
+
container_name: mcp-bioconductor-preprocesscore
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-preprocesscore
|
| 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-preprocesscore/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-preprocesscore
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-preprocesscore/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|