diff --git a/Biomni/mcp_generated/mcp_abundancebin/Dockerfile b/Biomni/mcp_generated/mcp_abundancebin/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..bef882fc9d87ab2e0ff61be73df1c30027f57489 --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install abundancebin via conda (e.g., from bioconda) +RUN conda install -c bioconda abundancebin -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/abundancebin_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/abundancebin_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/abundancebin_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_server.py b/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_server.py new file mode 100644 index 0000000000000000000000000000000000000000..662d55df42a63af445e36b294f437c59ab0a78ab --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_server.py @@ -0,0 +1,136 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List + +# Assume @mcp.tool() is defined in the execution environment. +# No import is needed for the final code. + +# Set up logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_abundancebin' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def abundancebin( + input_file: Path, + kmer_len: int = 20, + output: Optional[Path] = None, + exclude: Optional[int] = None, + exclude_max: Optional[int] = None, + output_fasta: bool = False, + bin_num: Optional[int] = None, + recursive_classification: bool = False, +): + """ + Performs abundance-based binning on a given input FASTA/FASTQ file. + + This tool uses k-mer frequency to classify sequences into bins. It can either + classify into a specified number of bins or use a recursive classification approach. + + Args: + input_file: Path to the input FASTA/FASTQ file. + kmer_len: The length of the k-mer to use for composition analysis (default: 20). + output: Path to the output log file. If not provided, defaults to '.log'. + exclude: Exclude contigs with coverage lower than this count. + exclude_max: Exclude contigs with coverage higher than this count. + output_fasta: If True, output binned sequences into separate FASTA files. + bin_num: The specific number of bins to classify sequences into. + recursive_classification: If True, undergo recursive classification instead of specifying a bin number. + This is mutually exclusive with 'bin_num'. + + Returns: + A dictionary containing the execution details and paths to output files. + """ + # 1. Input Validation + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + if bin_num is not None and recursive_classification: + raise ValueError("Parameters 'bin_num' and 'recursive_classification' are mutually exclusive. Please provide only one.") + + if kmer_len <= 0: + raise ValueError("kmer_len must be a positive integer.") + + # 2. Command Construction + # Determine the output file path based on the tool's default behavior + if output: + output_path = output + else: + output_path = input_file.with_suffix(".log") + + # Ensure the output directory exists + output_path.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "abundancebin", + "-input", str(input_file), + "-kmer_len", str(kmer_len), + "-output", str(output_path) + ] + + if exclude is not None: + cmd.extend(["-exclude", str(exclude)]) + + if exclude_max is not None: + cmd.extend(["-exclude_max", str(exclude_max)]) + + if output_fasta: + cmd.append("-OUTPUT_FASTA") + + if bin_num is not None: + cmd.extend(["-bin_num", str(bin_num)]) + + if recursive_classification: + cmd.append("-RECURSIVE_CLASSIFICATION") + + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # 3. Subprocess Execution and Error Handling + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + # The tool might generate more files if -OUTPUT_FASTA is used. + # For simplicity, we return the main log file. The user can infer others. + output_files = [str(output_path)] if output_path.exists() else [] + + # 4. Structured Result Return (Success) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + + except FileNotFoundError: + error_message = "abundancebin command not found. Please ensure the tool is installed and in your system's PATH." + logger.error(error_message) + # Re-raising is often better to signal a fatal environment error. + raise RuntimeError(error_message) from None + + except subprocess.CalledProcessError as e: + logger.error(f"abundancebin failed with exit code {e.returncode}") + logger.error(f"Stderr: {e.stderr}") + logger.error(f"Stdout: {e.stdout}") + + # 4. Structured Result Return (Failure) + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_shim_server.py b/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9c68c3ce81dca45d9870653fabdfecf266eba9ee --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/app/abundancebin_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_abundancebin/app/abundancebin_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_abundancebin' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_abundancebin/app/requirements.txt b/Biomni/mcp_generated/mcp_abundancebin/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_abundancebin/docker-compose.yml b/Biomni/mcp_generated/mcp_abundancebin/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c3e728f0802fb0284f48f87c588f826b60f324c2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-abundancebin: + build: . + image: mcp-abundancebin:latest + container_name: mcp-abundancebin + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=abundancebin + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_abundancebin/environment.yaml b/Biomni/mcp_generated/mcp_abundancebin/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fb3f2d4c33b5cfc882a79148b0d58be0b05b89bc --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - abundancebin + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_abundancebin/requirements.txt b/Biomni/mcp_generated/mcp_abundancebin/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_abundancebin/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_anarci/app/anarci_shim_server.py b/Biomni/mcp_generated/mcp_anarci/app/anarci_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..47677b71572a78207f99d4c18f6e33c1ab8da4f5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_anarci/app/anarci_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_anarci/app/anarci_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_anarci' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_anarci/app/requirements.txt b/Biomni/mcp_generated/mcp_anarci/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_anarci/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74cf53b9af99850cce584986545a93dfbf01f97c Binary files /dev/null and b/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-biostrings/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..04502dc2af170c29e07379c364d485a2b0cea7b8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-biostrings via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-biostrings -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY bioconductor-biostrings_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-biostrings_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-biostrings_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py b/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e10df7f977293903d22e6ed2ef6ca77064b0a04d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py @@ -0,0 +1,144 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Mock the decorator for standalone execution +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_biostrings' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def rscript( + script_file: Optional[Path] = None, + expressions: Optional[List[str]] = None, + script_args: Optional[List[str]] = None, + verbose: bool = False, + default_packages: Optional[str] = None, + vanilla: bool = False, + save: bool = False, + no_environ: bool = False, + no_site_file: bool = False, + no_init_file: bool = False, + restore: bool = False, +) -> Dict[str, Any]: + """ + Executes an R script or R expressions using the Rscript command-line tool. + + This tool serves as a wrapper for Rscript, allowing for the execution of R code + from a file or directly from string expressions. It mirrors the functionality + provided by `Rscript --help`. + + Args: + script_file: Path to the R script file to be executed. Mutually exclusive with 'expressions'. + expressions: A list of R expressions to be executed. Mutually exclusive with 'script_file'. + script_args: A list of arguments to be passed to the R script itself. + verbose: If True, enables verbose output, printing information on progress. + default_packages: A comma-separated string of package names to be loaded by default. + vanilla: If True, combines --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ. + If set, it overrides the individual flags (save, restore, etc.). + save: If True, the workspace is saved at the end of the session. Ignored if 'vanilla' is True. + no_environ: If True, site and user environment files are not read. Ignored if 'vanilla' is True. + no_site_file: If True, the site-wide Rprofile is not read. Ignored if 'vanilla' is True. + no_init_file: If True, the user's R profile is not read. Ignored if 'vanilla' is True. + restore: If True, previously saved objects are restored at startup. Ignored if 'vanilla' is True. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files (always empty). + """ + # 1. Input Validation + if not script_file and not expressions: + raise ValueError("Either 'script_file' or 'expressions' must be provided.") + if script_file and expressions: + raise ValueError("'script_file' and 'expressions' are mutually exclusive and cannot be used together.") + if script_file and not script_file.is_file(): + raise FileNotFoundError(f"The specified script file does not exist: {script_file}") + if expressions and not expressions: + raise ValueError("'expressions' list cannot be empty if provided.") + + # 2. Command Construction + cmd = ["Rscript"] + + if verbose: + cmd.append("--verbose") + + if default_packages: + cmd.extend(["--default-packages", default_packages]) + + if vanilla: + cmd.append("--vanilla") + else: + # These options are combined and handled by the --vanilla flag in Rscript + if save: + cmd.append("--save") + if no_environ: + cmd.append("--no-environ") + if no_site_file: + cmd.append("--no-site-file") + if no_init_file: + cmd.append("--no-init-file") + if restore: + cmd.append("--restore") + + # Add expressions or script file to the command + if expressions: + for expr in expressions: + cmd.extend(["-e", expr]) + elif script_file: + cmd.append(str(script_file)) + + # Add arguments for the R script itself + if script_args: + cmd.extend(script_args) + + command_str = " ".join(cmd) + logging.info(f"Executing command: {command_str}") + + # 3. Subprocess Execution and Error Handling + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + # This error occurs if 'Rscript' is not in the system's PATH + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'Rscript' command not found. Please ensure R is installed and accessible in your system's PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + # This error occurs if the R script itself fails (non-zero exit code) + logging.error(f"Rscript execution failed with return code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # 4. Structured Result Return + # Rscript itself does not have a dedicated output file parameter. Any files + # created are determined by the R code within the script. + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..68e8a6d69fc1468fa0e629197f8d96d8ac7d74d5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/app/bioconductor-biostrings_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biostrings/app/bioconductor-biostrings_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_biostrings' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-biostrings/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..04cc0ec4df0b6b32962f9077038c73df1dbbfe47 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-biostrings: + build: . + image: mcp-bioconductor-biostrings:latest + container_name: mcp-bioconductor-biostrings + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-biostrings + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-biostrings/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..45c3e252d78ba0fbedb6d8c87e58e82896f73d44 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-biostrings + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biostrings/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-biostrings/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biostrings/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-despace/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4da99cfeeacc2224edafb176136f0e1db9162e6c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-despace via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-despace -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-despace_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-despace_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-despace_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_server.py b/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ebab6e701e77bf65c7cabacc484b11911f32ba8f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_server.py @@ -0,0 +1,303 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List + +# MCP is a placeholder for the Model Context Protocol library. +# In a real environment, this would be: from mcp import tool +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_despace' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def cluster_scrnaseq( + sc_object_path: Path, + output_rds_path: Path, + s_topics: int = 10, + n_top_genes: int = 2000, +) -> dict: + """ + Performs clustering on single-cell RNA-seq data as a preprocessing step for DeSpace. + + This tool wraps the `cluster_scRNAseq` function from the DeSpace R package. It takes a + Seurat object, identifies highly variable genes, performs dimensionality reduction, + and identifies clusters based on topic modeling. + + Args: + sc_object_path: Path to the input single-cell Seurat object (.rds file). + output_rds_path: Path to save the clustered single-cell Seurat object (.rds file). + s_topics: The number of topics (S) for topic modeling, representing putative cell types. + n_top_genes: Number of highly variable genes to use for clustering. + + Returns: + A dictionary containing the executed command, stdout, stderr, and the path to the output file. + """ + # --- Input Validation --- + if not sc_object_path.is_file(): + raise FileNotFoundError(f"Input single-cell object not found: {sc_object_path}") + if not output_rds_path.parent.exists(): + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"Created output directory: {output_rds_path.parent}") + + if s_topics <= 0: + raise ValueError("s_topics must be a positive integer.") + if n_top_genes <= 0: + raise ValueError("n_top_genes must be a positive integer.") + + # --- Command Construction --- + # This assumes a wrapper R script 'cluster_scrnaseq.R' is in the system's PATH. + cmd = [ + "Rscript", "cluster_scrnaseq.R", + "--sc_object_path", str(sc_object_path), + "--output_rds_path", str(output_rds_path), + "--s_topics", str(s_topics), + "--n_top_genes", str(n_top_genes), + ] + command_executed = " ".join(cmd) + logging.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout = process.stdout + stderr = process.stderr + logging.info("Single-cell clustering completed successfully.") + except FileNotFoundError: + err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH." + logging.error(err_msg) + return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []} + except subprocess.CalledProcessError as e: + logging.error(f"Single-cell clustering failed with exit code {e.returncode}.") + return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []} + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_rds_path)] + } + +@mcp.tool() +def cluster_sp_data( + sp_object_path: Path, + output_rds_path: Path, + resolution: float = 0.8, +) -> dict: + """ + Performs clustering on spatial transcriptomics data as a preprocessing step for DeSpace. + + This tool wraps the `cluster_sp_data` function from the DeSpace R package. It takes a + spatial Seurat object, normalizes the data, finds variable features, and performs + graph-based clustering. + + Args: + sp_object_path: Path to the input spatial Seurat object (.rds file). + output_rds_path: Path to save the clustered spatial Seurat object (.rds file). + resolution: Clustering resolution for the Louvain algorithm. + + Returns: + A dictionary containing the executed command, stdout, stderr, and the path to the output file. + """ + # --- Input Validation --- + if not sp_object_path.is_file(): + raise FileNotFoundError(f"Input spatial object not found: {sp_object_path}") + if not output_rds_path.parent.exists(): + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"Created output directory: {output_rds_path.parent}") + + if resolution <= 0.0: + raise ValueError("resolution must be a positive float.") + + # --- Command Construction --- + # This assumes a wrapper R script 'cluster_sp_data.R' is in the system's PATH. + cmd = [ + "Rscript", "cluster_sp_data.R", + "--sp_object_path", str(sp_object_path), + "--output_rds_path", str(output_rds_path), + "--resolution", str(resolution), + ] + command_executed = " ".join(cmd) + logging.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout = process.stdout + stderr = process.stderr + logging.info("Spatial data clustering completed successfully.") + except FileNotFoundError: + err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH." + logging.error(err_msg) + return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []} + except subprocess.CalledProcessError as e: + logging.error(f"Spatial data clustering failed with exit code {e.returncode}.") + return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []} + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_rds_path)] + } + +@mcp.tool() +def run_despace( + sc_object_path: Path, + sp_object_path: Path, + output_rds_path: Path, + sc_assay: str = "RNA", + sc_slot: str = "counts", + sp_assay: str = "Spatial", + sp_slot: str = "counts", + num_markers_sc: int = 10, + num_markers_sp: int = 10, + s_topics: int = 10, + n_top_genes: int = 2000, + resolution: float = 0.8, + sample_id: Optional[str] = None, + num_threads: int = 1, + save_model: bool = False, + model_path: Optional[Path] = None, +) -> dict: + """ + Runs the main DeSpace algorithm to integrate single-cell and spatial transcriptomics data. + + This tool wraps the `DeSpace` R function. It can perform clustering internally if not + already present in the input objects, or it can use pre-computed clusters. The main + output is a Seurat object with cell-type deconvolution results. + + Args: + sc_object_path: Path to the single-cell Seurat object (.rds file). + sp_object_path: Path to the spatial transcriptomics Seurat object (.rds file). + output_rds_path: Path to save the resulting Seurat object with DeSpace results. + sc_assay: Assay to use from the single-cell Seurat object. + sc_slot: Slot to use from the single-cell assay (e.g., 'counts', 'data'). + sp_assay: Assay to use from the spatial Seurat object. + sp_slot: Slot to use from the spatial assay (e.g., 'counts', 'data'). + num_markers_sc: Number of markers to use for each single-cell cluster. + num_markers_sp: Number of markers to use for each spatial cluster. + s_topics: The number of topics (S) for topic modeling, used if sc-data is not pre-clustered. + n_top_genes: Number of highly variable genes, used if sc-data is not pre-clustered. + resolution: Clustering resolution, used if sp-data is not pre-clustered. + sample_id: Optional identifier for the sample, used for saving the model. + num_threads: Number of parallel threads to use. + save_model: If True, save the trained DeSpace model. + model_path: Path to save the DeSpace model file. Required if save_model is True. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not sc_object_path.is_file(): + raise FileNotFoundError(f"Single-cell input file not found: {sc_object_path}") + if not sp_object_path.is_file(): + raise FileNotFoundError(f"Spatial input file not found: {sp_object_path}") + + if not output_rds_path.parent.exists(): + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"Created output directory: {output_rds_path.parent}") + + if num_markers_sc <= 0: + raise ValueError("num_markers_sc must be a positive integer.") + if num_markers_sp <= 0: + raise ValueError("num_markers_sp must be a positive integer.") + if s_topics <= 0: + raise ValueError("s_topics must be a positive integer.") + if n_top_genes <= 0: + raise ValueError("n_top_genes must be a positive integer.") + if resolution <= 0.0: + raise ValueError("resolution must be a positive float.") + if num_threads <= 0: + raise ValueError("num_threads must be a positive integer.") + + if save_model: + if model_path is None: + raise ValueError("model_path must be provided when save_model is True.") + if not model_path.parent.exists(): + model_path.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"Created model output directory: {model_path.parent}") + + # --- Command Construction --- + # This assumes a wrapper R script 'run_despace.R' is in the system's PATH. + cmd = [ + "Rscript", "run_despace.R", + "--sc_object_path", str(sc_object_path), + "--sp_object_path", str(sp_object_path), + "--output_rds_path", str(output_rds_path), + "--sc_assay", sc_assay, + "--sc_slot", sc_slot, + "--sp_assay", sp_assay, + "--sp_slot", sp_slot, + "--num_markers_sc", str(num_markers_sc), + "--num_markers_sp", str(num_markers_sp), + "--s_topics", str(s_topics), + "--n_top_genes", str(n_top_genes), + "--resolution", str(resolution), + "--num_threads", str(num_threads), + ] + + if sample_id: + cmd.extend(["--sample_id", sample_id]) + + if save_model and model_path: + cmd.append("--save_model") + cmd.extend(["--model_path", str(model_path)]) + + command_executed = " ".join(cmd) + logging.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout = process.stdout + stderr = process.stderr + logging.info("DeSpace execution completed successfully.") + except FileNotFoundError: + err_msg = "Error: 'Rscript' command not found. Please ensure R and the required wrapper scripts are in the system's PATH." + logging.error(err_msg) + return {"command_executed": command_executed, "stdout": "", "stderr": err_msg, "output_files": []} + except subprocess.CalledProcessError as e: + logging.error(f"DeSpace execution failed with exit code {e.returncode}.") + return {"command_executed": command_executed, "stdout": e.stdout, "stderr": e.stderr, "output_files": []} + + # --- Structured Result Return --- + output_files: List[str] = [str(output_rds_path)] + if save_model and model_path: + output_files.append(str(model_path)) + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c1ab996f5409eb671a3d03e0d41f347214521533 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/app/bioconductor-despace_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-despace/app/bioconductor-despace_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_despace' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-despace/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-despace/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a02735107b0381e3e0a06f2c081e776f2bd05b3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-despace: + build: . + image: mcp-bioconductor-despace:latest + container_name: mcp-bioconductor-despace + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-despace + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-despace/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aa7dcfc187fbd6567c43123ac5cc15ac3a6a30d3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-despace + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-despace/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-despace/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-despace/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e7d23553fd16ce1626a84fcd3ab533e663d65307 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-geomxtools via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-geomxtools -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-geomxtools_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-geomxtools_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-geomxtools_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py new file mode 100644 index 0000000000000000000000000000000000000000..97379910779be165f55c0de66b7f61a0ff000a24 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py @@ -0,0 +1,688 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List + +# In a real MCP environment, this would be imported. +# from mcp import tool as mcp_tool +# For this exercise, we assume the decorator @mcp.tool() is available. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_geomxtools' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def read_nanostring_geomx_set( + dcc_files_dir: Path, + pkc_files: List[Path], + pheno_data_file: Path, + output_rds_path: Path, + pheno_data_sheet: Optional[str] = None, + pheno_data_dcc_col_name: str = "Sample_ID", + protocol_data_col_names: Optional[List[str]] = None, + experiment_data_col_names: Optional[List[str]] = None, +) -> dict: + """ + Reads NanoString GeoMx files (DCC, PKC, annotation) and creates a NanoStringGeomxSet object. + + This tool is a wrapper around the `readNanoStringGeoMxSet` function from the + R/Bioconductor package `GeomxTools`. It processes raw data into a structured + R object for downstream analysis. + + Args: + dcc_files_dir: Path to the directory containing DCC files. + pkc_files: A list of paths to PKC files. + pheno_data_file: Path to the sample annotation file (e.g., an Excel file). + output_rds_path: Path for the output RDS file which will contain the NanoStringGeomxSet object. + pheno_data_sheet: Optional name of the worksheet in the Excel annotation file. + pheno_data_dcc_col_name: Column name in the annotation file that matches DCC file names. + protocol_data_col_names: Optional list of column names in annotation to be added to protocolData. + experiment_data_col_names: Optional list of column names in annotation to be added to experimentData. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not dcc_files_dir.is_dir(): + raise ValueError(f"DCC files directory not found: {dcc_files_dir}") + if not pkc_files: + raise ValueError("At least one PKC file must be provided.") + for pkc_file in pkc_files: + if not pkc_file.is_file(): + raise ValueError(f"PKC file not found: {pkc_file}") + if not pheno_data_file.is_file(): + raise ValueError(f"Phenotype data file not found: {pheno_data_file}") + + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + # Safely create R vectors from Python lists + r_protocol_cols = f'c({", ".join(f"{col}" for col in protocol_data_col_names)})' if protocol_data_col_names else "NULL" + r_experiment_cols = f'c({", ".join(f"{col}" for col in experiment_data_col_names)})' if experiment_data_col_names else "NULL" + r_pheno_sheet = f'"{pheno_data_sheet}"' if pheno_data_sheet else "NULL" + pkc_files_r_vector = f'c({", ".join(f"{str(p)}" for p in pkc_files)})' + + r_script_content = f""" + library(GeomxTools) + + tryCatch({{ + dcc_dir <- "{dcc_files_dir}" + pkc_files_vec <- {pkc_files_r_vector} + pheno_file <- "{pheno_data_file}" + output_path <- "{output_rds_path}" + pheno_dcc_col <- "{pheno_data_dcc_col_name}" + + dcc_files <- list.files(dcc_dir, pattern = "\\\\.dcc$", full.names = TRUE, recursive = TRUE) + if (length(dcc_files) == 0) {{ + stop("No .dcc files found in the specified directory.") + }} + + geomx_data <- readNanoStringGeoMxSet( + dccFiles = dcc_files, + pkcFiles = pkc_files_vec, + phenoDataFile = pheno_file, + phenoDataSheet = {r_pheno_sheet}, + phenoDataDccColName = pheno_dcc_col, + protocolDataColNames = {r_protocol_cols}, + experimentDataColNames = {r_experiment_cols} + ) + + saveRDS(geomx_data, file = output_path) + cat("Successfully created NanoStringGeomxSet object and saved to", output_path, "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def set_segment_qc_flags( + input_rds_path: Path, + output_rds_path: Path, + min_segment_reads: Optional[int] = 1000, + percent_aligned: Optional[float] = 80, + percent_saturation: Optional[float] = 50, + min_negative_count: Optional[int] = 10, + max_ntc_count: Optional[int] = 1000, + min_nuclei: Optional[int] = 200, + min_area: Optional[int] = 16000, +) -> dict: + """ + Sets segment QC flags in a NanoStringGeomxSet object based on specified cutoffs. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_rds_path: Path for the output RDS file with QC flags applied. + min_segment_reads: Minimum number of reads in a segment. + percent_aligned: Minimum percentage of reads aligned. + percent_saturation: Minimum percentage of reads saturated. + min_negative_count: Minimum negative probe counts. + max_ntc_count: Maximum counts observed in NTC wells. + min_nuclei: Minimum number of nuclei in a segment. + min_area: Minimum area of a segment. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + r_script_content = f""" + library(GeomxTools) + + tryCatch({{ + input_rds <- "{input_rds_path}" + output_rds <- "{output_rds_path}" + + geomx_data <- readRDS(input_rds) + + qc_cutoffs <- list() + if (!is.null({min_segment_reads or 'NULL'})) {{ qc_cutoffs$minSegmentReads <- {min_segment_reads} }} + if (!is.null({percent_aligned or 'NULL'})) {{ qc_cutoffs$percentAligned <- {percent_aligned} }} + if (!is.null({percent_saturation or 'NULL'})) {{ qc_cutoffs$percentSaturation <- {percent_saturation} }} + if (!is.null({min_negative_count or 'NULL'})) {{ qc_cutoffs$minNegativeCount <- {min_negative_count} }} + if (!is.null({max_ntc_count or 'NULL'})) {{ qc_cutoffs$maxNTCCount <- {max_ntc_count} }} + if (!is.null({min_nuclei or 'NULL'})) {{ qc_cutoffs$minNuclei <- {min_nuclei} }} + if (!is.null({min_area or 'NULL'})) {{ qc_cutoffs$minArea <- {min_area} }} + + geomx_data_qc <- setSegmentQCFlags(geomx_data, qcCutoffs = qc_cutoffs) + + saveRDS(geomx_data_qc, file = output_rds) + cat("Successfully applied segment QC flags and saved to", output_rds, "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def set_bioprobe_qc_flags( + input_rds_path: Path, + output_rds_path: Path, + min_probe_ratio: float = 0.1, + percent_fail_grubbs: float = 20, + remove_local_outliers: bool = True, +) -> dict: + """ + Sets probe QC flags in a NanoStringGeomxSet object. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_rds_path: Path for the output RDS file with QC flags applied. + min_probe_ratio: Minimum ratio of probes to the geometric mean of all probes. + percent_fail_grubbs: Percentage of segments that must fail Grubbs test for a probe to be flagged. + remove_local_outliers: If TRUE, local outliers will be removed. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + r_script_content = f""" + library(GeomxTools) + + tryCatch({{ + input_rds <- "{input_rds_path}" + output_rds <- "{output_rds_path}" + + geomx_data <- readRDS(input_rds) + + qc_cutoffs <- list( + minProbeRatio = {min_probe_ratio}, + percentFailGrubbs = {percent_fail_grubbs} + ) + + geomx_data_qc <- setBioProbeQCFlags( + geomx_data, + qcCutoffs = qc_cutoffs, + removeLocalOutliers = {str(remove_local_outliers).upper()} + ) + + saveRDS(geomx_data_qc, file = output_rds) + cat("Successfully applied bioprobe QC flags and saved to", output_rds, "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def subset_geomx_set( + input_rds_path: Path, + output_rds_path: Path, + subset_by_pdata: bool = True, + subset_column: Optional[str] = None, + subset_values: Optional[List[str]] = None, +) -> dict: + """ + Subsets a NanoStringGeomxSet object based on phenotype (pData) or feature (fData) annotations. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_rds_path: Path for the output subsetted RDS file. + subset_by_pdata: If True, subset by sample annotations (pData). If False, subset by feature annotations (fData). + subset_column: The column name in pData or fData to use for subsetting. + subset_values: A list of values to keep from the subset_column. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + if not subset_column or not subset_values: + raise ValueError("subset_column and subset_values must be provided for subsetting.") + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + subset_values_r = f'c({", ".join(f"{v}" for v in subset_values)})' + + r_script_content = f""" + library(GeomxTools) + library(Biobase) + + tryCatch({{ + object <- readRDS("{input_rds_path}") + subset_col <- "{subset_column}" + subset_vals <- {subset_values_r} + + if ({str(subset_by_pdata).upper()}) {{ + logic <- pData(object)[[subset_col]] %in% subset_vals + logic[is.na(logic)] <- FALSE + subset_object <- object[, logic] + }} else {{ + logic <- fData(object)[[subset_col]] %in% subset_vals + logic[is.na(logic)] <- FALSE + subset_object <- object[logic, ] + }} + + saveRDS(subset_object, file = "{output_rds_path}") + cat("Successfully subset object and saved to", "{output_rds_path}", "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def aggregate_counts( + input_rds_path: Path, + output_rds_path: Path, + elt: str = "exprs", +) -> dict: + """ + Aggregates probe-level counts to the target level in a NanoStringGeomxSet object. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_rds_path: Path for the output RDS file with aggregated counts. + elt: The name of the assay data element to aggregate. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + r_script_content = f""" + library(GeomxTools) + + tryCatch({{ + geomx_data <- readRDS("{input_rds_path}") + aggregated_data <- aggregateCounts(geomx_data, elt = "{elt}") + + saveRDS(aggregated_data, file = "{output_rds_path}") + cat("Successfully aggregated counts and saved to", "{output_rds_path}", "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def normalize_geomx( + input_rds_path: Path, + output_rds_path: Path, + norm_method: str, + from_elt: str = "exprs", + to_elt: str = "exprs_norm", + housekeepers: Optional[List[str]] = None, +) -> dict: + """ + Normalizes the count data in a NanoStringGeomxSet object. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_rds_path: Path for the output RDS file with normalized data. + norm_method: Normalization method. Must be one of 'quant', 'neg', 'hk'. + from_elt: The assay data element to use for normalization. + to_elt: The name of the new assay data element to store normalized values. + housekeepers: A list of housekeeper gene names, required if norm_method is 'hk'. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output RDS file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + + valid_methods = ["quant", "neg", "hk"] + if norm_method not in valid_methods: + raise ValueError(f"Invalid norm_method '{norm_method}'. Must be one of {valid_methods}.") + + if norm_method == "hk" and not housekeepers: + raise ValueError("Housekeeper genes must be provided for 'hk' normalization.") + + output_rds_path.parent.mkdir(parents=True, exist_ok=True) + + housekeepers_r = f'c({", ".join(f"{hk}" for hk in housekeepers)})' if housekeepers else "NULL" + + # --- R Script Generation --- + r_script_content = f""" + library(GeomxTools) + + tryCatch({{ + geomx_data <- readRDS("{input_rds_path}") + + normalized_data <- normalize( + geomx_data, + norm.method = "{norm_method}", + fromElt = "{from_elt}", + toElt = "{to_elt}", + housekeepers = {housekeepers_r} + ) + + saveRDS(normalized_data, file = "{output_rds_path}") + cat("Successfully normalized data and saved to", "{output_rds_path}", "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +@mcp.tool() +def mixed_model_de( + input_rds_path: Path, + output_csv_path: Path, + elt: str, + model_formula_fixed: str, + group_var: str, + model_formula_random: Optional[str] = None, + contrasts: Optional[List[str]] = None, + n_cores: int = 1, +) -> dict: + """ + Performs differential expression analysis using a linear mixed model. + + Args: + input_rds_path: Path to the input RDS file containing a NanoStringGeomxSet object. + output_csv_path: Path for the output CSV file with DE results. + elt: The assay data element to use for the analysis (e.g., 'exprs_norm'). + model_formula_fixed: The fixed effects part of the model formula (e.g., 'region + diseaseStatus'). + group_var: The main variable of interest for testing (e.g., 'diseaseStatus'). + model_formula_random: The random effects part of the model formula (e.g., '(1|slideName)'). + contrasts: A list of contrasts to test (e.g., ['diseaseA - diseaseB', 'diseaseC - diseaseB']). + n_cores: Number of cores to use for parallel processing. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the path to the output CSV file. + """ + # --- Input Validation --- + if not input_rds_path.is_file(): + raise ValueError(f"Input RDS file not found: {input_rds_path}") + if n_cores < 1: + raise ValueError("n_cores must be at least 1.") + output_csv_path.parent.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + formula_str = f"~ {model_formula_fixed}" + if model_formula_random: + formula_str += f" + {model_formula_random}" + + contrasts_r = "NULL" + if contrasts: + contrasts_r = f'c({", ".join(f"{c}" for c in contrasts)})' + + r_script_content = f""" + library(GeomxTools) + library(limma) + library(Biobase) + + tryCatch({{ + object <- readRDS("{input_rds_path}") + + model_formula <- as.formula("{formula_str}") + + contrast_matrix <- NULL + if (!is.null({contrasts_r})) {{ + contrast_matrix <- makeContrasts(contrasts = {contrasts_r}, levels = unique(pData(object)[["{group_var}"]])) + }} + + results <- mixedModelDE( + object, + elt = "{elt}", + modelFormula = model_formula, + groupVar = "{group_var}", + nCores = {n_cores}, + multiCore = {str(n_cores > 1).upper()}, + contrasts = contrast_matrix + ) + + write.csv(results, file = "{output_csv_path}", row.names = FALSE) + cat("Successfully performed DE analysis and saved results to", "{output_csv_path}", "\\n") + }}, error = function(e) {{ + message("R script failed with error: ", e$message) + quit(status = 1) + }}) + """ + + # --- Subprocess Execution --- + command_executed = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = ["Rscript", r_script_path] + command_executed = " ".join(cmd) + + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_csv_path)] + } + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": [] + } + finally: + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c9ac19b536a5e35603ff15fe67ea1bd8e59da203 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_geomxtools' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ddcec77dc3ef15c0699109b8924d12c4ccb1737e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-geomxtools: + build: . + image: mcp-bioconductor-geomxtools:latest + container_name: mcp-bioconductor-geomxtools + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-geomxtools + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b21e8a4370c5f03f287e02d6c98afab673d9cd6b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-geomxtools + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-geomxtools/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-geomxtools/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e864b31d24f33ded903f987db861839ba126d44 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-glmgampoi: + build: . + image: mcp-bioconductor-glmgampoi:latest + container_name: mcp-bioconductor-glmgampoi + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-glmgampoi + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-glmgampoi/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-infercnv/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2a7999e32fcac07aa795437e3ed33b833e69b59f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-infercnv via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-infercnv -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY bioconductor-infercnv_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-infercnv_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-infercnv_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_server.py b/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1d0cb8c9549e9064121415412737002fc2452eff --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_server.py @@ -0,0 +1,298 @@ +import subprocess +import os +from pathlib import Path +from typing import Optional, List +import tempfile +import shlex + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_infercnv' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def infercnv_run( + raw_counts_matrix: str, + annotations_file: str, + gene_order_file: str, + out_dir: str, + ref_group_names: Optional[List[str]] = None, + cutoff: float = 1.0, + min_cells_per_gene: int = 3, + cluster_by_groups: bool = True, + denoise: bool = False, + hmm: bool = False, + hmm_type: str = "i6", + analysis_mode: str = "samples", + num_threads: int = 1, + plot_steps: bool = False, + no_plot: bool = False, + window_length: int = 101, + max_centered_threshold: float = 3.0, + leiden_resolution: float = 0.05, +): + """ + Run the full InferCNV pipeline to identify somatic copy number alterations in single-cell RNA-seq data. + + Args: + raw_counts_matrix: Path to the matrix of gene expression counts (genes as rows, cells as columns). + annotations_file: Path to the cell annotations file (cell name and group). + gene_order_file: Path to the gene positions file (gene, chromosome, start, stop). + out_dir: Directory to save the output files. + ref_group_names: List of group names to use as reference (normal) cells. If None, all cells are used. + cutoff: Threshold for gene expression. Use 1.0 for Smart-seq2 and 0.1 for 10x Genomics. + min_cells_per_gene: Minimum number of cells a gene must be expressed in to be kept. + cluster_by_groups: Whether to cluster cells by their annotation groups. + denoise: Whether to apply denoising filters. + hmm: Whether to run the Hidden Markov Model (HMM) to predict CNV states. + hmm_type: Type of HMM to use ('i6' for 6-state model, 'i3' for 3-state model). + analysis_mode: Analysis mode ('samples' or 'subclusters'). + num_threads: Number of CPU threads to use for parallel processing. + plot_steps: Whether to generate plots for every intermediate step. + no_plot: If True, skips the final heatmap generation. + window_length: Length of the moving average window for smoothing. + max_centered_threshold: Maximum value for centering the expression data. + leiden_resolution: Resolution for Leiden clustering if analysis_mode is 'subclusters'. + """ + # Input validation + raw_path = Path(raw_counts_matrix) + ann_path = Path(annotations_file) + gene_path = Path(gene_order_file) + out_path = Path(out_dir) + + if not raw_path.exists(): + return {"error": f"Raw counts matrix not found: {raw_counts_matrix}"} + if not ann_path.exists(): + return {"error": f"Annotations file not found: {annotations_file}"} + if not gene_path.exists(): + return {"error": f"Gene order file not found: {gene_order_file}"} + + if hmm_type not in ["i6", "i3"]: + return {"error": "hmm_type must be either 'i6' or 'i3'"} + + if analysis_mode not in ["samples", "subclusters"]: + return {"error": "analysis_mode must be either 'samples' or 'subclusters'"} + + os.makedirs(out_path, exist_ok=True) + + # Prepare R vector for reference groups + if ref_group_names: + ref_groups_r = "c(" + ", ".join([f"'{g}'" for g in ref_group_names]) + ")" + else: + ref_groups_r = "NULL" + + # Construct R script + r_script_content = f""" +library(infercnv) + +# Create InferCNV Object +infercnv_obj = CreateInfercnvObject( + raw_counts_matrix = "{raw_path.absolute()}", + gene_order_file = "{gene_path.absolute()}", + annotations_file = "{ann_path.absolute()}", + ref_group_names = {ref_groups_r} +) + +# Run InferCNV Pipeline +infercnv_obj = infercnv::run( + infercnv_obj, + cutoff = {cutoff}, + min_cells_per_gene = {min_cells_per_gene}, + out_dir = "{out_path.absolute()}", + cluster_by_groups = {str(cluster_by_groups).upper()}, + denoise = {str(denoise).upper()}, + HMM = {str(hmm).upper()}, + HMM_type = "{hmm_type}", + analysis_mode = "{analysis_mode}", + num_threads = {num_threads}, + plot_steps = {str(plot_steps).upper()}, + no_plot = {str(no_plot).upper()}, + window_length = {window_length}, + max_centered_threshold = {max_centered_threshold}, + leiden_resolution = {leiden_resolution} +) +""" + + try: + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script_content) + tmp_path = tmp.name + + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Cleanup temp file + os.unlink(tmp_path) + + # Identify output files + output_files = [str(f) for f in out_path.glob("*") if f.is_file()] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + "status": "success" + } + + except subprocess.CalledProcessError as e: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "InferCNV execution failed." + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def infercnv_plot( + infercnv_obj_path: str, + out_dir: str, + output_filename: str = "infercnv_plot", + color_safe_pal: bool = False, + title: str = "InferCNV Heatmap", + cluster_by_groups: bool = True, + x_center: float = 1.0, + x_range: Optional[float] = None, + custom_color_pal: Optional[List[str]] = None, +): + """ + Generate or regenerate plots from a saved InferCNV object. + + Args: + infercnv_obj_path: Path to the saved .rds or .obj InferCNV object. + out_dir: Directory to save the plot. + output_filename: Name of the output plot file (without extension). + color_safe_pal: Use a color-blind safe palette. + title: Title of the plot. + cluster_by_groups: Whether to cluster by groups in the plot. + x_center: Value to center the color scale on (usually 1.0). + x_range: Range of values to display (e.g., 0.1 means 0.9 to 1.1). + custom_color_pal: Optional list of colors for the heatmap palette. + """ + obj_path = Path(infercnv_obj_path) + out_path = Path(out_dir) + + if not obj_path.exists(): + return {"error": f"InferCNV object not found: {infercnv_obj_path}"} + + os.makedirs(out_path, exist_ok=True) + + x_range_r = f"{x_range}" if x_range is not None else "NULL" + color_pal_r = "NULL" + if custom_color_pal: + color_pal_r = "c(" + ", ".join([f"'{c}'" for c in custom_color_pal]) + ")" + + r_script_content = f""" +library(infercnv) +infercnv_obj = readRDS("{obj_path.absolute()}") + +plot_cnv( + infercnv_obj, + out_dir = "{out_path.absolute()}", + output_filename = "{output_filename}", + color_safe_pal = {str(color_safe_pal).upper()}, + title = "{title}", + cluster_by_groups = {str(cluster_by_groups).upper()}, + x_center = {x_center}, + x_range = {x_range_r}, + custom_color_pal = {color_pal_r} +) +""" + + try: + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script_content) + tmp_path = tmp.name + + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + os.unlink(tmp_path) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(f) for f in out_path.glob(f"{output_filename}*")], + "status": "success" + } + + except subprocess.CalledProcessError as e: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "InferCNV plotting failed." + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def infercnv_filter_genes( + raw_counts_matrix: str, + gene_order_file: str, + output_matrix_path: str, + min_cells_per_gene: int = 3, +): + """ + Pre-filter a counts matrix to remove genes expressed in fewer than a threshold number of cells. + + Args: + raw_counts_matrix: Path to the input counts matrix. + gene_order_file: Path to the gene order file. + output_matrix_path: Path to save the filtered matrix. + min_cells_per_gene: Minimum number of cells a gene must be expressed in. + """ + raw_path = Path(raw_counts_matrix) + gene_path = Path(gene_order_file) + out_path = Path(output_matrix_path) + + if not raw_path.exists(): + return {"error": f"Input matrix not found: {raw_counts_matrix}"} + + r_script_content = f""" +library(infercnv) +# Load data +counts = read.table("{raw_path.absolute()}", header=TRUE, row.names=1, check.names=FALSE) +# Filter +gene_counts = rowSums(counts > 0) +filtered_counts = counts[gene_counts >= {min_cells_per_gene}, ] +# Save +write.table(filtered_counts, file="{out_path.absolute()}", quote=FALSE, sep='\\t') +""" + + try: + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script_content) + tmp_path = tmp.name + + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + os.unlink(tmp_path) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_path)], + "status": "success" + } + except subprocess.CalledProcessError as e: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Filtering failed." + } + except Exception as e: + return {"error": str(e)} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-infercnv/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..f701e922db8fed281ae7e21356ec9c7d1a54f5e2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-infercnv: + build: . + image: mcp-bioconductor-infercnv:latest + container_name: mcp-bioconductor-infercnv + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-infercnv + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-infercnv/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3f15fb47aebdab67c6414b91b31d218a96f2e752 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-infercnv + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-infercnv/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4063797cda67f7442fa6031a699b1ed32b5b26f8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-org.hs.eg.db via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-org.hs.eg.db -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-org.hs.eg.db_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-org.hs.eg.db_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-org.hs.eg.db_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ef01efa092ad87d8d15838db861646b36b04b8b9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_server.py @@ -0,0 +1,195 @@ +import subprocess +import json +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any + +def run_r_command(script: str) -> Dict[str, Any]: + """ + Helper function to execute R code and capture output. + Uses jsonlite in R to return structured data. + """ + # Wrap the script to load the library and output JSON + full_script = f""" + suppressPackageStartupMessages(library(org.hs.eg.db)) + suppressPackageStartupMessages(library(jsonlite)) + + tryCatch({{ + {script} + }}, error = function(e) {{ + write(paste("ERROR:", e$message), stderr()) + q(status = 1) + }}) + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp: + tmp.write(full_script) + tmp_path = tmp.name + + try: + process = subprocess.run( + ["Rscript", tmp_path], + capture_output=True, + text=True, + check=True + ) + return { + "stdout": process.stdout, + "stderr": process.stderr, + "command_executed": f"Rscript {tmp_path}" + } + except subprocess.CalledProcessError as e: + return { + "error": e.stderr or e.stdout, + "command_executed": f"Rscript {tmp_path}", + "stdout": e.stdout, + "stderr": e.stderr + } + finally: + if Path(tmp_path).exists(): + Path(tmp_path).unlink() + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_org_hs_eg_db' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def org_hs_eg_db_select( + keys: List[str], + columns: List[str], + keytype: str = "ENTREZID", +) -> Dict[str, Any]: + """ + Retrieve annotations for the specified keys from the Human (org.hs.eg.db) database. + + Args: + keys: A list of identifiers to look up (e.g., ["7157", "4312"] or ["TP53", "BRCA1"]). + columns: The types of data to retrieve (e.g., ["SYMBOL", "GENENAME", "ENSEMBL"]). + keytype: The type of the input keys (e.g., "ENTREZID", "SYMBOL", "ENSEMBL"). + """ + # Validation + if not keys: + return {"error": "At least one key must be provided."} + if not columns: + return {"error": "At least one column must be provided."} + + # Format R vectors + r_keys = 'c("' + '","'.join(keys) + '")' + r_cols = 'c("' + '","'.join(columns) + '")' + + script = f""" + res <- select(org.hs.eg.db, keys = {r_keys}, columns = {r_cols}, keytype = "{keytype}") + cat(toJSON(res, pretty = TRUE)) + """ + + result = run_r_command(script) + return result + +@mcp.tool() +def org_hs_eg_db_keytypes() -> Dict[str, Any]: + """ + List all available types of identifiers (keytypes) that can be used as input for queries. + Common types include ENTREZID, SYMBOL, ENSEMBL, and UNIPROT. + """ + script = """ + res <- keytypes(org.hs.eg.db) + cat(toJSON(res)) + """ + return run_r_command(script) + +@mcp.tool() +def org_hs_eg_db_columns() -> Dict[str, Any]: + """ + List all available annotation columns that can be retrieved from the database. + """ + script = """ + res <- columns(org.hs.eg.db) + cat(toJSON(res)) + """ + return run_r_command(script) + +@mcp.tool() +def org_hs_eg_db_map_symbol_to_entrez( + symbols: List[str] +) -> Dict[str, Any]: + """ + A convenience tool to quickly map Human Gene Symbols to Entrez IDs. + + Args: + symbols: List of gene symbols (e.g., ["TP53", "APOE"]). + """ + if not symbols: + return {"error": "No symbols provided."} + + r_keys = 'c("' + '","'.join(symbols) + '")' + script = f""" + res <- select(org.hs.eg.db, keys = {r_keys}, columns = c("ENTREZID"), keytype = "SYMBOL") + cat(toJSON(res, pretty = TRUE)) + """ + return run_r_command(script) + +@mcp.tool() +def org_hs_eg_db_get_keys( + keytype: str = "SYMBOL", + pattern: str = "", + limit: int = 100 +) -> Dict[str, Any]: + """ + Retrieve a list of all valid keys of a specific type, optionally filtered by a pattern. + + Args: + keytype: The type of keys to retrieve (e.g., "SYMBOL", "ENSEMBL"). + pattern: A string pattern to filter keys (uses grep-style matching). + limit: Maximum number of keys to return to prevent overwhelming output. + """ + if limit <= 0: + limit = 100 + + script = f""" + all_keys <- keys(org.hs.eg.db, keytype = "{keytype}") + if ("{pattern}" != "") {{ + all_keys <- all_keys[grep("{pattern}", all_keys)] + }} + res <- head(all_keys, {limit}) + cat(toJSON(res)) + """ + return run_r_command(script) + +@mcp.tool() +def org_hs_eg_db_metadata() -> Dict[str, Any]: + """ + Get metadata about the org.hs.eg.db package, including version, organism, and data sources. + """ + script = """ + res <- metadata(org.hs.eg.db) + cat(toJSON(res, pretty = TRUE)) + """ + return run_r_command(script) + +@mcp.tool() +def org_hs_eg_db_map_ids( + ids: List[str], + from_type: str, + to_type: str +) -> Dict[str, Any]: + """ + Generic tool to map identifiers from one type to another. + + Args: + ids: List of input identifiers. + from_type: The source identifier type (e.g., "ENSEMBL"). + to_type: The target identifier type (e.g., "SYMBOL"). + """ + if not ids: + return {"error": "No IDs provided."} + + r_keys = 'c("' + '","'.join(ids) + '")' + script = f""" + res <- select(org.hs.eg.db, keys = {r_keys}, columns = c("{to_type}"), keytype = "{from_type}") + cat(toJSON(res, pretty = TRUE)) + """ + return run_r_command(script) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f13bc6c9b6c6b4bf2f8d9bfce633ca7d8a2a8b4d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +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') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_org_hs_eg_db' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..4eb289ab819090371a3aacbde6c3d48a7e26bd78 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-org.hs.eg.db: + build: . + image: mcp-bioconductor-org.hs.eg.db:latest + container_name: mcp-bioconductor-org.hs.eg.db + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-org.hs.eg.db + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ddb7785c9c4ce5c193d1ada773c1d53f4a86a89 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-org.hs.eg.db + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.hs.eg.db/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a575fb7871cf3df2022dfe794a043ce28ce6f649 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-preprocesscore via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-preprocesscore -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-preprocesscore_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-preprocesscore_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-preprocesscore_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5843e85775c1b27003df4b229a2a65e1198d779a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py @@ -0,0 +1,340 @@ +import subprocess +import os +from pathlib import Path +from typing import Optional, List +import tempfile + +def run_r_command(script_content: str): + """Helper to execute R code and handle errors.""" + with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp: + tmp.write("library(preprocessCore)\n") + tmp.write(script_content) + tmp_path = tmp.name + + try: + result = subprocess.run( + ["Rscript", tmp_path], + capture_output=True, + text=True, + check=True + ) + return result.stdout, result.stderr + except subprocess.CalledProcessError as e: + raise RuntimeError(f"R execution failed: {e.stderr}\nStdout: {e.stdout}") + finally: + if os.path.exists(tmp_path): + os.remove(tmp_path) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_preprocesscore' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def preprocesscore_normalize_quantiles( + input_file: str, + output_file: str, + keep_names: bool = True, + sep: str = ",", + header: bool = True +): + """ + Perform Quantile Normalization on a numeric matrix. + + Args: + input_file: Path to the input CSV/TSV file containing the matrix. + output_file: Path where the normalized matrix will be saved. + keep_names: Whether to preserve row and column names in the output. + sep: Delimiter used in the input file (e.g., ',' or '\t'). + header: Whether the input file has a header row. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + normalized_data <- normalize.quantiles(data, keep.names={str(keep_names).upper()}) + write.table(normalized_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "normalize.quantiles", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_normalize_quantiles_robust( + input_file: str, + output_file: str, + remove_extreme: str = "both", + n_remove: int = 1, + use_log2: bool = False, + sep: str = ",", + header: bool = True +): + """ + Perform Robust Quantile Normalization. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + remove_extreme: How to remove outliers: 'none', 'left', 'right', or 'both'. + n_remove: Number of extreme values to remove. + use_log2: Whether to apply log2 transformation before normalization. + sep: Delimiter used in the input file. + header: Whether the input file has a header row. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + if remove_extreme not in ["none", "left", "right", "both"]: + return {"error": "remove_extreme must be one of: none, left, right, both"} + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + normalized_data <- normalize.quantiles.robust( + data, + remove.extreme="{remove_extreme}", + n.remove={n_remove}, + use.log2={str(use_log2).upper()} + ) + write.table(normalized_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "normalize.quantiles.robust", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_background_correct( + input_file: str, + output_file: str, + method: str = "rma", + sep: str = ",", + header: bool = True +): + """ + Perform background correction on a matrix of intensities. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + method: Correction method (typically 'rma'). + sep: Delimiter used in the input file. + header: Whether the input file has a header row. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + corrected_data <- background.correct(data, method="{method}") + write.table(corrected_data, "{output_path}", sep="{sep}", col.names={str(header).upper()}, row.names=TRUE, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": f"background.correct(method='{method}')", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_sub_col_summarize_median_polish( + input_file: str, + output_file: str, + group_labels: List[int], + sep: str = ",", + header: bool = True +): + """ + Summarize columns of a matrix using Median Polish based on group labels. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + group_labels: A list of integers representing the group for each column (e.g., [1, 1, 2, 2]). + sep: Delimiter used in the input file. + header: Whether the input file has a header row. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + # Convert Python list to R vector string + r_groups = f"c({','.join(map(str, group_labels))})" + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + groups <- as.integer({r_groups}) + summarized <- subColSummarizeMedianPolish(data, groups) + write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "subColSummarizeMedianPolish", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_sub_col_summarize_log_avg( + input_file: str, + output_file: str, + group_labels: List[int], + sep: str = ",", + header: bool = True +): + """ + Summarize columns of a matrix using Log-Average based on group labels. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + group_labels: A list of integers representing the group for each column. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + r_groups = f"c({','.join(map(str, group_labels))})" + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + groups <- as.integer({r_groups}) + summarized <- subColSummarizeLogAvg(data, groups) + write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "subColSummarizeLogAvg", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_sub_col_summarize_log_median( + input_file: str, + output_file: str, + group_labels: List[int], + sep: str = ",", + header: bool = True +): + """ + Summarize columns of a matrix using Log-Median based on group labels. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + group_labels: A list of integers representing the group for each column. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + r_groups = f"c({','.join(map(str, group_labels))})" + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + groups <- as.integer({r_groups}) + summarized <- subColSummarizeLogMedian(data, groups) + write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "subColSummarizeLogMedian", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def preprocesscore_sub_col_summarize_biweight_midavg( + input_file: str, + output_file: str, + group_labels: List[int], + sep: str = ",", + header: bool = True +): + """ + Summarize columns of a matrix using Biweight Mid-average based on group labels. + + Args: + input_file: Path to the input CSV/TSV file. + output_file: Path for the output file. + group_labels: A list of integers representing the group for each column. + """ + input_path = Path(input_file) + output_path = Path(output_file) + + if not input_path.exists(): + return {"error": f"Input file {input_file} not found."} + + r_groups = f"c({','.join(map(str, group_labels))})" + + r_script = f""" + data <- as.matrix(read.table("{input_path}", header={str(header).upper()}, sep="{sep}")) + groups <- as.integer({r_groups}) + summarized <- subColSummarizeBiweightMidavg(data, groups) + write.table(summarized, "{output_path}", sep="{sep}", col.names=NA, quote=FALSE) + """ + + try: + stdout, stderr = run_r_command(r_script) + return { + "command_executed": "subColSummarizeBiweightMidavg", + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + except Exception as e: + return {"error": str(e)} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b17c42fb3906d5f8515f158588ed44dd7192c773 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_preprocesscore' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e344031a5676f11d324f8a07e7dbcf4341d74455 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-preprocesscore: + build: . + image: mcp-bioconductor-preprocesscore:latest + container_name: mcp-bioconductor-preprocesscore + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-preprocesscore + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9a3892f6e80633f97fad970e2fc73523e6b13dc8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-preprocesscore + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-preprocesscore/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..af4e00630553847553f47ff1414b313f83f1b409 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-scarray.sat + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-sccb2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ef9f5c761e496cb49c647c4b468c551764238a6f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-sccb2 via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-sccb2 -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY bioconductor-sccb2_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-sccb2_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-sccb2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_server.py b/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_server.py new file mode 100644 index 0000000000000000000000000000000000000000..24a6e8ec5dd199871036095be14deef4f459d48f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_server.py @@ -0,0 +1,143 @@ +import subprocess +from pathlib import Path +from typing import Optional, List + +# In a real MCP environment, the 'mcp' object with the 'tool' decorator +# would be provided by the MCP framework. +# from mcp import tool +# +# For demonstration purposes, a placeholder is defined. +class _MCP: + def tool(self, *args, **kwargs): + def decorator(f): + return f + return decorator +mcp = _MCP() + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_sccb2' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_rscript( + script_file: Optional[Path] = None, + expressions: Optional[List[str]] = None, + script_args: Optional[List[str]] = None, + verbose: bool = False, + default_packages: Optional[str] = None, + save: bool = False, + no_environ: bool = False, + no_site_file: bool = False, + no_init_file: bool = False, + restore: bool = False, + vanilla: bool = False, +): + """ + Executes an R script using the Rscript command-line interface. + + This tool is a generic wrapper for the 'Rscript' command, designed to run scripts + from Bioconductor packages like 'sccb2' or any other R script. You can either + provide a path to an R script file or a list of R expressions to be executed directly. + + Args: + script_file: Path to the R script file to be executed. Mutually exclusive with 'expressions'. + expressions: A list of R expressions to execute using the '-e' flag. Mutually exclusive with 'script_file'. + script_args: A list of arguments to be passed to the R script itself. + verbose: If True, prints information on progress. Corresponds to the --verbose flag. + default_packages: A comma-separated string of package names to be loaded. Corresponds to --default-packages. + save: If True, saves the workspace at the end of the session. Corresponds to --save. + no_environ: If True, does not read the site and user environment files. Corresponds to --no-environ. + no_site_file: If True, does not read the site-wide Rprofile. Corresponds to --no-site-file. + no_init_file: If True, does not read the user R profile. Corresponds to --no-init-file. + restore: If True, restores previously saved objects at startup. Corresponds to --restore. + vanilla: If True, combines --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ. + This option overrides the individual flags if set. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + Note: 'output_files' will be empty as the script's outputs are not known beforehand. + """ + # --- Input Validation --- + if script_file and expressions: + raise ValueError("Parameters 'script_file' and 'expressions' are mutually exclusive. Please provide only one.") + if not script_file and not expressions: + raise ValueError("Either 'script_file' or 'expressions' must be provided.") + + if script_file: + if not script_file.is_file(): + raise FileNotFoundError(f"Input script file not found at: {script_file}") + + # --- Command Construction --- + base_command = "Rscript" + command = [base_command] + + # Handle R session options + if vanilla: + command.append("--vanilla") + else: + if save: + command.append("--save") + if no_environ: + command.append("--no-environ") + if no_site_file: + command.append("--no-site-file") + if no_init_file: + command.append("--no-init-file") + if restore: + command.append("--restore") + + # Handle Rscript-specific options + if verbose: + command.append("--verbose") + if default_packages: + command.extend(["--default-packages", default_packages]) + + # Add expressions or script file to the command + if expressions: + for expr in expressions: + command.extend(["-e", expr]) + elif script_file: + command.append(str(script_file)) + + # Add arguments for the R script + if script_args: + command.extend(script_args) + + command_str = " ".join(command) + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + + except FileNotFoundError: + raise RuntimeError(f"Error: '{base_command}' not found. Is R installed and in your system's PATH?") + except subprocess.CalledProcessError as e: + # The process failed. Return a structured error with captured output. + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + # Since the output files are determined by the R script itself, + # we cannot predict them here. We return an empty list. + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..98d8e30154308a0c0fe8948488877961fbeead5f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/app/bioconductor-sccb2_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-sccb2/app/bioconductor-sccb2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_sccb2' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-sccb2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..64f80f2c0b9fee1954c7a433abd544684a676332 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-sccb2: + build: . + image: mcp-bioconductor-sccb2:latest + container_name: mcp-bioconductor-sccb2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-sccb2 + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-sccb2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f49b35fc0692ce48114a48bf1bbad3003d57ff13 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-sccb2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-sccb2/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-sccb2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-sccb2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_server.py b/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ab7918bc19546061f2c4a7b6b5af4c13684e2e1c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_server.py @@ -0,0 +1,269 @@ +import subprocess +import tempfile +import shutil +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Assume 'spari' is an executable available in the system's PATH. +# This wrapper simulates a command-line interface for the bioconductor-spari package, +# as if it were compiled into a standalone tool with subcommands. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_spari' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def preprocess_spatial_data( + input_dir: Path, + output_se_object: Path, + min_cells: int = 3, + min_features: int = 200, + normalization_method: str = "LogNormalize", +) -> Dict[str, Any]: + """ + Preprocesses raw spatial transcriptomics data into a format suitable for SPARi analysis. + + This function simulates the 'spari preprocess' command, taking raw spatial data + (e.g., from 10x Genomics Visium) and generating a processed Seurat or + SpatialExperiment object (saved as an RDS file). + + Args: + input_dir: Path to the directory containing raw spatial data (e.g., filtered_feature_bc_matrix, spatial). + output_se_object: Path to save the processed Seurat/SpatialExperiment object as an RDS file. + min_cells: Minimum number of cells a feature must be detected in to be kept. + min_features: Minimum number of features detected in a cell to be kept. + normalization_method: Normalization method to apply (e.g., "LogNormalize", "SCTransform"). + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_dir.is_dir(): + raise ValueError(f"Input directory '{input_dir}' does not exist or is not a directory.") + if output_se_object.suffix != ".rds": + raise ValueError(f"Output file '{output_se_object}' must have a '.rds' extension.") + if min_cells < 0: + raise ValueError("min_cells must be a non-negative integer.") + if min_features < 0: + raise ValueError("min_features must be a non-negative integer.") + if normalization_method not in ["LogNormalize", "SCTransform", "none"]: + raise ValueError(f"Unsupported normalization_method: '{normalization_method}'. " + "Choose from 'LogNormalize', 'SCTransform', or 'none'.") + + # Ensure output directory exists + output_se_object.parent.mkdir(parents=True, exist_ok=True) + + command = [ + "spari", "preprocess", + "--input", str(input_dir), + "--output", str(output_se_object), + "--min-cells", str(min_cells), + "--min-features", str(min_features), + "--normalization", normalization_method, + ] + + try: + process = subprocess.run(command, capture_output=True, text=True, check=True) + stdout = process.stdout + stderr = process.stderr + except FileNotFoundError: + raise RuntimeError("The 'spari' executable was not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"SPARi preprocess failed with exit code {e.returncode}", + "output_files": [], + } + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_se_object)], + } + + +@mcp.tool() +def analyze_spatial_data( + se_object_path: Path, + image_features_path: Path, + output_dir: Path, + n_pcs: int = 30, + resolution: float = 0.8, + n_neighbors: int = 20, + random_seed: int = 42, +) -> Dict[str, Any]: + """ + Performs spatial analysis using preprocessed spatial transcriptomics data and image features. + + This function simulates the 'spari analyze' command, taking a processed Seurat/SpatialExperiment + object and image-based features to perform clustering, dimensionality reduction, and + spatial domain identification. + + Args: + se_object_path: Path to the preprocessed Seurat/SpatialExperiment object (RDS file). + image_features_path: Path to the image features file (e.g., another RDS file or CSV). + output_dir: Directory to save analysis results (e.g., updated SE object, clustering results). + n_pcs: Number of principal components to use for dimensionality reduction. + resolution: Clustering resolution for graph-based clustering. + n_neighbors: Number of neighbors to use for UMAP/tSNE and graph construction. + random_seed: Seed for random number generation to ensure reproducibility. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not se_object_path.is_file(): + raise ValueError(f"Seurat/SpatialExperiment object file '{se_object_path}' does not exist.") + if not image_features_path.is_file(): + raise ValueError(f"Image features file '{image_features_path}' does not exist.") + if se_object_path.suffix != ".rds": + raise ValueError(f"Input Seurat object '{se_object_path}' must have a '.rds' extension.") + if n_pcs <= 0: + raise ValueError("n_pcs must be a positive integer.") + if not (0 < resolution <= 5.0): # Common range for Seurat resolution + raise ValueError("resolution must be between 0 and 5.0.") + if n_neighbors <= 0: + raise ValueError("n_neighbors must be a positive integer.") + + output_dir.mkdir(parents=True, exist_ok=True) + output_se_object = output_dir / "analyzed_se_object.rds" + output_clusters_csv = output_dir / "cluster_assignments.csv" + + command = [ + "spari", "analyze", + "--se-object", str(se_object_path), + "--image-features", str(image_features_path), + "--output-dir", str(output_dir), + "--n-pcs", str(n_pcs), + "--resolution", str(resolution), + "--n-neighbors", str(n_neighbors), + "--seed", str(random_seed), + ] + + try: + process = subprocess.run(command, capture_output=True, text=True, check=True) + stdout = process.stdout + stderr = process.stderr + except FileNotFoundError: + raise RuntimeError("The 'spari' executable was not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"SPARi analysis failed with exit code {e.returncode}", + "output_files": [], + } + + # Assuming the tool creates these files + output_files = [str(output_se_object), str(output_clusters_csv)] + # Check if files were actually created by the hypothetical tool + output_files = [f for f in output_files if Path(f).exists()] + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + + +@mcp.tool() +def plot_spatial_results( + se_object_path: Path, + output_plot_path: Path, + plot_type: str, + feature_to_plot: Optional[str] = None, + cluster_id: Optional[str] = None, + width: int = 800, + height: int = 600, + plot_format: str = "png", +) -> Dict[str, Any]: + """ + Generates visualizations of spatial data and analysis results. + + This function simulates the 'spari plot' command, allowing users to visualize + spatial gene expression, clusters, or other features on the spatial transcriptomics + slide. + + Args: + se_object_path: Path to the analyzed Seurat/SpatialExperiment object (RDS file). + output_plot_path: Path to save the generated plot (e.g., PNG, PDF). + plot_type: Type of plot to generate. Valid options: "feature_plot", "spatial_dim_plot", "cluster_plot". + feature_to_plot: Name of the feature (e.g., gene name) to plot. Required for "feature_plot". + cluster_id: Identifier of a specific cluster to highlight or plot. + width: Width of the output plot in pixels. + height: Height of the output plot in pixels. + plot_format: Format of the output plot (e.g., "png", "pdf", "jpeg"). + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not se_object_path.is_file(): + raise ValueError(f"Seurat/SpatialExperiment object file '{se_object_path}' does not exist.") + if se_object_path.suffix != ".rds": + raise ValueError(f"Input Seurat object '{se_object_path}' must have a '.rds' extension.") + if plot_type not in ["feature_plot", "spatial_dim_plot", "cluster_plot"]: + raise ValueError(f"Unsupported plot_type: '{plot_type}'. " + "Choose from 'feature_plot', 'spatial_dim_plot', 'cluster_plot'.") + if plot_type == "feature_plot" and feature_to_plot is None: + raise ValueError("feature_to_plot is required for 'feature_plot' type.") + if width <= 0 or height <= 0: + raise ValueError("Plot width and height must be positive integers.") + if plot_format not in ["png", "pdf", "jpeg", "tiff"]: + raise ValueError(f"Unsupported plot_format: '{plot_format}'. " + "Choose from 'png', 'pdf', 'jpeg', 'tiff'.") + if output_plot_path.suffix.lower().lstrip('.') != plot_format.lower(): + raise ValueError(f"Output plot file extension '{output_plot_path.suffix}' does not match " + f"specified plot_format '{plot_format}'.") + + output_plot_path.parent.mkdir(parents=True, exist_ok=True) + + command = [ + "spari", "plot", + "--se-object", str(se_object_path), + "--output", str(output_plot_path), + "--type", plot_type, + "--width", str(width), + "--height", str(height), + "--format", plot_format, + ] + + if feature_to_plot: + command.extend(["--feature", feature_to_plot]) + if cluster_id: + command.extend(["--cluster-id", cluster_id]) + + try: + process = subprocess.run(command, capture_output=True, text=True, check=True) + stdout = process.stdout + stderr = process.stderr + except FileNotFoundError: + raise RuntimeError("The 'spari' executable was not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"SPARi plot failed with exit code {e.returncode}", + "output_files": [], + } + + # Assuming the tool creates the specified output plot file + output_files = [str(output_plot_path)] + output_files = [f for f in output_files if Path(f).exists()] + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b7af9b4eed3010d2d3ed81add9c034c1e5e6ff8b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spari/app/bioconductor-spari_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spari/app/bioconductor-spari_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_spari' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-spari/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spari/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spari/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4e953a8080a0be08c686284f830f15529aacd419 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-spatialfda via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-spatialfda -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-spatialfda_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-spatialfda_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-spatialfda_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_server.py new file mode 100644 index 0000000000000000000000000000000000000000..241a695ca544ff93f7812baf9601443df44c4a1e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_server.py @@ -0,0 +1,296 @@ +import subprocess +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_spatialfda' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def spatial_fda_fit( + expression_csv: str, + coords_csv: str, + output_rds: str, + nbasis: int = 10, + n_comp: int = 3, + lambda_val: float = 0.1, + basis_type: str = "bspline", +) -> Dict[str, Any]: + """ + Fits a Spatial Functional Data Analysis (spatialFDA) model to spatial transcriptomics or proteomics data. + + Args: + expression_csv: Path to a CSV file where rows are observations and columns are functional points (e.g., time or distance). + coords_csv: Path to a CSV file containing 'x' and 'y' spatial coordinates. + output_rds: Path to save the fitted model object (.rds). + nbasis: Number of basis functions for functional smoothing. + n_comp: Number of functional principal components to retain. + lambda_val: Smoothing parameter (penalty). + basis_type: Type of basis functions to use ('bspline' or 'fourier'). + """ + # Input validation + expr_path = Path(expression_csv) + coords_path = Path(coords_csv) + out_path = Path(output_rds) + + if not expr_path.exists(): + return {"error": f"Expression file not found: {expression_csv}"} + if not coords_path.exists(): + return {"error": f"Coordinates file not found: {coords_csv}"} + if nbasis <= 0: + return {"error": "nbasis must be a positive integer"} + if n_comp <= 0: + return {"error": "n_comp must be a positive integer"} + + # Construct R script + r_code = f""" + library(spatialFDA) + expr_data <- read.csv("{expr_path}") + coords_data <- read.csv("{coords_path}") + + # Ensure data is matrix + expr_mat <- as.matrix(expr_data) + coords_mat <- as.matrix(coords_data) + + # Fit model + model <- spatialFDA( + data = expr_mat, + coords = coords_mat, + nbasis = {nbasis}, + n_comp = {n_comp}, + lambda = {lambda_val}, + basis_type = "{basis_type}" + ) + + saveRDS(model, "{out_path}") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"spatialFDA fit with {nbasis} basis functions", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def spatial_fda_predict( + model_rds: str, + new_coords_csv: str, + output_csv: str, +) -> Dict[str, Any]: + """ + Predicts functional values at new spatial locations using a pre-trained spatialFDA model. + + Args: + model_rds: Path to the fitted spatialFDA model (.rds). + new_coords_csv: Path to CSV containing new 'x' and 'y' coordinates for prediction. + output_csv: Path to save the predicted functional values. + """ + model_path = Path(model_rds) + coords_path = Path(new_coords_csv) + out_path = Path(output_csv) + + if not model_path.exists(): + return {"error": f"Model file not found: {model_rds}"} + if not coords_path.exists(): + return {"error": f"Coordinates file not found: {new_coords_csv}"} + + r_code = f""" + library(spatialFDA) + model <- readRDS("{model_path}") + new_coords <- read.csv("{coords_path}") + + predictions <- predict(model, new_coords = as.matrix(new_coords)) + write.csv(predictions, "{out_path}", row.names = FALSE) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "spatialFDA prediction", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def spatial_fda_fpca( + model_rds: str, + output_prefix: str, +) -> Dict[str, Any]: + """ + Extracts Functional Principal Component Analysis (FPCA) scores and eigenfunctions from the model. + + Args: + model_rds: Path to the fitted spatialFDA model (.rds). + output_prefix: Prefix for output CSV files (scores and eigenfunctions). + """ + model_path = Path(model_rds) + if not model_path.exists(): + return {"error": f"Model file not found: {model_rds}"} + + scores_out = f"{output_prefix}_scores.csv" + eigen_out = f"{output_prefix}_eigenfunctions.csv" + + r_code = f""" + library(spatialFDA) + model <- readRDS("{model_path}") + + # Extract FPCA components + scores <- model$fpca_scores + eigenfunctions <- model$eigenfunctions + + write.csv(scores, "{scores_out}", row.names = FALSE) + write.csv(eigenfunctions, "{eigen_out}", row.names = FALSE) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "spatialFDA FPCA extraction", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [scores_out, eigen_out] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def spatial_fda_summary_stats( + model_rds: str, +) -> Dict[str, Any]: + """ + Returns summary statistics of the fitted spatialFDA model, including variance explained. + + Args: + model_rds: Path to the fitted spatialFDA model (.rds). + """ + model_path = Path(model_rds) + if not model_path.exists(): + return {"error": f"Model file not found: {model_rds}"} + + r_code = f""" + library(spatialFDA) + model <- readRDS("{model_path}") + cat("--- Model Summary ---\\n") + cat("Number of Basis Functions:", model$nbasis, "\\n") + cat("Number of Components:", model$n_comp, "\\n") + cat("Variance Explained by Components:\\n") + print(model$var_explained) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "spatialFDA summary", + "stdout": result.stdout, + "stderr": result.stderr, + "model_info": result.stdout + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def spatial_fda_plot( + model_rds: str, + plot_type: str = "eigenfunctions", + output_pdf: str = "spatial_fda_plots.pdf", +) -> Dict[str, Any]: + """ + Generates diagnostic and results plots for the spatialFDA model. + + Args: + model_rds: Path to the fitted spatialFDA model (.rds). + plot_type: Type of plot to generate ('eigenfunctions', 'scores', or 'reconstruction'). + output_pdf: Path to save the resulting plot as a PDF. + """ + model_path = Path(model_rds) + out_path = Path(output_pdf) + + if not model_path.exists(): + return {"error": f"Model file not found: {model_rds}"} + + valid_plots = ["eigenfunctions", "scores", "reconstruction"] + if plot_type not in valid_plots: + return {"error": f"Invalid plot_type. Must be one of: {', '.join(valid_plots)}"} + + r_code = f""" + library(spatialFDA) + model <- readRDS("{model_path}") + pdf("{out_path}") + if ("{plot_type}" == "eigenfunctions") {{ + plot(model, type = "eigenfunctions") + }} else if ("{plot_type}" == "scores") {{ + plot(model, type = "scores") + }} else {{ + plot(model, type = "reconstruction") + }} + dev.off() + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"spatialFDA plot: {plot_type}", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..10cea93a03170b64c1d03a127dd8ac35245e1fa5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_spatialfda' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9efb8125ae1092edfc49a3a1d814191b8b3bd3a9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-spatialfda: + build: . + image: mcp-bioconductor-spatialfda:latest + container_name: mcp-bioconductor-spatialfda + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-spatialfda + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1b7a8a07517993c5588bf1e5fe4accb8295ec2b2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-spatialfda + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfda/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfda/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-standr/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..86f93dbc55057d04067ba580336f730c08da84c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-standr via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-standr -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-standr_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-standr_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-standr_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_server.py b/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_server.py new file mode 100644 index 0000000000000000000000000000000000000000..98a452a40df6d5a842d36015e82c896d26617646 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_server.py @@ -0,0 +1,249 @@ +import subprocess +import tempfile +import shlex +from pathlib import Path +from typing import Optional, List, Dict, Union + +def _format_r_list(items: Optional[str]) -> str: + """Formats a comma-separated string into an R character vector string.""" + if items is None: + return "NULL" + # Split, strip whitespace, and quote each item + item_list = [f'"{item.strip()}"' for item in items.split(',')] + return f"c({', '.join(item_list)})" + +def _format_r_value(value: Union[str, int, float, bool, None]) -> str: + """Formats a Python value into its R string representation.""" + if value is None: + return "NULL" + if isinstance(value, bool): + return "TRUE" if value else "FALSE" + if isinstance(value, str): + return f'"{value}"' + return str(value) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_standr' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_standr_analysis( + dcc_files_dir: Path, + pkc_file: Path, + sample_sheet: Path, + output_dir: Path, + min_segment_reads: Optional[int] = None, + min_nuclei: Optional[int] = None, + percent_aligned_threshold: Optional[float] = None, + percent_saturation_threshold: Optional[float] = None, + norm_method: str = "quant", + housekeeping_genes: Optional[str] = None, + batch_variable: Optional[str] = None, + correction_method: str = "RUV", + run_dim_reduction: bool = True, + run_clustering: bool = True, +) -> Dict[str, Union[str, List[str]]]: + """ + Runs a standard spatial transcriptomics analysis workflow for Nanostring GeoMx data using the standR Bioconductor package. + + This tool performs data loading, quality control, normalization, optional batch correction, + dimensionality reduction (UMAP), and clustering. + """ + # --- Input Validation --- + if not dcc_files_dir.is_dir(): + raise ValueError(f"Input DCC directory not found: {dcc_files_dir}") + if not pkc_file.is_file(): + raise ValueError(f"Input PKC file not found: {pkc_file}") + if not sample_sheet.is_file(): + raise ValueError(f"Input sample sheet file not found: {sample_sheet}") + + if norm_method not in ["quant", "neg", "hk"]: + raise ValueError(f"Invalid normalization method '{norm_method}'. Must be one of 'quant', 'neg', 'hk'.") + if norm_method == "hk" and housekeeping_genes is None: + raise ValueError("`housekeeping_genes` must be provided when `norm_method` is 'hk'.") + + if correction_method not in ["RUV", "limma"]: + raise ValueError(f"Invalid batch correction method '{correction_method}'. Must be 'RUV' or 'limma'.") + + if percent_aligned_threshold is not None and not (0 <= percent_aligned_threshold <= 100): + raise ValueError("`percent_aligned_threshold` must be between 0 and 100.") + if percent_saturation_threshold is not None and not (0 <= percent_saturation_threshold <= 100): + raise ValueError("`percent_saturation_threshold` must be between 0 and 100.") + + # --- File Path Handling --- + output_dir.mkdir(parents=True, exist_ok=True) + + # --- R Script Generation --- + r_script_content = f""" + # Load required libraries + suppressPackageStartupMessages(library(standR)) + suppressPackageStartupMessages(library(SpatialExperiment)) + suppressPackageStartupMessages(library(dplyr)) + suppressPackageStartupMessages(library(readr)) + suppressPackageStartupMessages(library(ggplot2)) + suppressPackageStartupMessages(library(tibble)) + + # --- Parameters --- + dcc_dir <- "{dcc_files_dir.resolve()}" + pkc_f <- "{pkc_file.resolve()}" + sample_sheet_f <- "{sample_sheet.resolve()}" + output_dir <- "{output_dir.resolve()}" + + # QC Parameters + min_seg_reads <- {_format_r_value(min_segment_reads)} + min_nuc <- {_format_r_value(min_nuclei)} + perc_aligned <- {_format_r_value(percent_aligned_threshold)} + perc_sat <- {_format_r_value(percent_saturation_threshold)} + + # Normalization Parameters + norm_m <- "{norm_method}" + hk_genes <- {_format_r_list(housekeeping_genes)} + + # Batch Correction Parameters + batch_var <- {_format_r_value(batch_variable)} + correction_m <- "{correction_method}" + + # Analysis Parameters + run_dim_red <- {_format_r_value(run_dim_reduction)} + run_clust <- {_format_r_value(run_clustering)} + + # --- Main Workflow --- + tryCatch({{ + # 1. Load data + message("Step 1: Loading GeoMx data...") + spe <- readGeoMx(dcc.files = dcc_dir, + pkc.files = pkc_f, + pheno.data.file = sample_sheet_f) + message("Data loading complete.") + + # 2. QC + message("Step 2: Performing Quality Control...") + qc_criteria <- list() + if (!is.null(min_seg_reads)) {{ qc_criteria$minSegmentReads <- min_seg_reads }} + if (!is.null(min_nuc)) {{ qc_criteria$minNuclei <- min_nuc }} + if (!is.null(perc_aligned)) {{ qc_criteria$percentAligned <- perc_aligned }} + if (!is.null(perc_sat)) {{ qc_criteria$percentSaturation <- perc_sat }} + + if (length(qc_criteria) > 0) {{ + spe <- geomx_qc(spe, qc.criteria = qc_criteria) + message("QC filtering applied.") + }} else {{ + message("No QC criteria specified, skipping filtering.") + }} + + # 3. Normalization + message(paste0("Step 3: Normalizing data using '", norm_m, "' method...")) + if (norm_m == "hk" && !is.null(hk_genes)) {{ + spe <- normalise(spe, norm.method = "hk", from.assay = "counts", to.assay = "norm", hk = hk_genes) + }} else if (norm_m == "neg") {{ + spe <- normalise(spe, norm.method = "neg", from.assay = "counts", to.assay = "norm") + }} else {{ + spe <- normalise(spe, norm.method = "quant", from.assay = "counts", to.assay = "norm") + }} + message("Normalization complete.") + + # 4. Batch Correction + final_assay <- "norm" + if (!is.null(batch_var)) {{ + message(paste0("Step 4: Applying batch correction on variable '", batch_var, "' using '", correction_m, "'...")) + spe <- batch_correct(spe, + batch = colData(spe)[[batch_var]], + from.assay = "norm", + to.assay = "norm_bc", + method = correction_m) + final_assay <- "norm_bc" + message("Batch correction complete.") + }} else {{ + message("Step 4: No batch variable provided, skipping batch correction.") + }} + + # 5. Dimensionality Reduction & Clustering + if (run_dim_red) {{ + message("Step 5a: Running UMAP for dimensionality reduction...") + spe <- dim_reduce(spe, from.assay = final_assay, method = "UMAP") + p_umap <- plot_dim_reduce(spe, colour.by = batch_var) + ggsave(file.path(output_dir, "UMAP_plot.pdf"), p_umap, width = 7, height = 6) + message("UMAP plot saved.") + }} + + if (run_clust) {{ + message("Step 5b: Running clustering...") + spe <- find_clusters(spe, from.assay = final_assay) + p_clust <- plot_dim_reduce(spe, colour.by = "clusters") + ggsave(file.path(output_dir, "UMAP_clusters_plot.pdf"), p_clust, width = 7, height = 6) + message("Clustering plot saved.") + }} + + # 6. Save results + message("Step 6: Saving output files...") + saveRDS(spe, file = file.path(output_dir, "final_spe_object.rds")) + + final_counts <- as.data.frame(assay(spe, final_assay)) + final_counts <- tibble::rownames_to_column(final_counts, "Gene") + write_csv(final_counts, file = file.path(output_dir, "final_processed_counts.csv")) + + write_csv(as.data.frame(colData(spe)), file = file.path(output_dir, "final_coldata.csv")) + message("Output files saved successfully.") + + message("\\nstandR workflow completed successfully.") + + }}, error = function(e) {{ + message("An error occurred during the standR workflow:") + message(e$message) + quit(status = 1, save = "no") + }}) + """ + + # --- Subprocess Execution --- + output_files = [] + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as f: + script_path = Path(f.name) + f.write(r_script_content) + + cmd = ["Rscript", str(script_path)] + command_executed = shlex.join(cmd) + + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + + # --- Collect Output Files --- + output_files = [ + str(output_dir / "final_spe_object.rds"), + str(output_dir / "final_processed_counts.csv"), + str(output_dir / "final_coldata.csv"), + ] + if run_dim_reduction: + output_files.append(str(output_dir / "UMAP_plot.pdf")) + if run_clustering: + output_files.append(str(output_dir / "UMAP_clusters_plot.pdf")) + + # Filter for files that actually exist + output_files = [p for p in output_files if Path(p).exists()] + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": output_files, + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": f"R script execution failed with exit code {e.returncode}", + } + finally: + if 'script_path' in locals() and script_path.exists(): + script_path.unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5124de6f1a26cd061d0f12325733f6f58b583463 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/app/bioconductor-standr_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-standr/app/bioconductor-standr_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_standr' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-standr/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-standr/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..d7206c638d7159d286d43f36148bc70f38cd90c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-standr: + build: . + image: mcp-bioconductor-standr:latest + container_name: mcp-bioconductor-standr + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-standr + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-standr/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..80500636d4d664f54829cb7877154b020109c530 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-standr + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-standr/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-standr/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-standr/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-svp/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..55d520017b65e7ab21c5d60c7882e80cb419d065 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-svp via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-svp -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-svp_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-svp_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-svp_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/app/bioconductor-svp_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-svp/app/bioconductor-svp_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4dad7a55123579904eabc2f82fd5bb0ef34d2c00 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/app/bioconductor-svp_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-svp/app/bioconductor-svp_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_svp' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-svp/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-svp/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..deb678c44d45a833fb58861466267c1f9fcc3fc7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-svp: + build: . + image: mcp-bioconductor-svp:latest + container_name: mcp-bioconductor-svp + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-svp + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-svp/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f48187327f038c5fc31b8a5b5753cbd1ef47b1c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-svp + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-svp/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-svp/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-svp/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bowtie2/Dockerfile b/Biomni/mcp_generated/mcp_bowtie2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..484df324fc962e67f03afc5f4a0efc621fb6b948 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bowtie2/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bowtie2 via conda (e.g., from bioconda) +RUN conda install -c bioconda bowtie2 -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bowtie2_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bowtie2_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bowtie2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bowtie2/docker-compose.yml b/Biomni/mcp_generated/mcp_bowtie2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..add4d2c95250fdada71909666fb03ab9baf80c82 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bowtie2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bowtie2: + build: . + image: mcp-bowtie2:latest + container_name: mcp-bowtie2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bowtie2 + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bowtie2/environment.yaml b/Biomni/mcp_generated/mcp_bowtie2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..56712b35c54c869a0d14e760ba6372aa027278b9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bowtie2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bowtie2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bowtie2/requirements.txt b/Biomni/mcp_generated/mcp_bowtie2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bowtie2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_dnaio/Dockerfile b/Biomni/mcp_generated/mcp_dnaio/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4a6248af07c327b836dbbcba8614614a5ef1bd5d --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install dnaio via conda (e.g., from bioconda) +RUN conda install -c bioconda dnaio -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/dnaio_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/dnaio_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/dnaio_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_dnaio/app/dnaio_shim_server.py b/Biomni/mcp_generated/mcp_dnaio/app/dnaio_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cde6fc04821de91372bd1d3ef3860f2f54bc8d7e --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/app/dnaio_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dnaio/app/dnaio_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_dnaio' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_dnaio/app/requirements.txt b/Biomni/mcp_generated/mcp_dnaio/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_dnaio/docker-compose.yml b/Biomni/mcp_generated/mcp_dnaio/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..dbda3b4accdf3423e87829527f249f3a8673120a --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-dnaio: + build: . + image: mcp-dnaio:latest + container_name: mcp-dnaio + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=dnaio + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_dnaio/environment.yaml b/Biomni/mcp_generated/mcp_dnaio/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1423be2bb7ab06e8fa244620baa4c923209d88ce --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - dnaio + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_dnaio/requirements.txt b/Biomni/mcp_generated/mcp_dnaio/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_dnaio/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_server.py b/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9e8e68147d3efcb0960a579d9db0e69b39d327bc --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_server.py @@ -0,0 +1,272 @@ +from pathlib import Path +import subprocess +import sys +import tempfile +from typing import Optional + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_doubletdetection' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def boost_classifier( + input_path: str, + output_path: str, + n_iters: int = 25, + clustering_algorithm: str = "louvain", + standard_scaling: bool = False, + p_thresh: float = 1e-7, + voter_thresh: float = 0.5, + n_jobs: int = -1, + random_state: int = 123, + n_top_genes: int = 10000, + pseudocount: float = 0.1, + save_model_path: Optional[str] = None, +): + """ + Run DoubletDetection BoostClassifier to detect doublets in scRNA-seq data. + + Args: + input_path: Path to input file (.h5ad, .csv, .tsv, or .mtx). + output_path: Path to save the results (AnnData .h5ad or .csv). + n_iters: Number of iterations for doublet simulation. + clustering_algorithm: Clustering method to use ('louvain', 'leiden', or 'phenograph'). + standard_scaling: Whether to use standard scaling. + p_thresh: P-value threshold for doublet calling. + voter_thresh: Voter threshold for doublet calling. + n_jobs: Number of jobs for parallel processing (-1 for all processors). + random_state: Random seed for reproducibility. + n_top_genes: Number of top genes to use. + pseudocount: Pseudocount for log transformation. + save_model_path: Optional path to save the fitted classifier object (as a pickle file) for plotting. + """ + # Input validation + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + output_file = Path(output_path) + + if n_iters <= 0: + return {"error": "n_iters must be a positive integer"} + + valid_algorithms = ["louvain", "leiden", "phenograph"] + if clustering_algorithm not in valid_algorithms: + return {"error": f"clustering_algorithm must be one of {valid_algorithms}"} + + if not (0 <= p_thresh <= 1): + return {"error": "p_thresh must be between 0 and 1"} + + if not (0 <= voter_thresh <= 1): + return {"error": "voter_thresh must be between 0 and 1"} + + # Create a temporary python script to run the analysis + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + script_path = tmp.name + tmp.write(f""" +import doubletdetection +import scanpy as sc +import pandas as pd +import numpy as np +import pickle +from pathlib import Path + +def run(): + input_path = r"{input_path}" + output_path = r"{output_path}" + + # Load data + if input_path.endswith('.h5ad'): + adata = sc.read_h5ad(input_path) + raw_counts = adata.X + elif input_path.endswith('.csv'): + raw_counts = pd.read_csv(input_path, index_col=0) + elif input_path.endswith('.tsv'): + raw_counts = pd.read_csv(input_path, sep='\\t', index_col=0) + else: + # Fallback for other formats supported by scanpy + adata = sc.read(input_path) + raw_counts = adata.X + + # Initialize classifier + clf = doubletdetection.BoostClassifier( + n_iters={n_iters}, + clustering_algorithm="{clustering_algorithm}", + standard_scaling={standard_scaling}, + n_jobs={n_jobs}, + random_state={random_state}, + n_top_genes={n_top_genes}, + pseudocount={pseudocount} + ) + + # Fit and Predict + labels = clf.fit(raw_counts).predict(p_thresh={p_thresh}, voter_thresh={voter_thresh}) + scores = clf.doublet_score() + + # Save results + if input_path.endswith('.h5ad'): + adata.obs['doublet_score'] = scores + adata.obs['doublet_label'] = labels + adata.write(output_path) + else: + results = pd.DataFrame({{ + 'doublet_score': scores, + 'doublet_label': labels + }}, index=range(len(labels))) + results.to_csv(output_path) + + # Save model if requested + save_model_path = "{save_model_path if save_model_path else ''}" + if save_model_path: + with open(save_model_path, 'wb') as f: + pickle.dump(clf, f) + +if __name__ == "__main__": + run() +""") + + try: + # Execute the script + result = subprocess.run( + [sys.executable, script_path], + capture_output=True, + text=True, + check=True + ) + + return { + "command_executed": f"doubletdetection.BoostClassifier(n_iters={n_iters}, ...).fit().predict()", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_path] + ([save_model_path] if save_model_path else []) + } + except subprocess.CalledProcessError as e: + return { + "error": "DoubletDetection execution failed", + "stdout": e.stdout, + "stderr": e.stderr, + "command_executed": e.cmd + } + finally: + if Path(script_path).exists(): + Path(script_path).unlink() + +@mcp.tool() +def plot_convergence( + model_path: str, + output_plot_path: str, +): + """ + Generate a convergence plot for a fitted DoubletDetection classifier. + + Args: + model_path: Path to the pickled BoostClassifier object (saved from boost_classifier). + output_plot_path: Path to save the convergence plot (e.g., .png, .pdf). + """ + model_file = Path(model_path) + if not model_file.exists(): + return {"error": f"Model file not found: {model_path}"} + + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + script_path = tmp.name + tmp.write(f""" +import doubletdetection +import pickle +import matplotlib.pyplot as plt +from pathlib import Path + +with open(r"{model_path}", 'rb') as f: + clf = pickle.load(f) + +fig = doubletdetection.plot.convergence(clf, show=False, save=r"{output_plot_path}") +""") + + try: + result = subprocess.run( + [sys.executable, script_path], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "doubletdetection.plot.convergence()", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_plot_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "Plotting convergence failed", + "stdout": e.stdout, + "stderr": e.stderr + } + finally: + if Path(script_path).exists(): + Path(script_path).unlink() + +@mcp.tool() +def plot_threshold( + model_path: str, + output_plot_path: str, + p_thresh: float = 1e-7, + voter_thresh: float = 0.5, +): + """ + Generate a threshold plot for a fitted DoubletDetection classifier. + + Args: + model_path: Path to the pickled BoostClassifier object. + output_plot_path: Path to save the threshold plot (e.g., .png, .pdf). + p_thresh: P-value threshold to visualize. + voter_thresh: Voter threshold to visualize. + """ + model_file = Path(model_path) + if not model_file.exists(): + return {"error": f"Model file not found: {model_path}"} + + if not (0 <= p_thresh <= 1): + return {"error": "p_thresh must be between 0 and 1"} + + if not (0 <= voter_thresh <= 1): + return {"error": "voter_thresh must be between 0 and 1"} + + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + script_path = tmp.name + tmp.write(f""" +import doubletdetection +import pickle +import matplotlib.pyplot as plt +from pathlib import Path + +with open(r"{model_path}", 'rb') as f: + clf = pickle.load(f) + +fig = doubletdetection.plot.threshold(clf, p_thresh={p_thresh}, voter_thresh={voter_thresh}, show=False, save=r"{output_plot_path}") +""") + + try: + result = subprocess.run( + [sys.executable, script_path], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"doubletdetection.plot.threshold(p_thresh={p_thresh}, voter_thresh={voter_thresh})", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_plot_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "Plotting threshold failed", + "stdout": e.stdout, + "stderr": e.stderr + } + finally: + if Path(script_path).exists(): + Path(script_path).unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_shim_server.py b/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..688572745f75b2396d4c15cb5ea0ced33ddc529d --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/app/doubletdetection_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_doubletdetection/app/doubletdetection_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_doubletdetection' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_doubletdetection/environment.yaml b/Biomni/mcp_generated/mcp_doubletdetection/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7df192fe3bbb0d251940837524455b3b04fbe495 --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - doubletdetection + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_eva-sub-cli/Dockerfile b/Biomni/mcp_generated/mcp_eva-sub-cli/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d1fe5ffdae2ecfc2a58f737d38397b3a41e6ba61 --- /dev/null +++ b/Biomni/mcp_generated/mcp_eva-sub-cli/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install eva-sub-cli via conda (e.g., from bioconda) +RUN conda install -c bioconda eva-sub-cli -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/eva-sub-cli_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/eva-sub-cli_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/eva-sub-cli_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_eva-sub-cli/app/eva-sub-cli_server.py b/Biomni/mcp_generated/mcp_eva-sub-cli/app/eva-sub-cli_server.py new file mode 100644 index 0000000000000000000000000000000000000000..2972b88e783e4fd04a61f5ecabfa3721f05b870f --- /dev/null +++ b/Biomni/mcp_generated/mcp_eva-sub-cli/app/eva-sub-cli_server.py @@ -0,0 +1,404 @@ +import subprocess +import logging +from pathlib import Path +from typing import List, Dict, Any, Optional + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Mock decorator for standalone execution. In a real MCP environment, this would be provided. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_eva_sub_cli' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def eva_validate( + vcf_files: List[Path], + metadata_json: Path, + metadata_dataset: str, + submission_dir: Path, + username: str, + password: str, +) -> Dict[str, Any]: + """ + Validates VCF files and associated metadata for submission to the European Variation Archive (EVA). + + This command checks the format and content of VCF files and the metadata JSON file to ensure + they meet EVA's requirements before submission. The results and intermediate files are + stored in the specified submission directory. + + Args: + vcf_files: A list of paths to VCF files to validate. + metadata_json: Path to the metadata JSON file. + metadata_dataset: The name of the dataset within the metadata JSON file to use for validation. + submission_dir: Directory to store submission files and validation reports. It will be created if it doesn't exist. + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details, including the command, stdout, stderr, + and the path to the submission directory. + """ + # --- Input Validation --- + if not vcf_files: + raise ValueError("At least one VCF file must be provided in 'vcf_files'.") + for vcf_file in vcf_files: + if not vcf_file.is_file(): + raise FileNotFoundError(f"VCF file not found: {vcf_file}") + + if not metadata_json.is_file(): + raise FileNotFoundError(f"Metadata JSON file not found: {metadata_json}") + + if not metadata_dataset: + raise ValueError("'metadata_dataset' cannot be empty.") + + # --- File Path Handling --- + try: + submission_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + raise IOError(f"Failed to create submission directory {submission_dir}: {e}") + + # --- Command Construction --- + vcf_files_str = ",".join(map(str, vcf_files)) + + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "validate", + "--vcf-files", vcf_files_str, + "--metadata-json", str(metadata_json), + "--metadata-dataset", metadata_dataset, + "--submission-dir", str(submission_dir), + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": { + "submission_directory": str(submission_dir) + } + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA validation failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +@mcp.tool() +def eva_submit( + submission_dir: Path, + username: str, + password: str, +) -> Dict[str, Any]: + """ + Submits previously validated data to the European Variation Archive (EVA). + + This command uses the files generated in the 'validate' step from the specified + submission directory to upload the data to EVA. + + Args: + submission_dir: Directory containing the submission files from a successful 'validate' run. + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details, including the command, stdout, and stderr. + """ + # --- Input Validation --- + if not submission_dir.is_dir(): + raise FileNotFoundError(f"Submission directory not found: {submission_dir}. Please run the 'validate' step first.") + + # --- Command Construction --- + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "submit", + "--submission-dir", str(submission_dir), + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {} + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA submission failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +@mcp.tool() +def eva_resume( + submission_dir: Path, + username: str, + password: str, +) -> Dict[str, Any]: + """ + Resumes an interrupted submission to the European Variation Archive (EVA). + + This command attempts to continue a submission that was previously started but did not complete, + using the state information stored in the submission directory. + + Args: + submission_dir: Directory containing the files of the submission to be resumed. + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details, including the command, stdout, and stderr. + """ + # --- Input Validation --- + if not submission_dir.is_dir(): + raise FileNotFoundError(f"Submission directory not found: {submission_dir}.") + + # --- Command Construction --- + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "resume", + "--submission-dir", str(submission_dir), + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {} + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA resume failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +@mcp.tool() +def eva_list_submissions( + username: str, + password: str, +) -> Dict[str, Any]: + """ + Lists all submissions associated with the provided user account. + + Args: + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details. The list of submissions is in the stdout. + """ + # --- Command Construction --- + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "list", + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {} + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA list submissions failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +@mcp.tool() +def eva_get_submission( + submission_id: str, + username: str, + password: str, +) -> Dict[str, Any]: + """ + Retrieves the details of a specific submission by its ID. + + Args: + submission_id: The ID of the submission to retrieve. + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details. Submission details are in the stdout. + """ + # --- Input Validation --- + if not submission_id: + raise ValueError("'submission_id' cannot be empty.") + + # --- Command Construction --- + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "get-submission", + submission_id, + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {} + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA get submission failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +@mcp.tool() +def eva_get_status( + submission_id: str, + username: str, + password: str, +) -> Dict[str, Any]: + """ + Gets the processing status of a specific submission by its ID. + + Args: + submission_id: The ID of the submission to check. + username: Username for the ENA Webin account. + password: Password for the ENA Webin account. + + Returns: + A dictionary containing the execution details. The submission status is in the stdout. + """ + # --- Input Validation --- + if not submission_id: + raise ValueError("'submission_id' cannot be empty.") + + # --- Command Construction --- + command = [ + "eva-sub-cli", + "--username", username, + "--password", password, + "get-status", + submission_id, + ] + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {} + } + except FileNotFoundError: + raise RuntimeError("`eva-sub-cli` command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"EVA get status failed with exit code {e.returncode}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Process failed with exit code {e.returncode}", + "output_files": {} + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_eva-sub-cli/docker-compose.yml b/Biomni/mcp_generated/mcp_eva-sub-cli/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..f88894b59ef732e252c84eaa4c60833865b3ca4b --- /dev/null +++ b/Biomni/mcp_generated/mcp_eva-sub-cli/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-eva-sub-cli: + build: . + image: mcp-eva-sub-cli:latest + container_name: mcp-eva-sub-cli + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=eva-sub-cli + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_eva-sub-cli/environment.yaml b/Biomni/mcp_generated/mcp_eva-sub-cli/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d6b63f7277a3c56d280f6e3cf5d0610985cfdde7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_eva-sub-cli/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - eva-sub-cli + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_eva-sub-cli/requirements.txt b/Biomni/mcp_generated/mcp_eva-sub-cli/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_eva-sub-cli/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_fastqc/Dockerfile b/Biomni/mcp_generated/mcp_fastqc/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4a46b609ae07f90c3549799577d88835ea8fa245 --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install fastqc via conda (e.g., from bioconda) +RUN conda install -c bioconda fastqc -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/fastqc_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/fastqc_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/fastqc_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_fastqc/app/fastqc_server.py b/Biomni/mcp_generated/mcp_fastqc/app/fastqc_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f36091d83309d23a007c5bd9df589c96c1fc2874 --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/app/fastqc_server.py @@ -0,0 +1,193 @@ +import subprocess +import logging +from pathlib import Path +from typing import List, Optional, Literal + +# Setup logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be: +# from mcp import tool as mcp_tool +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def fastqc( + seqfiles: List[Path], + outdir: Optional[Path] = None, + threads: int = 1, + contaminants: Optional[Path] = None, + adapters: Optional[Path] = None, + kmers: int = 7, + format: Optional[Literal["bam", "sam", "bam_mapped", "sam_mapped", "fastq"]] = None, + quiet: bool = False, + casava: bool = False, + nano: bool = False, + nofilter: bool = False, + extract: bool = False, + nogroup: bool = False, + min_length: Optional[int] = None, + fast5_dir: Optional[Path] = None, + no_summary: bool = False, +) -> dict: + """ + Runs FastQC, a quality control tool for high throughput sequence data. + + FastQC reads a set of sequence files and produces a quality control report + in HTML format, along with a zip archive containing the raw data. + + Args: + seqfiles: A list of sequence files to be processed (e.g., FASTQ, BAM, SAM). + outdir: Directory to store all output files. Must exist beforehand. + If not set, output is saved in the same directory as each input file. + threads: Number of files to process simultaneously. Defaults to 1. + contaminants: Path to a non-default file listing contaminants. + adapters: Path to a non-default file listing adapter sequences. + kmers: The length of k-mer to look for in the K-mer content module. Must be between 2 and 10. Defaults to 7. + format: Bypasses format detection. Forces FastQC to use the specified format. + quiet: Suppress all progress messages on stdout and only report errors. + casava: Indicates that files come from raw Casava output. + nano: Indicates that files are Nanopore fast5 files. + nofilter: Disables read filtering for reads failing QC. + extract: Uncompresses the output zip file after creation. + nogroup: Disables base grouping for reads >50bp. WARNING: Can be memory-intensive. + min_length: Sets an artificial lower limit on the sequence length for reporting. + fast5_dir: For Nanopore data, specifies the directory containing raw fast5 files. + no_summary: Prevents the generation of summary.txt and images.zip files. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not seqfiles: + raise ValueError("At least one sequence file must be provided.") + for seqfile in seqfiles: + if not seqfile.is_file(): + raise FileNotFoundError(f"Input sequence file not found: {seqfile}") + + if outdir: + if not outdir.is_dir(): + raise NotADirectoryError(f"Output directory does not exist: {outdir}") + + if not 1 <= threads: + raise ValueError("Number of threads must be at least 1.") + + if contaminants and not contaminants.is_file(): + raise FileNotFoundError(f"Contaminants file not found: {contaminants}") + + if adapters and not adapters.is_file(): + raise FileNotFoundError(f"Adapters file not found: {adapters}") + + if not 2 <= kmers <= 10: + raise ValueError("K-mer size must be between 2 and 10.") + + if min_length is not None and min_length < 1: + raise ValueError("min_length must be a positive integer.") + + if fast5_dir and not fast5_dir.is_dir(): + raise NotADirectoryError(f"Fast5 directory not found: {fast5_dir}") + + # --- Command Construction --- + cmd = ["fastqc"] + + if outdir: + cmd.extend(["--outdir", str(outdir)]) + + cmd.extend(["--threads", str(threads)]) + + if contaminants: + cmd.extend(["--contaminants", str(contaminants)]) + + if adapters: + cmd.extend(["--adapters", str(adapters)]) + + cmd.extend(["--kmers", str(kmers)]) + + if format: + cmd.extend(["--format", format]) + + if quiet: + cmd.append("--quiet") + if casava: + cmd.append("--casava") + if nano: + cmd.append("--nano") + if nofilter: + cmd.append("--nofilter") + if extract: + cmd.append("--extract") + if nogroup: + cmd.append("--nogroup") + if no_summary: + cmd.append("--no-summary") + + if min_length is not None: + cmd.extend(["--min_length", str(min_length)]) + + if fast5_dir: + cmd.extend(["--fast5-dir", str(fast5_dir)]) + + cmd.extend([str(sf) for sf in seqfiles]) + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + except FileNotFoundError: + raise RuntimeError("fastqc not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + logger.error(f"FastQC failed with exit code {e.returncode}") + logger.error(f"Stdout: {e.stdout}") + logger.error(f"Stderr: {e.stderr}") + raise e + + # --- Output File Discovery --- + output_files = [] + for seqfile in seqfiles: + # FastQC uses the file stem for output names. + # e.g., 'my_reads.fastq.gz' -> stem 'my_reads.fastq' -> output 'my_reads.fastq_fastqc.html' + # e.g., 'my_reads.fastq' -> stem 'my_reads' -> output 'my_reads_fastqc.html' + stem = seqfile.stem + + # Output is placed in outdir if specified, otherwise next to the input file. + current_output_dir = outdir if outdir else seqfile.parent + + html_output = current_output_dir / f"{stem}_fastqc.html" + zip_output = current_output_dir / f"{stem}_fastqc.zip" + + if html_output.exists(): + output_files.append(str(html_output)) + if zip_output.exists(): + output_files.append(str(zip_output)) + + if extract: + extracted_dir = current_output_dir / f"{stem}_fastqc" + if extracted_dir.is_dir(): + output_files.append(str(extracted_dir)) + + # Discover summary files, which are generated in the main output directory + if not no_summary: + summary_dir = outdir if outdir else seqfiles[0].parent + summary_txt = summary_dir / "summary.txt" + images_zip = summary_dir / "images.zip" + if summary_txt.exists(): + output_files.append(str(summary_txt)) + if images_zip.exists(): + output_files.append(str(images_zip)) + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": sorted(list(set(output_files))), # Ensure unique paths + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_fastqc/app/fastqc_shim_server.py b/Biomni/mcp_generated/mcp_fastqc/app/fastqc_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cb82c80a62eb36eefe63c61a0955ee7bab5ab4f8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/app/fastqc_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fastqc/app/fastqc_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_fastqc' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_fastqc/app/requirements.txt b/Biomni/mcp_generated/mcp_fastqc/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_fastqc/docker-compose.yml b/Biomni/mcp_generated/mcp_fastqc/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b485c7107b50c9c6a9edf5ee3c6a0f48ba7f7d0f --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-fastqc: + build: . + image: mcp-fastqc:latest + container_name: mcp-fastqc + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=fastqc + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_fastqc/environment.yaml b/Biomni/mcp_generated/mcp_fastqc/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e27c18d74da1786962c7e7a84138540b00eb640a --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - fastqc + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_fastqc/requirements.txt b/Biomni/mcp_generated/mcp_fastqc/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_fastqc/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gseapy/Dockerfile b/Biomni/mcp_generated/mcp_gseapy/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4c98d38ba9dfc4f4d382f2396604b285363d4027 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install gseapy via conda (e.g., from bioconda) +RUN conda install -c bioconda gseapy -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/gseapy_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/gseapy_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/gseapy_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gseapy/app/gseapy_server.py b/Biomni/mcp_generated/mcp_gseapy/app/gseapy_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3a2bddda784e09c1091ca565f702387dc3c7c830 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/app/gseapy_server.py @@ -0,0 +1,372 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_gseapy' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def gseapy_gsea( + data: str, + cls: str, + gmt: str, + outdir: str = "GSEA_Result", + min_size: int = 15, + max_size: int = 500, + permutation_num: int = 1000, + weighted_score_type: float = 1.0, + method: str = "signal_to_noise", + ascending: bool = False, + threads: int = 1, + format: str = "pdf", + graph_num: int = 20, + no_plot: bool = False, + seed: int = 123, + verbose: bool = False +): + """ + Run Gene Set Enrichment Analysis (GSEA). + + Args: + data: Path to expression data file (txt, csv, gct). + cls: Path to CLS file (class vector). + gmt: Path to gene set file (gmt). + outdir: Output directory. + min_size: Minimum allowed number of genes from gene set also the data set. + max_size: Maximum allowed number of genes from gene set also the data set. + permutation_num: Number of permutations. + weighted_score_type: Weighted score type. + method: Methods to calculate correlations. + ascending: Sorting order of enrichment scores. + threads: Number of threads. + format: Graphics format (pdf, png, svg). + graph_num: Number of top gene sets to plot. + no_plot: If True, do not create plots. + seed: Random seed. + verbose: Increase output verbosity. + """ + # Input validation + data_path = Path(data) + cls_path = Path(cls) + gmt_path = Path(gmt) + + if not data_path.exists(): + return {"error": f"Data file not found: {data}"} + if not cls_path.exists(): + return {"error": f"CLS file not found: {cls}"} + if not gmt_path.exists(): + return {"error": f"GMT file not found: {gmt}"} + + cmd = [ + "gseapy", "gsea", + "-d", str(data_path), + "-c", str(cls_path), + "-g", str(gmt_path), + "-o", outdir, + "--min-size", str(min_size), + "--max-size", str(max_size), + "-n", str(permutation_num), + "-w", str(weighted_score_type), + "-m", method, + "-t", str(threads), + "-f", format, + "--graph", str(graph_num), + "--seed", str(seed) + ] + + if ascending: + cmd.append("-a") + if no_plot: + cmd.append("--no-plot") + if verbose: + cmd.append("-v") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(p) for p in Path(outdir).rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "GSEA execution failed" + } + +@mcp.tool() +def gseapy_prerank( + rnk: str, + gmt: str, + outdir: str = "Prerank_Result", + min_size: int = 15, + max_size: int = 500, + permutation_num: int = 1000, + weighted_score_type: float = 1.0, + ascending: bool = False, + threads: int = 1, + format: str = "pdf", + graph_num: int = 20, + no_plot: bool = False, + seed: int = 123, + verbose: bool = False +): + """ + Run Pre-ranked Gene Set Enrichment Analysis. + + Args: + rnk: Path to ranked list file (rnk). + gmt: Path to gene set file (gmt). + outdir: Output directory. + min_size: Minimum allowed number of genes from gene set also the data set. + max_size: Maximum allowed number of genes from gene set also the data set. + permutation_num: Number of permutations. + weighted_score_type: Weighted score type. + ascending: Sorting order of enrichment scores. + threads: Number of threads. + format: Graphics format. + graph_num: Number of top gene sets to plot. + no_plot: If True, do not create plots. + seed: Random seed. + verbose: Increase output verbosity. + """ + rnk_path = Path(rnk) + gmt_path = Path(gmt) + + if not rnk_path.exists(): + return {"error": f"Ranked list file not found: {rnk}"} + if not gmt_path.exists(): + return {"error": f"GMT file not found: {gmt}"} + + cmd = [ + "gseapy", "prerank", + "-r", str(rnk_path), + "-g", str(gmt_path), + "-o", outdir, + "--min-size", str(min_size), + "--max-size", str(max_size), + "-n", str(permutation_num), + "-w", str(weighted_score_type), + "-t", str(threads), + "-f", format, + "--graph", str(graph_num), + "--seed", str(seed) + ] + + if ascending: + cmd.append("-a") + if no_plot: + cmd.append("--no-plot") + if verbose: + cmd.append("-v") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(p) for p in Path(outdir).rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Prerank execution failed" + } + +@mcp.tool() +def gseapy_ssgsea( + data: str, + gmt: str, + outdir: str = "ssGSEA_Result", + min_size: int = 15, + max_size: int = 500, + sample_norm_method: str = "rank", + weight: float = 0.25, + threads: int = 1, + format: str = "pdf", + graph_num: int = 20, + no_plot: bool = False, + seed: int = 123, + verbose: bool = False +): + """ + Run Single-sample Gene Set Enrichment Analysis (ssGSEA). + + Args: + data: Path to expression data file. + gmt: Path to gene set file. + outdir: Output directory. + min_size: Minimum allowed number of genes from gene set also the data set. + max_size: Maximum allowed number of genes from gene set also the data set. + sample_norm_method: Sample normalization method (rank, log, log_rank). + weight: Weighted score type. + threads: Number of threads. + format: Graphics format. + graph_num: Number of top gene sets to plot. + no_plot: If True, do not create plots. + seed: Random seed. + verbose: Increase output verbosity. + """ + data_path = Path(data) + gmt_path = Path(gmt) + + if not data_path.exists(): + return {"error": f"Data file not found: {data}"} + if not gmt_path.exists(): + return {"error": f"GMT file not found: {gmt}"} + + cmd = [ + "gseapy", "ssgsea", + "-d", str(data_path), + "-g", str(gmt_path), + "-o", outdir, + "--min-size", str(min_size), + "--max-size", str(max_size), + "-m", sample_norm_method, + "-w", str(weight), + "-t", str(threads), + "-f", format, + "--graph", str(graph_num), + "--seed", str(seed) + ] + + if no_plot: + cmd.append("--no-plot") + if verbose: + cmd.append("-v") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(p) for p in Path(outdir).rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ssGSEA execution failed" + } + +@mcp.tool() +def gseapy_enrichr( + gene_list: str, + gene_sets: str, + outdir: str = "Enrichr_Result", + organism: str = "human", + format: str = "pdf", + no_plot: bool = False, + verbose: bool = False +): + """ + Run Gene Set Enrichment using Enrichr API. + + Args: + gene_list: Path to gene list file or a comma-separated string of genes. + gene_sets: Enrichr Library name(s) (comma-separated). + outdir: Output directory. + organism: Organism (human, mouse, yeast, fly, fish, worm). + format: Graphics format. + no_plot: If True, do not create plots. + verbose: Increase output verbosity. + """ + # Check if gene_list is a file or a string + gene_list_path = Path(gene_list) + input_arg = str(gene_list) + if gene_list_path.exists(): + input_arg = str(gene_list_path) + + cmd = [ + "gseapy", "enrichr", + "-i", input_arg, + "-g", gene_sets, + "-o", outdir, + "--organism", organism, + "-f", format + ] + + if no_plot: + cmd.append("--no-plot") + if verbose: + cmd.append("-v") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(p) for p in Path(outdir).rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Enrichr execution failed" + } + +@mcp.tool() +def gseapy_replot( + indir: str, + outdir: str = "Replot_Result", + format: str = "pdf", + verbose: bool = False +): + """ + Replot GSEA results from a previous run. + + Args: + indir: Input directory containing GSEA results. + outdir: Output directory for new plots. + format: Graphics format. + verbose: Increase output verbosity. + """ + indir_path = Path(indir) + if not indir_path.exists() or not indir_path.is_dir(): + return {"error": f"Input directory not found or invalid: {indir}"} + + cmd = [ + "gseapy", "replot", + "-i", str(indir_path), + "-o", outdir, + "-f", format + ] + + if verbose: + cmd.append("-v") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(p) for p in Path(outdir).rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Replot execution failed" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_gseapy/app/gseapy_shim_server.py b/Biomni/mcp_generated/mcp_gseapy/app/gseapy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..18d5fb4d3b271cdb16d59111839e1d6bc6ec3d67 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/app/gseapy_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gseapy/app/gseapy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_gseapy' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_gseapy/app/requirements.txt b/Biomni/mcp_generated/mcp_gseapy/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_gseapy/docker-compose.yml b/Biomni/mcp_generated/mcp_gseapy/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..4435ac46fd3fde41c2455bea0287c6b2f97436e4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-gseapy: + build: . + image: mcp-gseapy:latest + container_name: mcp-gseapy + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=gseapy + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gseapy/environment.yaml b/Biomni/mcp_generated/mcp_gseapy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eabe3d57d295d6cdfda2c43ea4169f58753fffd5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - gseapy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gseapy/requirements.txt b/Biomni/mcp_generated/mcp_gseapy/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gseapy/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_medaka/Dockerfile b/Biomni/mcp_generated/mcp_medaka/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..5ae0731bf52a3b96391cd25de5fcb35fa0c875a0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install medaka via conda (e.g., from bioconda) +RUN conda install -c bioconda medaka -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/medaka_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/medaka_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/medaka_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_medaka/app/medaka_server.py b/Biomni/mcp_generated/mcp_medaka/app/medaka_server.py new file mode 100644 index 0000000000000000000000000000000000000000..79199228261ca939b539f871d84ac620245d2ef6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/app/medaka_server.py @@ -0,0 +1,380 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +# No need to import mcp, as per instructions. + +def _run_command(cmd: List[str], cwd: Optional[Path] = None): + """Helper function to run a shell command and handle errors.""" + cmd_str = " ".join(map(str, cmd)) + try: + result = subprocess.run( + cmd, + cwd=cwd, + check=True, + capture_output=True, + text=True + ) + return { + "command_executed": cmd_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": cmd_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": cmd_str, + "stdout": "", + "stderr": f"Error: medaka or one of its dependencies not found. " + f"Please ensure 'medaka' is installed and in your PATH.", + "error": "Executable not found", + "output_files": [] + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_medaka' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def medaka_consensus( + input_basecalls: Path, + draft_assembly: Path, + output_directory: Path, + threads: int = 1, + batch_size: Optional[int] = None, + use_bacterial_model: bool = False, +) -> dict: + """ + Creates consensus sequences and variant calls from nanopore sequencing data using neural networks. + + This program uses both samtools and minimap2. If medaka has been installed using + the from-source method these will be present within the medaka environment, + otherwise they will need to be provided by the user. + + Args: + input_basecalls: Path to input basecalls (reads) in .fasta or .fastq format. + draft_assembly: Path to input draft assembly in .fasta format. + output_directory: Path to the output directory where consensus.fasta will be saved. + threads: Number of CPU threads to use. Must be a positive integer. + batch_size: Inference batch size. A value of 100 is suitable for 11Gb GPUs. + Must be a positive integer if provided. + use_bacterial_model: Flag to select the bacterial model if compatible. + A legacy default model will be used if not compatible. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_basecalls.is_file(): + raise FileNotFoundError(f"Input basecalls file not found: {input_basecalls}") + if not draft_assembly.is_file(): + raise FileNotFoundError(f"Draft assembly file not found: {draft_assembly}") + + output_directory.mkdir(parents=True, exist_ok=True) + + if threads <= 0: + raise ValueError("Number of threads must be a positive integer.") + + cmd = [ + "medaka_consensus", + "-i", str(input_basecalls), + "-d", str(draft_assembly), + "-o", str(output_directory), + "-t", str(threads), + ] + + if batch_size is not None: + if batch_size <= 0: + raise ValueError("Batch size must be a positive integer.") + cmd.extend(["-b", str(batch_size)]) + + if use_bacterial_model: + cmd.append("--bacteria") + + result = _run_command(cmd) + if "error" not in result: + # The documentation explicitly states: "When medaka_consensus has finished running, + # the consensus will be saved to ${OUTDIR}/consensus.fasta." + result["output_files"].append(str(output_directory / "consensus.fasta")) + return result + + +@mcp.tool() +def medaka_variant( + input_reads: Path, + reference_fasta: Path, + output_directory: Path, + threads: int = 1, +) -> dict: + """ + Performs haploid variant calling from nanopore sequencing data. + + Requires reads as a .fasta or .fastq and a reference sequence as a .fasta file. + This function infers the use of an output directory and threads based on + the similar `medaka_consensus` helper script, as the documentation for + `medaka_variant` is less explicit on these parameters. + + Args: + input_reads: Path to input reads in .fasta or .fastq format. Must exist. + reference_fasta: Path to the reference sequence in .fasta format. Must exist. + output_directory: Path to the output directory for variant calling results. + Expected to contain files like variants.vcf.gz. + threads: Number of CPU threads to use. Must be a positive integer. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_reads.is_file(): + raise FileNotFoundError(f"Input reads file not found: {input_reads}") + if not reference_fasta.is_file(): + raise FileNotFoundError(f"Reference FASTA file not found: {reference_fasta}") + + output_directory.mkdir(parents=True, exist_ok=True) + + if threads <= 0: + raise ValueError("Number of threads must be a positive integer.") + + cmd = [ + "medaka_variant", + "-i", str(input_reads), + "-r", str(reference_fasta), + "-o", str(output_directory), # Inferred from medaka_consensus pattern + "-t", str(threads), # Inferred from medaka_consensus pattern + ] + + result = _run_command(cmd) + if "error" not in result: + # Common output for variant calling is variants.vcf.gz, but not explicitly stated. + # Assuming this pattern for a "workflow" tool. + result["output_files"].append(str(output_directory / "variants.vcf.gz")) + return result + + +@mcp.tool() +def medaka_inference( + input_bam: Path, + output_hdf: Path, + model: Optional[str] = None, + regions: Optional[List[str]] = None, + threads: int = 1, # Recommended not to exceed 2, but allow user to specify. + batch_size: Optional[int] = None, +) -> dict: + """ + Runs the medaka inference algorithm across assembly regions. + + This is a component step of medaka_consensus, useful for parallelization. + It is not recommended to specify a value of --threads greater than 2 for medaka inference + since the compute scaling efficiency is poor beyond this. + + Args: + input_bam: Path to the input BAM file (e.g., from mini_align). Must exist. + output_hdf: Path to the output HDF file. Its parent directory will be created if it doesn't exist. + model: Specify the basecaller model (e.g., 'dna_r10.4.1_e8.2_400bps_hac@v4.1.0:variant'). + If not provided, medaka will attempt to auto-determine. + regions: List of assembly sequence names to restrict action to. + threads: Number of threads to use. Must be a positive integer. + batch_size: Inference batch size. Must be a positive integer if provided. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_bam.is_file(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + + output_hdf.parent.mkdir(parents=True, exist_ok=True) + + if threads <= 0: + raise ValueError("Number of threads must be a positive integer.") + if threads > 2: + # This is a warning/recommendation, not a hard error. + print(f"Warning: medaka inference performance scales poorly beyond 2 threads. " + f"You specified {threads} threads.") + + cmd = [ + "medaka", "inference", + str(input_bam), + str(output_hdf), + "--threads", str(threads), + ] + + if model: + cmd.extend(["--model", model]) + + if regions: + cmd.append("--region") + cmd.extend(regions) + + if batch_size is not None: + if batch_size <= 0: + raise ValueError("Batch size must be a positive integer.") + cmd.extend(["-b", str(batch_size)]) + + result = _run_command(cmd) + if "error" not in result: + result["output_files"].append(str(output_hdf)) + return result + + +@mcp.tool() +def medaka_sequence( + input_h5_files: List[Path], + assembly_fasta: Path, + polished_assembly_fasta: Path, +) -> dict: + """ + Aggregates results from medaka inference to create consensus sequences. + + This is the final step in the parallel medaka_consensus workflow. + + Args: + input_h5_files: List of one or more HDF files output by medaka inference. + Each path must point to an existing file. + assembly_fasta: Path to the input assembly FASTA file. Must exist. + polished_assembly_fasta: Path to the output polished assembly FASTA file. + Its parent directory will be created if it doesn't exist. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_h5_files: + raise ValueError("At least one input HDF file must be provided.") + for h5_file in input_h5_files: + if not h5_file.is_file(): + raise FileNotFoundError(f"Input HDF file not found: {h5_file}") + + if not assembly_fasta.is_file(): + raise FileNotFoundError(f"Assembly FASTA file not found: {assembly_fasta}") + + polished_assembly_fasta.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "medaka", "sequence", + ] + cmd.extend(map(str, input_h5_files)) + cmd.extend([ + str(assembly_fasta), + str(polished_assembly_fasta), + ]) + + result = _run_command(cmd) + if "error" not in result: + result["output_files"].append(str(polished_assembly_fasta)) + return result + + +@mcp.tool() +def medaka_tools_list_models() -> dict: + """ + Lists all allowed inference models available in medaka. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + cmd = ["medaka", "tools", "list_models"] + result = _run_command(cmd) + return result + + +@mcp.tool() +def medaka_tools_resolve_model( + auto_model_type: str, + input_file: Path, +) -> dict: + """ + Prints the model that automatic model selection will use for a given input file. + + Args: + auto_model_type: Type of auto-model to resolve. Must be one of "consensus", + "variant", or "consensus_bacteria". + input_file: Path to the input BAM or FASTQ file. Must exist. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if auto_model_type not in ["consensus", "variant", "consensus_bacteria"]: + raise ValueError( + f"Invalid auto_model_type: '{auto_model_type}'. " + "Must be 'consensus', 'variant', or 'consensus_bacteria'." + ) + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + cmd = [ + "medaka", "tools", "resolve_model", + "--auto_model", auto_model_type, + str(input_file), + ] + + result = _run_command(cmd) + return result + + +@mcp.tool() +def mini_align( + input_basecalls: Path, + reference_fasta: Path, + output_bam: Path, + threads: int = 1, + flag_P: bool = False, + flag_m: bool = False, +) -> dict: + """ + Aligns reads to an input assembly using minimap2 (via mini_align). + + This is a component step of medaka_consensus, useful for parallelization. + + Args: + input_basecalls: Path to input basecalls (reads) in .fasta or .fastq format. Must exist. + reference_fasta: Path to input assembly in .fasta format. Must exist. + output_bam: Path to the output BAM file. Its parent directory will be created if it doesn't exist. + threads: Number of threads to use. Must be a positive integer. + flag_P: Optional flag, meaning not specified in docs but present in example. + flag_m: Optional flag, meaning not specified in docs but present in example. + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # Input validation + if not input_basecalls.is_file(): + raise FileNotFoundError(f"Input basecalls file not found: {input_basecalls}") + if not reference_fasta.is_file(): + raise FileNotFoundError(f"Reference FASTA file not found: {reference_fasta}") + + output_bam.parent.mkdir(parents=True, exist_ok=True) + + if threads <= 0: + raise ValueError("Number of threads must be a positive integer.") + + cmd = [ + "mini_align", + "-i", str(input_basecalls), + "-r", str(reference_fasta), + "-p", str(output_bam), + "-t", str(threads), + ] + + if flag_P: + cmd.append("-P") + if flag_m: + cmd.append("-m") + + result = _run_command(cmd) + if "error" not in result: + result["output_files"].append(str(output_bam)) + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_medaka/app/medaka_shim_server.py b/Biomni/mcp_generated/mcp_medaka/app/medaka_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8d82fa4570ab656bd5a5af6d8abeacf2df82697c --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/app/medaka_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_medaka/app/medaka_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_medaka' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_medaka/app/requirements.txt b/Biomni/mcp_generated/mcp_medaka/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_medaka/docker-compose.yml b/Biomni/mcp_generated/mcp_medaka/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..075875f5a0824da689e2e6f12efaca712b5797c0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-medaka: + build: . + image: mcp-medaka:latest + container_name: mcp-medaka + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=medaka + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_medaka/environment.yaml b/Biomni/mcp_generated/mcp_medaka/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b574a717527c3bb00979054d53105ba89f166ae --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - medaka + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_medaka/requirements.txt b/Biomni/mcp_generated/mcp_medaka/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_medaka/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_mmtf-python/Dockerfile b/Biomni/mcp_generated/mcp_mmtf-python/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9df5a1c75dce2b7201e3661272618f79297a5ab8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install mmtf-python via conda (e.g., from bioconda) +RUN conda install -c bioconda mmtf-python -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/mmtf-python_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/mmtf-python_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/mmtf-python_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_server.py b/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_server.py new file mode 100644 index 0000000000000000000000000000000000000000..87e1d6181645456f10083bc51d0c96b9ea2564e7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_server.py @@ -0,0 +1,114 @@ +import subprocess +from pathlib import Path +from typing import List + +# MCP decorator placeholder +# In a real MCP environment, this would be: +# from mcp import mcp +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_mmtf_python' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def mcp_tool_decorator(): + def decorator(func): + return func + return decorator + +@mcp_tool_decorator() +def fetch_mmtf( + pdb_id: str, + output_file: Path, +): + """ + Fetches a PDB entry in MMTF format from the RCSB PDB and saves it to a file. + + This tool acts as a command-line wrapper for the 'mmtf-python' library, + which does not have its own executable. It uses the library's `fetch` and + `write_mmtf` functions to download and save the specified structure. + + Args: + pdb_id (str): The 4-character PDB ID to fetch (e.g., '1AQ1'). + output_file (Path): The path to save the output MMTF file. + """ + # --- Input Validation --- + if not pdb_id or len(pdb_id) != 4 or not pdb_id.isalnum(): + raise ValueError("pdb_id must be a 4-character alphanumeric string.") + + if not output_file.parent.exists(): + try: + output_file.parent.mkdir(parents=True, exist_ok=True) + except OSError as e: + raise OSError(f"Failed to create output directory {output_file.parent}: {e}") + + # --- Command-line script construction --- + # This Python one-liner imports the necessary functions from the mmtf library, + # fetches the data for the given PDB ID, and writes it to the specified output file. + py_command = ( + f"from mmtf import fetch, write_mmtf; " + f"mmtf_data = fetch('{pdb_id}'); " + f"write_mmtf('{str(output_file)}', mmtf_data)" + ) + + cmd = ["python", "-c", py_command] + command_executed = " ".join(cmd) + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + + if not output_file.exists() or output_file.stat().st_size == 0: + # This case handles situations where the command succeeds (exit code 0) + # but the output file is not created or is empty, which can happen + # if the PDB ID is valid but has no MMTF data available. + error_message = ( + f"Command executed successfully, but the output file was not created or is empty. " + f"This may indicate that MMTF data is not available for PDB ID '{pdb_id}'.\n" + f"Stderr: {process.stderr}" + ) + return { + "error": error_message, + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "return_code": 0, + } + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_file)] + } + except FileNotFoundError: + # This error occurs if the 'python' executable is not found in the system's PATH. + return { + "error": "Python executable not found. Ensure Python is installed and in the system's PATH.", + "command_executed": command_executed, + } + except subprocess.CalledProcessError as e: + # This catches non-zero exit codes from the subprocess, which typically indicate + # an error within the mmtf-python library (e.g., PDB ID not found, network issues). + return { + "error": f"Execution failed with return code {e.returncode}.", + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + } + except Exception as e: + # Catch any other unexpected errors during execution. + return { + "error": f"An unexpected error occurred: {str(e)}", + "command_executed": command_executed, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_shim_server.py b/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8940c0ab0a7433a2d0bf16334dbcef47bed44f66 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/app/mmtf-python_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mmtf-python/app/mmtf-python_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_mmtf_python' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_mmtf-python/app/requirements.txt b/Biomni/mcp_generated/mcp_mmtf-python/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_mmtf-python/docker-compose.yml b/Biomni/mcp_generated/mcp_mmtf-python/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..828e949edea85fb49e8023afe6dc87600de6894f --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-mmtf-python: + build: . + image: mcp-mmtf-python:latest + container_name: mcp-mmtf-python + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=mmtf-python + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_mmtf-python/requirements.txt b/Biomni/mcp_generated/mcp_mmtf-python/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mmtf-python/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_mothur/app/mothur_server.py b/Biomni/mcp_generated/mcp_mothur/app/mothur_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d25c59e00d8e622d6ad3e9cfe8fe10230de68cba --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/app/mothur_server.py @@ -0,0 +1,311 @@ +import subprocess +import tempfile +import logging +from pathlib import Path +from typing import List, Optional, Dict, Any + +# Configure logging +logging.basicConfig(level=logging.INFO) + +def _build_mothur_command_string(command_name: str, params: Dict[str, Any]) -> str: + """Builds a mothur-compatible command string from a dictionary of parameters.""" + param_list = [] + for key, value in params.items(): + if value is None: + continue + if isinstance(value, bool): + param_list.append(f"{key}={str(value).lower()}") + elif isinstance(value, Path): + # Use absolute paths to ensure mothur can find the files + param_list.append(f"{key}={str(value.resolve())}") + else: + param_list.append(f"{key}={value}") + return f"{command_name}({','.join(param_list)})" + +def _run_mothur(command_str: str) -> Dict[str, Any]: + """ + Executes a mothur command string in an isolated temporary directory. + + Args: + command_str: The mothur command to execute (e.g., "unique.seqs(fasta=...)"). + + Returns: + A dictionary containing execution details and output file paths. + """ + with tempfile.TemporaryDirectory() as temp_dir_str: + temp_dir = Path(temp_dir_str) + + # Prepend set.dir to control output location reliably + full_command_str = f"set.dir(output={temp_dir.resolve()});{command_str}" + + # The '#command' syntax is mothur's way of running a batch command + cmd = ["mothur", f"#{full_command_str}"] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + cwd=temp_dir, # Run from temp_dir for safety + ) + + # Discover all files created in the output directory + output_files = [str(p.resolve()) for p in temp_dir.glob("*") if p.is_file()] + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + logging.error("mothur executable not found. Please ensure it is in your PATH.") + raise + except subprocess.CalledProcessError as e: + logging.error(f"Mothur command failed with exit code {e.returncode}") + logging.error(f"STDOUT: {e.stdout}") + logging.error(f"STDERR: {e.stderr}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Mothur command failed.", + "return_code": e.returncode, + "output_files": [] + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_mothur' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def make_contigs( + ffastq: Optional[Path] = None, + rfastq: Optional[Path] = None, + file: Optional[Path] = None, + fname: Optional[Path] = None, + oligos: Optional[Path] = None, + processors: int = 1, + bdiffs: int = 0, + pdiffs: int = 0, + checkorient: bool = True, + insert: int = 25, + trimoverlap: bool = False, + deltaq: int = 6, + maxee: float = 1.0, +) -> Dict[str, Any]: + """ + Assembles paired-end DNA sequences into contigs. + + This command aligns and merges paired-end reads from FASTQ files to create longer, + contiguous sequences for further analysis. + """ + # Input validation + if not ((ffastq and rfastq) or file or fname): + raise ValueError("You must provide one of the following: 'file', 'fname', or both 'ffastq' and 'rfastq'.") + if (ffastq and not rfastq) or (rfastq and not ffastq): + raise ValueError("'ffastq' and 'rfastq' must be provided together.") + + if ffastq and not ffastq.is_file(): raise FileNotFoundError(f"File not found: {ffastq}") + if rfastq and not rfastq.is_file(): raise FileNotFoundError(f"File not found: {rfastq}") + if file and not file.is_file(): raise FileNotFoundError(f"File not found: {file}") + if fname and not fname.is_file(): raise FileNotFoundError(f"File not found: {fname}") + if oligos and not oligos.is_file(): raise FileNotFoundError(f"File not found: {oligos}") + + params = { + "ffastq": ffastq, + "rfastq": rfastq, + "file": file, + "fname": fname, + "oligos": oligos, + "processors": processors, + "bdiffs": bdiffs, + "pdiffs": pdiffs, + "checkorient": checkorient, + "insert": insert, + "trimoverlap": trimoverlap, + "deltaq": deltaq, + "maxee": maxee, + } + + command_str = _build_mothur_command_string("make.contigs", params) + return _run_mothur(command_str) + +@mcp.tool() +def screen_seqs( + fasta: Path, + count: Optional[Path] = None, + group: Optional[Path] = None, + taxonomy: Optional[Path] = None, + qfile: Optional[Path] = None, + contigsreport: Optional[Path] = None, + summary: Optional[Path] = None, + start: Optional[int] = None, + end: Optional[int] = None, + maxambig: int = 0, + maxhomop: int = 8, + minlength: Optional[int] = None, + maxlength: Optional[int] = None, + processors: int = 1, +) -> Dict[str, Any]: + """ + Removes sequences that do not meet specified quality criteria. + + This command filters sequences based on length, number of ambiguous bases, + and homopolymer length to improve the quality of the dataset. + """ + # Input validation + if not fasta.is_file(): raise FileNotFoundError(f"FASTA file not found: {fasta}") + if count and not count.is_file(): raise FileNotFoundError(f"Count file not found: {count}") + if group and not group.is_file(): raise FileNotFoundError(f"Group file not found: {group}") + if taxonomy and not taxonomy.is_file(): raise FileNotFoundError(f"Taxonomy file not found: {taxonomy}") + if qfile and not qfile.is_file(): raise FileNotFoundError(f"Quality file not found: {qfile}") + if contigsreport and not contigsreport.is_file(): raise FileNotFoundError(f"Contigs report file not found: {contigsreport}") + if summary and not summary.is_file(): raise FileNotFoundError(f"Summary file not found: {summary}") + + params = { + "fasta": fasta, + "count": count, + "group": group, + "taxonomy": taxonomy, + "qfile": qfile, + "contigsreport": contigsreport, + "summary": summary, + "start": start, + "end": end, + "maxambig": maxambig, + "maxhomop": maxhomop, + "minlength": minlength, + "maxlength": maxlength, + "processors": processors, + } + + command_str = _build_mothur_command_string("screen.seqs", params) + return _run_mothur(command_str) + +@mcp.tool() +def unique_seqs( + fasta: Optional[Path] = None, + count: Optional[Path] = None, + format: str = "name", +) -> Dict[str, Any]: + """ + Finds the unique sequences in a dataset and generates a count/name file. + + This command dereplicates a FASTA file, keeping only the unique sequences and + creating a corresponding file that tracks the abundance of each unique sequence. + """ + # Input validation + if not fasta and not count: + raise ValueError("Either 'fasta' or 'count' file must be provided.") + if fasta and count: + raise ValueError("Provide either 'fasta' or 'count', not both.") + if fasta and not fasta.is_file(): + raise FileNotFoundError(f"FASTA file not found: {fasta}") + if count and not count.is_file(): + raise FileNotFoundError(f"Count file not found: {count}") + + valid_formats = ["name", "count"] + if format not in valid_formats: + raise ValueError(f"Invalid format '{format}'. Must be one of {valid_formats}.") + + params = { + "fasta": fasta, + "count": count, + "format": format, + } + + command_str = _build_mothur_command_string("unique.seqs", params) + return _run_mothur(command_str) + +@mcp.tool() +def dist_seqs( + fasta: Path, + cutoff: float = 0.25, + output: str = "lt", + processors: int = 1, + calc: str = "onegap", + countends: bool = True, +) -> Dict[str, Any]: + """ + Calculates pairwise distances between DNA sequences. + + This command generates a distance matrix from an aligned FASTA file, which is a + prerequisite for clustering sequences into OTUs. + """ + # Input validation + if not fasta.is_file(): + raise FileNotFoundError(f"FASTA file not found: {fasta}") + + valid_outputs = ["lt", "square", "column"] + if output not in valid_outputs: + raise ValueError(f"Invalid output format '{output}'. Must be one of {valid_outputs}.") + + valid_calcs = ["onegap", "nogap"] + if calc not in valid_calcs: + raise ValueError(f"Invalid calc method '{calc}'. Must be one of {valid_calcs}.") + + params = { + "fasta": fasta, + "cutoff": cutoff, + "output": output, + "processors": processors, + "calc": calc, + "countends": countends, + } + + command_str = _build_mothur_command_string("dist.seqs", params) + return _run_mothur(command_str) + +@mcp.tool() +def cluster( + phylip: Optional[Path] = None, + column: Optional[Path] = None, + name: Optional[Path] = None, + count: Optional[Path] = None, + method: str = "furthest", + cutoff: Optional[float] = None, + precision: int = 100, +) -> Dict[str, Any]: + """ + Assigns sequences to OTUs based on a distance matrix. + + This command uses one of several clustering algorithms (furthest neighbor, + nearest neighbor, or average neighbor) to group sequences into OTUs. + """ + # Input validation + if not phylip and not column: + raise ValueError("Either a 'phylip' or 'column' formatted distance file must be provided.") + if phylip and column: + raise ValueError("Provide either 'phylip' or 'column', not both.") + if phylip and not phylip.is_file(): + raise FileNotFoundError(f"Phylip file not found: {phylip}") + if column and not column.is_file(): + raise FileNotFoundError(f"Column file not found: {column}") + if name and not name.is_file(): + raise FileNotFoundError(f"Name file not found: {name}") + if count and not count.is_file(): + raise FileNotFoundError(f"Count file not found: {count}") + + valid_methods = ["furthest", "nearest", "average"] + if method not in valid_methods: + raise ValueError(f"Invalid method '{method}'. Must be one of {valid_methods}.") + + params = { + "phylip": phylip, + "column": column, + "name": name, + "count": count, + "method": method, + "cutoff": cutoff, + "precision": precision, + } + + command_str = _build_mothur_command_string("cluster", params) + return _run_mothur(command_str) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_mothur/app/mothur_shim_server.py b/Biomni/mcp_generated/mcp_mothur/app/mothur_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..74b80fdcd4cfc83b8dad9109e7a61c6ed1e91eac --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/app/mothur_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mothur/app/mothur_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_mothur' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_mothur/app/requirements.txt b/Biomni/mcp_generated/mcp_mothur/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_mothur/docker-compose.yml b/Biomni/mcp_generated/mcp_mothur/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1be489ab3a2f54856798a05e8f23026f7d0be060 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-mothur: + build: . + image: mcp-mothur:latest + container_name: mcp-mothur + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=mothur + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_mothur/environment.yaml b/Biomni/mcp_generated/mcp_mothur/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..39de0565c285d4fc82e19e4a7ba2fe1edc711c84 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - mothur + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_mothur/requirements.txt b/Biomni/mcp_generated/mcp_mothur/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/Dockerfile b/Biomni/mcp_generated/mcp_msisensor-pro/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..05a41889ef124ef55534f07a6aa90793d28320b1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install msisensor-pro via conda (e.g., from bioconda) +RUN conda install -c bioconda msisensor-pro -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/msisensor-pro_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/msisensor-pro_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/msisensor-pro_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_server.py b/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4ac085bf50c926165b3d29759a71e26db42bbc95 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_server.py @@ -0,0 +1,262 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List + +# Set up basic logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# This is a placeholder for the actual MCP decorator. +# The code will work in an MCP environment where this is defined. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_msisensor_pro' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def msisensor_pro_scan( + reference: Path, + output: Path, + min_length: int = 5, + min_mononucleotide_repeat: int = 5, + min_dinucleotide_repeat: int = 3, + min_trinucleotide_repeat: int = 3, + min_tetranucleotide_repeat: int = 3, + min_pentanucleotide_repeat: int = 3, + context_size: int = 2, + threads: int = 1, +): + """ + Scans a reference genome to identify microsatellites for MSI analysis. + + This function corresponds to the 'msisensor-pro scan' command. It identifies + homopolymers and microsatellites in a given FASTA reference file and writes + the locations to an output file. + + Args: + reference: Path to the reference genome file in FASTA format. + output: Path to the output file for the microsatellites list. + min_length: Minimum length of homopolymers (Default: 5). + min_mononucleotide_repeat: Minimum length of mononucleotide repeat (Default: 5). + min_dinucleotide_repeat: Minimum length of dinucleotide repeat (Default: 3). + min_trinucleotide_repeat: Minimum length of trinucleotide repeat (Default: 3). + min_tetranucleotide_repeat: Minimum length of tetranucleotide repeat (Default: 3). + min_pentanucleotide_repeat: Minimum length of pentanucleotide repeat (Default: 3). + context_size: Context size of microsatellite (Default: 2). + threads: Number of threads to use (Default: 1). + + Returns: + A dictionary containing the execution command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not reference.is_file(): + raise FileNotFoundError(f"Reference FASTA file not found at: {reference}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + if context_size < 0: + raise ValueError("Context size cannot be negative.") + + # Ensure output directory exists + output.parent.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "msisensor-pro", "scan", + "-d", str(reference), + "-o", str(output), + "-l", str(min_length), + "-m", str(min_mononucleotide_repeat), + "-i", str(min_dinucleotide_repeat), + "-r", str(min_trinucleotide_repeat), + "-q", str(min_tetranucleotide_repeat), + "-p", str(min_pentanucleotide_repeat), + "-c", str(context_size), + "-t", str(threads), + ] + + command_executed = " ".join(cmd) + logging.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + output_files = [str(output)] if output.exists() else [] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("msisensor-pro executable not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + logging.error(f"msisensor-pro scan failed with exit code {e.returncode}") + logging.error(f"Stderr: {e.stderr}") + logging.error(f"Stdout: {e.stdout}") + raise e + +@mcp.tool() +def msisensor_pro_msi( + msi_sites: Path, + tumor_bam: Path, + output_prefix: str, + normal_bam: Optional[Path] = None, + bed_file: Optional[Path] = None, + coverage: int = 20, + msi_sites_pass_coverage: float = 0.2, + p_value: float = 0.05, + fdr: float = 0.05, + min_somatic_sites: int = 20, + min_msi_score: float = 0.4, + min_homopolymer_len: int = 10, + threads: int = 1, + disable_thread_pool: bool = False, + output_all: bool = False, + output_somatic_sites: bool = False, + output_msi_sites: bool = False, + output_unstable_msi_sites: bool = False, + output_json: bool = False, +): + """ + Performs MSI analysis for tumor-normal paired or tumor-only samples. + + This function corresponds to the 'msisensor-pro msi' command. It calculates + an MSI score based on the instability of microsatellite sites. + + Args: + msi_sites: Path to the microsatellites list file (from 'scan' command). + tumor_bam: Path to the tumor BAM file. + output_prefix: Prefix for all output files. + normal_bam: Path to the normal BAM file (for paired analysis). + bed_file: Path to a BED file to restrict analysis to specific regions. + coverage: Coverage threshold for MSI sites (Default: 20). + msi_sites_pass_coverage: Min percentage of MSI sites passing coverage (Default: 0.2). + p_value: P-value for Fisher's exact test (Default: 0.05). + fdr: FDR for somatic sites detection (Default: 0.05). + min_somatic_sites: Minimum number of somatic sites to call MSI (Default: 20). + min_msi_score: Minimum MSI score to call MSI (Default: 0.4). + min_homopolymer_len: Min homopolymer length for MSI score calculation (Default: 10). + threads: Number of threads to use (Default: 1). + disable_thread_pool: Disable the thread pool. + output_all: Output all sites. + output_somatic_sites: Output somatic sites to '_somatic'. + output_msi_sites: Output MSI sites to '_msi'. + output_unstable_msi_sites: Output unstable MSI sites. + output_json: Output results in JSON format to '.json'. + + Returns: + A dictionary containing the execution command, stdout, stderr, and a list of generated output files. + """ + # --- Input Validation --- + if not msi_sites.is_file(): + raise FileNotFoundError(f"MSI sites file not found at: {msi_sites}") + if not tumor_bam.is_file(): + raise FileNotFoundError(f"Tumor BAM file not found at: {tumor_bam}") + if normal_bam and not normal_bam.is_file(): + raise FileNotFoundError(f"Normal BAM file not found at: {normal_bam}") + if bed_file and not bed_file.is_file(): + raise FileNotFoundError(f"BED file not found at: {bed_file}") + + if not (0 < msi_sites_pass_coverage <= 1.0): + raise ValueError("msi_sites_pass_coverage must be between 0 and 1.") + if not (0 < p_value <= 1.0): + raise ValueError("p_value must be between 0 and 1.") + if not (0 < fdr <= 1.0): + raise ValueError("fdr must be between 0 and 1.") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + if coverage < 0: + raise ValueError("Coverage cannot be negative.") + + # Ensure output directory exists + Path(output_prefix).parent.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "msisensor-pro", "msi", + "-d", str(msi_sites), + "-t", str(tumor_bam), + "-o", output_prefix, + "-c", str(coverage), + "-f", str(msi_sites_pass_coverage), + "-p", str(p_value), + "-s", str(fdr), + "-m", str(min_somatic_sites), + "-w", str(min_msi_score), + "-u", str(min_homopolymer_len), + "-b", str(threads), + ] + + if normal_bam: + cmd.extend(["-n", str(normal_bam)]) + if bed_file: + cmd.extend(["-e", str(bed_file)]) + if disable_thread_pool: + cmd.append("-x") + if output_all: + cmd.append("-g") + if output_somatic_sites: + cmd.append("-r") + if output_msi_sites: + cmd.append("-a") + if output_unstable_msi_sites: + cmd.append("-z") + if output_json: + cmd.append("-j") + + command_executed = " ".join(cmd) + logging.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + + # --- Collect Output Files --- + generated_files = [ + output_prefix, + f"{output_prefix}_dis", + ] + if normal_bam: + generated_files.append(f"{output_prefix}_germline") + if output_somatic_sites: + generated_files.append(f"{output_prefix}_somatic") + if output_msi_sites: + generated_files.append(f"{output_prefix}_msi") + if output_json: + generated_files.append(f"{output_prefix}.json") + + output_files = [f for f in generated_files if Path(f).exists()] + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("msisensor-pro executable not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + logging.error(f"msisensor-pro msi failed with exit code {e.returncode}") + logging.error(f"Stderr: {e.stderr}") + logging.error(f"Stdout: {e.stdout}") + raise e + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_shim_server.py b/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a03afcf40ff8559fa2d425a2a83f15d5f15b94ea --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/app/msisensor-pro_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_msisensor-pro/app/msisensor-pro_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_msisensor_pro' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/app/requirements.txt b/Biomni/mcp_generated/mcp_msisensor-pro/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/docker-compose.yml b/Biomni/mcp_generated/mcp_msisensor-pro/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..f25a3d6d4a24f0d403c6f805f6a3df17787f5a6c --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-msisensor-pro: + build: . + image: mcp-msisensor-pro:latest + container_name: mcp-msisensor-pro + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=msisensor-pro + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/environment.yaml b/Biomni/mcp_generated/mcp_msisensor-pro/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1393998b28b433a3c013cd1f882fea6dcaae93b8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - msisensor-pro + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_msisensor-pro/requirements.txt b/Biomni/mcp_generated/mcp_msisensor-pro/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msisensor-pro/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_pangolearn/Dockerfile b/Biomni/mcp_generated/mcp_pangolearn/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a0e00ce60229a14336eadb2e114f7ff75c59400c --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install pangolearn via conda (e.g., from bioconda) +RUN conda install -c bioconda pangolearn -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/pangolearn_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/pangolearn_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/pangolearn_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_server.py b/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_server.py new file mode 100644 index 0000000000000000000000000000000000000000..191f1b40bb5d2a5cc43ba5d7776e957f274aae75 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_server.py @@ -0,0 +1,48 @@ +import subprocess +from pathlib import Path +from typing import Dict, List, Optional + +# MCP-ready code for pangolearn + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def mcp_tool_decorator_placeholder(*args, **kwargs): + def decorator(func): + return func + return decorator + +mcp = type('mcp', (), {'tool': mcp_tool_decorator_placeholder}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_pangolearn' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def pangolearn_info() -> Dict[str, any]: + """ + Provides information about the pangolearn package. + + pangolearn is not an executable command-line tool but a data package containing + the trained decision tree model for pangolin versions 2.0 and 3.0. This repository + is now deprecated. For pangolin 4.0 and later, the 'pangolin-data' repository + should be used instead. This function does not execute any command. + """ + + info_message = ( + "pangolearn is a data package, not an executable tool. " + "It provides the trained model for pangolin v2.0 and v3.0. " + "This package is deprecated. For current pangolin versions (v4.0+), " + "please use the 'pangolin-data' package." + ) + + return { + "command_executed": "N/A (pangolearn is a data package)", + "stdout": info_message, + "stderr": "", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_shim_server.py b/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7937df0e7a6b50f4ea243671486c5b5f1e50883c --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/app/pangolearn_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pangolearn/app/pangolearn_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_pangolearn' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pangolearn/app/requirements.txt b/Biomni/mcp_generated/mcp_pangolearn/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_pangolearn/docker-compose.yml b/Biomni/mcp_generated/mcp_pangolearn/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..188ecfcade221a506946d0f90686ab71c813c997 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-pangolearn: + build: . + image: mcp-pangolearn:latest + container_name: mcp-pangolearn + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=pangolearn + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pangolearn/environment.yaml b/Biomni/mcp_generated/mcp_pangolearn/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aa8f579b6d9ef9f5f6d71888d9b2ff1e576c59a1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - pangolearn + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pangolearn/requirements.txt b/Biomni/mcp_generated/mcp_pangolearn/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pangolearn/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_pcdl/Dockerfile b/Biomni/mcp_generated/mcp_pcdl/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..92ec7358b2ef666b931e59d6016b848b7c393e68 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install pcdl via conda (e.g., from bioconda) +RUN conda install -c bioconda pcdl -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/pcdl_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/pcdl_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/pcdl_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pcdl/app/pcdl_server.py b/Biomni/mcp_generated/mcp_pcdl/app/pcdl_server.py new file mode 100644 index 0000000000000000000000000000000000000000..51649390122baf72c345e856c5c40045566c0391 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/app/pcdl_server.py @@ -0,0 +1,440 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_pcdl' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def pcdl_get_version() -> Dict[str, Any]: + """Retrieve the version of the pcdl (PhysiCell Data Loader) library.""" + try: + cmd = ["pcdl_get_version"] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + } + except subprocess.CalledProcessError as e: + return { + "error": f"Command failed with return code {e.returncode}", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def pcdl_get_anndata( + path: str, + output_path: str = ".", + microenv: bool = True, + graph: bool = True, + settingxml: str = "PhysiCell_settings.xml", + verbose: bool = True +) -> Dict[str, Any]: + """ + Transform PhysiCell output into AnnData objects (h5ad format). + + :param path: Path to the PhysiCell output directory or a specific XML file. + :param output_path: Directory where the AnnData file will be saved. + :param microenv: Whether to include microenvironment (substrate) data. + :param graph: Whether to include cell graph data (neighbor/attached). + :param settingxml: Name of the PhysiCell settings XML file. + :param verbose: Enable verbose text output during processing. + """ + input_path = Path(path) + if not input_path.exists(): + return {"error": f"Input path {path} does not exist."} + + cmd = ["pcdl_get_anndata", str(input_path), "--output_path", output_path] + if not microenv: cmd.append("--no-microenv") + if not graph: cmd.append("--no-graph") + cmd.extend(["--settingxml", settingxml]) + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": list(Path(output_path).glob("*.h5ad")) + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr, "stdout": e.stdout} + +@mcp.tool() +def pcdl_get_cell_df( + path: str, + output_path: str = ".", + drop: Optional[str] = None, + keep: Optional[str] = None, + verbose: bool = True +) -> Dict[str, Any]: + """ + Extract cell data as a CSV dataframe. + + :param path: Path to the PhysiCell output. + :param output_path: Directory to save the CSV. + :param drop: Comma-separated list of columns to drop. + :param keep: Comma-separated list of columns to keep. + :param verbose: Enable verbose output. + """ + input_path = Path(path) + if not input_path.exists(): + return {"error": "Input path does not exist."} + + cmd = ["pcdl_get_cell_df", str(input_path), "--output_path", output_path] + if drop: cmd.extend(["--drop", drop]) + if keep: cmd.extend(["--keep", keep]) + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_plot_scatter( + path: str, + focus_column: str = "cell_type", + output_path: str = ".", + z_slice: float = 0.0, + verbose: bool = True +) -> Dict[str, Any]: + """ + Generate scatter plots of cell data. + + :param path: Path to the PhysiCell output. + :param focus_column: The cell attribute to color the scatter plot by. + :param output_path: Directory to save the generated images. + :param z_slice: Z-coordinate slice to plot (for 3D data). + :param verbose: Enable verbose output. + """ + input_path = Path(path) + if not input_path.exists(): + return {"error": "Input path does not exist."} + + cmd = ["pcdl_plot_scatter", str(input_path), "--column", focus_column, "--output_path", output_path, "--z_slice", str(z_slice)] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": list(Path(output_path).glob("*.png")) + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_plot_contour( + path: str, + focus_substrate: str, + output_path: str = ".", + z_slice: float = 0.0, + verbose: bool = True +) -> Dict[str, Any]: + """ + Generate contour plots for a specific substrate. + + :param path: Path to the PhysiCell output. + :param focus_substrate: Name of the substrate to plot. + :param output_path: Directory to save the generated images. + :param z_slice: Z-coordinate slice to plot. + :param verbose: Enable verbose output. + """ + input_path = Path(path) + if not input_path.exists(): + return {"error": "Input path does not exist."} + + cmd = ["pcdl_plot_contour", str(input_path), "--substrate", focus_substrate, "--output_path", output_path, "--z_slice", str(z_slice)] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_make_gif( + path: str, + interface: str = "scatter", + output_path: str = ".", + verbose: bool = True +) -> Dict[str, Any]: + """ + Create an animated GIF from a time series of plots. + + :param path: Path to the PhysiCell time series directory. + :param interface: Type of plot to use ('scatter' or 'contour'). + :param output_path: Directory to save the GIF. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_make_gif", path, "--interface", interface, "--output_path", output_path] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_make_movie( + path: str, + interface: str = "scatter", + output_path: str = ".", + fps: int = 24, + verbose: bool = True +) -> Dict[str, Any]: + """ + Create a movie (MP4) from a time series of plots. + + :param path: Path to the PhysiCell time series directory. + :param interface: Type of plot to use ('scatter' or 'contour'). + :param output_path: Directory to save the movie. + :param fps: Frames per second for the movie. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_make_movie", path, "--interface", interface, "--output_path", output_path, "--fps", str(fps)] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_make_cell_vtk( + path: str, + output_path: str = ".", + ext: str = "vtp", + verbose: bool = True +) -> Dict[str, Any]: + """ + Save cell data as VTK glyph files. + + :param path: Path to PhysiCell output. + :param output_path: Directory to save VTK files. + :param ext: File extension (e.g., 'vtp'). + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_make_cell_vtk", path, "--output_path", output_path, "--ext", ext] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_make_conc_vtk( + path: str, + output_path: str = ".", + ext: str = "vti", + verbose: bool = True +) -> Dict[str, Any]: + """ + Save substrate concentration data as rectilinear grid VTK files. + + :param path: Path to PhysiCell output. + :param output_path: Directory to save VTK files. + :param ext: File extension (e.g., 'vti'). + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_make_conc_vtk", path, "--output_path", output_path, "--ext", ext] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_graph_gml( + path: str, + output_path: str = ".", + verbose: bool = True +) -> Dict[str, Any]: + """ + Save cell graphs in GML format (compatible with NetworkX and igraph). + + :param path: Path to PhysiCell output. + :param output_path: Directory to save GML files. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_get_graph_gml", path, "--output_path", output_path] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_celltype_list(path: str) -> Dict[str, Any]: + """Retrieve a list of cell types present in the PhysiCell output.""" + cmd = ["pcdl_get_celltype_list", path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_substrate_list(path: str) -> Dict[str, Any]: + """Retrieve a list of substrates present in the PhysiCell output.""" + cmd = ["pcdl_get_substrate_list", path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_cell_attribute_list(path: str) -> Dict[str, Any]: + """Retrieve a list of all tracked cell attribute labels.""" + cmd = ["pcdl_get_cell_attribute_list", path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_plot_timeseries( + path: str, + focus_column: str, + frame: str = "cell", + output_path: str = ".", + ext: str = "png", + verbose: bool = True +) -> Dict[str, Any]: + """ + Plot cell or substrate attributes over time. + + :param path: Path to the PhysiCell time series. + :param focus_column: Attribute to plot. + :param frame: Data source ('cell' or 'conc'). + :param output_path: Directory to save the plot. + :param ext: Output format ('png', 'csv', 'fig'). + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_plot_timeseries", path, "--column", focus_column, "--frame", frame, "--output_path", output_path, "--ext", ext] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_spatialdata( + path: str, + output_path: str = ".", + verbose: bool = True +) -> Dict[str, Any]: + """ + Transform PhysiCell output into SpatialData objects. + + :param path: Path to PhysiCell output. + :param output_path: Directory to save SpatialData. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_get_spatialdata", path, "--output_path", output_path] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_get_muspan( + path: str, + output_path: str = ".", + verbose: bool = True +) -> Dict[str, Any]: + """ + Transform PhysiCell output into MuSpan compatible format. + + :param path: Path to PhysiCell output. + :param output_path: Directory to save MuSpan data. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_get_muspan", path, "--output_path", output_path] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_render_neuroglancer( + path: str, + port: int = 8080 +) -> Dict[str, Any]: + """ + Render OME-TIFF images into Neuroglancer for visualization. + + :param path: Path to the OME-TIFF file. + :param port: Port to run the Neuroglancer server on. + """ + if not Path(path).exists(): + return {"error": f"File {path} not found."} + + cmd = ["pcdl_render_neuroglancer", path, "--port", str(port)] + try: + # Note: This might be a long-running process + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def pcdl_make_ome_tiff( + path: str, + output_path: str = ".", + verbose: bool = True +) -> Dict[str, Any]: + """ + Save PhysiCell output data in OME-TIFF file format. + + :param path: Path to PhysiCell output. + :param output_path: Directory to save OME-TIFF files. + :param verbose: Enable verbose output. + """ + cmd = ["pcdl_make_ome_tiff", path, "--output_path", output_path] + if not verbose: cmd.append("--quiet") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return {"command_executed": " ".join(cmd), "stdout": result.stdout} + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pcdl/app/pcdl_shim_server.py b/Biomni/mcp_generated/mcp_pcdl/app/pcdl_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cf97023d9fb11d6cee3c75fdd36bb7d9bba448db --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/app/pcdl_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pcdl/app/pcdl_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_pcdl' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pcdl/app/requirements.txt b/Biomni/mcp_generated/mcp_pcdl/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_pcdl/docker-compose.yml b/Biomni/mcp_generated/mcp_pcdl/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..24429f0644039dcd70388bd4a749c9e84d8b6eb2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-pcdl: + build: . + image: mcp-pcdl:latest + container_name: mcp-pcdl + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=pcdl + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pcdl/environment.yaml b/Biomni/mcp_generated/mcp_pcdl/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b65dbadc4c12f08ae8956e3bd98815a0ef07e0af --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - pcdl + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pcdl/requirements.txt b/Biomni/mcp_generated/mcp_pcdl/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pcdl/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/Dockerfile b/Biomni/mcp_generated/mcp_perl-common-sense/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0f9a181afd2fc349b199851940b4c391c7d26b70 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-common-sense via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-common-sense -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-common-sense_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-common-sense_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-common-sense_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_server.py b/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7e5e8531ae20290854ae83faa8d999d47c9b734a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_server.py @@ -0,0 +1,237 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Note: The 'mcp' decorator is a placeholder for the Model Context Protocol (MCP) +# system and is not defined in this script. It is assumed to be provided by the +# execution environment. + +@mcp.tool +def perl_run( + # --- Primary Execution Control --- + programfile: Optional[Path] = None, + execute_program_string: Optional[List[str]] = None, + execute_program_string_with_features: Optional[List[str]] = None, + arguments: Optional[List[str]] = None, + + # --- Informational Flags (exit after running) --- + print_version: bool = False, + print_config: bool = False, + config_variable: Optional[str] = None, + check_syntax_only: bool = False, + + # --- Input/Output Processing --- + record_separator: Optional[str] = None, + line_ending_processing: Optional[str] = None, + autosplit: bool = False, + split_pattern: Optional[str] = None, + in_place_edit: bool = False, + in_place_edit_backup_extension: Optional[str] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + + # --- Module and Library Loading --- + include_directories: Optional[List[Path]] = None, + use_modules: Optional[List[str]] = None, + no_modules: Optional[List[str]] = None, + use_modules_no_import: Optional[List[str]] = None, + no_modules_no_import: Optional[List[str]] = None, + no_sitecustomize: bool = False, + + # --- Debugging and Warnings --- + run_debugger: bool = False, + debugger_module: Optional[str] = None, + debugging_flags: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + + # --- Advanced/Miscellaneous --- + unicode_features: Optional[str] = None, + parse_switches: bool = False, + search_path_for_program: bool = False, + dump_core: bool = False, + allow_unsafe_operations: bool = False, + extract_script: bool = False, + change_dir_before_extract: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Executes a Perl program with a wide range of options. + + This tool acts as a wrapper for the `perl` interpreter, providing access to its + command-line switches. You must specify a program to run, either via a file + (`programfile`) or directly as a string (`execute_program_string` or + `execute_program_string_with_features`). + + Args: + programfile: Path to the Perl program file to execute. + execute_program_string: One or more lines of a Perl program to execute directly. + execute_program_string_with_features: Like -e, but enables all optional features. + arguments: A list of arguments to pass to the program file. + print_version: Print version, patchlevel, and license. + print_config: Print configuration summary. + config_variable: Print a single Config.pm variable when `print_config` is True. + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). + record_separator: Specify record separator (e.g., '\\0'). + line_ending_processing: Enable line ending processing, specifies line terminator. + autosplit: Autosplit mode with -n or -p (splits $_ into @F). + split_pattern: split() pattern for autosplit mode. + in_place_edit: Edit <> files in place. + in_place_edit_backup_extension: Create a backup with this extension if supplied. + loop_around_program: Assume "while (<>) { ... }" loop around program. + loop_and_print: Assume loop like -n but also print line, like sed. + include_directories: Specify @INC/#include directory (can be multiple). + use_modules: Execute "use module..." before executing the program. + no_modules: Execute "no module..." before executing the program. + use_modules_no_import: Like -M, but without importing module symbols. + no_modules_no_import: Like -M-, but without importing module symbols. + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. + run_debugger: Run the program under the debugger. + debugger_module: Specify a specific debugger module to use. + debugging_flags: Set debugging flags (bit mask or alphabets). + enable_warnings: Enable many useful warnings. + enable_all_warnings: Enable all warnings. + disable_all_warnings: Disable all warnings. + tainting_warnings: Enable tainting warnings. + tainting_checks: Enable tainting checks. + unicode_features: Enables the listed Unicode features. + parse_switches: Enable rudimentary parsing for switches after programfile. + search_path_for_program: Look for programfile using the PATH environment variable. + dump_core: Dump core after parsing the program. + allow_unsafe_operations: Allow unsafe operations. + extract_script: Ignore text before #!perl line. + change_dir_before_extract: Optionally cd to this directory before extracting script. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + execution_sources = [ + programfile, + execute_program_string, + execute_program_string_with_features, + ] + num_execution_sources = sum(1 for source in execution_sources if source) + + info_flags_set = print_version or print_config or check_syntax_only + + if num_execution_sources > 1: + raise ValueError( + "Only one of 'programfile', 'execute_program_string', or " + "'execute_program_string_with_features' can be specified." + ) + + if num_execution_sources == 0 and not info_flags_set: + raise ValueError( + "You must specify a program to run (e.g., 'programfile') or an " + "informational flag to display (e.g., 'print_version')." + ) + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"The program file '{programfile}' does not exist.") + + if arguments and not programfile: + raise ValueError("'arguments' can only be provided when 'programfile' is specified.") + + if in_place_edit_backup_extension is not None and not in_place_edit: + raise ValueError("'in_place_edit_backup_extension' requires 'in_place_edit' to be True.") + + if debugger_module is not None and not run_debugger: + raise ValueError("'debugger_module' requires 'run_debugger' to be True.") + + if config_variable is not None and not print_config: + raise ValueError("'config_variable' requires 'print_config' to be True.") + + if change_dir_before_extract is not None and not extract_script: + raise ValueError("'change_dir_before_extract' requires 'extract_script' to be True.") + + # --- Command Construction --- + cmd = ["perl"] + + if record_separator is not None: cmd.append(f"-0{record_separator}") + if autosplit: cmd.append("-a") + if unicode_features is not None: cmd.append(f"-C{unicode_features}") + if check_syntax_only: cmd.append("-c") + if debugging_flags is not None: cmd.append(f"-D{debugging_flags}") + if no_sitecustomize: cmd.append("-f") + if split_pattern is not None: cmd.append(f"-F{split_pattern}") + if line_ending_processing is not None: cmd.append(f"-l{line_ending_processing}") + if loop_around_program: cmd.append("-n") + if loop_and_print: cmd.append("-p") + if parse_switches: cmd.append("-s") + if search_path_for_program: cmd.append("-S") + if tainting_warnings: cmd.append("-t") + if tainting_checks: cmd.append("-T") + if dump_core: cmd.append("-u") + if allow_unsafe_operations: cmd.append("-U") + if print_version: cmd.append("-v") + if enable_warnings: cmd.append("-w") + if enable_all_warnings: cmd.append("-W") + if disable_all_warnings: cmd.append("-X") + + if run_debugger: + flag = "-d" + if debugger_module: + flag += f":{debugger_module}" + cmd.append(flag) + + if in_place_edit: + flag = "-i" + if in_place_edit_backup_extension is not None: + flag += in_place_edit_backup_extension + cmd.append(flag) + + if print_config: + flag = "-V" + if config_variable: + flag += f":{config_variable}" + cmd.append(flag) + + if extract_script: + flag = "-x" + if change_dir_before_extract: + flag += str(change_dir_before_extract) + cmd.append(flag) + + if execute_program_string: + for prog_line in execute_program_string: cmd.extend(["-e", prog_line]) + if execute_program_string_with_features: + for prog_line in execute_program_string_with_features: cmd.extend(["-E", prog_line]) + if include_directories: + for directory in include_directories: cmd.extend(["-I", str(directory)]) + if use_modules: + for module in use_modules: cmd.append(f"-M{module}") + if no_modules: + for module in no_modules: cmd.append(f"-M-{module}") + if use_modules_no_import: + for module in use_modules_no_import: cmd.append(f"-m{module}") + if no_modules_no_import: + for module in no_modules_no_import: cmd.append(f"-m-{module}") + + if programfile: + cmd.append(str(programfile)) + if arguments: + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=False, # Perl can return non-zero status for valid reasons + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "return_code": result.returncode, + "output_files": [], + } + except FileNotFoundError: + raise RuntimeError("Error: 'perl' command not found. Please ensure Perl is installed and in your PATH.") + except Exception as e: + raise RuntimeError(f"An unexpected error occurred: {e}") diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_shim_server.py b/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cc22b0142c979f8c1e87b5a5fdb7e23bd4c5da7e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/app/perl-common-sense_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-common-sense/app/perl-common-sense_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_common_sense' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-common-sense/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-common-sense/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..66d6be5a118665e4fdf54f704c48cc2143e229b9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-common-sense: + build: . + image: mcp-perl-common-sense:latest + container_name: mcp-perl-common-sense + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-common-sense + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/environment.yaml b/Biomni/mcp_generated/mcp_perl-common-sense/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9448f69e4fbcea6f8ed9ddd968c3ccbee076550c --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-common-sense + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-common-sense/requirements.txt b/Biomni/mcp_generated/mcp_perl-common-sense/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-common-sense/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-cpan-meta/requirements.txt b/Biomni/mcp_generated/mcp_perl-cpan-meta/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-cpan-meta/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-json-pp/Dockerfile b/Biomni/mcp_generated/mcp_perl-json-pp/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7e324acdc328a7d478bb3d2aa91766a1bd49de25 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-pp/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-json-pp via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-json-pp -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-json-pp_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-json-pp_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-json-pp_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-pp/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-json-pp/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..07d786bf4ac683261dd729143f5f388d71be3618 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-pp/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-json-pp: + build: . + image: mcp-perl-json-pp:latest + container_name: mcp-perl-json-pp + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-json-pp + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-pp/environment.yaml b/Biomni/mcp_generated/mcp_perl-json-pp/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f65fd8e82f6643a171ed05900e531c668e9541a6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-pp/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-json-pp + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-pp/requirements.txt b/Biomni/mcp_generated/mcp_perl-json-pp/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-pp/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-test/Dockerfile b/Biomni/mcp_generated/mcp_perl-test/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a9d0aa991eda811b0b47da9b8b4fbfafd809eaa0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-test via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-test -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-test_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-test_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-test_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test/app/perl-test_server.py b/Biomni/mcp_generated/mcp_perl-test/app/perl-test_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4ae00d37d38c74458e04e97c37b90fa13ba439b1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/app/perl-test_server.py @@ -0,0 +1,163 @@ +import subprocess +from pathlib import Path +from typing import Optional, Dict + +# Assuming mcp is imported from the Model Context Protocol library +# e.g., from mcp import mcp + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_test' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perl_test( + input_file: Path, + output_file: Path, + threshold: float = 0.05, + iterations: int = 1000, + verbose: bool = False, + seed: Optional[int] = None, +) -> Dict: + """ + Runs a hypothetical 'perl-test' analysis. + + This tool simulates running a Perl-based bioinformatics test, taking an input file + and generating an output file based on specified parameters. + + Args: + input_file: Path to the input data file. + output_file: Path to write the output results. + threshold: A significance threshold for the test. Defaults to 0.05. + iterations: Number of iterations or permutations to run. Defaults to 1000. + verbose: If True, enables verbose logging. Defaults to False. + seed: An optional integer seed for reproducibility. Defaults to None. + + Returns: + A dictionary containing the execution details and output file path. + """ + # --- Input Validation --- + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + if not (0.0 <= threshold <= 1.0): + raise ValueError("Threshold must be between 0.0 and 1.0.") + + if iterations <= 0: + raise ValueError("Iterations must be a positive integer.") + + if seed is not None and seed < 0: + raise ValueError("Seed must be a non-negative integer.") + + # Ensure the output directory exists + output_file.parent.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "perl-test", + "--input", str(input_file), + "--output", str(output_file), + "--threshold", str(threshold), + "--iterations", str(iterations), + ] + + if verbose: + cmd.append("--verbose") + + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + command_executed = " ".join(cmd) + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {"results": str(output_file)}, + } + except FileNotFoundError: + raise RuntimeError( + "The 'perl-test' command was not found. Please ensure it is installed and in your system's PATH." + ) + except subprocess.CalledProcessError as e: + # Return a structured error if the tool fails + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"perl-test failed with exit code {e.returncode}", + "output_files": {}, + } + + +@mcp.tool() +def perl_test_validate( + input_file: Path, + format: str = "fasta", +) -> Dict: + """ + Validates the format of an input file using the 'perl-test validate' subcommand. + + This tool checks if the input file conforms to a specified format like 'fasta' or 'fastq'. + + Args: + input_file: The file to validate. + format: The expected format (e.g., 'fasta', 'fastq', 'gff'). Defaults to 'fasta'. + + Returns: + A dictionary containing the execution details of the validation command. + """ + # --- Input Validation --- + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + allowed_formats = ["fasta", "fastq", "gff", "vcf", "bed"] + if format.lower() not in allowed_formats: + raise ValueError( + f"Invalid format '{format}'. Allowed formats are: {', '.join(allowed_formats)}" + ) + + # --- Command Construction --- + cmd = [ + "perl-test", + "validate", + "--input", str(input_file), + "--format", format, + ] + command_executed = " ".join(cmd) + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + } + except FileNotFoundError: + raise RuntimeError( + "The 'perl-test' command was not found. Please ensure it is installed and in your system's PATH." + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"perl-test validate failed with exit code {e.returncode}", + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-test/app/perl-test_shim_server.py b/Biomni/mcp_generated/mcp_perl-test/app/perl-test_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f4caac6ea13a8b59150ed179dc2aaaea0a31e012 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/app/perl-test_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test/app/perl-test_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_test' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-test/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-test/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-test/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-test/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..98f5ce3b1a2bf34ad7a408d5c89951c77cf75671 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-test: + build: . + image: mcp-perl-test:latest + container_name: mcp-perl-test + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-test + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test/environment.yaml b/Biomni/mcp_generated/mcp_perl-test/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b7b78471c2592dfcf9002cc6541fcbaa53dcc42 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-test + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test/requirements.txt b/Biomni/mcp_generated/mcp_perl-test/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/Dockerfile b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..64e41c86c971ea103bcf1564da14eb4f139de2f2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-xml-namespacesupport via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-xml-namespacesupport -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-xml-namespacesupport_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-xml-namespacesupport_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-xml-namespacesupport_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_server.py b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6df4ef2cfa23c9eed1011e5118d617f8a165fdd2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_server.py @@ -0,0 +1,91 @@ +import subprocess +from pathlib import Path +from typing import Optional, Dict, Any + +# No need to import mcp as per instructions + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_xml_namespacesupport' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def get_module_info() -> Dict[str, Any]: + """ + Provides information about the perl-xml-namespacesupport module. + + This tool is designed to interact with the perl-xml-namespacesupport module, + which is primarily a Perl library for programmatic use, not a standalone + command-line executable. This function attempts to verify the module's + presence and retrieve its version by executing a Perl one-liner. + + As perl-xml-namespacesupport does not expose direct command-line functions, + this tool serves as a utility to confirm its installation and basic status. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any + output files (which will be empty in this case). + """ + command = ["perl", "-MXML::NamespaceSupport", "-e", "print $XML::NamespaceSupport::VERSION // 'VERSION not defined'"] + command_str = " ".join(command) + stdout = "" + stderr = "" + output_files: list[str] = [] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + stdout = result.stdout.strip() + stderr = result.stderr.strip() + + # Check for module not found in stderr, even if check=True didn't catch it (less likely with -M) + if "Can't locate XML/NamespaceSupport.pm" in stderr: + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": f"Error: Perl module XML::NamespaceSupport not found. Please ensure it is installed in your Perl environment. Original stderr: {stderr}", + "output_files": output_files, + } + + except subprocess.CalledProcessError as e: + stdout = e.stdout.strip() + stderr = e.stderr.strip() + + if "Can't locate XML/NamespaceSupport.pm" in stderr: + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": f"Error: Perl module XML::NamespaceSupport not found. Please ensure it is installed in your Perl environment. Original stderr: {stderr}", + "output_files": output_files, + } + else: + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": f"Error executing Perl command (exit code {e.returncode}): {e}\n{stderr}", + "output_files": output_files, + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'perl' command not found. Please ensure Perl is installed and accessible in your PATH.", + "output_files": output_files, + } + except Exception as e: + # Catch any other unexpected errors during execution + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": f"An unexpected error occurred: {e}\n{stderr}", + "output_files": output_files, + } + + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_shim_server.py b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0939d795b106fc46480bd4bc9677fab5f261aa02 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_xml_namespacesupport' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e4a75cea631affd21a341310dbc648e7faadb29f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-xml-namespacesupport: + build: . + image: mcp-perl-xml-namespacesupport:latest + container_name: mcp-perl-xml-namespacesupport + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-xml-namespacesupport + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/perl-xml-xpathengine_server.py b/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/perl-xml-xpathengine_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ccb5bbb9245bcf0e61343c91a0e294fdc99bb878 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/perl-xml-xpathengine_server.py @@ -0,0 +1,238 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +# No need to import mcp as per instructions + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_xml_xpathengine' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl_script( + program_file: Optional[Path] = None, + inline_program: Optional[List[str]] = None, + inline_program_extended: Optional[List[str]] = None, + program_arguments: Optional[List[str]] = None, + record_separator_octal: Optional[str] = None, + autosplit_mode: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + no_sitecustomize: bool = False, + split_pattern: Optional[str] = None, + edit_in_place_extension: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + line_ending_processing: Optional[str] = None, + use_modules: Optional[List[str]] = None, + no_use_modules: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + rudimentary_switch_parsing: bool = False, + search_path_for_program: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + dump_core: bool = False, + allow_unsafe_operations: bool = False, + print_version: bool = False, + print_config_summary: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + ignore_text_before_shebang_dir: Optional[Path] = None, + disable_all_warnings: bool = False, +) -> dict: + """ + Executes a Perl script with specified interpreter options. + + This tool provides access to the command-line options of the Perl interpreter. + The 'perl-xml-xpathengine' package is a Perl module and does not have its + own standalone command-line interface. This tool allows running a generic + Perl script, which can then internally 'use XML::XPathEngine;' to utilize + its functionality. + + Args: + program_file: Path to the Perl script file to execute. Required if no inline program is provided. + inline_program: One or more lines of Perl program code to execute directly. + Can be specified multiple times via a list. Corresponds to -e. + inline_program_extended: One or more lines of Perl program code, like `inline_program`, + but enables all optional features. Can be specified multiple times via a list. + Corresponds to -E. + program_arguments: Arguments to be passed to the Perl script itself. + record_separator_octal: Specify record separator (e.g., '0' for NUL, '0777' for paragraph mode). + If no argument, defaults to '\0'. Corresponds to -0[octal]. + autosplit_mode: Enable autosplit mode with -n or -p (splits $_ into @F). Corresponds to -a. + unicode_features: Enables listed Unicode features (e.g., 'all', 'io', 'say'). Corresponds to -C[number/list]. + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). Corresponds to -c. + debugger: Run program under debugger. Optionally specify debugger name (e.g., 'MyDebugger'). + Corresponds to -d[:debugger]. + debugging_flags: Set debugging flags (argument is a bit mask or alphabets). Corresponds to -D[number/list]. + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. Corresponds to -f. + split_pattern: Pattern for split() function used with -a switch (e.g., '/\\s+/'). + Slashes are optional. Corresponds to -F/pattern/. + edit_in_place_extension: Edit <> files in place. If extension is supplied (e.g., '.bak'), + makes a backup. If empty string, no backup. Corresponds to -i[extension]. + include_directories: Specify @INC/#include directories. Can be specified multiple times. + Corresponds to -Idirectory. + line_ending_processing: Enable line ending processing. Optionally specifies line terminator + (e.g., '012' for newline). Corresponds to -l[octal]. + use_modules: List of modules to execute "use module..." before executing the program. + Corresponds to -Mmodule. + no_use_modules: List of modules to execute "no module..." before executing the program. + Corresponds to -M-module. + loop_around_program: Assume "while (<>) { ... }" loop around program. Corresponds to -n. + loop_and_print: Assume loop like -n but print line also, like sed. Corresponds to -p. + rudimentary_switch_parsing: Enable rudimentary parsing for switches after programfile. Corresponds to -s. + search_path_for_program: Look for programfile using PATH environment variable. Corresponds to -S. + tainting_warnings: Enable tainting warnings. Corresponds to -t. + tainting_checks: Enable tainting checks. Corresponds to -T. + dump_core: Dump core after parsing program. Corresponds to -u. + allow_unsafe_operations: Allow unsafe operations. Corresponds to -U. + print_version: Print Perl version, patchlevel, and license. Corresponds to -v. + print_config_summary: Print configuration summary. Optionally specify a single Config.pm variable. + Corresponds to -V[:variable]. + enable_warnings: Enable many useful warnings (-w). Corresponds to -w. + enable_all_warnings: Enable all warnings (-W). Corresponds to -W. + ignore_text_before_shebang_dir: Ignore text before #!perl line. Optionally change to directory. + If an empty Path is provided (Path('')), it corresponds to -x. + Corresponds to -x[directory]. + disable_all_warnings: Disable all warnings (-X). Corresponds to -X. + """ + cmd = ["perl"] + output_files = [] + + # Input validation + if program_file and not program_file.is_file(): + raise FileNotFoundError(f"Program file not found: {program_file}") + if program_file and (inline_program or inline_program_extended): + raise ValueError("Cannot specify both a program file and inline program code (-e/-E).") + if not program_file and not (inline_program or inline_program_extended): + raise ValueError("Either a program file or inline program code (-e/-E) must be provided.") + + if loop_around_program and loop_and_print: + raise ValueError("Cannot use both -n (loop_around_program) and -p (loop_and_print) simultaneously.") + + if enable_warnings and enable_all_warnings: + raise ValueError("Cannot use both -w (enable_warnings) and -W (enable_all_warnings) simultaneously.") + if (enable_warnings or enable_all_warnings) and disable_all_warnings: + raise ValueError("Cannot enable warnings (-w or -W) and disable all warnings (-X) simultaneously.") + + # Add switches + if record_separator_octal is not None: + cmd.append(f"-0{record_separator_octal}") + if autosplit_mode: + cmd.append("-a") + if unicode_features is not None: + cmd.append(f"-C{unicode_features}") + if check_syntax_only: + cmd.append("-c") + if debugger is not None: + cmd.append(f"-d:{debugger}" if debugger else "-d") + if debugging_flags is not None: + cmd.append(f"-D{debugging_flags}") + if inline_program: + for prog_line in inline_program: + cmd.extend(["-e", prog_line]) + if inline_program_extended: + for prog_line in inline_program_extended: + cmd.extend(["-E", prog_line]) + if no_sitecustomize: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if edit_in_place_extension is not None: + cmd.append(f"-i{edit_in_place_extension}") + if include_directories: + for inc_dir in include_directories: + if not inc_dir.is_dir(): + raise FileNotFoundError(f"Include directory not found: {inc_dir}") + cmd.extend(["-I", str(inc_dir)]) + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if use_modules: + for module in use_modules: + cmd.append(f"-M{module}") # Using -M as it's more common for CLI + if no_use_modules: + for module in no_use_modules: + cmd.append(f"-M-{module}") # Using -M- + if loop_around_program: + cmd.append("-n") + if loop_and_print: + cmd.append("-p") + if rudimentary_switch_parsing: + cmd.append("-s") + if search_path_for_program: + cmd.append("-S") + if tainting_warnings: + cmd.append("-t") + if tainting_checks: + cmd.append("-T") + if dump_core: + cmd.append("-u") + if allow_unsafe_operations: + cmd.append("-U") + if print_version: + cmd.append("-v") + if print_config_summary is not None: + cmd.append(f"-V:{print_config_summary}" if print_config_summary else "-V") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if ignore_text_before_shebang_dir is not None: + # If Path('') is provided, it means -x with no directory, which is valid. + # If a specific directory is provided, check if it exists. + if str(ignore_text_before_shebang_dir) and not ignore_text_before_shebang_dir.is_dir(): + raise FileNotFoundError(f"Directory for -x not found: {ignore_text_before_shebang_dir}") + cmd.append(f"-x{ignore_text_before_shebang_dir}") + if disable_all_warnings: + cmd.append("-X") + + # Add program file + if program_file: + cmd.append(str(program_file)) + # If only inline programs are given, no program_file is needed. + # The -e/-E options handle the script directly. + + # Add program arguments + if program_arguments: + cmd.extend(program_arguments) + + try: + process = subprocess.run( + [str(arg) for arg in cmd], # Ensure all args are strings + capture_output=True, + text=True, + check=True + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join([str(arg) for arg in cmd]), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": output_files, + } + except FileNotFoundError: + return { + "command_executed": " ".join([str(arg) for arg in cmd]), + "stdout": "", + "stderr": "Error: 'perl' command not found. Is Perl installed and in your PATH?", + "error": "Perl executable not found.", + "returncode": 127, + "output_files": output_files, + } + + return { + "command_executed": " ".join([str(arg) for arg in cmd]), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-xpathengine/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-yaml/Dockerfile b/Biomni/mcp_generated/mcp_perl-yaml/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9f5a7a5ee239375dcb74afc06a9aba79e54661d5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-yaml via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-yaml -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-yaml_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-yaml_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-yaml_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_server.py b/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1ca94c73ee41daffffdf08860e2d4f025f56107b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_server.py @@ -0,0 +1,298 @@ +import subprocess +import json +from pathlib import Path +from typing import Optional, List, Dict, Any, Union + +def _run_perl_cmd(args: List[str], stdin: Optional[str] = None) -> Dict[str, Any]: + """Helper function to execute perl commands and return structured output.""" + cmd = ["perl"] + args + try: + result = subprocess.run( + cmd, + input=stdin, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with return code {e.returncode}" + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_yaml' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def yaml_dump( + json_data: str, + indent: int = 2, + sort_keys: bool = True, + use_header: bool = True, + use_version: bool = False, + compress_series: bool = True, + stringify: bool = False, + numify: bool = False, + quote_numeric_strings: bool = False, + anchor_prefix: str = "", +) -> Dict[str, Any]: + """ + Turn Perl data structures (passed as JSON) into a YAML stream using YAML::Dump. + + Args: + json_data: JSON string representing the data structure(s) to serialize. + indent: Number of space characters to use for each indentation level (default 2). + sort_keys: Whether to sort hash keys alphabetically (default True). + use_header: Whether to include the '---' separator string (default True). + use_version: Whether to include the YAML version on the header (default False). + compress_series: Whether to compress the formatting of arrays of hashes (default True). + stringify: Honor string overloading for objects (default False). + numify: Values that look like numbers will be numified when loaded (default False). + quote_numeric_strings: Force quoting of strings that resemble numbers (default False). + anchor_prefix: String to prepend to each anchor number. + """ + if indent < 0: + indent = 2 + + perl_script = ( + "use YAML; use JSON::PP; " + f"$YAML::Indent = {indent}; " + f"$YAML::SortKeys = {1 if sort_keys else 0}; " + f"$YAML::UseHeader = {1 if use_header else 0}; " + f"$YAML::UseVersion = {1 if use_version else 0}; " + f"$YAML::CompressSeries = {1 if compress_series else 0}; " + f"$YAML::Stringify = {1 if stringify else 0}; " + f"$YAML::Numify = {1 if numify else 0}; " + f"$YAML::QuoteNumericStrings = {1 if quote_numeric_strings else 0}; " + f"$YAML::AnchorPrefix = '{anchor_prefix}'; " + "my $json_text = do { local $/; }; " + "my $data = decode_json($json_text); " + "if (ref($data) eq 'ARRAY') { print Dump(@$data); } else { print Dump($data); }" + ) + + return _run_perl_cmd(["-e", perl_script], stdin=json_data) + +@mcp.tool() +def yaml_load( + yaml_stream: str, + load_blessed: bool = False, + numify: bool = False, +) -> Dict[str, Any]: + """ + Turn a YAML stream into Perl data structures (returned as JSON) using YAML::Load. + + Args: + yaml_stream: The YAML content string to parse. + load_blessed: When true, YAML nodes with special tags will be automatically blessed into objects. + numify: Values that look like numbers will be numified when loaded. + """ + perl_script = ( + "use YAML; use JSON::PP; " + f"$YAML::LoadBlessed = {1 if load_blessed else 0}; " + f"$YAML::Numify = {1 if numify else 0}; " + "my $yaml_text = do { local $/; }; " + "my @docs = Load($yaml_text); " + "print encode_json(\\@docs);" + ) + + return _run_perl_cmd(["-e", perl_script], stdin=yaml_stream) + +@mcp.tool() +def yaml_dump_file( + filepath: str, + json_data: str, + indent: int = 2, + sort_keys: bool = True, +) -> Dict[str, Any]: + """ + Writes a YAML stream to a file using YAML::DumpFile. + + Args: + filepath: Path to the output YAML file. + json_data: JSON string representing the data to serialize. + indent: Number of spaces for each indentation level. + sort_keys: Whether to sort hash keys. + """ + path = Path(filepath) + if not path.parent.exists(): + return {"error": f"Parent directory does not exist: {path.parent}"} + + perl_script = ( + "use YAML; use JSON::PP; " + f"$YAML::Indent = {indent}; " + f"$YAML::SortKeys = {1 if sort_keys else 0}; " + "my $file = shift; " + "my $json_text = do { local $/; }; " + "my $data = decode_json($json_text); " + "if (ref($data) eq 'ARRAY') { DumpFile($file, @$data); } else { DumpFile($file, $data); }" + ) + + result = _run_perl_cmd(["-e", perl_script, str(path)], stdin=json_data) + if "error" not in result: + result["output_files"] = [str(path)] + return result + +@mcp.tool() +def yaml_load_file( + filepath: str, + load_blessed: bool = False, +) -> Dict[str, Any]: + """ + Reads a YAML stream from a file using YAML::LoadFile. + + Args: + filepath: Path to the input YAML file. + load_blessed: Whether to load blessed objects. + """ + path = Path(filepath) + if not path.exists(): + return {"error": f"File not found: {filepath}"} + + perl_script = ( + "use YAML; use JSON::PP; " + f"$YAML::LoadBlessed = {1 if load_blessed else 0}; " + "my $file = shift; " + "my @docs = LoadFile($file); " + "print encode_json(\\@docs);" + ) + + return _run_perl_cmd(["-e", perl_script, str(path)]) + +@mcp.tool() +def yaml_freeze( + json_data: str, +) -> Dict[str, Any]: + """ + Alias to Dump(). Turn Perl data into YAML. + + Args: + json_data: JSON string representing the data. + """ + perl_script = "use YAML; use JSON::PP; print freeze(decode_json(do { local $/; }));" + return _run_perl_cmd(["-e", perl_script], stdin=json_data) + +@mcp.tool() +def yaml_thaw( + yaml_stream: str, +) -> Dict[str, Any]: + """ + Alias to Load(). Turn YAML into Perl data. + + Args: + yaml_stream: The YAML content to parse. + """ + perl_script = "use YAML; use JSON::PP; print encode_json([thaw(do { local $/; })]);" + return _run_perl_cmd(["-e", perl_script], stdin=yaml_stream) + +@mcp.tool() +def yaml_bless( + json_data: str, + class_name: str, +) -> Dict[str, Any]: + """ + Associate a Perl data structure with a class name for YAML serialization using YAML::Bless. + + Args: + json_data: JSON string representing the data. + class_name: The class name to bless the data into. + """ + perl_script = ( + "use YAML; use JSON::PP; " + "my $json = do { local $/; }; " + "my $data = decode_json($json); " + f"Bless($data, '{class_name}'); " + "print Dump($data);" + ) + return _run_perl_cmd(["-e", perl_script], stdin=json_data) + +@mcp.tool() +def perl_cli( + one_liner: str = "", + program_file: Optional[str] = None, + arguments: Optional[List[str]] = None, + autosplit: bool = False, + check_syntax: bool = False, + warnings: bool = False, + all_warnings: bool = False, + no_warnings: bool = False, + include_dirs: Optional[List[str]] = None, + modules: Optional[List[str]] = None, + inplace_extension: Optional[str] = None, + assume_loop_n: bool = False, + assume_loop_p: bool = False, + version: bool = False, +) -> Dict[str, Any]: + """ + Execute the Perl interpreter with various switches. + + Args: + one_liner: One line of program (equivalent to -e). + program_file: Path to a perl script file to execute. + arguments: Arguments passed to the program. + autosplit: Enable autosplit mode with -n or -p (equivalent to -a). + check_syntax: Check syntax only (equivalent to -c). + warnings: Enable many useful warnings (equivalent to -w). + all_warnings: Enable all warnings (equivalent to -W). + no_warnings: Disable all warnings (equivalent to -X). + include_dirs: Directories to search for modules (equivalent to -I). + modules: Modules to execute 'use module' before the program (equivalent to -m). + inplace_extension: Edit files in place (equivalent to -i). + assume_loop_n: Assume 'while (<>) { ... }' loop (equivalent to -n). + assume_loop_p: Assume loop like -n but print line (equivalent to -p). + version: Print version and patchlevel (equivalent to -v). + """ + cmd_args = [] + if autosplit: cmd_args.append("-a") + if check_syntax: cmd_args.append("-c") + if warnings: cmd_args.append("-w") + if all_warnings: cmd_args.append("-W") + if no_warnings: cmd_args.append("-X") + if assume_loop_n: cmd_args.append("-n") + if assume_loop_p: cmd_args.append("-p") + if version: cmd_args.append("-v") + + if include_dirs: + for d in include_dirs: + cmd_args.append(f"-I{d}") + + if modules: + for m in modules: + cmd_args.append(f"-m{m}") + + if inplace_extension is not None: + cmd_args.append(f"-i{inplace_extension}") + + if one_liner: + cmd_args.extend(["-e", one_liner]) + + if program_file: + p = Path(program_file) + if not p.exists(): + return {"error": f"Program file not found: {program_file}"} + cmd_args.append(str(p)) + + if arguments: + cmd_args.extend(arguments) + + return _run_perl_cmd(cmd_args) + +@mcp.tool() +def perl_yaml_version() -> Dict[str, Any]: + """ + Print the version of the YAML module and the Perl interpreter. + """ + perl_script = "use YAML; print 'YAML version: ' . $YAML::VERSION . \"\\n\";" + return _run_perl_cmd(["-e", perl_script]) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_shim_server.py b/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c5f8239efa0b4129f5e8ccce9d0e74deab3c55b7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/app/perl-yaml_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-yaml/app/perl-yaml_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_yaml' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-yaml/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-yaml/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-yaml/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-yaml/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9cc13a8511fb6aef79c053dc44e1c88ce0f1142d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-yaml: + build: . + image: mcp-perl-yaml:latest + container_name: mcp-perl-yaml + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-yaml + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-yaml/environment.yaml b/Biomni/mcp_generated/mcp_perl-yaml/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a5d44d3c424ed030a2ea49f57c26e2d3090ece12 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-yaml + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-yaml/requirements.txt b/Biomni/mcp_generated/mcp_perl-yaml/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-yaml/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_planemo/requirements.txt b/Biomni/mcp_generated/mcp_planemo/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_planemo/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_portcullis/Dockerfile b/Biomni/mcp_generated/mcp_portcullis/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a1c48d470334f96d83cd33502e6a74b9d0e9ea0d --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install portcullis via conda (e.g., from bioconda) +RUN conda install -c bioconda portcullis -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/portcullis_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/portcullis_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/portcullis_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_portcullis/app/portcullis_server.py b/Biomni/mcp_generated/mcp_portcullis/app/portcullis_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d904293bd38947ba8374d9fd6f63460257211ff9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/app/portcullis_server.py @@ -0,0 +1,377 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_portcullis' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def portcullis_full( + bam_file: str, + reference: str, + output_dir: str = "portcullis_out", + threads: int = 1, + force: bool = False, + bamfilt: bool = False, + extra_args: Optional[List[str]] = None, +): + """ + Runs the complete Portcullis pipeline: prep, junc, filter, and optionally bamfilt. + + Args: + bam_file: Path to the input BAM file. + reference: Path to the reference genome (FASTA). + output_dir: Directory to store output files. + threads: Number of threads to use. + force: Force overwrite of existing output directory. + bamfilt: If True, also runs the bamfilt step to produce a filtered BAM. + extra_args: Additional command line arguments for portcullis full. + """ + bam_path = Path(bam_file) + ref_path = Path(reference) + if not bam_path.exists(): + return {"error": f"BAM file not found: {bam_file}"} + if not ref_path.exists(): + return {"error": f"Reference file not found: {reference}"} + + cmd = ["portcullis", "full"] + cmd.extend(["--output", output_dir]) + cmd.extend(["--reference", reference]) + cmd.extend(["--threads", str(threads)]) + + if force: + cmd.append("--force") + if bamfilt: + cmd.append("--bamfilt") + if extra_args: + cmd.extend(extra_args) + + cmd.append(bam_file) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": output_dir + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def portcullis_prep( + bam_file: str, + output_dir: str = "portcullis_prep_out", + threads: int = 1, + force: bool = False, +): + """ + Prepares input data (BAM) so that it is suitable for junction analysis. + + Args: + bam_file: Path to the input BAM file. + output_dir: Directory to store prepared data. + threads: Number of threads to use. + force: Force overwrite of existing output directory. + """ + if not Path(bam_file).exists(): + return {"error": f"BAM file not found: {bam_file}"} + + cmd = ["portcullis", "prep", "--output", output_dir, "--threads", str(threads)] + if force: + cmd.append("--force") + cmd.append(bam_file) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": output_dir + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def portcullis_junc( + input_data: str, + reference: str, + output_dir: str = "portcullis_junc_out", + threads: int = 1, + force: bool = False, +): + """ + Calculates junction metrics for the prepared data or BAM file. + + Args: + input_data: Path to the input BAM file or the directory generated by 'prep'. + reference: Path to the reference genome (FASTA). + output_dir: Directory to store junction metrics. + threads: Number of threads to use. + force: Force overwrite of existing output directory. + """ + if not Path(input_data).exists(): + return {"error": f"Input data path not found: {input_data}"} + if not Path(reference).exists(): + return {"error": f"Reference file not found: {reference}"} + + cmd = ["portcullis", "junc", "--output", output_dir, "--reference", reference, "--threads", str(threads)] + if force: + cmd.append("--force") + cmd.append(input_data) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": output_dir + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def portcullis_filter( + input_dir: str, + output_dir: str = "portcullis_filter_out", + threads: int = 1, + force: bool = False, +): + """ + Separates alignments based on whether they are likely to represent genuine splice junctions or not. + + Args: + input_dir: Path to the directory containing junction metrics (output from 'junc'). + output_dir: Directory to store filtered results. + threads: Number of threads to use. + force: Force overwrite of existing output directory. + """ + if not Path(input_dir).exists(): + return {"error": f"Input directory not found: {input_dir}"} + + cmd = ["portcullis", "filter", "--output", output_dir, "--threads", str(threads)] + if force: + cmd.append("--force") + cmd.append(input_dir) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": output_dir + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def portcullis_bamfilt( + bam_file: str, + junction_file: str, + output_bam: str = "filtered.bam", + threads: int = 1, +): + """ + Filters a BAM file to remove any reads associated with invalid junctions. + + Args: + bam_file: Path to the original BAM file. + junction_file: Path to the filtered junctions file (usually .pass.junctions.bed). + output_bam: Path to the output filtered BAM file. + threads: Number of threads to use. + """ + if not Path(bam_file).exists(): + return {"error": f"BAM file not found: {bam_file}"} + if not Path(junction_file).exists(): + return {"error": f"Junction file not found: {junction_file}"} + + cmd = ["portcullis", "bamfilt", "--output", output_bam, "--threads", str(threads), bam_file, junction_file] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_file": output_bam + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def junctools_convert( + input_file: str, + output_file: str, + input_format: str = "bed", + output_format: str = "portcullis", +): + """ + Converts junction files between different formats using junctools. + + Args: + input_file: Path to the input junction file. + output_file: Path to the output junction file. + input_format: Format of the input file (e.g., bed, portcullis, tophat). + output_format: Format of the output file. + """ + if not Path(input_file).exists(): + return {"error": f"Input file not found: {input_file}"} + + cmd = ["junctools", "convert", "-i", input_format, "-o", output_format, input_file, output_file] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_file": output_file + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def junctools_merge( + input_files: List[str], + output_file: str, + force: bool = False, +): + """ + Merges multiple junction files into a single file using junctools. + + Args: + input_files: List of paths to junction files to merge. + output_file: Path to the output merged junction file. + force: Force overwrite of existing output file. + """ + for f in input_files: + if not Path(f).exists(): + return {"error": f"Input file not found: {f}"} + + cmd = ["junctools", "merge"] + if force: + cmd.append("--force") + cmd.extend(["--output", output_file]) + cmd.extend(input_files) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_file": output_file + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def junctools_compare( + file1: str, + file2: str, + output_prefix: str = "comparison", +): + """ + Compares two junction files using junctools. + + Args: + file1: Path to the first junction file. + file2: Path to the second junction file. + output_prefix: Prefix for output comparison files. + """ + if not Path(file1).exists(): + return {"error": f"File 1 not found: {file1}"} + if not Path(file2).exists(): + return {"error": f"File 2 not found: {file2}"} + + cmd = ["junctools", "compare", "--output", output_prefix, file1, file2] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_prefix": output_prefix + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def junctools_stats( + input_file: str, +): + """ + Generates statistics for a junction file using junctools. + + Args: + input_file: Path to the junction file. + """ + if not Path(input_file).exists(): + return {"error": f"Input file not found: {input_file}"} + + cmd = ["junctools", "stats", input_file] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_portcullis/app/portcullis_shim_server.py b/Biomni/mcp_generated/mcp_portcullis/app/portcullis_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ed86b649c779398edbea22ee361ac8a2504a4b4c --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/app/portcullis_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_portcullis/app/portcullis_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_portcullis' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_portcullis/app/requirements.txt b/Biomni/mcp_generated/mcp_portcullis/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_portcullis/docker-compose.yml b/Biomni/mcp_generated/mcp_portcullis/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..bcaec395a500d4fb54e93b67c206040366f75216 --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-portcullis: + build: . + image: mcp-portcullis:latest + container_name: mcp-portcullis + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=portcullis + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_portcullis/environment.yaml b/Biomni/mcp_generated/mcp_portcullis/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..81a2a55f6586b2834b82c059ad87957ddcb6f140 --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - portcullis + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_portcullis/requirements.txt b/Biomni/mcp_generated/mcp_portcullis/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_portcullis/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_primer3/Dockerfile b/Biomni/mcp_generated/mcp_primer3/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..27708a72324fb68a66e2aea6c1dc36885f2b5d43 --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install primer3 via conda (e.g., from bioconda) +RUN conda install -c bioconda primer3 -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/primer3_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/primer3_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/primer3_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_primer3/app/primer3_server.py b/Biomni/mcp_generated/mcp_primer3/app/primer3_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8c29ca043942fa61bbd90be6cd82d8f1f8e40841 --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/app/primer3_server.py @@ -0,0 +1,325 @@ +import subprocess +from typing import Optional, List, Dict, Any +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_primer3' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def primer3_design( + sequence_template: str, + sequence_id: str = "mcp_query", + primer_task: str = "generic", + pick_left: bool = True, + pick_right: bool = True, + pick_internal: bool = False, + min_size: int = 18, + opt_size: int = 20, + max_size: int = 27, + min_tm: float = 57.0, + opt_tm: float = 60.0, + max_tm: float = 63.0, + min_gc: float = 20.0, + max_gc: float = 80.0, + product_size_range: str = "150-250", + thermo_params_path: Optional[str] = None, + additional_boulder_tags: Optional[str] = None, +): + """ + Design PCR primers using primer3_core. + Uses the BoulderIO format to communicate with the primer3 engine. + + Args: + sequence_template: The DNA sequence to use as a template for primer design. + sequence_id: An identifier for the sequence. + primer_task: The task to perform (generic, pick_detection_primers, pick_cloning_primers, etc.). + pick_left: Whether to pick a left primer. + pick_right: Whether to pick a right primer. + pick_internal: Whether to pick an internal oligo (hybridization probe). + min_size: Minimum primer size. + opt_size: Optimal primer size. + max_size: Maximum primer size. + min_tm: Minimum melting temperature (Tm). + opt_tm: Optimal melting temperature (Tm). + max_tm: Maximum melting temperature (Tm). + min_gc: Minimum GC content percentage. + max_gc: Maximum GC content percentage. + product_size_range: Desired product size range (e.g., '150-250'). + thermo_params_path: Path to the directory containing thermodynamic parameters. + additional_boulder_tags: Raw BoulderIO tags (e.g., 'PRIMER_MAX_POLY_X=5') to include. + """ + # Input validation + if not sequence_template: + return {"error": "sequence_template is required"} + + # Construct BoulderIO input + boulder_input = [ + f"SEQUENCE_ID={sequence_id}", + f"SEQUENCE_TEMPLATE={sequence_template}", + f"PRIMER_TASK={primer_task}", + f"PRIMER_PICK_LEFT_PRIMER={1 if pick_left else 0}", + f"PRIMER_PICK_RIGHT_PRIMER={1 if pick_right else 0}", + f"PRIMER_PICK_INTERNAL_OLIGO={1 if pick_internal else 0}", + f"PRIMER_MIN_SIZE={min_size}", + f"PRIMER_OPT_SIZE={opt_size}", + f"PRIMER_MAX_SIZE={max_size}", + f"PRIMER_MIN_TM={min_tm}", + f"PRIMER_OPT_TM={opt_tm}", + f"PRIMER_MAX_TM={max_tm}", + f"PRIMER_MIN_GC={min_gc}", + f"PRIMER_MAX_GC={max_gc}", + f"PRIMER_PRODUCT_SIZE_RANGE={product_size_range}", + ] + + if thermo_params_path: + p = Path(thermo_params_path) + if p.exists() and p.is_dir(): + boulder_input.append(f"PRIMER_THERMODYNAMIC_PARAMETERS_PATH={thermo_params_path}") + else: + return {"error": f"Thermodynamic parameters path does not exist or is not a directory: {thermo_params_path}"} + + if additional_boulder_tags: + boulder_input.append(additional_boulder_tags) + + boulder_input.append("=") + input_str = "\n".join(boulder_input) + + try: + process = subprocess.run( + ["primer3_core"], + input=input_str.encode(), + capture_output=True, + check=True + ) + stdout = process.stdout.decode() + stderr = process.stderr.decode() + + # Parse output for errors reported inside BoulderIO + if "PRIMER_ERROR=" in stdout: + return { + "command_executed": "primer3_core", + "stdout": stdout, + "stderr": stderr, + "status": "error_in_output" + } + + return { + "command_executed": "primer3_core", + "stdout": stdout, + "stderr": stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "primer3_core", + "stdout": e.stdout.decode() if e.stdout else "", + "stderr": e.stderr.decode() if e.stderr else str(e), + "error": "Subprocess failed" + } + except FileNotFoundError: + return {"error": "primer3_core binary not found in PATH."} + +@mcp.tool() +def primer3_ntthal( + seq1: str, + seq2: Optional[str] = None, + alignment_type: str = "ANY", + mv_conc: float = 50.0, + dv_conc: float = 0.0, + dntp_conc: float = 0.0, + dna_conc: float = 50.0, + temp: float = 37.0, +): + """ + Calculate thermodynamic alignment properties using ntthal. + Useful for checking primer-dimers or hairpins. + + Args: + seq1: First DNA sequence. + seq2: Second DNA sequence (optional, required for dimer checks). + alignment_type: Type of alignment: ANY, HAIRPIN, END1, END2, DIMER. + mv_conc: Monovalent salt concentration (mM). + dv_conc: Divalent salt concentration (mM). + dntp_conc: dNTP concentration (mM). + dna_conc: DNA concentration (nM). + temp: Temperature for calculation (Celsius). + """ + cmd = ["ntthal"] + + # Validate alignment type + valid_types = ["ANY", "HAIRPIN", "END1", "END2", "DIMER"] + if alignment_type.upper() not in valid_types: + return {"error": f"Invalid alignment_type. Must be one of {valid_types}"} + + cmd.extend(["-a", alignment_type.upper()]) + cmd.extend(["-mv", str(mv_conc)]) + cmd.extend(["-dv", str(dv_conc)]) + cmd.extend(["-n", str(dntp_conc)]) + cmd.extend(["-d", str(dna_conc)]) + cmd.extend(["-t", str(temp)]) + + cmd.extend(["-s1", seq1]) + if seq2: + cmd.extend(["-s2", seq2]) + elif alignment_type.upper() not in ["HAIRPIN"]: + return {"error": "seq2 is required for alignment types other than HAIRPIN"} + + try: + process = subprocess.run(cmd, capture_output=True, check=True, text=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Subprocess failed" + } + except FileNotFoundError: + return {"error": "ntthal binary not found in PATH."} + +@mcp.tool() +def primer3_oligotm( + sequence: str, + mv_conc: float = 50.0, + dv_conc: float = 0.0, + dntp_conc: float = 0.0, + dna_conc: float = 50.0, + tp_method: int = 1, +): + """ + Calculate the melting temperature (Tm) of an oligonucleotide using oligotm. + + Args: + sequence: DNA sequence. + mv_conc: Monovalent salt concentration (mM). + dv_conc: Divalent salt concentration (mM). + dntp_conc: dNTP concentration (mM). + dna_conc: DNA concentration (nM). + tp_method: Tm calculation method (0: Breslauer et al. 1986, 1: SantaLucia 1998). + """ + if tp_method not in [0, 1]: + return {"error": "tp_method must be 0 or 1"} + + cmd = [ + "oligotm", + "-mv", str(mv_conc), + "-dv", str(dv_conc), + "-n", str(dntp_conc), + "-d", str(dna_conc), + "-tp", str(tp_method), + sequence + ] + + try: + process = subprocess.run(cmd, capture_output=True, check=True, text=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout.strip(), + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Subprocess failed" + } + except FileNotFoundError: + return {"error": "oligotm binary not found in PATH."} + +@mcp.tool() +def primer3_ntmtt( + sequence: str, + mv_conc: float = 50.0, + dv_conc: float = 0.0, + dntp_conc: float = 0.0, + dna_conc: float = 50.0, +): + """ + Calculate the melting temperature (Tm) of a DNA duplex using ntmtt. + + Args: + sequence: DNA sequence. + mv_conc: Monovalent salt concentration (mM). + dv_conc: Divalent salt concentration (mM). + dntp_conc: dNTP concentration (mM). + dna_conc: DNA concentration (nM). + """ + cmd = [ + "ntmtt", + "-mv", str(mv_conc), + "-dv", str(dv_conc), + "-n", str(dntp_conc), + "-d", str(dna_conc), + sequence + ] + + try: + process = subprocess.run(cmd, capture_output=True, check=True, text=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout.strip(), + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Subprocess failed" + } + except FileNotFoundError: + return {"error": "ntmtt binary not found in PATH."} + +@mcp.tool() +def primer3_ntot( + sequence: str, + mv_conc: float = 50.0, + dv_conc: float = 0.0, + dntp_conc: float = 0.0, + dna_conc: float = 50.0, +): + """ + Calculate thermodynamic properties of an oligonucleotide using ntot. + + Args: + sequence: DNA sequence. + mv_conc: Monovalent salt concentration (mM). + dv_conc: Divalent salt concentration (mM). + dntp_conc: dNTP concentration (mM). + dna_conc: DNA concentration (nM). + """ + cmd = [ + "ntot", + "-mv", str(mv_conc), + "-dv", str(dv_conc), + "-n", str(dntp_conc), + "-d", str(dna_conc), + sequence + ] + + try: + process = subprocess.run(cmd, capture_output=True, check=True, text=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout.strip(), + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Subprocess failed" + } + except FileNotFoundError: + return {"error": "ntot binary not found in PATH."} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_primer3/app/primer3_shim_server.py b/Biomni/mcp_generated/mcp_primer3/app/primer3_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a51241340948cbaea9750f65d2afc7b5048f4595 --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/app/primer3_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_primer3/app/primer3_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_primer3' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_primer3/app/requirements.txt b/Biomni/mcp_generated/mcp_primer3/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_primer3/docker-compose.yml b/Biomni/mcp_generated/mcp_primer3/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ac0b0d629263dcc2c2f2c7974280b251e29812c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-primer3: + build: . + image: mcp-primer3:latest + container_name: mcp-primer3 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=primer3 + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_primer3/environment.yaml b/Biomni/mcp_generated/mcp_primer3/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bfbd06d264ddfb5b06f2f2ba42888e47a3362bd --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - primer3 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_primer3/requirements.txt b/Biomni/mcp_generated/mcp_primer3/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_primer3/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_qualimap/Dockerfile b/Biomni/mcp_generated/mcp_qualimap/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6c49eba35dc945d2b70cc4b259b5bf004d7cd6ab --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install qualimap via conda (e.g., from bioconda) +RUN conda install -c bioconda qualimap -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/qualimap_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/qualimap_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/qualimap_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_qualimap/app/qualimap_server.py b/Biomni/mcp_generated/mcp_qualimap/app/qualimap_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ffb6cd4f8568039980fb1cb49779178eadc3764e --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/app/qualimap_server.py @@ -0,0 +1,362 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_qualimap' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def qualimap_bamqc( + bam_file: str, + out_dir: str, + gff_file: Optional[str] = None, + num_threads: int = 1, + java_mem_size: str = "1200M", + out_format: str = "HTML", + collect_overlap_pairs: bool = False, + hm: int = 3, + nr: int = 1000, + nw: int = 400, + skip_duplicated: bool = False, + paint_chromosome_limits: bool = True, +): + """ + Performs QC analysis of alignment data in BAM format. + + Args: + bam_file: Path to the input BAM file. + out_dir: Path to the output directory. + gff_file: Optional path to a GFF/GTF/BED file to restrict analysis to specific regions. + num_threads: Number of threads to use. + java_mem_size: Memory settings for Java (e.g., '4G', '1200M'). + out_format: Output format (HTML or PDF). + collect_overlap_pairs: If true, overlapping pairs are collected. + hm: Minimum size of homopolymer to be considered. + nr: Number of reads in the chunk to calculate the insert size. + nw: Number of windows to calculate the coverage. + skip_duplicated: If true, duplicated reads are skipped. + paint_chromosome_limits: If true, chromosome limits are painted in the coverage plot. + """ + # Input validation + bam_path = Path(bam_file) + if not bam_path.exists(): + return {"error": f"BAM file not found: {bam_file}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "bamqc", + "-bam", str(bam_path), + "-outdir", str(out_path), + "-nt", str(num_threads), + "-outformat", out_format, + "-hm", str(hm), + "-nr", str(nr), + "-nw", str(nw), + "--java-mem-size=" + java_mem_size + ] + + if gff_file: + gff_path = Path(gff_file) + if gff_path.exists(): + cmd.extend(["-gff", str(gff_path)]) + + if collect_overlap_pairs: + cmd.append("-c") + if skip_duplicated: + cmd.append("-sd") + if paint_chromosome_limits: + cmd.append("-p") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def qualimap_rnaseq( + bam_file: str, + gtf_file: str, + out_dir: str, + strand_specificity: str = "non-strand-specific", + algorithm: str = "uniquely-mapped-reads", + java_mem_size: str = "1200M", + out_format: str = "HTML", + paired: bool = False, + sorted_bam: bool = False, +): + """ + Performs QC analysis of RNA-seq data. + + Args: + bam_file: Path to the input BAM file. + gtf_file: Path to the genomic annotation file in GTF format. + out_dir: Path to the output directory. + strand_specificity: Strand specificity (non-strand-specific, strand-specific-forward, strand-specific-reverse). + algorithm: Counting algorithm (uniquely-mapped-reads or proportional). + java_mem_size: Memory settings for Java. + out_format: Output format (HTML or PDF). + paired: Set this flag if the library is paired-end. + sorted_bam: Set this flag if the BAM file is already sorted by name. + """ + bam_path = Path(bam_file) + gtf_path = Path(gtf_file) + + if not bam_path.exists(): + return {"error": f"BAM file not found: {bam_file}"} + if not gtf_path.exists(): + return {"error": f"GTF file not found: {gtf_file}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "rnaseq", + "-bam", str(bam_path), + "-gtf", str(gtf_path), + "-outdir", str(out_path), + "-p", strand_specificity, + "-a", algorithm, + "-outformat", out_format, + "--java-mem-size=" + java_mem_size + ] + + if paired: + cmd.append("-pe") + if sorted_bam: + cmd.append("-s") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def qualimap_multi_bamqc( + data_config: str, + out_dir: str, + label: Optional[str] = None, + java_mem_size: str = "1200M", + out_format: str = "HTML", +): + """ + Performs comparison of multiple BAM files. + + Args: + data_config: Path to the configuration file or a string containing the data description. + out_dir: Path to the output directory. + label: Optional labels for the samples, separated by semicolon. + java_mem_size: Memory settings for Java. + out_format: Output format (HTML or PDF). + """ + config_path = Path(data_config) + if not config_path.exists(): + return {"error": f"Config file not found: {data_config}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "multi-bamqc", + "-d", str(config_path), + "-outdir", str(out_path), + "-outformat", out_format, + "--java-mem-size=" + java_mem_size + ] + + if label: + cmd.extend(["-label", label]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def qualimap_counts( + input_file: str, + out_dir: str, + species: str = "human", + java_mem_size: str = "1200M", + out_format: str = "HTML", + count_format: str = "raw", +): + """ + Performs QC analysis of feature counts. + + Args: + input_file: Path to the input counts file. + out_dir: Path to the output directory. + species: Species name (e.g., 'human', 'mouse'). + java_mem_size: Memory settings for Java. + out_format: Output format (HTML or PDF). + count_format: Format of the counts (raw or normalized). + """ + input_path = Path(input_file) + if not input_path.exists(): + return {"error": f"Input file not found: {input_file}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "counts", + "-i", str(input_path), + "-outdir", str(out_path), + "-s", species, + "-f", count_format, + "-outformat", out_format, + "--java-mem-size=" + java_mem_size + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def qualimap_clustering( + input_file: str, + out_dir: str, + java_mem_size: str = "1200M", + num_clusters: int = 2, + out_format: str = "HTML", +): + """ + Performs clustering analysis of samples based on feature counts. + + Args: + input_file: Path to the input counts file. + out_dir: Path to the output directory. + java_mem_size: Memory settings for Java. + num_clusters: Number of clusters to find. + out_format: Output format (HTML or PDF). + """ + input_path = Path(input_file) + if not input_path.exists(): + return {"error": f"Input file not found: {input_file}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "clustering", + "-i", str(input_path), + "-outdir", str(out_path), + "-k", str(num_clusters), + "-outformat", out_format, + "--java-mem-size=" + java_mem_size + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def qualimap_comp_counts( + input_file: str, + out_dir: str, + java_mem_size: str = "1200M", + out_format: str = "HTML", +): + """ + Performs comparison of feature counts between samples. + + Args: + input_file: Path to the input counts file. + out_dir: Path to the output directory. + java_mem_size: Memory settings for Java. + out_format: Output format (HTML or PDF). + """ + input_path = Path(input_file) + if not input_path.exists(): + return {"error": f"Input file not found: {input_file}"} + + out_path = Path(out_dir) + out_path.mkdir(parents=True, exist_ok=True) + + cmd = [ + "qualimap", "comp-counts", + "-i", str(input_path), + "-outdir", str(out_path), + "-outformat", out_format, + "--java-mem-size=" + java_mem_size + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(out_path.absolute()) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_qualimap/app/requirements.txt b/Biomni/mcp_generated/mcp_qualimap/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_qualimap/docker-compose.yml b/Biomni/mcp_generated/mcp_qualimap/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e02afa295a48215e6a0ade2f63d108e23df5d335 --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-qualimap: + build: . + image: mcp-qualimap:latest + container_name: mcp-qualimap + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=qualimap + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_qualimap/environment.yaml b/Biomni/mcp_generated/mcp_qualimap/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3e691edcc119acab9a2ee2bd4e1ec54d06180d60 --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - qualimap + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_qualimap/requirements.txt b/Biomni/mcp_generated/mcp_qualimap/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-dwls/Dockerfile b/Biomni/mcp_generated/mcp_r-dwls/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1eb13f83d5194d6f88fd88fcb849dbf0e9c3c16c --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install r-dwls via conda (e.g., from bioconda) +RUN conda install -c bioconda r-dwls -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/r-dwls_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/r-dwls_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/r-dwls_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_server.py b/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4b1ff6190376ce56f5644a84aeecc7036b4f5fd5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_server.py @@ -0,0 +1,300 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any +import tempfile + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_dwls' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def dwls_build_signature_matrix_mast( + sc_counts_path: str, + cell_labels_path: str, + output_path: str, + project_name: str = "DWLS_Project", + min_pct: float = 0.1, + logfc_threshold: float = 0.25 +) -> Dict[str, Any]: + """ + Builds a signature matrix from single-cell RNA-seq data using the MAST differential expression method. + This is a prerequisite for DWLS deconvolution. + + Args: + sc_counts_path: Path to the scRNA-seq count matrix (CSV or TSV, genes as rows, cells as columns). + cell_labels_path: Path to a CSV/TSV file containing cell identifiers and their corresponding cell type labels. + output_path: Path where the resulting signature matrix (RData or CSV) will be saved. + project_name: Name of the Seurat project. + min_pct: Minimum percentage of cells expressing a gene to be considered for markers. + logfc_threshold: Log fold change threshold for marker detection. + """ + sc_path = Path(sc_counts_path) + labels_path = Path(cell_labels_path) + out_path = Path(output_path) + + if not sc_path.exists(): + return {"error": f"Single-cell counts file not found: {sc_counts_path}"} + if not labels_path.exists(): + return {"error": f"Cell labels file not found: {cell_labels_path}"} + + # R script to perform the signature matrix construction + r_script = f""" + library(DWLS) + library(Seurat) + + # Load data + sc_data <- read.table("{sc_path}", header=TRUE, row.names=1, sep=",") + labels <- read.table("{labels_path}", header=TRUE, sep=",") + + # Create Seurat object + scrna <- CreateSeuratObject(counts = sc_data, project = "{project_name}") + scrna@meta.data$cell_type <- labels[,2] # Assuming 2nd column is labels + SetAllIdent(scrna, id = "cell_type") + + # Build Signature Matrix using MAST + # Note: buildSignatureMatrixMAST is the internal DWLS function + Signature <- buildSignatureMatrixMAST(sc_data, labels[,2], "{out_path.parent}") + + write.csv(Signature, file="{out_path}") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e 'DWLS::buildSignatureMatrixMAST(...)'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def dwls_trim_data( + signature_matrix_path: str, + bulk_counts_path: str, + output_prefix: str +) -> Dict[str, Any]: + """ + Trims the signature matrix and bulk data to contain only the overlapping genes. + This ensures compatibility before running the deconvolution solvers. + + Args: + signature_matrix_path: Path to the signature matrix CSV. + bulk_counts_path: Path to the bulk RNA-seq expression matrix CSV. + output_prefix: Prefix for the trimmed output files. + """ + sig_path = Path(signature_matrix_path) + bulk_path = Path(bulk_counts_path) + + if not sig_path.exists() or not bulk_path.exists(): + return {"error": "Input files not found."} + + r_script = f""" + library(DWLS) + sig <- read.csv("{sig_path}", row.names=1) + bulk <- read.csv("{bulk_path}", row.names=1) + + # trimData is the internal DWLS function to align genes + trimmed <- trimData(sig, bulk) + + write.csv(trimmed$sig, "{output_prefix}_trimmed_sig.csv") + write.csv(trimmed$bulk, "{output_prefix}_trimmed_bulk.csv") + """ + + try: + result = subprocess.run(["Rscript", "-e", r_script], capture_output=True, text=True, check=True) + return { + "command_executed": "DWLS::trimData", + "stdout": result.stdout, + "output_files": [f"{output_prefix}_trimmed_sig.csv", f"{output_prefix}_trimmed_bulk.csv"] + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def dwls_solve_dampened_wls( + signature_matrix_path: str, + bulk_counts_path: str, + output_path: str +) -> Dict[str, Any]: + """ + Performs deconvolution using the Dampened Weighted Least Squares (DWLS) algorithm. + This is the primary method of the package. + + Args: + signature_matrix_path: Path to the (trimmed) signature matrix CSV. + bulk_counts_path: Path to the (trimmed) bulk RNA-seq matrix CSV. + output_path: Path to save the resulting cell type proportions. + """ + sig_path = Path(signature_matrix_path) + bulk_path = Path(bulk_counts_path) + + r_script = f""" + library(DWLS) + Signature <- as.matrix(read.csv("{sig_path}", row.names=1)) + Bulk <- as.matrix(read.csv("{bulk_path}", row.names=1)) + + results <- matrix(0, nrow=ncol(Bulk), ncol=ncol(Signature)) + colnames(results) <- colnames(Signature) + rownames(results) <- colnames(Bulk) + + for(i in 1:ncol(Bulk)){{ + # solveDampenedWLS is the core internal solver + results[i,] <- solveDampenedWLS(Signature, Bulk[,i]) + }} + + write.csv(results, "{output_path}") + """ + + try: + result = subprocess.run(["Rscript", "-e", r_script], capture_output=True, text=True, check=True) + return { + "command_executed": "DWLS::solveDampenedWLS", + "stdout": result.stdout, + "output_files": [output_path] + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def dwls_solve_ols( + signature_matrix_path: str, + bulk_counts_path: str, + output_path: str +) -> Dict[str, Any]: + """ + Performs deconvolution using Ordinary Least Squares (OLS). + Often used as a baseline comparison for DWLS. + + Args: + signature_matrix_path: Path to the signature matrix CSV. + bulk_counts_path: Path to the bulk RNA-seq matrix CSV. + output_path: Path to save the resulting cell type proportions. + """ + sig_path = Path(signature_matrix_path) + bulk_path = Path(bulk_counts_path) + + r_script = f""" + library(DWLS) + Signature <- as.matrix(read.csv("{sig_path}", row.names=1)) + Bulk <- as.matrix(read.csv("{bulk_path}", row.names=1)) + + results <- matrix(0, nrow=ncol(Bulk), ncol=ncol(Signature)) + colnames(results) <- colnames(Signature) + rownames(results) <- colnames(Bulk) + + for(i in 1:ncol(Bulk)){{ + # solveOLS is the internal OLS solver + results[i,] <- solveOLS(Signature, Bulk[,i]) + }} + + write.csv(results, "{output_path}") + """ + + try: + result = subprocess.run(["Rscript", "-e", r_script], capture_output=True, text=True, check=True) + return { + "command_executed": "DWLS::solveOLS", + "stdout": result.stdout, + "output_files": [output_path] + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def dwls_solve_svr( + signature_matrix_path: str, + bulk_counts_path: str, + output_path: str +) -> Dict[str, Any]: + """ + Performs deconvolution using Support Vector Regression (SVR). + Uses the e1071 package internally via DWLS. + + Args: + signature_matrix_path: Path to the signature matrix CSV. + bulk_counts_path: Path to the bulk RNA-seq matrix CSV. + output_path: Path to save the resulting cell type proportions. + """ + sig_path = Path(signature_matrix_path) + bulk_path = Path(bulk_counts_path) + + r_script = f""" + library(DWLS) + library(e1071) + Signature <- as.matrix(read.csv("{sig_path}", row.names=1)) + Bulk <- as.matrix(read.csv("{bulk_path}", row.names=1)) + + results <- matrix(0, nrow=ncol(Bulk), ncol=ncol(Signature)) + colnames(results) <- colnames(Signature) + rownames(results) <- colnames(Bulk) + + for(i in 1:ncol(Bulk)){{ + # solveSVR is the internal SVR solver + results[i,] <- solveSVR(Signature, Bulk[,i]) + }} + + write.csv(results, "{output_path}") + """ + + try: + result = subprocess.run(["Rscript", "-e", r_script], capture_output=True, text=True, check=True) + return { + "command_executed": "DWLS::solveSVR", + "stdout": result.stdout, + "output_files": [output_path] + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def dwls_deanalysis_mast( + sc_counts_path: str, + cell_labels_path: str, + output_path: str +) -> Dict[str, Any]: + """ + Performs Differential Expression (DE) analysis using MAST as implemented in the DWLS package. + This is an internal step used to identify marker genes for cell types. + + Args: + sc_counts_path: Path to the scRNA-seq count matrix. + cell_labels_path: Path to cell labels. + output_path: Path to save the DE results (RData). + """ + sc_path = Path(sc_counts_path) + labels_path = Path(cell_labels_path) + + r_script = f""" + library(DWLS) + sc_data <- read.table("{sc_path}", header=TRUE, row.names=1, sep=",") + labels <- read.table("{labels_path}", header=TRUE, sep=",") + + # DEAnalysisMast is the internal DWLS function + de_results <- DEAnalysisMast(sc_data, labels[,2]) + save(de_results, file="{output_path}") + """ + + try: + result = subprocess.run(["Rscript", "-e", r_script], capture_output=True, text=True, check=True) + return { + "command_executed": "DWLS::DEAnalysisMast", + "stdout": result.stdout, + "output_files": [output_path] + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_shim_server.py b/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8bc75393c791c92b9e91dfe9e1e07bfe4f1182e0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/app/r-dwls_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-dwls/app/r-dwls_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_r_dwls' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-dwls/app/requirements.txt b/Biomni/mcp_generated/mcp_r-dwls/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_r-dwls/docker-compose.yml b/Biomni/mcp_generated/mcp_r-dwls/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..199557aa38b4c6a96b6be1e61d43067496e3fa25 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-dwls: + build: . + image: mcp-r-dwls:latest + container_name: mcp-r-dwls + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-dwls + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-dwls/environment.yaml b/Biomni/mcp_generated/mcp_r-dwls/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..35bf1ce28d16ccc1ef5ac334ff6372541af71edf --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - r-dwls + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-dwls/requirements.txt b/Biomni/mcp_generated/mcp_r-dwls/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-dwls/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-epitrace/Dockerfile b/Biomni/mcp_generated/mcp_r-epitrace/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..faed9d050d83c06634e1015b3109b22e63df0b89 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install r-epitrace via conda (e.g., from bioconda) +RUN conda install -c bioconda r-epitrace -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/r-epitrace_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/r-epitrace_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/r-epitrace_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_shim_server.py b/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..66bb26d94349980e0aedf2468d1d9d7a24200ccf --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-epitrace/app/r-epitrace_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_r_epitrace' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-epitrace/app/requirements.txt b/Biomni/mcp_generated/mcp_r-epitrace/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_r-epitrace/docker-compose.yml b/Biomni/mcp_generated/mcp_r-epitrace/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..d54b35fbcdc32e7fa959f2bbe7c6551ffffb5e8f --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-epitrace: + build: . + image: mcp-r-epitrace:latest + container_name: mcp-r-epitrace + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-epitrace + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-epitrace/environment.yaml b/Biomni/mcp_generated/mcp_r-epitrace/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..305add1d2b2ff634c1e9c93550b3a8a87e4c824c --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - r-epitrace + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-epitrace/requirements.txt b/Biomni/mcp_generated/mcp_r-epitrace/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-saige/requirements.txt b/Biomni/mcp_generated/mcp_r-saige/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-saige/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-scpred/Dockerfile b/Biomni/mcp_generated/mcp_r-scpred/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..130cc046b9b4ba419e8625b3dbab709542e2bd0a --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install r-scpred via conda (e.g., from bioconda) +RUN conda install -c bioconda r-scpred -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY r-scpred_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/r-scpred_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/r-scpred_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_server.py b/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0341b2b7e69f84ebb86eb95ce78760571594a92f --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_server.py @@ -0,0 +1,298 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List + +# MCP decorator stub. In a real environment, this would be imported. +class mcp: + @staticmethod + def tool(): + def decorator(f): + return f + return decorator + +# Set up logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_scpred' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def scpred_train( + train_data: Path, + cell_type_col: str, + output_model: Path, + n_features: int = 30, + model: str = "svmRadial", + allow_parallel: bool = False, + seed: Optional[int] = None, +) -> dict: + """ + Trains an scpred cell type classification model. + + This function wraps the training process of scpred, which involves feature selection + from principal components and training a specified classification model. + + Args: + train_data: Path to the training data (e.g., a Seurat object in .rds format). + cell_type_col: The name of the metadata column containing the cell type labels. + output_model: Path to save the trained scpred model object (.rds format). + n_features: The number of principal components to use for feature selection. + model: The classification model to use (e.g., 'svmRadial', 'glmnet', 'ranger'). + allow_parallel: If True, enables parallel processing for model training. + seed: An optional integer for setting the random seed for reproducibility. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file path. + """ + # --- Input Validation --- + if not train_data.exists(): + raise FileNotFoundError(f"Training data not found at: {train_data}") + if n_features <= 0: + raise ValueError("n_features must be a positive integer.") + if not cell_type_col: + raise ValueError("cell_type_col must be a non-empty string.") + + # Ensure output directory exists + output_model.parent.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + # Based on a hypothetical scpred-cli.R script that wraps the R package functions. + cmd = [ + "Rscript", "scpred-cli.R", "train", + "--train-data", str(train_data), + "--cell-type-col", cell_type_col, + "--output-model", str(output_model), + "--n-features", str(n_features), + "--model", model, + ] + + if allow_parallel: + cmd.append("--allow-parallel") + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + stdout = result.stdout + stderr = result.stderr + output_files = [str(output_model)] if output_model.exists() else [] + + except FileNotFoundError: + logger.error("Rscript or scpred-cli.R not found. Ensure R and the scpred wrapper script are in your PATH.") + raise + except subprocess.CalledProcessError as e: + logger.error(f"scpred training failed with exit code {e.returncode}") + logger.error(f"STDOUT: {e.stdout}") + logger.error(f"STDERR: {e.stderr}") + raise e + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +@mcp.tool() +def scpred_predict( + model_file: Path, + test_data: Path, + output_rds: Path, + threshold: float = 0.9, + output_csv: Optional[Path] = None, +) -> dict: + """ + Predicts cell types in a new dataset using a trained scpred model. + + This function applies a pre-trained scpred model to a new single-cell dataset + to classify each cell. + + Args: + model_file: Path to the trained scpred model object (.rds format). + test_data: Path to the test data to be classified (e.g., a Seurat object in .rds format). + output_rds: Path to save the test data object with prediction results added (.rds format). + threshold: The minimum probability score required to assign a cell to a class. + output_csv: Optional path to save a CSV file with cell predictions. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file paths. + """ + # --- Input Validation --- + if not model_file.exists(): + raise FileNotFoundError(f"Model file not found at: {model_file}") + if not test_data.exists(): + raise FileNotFoundError(f"Test data not found at: {test_data}") + if not (0.0 <= threshold <= 1.0): + raise ValueError("threshold must be between 0.0 and 1.0.") + + # Ensure output directories exist + output_rds.parent.mkdir(parents=True, exist_ok=True) + if output_csv: + output_csv.parent.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "Rscript", "scpred-cli.R", "predict", + "--model-file", str(model_file), + "--test-data", str(test_data), + "--output-rds", str(output_rds), + "--threshold", str(threshold), + ] + + if output_csv: + cmd.extend(["--output-csv", str(output_csv)]) + + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + stdout = result.stdout + stderr = result.stderr + + output_files = [] + if output_rds.exists(): + output_files.append(str(output_rds)) + if output_csv and output_csv.exists(): + output_files.append(str(output_csv)) + + except FileNotFoundError: + logger.error("Rscript or scpred-cli.R not found. Ensure R and the scpred wrapper script are in your PATH.") + raise + except subprocess.CalledProcessError as e: + logger.error(f"scpred prediction failed with exit code {e.returncode}") + logger.error(f"STDOUT: {e.stdout}") + logger.error(f"STDERR: {e.stderr}") + raise e + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +@mcp.tool() +def scpred_cv( + train_data: Path, + cell_type_col: str, + output_prefix: str, + n_folds: int = 10, + n_repeats: int = 10, + n_features: int = 30, + model: str = "svmRadial", + allow_parallel: bool = False, + seed: Optional[int] = None, +) -> dict: + """ + Performs cross-validation for an scpred model on a training dataset. + + This function evaluates model performance using k-fold cross-validation, which can help + in tuning parameters like the number of features. + + Args: + train_data: Path to the training data (e.g., a Seurat object in .rds format). + cell_type_col: The name of the metadata column containing the cell type labels. + output_prefix: A prefix for output files (e.g., plots, summary tables). + n_folds: The number of folds for cross-validation. + n_repeats: The number of times to repeat the cross-validation. + n_features: The number of principal components to use for feature selection. + model: The classification model to use (e.g., 'svmRadial', 'glmnet', 'ranger'). + allow_parallel: If True, enables parallel processing. + seed: An optional integer for setting the random seed for reproducibility. + + Returns: + A dictionary containing the execution command, stdout, and stderr. + Output files are not explicitly listed as they depend on the script's implementation. + """ + # --- Input Validation --- + if not train_data.exists(): + raise FileNotFoundError(f"Training data not found at: {train_data}") + if n_folds <= 1: + raise ValueError("n_folds must be greater than 1.") + if n_repeats <= 0: + raise ValueError("n_repeats must be a positive integer.") + if n_features <= 0: + raise ValueError("n_features must be a positive integer.") + if not cell_type_col: + raise ValueError("cell_type_col must be a non-empty string.") + if not output_prefix: + raise ValueError("output_prefix must be a non-empty string.") + + # --- Command Construction --- + cmd = [ + "Rscript", "scpred-cli.R", "cv", + "--train-data", str(train_data), + "--cell-type-col", cell_type_col, + "--output-prefix", output_prefix, + "--n-folds", str(n_folds), + "--n-repeats", str(n_repeats), + "--n-features", str(n_features), + "--model", model, + ] + + if allow_parallel: + cmd.append("--allow-parallel") + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + stdout = result.stdout + stderr = result.stderr + # Note: The exact output files are unknown and depend on the R script. + # The user should know what files are generated based on the output_prefix. + output_files = [] + + except FileNotFoundError: + logger.error("Rscript or scpred-cli.R not found. Ensure R and the scpred wrapper script are in your PATH.") + raise + except subprocess.CalledProcessError as e: + logger.error(f"scpred cross-validation failed with exit code {e.returncode}") + logger.error(f"STDOUT: {e.stdout}") + logger.error(f"STDERR: {e.stderr}") + raise e + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_shim_server.py b/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..143e4c1ab7a49ee09f9a240c61d1a7832750bcbd --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/app/r-scpred_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_r-scpred/app/r-scpred_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_r_scpred' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-scpred/docker-compose.yml b/Biomni/mcp_generated/mcp_r-scpred/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ddf0527d367f6c800597d2fb4e6dde82c668d966 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-scpred: + build: . + image: mcp-r-scpred:latest + container_name: mcp-r-scpred + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-scpred + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-scpred/environment.yaml b/Biomni/mcp_generated/mcp_r-scpred/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b6eb1b829c2a4959b7abb8f5cf0548248ffcfc6c --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - r-scpred + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-scpred/requirements.txt b/Biomni/mcp_generated/mcp_r-scpred/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-scpred/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-seurat-scripts/app/r-seurat-scripts_server.py b/Biomni/mcp_generated/mcp_r-seurat-scripts/app/r-seurat-scripts_server.py new file mode 100644 index 0000000000000000000000000000000000000000..660c9704606418e32d882defe4878048d7eb6dd8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-scripts/app/r-seurat-scripts_server.py @@ -0,0 +1,139 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List, Dict + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_seurat_scripts' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def rscript_run( + script_file: Optional[Path] = None, + expressions: Optional[List[str]] = None, + script_args: Optional[List[str]] = None, + verbose: bool = False, + default_packages: Optional[str] = None, + save: bool = False, + no_environ: bool = False, + no_site_file: bool = False, + no_init_file: bool = False, + restore: bool = False, + vanilla: bool = False, +) -> Dict: + """ + Executes an R script or R expressions using the Rscript command-line tool. + + This tool serves as a wrapper for Rscript, commonly used for running bioinformatics + scripts, such as those using the Seurat package. You must provide either a script + file to execute or a list of R expressions. + + Args: + script_file (Optional[Path]): Path to the R script file to be executed. Mutually exclusive with 'expressions'. + expressions (Optional[List[str]]): A list of R expressions to execute using the '-e' flag. Mutually exclusive with 'script_file'. + script_args (Optional[List[str]]): A list of arguments to be passed to the R script itself. + verbose (bool): If True, print information on progress (--verbose). Defaults to False. + default_packages (Optional[str]): A comma-separated list of package names to be loaded by default (--default-packages). + save (bool): If True, save the workspace at the end of the session (--save). Ignored if 'vanilla' is True. Defaults to False. + no_environ (bool): If True, do not read the site and user environment files (--no-environ). Ignored if 'vanilla' is True. Defaults to False. + no_site_file (bool): If True, do not read the site-wide Rprofile (--no-site-file). Ignored if 'vanilla' is True. Defaults to False. + no_init_file (bool): If True, do not read the user R profile (--no-init-file). Ignored if 'vanilla' is True. Defaults to False. + restore (bool): If True, restore previously saved objects at startup (--restore). Ignored if 'vanilla' is True. Defaults to False. + vanilla (bool): If True, combines --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ. Defaults to False. + + Returns: + Dict: A dictionary containing the executed command, stdout, stderr, and a list of potential output files. + """ + # 1. Input Validation + if script_file and expressions: + raise ValueError("Cannot provide both 'script_file' and 'expressions'. They are mutually exclusive.") + if not script_file and not expressions: + raise ValueError("Must provide either 'script_file' or 'expressions' to execute.") + + if script_file: + if not script_file.is_file(): + raise FileNotFoundError(f"The specified script file does not exist: {script_file}") + + # 2. Command Construction + command = ["Rscript"] + + if vanilla: + command.append("--vanilla") + else: + # These options are combined in --vanilla, so we only process them if vanilla is false. + if save: + command.append("--save") + if no_environ: + command.append("--no-environ") + if no_site_file: + command.append("--no-site-file") + if no_init_file: + command.append("--no-init-file") + if restore: + command.append("--restore") + + if verbose: + command.append("--verbose") + + if default_packages: + command.append(f"--default-packages={default_packages}") + + if expressions: + for expr in expressions: + command.extend(["-e", expr]) + elif script_file: + command.append(str(script_file)) + + if script_args: + command.extend(script_args) + + # 3. Subprocess Execution + command_str = " ".join(command) + logging.info(f"Executing command: {command_str}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + logging.info("Rscript execution completed successfully.") + + except FileNotFoundError: + error_msg = "Rscript command not found. Please ensure R is installed and in your system's PATH." + logging.error(error_msg) + return { + "command_executed": command_str, + "stdout": "", + "stderr": error_msg, + "output_files": [] + } + except subprocess.CalledProcessError as e: + logging.error(f"Rscript execution failed with exit code {e.returncode}.") + logging.error(f"Stderr: {e.stderr}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # 4. Structured Result Return + # Rscript itself doesn't define output files; the script it runs does. + # We return an empty list, as we can't reliably determine what files the script might create. + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-seurat-scripts/docker-compose.yml b/Biomni/mcp_generated/mcp_r-seurat-scripts/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c7e41432b1df433fcc2bdb0aa9cff82423355741 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-scripts/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-seurat-scripts: + build: . + image: mcp-r-seurat-scripts:latest + container_name: mcp-r-seurat-scripts + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-seurat-scripts + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sambamba/app/sambamba_server.py b/Biomni/mcp_generated/mcp_sambamba/app/sambamba_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d89a97c6fdd7c64cce4fba510eadae4af7325f41 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sambamba/app/sambamba_server.py @@ -0,0 +1,565 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Literal, Union +import tempfile + +# Helper function to run sambamba commands +def _run_sambamba_command( + command: List[str], + output_file: Optional[Path] = None, + index_file: Optional[Path] = None, # For commands that might generate an index +) -> dict: + """ + Helper function to execute sambamba commands and handle output. + """ + output_files_generated: List[str] = [] + stdout_content: str = "" + stderr_content: str = "" + command_str = " ".join(str(arg) for arg in command) + + try: + if output_file: + # Ensure parent directory exists for output_file + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + process = subprocess.run( + command, + stdout=f, + stderr=subprocess.PIPE, + check=True, + text=True, + encoding="utf-8" + ) + output_files_generated.append(str(output_file)) + stderr_content = process.stderr + else: + process = subprocess.run( + command, + capture_output=True, + check=True, + text=True, + encoding="utf-8" + ) + stdout_content = process.stdout + stderr_content = process.stderr + + if index_file: + output_files_generated.append(str(index_file)) + + return { + "command_executed": command_str, + "stdout": stdout_content, + "stderr": stderr_content, + "output_files": output_files_generated, + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: sambamba executable not found. Please ensure it is installed and in your PATH.", + "error": "sambamba executable not found", + "output_files": [], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"sambamba command failed with exit code {e.returncode}", + "output_files": [], + } + except Exception as e: + return { + "command_executed": command_str, + "stdout": stdout_content, + "stderr": stderr_content, + "error": f"An unexpected error occurred: {str(e)}", + "output_files": [], + } + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_sambamba' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def view( + input_file: Path, + regions: Optional[List[str]] = None, + filter_expression: Optional[str] = None, + output_format: Literal["sam", "bam", "json", "msgpack"] = "sam", + with_header: bool = False, + only_header: bool = False, + reference_info: bool = False, + count_records: bool = False, + valid_reads_only: bool = False, + sam_input: bool = False, + show_progress: bool = False, + compression_level: Optional[int] = None, + output_filename: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Extracts information from SAM/BAM files, filters alignments, and converts formats. + + Args: + input_file: Path to the input SAM or BAM file. + regions: Optional list of regions to extract (e.g., "chr1:100-200"). + Requires an indexed BAM file. + filter_expression: Custom filter for alignments using sambamba's filter language. + output_format: Specify output format (sam, bam, json, or msgpack). Default is SAM. + with_header: Print SAM header before reads. Always done for BAM output. + only_header: Print only SAM header to STDOUT. Mutually exclusive with with_header, + reference_info, and count_records. + reference_info: Output reference sequence names and lengths in JSON to STDOUT. + Mutually exclusive with with_header, only_header, and count_records. + count_records: Output only the number of matching records to STDOUT. + Mutually exclusive with with_header, only_header, and reference_info. + valid_reads_only: Output only valid reads. + sam_input: Specify that the input is a SAM file (default is BAM). + show_progress: Show a progress bar in STDERR. Works only for BAM files + and when reading the full file (no regions specified). + compression_level: Set compression level for BAM output, a number from 0 to 9. + output_filename: Specify output filename. If not provided, output goes to STDOUT. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + if compression_level is not None and not (0 <= compression_level <= 9): + raise ValueError("Compression level must be an integer between 0 and 9.") + + exclusive_options = [with_header, only_header, reference_info, count_records] + if sum(1 for opt in exclusive_options if opt) > 1: + raise ValueError( + "Options --with-header, --only-header, --reference-info, and --count " + "are mutually exclusive. Please specify at most one." + ) + + command = ["sambamba", "view"] + + if quiet: + command.append("-q") + if filter_expression: + command.extend(["-F", filter_expression]) + if output_format: + command.extend(["-f", output_format]) + if with_header: + command.append("-h") + if only_header: + command.append("-H") + if reference_info: + command.append("-I") + if count_records: + command.append("-c") + if valid_reads_only: + command.append("-v") + if sam_input: + command.append("-S") + if show_progress: + command.append("-p") + if compression_level is not None: + command.extend(["-l", str(compression_level)]) + if output_filename: + command.extend(["-o", str(output_filename)]) + + command.extend(["-t", str(nthreads)]) + command.append(str(input_file)) + + if regions: + command.extend(regions) + + return _run_sambamba_command(command, output_file=output_filename) + + +@mcp.tool() +def index( + input_bam: Path, + output_bai: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Builds a BAI index for a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + output_bai: Optional path for the output BAI index file. If not specified, + sambamba will create '{input_bam}.bai'. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + + command = ["sambamba", "index"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + + actual_output_bai = output_bai if output_bai else input_bam.with_suffix(".bam.bai") + command.append(str(actual_output_bai)) + + return _run_sambamba_command(command, index_file=actual_output_bai) + + +@mcp.tool() +def merge( + output_bam: Path, + input_bams: List[Path], + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Merges multiple BAM files into a single BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + output_bam: Path for the merged output BAM file. + input_bams: List of paths to input BAM files to be merged. At least one is required. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bams: + raise ValueError("At least one input BAM file must be provided for merging.") + for bam_file in input_bams: + if not bam_file.exists(): + raise FileNotFoundError(f"Input BAM file not found: {bam_file}") + if not bam_file.suffix == ".bam": + raise ValueError(f"Input file '{bam_file}' does not have a .bam extension.") + if not output_bam.suffix == ".bam": + raise ValueError(f"Output file '{output_bam}' does not have a .bam extension.") + + command = ["sambamba", "merge"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(output_bam)) + command.extend([str(f) for f in input_bams]) + + return _run_sambamba_command(command, output_file=output_bam) + + +@mcp.tool() +def sort( + input_bam: Path, + output_bam: Optional[Path] = None, + nthreads: int = 1, + memory_limit: Optional[str] = None, + tmp_dir: Optional[Path] = None, + quiet: bool = False, +) -> dict: + """ + Sorts a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + output_bam: Optional path for the sorted output BAM file. If not specified, + output goes to STDOUT. + nthreads: Number of threads to use. + memory_limit: Memory limit for sorting, e.g., "2G" or "500M". + tmp_dir: Optional path to a directory for temporary files during sorting. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + if output_bam and not output_bam.suffix == ".bam": + raise ValueError(f"Output file '{output_bam}' does not have a .bam extension.") + if tmp_dir and not tmp_dir.is_dir(): + raise NotADirectoryError(f"Temporary directory not found or is not a directory: {tmp_dir}") + + command = ["sambamba", "sort"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + if memory_limit: + command.extend(["-m", memory_limit]) + if tmp_dir: + command.extend(["--tmpdir", str(tmp_dir)]) + if output_bam: + command.extend(["-o", str(output_bam)]) + + command.append(str(input_bam)) + + return _run_sambamba_command(command, output_file=output_bam) + + +@mcp.tool() +def slice_bam( + input_bam: Path, + bed_file: Path, + output_bam: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Extracts a region from a BAM file using a BED file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. Must be indexed. + bed_file: Path to the BED file specifying regions to extract. + output_bam: Optional path for the sliced output BAM file. If not specified, + output goes to STDOUT. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + if not bed_file.exists(): + raise FileNotFoundError(f"BED file not found: {bed_file}") + if output_bam and not output_bam.suffix == ".bam": + raise ValueError(f"Output file '{output_bam}' does not have a .bam extension.") + + command = ["sambamba", "slice"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + command.append(str(bed_file)) + + if output_bam: + command.append(str(output_bam)) + + return _run_sambamba_command(command, output_file=output_bam) + + +@mcp.tool() +def markdup( + input_bam: Path, + output_bam: Path, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Marks or removes duplicate reads in a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + output_bam: Path for the output BAM file with duplicates marked/removed. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + if not output_bam.suffix == ".bam": + raise ValueError(f"Output file '{output_bam}' does not have a .bam extension.") + + command = ["sambamba", "markdup"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + command.append(str(output_bam)) + + return _run_sambamba_command(command, output_file=output_bam) + + +@mcp.tool() +def subsample( + input_bam: Path, + fraction: float, + output_bam: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Subsamples a BAM file by a given fraction. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + fraction: The fraction of reads to subsample (e.g., 0.1 for 10%). Must be > 0 and <= 1. + output_bam: Optional path for the subsampled output BAM file. If not specified, + output goes to STDOUT. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + if not (0 < fraction <= 1): + raise ValueError("Fraction must be a float greater than 0 and less than or equal to 1.") + if output_bam and not output_bam.suffix == ".bam": + raise ValueError(f"Output file '{output_bam}' does not have a .bam extension.") + + command = ["sambamba", "subsample"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + command.append(str(fraction)) + + if output_bam: + command.append(str(output_bam)) + + return _run_sambamba_command(command, output_file=output_bam) + + +@mcp.tool() +def flagstat( + input_bam: Path, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Outputs statistics (flagstat) for a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. Output is always to STDOUT. + + Args: + input_bam: Path to the input BAM file. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + + command = ["sambamba", "flagstat"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + + return _run_sambamba_command(command) + + +@mcp.tool() +def depth( + input_bam: Path, + output_file: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Outputs coverage statistics (depth) for a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + output_file: Optional path for the output depth statistics file. If not specified, + output goes to STDOUT. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + + command = ["sambamba", "depth"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + + return _run_sambamba_command(command, output_file=output_file) + + +@mcp.tool() +def validate( + input_bam: Path, + quiet: bool = False, +) -> dict: + """ + Performs a simple validation check on a BAM file. + + Note: Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. Output is always to STDOUT. + + Args: + input_bam: Path to the input BAM file. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + + command = ["sambamba", "validate"] + if quiet: + command.append("-q") + + command.append(str(input_bam)) + + return _run_sambamba_command(command) + + +@mcp.tool() +def mpileup( + input_bam: Path, + output_file: Optional[Path] = None, + nthreads: int = 1, + quiet: bool = False, +) -> dict: + """ + Performs parallel execution of samtools mpileup. + + Note: This command is no longer recommended by sambamba. + Detailed options for this subcommand were not provided in the documentation, + so only common parameters are exposed. + + Args: + input_bam: Path to the input BAM file. + output_file: Optional path for the output mpileup file. If not specified, + output goes to STDOUT. + nthreads: Number of threads to use. + quiet: Suppress the sambamba banner. + """ + if not input_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {input_bam}") + if not input_bam.suffix == ".bam": + raise ValueError(f"Input file '{input_bam}' does not have a .bam extension.") + + command = ["sambamba", "mpileup"] + if quiet: + command.append("-q") + + command.extend(["-t", str(nthreads)]) + command.append(str(input_bam)) + + return _run_sambamba_command(command, output_file=output_file) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_sccellfie/app/sccellfie_server.py b/Biomni/mcp_generated/mcp_sccellfie/app/sccellfie_server.py new file mode 100644 index 0000000000000000000000000000000000000000..dff604c6a67fb649286cd0721888337a4c82e40f --- /dev/null +++ b/Biomni/mcp_generated/mcp_sccellfie/app/sccellfie_server.py @@ -0,0 +1,228 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_sccellfie' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_sccellfie_pipeline( + input_path: str, + organism: str = "human", + output_dir: str = "./sccellfie_output", + database: str = "recon3d", + n_procs: int = 1, + threshold: float = 0.1, + alpha: float = 0.5, + min_max_norm: bool = True, + attribute_column: Optional[str] = None, + chunk_size: int = 1000 +) -> Dict[str, Any]: + """ + Runs the full scCellFie pipeline to estimate metabolic fluxes from single-cell RNA-seq data. + + Args: + input_path: Path to the input AnnData (.h5ad) file. + organism: Organism type, either 'human' or 'mouse'. + output_dir: Directory where output files and plots will be saved. + database: Metabolic database to use (e.g., 'recon3d' for human, 'mm10' for mouse). + n_procs: Number of processors to use for parallel computation. + threshold: Expression threshold for gene activity. + alpha: Weighting parameter for the scoring algorithm (0 to 1). + min_max_norm: Whether to apply min-max normalization to the scores. + attribute_column: Column in obs metadata to group cells (optional). + chunk_size: Number of cells to process per chunk to manage memory. + """ + # Input validation + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + if organism.lower() not in ["human", "mouse"]: + return {"error": "Organism must be either 'human' or 'mouse'"} + + if n_procs < 1: + n_procs = 1 + + if not 0 <= alpha <= 1: + return {"error": "Alpha must be between 0 and 1"} + + # Prepare output directory + out_path = Path(output_dir) + out_path.mkdir(parents=True, exist_ok=True) + + # Construct command + cmd = [ + "sccellfie", + "--input", str(input_file), + "--organism", organism, + "--output", str(out_path), + "--database", database, + "--n_procs", str(n_procs), + "--threshold", str(threshold), + "--alpha", str(alpha), + "--chunk_size", str(chunk_size) + ] + + if min_max_norm: + cmd.append("--min_max_norm") + + if attribute_column: + cmd.extend(["--attribute", attribute_column]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Collect list of generated files + output_files = [str(p) for p in out_path.glob("*") if p.is_file()] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"CLI execution failed with return code {e.returncode}", + "status": "error" + } + +@mcp.tool() +def sccellfie_gene_to_rxn( + input_path: str, + organism: str = "human", + database: str = "recon3d", + output_path: Optional[str] = None +) -> Dict[str, Any]: + """ + Maps gene expression data to metabolic reactions using GPR (Gene-Protein-Reaction) rules. + + Args: + input_path: Path to the input AnnData (.h5ad) file. + organism: 'human' or 'mouse'. + database: Metabolic database to use. + output_path: Path to save the resulting reaction-level AnnData. + """ + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + cmd = [ + "sccellfie-map", + "--input", str(input_file), + "--organism", organism, + "--database", database + ] + + if output_path: + cmd.extend(["--output", output_path]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "status": "error" + } + +@mcp.tool() +def sccellfie_score_pathways( + input_path: str, + organism: str = "human", + database: str = "recon3d", + output_path: Optional[str] = None +) -> Dict[str, Any]: + """ + Aggregates reaction scores into metabolic pathway scores. + + Args: + input_path: Path to the reaction-level AnnData file. + organism: 'human' or 'mouse'. + database: Metabolic database to use. + output_path: Path to save the resulting pathway-level AnnData. + """ + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + cmd = [ + "sccellfie-pathways", + "--input", str(input_file), + "--organism", organism, + "--database", database + ] + + if output_path: + cmd.extend(["--output", output_path]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "status": "error" + } + +@mcp.tool() +def sccellfie_inspect_database( + organism: str = "human", + database: str = "recon3d" +) -> Dict[str, Any]: + """ + Provides information about the metabolic database being used, including number of reactions and genes. + + Args: + organism: 'human' or 'mouse'. + database: Metabolic database name. + """ + cmd = [ + "sccellfie-db", + "--organism", organism, + "--database", database, + "--info" + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "status": "error" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_sepp/Dockerfile b/Biomni/mcp_generated/mcp_sepp/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9edce5e2b8e46b24c38acbb7924ddff141089f0a --- /dev/null +++ b/Biomni/mcp_generated/mcp_sepp/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install sepp via conda (e.g., from bioconda) +RUN conda install -c bioconda sepp -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/sepp_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/sepp_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/sepp_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sepp/app/sepp_server.py b/Biomni/mcp_generated/mcp_sepp/app/sepp_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7862f7d9e4b987582ae9f2fa834bb4e56137a697 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sepp/app/sepp_server.py @@ -0,0 +1,329 @@ +import subprocess +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Helper function to run shell commands +def _run_command(cmd: List[str], cwd: Optional[Path] = None) -> Dict[str, Any]: + """ + Executes a shell command and captures its output. + + Args: + cmd: A list of strings representing the command and its arguments. + cwd: The current working directory for the command. + + Returns: + A dictionary containing the command executed, stdout, stderr, + and any error information. + """ + try: + process = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [], # To be populated by specific tool functions + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with exit code {e.returncode}: {e.stderr}", + "returncode": e.returncode, + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": f"Error: The executable '{cmd[0]}' was not found. " + "Ensure it is installed and accessible in your system's PATH.", + "error": "Executable not found", + "output_files": [], + } + except Exception as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": f"An unexpected error occurred: {str(e)}", + "error": "Unexpected error", + "output_files": [], + } + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_sepp' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def sepp_placement( + reference_tree: Path, + reference_alignment: Path, + query_file: Path, + output_dir: Path, + num_cpus: Optional[int] = None, + num_hmm_profiles: Optional[int] = None, + algorithm: Optional[str] = None, + dendropy_version: Optional[str] = None, + seed_file: Optional[Path] = None, + starting_tree_file: Optional[Path] = None, + upp_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + pplacer_path: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Performs SATe-enabled Phylogenetic Placement (SEPP) of query sequences into + a reference alignment and tree. + + Args: + reference_tree: Path to the reference tree file (Newick format). + reference_alignment: Path to the reference alignment file (FASTA format). + query_file: Path to the query sequences file (FASTA format). + output_dir: Directory to store all output files. Will be created if it doesn't exist. + num_cpus: Number of CPUs to use. If None, SEPP will use its internal default. + Must be a positive integer. + num_hmm_profiles: Number of HMM profiles to use. If None, SEPP will use its internal default. + Must be a positive integer. + algorithm: Specific algorithm to use within SEPP. If None, SEPP will use its internal default. + dendropy_version: Specify Dendropy version for compatibility. + seed_file: Path to an optional seed alignment file. + starting_tree_file: Path to an optional starting tree file. + upp_path: Path to the UPP executable if not in PATH. + hmmer_path: Path to the HMMER executable (e.g., hmmbuild) if not in PATH. + The tool expects other HMMER tools (hmmalign, hmmsearch) to be + in the same directory or system PATH. + pplacer_path: Path to the PPlacer executable if not in PATH. + verbose: If True, enable verbose output. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # Input validation + if not isinstance(reference_tree, Path) or not reference_tree.is_file(): + raise ValueError(f"Reference tree file not found or invalid: {reference_tree}") + if not isinstance(reference_alignment, Path) or not reference_alignment.is_file(): + raise ValueError(f"Reference alignment file not found or invalid: {reference_alignment}") + if not isinstance(query_file, Path) or not query_file.is_file(): + raise ValueError(f"Query sequences file not found or invalid: {query_file}") + if not isinstance(output_dir, Path): + raise ValueError(f"Output directory path is invalid: {output_dir}") + + output_dir.mkdir(parents=True, exist_ok=True) + + if num_cpus is not None: + if not isinstance(num_cpus, int) or num_cpus <= 0: + raise ValueError("Number of CPUs must be a positive integer.") + if num_hmm_profiles is not None: + if not isinstance(num_hmm_profiles, int) or num_hmm_profiles <= 0: + raise ValueError("Number of HMM profiles must be a positive integer.") + + if seed_file is not None and (not isinstance(seed_file, Path) or not seed_file.is_file()): + raise ValueError(f"Seed file not found or invalid: {seed_file}") + if starting_tree_file is not None and (not isinstance(starting_tree_file, Path) or not starting_tree_file.is_file()): + raise ValueError(f"Starting tree file not found or invalid: {starting_tree_file}") + if upp_path is not None and (not isinstance(upp_path, Path) or not upp_path.is_file()): + raise ValueError(f"UPP executable not found or invalid: {upp_path}") + if hmmer_path is not None and (not isinstance(hmmer_path, Path) or not hmmer_path.is_file()): + raise ValueError(f"HMMER executable not found or invalid: {hmmer_path}") + if pplacer_path is not None and (not isinstance(pplacer_path, Path) or not pplacer_path.is_file()): + raise ValueError(f"PPlacer executable not found or invalid: {pplacer_path}") + + cmd = ["python", "run_sepp.py"] + cmd.extend(["-t", str(reference_tree)]) + cmd.extend(["-a", str(reference_alignment)]) + cmd.extend(["-f", str(query_file)]) + cmd.extend(["-o", str(output_dir)]) + + if num_cpus is not None: + cmd.extend(["-p", str(num_cpus)]) + if num_hmm_profiles is not None: + cmd.extend(["-x", str(num_hmm_profiles)]) + if algorithm is not None: + cmd.extend(["-A", algorithm]) + if dendropy_version is not None: + cmd.extend(["-D", dendropy_version]) + if seed_file is not None: + cmd.extend(["-r", str(seed_file)]) + if starting_tree_file is not None: + cmd.extend(["-s", str(starting_tree_file)]) + if upp_path is not None: + cmd.extend(["-u", str(upp_path)]) + if hmmer_path is not None: + cmd.extend(["-H", str(hmmer_path)]) + if pplacer_path is not None: + cmd.extend(["-P", str(pplacer_path)]) + if verbose: + cmd.append("-v") + + result = _run_command(cmd) + + # Assuming SEPP outputs files into the specified output_dir + if "error" not in result: + result["output_files"] = [str(f) for f in output_dir.iterdir() if f.is_file()] + + return result + + +@mcp.tool() +def upp_alignment( + input_sequences: Path, + reference_tree: Path, + reference_alignment: Path, + output_alignment: Path, + num_cpus: Optional[int] = None, + memory_limit_gb: Optional[int] = None, + algorithm: Optional[str] = None, + hmmer_path: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Performs Ultra-large alignments using Phylogeny-aware Profiles (UPP). + + Args: + input_sequences: Path to the input sequences file (FASTA format). + reference_tree: Path to the reference tree file (Newick format). + reference_alignment: Path to the reference alignment file (FASTA format). + output_alignment: Path to the output alignment file (FASTA format). + The parent directory will be created if it doesn't exist. + num_cpus: Number of CPUs to use. If None, UPP will use its internal default. + Must be a positive integer. + memory_limit_gb: Memory limit in GB. If None, UPP will use its internal default. + Must be a positive integer. + algorithm: Specific algorithm to use within UPP. If None, UPP will use its internal default. + hmmer_path: Path to the HMMER executable if not in PATH. + verbose: If True, enable verbose output. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # Input validation + if not isinstance(input_sequences, Path) or not input_sequences.is_file(): + raise ValueError(f"Input sequences file not found or invalid: {input_sequences}") + if not isinstance(reference_tree, Path) or not reference_tree.is_file(): + raise ValueError(f"Reference tree file not found or invalid: {reference_tree}") + if not isinstance(reference_alignment, Path) or not reference_alignment.is_file(): + raise ValueError(f"Reference alignment file not found or invalid: {reference_alignment}") + if not isinstance(output_alignment, Path): + raise ValueError(f"Output alignment path is invalid: {output_alignment}") + + output_alignment.parent.mkdir(parents=True, exist_ok=True) + + if num_cpus is not None: + if not isinstance(num_cpus, int) or num_cpus <= 0: + raise ValueError("Number of CPUs must be a positive integer.") + if memory_limit_gb is not None: + if not isinstance(memory_limit_gb, int) or memory_limit_gb <= 0: + raise ValueError("Memory limit in GB must be a positive integer.") + + if hmmer_path is not None and (not isinstance(hmmer_path, Path) or not hmmer_path.is_file()): + raise ValueError(f"HMMER executable not found or invalid: {hmmer_path}") + + cmd = ["python", "run_upp.py"] + cmd.extend(["-s", str(input_sequences)]) + cmd.extend(["-t", str(reference_tree)]) + cmd.extend(["-a", str(reference_alignment)]) + cmd.extend(["-o", str(output_alignment)]) + + if num_cpus is not None: + cmd.extend(["-p", str(num_cpus)]) + if memory_limit_gb is not None: + cmd.extend(["-m", str(memory_limit_gb)]) + if algorithm is not None: + cmd.extend(["-A", algorithm]) + if hmmer_path is not None: + cmd.extend(["-H", str(hmmer_path)]) + if verbose: + cmd.append("-v") + + result = _run_command(cmd) + + if "error" not in result: + result["output_files"] = [str(output_alignment)] + + return result + + +@mcp.tool() +def hippi_classification( + query_sequences: Path, + reference_sequences: Path, + output_file: Path, + num_cpus: Optional[int] = None, + num_hmm_profiles: Optional[int] = None, + algorithm: Optional[str] = None, + hmmer_path: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Performs Highly Accurate Protein Family Classification with Ensembles of HMMs (HIPPI). + + Args: + query_sequences: Path to the query sequences file (FASTA format). + reference_sequences: Path to the reference sequences file (FASTA format). + output_file: Path to the output classification file. + The parent directory will be created if it doesn't exist. + num_cpus: Number of CPUs to use. If None, HIPPI will use its internal default. + Must be a positive integer. + num_hmm_profiles: Number of HMM profiles to use. If None, HIPPI will use its internal default. + Must be a positive integer. + algorithm: Specific algorithm to use within HIPPI. If None, HIPPI will use its internal default. + hmmer_path: Path to the HMMER executable if not in PATH. + verbose: If True, enable verbose output. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # Input validation + if not isinstance(query_sequences, Path) or not query_sequences.is_file(): + raise ValueError(f"Query sequences file not found or invalid: {query_sequences}") + if not isinstance(reference_sequences, Path) or not reference_sequences.is_file(): + raise ValueError(f"Reference sequences file not found or invalid: {reference_sequences}") + if not isinstance(output_file, Path): + raise ValueError(f"Output file path is invalid: {output_file}") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + if num_cpus is not None: + if not isinstance(num_cpus, int) or num_cpus <= 0: + raise ValueError("Number of CPUs must be a positive integer.") + if num_hmm_profiles is not None: + if not isinstance(num_hmm_profiles, int) or num_hmm_profiles <= 0: + raise ValueError("Number of HMM profiles must be a positive integer.") + + if hmmer_path is not None and (not isinstance(hmmer_path, Path) or not hmmer_path.is_file()): + raise ValueError(f"HMMER executable not found or invalid: {hmmer_path}") + + cmd = ["python", "run_hippi.py"] + cmd.extend(["-q", str(query_sequences)]) + cmd.extend(["-r", str(reference_sequences)]) + cmd.extend(["-o", str(output_file)]) + + if num_cpus is not None: + cmd.extend(["-p", str(num_cpus)]) + if num_hmm_profiles is not None: + cmd.extend(["-x", str(num_hmm_profiles)]) + if algorithm is not None: + cmd.extend(["-A", algorithm]) + if hmmer_path is not None: + cmd.extend(["-H", str(hmmer_path)]) + if verbose: + cmd.append("-v") + + result = _run_command(cmd) + + if "error" not in result: + result["output_files"] = [str(output_file)] + + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_sepp/environment.yaml b/Biomni/mcp_generated/mcp_sepp/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9a23f036dadeca7e5f0a94ede6137732d1bcfff2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sepp/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - sepp + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_seqcluster/Dockerfile b/Biomni/mcp_generated/mcp_seqcluster/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..85789e88ee4c48a0bda32793cc2b5c7a7f3fb502 --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install seqcluster via conda (e.g., from bioconda) +RUN conda install -c bioconda seqcluster -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/seqcluster_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/seqcluster_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/seqcluster_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snippy/Dockerfile b/Biomni/mcp_generated/mcp_snippy/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..58656423f28e459c323c826fc4040eb93ab6f716 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install snippy via conda (e.g., from bioconda) +RUN conda install -c bioconda snippy -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/snippy_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/snippy_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/snippy_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snippy/app/requirements.txt b/Biomni/mcp_generated/mcp_snippy/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_snippy/app/snippy_server.py b/Biomni/mcp_generated/mcp_snippy/app/snippy_server.py new file mode 100644 index 0000000000000000000000000000000000000000..02df6fd324d29499fda7f89edb8c1052e408b349 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/app/snippy_server.py @@ -0,0 +1,774 @@ +import subprocess +import tempfile +import os +from pathlib import Path +from typing import List, Optional, Dict, Union + +# Helper function for running subprocess commands +def _run_command(cmd: List[str], cwd: Optional[Path] = None) -> Dict[str, Union[str, List[str]]]: + """ + Executes a shell command and captures its output. + """ + try: + process = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": f"Error: Command '{cmd[0]}' not found. Ensure snippy and its dependencies are in your PATH.", + "error": "Command not found", + "output_files": [] + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_snippy' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def snippy( + outdir: Path, + reference: Path, + cpus: int = 1, + r1: Optional[Path] = None, + r2: Optional[Path] = None, + contigs_file: Optional[Path] = None, + rgid: Optional[str] = None, + mapqual: int = 60, + basequal: int = 13, + maxsoft: int = 10, + mincov: int = 10, + minfrac: float = 0.9, + minqual: int = 100, + targets: Optional[Path] = None, + mask: Optional[Path] = None, + cleanup: bool = False, + subsample: Optional[float] = None, + unmapped: bool = False, + report: bool = False, +) -> Dict[str, Union[str, List[str]]]: + """ + Rapid bacterial SNP calling and core genome alignments. + + Finds SNPs between a haploid reference genome and NGS sequence reads or contigs. + It will find both substitutions (SNPs) and insertions/deletions (indels). + """ + # Input validation + if not reference.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Reference genome file not found: {reference}", + "error": "Input file not found", + "output_files": [] + } + + if r1 and not r1.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Read 1 file not found: {r1}", + "error": "Input file not found", + "output_files": [] + } + if r2 and not r2.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Read 2 file not found: {r2}", + "error": "Input file not found", + "output_files": [] + } + if r2 and not r1: + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: --R2 cannot be provided without --R1.", + "error": "Invalid parameter combination", + "output_files": [] + } + + if contigs_file and not contigs_file.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Contigs file not found: {contigs_file}", + "error": "Input file not found", + "output_files": [] + } + + if (r1 or r2) and contigs_file: + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: Cannot provide both --R1/--R2 and --ctgs. They are mutually exclusive.", + "error": "Invalid parameter combination", + "output_files": [] + } + + if not (r1 or contigs_file): + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: Either --R1 (and optionally --R2) or --ctgs must be provided.", + "error": "Missing input data", + "output_files": [] + } + + if cpus < 1: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --cpus must be a positive integer (got {cpus}).", + "error": "Invalid parameter value", + "output_files": [] + } + if mapqual < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --mapqual must be a non-negative integer (got {mapqual}).", + "error": "Invalid parameter value", + "output_files": [] + } + if basequal < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --basequal must be a non-negative integer (got {basequal}).", + "error": "Invalid parameter value", + "output_files": [] + } + if maxsoft < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --maxsoft must be a non-negative integer (got {maxsoft}).", + "error": "Invalid parameter value", + "output_files": [] + } + if mincov < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --mincov must be a non-negative integer (got {mincov}).", + "error": "Invalid parameter value", + "output_files": [] + } + if not (0.0 <= minfrac <= 1.0): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --minfrac must be between 0.0 and 1.0 (got {minfrac}).", + "error": "Invalid parameter value", + "output_files": [] + } + if minqual < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --minqual must be a non-negative integer (got {minqual}).", + "error": "Invalid parameter value", + "output_files": [] + } + if targets and not targets.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Targets BED file not found: {targets}", + "error": "Input file not found", + "output_files": [] + } + if mask and not mask.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Mask BED file not found: {mask}", + "error": "Input file not found", + "output_files": [] + } + if subsample is not None and not (0.0 <= subsample <= 1.0): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --subsample must be between 0.0 and 1.0 (got {subsample}).", + "error": "Invalid parameter value", + "output_files": [] + } + + outdir.mkdir(parents=True, exist_ok=True) + + cmd = ["snippy"] + cmd.extend(["--outdir", str(outdir)]) + cmd.extend(["--ref", str(reference)]) + cmd.extend(["--cpus", str(cpus)]) + + if r1: + cmd.extend(["--R1", str(r1)]) + if r2: + cmd.extend(["--R2", str(r2)]) + if contigs_file: + cmd.extend(["--ctgs", str(contigs_file)]) + + if rgid: + cmd.extend(["--rgid", rgid]) + cmd.extend(["--mapqual", str(mapqual)]) + cmd.extend(["--basequal", str(basequal)]) + cmd.extend(["--maxsoft", str(maxsoft)]) + cmd.extend(["--mincov", str(mincov)]) + cmd.extend(["--minfrac", str(minfrac)]) + cmd.extend(["--minqual", str(minqual)]) + if targets: + cmd.extend(["--targets", str(targets)]) + if mask: + cmd.extend(["--mask", str(mask)]) + if cleanup: + cmd.append("--cleanup") + if subsample is not None: + cmd.extend(["--subsample", str(subsample)]) + if unmapped: + cmd.append("--unmapped") + if report: + cmd.append("--report") + + result = _run_command(cmd) + + # Collect output files + output_files = [] + if outdir.is_dir(): + # List all files in the output directory and its subdirectories + for root, _, files in os.walk(outdir): + for f in files: + output_files.append(str(Path(root) / f)) + + # Add specific files mentioned in docs that might not be in the root of outdir + # (e.g., reference/ subdirectory) + expected_files = [ + outdir / "snps.tab", outdir / "snps.csv", outdir / "snps.html", + outdir / "snps.vcf", outdir / "snps.bed", outdir / "snps.gff", + outdir / "snps.bam", outdir / "snps.bam.bai", outdir / "snps.log", + outdir / "snps.aligned.fa", outdir / "snps.consensus.fa", + outdir / "snps.consensus.subs.fa", outdir / "snps.raw.vcf", + outdir / "snps.filt.vcf", outdir / "snps.vcf.gz", + outdir / "snps.vcf.gz.csi" + ] + if report: + expected_files.append(outdir / "snps.report.txt") # Assuming default is txt if --report is on + + for f_path in expected_files: + if f_path.is_file() and str(f_path) not in output_files: + output_files.append(str(f_path)) + + result["output_files"] = [f for f in output_files if Path(f).exists()] # Filter to actual existing files + return result + + +@mcp.tool() +def snippy_core( + snippy_folders: List[Path], + prefix: str = "core", + reference: Optional[Path] = None, + aformat: str = "fasta", + mask: Optional[Union[Path, str]] = None, # Can be "auto" or a Path + mincov: int = 10, + minqual: int = 100, +) -> Dict[str, Union[str, List[str]]]: + """ + Generates a core SNP alignment from multiple Snippy output folders. + """ + # Input validation + if not snippy_folders: + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: At least one Snippy output folder must be provided.", + "error": "Missing input data", + "output_files": [] + } + for folder in snippy_folders: + if not folder.is_dir(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Snippy output folder not found: {folder}", + "error": "Input directory not found", + "output_files": [] + } + if reference and not reference.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Reference FASTA file not found: {reference}", + "error": "Input file not found", + "output_files": [] + } + if mask and isinstance(mask, Path) and not mask.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Mask BED file not found: {mask}", + "error": "Input file not found", + "output_files": [] + } + if mincov < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --mincov must be a non-negative integer (got {mincov}).", + "error": "Invalid parameter value", + "output_files": [] + } + if minqual < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --minqual must be a non-negative integer (got {minqual}).", + "error": "Invalid parameter value", + "output_files": [] + } + + cmd = ["snippy-core"] + cmd.extend(["--prefix", prefix]) + if reference: + cmd.extend(["--reference", str(reference)]) + cmd.extend(["--aformat", aformat]) + if mask: + cmd.extend(["--mask", str(mask)]) + cmd.extend(["--mincov", str(mincov)]) + cmd.extend(["--minqual", str(minqual)]) + cmd.extend([str(f) for f in snippy_folders]) + + result = _run_command(cmd) + + # Collect output files (assuming current working directory for output) + output_files = [ + f"{prefix}.aln", + f"{prefix}.full.aln", + f"{prefix}.tab", + f"{prefix}.vcf", + f"{prefix}.txt", + f"{prefix}.ref.fa", + f"{prefix}.self_mask.bed", + ] + result["output_files"] = [str(Path(f)) for f in output_files if Path(f).exists()] + return result + + +@mcp.tool() +def snippy_vcf_report( + snippy_output_dir: Path, + cpus: int = 1, + auto: bool = True, + html: bool = False, + vcf_file: Optional[Path] = None, + bam_file: Optional[Path] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Generates a detailed report for variants in a Snippy VCF file. + """ + # Input validation + if not snippy_output_dir.is_dir(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Snippy output directory not found: {snippy_output_dir}", + "error": "Input directory not found", + "output_files": [] + } + if cpus < 1: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --cpus must be a positive integer (got {cpus}).", + "error": "Invalid parameter value", + "output_files": [] + } + + if auto: + if vcf_file or bam_file: + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: --auto is mutually exclusive with explicit --vcf and --bam files.", + "error": "Invalid parameter combination", + "output_files": [] + } + expected_vcf = snippy_output_dir / "snps.vcf" + expected_bam = snippy_output_dir / "snps.bam" + if not expected_vcf.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --auto specified, but {expected_vcf} not found in {snippy_output_dir}.", + "error": "Input file not found", + "output_files": [] + } + if not expected_bam.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --auto specified, but {expected_bam} not found in {snippy_output_dir}.", + "error": "Input file not found", + "output_files": [] + } + else: # not auto + if not vcf_file or not bam_file: + return { + "command_executed": "", + "stdout": "", + "stderr": "Error: If --auto is not used, both --vcf and --bam must be explicitly provided.", + "error": "Missing input data", + "output_files": [] + } + if not vcf_file.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: VCF file not found: {vcf_file}", + "error": "Input file not found", + "output_files": [] + } + if not bam_file.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: BAM file not found: {bam_file}", + "error": "Input file not found", + "output_files": [] + } + + cmd = ["snippy-vcf_report"] + cmd.extend(["--cpus", str(cpus)]) + if auto: + cmd.append("--auto") + else: + cmd.extend(["--vcf", str(vcf_file)]) + cmd.extend(["--bam", str(bam_file)]) + if html: + cmd.append("--html") + + # snippy-vcf_report writes to stdout. We need to capture this and save it to a file. + # The tool is designed to be run from within the snippy_output_dir. + # We will run it in the specified directory and capture its stdout. + + original_cwd = Path.cwd() + os.chdir(snippy_output_dir) + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + stdout_content = process.stdout + stderr_content = process.stderr + error_message = None + except subprocess.CalledProcessError as e: + stdout_content = e.stdout + stderr_content = e.stderr + error_message = str(e) + except FileNotFoundError: + stdout_content = "" + stderr_content = f"Error: Command '{cmd[0]}' not found. Ensure snippy and its dependencies are in your PATH." + error_message = "Command not found" + finally: + os.chdir(original_cwd) # Always change back + + if error_message: + return { + "command_executed": " ".join(cmd), + "stdout": stdout_content, + "stderr": stderr_content, + "error": error_message, + "output_files": [] + } + else: + # Save the captured stdout to a file in the snippy_output_dir + report_filename = f"snps.report.{'html' if html else 'txt'}" + final_output_file = snippy_output_dir / report_filename + final_output_file.write_text(stdout_content) + + return { + "command_executed": " ".join(cmd), + "stdout": stdout_content, + "stderr": stderr_content, + "output_files": [str(final_output_file)] + } + + +@mcp.tool() +def snippy_multi( + input_tab_file: Path, + reference: Path, + cpus: int = 1, + outdir_base: Path = Path("."), # Base directory where individual snippy output folders will be created + mapqual: int = 60, + basequal: int = 13, + maxsoft: int = 10, + mincov: int = 10, + minfrac: float = 0.9, + minqual: int = 100, + targets: Optional[Path] = None, + mask: Optional[Path] = None, + cleanup: bool = False, + subsample: Optional[float] = None, + unmapped: bool = False, + report: bool = False, +) -> Dict[str, Union[str, List[str]]]: + """ + Simplifies running a set of isolate sequences against the same reference, + then generates a core genome SNP alignment. + """ + # Input validation + if not input_tab_file.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Input tab file not found: {input_tab_file}", + "error": "Input file not found", + "output_files": [] + } + if not reference.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Reference genome file not found: {reference}", + "error": "Input file not found", + "output_files": [] + } + if cpus < 1: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --cpus must be a positive integer (got {cpus}).", + "error": "Invalid parameter value", + "output_files": [] + } + if mapqual < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --mapqual must be a non-negative integer (got {mapqual}).", + "error": "Invalid parameter value", + "output_files": [] + } + if basequal < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --basequal must be a non-negative integer (got {basequal}).", + "error": "Invalid parameter value", + "output_files": [] + } + if maxsoft < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --maxsoft must be a non-negative integer (got {maxsoft}).", + "error": "Invalid parameter value", + "output_files": [] + } + if mincov < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --mincov must be a non-negative integer (got {mincov}).", + "error": "Invalid parameter value", + "output_files": [] + } + if not (0.0 <= minfrac <= 1.0): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --minfrac must be between 0.0 and 1.0 (got {minfrac}).", + "error": "Invalid parameter value", + "output_files": [] + } + if minqual < 0: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --minqual must be a non-negative integer (got {minqual}).", + "error": "Invalid parameter value", + "output_files": [] + } + if targets and not targets.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Targets BED file not found: {targets}", + "error": "Input file not found", + "output_files": [] + } + if mask and not mask.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Mask BED file not found: {mask}", + "error": "Input file not found", + "output_files": [] + } + if subsample is not None and not (0.0 <= subsample <= 1.0): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: --subsample must be between 0.0 and 1.0 (got {subsample}).", + "error": "Invalid parameter value", + "output_files": [] + } + + outdir_base.mkdir(parents=True, exist_ok=True) + + cmd = ["snippy-multi", str(input_tab_file)] + cmd.extend(["--ref", str(reference)]) + cmd.extend(["--cpus", str(cpus)]) + + # snippy-multi uses the ID from input.tab for --outdir, so we don't pass --outdir here. + # The tool will create subdirectories within the current working directory or where it's run. + # We will run it in the specified outdir_base. + + cmd.extend(["--mapqual", str(mapqual)]) + cmd.extend(["--basequal", str(basequal)]) + cmd.extend(["--maxsoft", str(maxsoft)]) + cmd.extend(["--mincov", str(mincov)]) + cmd.extend(["--minfrac", str(minfrac)]) + cmd.extend(["--minqual", str(minqual)]) + if targets: + cmd.extend(["--targets", str(targets)]) + if mask: + cmd.extend(["--mask", str(mask)]) + if cleanup: + cmd.append("--cleanup") + if subsample is not None: + cmd.extend(["--subsample", str(subsample)]) + if unmapped: + cmd.append("--unmapped") + if report: + cmd.append("--report") + + # snippy-multi outputs a shell script to stdout. + # We need to capture this script and then execute it. + # The script itself will create the snippy output folders and run snippy-core. + + with tempfile.TemporaryDirectory() as tmpdir: + script_path = Path(tmpdir) / "runme.sh" + + try: + process = subprocess.run( + cmd, + cwd=outdir_base, # Run snippy-multi in the base output directory + capture_output=True, + text=True, + check=True + ) + script_path.write_text(process.stdout) + stdout_multi = process.stdout + stderr_multi = process.stderr + + # Now execute the generated script + run_script_cmd = ["sh", str(script_path)] + script_result = _run_command(run_script_cmd, cwd=outdir_base) + + # Aggregate results + final_stdout = f"snippy-multi stdout:\n{stdout_multi}\n\nGenerated script stdout:\n{script_result['stdout']}" + final_stderr = f"snippy-multi stderr:\n{stderr_multi}\n\nGenerated script stderr:\n{script_result['stderr']}" + + if "error" in script_result: + return { + "command_executed": " ".join(cmd) + "\n" + " ".join(run_script_cmd), + "stdout": final_stdout, + "stderr": final_stderr, + "error": script_result["error"], + "output_files": [] + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": f"Error: Command '{cmd[0]}' not found. Ensure snippy and its dependencies are in your PATH.", + "error": "Command not found", + "output_files": [] + } + + # Collect output files from outdir_base + output_files = [] + if outdir_base.is_dir(): + for root, _, files in os.walk(outdir_base): + for f in files: + output_files.append(str(Path(root) / f)) + + # Also add core files explicitly, as they are generated by snippy-core at the end + # Assuming core files are generated in outdir_base + for core_file_suffix in [".aln", ".full.aln", ".tab", ".vcf", ".txt", ".ref.fa", ".self_mask.bed"]: + core_file_path = outdir_base / f"core{core_file_suffix}" # snippy-multi uses 'core' prefix by default + if core_file_path.is_file() and str(core_file_path) not in output_files: + output_files.append(str(core_file_path)) + + return { + "command_executed": " ".join(cmd) + "\n" + " ".join(run_script_cmd), + "stdout": final_stdout, + "stderr": final_stderr, + "output_files": [f for f in output_files if Path(f).exists()] + } + + +@mcp.tool() +def snippy_clean_full_aln( + full_alignment_file: Path, +) -> Dict[str, Union[str, List[str]]]: + """ + Cleans a core.full.aln file by replacing 'weird' characters with 'N'. + Outputs the cleaned alignment to stdout. + """ + # Input validation + if not full_alignment_file.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Input alignment file not found: {full_alignment_file}", + "error": "Input file not found", + "output_files": [] + } + + cmd = ["snippy-clean_full_aln", str(full_alignment_file)] + + # snippy-clean_full_aln writes to stdout. We will capture it. + result = _run_command(cmd) + + # The output is typically redirected to a new file by the user. + # We will return the stdout content, and the user can save it. + # No specific output files are created by the tool itself, only stdout. + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_snippy/app/snippy_shim_server.py b/Biomni/mcp_generated/mcp_snippy/app/snippy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f9af8ac84d536f26dc22261d9c7a8f072b645220 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/app/snippy_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snippy/app/snippy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_snippy' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_snippy/docker-compose.yml b/Biomni/mcp_generated/mcp_snippy/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3afd10bccd9c1c8094a40ba1f7d5c3e223c8ec74 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-snippy: + build: . + image: mcp-snippy:latest + container_name: mcp-snippy + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=snippy + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snippy/environment.yaml b/Biomni/mcp_generated/mcp_snippy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fc4cf0ae50a4c31d7a290f15bc8a0ddf75b4afe1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - snippy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snippy/requirements.txt b/Biomni/mcp_generated/mcp_snippy/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snippy/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_stacks/Dockerfile b/Biomni/mcp_generated/mcp_stacks/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fc8217e12474d99f931df87452eed8b2021b0e11 --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install stacks via conda (e.g., from bioconda) +RUN conda install -c bioconda stacks -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/stacks_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/stacks_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/stacks_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_stacks/app/requirements.txt b/Biomni/mcp_generated/mcp_stacks/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_stacks/app/stacks_shim_server.py b/Biomni/mcp_generated/mcp_stacks/app/stacks_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e5b03b441f7011dee0b9e4a252b43e2eebd5f665 --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/app/stacks_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_stacks/app/stacks_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_stacks' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_stacks/docker-compose.yml b/Biomni/mcp_generated/mcp_stacks/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..507654a0f8d116756fe145200bb7ac0e5b23b1c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-stacks: + build: . + image: mcp-stacks:latest + container_name: mcp-stacks + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=stacks + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_stacks/environment.yaml b/Biomni/mcp_generated/mcp_stacks/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bba10181cc36f9b4ece9abe5364b680597471946 --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - stacks + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_stacks/requirements.txt b/Biomni/mcp_generated/mcp_stacks/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_stacks/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_vt/Dockerfile b/Biomni/mcp_generated/mcp_vt/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..bd05a6aab46e1ec727e06ac588f7aa1c8e23437b --- /dev/null +++ b/Biomni/mcp_generated/mcp_vt/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +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 + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install vt via conda (e.g., from bioconda) +RUN conda install -c bioconda vt -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/vt_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/vt_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/vt_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_vt/app/vt_server.py b/Biomni/mcp_generated/mcp_vt/app/vt_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a21fa408cdd65ae092d5d682739f3a2eb6065899 --- /dev/null +++ b/Biomni/mcp_generated/mcp_vt/app/vt_server.py @@ -0,0 +1,469 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_vt' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def vt_normalize( + input_vcf: str, + reference_fasta: str, + output_vcf: Optional[str] = None, + window_size: int = 10000, + no_realign: bool = False, + quiet: bool = False, +): + """ + Normalizes variants in a VCF file. Normalization involves parsimony and left-alignment. + + Args: + input_vcf: Path to the input VCF/BCF file. + reference_fasta: Path to the reference genome FASTA file. + output_vcf: Path to the output VCF/BCF file. If not provided, outputs to stdout. + window_size: Window size for local realignment (default: 10000). + no_realign: Do not perform local realignment. + quiet: Do not print progress to stderr. + """ + input_path = Path(input_vcf) + ref_path = Path(reference_fasta) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + if not ref_path.exists(): + return {"error": f"Reference FASTA file not found: {reference_fasta}"} + + cmd = ["vt", "normalize", str(input_path), "-r", str(ref_path)] + + if output_vcf: + cmd.extend(["-o", output_vcf]) + if window_size != 10000: + cmd.extend(["-w", str(window_size)]) + if no_realign: + cmd.append("-n") + if quiet: + cmd.append("-q") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_decompose( + input_vcf: str, + output_vcf: Optional[str] = None, + smart: bool = False, + quiet: bool = False, +): + """ + Decomposes multiallelic variants into biallelic ones. + + Args: + input_vcf: Path to the input VCF/BCF file. + output_vcf: Path to the output VCF/BCF file. + smart: Smart decomposition (decomposes MNPs into SNPs). + quiet: Do not print progress to stderr. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "decompose", str(input_path)] + + if output_vcf: + cmd.extend(["-o", output_vcf]) + if smart: + cmd.append("-s") + if quiet: + cmd.append("-q") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_decompose_blocksub( + input_vcf: str, + output_vcf: Optional[str] = None, + alignment_penalty_match: int = 0, + alignment_penalty_mismatch: int = 5, + alignment_penalty_gap_open: int = 10, + alignment_penalty_gap_extend: int = 2, +): + """ + Decomposes block substitutions into smaller variants. + + Args: + input_vcf: Path to the input VCF/BCF file. + output_vcf: Path to the output VCF/BCF file. + alignment_penalty_match: Penalty for match (default: 0). + alignment_penalty_mismatch: Penalty for mismatch (default: 5). + alignment_penalty_gap_open: Penalty for gap open (default: 10). + alignment_penalty_gap_extend: Penalty for gap extend (default: 2). + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "decompose_blocksub", str(input_path)] + + if output_vcf: + cmd.extend(["-o", output_vcf]) + + # Construct alignment penalty string if any are non-default + penalties = f"{alignment_penalty_match},{alignment_penalty_mismatch},{alignment_penalty_gap_open},{alignment_penalty_gap_extend}" + if penalties != "0,5,10,2": + cmd.extend(["-a", penalties]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_sort( + input_vcf: str, + output_vcf: Optional[str] = None, +): + """ + Sorts a VCF file by chromosome and position. + + Args: + input_vcf: Path to the input VCF file. + output_vcf: Path to the output VCF file. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "sort", str(input_path)] + + if output_vcf: + cmd.extend(["-o", output_vcf]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_uniq( + input_vcf: str, + output_vcf: Optional[str] = None, +): + """ + Removes duplicate variants in a VCF file. + + Args: + input_vcf: Path to the input VCF file. + output_vcf: Path to the output VCF file. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "uniq", str(input_path)] + + if output_vcf: + cmd.extend(["-o", output_vcf]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_peek( + input_vcf: str, +): + """ + Summarizes the variants in a VCF file. + + Args: + input_vcf: Path to the input VCF file. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "peek", str(input_path)] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_index( + input_vcf: str, +): + """ + Indexes a VCF/BCF file. + + Args: + input_vcf: Path to the input VCF/BCF file. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "index", str(input_path)] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f"{input_vcf}.tbi"] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_view( + input_vcf: str, + filter_expression: Optional[str] = None, + output_vcf: Optional[str] = None, + intervals: Optional[str] = None, + header_only: bool = False, + no_header: bool = False, +): + """ + Views and filters VCF/BCF files. + + Args: + input_vcf: Path to the input VCF/BCF file. + filter_expression: Filter expression (e.g., 'TYPE==SNP'). + output_vcf: Path to the output VCF/BCF file. + intervals: Genomic intervals (e.g., 'chr1:100-200,chr2'). + header_only: Print only the header. + no_header: Do not print the header. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "view", str(input_path)] + + if filter_expression: + cmd.extend(["-f", filter_expression]) + if output_vcf: + cmd.extend(["-o", output_vcf]) + if intervals: + cmd.extend(["-i", intervals]) + if header_only: + cmd.append("-h") + if no_header: + cmd.append("-H") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] if output_vcf else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_merge( + input_vcfs: List[str], + output_vcf: str, + quiet: bool = False, +): + """ + Merges multiple VCF files. + + Args: + input_vcfs: List of paths to input VCF files. + output_vcf: Path to the output merged VCF file. + quiet: Do not print progress to stderr. + """ + if not input_vcfs: + return {"error": "No input VCF files provided."} + + cmd = ["vt", "merge"] + + for vcf in input_vcfs: + if not Path(vcf).exists(): + return {"error": f"Input VCF file not found: {vcf}"} + cmd.extend(["-i", vcf]) + + cmd.extend(["-o", output_vcf]) + + if quiet: + cmd.append("-q") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_concat( + input_vcfs: List[str], + output_vcf: str, +): + """ + Concatenates multiple VCF files. + + Args: + input_vcfs: List of paths to input VCF files. + output_vcf: Path to the output concatenated VCF file. + """ + if not input_vcfs: + return {"error": "No input VCF files provided."} + + cmd = ["vt", "concat"] + + for vcf in input_vcfs: + if not Path(vcf).exists(): + return {"error": f"Input VCF file not found: {vcf}"} + cmd.append(vcf) + + cmd.extend(["-o", output_vcf]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_vcf] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def vt_partition( + input_vcf: str, + output_prefix: str, +): + """ + Partitions a VCF file into SNPs, Indels, and other variants. + + Args: + input_vcf: Path to the input VCF file. + output_prefix: Prefix for the output files. + """ + input_path = Path(input_vcf) + if not input_path.exists(): + return {"error": f"Input VCF file not found: {input_vcf}"} + + cmd = ["vt", "partition", str(input_path), "-o", output_prefix] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f"{output_prefix}.snp.vcf", f"{output_prefix}.indel.vcf", f"{output_prefix}.other.vcf"] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_xopen/requirements.txt b/Biomni/mcp_generated/mcp_xopen/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_xopen/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp