diff --git a/Biomni/mcp_generated/mcp_bioawk/Dockerfile b/Biomni/mcp_generated/mcp_bioawk/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..26acb9706d6e3e8a943d4d9676b9eb014a983372 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/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 bioawk via conda (e.g., from bioconda) +RUN conda install -c bioconda bioawk -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/bioawk_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/bioawk_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/bioawk_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioawk/app/bioawk_server.py b/Biomni/mcp_generated/mcp_bioawk/app/bioawk_server.py new file mode 100644 index 0000000000000000000000000000000000000000..045640223e6d54a4636d76ad5fad59535ca67373 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/app/bioawk_server.py @@ -0,0 +1,132 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Dict, Any + +# Assume 'mcp' is a globally available module in the execution environment. +# from some_mcp_library import mcp + +@mcp.tool +def bioawk( + program: Optional[str] = None, + program_file: Optional[Path] = None, + input_files: Optional[List[Path]] = None, + output_file: Optional[Path] = None, + format: Optional[str] = None, + tab_separator: bool = False, + include_header: bool = False, + variables: Optional[List[str]] = None, + field_separator: Optional[str] = None, +) -> Dict[str, Any]: + """ + Executes the bioawk command, a powerful stream editor for biological data formats. + + bioawk is an extension of awk that understands common biological data formats + like FASTA, FASTQ, SAM, VCF, BED, and GFF. At least one of 'program' or + 'program_file' must be provided. + + Args: + program: The AWK program string to execute. Mutually exclusive with 'program_file'. + program_file: Path to a file containing the AWK program. Mutually exclusive with 'program'. + input_files: A list of input files to process. If not provided, bioawk reads from standard input. + output_file: Path to a file where the output (stdout) will be saved. If not provided, stdout is captured and returned. + format: Input format (-c). Supported formats include 'fasta', 'fastq', 'sam', 'vcf', 'bed', 'gff'. + tab_separator: Use tab as the input and output field separator (-t flag). + include_header: Include header in the output for formats like VCF/SAM (-H flag). + variables: A list of 'var=value' strings to define AWK variables (-v flag). + field_separator: The input field separator string (-F flag). + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not program and not program_file: + raise ValueError("Either 'program' or 'program_file' must be provided.") + if program and program_file: + raise ValueError("'program' and 'program_file' are mutually exclusive and cannot be used together.") + + if program_file: + if not program_file.is_file(): + raise FileNotFoundError(f"Program file not found: {program_file}") + + if input_files: + for file_path in input_files: + if not file_path.is_file(): + raise FileNotFoundError(f"Input file not found: {file_path}") + + VALID_FORMATS = {"fasta", "fastq", "sam", "vcf", "bed", "gff"} + if format and format.lower() not in VALID_FORMATS: + raise ValueError(f"Invalid format '{format}'. Must be one of {VALID_FORMATS}.") + + if variables: + for var in variables: + if "=" not in var: + raise ValueError(f"Invalid variable assignment '{var}'. Must be in 'var=value' format.") + + # --- Command Construction --- + cmd = ["bioawk"] + + if format: + cmd.extend(["-c", format.lower()]) + if tab_separator: + cmd.append("-t") + if include_header: + cmd.append("-H") + if field_separator: + cmd.extend(["-F", field_separator]) + if variables: + for var in variables: + cmd.extend(["-v", var]) + + # The program or program_file argument must come after options but before input files. + if program_file: + cmd.extend(["-f", str(program_file)]) + elif program: + cmd.append(program) + + if input_files: + cmd.extend([str(p) for p in input_files]) + + command_executed = " ".join(cmd) + + # --- Subprocess Execution --- + try: + if output_file: + # Create parent directories if they don't exist + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f_out: + result = subprocess.run( + cmd, + check=True, + stdout=f_out, + stderr=subprocess.PIPE, + text=True, + ) + stdout_content = f"Output successfully written to {output_file}" + output_files_list = [str(output_file)] + else: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout_content = result.stdout + output_files_list = [] + + return { + "command_executed": command_executed, + "stdout": stdout_content, + "stderr": result.stderr, + "output_files": output_files_list, + } + except FileNotFoundError: + raise RuntimeError("bioawk command not found. Please ensure it is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + # Return a structured error response if the command fails + return { + "command_executed": command_executed, + "stdout": e.stdout or "", + "stderr": e.stderr or f"bioawk exited with error code {e.returncode}", + "output_files": [], + "error": f"Command failed with exit code {e.returncode}" + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioawk/app/bioawk_shim_server.py b/Biomni/mcp_generated/mcp_bioawk/app/bioawk_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4a812b820b41c220f311dc669325bbc5c417ec43 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/app/bioawk_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_bioawk/app/bioawk_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioawk' + + +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_bioawk/app/requirements.txt b/Biomni/mcp_generated/mcp_bioawk/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioawk/docker-compose.yml b/Biomni/mcp_generated/mcp_bioawk/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7225a96d51707881729bddfe8df7a8a8ad5a61bd --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioawk: + build: . + image: mcp-bioawk:latest + container_name: mcp-bioawk + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioawk + 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_bioawk/environment.yaml b/Biomni/mcp_generated/mcp_bioawk/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d850d38d19bdb05c11c590472bc88660b4d32fef --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioawk + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioawk/requirements.txt b/Biomni/mcp_generated/mcp_bioawk/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioawk/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-ancombc/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-ancombc/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ancombc/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-ancombc/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-ancombc/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ancombc/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py b/Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py new file mode 100644 index 0000000000000000000000000000000000000000..588b7a281f58ec1138351f0da97d519f7db6badf --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py @@ -0,0 +1,204 @@ +import subprocess +import tempfile +import shutil +import re +from pathlib import Path +from typing import Optional, List + +# This is a placeholder for the actual decorator. +# In a real MCP environment, you would use `@mcp.tool()`. +def mcp_tool_placeholder(*args, **kwargs): + def decorator(func): + return func + return decorator + +@mcp_tool_placeholder() +def forge_bsgenome_data_pkg( + organism: str, + common_name: str, + organism_abbreviation: str, + provider: str, + provider_version: str, + release_date: str, + package_version: str, + author: str, + maintainer: str, + seqs_srcdir: Path, + output_pkg_path: Path, + circ_seqs: Optional[List[str]] = None, + seqfiles_prefix: Optional[str] = None, + seqfiles_suffix: Optional[str] = None, + license_str: str = "Artistic-2.0", + title: Optional[str] = None, + description: Optional[str] = None, + organism_biocview: Optional[str] = None, +) -> dict: + """ + Creates a Bioconductor BSgenome data package from a set of FASTA files. + + This tool wraps the `forgeBSgenomeDataPkg` function from the R/Bioconductor + BSgenome package. It automates the creation of the necessary seed file and + executes an R script to build the package source directory, which is then + returned as a gzipped tarball. + + Args: + organism: Full organism name (e.g., "Homo sapiens"). + common_name: Common name for the organism (e.g., "Human"). + organism_abbreviation: Abbreviation for the organism, used in package naming (e.g., "Hsapiens"). + provider: Source of the genome data (e.g., "UCSC"). + provider_version: Version or build of the genome from the provider (e.g., "hg38"). + release_date: Release date of the genome build (e.g., "Dec. 2013"). + package_version: Version for the new BSgenome package, in X.Y.Z format (e.g., "1.0.0"). + author: The author of this BSgenome package. + maintainer: The maintainer of the package, including email (e.g., "Your Name "). + seqs_srcdir: Input directory containing the genome sequence files (e.g., FASTA files). + output_pkg_path: Path to save the final gzipped tarball of the package source (e.g., 'MyPackage.tar.gz'). + circ_seqs: Optional list of sequence names that are circular (e.g., ["chrM"]). + seqfiles_prefix: Optional prefix for the sequence files within `seqs_srcdir`. + seqfiles_suffix: Optional suffix for the sequence files (e.g., ".fa", ".fasta"). + license_str: The license for the generated package. + title: Optional title for the package. A default will be generated if not provided. + description: Optional description for the package. A default will be generated if not provided. + organism_biocview: Optional Bioconductor view term for the organism (e.g., "Homo_sapiens"). + + Returns: + A dictionary containing the execution details and path to the output file. + """ + # 1. Input Validation + if not seqs_srcdir.is_dir(): + raise ValueError(f"Input directory '{seqs_srcdir}' does not exist or is not a directory.") + if not re.match(r"^\d+\.\d+\.\d+$", package_version): + raise ValueError(f"Package version '{package_version}' is not in X.Y.Z format.") + for name_part in [organism_abbreviation, provider]: + if not name_part.isalnum(): + raise ValueError(f"Package name component '{name_part}' contains invalid characters. Use only alphanumerics.") + if not output_pkg_path.parent.exists(): + output_pkg_path.parent.mkdir(parents=True, exist_ok=True) + + # 2. Generate derived values + sanitized_provider_version = re.sub(r'[^a-zA-Z0-9.]', '', provider_version) + package_name = f"BSgenome.{organism_abbreviation}.{provider}.{sanitized_provider_version}" + bsgenome_objname = organism_abbreviation + + if title is None: + title = f"Full genome sequences for {organism} ({provider} version {provider_version})" + if description is None: + description = ( + f"Full genome sequences for {organism} ({common_name}) as provided by " + f"{provider} ({provider_version}, {release_date}) and stored in Biostrings objects." + ) + + organism_biocview_val = organism_biocview if organism_biocview else organism.replace(" ", "_") + + # 3. Create seed file content + seed_content = [ + f"Package: {package_name}", + f"Title: {title}", + f"Description: {description}", + f"Version: {package_version}", + f"Author: {author}", + f"Maintainer: {maintainer}", + f"License: {license_str}", + f"organism: {organism}", + f"common_name: {common_name}", + f"provider: {provider}", + f"provider_version: {provider_version}", + f"release_date: {release_date}", + f"BSgenomeObjname: {bsgenome_objname}", + f"organism_biocview: {organism_biocview_val}", + f"seqs_srcdir: {seqs_srcdir.resolve()}", + ] + + if circ_seqs: + r_vector = ', '.join(f'"{seq}"' for seq in circ_seqs) + seed_content.append(f"circ_seqs: c({r_vector})") + if seqfiles_prefix: + seed_content.append(f"seqfiles_prefix: {seqfiles_prefix}") + if seqfiles_suffix: + seed_content.append(f"seqfiles_suffix: {seqfiles_suffix}") + + seed_file_text = "\n".join(seed_content) + + # 4. Create R script + r_script_text = f""" + # Ensure BiocManager and BSgenome are installed + if (!requireNamespace("BiocManager", quietly = TRUE)) {{ + install.packages("BiocManager", repos="http://cran.us.r-project.org") + }} + if (!requireNamespace("BSgenome", quietly = TRUE)) {{ + BiocManager::install("BSgenome", update=FALSE, ask=FALSE) + }} + + library(BSgenome) + + # Forge the package + tryCatch({{ + forgeBSgenomeDataPkg(seed_file="seed.txt", destdir=".") + cat("BSgenome package '{package_name}' successfully forged.\\n") + }}, error = function(e) {{ + cat("Error during package forging:\\n") + cat(conditionMessage(e), "\\n") + quit(status = 1) + }}) + """ + + # 5. Execute in a temporary directory + with tempfile.TemporaryDirectory() as tmpdir: + work_dir = Path(tmpdir) + seed_file_path = work_dir / "seed.txt" + r_script_path = work_dir / "run_forge.R" + + seed_file_path.write_text(seed_file_text) + r_script_path.write_text(r_script_text) + + cmd = ["Rscript", str(r_script_path)] + command_executed = " ".join(cmd) + + try: + process = subprocess.run( + cmd, + cwd=work_dir, + check=True, + capture_output=True, + text=True, + encoding='utf-8', + ) + + # 6. Package the result + created_pkg_dir = work_dir / package_name + if not created_pkg_dir.is_dir(): + raise FileNotFoundError( + f"BSgenome package directory '{package_name}' was not created. " + f"Check R script output for errors.\nStderr: {process.stderr}" + ) + + archive_base_name = str(output_pkg_path.with_suffix('')) + archive_path_str = shutil.make_archive( + base_name=archive_base_name, + format='gztar', + root_dir=work_dir, + base_dir=package_name + ) + + final_archive_path = Path(archive_path_str) + if final_archive_path != output_pkg_path: + final_archive_path.rename(output_pkg_path) + + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": {"bsgenome_package_archive": str(output_pkg_path)} + } + + except subprocess.CalledProcessError as e: + # Using .decode() in case the output is not valid utf-8 on error + stdout = e.stdout if isinstance(e.stdout, str) else e.stdout.decode('utf-8', errors='replace') + stderr = e.stderr if isinstance(e.stderr, str) else e.stderr.decode('utf-8', errors='replace') + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-bsgenome/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-bsgenome/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a16172fc9d353b1fa84f3f1c445702f0bc06751b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-bsgenome/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-bsgenome: + build: . + image: mcp-bioconductor-bsgenome:latest + container_name: mcp-bioconductor-bsgenome + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-bsgenome + 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-dnacopy/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-dnacopy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a899be3d3c4b68ef4f21429190feaaf5b44d4dbf --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-dnacopy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-dnacopy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dc26f6e5aed612e1d00f134418a4dfb7f9cfe0cb --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/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-genomeinfodbdata via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-genomeinfodbdata -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-genomeinfodbdata_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-genomeinfodbdata_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-genomeinfodbdata_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..417a651e07b0490b797620fdf758562f52d5a1cb --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-genomeinfodbdata: + build: . + image: mcp-bioconductor-genomeinfodbdata:latest + container_name: mcp-bioconductor-genomeinfodbdata + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-genomeinfodbdata + 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-genomeinfodbdata/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-go.db/app/bioconductor-go.db_server.py b/Biomni/mcp_generated/mcp_bioconductor-go.db/app/bioconductor-go.db_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0820278a2907da2e80f7584bd0470baaf5dd3694 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-go.db/app/bioconductor-go.db_server.py @@ -0,0 +1,196 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any + +# MCP decorator is not imported as per instructions. +# It is assumed to be available in the execution environment. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_go_db' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def list_go_db_columns() -> Dict[str, Any]: + """ + Lists all available columns for querying in the Bioconductor GO.db. + + This function executes an R script to load the GO.db package and + extract the names of all queryable columns. It is useful for discovering + what data can be retrieved using the `query_go_db` function. + + Requirements: + - R must be installed and in the system's PATH. + - The R packages 'BiocManager', 'AnnotationDbi', and 'GO.db' must be installed. + (e.g., in R: BiocManager::install(c("AnnotationDbi", "GO.db"))) + """ + r_script = "library(GO.db); cat(columns(GO.db), sep='\\n')" + command = ["Rscript", "-e", r_script] + command_executed = " ".join(command) + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + # The columns are returned as a newline-separated string + columns_list = result.stdout.strip().split('\n') + return { + "command_executed": command_executed, + "stdout": f"Available columns: {', '.join(columns_list)}", + "stderr": result.stderr, + "columns": columns_list, + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'Rscript' not found. Please ensure R is installed and in your PATH.", + "error": "Dependency not found" + } + except subprocess.CalledProcessError as e: + stderr = e.stderr + if "there is no package called" in stderr: + stderr += "\n\nHint: Please ensure the 'GO.db' and 'AnnotationDbi' R packages are installed." + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": stderr, + "error": "R script execution failed" + } + +@mcp.tool() +def query_go_db( + go_ids: List[str], + columns: Optional[List[str]] = None, + output_file: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Query the Bioconductor GO.db to retrieve information about Gene Ontology terms. + + This tool uses R/Bioconductor's GO.db annotation package to fetch details + for a given list of GO IDs. You can specify which columns of data to retrieve. + + Requirements: + - R must be installed and in the system's PATH. + - The R packages 'BiocManager', 'AnnotationDbi', and 'GO.db' must be installed. + (e.g., in R: BiocManager::install(c("AnnotationDbi", "GO.db"))) + + Args: + go_ids: A list of Gene Ontology (GO) IDs to query (e.g., ['GO:0005575', 'GO:0008150']). + columns: A list of columns to retrieve. Defaults to ['GOID', 'TERM', 'DEFINITION', 'ONTOLOGY']. + Use the `list_go_db_columns` tool to see all available columns. + output_file: Optional path to save the output as a tab-separated (TSV) file. + If not provided, the output is returned in stdout. + """ + # --- Input Validation --- + if not go_ids: + raise ValueError("The 'go_ids' list cannot be empty.") + + for go_id in go_ids: + if not isinstance(go_id, str) or not go_id.startswith("GO:"): + raise ValueError(f"Invalid GO ID format: '{go_id}'. Expected format is 'GO:#######'.") + + if columns is None: + # Default columns provide the most common information + columns_to_query = ["GOID", "TERM", "DEFINITION", "ONTOLOGY"] + else: + if not isinstance(columns, list) or not all(isinstance(c, str) for c in columns): + raise TypeError("The 'columns' parameter must be a list of strings.") + columns_to_query = columns + + # --- R Script Construction --- + # Convert Python lists to R vector syntax: c("item1", "item2") + r_go_ids = 'c({})'.format(','.join(f'"{gid}"' for gid in go_ids)) + r_columns = 'c({})'.format(','.join(f'"{col}"' for col in columns_to_query)) + + # Using a temporary file for the output if no output_file is specified by the user + # This avoids complex stdout redirection and ensures clean capture + temp_dir = tempfile.TemporaryDirectory() + final_output_path = Path(output_file) if output_file else Path(temp_dir.name) / "go_query_results.tsv" + + r_script = f""" + # Suppress package startup messages for cleaner output + suppressPackageStartupMessages(library(AnnotationDbi)) + suppressPackageStartupMessages(library(GO.db)) + + go_keys <- {r_go_ids} + cols_to_get <- {r_columns} + output_path <- "{str(final_output_path)}" + + # Perform the query + results <- tryCatch( + AnnotationDbi::select(GO.db, keys=go_keys, columns=cols_to_get, keytype="GOID"), + error = function(e) {{ + write(paste("Error during AnnotationDbi::select:", e$message), stderr()) + return(NULL) + }} + ) + + # Write results to the specified output file + if (!is.null(results)) {{ + write.table( + results, + file=output_path, + sep="\\t", + row.names=FALSE, + quote=FALSE + ) + }} + """ + + command = ["Rscript", "-e", r_script] + command_executed = " ".join(command) + output_files_list = [] + + try: + # --- Subprocess Execution --- + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + + stdout_content = "" + if final_output_path.exists() and final_output_path.stat().st_size > 0: + output_files_list.append(str(final_output_path)) + # If no output file was requested by the user, read the temp file content for stdout + if not output_file: + stdout_content = final_output_path.read_text() + else: + # Handle cases where the R script runs but produces no output (e.g., no matches) + stdout_content = "Query executed successfully, but no results were found for the given GO IDs." + + return { + "command_executed": command_executed, + "stdout": stdout_content, + "stderr": result.stderr, + "output_files": output_files_list, + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'Rscript' not found. Please ensure R is installed and in your PATH.", + "error": "Dependency not found" + } + except subprocess.CalledProcessError as e: + stderr = e.stderr + if "there is no package called" in stderr: + stderr += "\n\nHint: Please ensure the 'GO.db' and 'AnnotationDbi' R packages are installed." + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": stderr, + "error": "R script execution failed" + } + finally: + # Clean up the temporary directory + temp_dir.cleanup() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-go.db/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-go.db/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-go.db/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..aec6ef0262e5e91545dd92f443010da377b15b4d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/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-hoodscanr via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-hoodscanr -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-hoodscanr_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-hoodscanr_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-hoodscanr_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9daf9003889c21e63bae23c5aacba5b25faf025d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py @@ -0,0 +1,340 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_hoodscanr' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def hoodscanr_estimate_radius( + input_spe_path: str, + k: int = 6, + method: str = "mean", +) -> Dict[str, Any]: + """ + Estimate the search radius for neighborhood scanning based on the k-nearest neighbors + of cells in a SpatialExperiment object. + + Args: + input_spe_path: Path to the input SpatialExperiment object saved as an RDS file. + k: Number of nearest neighbors to consider for radius estimation. Default is 6. + method: Method to summarize distances ('mean' or 'median'). Default is 'mean'. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + if k <= 0: + return {"error": "Parameter 'k' must be a positive integer."} + + if method not in ["mean", "median"]: + return {"error": "Parameter 'method' must be either 'mean' or 'median'."} + + # R script to estimate radius + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + spe <- readRDS('{input_spe_path}') + radius <- estimate_radius(spe, k = {k}, method = '{method}') + cat(radius) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"hoodscanR::estimate_radius(spe, k={k}, method='{method}')", + "estimated_radius": float(result.stdout.strip()), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + except ValueError: + return { + "error": "Could not parse radius from R output", + "stdout": result.stdout + } + +@mcp.tool() +def hoodscanr_hoodscan( + input_spe_path: str, + output_spe_path: str, + radius: float, + focal_cell_type: Optional[str] = None, + neighbor_cell_type: Optional[str] = None, +) -> Dict[str, Any]: + """ + Perform cellular neighborhood scanning on a SpatialExperiment object. + + Args: + input_spe_path: Path to the input SpatialExperiment RDS file. + output_spe_path: Path where the updated SpatialExperiment RDS file will be saved. + radius: The search radius for identifying neighbors. + focal_cell_type: Optional; specific cell type to act as the center of neighborhoods. + neighbor_cell_type: Optional; specific cell type to look for in the neighborhood. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + if radius <= 0: + return {"error": "Radius must be a positive number."} + + focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL" + neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL" + + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + spe <- readRDS('{input_spe_path}') + spe <- hoodscan(spe, radius = {radius}, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg}) + saveRDS(spe, '{output_spe_path}') + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"hoodscanR::hoodscan(spe, radius={radius})", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_spe_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def hoodscanr_calc_colocalisation( + input_spe_path: str, + output_csv_path: str, + focal_cell_type: Optional[str] = None, + neighbor_cell_type: Optional[str] = None, +) -> Dict[str, Any]: + """ + Calculate the colocalisation matrix between cell types based on neighborhood scanning results. + + Args: + input_spe_path: Path to the SpatialExperiment RDS file (must have been processed by hoodscan). + output_csv_path: Path to save the colocalisation matrix as a CSV file. + focal_cell_type: Optional; filter for specific focal cell types. + neighbor_cell_type: Optional; filter for specific neighbor cell types. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL" + neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL" + + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + spe <- readRDS('{input_spe_path}') + coloc_mat <- calc_colocalisation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg}) + write.csv(as.matrix(coloc_mat), '{output_csv_path}') + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "hoodscanR::calc_colocalisation(spe)", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_csv_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def hoodscanr_calc_spatial_correlation( + input_spe_path: str, + output_csv_path: str, + focal_cell_type: Optional[str] = None, + neighbor_cell_type: Optional[str] = None, +) -> Dict[str, Any]: + """ + Calculate the spatial correlation between cell types. + + Args: + input_spe_path: Path to the SpatialExperiment RDS file. + output_csv_path: Path to save the spatial correlation matrix as a CSV file. + focal_cell_type: Optional; filter for specific focal cell types. + neighbor_cell_type: Optional; filter for specific neighbor cell types. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL" + neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL" + + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + spe <- readRDS('{input_spe_path}') + corr_mat <- calc_spatial_correlation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg}) + write.csv(as.matrix(corr_mat), '{output_csv_path}') + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "hoodscanR::calc_spatial_correlation(spe)", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_csv_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def hoodscanr_plot_colocalisation( + input_spe_path: str, + output_plot_path: str, + focal_cell_type: Optional[str] = None, + neighbor_cell_type: Optional[str] = None, + width: int = 8, + height: int = 6, +) -> Dict[str, Any]: + """ + Generate a heatmap of the colocalisation matrix. + + Args: + input_spe_path: Path to the SpatialExperiment RDS file. + output_plot_path: Path to save the plot (e.g., 'plot.png' or 'plot.pdf'). + focal_cell_type: Optional; filter for specific focal cell types. + neighbor_cell_type: Optional; filter for specific neighbor cell types. + width: Width of the output plot in inches. Default is 8. + height: Height of the output plot in inches. Default is 6. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL" + neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL" + + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + library(ggplot2) + spe <- readRDS('{input_spe_path}') + p <- plot_colocalisation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg}) + ggsave('{output_plot_path}', plot = p, width = {width}, height = {height}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "hoodscanR::plot_colocalisation(spe)", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_plot_path] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def hoodscanr_plot_spatial_correlation( + input_spe_path: str, + output_plot_path: str, + focal_cell_type: Optional[str] = None, + neighbor_cell_type: Optional[str] = None, + width: int = 8, + height: int = 6, +) -> Dict[str, Any]: + """ + Generate a heatmap of the spatial correlation matrix. + + Args: + input_spe_path: Path to the SpatialExperiment RDS file. + output_plot_path: Path to save the plot (e.g., 'plot.png' or 'plot.pdf'). + focal_cell_type: Optional; filter for specific focal cell types. + neighbor_cell_type: Optional; filter for specific neighbor cell types. + width: Width of the output plot in inches. Default is 8. + height: Height of the output plot in inches. Default is 6. + """ + input_path = Path(input_spe_path) + if not input_path.exists(): + return {"error": f"Input file {input_spe_path} does not exist."} + + focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL" + neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL" + + r_command = f""" + library(hoodscanR) + library(SpatialExperiment) + library(ggplot2) + spe <- readRDS('{input_spe_path}') + p <- plot_spatial_correlation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg}) + ggsave('{output_plot_path}', plot = p, width = {width}, height = {height}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "hoodscanR::plot_spatial_correlation(spe)", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_plot_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-hoodscanr/app/bioconductor-hoodscanr_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..90b783133bc48ad25e3a770ac9055f1a1043897b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_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-hoodscanr/app/bioconductor-hoodscanr_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_hoodscanr' + + +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.mm.eg.db/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4bde49a4d261b08d5cc4b18d0a28d63809e09d28 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.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.mm.eg.db via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-org.mm.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.mm.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.mm.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.mm.eg.db_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_server.py b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7f3628aef9ccc1b93406e451d1d07d4ba8cd227e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_server.py @@ -0,0 +1,279 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# MCP.TOOL DECORATOR (should not be included in the final output) +# This is a placeholder for the actual decorator. +def mcp_tool_placeholder(*args, **kwargs): + def decorator(func): + return func + return decorator + +# In a real MCP environment, the following line would be: +# import mcp +# and the decorator would be @mcp.tool() +mcp = type("mcp", (), {"tool": mcp_tool_placeholder}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_org_mm_eg_db' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def org_mm_eg_db_query( + keys: List[str], + keytype: str, + columns: List[str], + output_file: Path, +) -> dict: + """ + Performs an annotation query on the org.Mm.eg.db database. + + This tool uses the AnnotationDbi::select function to retrieve annotations + for a given set of keys. It maps identifiers from a specified keytype to + the desired annotation columns. + + Args: + keys: A list of identifiers to query (e.g., ["14189", "14190"]). + keytype: The type of identifier being provided in `keys`. + Use the `org_mm_eg_db_list_keytypes` tool to see available options. + Example: "ENTREZID". + columns: A list of annotation columns to retrieve. + Use the `org_mm_eg_db_list_columns` tool to see available options. + Example: ["SYMBOL", "GENENAME", "GO"]. + output_file: The path to save the resulting annotation table in TSV format. + + Returns: + A dictionary containing the command executed, stdout, stderr, and the output file path. + """ + if not keys: + raise ValueError("The 'keys' list cannot be empty.") + if not keytype: + raise ValueError("The 'keytype' parameter cannot be empty.") + if not columns: + raise ValueError("The 'columns' list cannot be empty.") + + # Convert Python lists to R vector strings, e.g., c("item1", "item2") + r_keys = 'c({})'.format(', '.join(f'"{key}"' for key in keys)) + r_columns = 'c({})'.format(', '.join(f'"{col}"' for col in columns)) + + r_script_content = f""" + # Suppress startup messages for cleaner output + suppressPackageStartupMessages(library("AnnotationDbi")) + suppressPackageStartupMessages(library("org.Mm.eg.db")) + + # Define parameters + keys_to_query <- {r_keys} + cols_to_fetch <- {r_columns} + key_type <- "{keytype}" + output_path <- "{output_file}" + + # Perform the query + # Use tryCatch to handle potential errors, like invalid keytypes or columns + results <- tryCatch( + {{ + AnnotationDbi::select( + org.Mm.eg.db, + keys = keys_to_query, + columns = cols_to_fetch, + keytype = key_type + ) + }}, + error = function(e) {{ + stop(paste("AnnotationDbi::select failed:", e$message)) + }} + ) + + # Write the output + write.table( + results, + file = output_path, + sep = "\\t", + row.names = FALSE, + quote = FALSE + ) + """ + + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_script: + tmp_script.write(r_script_content) + script_path = tmp_script.name + + cmd = ["Rscript", script_path] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + # Clean up the temporary script file + Path(script_path).unlink() + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_file)], + } + except subprocess.CalledProcessError as e: + # Clean up the temporary script file even on error + Path(script_path).unlink() + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\n" + f"Command: {command_executed}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def org_mm_eg_db_list_columns() -> dict: + """ + Lists all available annotation columns in the org.Mm.eg.db database. + + These column names can be used in the `columns` parameter of the + `org_mm_eg_db_query` tool. + + Returns: + A dictionary containing the command executed and a list of available columns in stdout. + """ + r_script_content = """ + suppressPackageStartupMessages(library("org.Mm.eg.db")) + cat(columns(org.Mm.eg.db), sep = "\\n") + """ + cmd = ["Rscript", "-e", r_script_content] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\n" + f"Command: {command_executed}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def org_mm_eg_db_list_keytypes() -> dict: + """ + Lists all available keytypes in the org.Mm.eg.db database. + + Keytypes define the type of primary identifiers that can be used for querying + in the `keytype` parameter of the `org_mm_eg_db_query` and + `org_mm_eg_db_list_keys` tools. + + Returns: + A dictionary containing the command executed and a list of available keytypes in stdout. + """ + r_script_content = """ + suppressPackageStartupMessages(library("org.Mm.eg.db")) + cat(keytypes(org.Mm.eg.db), sep = "\\n") + """ + cmd = ["Rscript", "-e", r_script_content] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\n" + f"Command: {command_executed}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def org_mm_eg_db_list_keys( + keytype: str, + output_file: Optional[Path] = None, +) -> dict: + """ + Lists all primary keys for a given keytype from the org.Mm.eg.db database. + + Args: + keytype: The type of identifiers to retrieve. Use the + `org_mm_eg_db_list_keytypes` tool to see available options. + output_file: Optional. If provided, saves the list of keys to this file, + one key per line. Otherwise, keys are printed to stdout. + + Returns: + A dictionary containing the command executed, stdout, stderr, and the output file path if provided. + """ + if not keytype: + raise ValueError("The 'keytype' parameter cannot be empty.") + + if output_file: + r_script_content = f""" + suppressPackageStartupMessages(library("org.Mm.eg.db")) + key_type <- "{keytype}" + output_path <- "{output_file}" + keys_list <- keys(org.Mm.eg.db, keytype = key_type) + write.table( + keys_list, + file = output_path, + col.names = FALSE, + row.names = FALSE, + quote = FALSE + ) + """ + else: + r_script_content = f""" + suppressPackageStartupMessages(library("org.Mm.eg.db")) + key_type <- "{keytype}" + keys_list <- keys(org.Mm.eg.db, keytype = key_type) + cat(keys_list, sep = "\\n") + """ + + cmd = ["Rscript", "-e", r_script_content] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + output_files = [str(output_file)] if output_file else [] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\n" + f"Command: {command_executed}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..0fda34df59609c6bfa58fac6b4e3c48b1cf12d84 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-org.mm.eg.db: + build: . + image: mcp-bioconductor-org.mm.eg.db:latest + container_name: mcp-bioconductor-org.mm.eg.db + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-org.mm.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.mm.eg.db/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e0edb9c821b81883fd437f4d3d385811b12089ee --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-org.mm.eg.db + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5130cbff80b4d7fac55a35c1dd07ec607f41d272 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_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-poem/app/bioconductor-poem_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_poem' + + +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-seraster/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-seraster/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9582030e3afccb39e21c45951a522ff905f10287 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-seraster/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-seraster via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-seraster -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-seraster_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-seraster_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-seraster_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-seraster/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-seraster/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7fa3c4adf558d55a01c062934774fb43e1ec000a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-seraster/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-seraster + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7c44948db6edc3bc6b56ff992ca3f6b7ce06e115 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/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-spatialomicsoverlay via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-spatialomicsoverlay -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-spatialomicsoverlay_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-spatialomicsoverlay_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-spatialomicsoverlay_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ed788a8b75160c77c3f23ce0d698f3a184947040 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py @@ -0,0 +1,197 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, Literal, List +import textwrap + +# from mcp import tool as mcp_tool +# The @mcp.tool decorator is commented out as 'mcp' is not available for import. +# In a real MCP environment, this would be uncommented. + +# @mcp_tool() +def spatial_overlay( + spe_object: Path, + tissue_data: Path, + image_data: Path, + sample_id: str, + image_resolution: float, + output_file: Path, + fluorescence_data: Optional[Path] = None, + flip: Literal["none", "h", "v", "both"] = "none", + show_scale_bar: bool = True, + show_legend: bool = True, + point_alpha: Optional[float] = None, + point_size: Optional[float] = None, + point_color: Optional[str] = None, +) -> dict: + """ + Generates a spatial overlay plot using the Bioconductor spatialomicsOverlay package. + + This tool overlays spatial transcriptomics data (from a SpatialExperiment or Seurat + object) onto high-resolution histology images, allowing for visualization of + gene expression or other features in their spatial context. + + Args: + spe_object: Path to the input SpatialExperiment or Seurat object in RDS format. + tissue_data: Path to the tissue segmentation data in CSV or TSV format. + image_data: Path to the image data in CSV or TSV format. + sample_id: The sample ID within the object to plot. + image_resolution: The resolution of the image (e.g., pixels per micron). + output_file: Path to save the output plot (e.g., plot.png, plot.pdf). + fluorescence_data: Optional path to fluorescence data in CSV or TSV format. + flip: Image flip orientation. Can be 'none', 'h' (horizontal), 'v' (vertical), or 'both'. + show_scale_bar: If True, a scale bar is added to the plot. + show_legend: If True, a legend is added to the plot. + point_alpha: Optional opacity for the data points (value between 0.0 and 1.0). + point_size: Optional size for the data points. + point_color: Optional color for the data points (e.g., 'blue', '#FF0000'). + + Returns: + A dictionary containing the execution command, stdout, stderr, and a list of output files. + """ + # 1. Input Validation + if not spe_object.is_file(): + raise FileNotFoundError(f"Input SPE object not found: {spe_object}") + if not tissue_data.is_file(): + raise FileNotFoundError(f"Input tissue data not found: {tissue_data}") + if not image_data.is_file(): + raise FileNotFoundError(f"Input image data not found: {image_data}") + if fluorescence_data and not fluorescence_data.is_file(): + raise FileNotFoundError(f"Input fluorescence data not found: {fluorescence_data}") + + if point_alpha is not None and not (0.0 <= point_alpha <= 1.0): + raise ValueError("point_alpha must be between 0.0 and 1.0") + if point_size is not None and point_size < 0: + raise ValueError("point_size must be a non-negative number") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + # 2. R Script Generation + # This R script is generated on-the-fly to call the spatialomicsOverlay functions. + r_script_content = textwrap.dedent("""\ + suppressPackageStartupMessages(library(optparse)) + suppressPackageStartupMessages(library(spatialomicsOverlay)) + suppressPackageStartupMessages(library(SpatialExperiment)) + suppressPackageStartupMessages(library(ggplot2)) + suppressPackageStartupMessages(library(SeuratObject)) + + option_list <- list( + make_option(c("--spe_object"), type="character", help="Path to the SpatialExperiment/Seurat RDS object"), + make_option(c("--tissue_data"), type="character", help="Path to the tissue segmentation data (CSV/TSV)"), + make_option(c("--image_data"), type="character", help="Path to the image data (CSV/TSV)"), + make_option(c("--sample_id"), type="character", help="Sample ID to plot"), + make_option(c("--image_resolution"), type="double", help="Resolution of the image"), + make_option(c("--output_file"), type="character", help="Path for the output plot"), + make_option(c("--fluorescence_data"), type="character", default=NULL, help="Path to optional fluorescence data"), + make_option(c("--flip"), type="character", default="none", help="Image flip orientation (none, h, v, both)"), + make_option(c("--show_scale_bar"), action="store_true", default=FALSE, help="Flag to show scale bar"), + make_option(c("--show_legend"), action="store_true", default=FALSE, help="Flag to show legend"), + make_option(c("--point_alpha"), type="double", default=NULL, help="Alpha for points"), + make_option(c("--point_size"), type="double", default=NULL, help="Size for points"), + make_option(c("--point_color"), type="character", default=NULL, help="Color for points") + ) + + opt_parser <- OptionParser(option_list=option_list) + opt <- parse_args(opt_parser) + + if (is.null(opt$spe_object) || is.null(opt$tissue_data) || is.null(opt$image_data) || is.null(opt$sample_id) || is.null(opt$image_resolution) || is.null(opt$output_file)) { + print_help(opt_parser) + stop("Missing one or more required arguments.", call.=FALSE) + } + + # Load data + spe <- readRDS(opt$spe_object) + tissue <- read.csv(opt$tissue_data, row.names=1) + image <- read.csv(opt$image_data, row.names=1) + fluor <- if (!is.null(opt$fluorescence_data)) read.csv(opt$fluorescence_data, row.names=1) else NULL + + # Prepare additional arguments for geom_point + geom_args <- list() + if (!is.null(opt$point_alpha)) geom_args$alpha <- opt$point_alpha + if (!is.null(opt$point_size)) geom_args$size <- opt$point_size + if (!is.null(opt$point_color)) geom_args$color <- opt$point_color + + # Generate plot by calling spatialOverlay with dynamic arguments + plot_obj <- do.call(spatialOverlay, c( + list( + object = spe, + tissue = tissue, + sample = opt$sample_id, + image = image, + res = opt$image_resolution, + fluor = fluor, + flip = opt$flip, + scaleBar = opt$show_scale_bar, + legend = opt$show_legend + ), + geom_args + )) + + # Save the generated plot + ggsave(opt$output_file, plot=plot_obj, width=10, height=10, units="in") + + cat("Plot saved successfully to:", opt$output_file, "\\n") + """) + + # 3. Command Construction + r_script_path = "" + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + cmd = [ + "Rscript", + r_script_path, + "--spe_object", str(spe_object.resolve()), + "--tissue_data", str(tissue_data.resolve()), + "--image_data", str(image_data.resolve()), + "--sample_id", sample_id, + "--image_resolution", str(image_resolution), + "--output_file", str(output_file.resolve()), + "--flip", flip, + ] + + if fluorescence_data: + cmd.extend(["--fluorescence_data", str(fluorescence_data.resolve())]) + if show_scale_bar: + cmd.append("--show_scale_bar") + if show_legend: + cmd.append("--show_legend") + if point_alpha is not None: + cmd.extend(["--point_alpha", str(point_alpha)]) + if point_size is not None: + cmd.extend(["--point_size", str(point_size)]) + if point_color is not None: + cmd.extend(["--point_color", point_color]) + + # 4. Subprocess Execution + command_executed = " ".join(cmd) + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + + # 5. Structured Result Return + return { + "command_executed": command_executed, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_file.resolve())] + } + + except subprocess.CalledProcessError as e: + # Enhanced error reporting + error_message = ( + f"R script execution failed with return code {e.returncode}.\n" + f"Stderr:\n{e.stderr}\n" + f"Stdout:\n{e.stdout}" + ) + raise RuntimeError(error_message) from e + + finally: + # Clean up the temporary R script + if r_script_path and Path(r_script_path).exists(): + Path(r_script_path).unlink() diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..bcae945f30b2faacd582228dfffb8282a78327a3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_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-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_spatialomicsoverlay' + + +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-spatialomicsoverlay/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..d4c77d9fe1eb83201b0a81ba0e6f16f6c28dc02c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-spatialomicsoverlay: + build: . + image: mcp-bioconductor-spatialomicsoverlay:latest + container_name: mcp-bioconductor-spatialomicsoverlay + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-spatialomicsoverlay + 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-spatialomicsoverlay/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0ba24fdf1fe638c8118d6b31186f4e7982185968 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-spatialomicsoverlay + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_checkm-genome/Dockerfile b/Biomni/mcp_generated/mcp_checkm-genome/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..45f32a9d8a3e4b71b03206319f3bed5e2c2f8713 --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/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 checkm-genome via conda (e.g., from bioconda) +RUN conda install -c bioconda checkm-genome -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/checkm-genome_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/checkm-genome_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/checkm-genome_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_server.py b/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6b7e07b4ea76ab8b0fcfdd319332909f023741ca --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_server.py @@ -0,0 +1,1238 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any, Union + +# Helper function to execute CheckM commands +def _run_checkm_command( + command: List[str], + output_dir: Optional[Path] = None, + log_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, + pplacer_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + prodigal_path: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Internal helper function to execute CheckM commands and handle common options. + + Args: + command: A list of strings representing the CheckM subcommand and its specific arguments. + output_dir: An optional Path to a directory where the tool is expected to write output files. + Used for collecting output_files in the result. + log_file: An optional Path to a file where stdout/stderr should be redirected. + quiet: If True, suppress CheckM's stdout. + debug: If True, enable CheckM's debug mode. + force: If True, force overwrite of existing files. + tmpdir: An optional Path to a temporary directory for CheckM. + threads: Number of threads for CheckM to use. + pplacer_path: Path to the pplacer executable. + hmmer_path: Path to HMMER executables. + prodigal_path: Path to the Prodigal executable. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + cmd = ["checkm"] + command + + if quiet: + cmd.append("-q") + if debug: + cmd.append("-d") + if force: + cmd.append("--force") + if tmpdir: + if not tmpdir.is_dir(): + raise ValueError(f"Temporary directory '{tmpdir}' does not exist or is not a directory.") + cmd.extend(["--tmpdir", str(tmpdir)]) + if threads > 1: + cmd.extend(["-t", str(threads)]) + if pplacer_path: + if not pplacer_path.is_file(): + raise ValueError(f"pplacer executable '{pplacer_path}' not found.") + cmd.extend(["--pplacer_path", str(pplacer_path)]) + if hmmer_path: + if not hmmer_path.is_file(): + raise ValueError(f"HMMER executable '{hmmer_path}' not found.") + cmd.extend(["--hmmer_path", str(hmmer_path)]) + if prodigal_path: + if not prodigal_path.is_file(): + raise ValueError(f"Prodigal executable '{prodigal_path}' not found.") + cmd.extend(["--prodigal_path", str(prodigal_path)]) + + stdout_output = "" + stderr_output = "" + output_files = [] + + try: + process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + stdout_output = process.stdout + stderr_output = process.stderr + except subprocess.CalledProcessError as e: + stdout_output = e.stdout + stderr_output = e.stderr + return { + "command_executed": " ".join(cmd), + "stdout": stdout_output, + "stderr": stderr_output, + "output_files": [], + "error": str(e), + } + + if log_file: + log_file.parent.mkdir(parents=True, exist_ok=True) + with open(log_file, "w") as f: + f.write(f"Command: {' '.join(cmd)}\n\n") + f.write("STDOUT:\n") + f.write(stdout_output) + f.write("\nSTDERR:\n") + f.write(stderr_output) + + if output_dir and output_dir.is_dir(): + output_files.extend([str(p) for p in output_dir.rglob("*") if p.is_file()]) + + return { + "command_executed": " ".join(cmd), + "stdout": stdout_output, + "stderr": stderr_output, + "output_files": output_files, + } + +# Helper function to add plotting arguments +def _add_plotting_args( + cmd: List[str], + image_file: Optional[Path] = None, + image_type: Optional[str] = None, + font_size: Optional[int] = None, + dpi: Optional[int] = None, + width: Optional[float] = None, + height: Optional[float] = None, + title: Optional[str] = None, + label: Optional[str] = None, + color: Optional[str] = None, + marker_size: Optional[int] = None, + line_width: Optional[int] = None, +) -> None: + """ + Internal helper function to append common plotting arguments to a command list. + Only appends arguments if their values are explicitly provided (not None). + """ + if image_file: + cmd.extend(["--image_file", str(image_file)]) + if image_type: + cmd.extend(["--image_type", image_type]) + if font_size is not None: + cmd.extend(["--font_size", str(font_size)]) + if dpi is not None: + cmd.extend(["--dpi", str(dpi)]) + if width is not None: + cmd.extend(["--width", str(width)]) + if height is not None: + cmd.extend(["--height", str(height)]) + if title: + cmd.extend(["--title", title]) + if label: + cmd.extend(["--label", label]) + if color: + cmd.extend(["--color", color]) + if marker_size is not None: + cmd.extend(["--marker_size", str(marker_size)]) + if line_width is not None: + cmd.extend(["--line_width", str(line_width)]) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_checkm_genome' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def data( + download_folder: Optional[Path] = None, + output_file: Optional[Path] = None, + force: bool = False, + quiet: bool = False, + debug: bool = False, +) -> Dict[str, Any]: + """ + Downloads and installs the CheckM reference data. + + Args: + download_folder: Folder to download data to. If not specified, CheckM's default data folder is used. + output_file: File to write output to. If not specified, output goes to stdout. + force: Force overwrite of existing files. + quiet: Suppress stdout. + debug: Enable debug mode. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + cmd = ["data"] + + if download_folder: + download_folder.mkdir(parents=True, exist_ok=True) + cmd.extend(["--download_folder", str(download_folder)]) + + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--file", str(output_file)]) + + return _run_checkm_command( + cmd, + log_file=output_file, + force=force, + quiet=quiet, + debug=debug, + ) + + +@mcp.tool() +def lineage_wf( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + reduced_tree: bool = False, + output_alignment_files: bool = False, + output_nucleotide_sequences: bool = False, + tab_output_file: Optional[Path] = None, + skip_gtdb_r207: bool = False, + skip_gtdb_r207_cpr: bool = False, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, + pplacer_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + prodigal_path: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Performs a lineage-specific workflow for assessing genome quality. + This is the recommended workflow for most users. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + reduced_tree: Use a reduced tree for phylogenetic placement. + output_alignment_files: Output alignment files. + output_nucleotide_sequences: Output nucleotide sequences. + tab_output_file: File to write tab-separated output to. + skip_gtdb_r207: Skip GTDB R207 marker set. + skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + pplacer_path: Path to pplacer executable. + hmmer_path: Path to HMMER executables. + prodigal_path: Path to Prodigal executable. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["lineage_wf", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if reduced_tree: + cmd.append("-r") + if output_alignment_files: + cmd.append("--ali") + if output_nucleotide_sequences: + cmd.append("--nt") + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + if skip_gtdb_r207: + cmd.append("--skip_gtdb_r207") + if skip_gtdb_r207_cpr: + cmd.append("--skip_gtdb_r207_cpr") + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + pplacer_path=pplacer_path, + hmmer_path=hmmer_path, + prodigal_path=prodigal_path, + ) + + +@mcp.tool() +def taxonomy_wf( + taxonomy_level: str, + taxonomy_id: str, + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + output_alignment_files: bool = False, + output_nucleotide_sequences: bool = False, + tab_output_file: Optional[Path] = None, + skip_gtdb_r207: bool = False, + skip_gtdb_r207_cpr: bool = False, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, + pplacer_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + prodigal_path: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Performs a taxonomy-specific workflow for assessing genome quality. + + Args: + taxonomy_level: Taxonomy level (e.g., 'domain', 'phylum', 'class', 'order', 'family', 'genus', 'species'). + taxonomy_id: Taxonomy identifier (e.g., 'Bacteria', 'Firmicutes', 'Bacilli'). + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + output_alignment_files: Output alignment files. + output_nucleotide_sequences: Output nucleotide sequences. + tab_output_file: File to write tab-separated output to. + skip_gtdb_r207: Skip GTDB R207 marker set. + skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + pplacer_path: Path to pplacer executable. + hmmer_path: Path to HMMER executables. + prodigal_path: Path to Prodigal executable. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + valid_levels = ['domain', 'phylum', 'class', 'order', 'family', 'genus', 'species'] + if taxonomy_level not in valid_levels: + raise ValueError(f"Invalid taxonomy_level: '{taxonomy_level}'. Must be one of {valid_levels}.") + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["taxonomy_wf", taxonomy_level, taxonomy_id, str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if output_alignment_files: + cmd.append("--ali") + if output_nucleotide_sequences: + cmd.append("--nt") + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + if skip_gtdb_r207: + cmd.append("--skip_gtdb_r207") + if skip_gtdb_r207_cpr: + cmd.append("--skip_gtdb_r207_cpr") + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + pplacer_path=pplacer_path, + hmmer_path=hmmer_path, + prodigal_path=prodigal_path, + ) + + +@mcp.tool() +def tree_wf( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + full_tree: bool = False, + output_alignment_files: bool = False, + output_nucleotide_sequences: bool = False, + tab_output_file: Optional[Path] = None, + skip_gtdb_r207: bool = False, + skip_gtdb_r207_cpr: bool = False, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, + pplacer_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + prodigal_path: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Performs a tree-specific workflow for assessing genome quality. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + full_tree: Use a full tree for phylogenetic placement instead of the default reduced tree. + output_alignment_files: Output alignment files. + output_nucleotide_sequences: Output nucleotide sequences. + tab_output_file: File to write tab-separated output to. + skip_gtdb_r207: Skip GTDB R207 marker set. + skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + pplacer_path: Path to pplacer executable. + hmmer_path: Path to HMMER executables. + prodigal_path: Path to Prodigal executable. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["tree_wf", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if full_tree: + cmd.append("--full_tree") + if output_alignment_files: + cmd.append("--ali") + if output_nucleotide_sequences: + cmd.append("--nt") + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + if skip_gtdb_r207: + cmd.append("--skip_gtdb_r207") + if skip_gtdb_r207_cpr: + cmd.append("--skip_gtdb_r207_cpr") + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + pplacer_path=pplacer_path, + hmmer_path=hmmer_path, + prodigal_path=prodigal_path, + ) + + +@mcp.tool() +def tree( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + reduced_tree: bool = False, + output_alignment_files: bool = False, + output_nucleotide_sequences: bool = False, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, + pplacer_path: Optional[Path] = None, + hmmer_path: Optional[Path] = None, + prodigal_path: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Constructs a phylogenetic tree from a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + reduced_tree: Use a reduced tree for phylogenetic placement. + output_alignment_files: Output alignment files. + output_nucleotide_sequences: Output nucleotide sequences. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + pplacer_path: Path to pplacer executable. + hmmer_path: Path to HMMER executables. + prodigal_path: Path to Prodigal executable. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["tree", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if reduced_tree: + cmd.append("-r") + if output_alignment_files: + cmd.append("--ali") + if output_nucleotide_sequences: + cmd.append("--nt") + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + pplacer_path=pplacer_path, + hmmer_path=hmmer_path, + prodigal_path=prodigal_path, + ) + + +@mcp.tool() +def qa( + marker_gene_folder: Path, + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + skip_gtdb_r207: bool = False, + skip_gtdb_r207_cpr: bool = False, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Performs quality assessment on a set of genomes using marker genes. + + Args: + marker_gene_folder: Folder containing marker gene files (output from `checkm tree` or `checkm lineage_wf`). + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + skip_gtdb_r207: Skip GTDB R207 marker set. + skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not marker_gene_folder.is_dir(): + raise ValueError(f"Marker gene folder '{marker_gene_folder}' does not exist or is not a directory.") + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["qa", str(marker_gene_folder), str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + if skip_gtdb_r207: + cmd.append("--skip_gtdb_r207") + if skip_gtdb_r207_cpr: + cmd.append("--skip_gtdb_r207_cpr") + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def plot( + checkm_output_folder: Path, + image_folder: Path, + output_file: Optional[Path] = None, + image_file: Optional[Path] = None, + image_type: str = 'png', + font_size: int = 12, + dpi: int = 300, + width: float = 10.0, + height: float = 8.0, + title: Optional[str] = None, + label: Optional[str] = None, + color: Optional[str] = None, + marker_size: int = 10, + line_width: int = 1, + force: bool = False, + quiet: bool = False, + debug: bool = False, +) -> Dict[str, Any]: + """ + Generates various plots for visualizing genome quality. + + Args: + checkm_output_folder: Folder containing CheckM output files (e.g., from `lineage_wf`). + image_folder: Folder to write image files to. + output_file: File to write summary output to. If not specified, output goes to stdout. + image_file: File to write the main image to. If not specified, 'checkm_plot.png' in image_folder. + image_type: Image type (e.g., 'png', 'pdf', 'svg'). + font_size: Font size for plots. + dpi: DPI for plots. + width: Width of plots in inches. + height: Height of plots in inches. + title: Title for plots. + label: Label for plots. + color: Color for plots (e.g., 'red', '#FF0000'). + marker_size: Marker size for plots. + line_width: Line width for plots. + force: Force overwrite of existing files. + quiet: Suppress stdout. + debug: Enable debug mode. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not checkm_output_folder.is_dir(): + raise ValueError(f"CheckM output folder '{checkm_output_folder}' does not exist or is not a directory.") + image_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["plot", str(checkm_output_folder), str(image_folder)] + + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + + _add_plotting_args( + cmd, + image_file=image_file, + image_type=image_type, + font_size=font_size, + dpi=dpi, + width=width, + height=height, + title=title, + label=label, + color=color, + marker_size=marker_size, + line_width=line_width, + ) + + return _run_checkm_command( + cmd, + output_dir=image_folder, + log_file=output_file, + force=force, + quiet=quiet, + debug=debug, + ) + + +@mcp.tool() +def ssu_finder( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Identifies SSU rRNA genes in genome files. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["ssu_finder", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def unique_markers( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Identifies unique marker genes for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["unique_markers", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def profile( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Profiles the distribution of marker genes across a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["profile", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def tetra( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Calculates tetranucleotide frequencies for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["tetra", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def dist( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Calculates genomic characteristics distributions for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["dist", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def coverage( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + tab_output_file: Optional[Path] = None, + quiet: bool = False, + debug: bool = False, + force: bool = False, + tmpdir: Optional[Path] = None, + threads: int = 1, +) -> Dict[str, Any]: + """ + Calculates coverage statistics for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + tab_output_file: File to write tab-separated output to. + quiet: Suppress stdout. + debug: Enable debug mode. + force: Force overwrite of existing files. + tmpdir: Temporary directory. + threads: Number of threads to use. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["coverage", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + if tab_output_file: + tab_output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--tab_file", str(tab_output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + quiet=quiet, + debug=debug, + force=force, + tmpdir=tmpdir, + threads=threads, + ) + + +@mcp.tool() +def len_hist( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + image_file: Optional[Path] = None, + image_type: str = 'png', + font_size: int = 12, + dpi: int = 300, + width: float = 10.0, + height: float = 8.0, + title: Optional[str] = None, + label: Optional[str] = None, + color: Optional[str] = None, + force: bool = False, + quiet: bool = False, + debug: bool = False, +) -> Dict[str, Any]: + """ + Generates a length histogram for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + image_file: File to write the image to. If not specified, 'len_hist.png' in output_folder. + image_type: Image type (e.g., 'png', 'pdf', 'svg'). + font_size: Font size for plots. + dpi: DPI for plots. + width: Width of plots in inches. + height: Height of plots in inches. + title: Title for plots. + label: Label for plots. + color: Color for plots (e.g., 'red', '#FF0000'). + force: Force overwrite of existing files. + quiet: Suppress stdout. + debug: Enable debug mode. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["len_hist", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + + _add_plotting_args( + cmd, + image_file=image_file, + image_type=image_type, + font_size=font_size, + dpi=dpi, + width=width, + height=height, + title=title, + label=label, + color=color, + ) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + force=force, + quiet=quiet, + debug=debug, + ) + + +@mcp.tool() +def nx_plot( + genome_folder: Path, + output_folder: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + image_file: Optional[Path] = None, + image_type: str = 'png', + font_size: int = 12, + dpi: int = 300, + width: float = 10.0, + height: float = 8.0, + title: Optional[str] = None, + label: Optional[str] = None, + color: Optional[str] = None, + force: bool = False, + quiet: bool = False, + debug: bool = False, +) -> Dict[str, Any]: + """ + Generates an N50/L50 plot for a set of genomes. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_folder: Folder to write output to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + image_file: File to write the image to. If not specified, 'nx_plot.png' in output_folder. + image_type: Image type (e.g., 'png', 'pdf', 'svg'). + font_size: Font size for plots. + dpi: DPI for plots. + width: Width of plots in inches. + height: Height of plots in inches. + title: Title for plots. + label: Label for plots. + color: Color for plots (e.g., 'red', '#FF0000'). + force: Force overwrite of existing files. + quiet: Suppress stdout. + debug: Enable debug mode. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_folder.mkdir(parents=True, exist_ok=True) + + cmd = ["nx_plot", str(genome_folder), str(output_folder)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + + _add_plotting_args( + cmd, + image_file=image_file, + image_type=image_type, + font_size=font_size, + dpi=dpi, + width=width, + height=height, + title=title, + label=label, + color=color, + ) + + return _run_checkm_command( + cmd, + output_dir=output_folder, + log_file=output_file, + force=force, + quiet=quiet, + debug=debug, + ) + + +@mcp.tool() +def bin_set( + genome_folder: Path, + output_bin_set_file: Path, + extension: str = "fna", + output_file: Optional[Path] = None, + force: bool = False, + quiet: bool = False, + debug: bool = False, +) -> Dict[str, Any]: + """ + Creates a set of bins from a folder of genome files. + + Args: + genome_folder: Folder containing genome files (e.g., .fna, .fasta). + output_bin_set_file: File to write the bin set to. + extension: Extension for genome files (e.g., 'fna', 'fasta'). + output_file: File to write summary output to. If not specified, output goes to stdout. + force: Force overwrite of existing files. + quiet: Suppress stdout. + debug: Enable debug mode. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any output files. + """ + if not genome_folder.is_dir(): + raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.") + output_bin_set_file.parent.mkdir(parents=True, exist_ok=True) + + cmd = ["bin_set", str(genome_folder), str(output_bin_set_file)] + + if extension: + cmd.extend(["-x", extension]) + if output_file: + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-f", str(output_file)]) + + return _run_checkm_command( + cmd, + output_dir=output_bin_set_file.parent, + log_file=output_file, + force=force, + quiet=quiet, + debug=debug, + ) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_shim_server.py b/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8152af3dab833607c5433c9010592a149640d3b0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_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_checkm-genome/app/checkm-genome_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_checkm_genome' + + +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_checkm-genome/app/requirements.txt b/Biomni/mcp_generated/mcp_checkm-genome/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_checkm-genome/docker-compose.yml b/Biomni/mcp_generated/mcp_checkm-genome/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..827bc8b98e8dbac49cb5cdf3b5fd1c80af23be1d --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-checkm-genome: + build: . + image: mcp-checkm-genome:latest + container_name: mcp-checkm-genome + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=checkm-genome + 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_checkm-genome/environment.yaml b/Biomni/mcp_generated/mcp_checkm-genome/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..db3586dbcccb4a04ca221852c6bcb0650b5b783a --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - checkm-genome + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_checkm-genome/requirements.txt b/Biomni/mcp_generated/mcp_checkm-genome/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_checkm-genome/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_dca/Dockerfile b/Biomni/mcp_generated/mcp_dca/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..711ea217d5954a4d0531722c43adaf4b207c10a9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/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 dca via conda (e.g., from bioconda) +RUN conda install -c bioconda dca -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/dca_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/dca_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/dca_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_dca/app/dca_shim_server.py b/Biomni/mcp_generated/mcp_dca/app/dca_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e41854e851c5732a937b5bedeae771c04f8fc68f --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/app/dca_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_dca/app/dca_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_dca' + + +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_dca/app/requirements.txt b/Biomni/mcp_generated/mcp_dca/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_dca/docker-compose.yml b/Biomni/mcp_generated/mcp_dca/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..028f9bb4b02dc966952ae4a59cddb09d44657f60 --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-dca: + build: . + image: mcp-dca:latest + container_name: mcp-dca + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=dca + 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_dca/environment.yaml b/Biomni/mcp_generated/mcp_dca/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ad6dfb7df7bef5fb4ff3eb9c878ea82e1af72ea0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - dca + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_dca/requirements.txt b/Biomni/mcp_generated/mcp_dca/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_dca/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_echidna/app/echidna_server.py b/Biomni/mcp_generated/mcp_echidna/app/echidna_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7323fab0a85993b396768ad1789cc24c12a5015e --- /dev/null +++ b/Biomni/mcp_generated/mcp_echidna/app/echidna_server.py @@ -0,0 +1,140 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def tool(*args, **kwargs): + def decorator(func): + return func + return decorator + +mcp = type("mcp", (), {"tool": tool}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_echidna' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_echidna( + input_adata: Path, + output_prefix: str, + timepoint_label: str = "timepoint", + counts_layer: str = "counts", + clusters: str = "leiden", + seed: int = 42, + n_steps: int = 10000, + learning_rate: float = 0.1, + val_split: float = 0.1, + patience: int = 30, + device: str = "cpu", + verbose: bool = True, + inverse_gamma: bool = False, + eta_mean_init: float = 2.0, + lkj_concentration: float = 1.0, + q_corr_init: float = 0.01, + q_shape_rate_scaler: float = 10.0, +): + """ + Runs the Echidna model for joint probabilistic modeling of single-cell gene + expression and chromosomal copy number variation. + + This tool serves as a command-line wrapper for the `echidna` Python library, + which integrates scRNA-seq and bulk WGS data to quantify gene dosage effects. + """ + # --- Input Validation --- + if not input_adata.is_file(): + raise FileNotFoundError(f"Input AnnData file not found: {input_adata}") + + if n_steps <= 0: + raise ValueError("n_steps must be a positive integer.") + if learning_rate <= 0: + raise ValueError("learning_rate must be positive.") + if not (0.0 < val_split < 1.0): + raise ValueError("val_split must be between 0.0 and 1.0.") + if patience < 0: + raise ValueError("patience must be a non-negative integer.") + if device not in ["cpu", "cuda"]: + raise ValueError("device must be either 'cpu' or 'cuda'.") + if lkj_concentration <= 0: + raise ValueError("lkj_concentration must be positive.") + + # --- Command-line construction --- + # Echidna is a library, so we are assuming a hypothetical wrapper script + # `run_echidna_script.py` that exposes its functionality via CLI. + # This is a common pattern for converting libraries to MCP tools. + cmd = [ + "echidna_runner", # Hypothetical executable script + "--input-adata", str(input_adata), + "--output-prefix", output_prefix, + "--timepoint-label", timepoint_label, + "--counts-layer", counts_layer, + "--clusters", clusters, + "--seed", str(seed), + "--n-steps", str(n_steps), + "--learning-rate", str(learning_rate), + "--val-split", str(val_split), + "--patience", str(patience), + "--device", device, + "--eta-mean-init", str(eta_mean_init), + "--lkj-concentration", str(lkj_concentration), + "--q-corr-init", str(q_corr_init), + "--q-shape-rate-scaler", str(q_shape_rate_scaler), + ] + + if verbose: + cmd.append("--verbose") + if inverse_gamma: + cmd.append("--inverse-gamma") + + # --- Subprocess Execution --- + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: The 'echidna_runner' executable was not found. Please ensure the echidna environment is correctly configured and the wrapper script is in the system's PATH.", + "output_files": [], + "return_code": 1, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "return_code": e.returncode, + } + + # --- Structured Result Return --- + # Define expected output files based on the hypothetical wrapper's behavior. + # This typically includes the updated AnnData object, a saved model, and plots. + output_files = [ + f"{output_prefix}_adata.h5ad", + f"{output_prefix}_model.pt", + f"{output_prefix}_gene_dosage.csv", + f"{output_prefix}_gene_dosage.png", + ] + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + "return_code": 0, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_echidna/app/requirements.txt b/Biomni/mcp_generated/mcp_echidna/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_echidna/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_echidna/docker-compose.yml b/Biomni/mcp_generated/mcp_echidna/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..78fd44fdf9682830e405e76fca22a707c8231e55 --- /dev/null +++ b/Biomni/mcp_generated/mcp_echidna/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-echidna: + build: . + image: mcp-echidna:latest + container_name: mcp-echidna + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=echidna + 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_echidna/requirements.txt b/Biomni/mcp_generated/mcp_echidna/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_echidna/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gffutils/Dockerfile b/Biomni/mcp_generated/mcp_gffutils/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2e31cd043c911df0418526ab7803215b8f145169 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/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 gffutils via conda (e.g., from bioconda) +RUN conda install -c bioconda gffutils -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/gffutils_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/gffutils_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/gffutils_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gffutils/app/gffutils_server.py b/Biomni/mcp_generated/mcp_gffutils/app/gffutils_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fa435ff2d0ae7eade3c39386a4b4b44957680dce --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/app/gffutils_server.py @@ -0,0 +1,624 @@ +import subprocess +from pathlib import Path +from typing import List, Optional + +# This is a placeholder for the actual decorator as per the user's request. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +def _add_flag(cmd: list, flag: str, value: bool): + """Helper to add a boolean flag to a command list if True.""" + if value: + cmd.append(flag) + +def _add_optional_arg(cmd: list, arg_name: str, value): + """Helper to add an optional argument with its value to a command list.""" + if value is not None: + cmd.extend([arg_name, str(value)]) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_gffutils' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def gffutils_create( + db: Path, + gff_files: List[Path], + gff_dialect: Optional[str] = None, + id_spec: Optional[str] = None, + merge_strategy: str = "error", + transform: Optional[Path] = None, + force: bool = False, + verbose: bool = False, + disable_infer_transcripts: bool = False, + disable_infer_genes: bool = False, + disable_infer_extents: bool = False, + disable_infer_cds_extent: bool = False, + disable_infer_exon_extent: bool = False, + disable_infer_transcript_extent: bool = False, + disable_infer_gene_extent: bool = False, + disable_create_introns: bool = False, + disable_fix_strand: bool = False, + disable_fix_parent_child: bool = False, + disable_fix_start_stop: bool = False, + disable_fix_attributes: bool = False, + disable_fix_gff3_dialect: bool = False, + disable_fix_gtf_dialect: bool = False, + disable_dialect_check: bool = False, + disable_pragma_check: bool = False, + disable_order_check: bool = False, + disable_id_check: bool = False, + disable_feature_check: bool = False, + disable_relationship_check: bool = False, + disable_format_check: bool = False, + disable_all_fixes: bool = False, + disable_all_checks: bool = False, +): + """ + Creates a new gffutils database from GFF/GTF files. + """ + if not gff_files: + raise ValueError("At least one GFF/GTF file must be provided.") + for gff_file in gff_files: + if str(gff_file) != "-" and not gff_file.exists(): + raise FileNotFoundError(f"Input GFF/GTF file not found: {gff_file}") + if transform and not transform.exists(): + raise FileNotFoundError(f"Transform file not found: {transform}") + if merge_strategy not in ["merge", "create_unique", "error", "warning", "ignore"]: + raise ValueError(f"Invalid merge_strategy: {merge_strategy}") + + cmd = ["gffutils-cli", "create", str(db)] + cmd.extend([str(f) for f in gff_files]) + + _add_optional_arg(cmd, "--gff-dialect", gff_dialect) + _add_optional_arg(cmd, "--id-spec", id_spec) + _add_optional_arg(cmd, "--merge-strategy", merge_strategy) + _add_optional_arg(cmd, "--transform", transform) + _add_flag(cmd, "--force", force) + _add_flag(cmd, "--verbose", verbose) + + disable_flags = { + "--disable-infer-transcripts": disable_infer_transcripts, "--disable-infer-genes": disable_infer_genes, + "--disable-infer-extents": disable_infer_extents, "--disable-infer-cds-extent": disable_infer_cds_extent, + "--disable-infer-exon-extent": disable_infer_exon_extent, "--disable-infer-transcript-extent": disable_infer_transcript_extent, + "--disable-infer-gene-extent": disable_infer_gene_extent, "--disable-create-introns": disable_create_introns, + "--disable-fix-strand": disable_fix_strand, "--disable-fix-parent-child": disable_fix_parent_child, + "--disable-fix-start-stop": disable_fix_start_stop, "--disable-fix-attributes": disable_fix_attributes, + "--disable-fix-gff3-dialect": disable_fix_gff3_dialect, "--disable-fix-gtf-dialect": disable_fix_gtf_dialect, + "--disable-dialect-check": disable_dialect_check, "--disable-pragma-check": disable_pragma_check, + "--disable-order-check": disable_order_check, "--disable-id-check": disable_id_check, + "--disable-feature-check": disable_feature_check, "--disable-relationship-check": disable_relationship_check, + "--disable-format-check": disable_format_check, "--disable-all-fixes": disable_all_fixes, + "--disable-all-checks": disable_all_checks, + } + for flag, value in disable_flags.items(): + _add_flag(cmd, flag, value) + + 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": [str(db)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli create failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_update( + db: Path, + gff_files: List[Path], + gff_dialect: Optional[str] = None, + id_spec: Optional[str] = None, + merge_strategy: str = "error", + transform: Optional[Path] = None, + verbose: bool = False, + disable_infer_transcripts: bool = False, + disable_infer_genes: bool = False, + disable_infer_extents: bool = False, + disable_infer_cds_extent: bool = False, + disable_infer_exon_extent: bool = False, + disable_infer_transcript_extent: bool = False, + disable_infer_gene_extent: bool = False, + disable_create_introns: bool = False, + disable_fix_strand: bool = False, + disable_fix_parent_child: bool = False, + disable_fix_start_stop: bool = False, + disable_fix_attributes: bool = False, + disable_fix_gff3_dialect: bool = False, + disable_fix_gtf_dialect: bool = False, + disable_dialect_check: bool = False, + disable_pragma_check: bool = False, + disable_order_check: bool = False, + disable_id_check: bool = False, + disable_feature_check: bool = False, + disable_relationship_check: bool = False, + disable_format_check: bool = False, + disable_all_fixes: bool = False, + disable_all_checks: bool = False, +): + """ + Updates an existing gffutils database with new GFF/GTF files. + """ + if not db.exists(): + raise FileNotFoundError(f"Database to update not found: {db}") + if not gff_files: + raise ValueError("At least one GFF/GTF file must be provided.") + for gff_file in gff_files: + if str(gff_file) != "-" and not gff_file.exists(): + raise FileNotFoundError(f"Input GFF/GTF file not found: {gff_file}") + if transform and not transform.exists(): + raise FileNotFoundError(f"Transform file not found: {transform}") + if merge_strategy not in ["merge", "create_unique", "error", "warning", "ignore"]: + raise ValueError(f"Invalid merge_strategy: {merge_strategy}") + + cmd = ["gffutils-cli", "update", str(db)] + cmd.extend([str(f) for f in gff_files]) + + _add_optional_arg(cmd, "--gff-dialect", gff_dialect) + _add_optional_arg(cmd, "--id-spec", id_spec) + _add_optional_arg(cmd, "--merge-strategy", merge_strategy) + _add_optional_arg(cmd, "--transform", transform) + _add_flag(cmd, "--verbose", verbose) + + disable_flags = { + "--disable-infer-transcripts": disable_infer_transcripts, "--disable-infer-genes": disable_infer_genes, + "--disable-infer-extents": disable_infer_extents, "--disable-infer-cds-extent": disable_infer_cds_extent, + "--disable-infer-exon-extent": disable_infer_exon_extent, "--disable-infer-transcript-extent": disable_infer_transcript_extent, + "--disable-infer-gene-extent": disable_infer_gene_extent, "--disable-create-introns": disable_create_introns, + "--disable-fix-strand": disable_fix_strand, "--disable-fix-parent-child": disable_fix_parent_child, + "--disable-fix-start-stop": disable_fix_start_stop, "--disable-fix-attributes": disable_fix_attributes, + "--disable-fix-gff3-dialect": disable_fix_gff3_dialect, "--disable-fix-gtf-dialect": disable_fix_gtf_dialect, + "--disable-dialect-check": disable_dialect_check, "--disable-pragma-check": disable_pragma_check, + "--disable-order-check": disable_order_check, "--disable-id-check": disable_id_check, + "--disable-feature-check": disable_feature_check, "--disable-relationship-check": disable_relationship_check, + "--disable-format-check": disable_format_check, "--disable-all-fixes": disable_all_fixes, + "--disable-all-checks": disable_all_checks, + } + for flag, value in disable_flags.items(): + _add_flag(cmd, flag, value) + + 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": [str(db)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli update failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_merge( + dbs: List[Path], + output: Optional[Path] = None, + force: bool = False, +): + """ + Merges multiple gffutils databases into a single database. + """ + if not dbs: + raise ValueError("At least one database must be provided for merging.") + for db_path in dbs: + if not db_path.exists(): + raise FileNotFoundError(f"Input database not found: {db_path}") + + cmd = ["gffutils-cli", "merge"] + cmd.extend([str(db) for db in dbs]) + + _add_optional_arg(cmd, "--output", output) + _add_flag(cmd, "--force", force) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli merge failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_clean( + db: Path, + output: Optional[Path] = None, + force: bool = False, +): + """ + Cleans a gffutils database by removing orphaned features. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + + cmd = ["gffutils-cli", "clean", str(db)] + _add_optional_arg(cmd, "--output", output) + _add_flag(cmd, "--force", force) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_file = output if output else db + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_file)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli clean failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_dump( + db: Path, + output: Optional[Path] = None, + dialect: Optional[str] = None, + featuretype: Optional[str] = None, + id: Optional[str] = None, + seqid: Optional[str] = None, + start: Optional[int] = None, + end: Optional[int] = None, + strand: Optional[str] = None, + revcomp: bool = False, + limit: Optional[int] = None, + offset: Optional[int] = None, + attribute_split_char: Optional[str] = None, + attributes: Optional[str] = None, + extra_attributes: Optional[str] = None, + no_attributes: bool = False, + raw: bool = False, + table: Optional[str] = None, + fasta: Optional[Path] = None, + chrom_lookup: Optional[Path] = None, +): + """ + Dumps data from a gffutils database to various formats. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + if fasta and not fasta.exists(): + raise FileNotFoundError(f"FASTA file not found: {fasta}") + if chrom_lookup and not chrom_lookup.exists(): + raise FileNotFoundError(f"Chromosome lookup file not found: {chrom_lookup}") + if strand and strand not in ["+", "-", "."]: + raise ValueError(f"Invalid strand: {strand}. Must be '+', '-', or '.'.") + + cmd = ["gffutils-cli", "dump", str(db)] + _add_optional_arg(cmd, "--output", output) + _add_optional_arg(cmd, "--dialect", dialect) + _add_optional_arg(cmd, "--featuretype", featuretype) + _add_optional_arg(cmd, "--id", id) + _add_optional_arg(cmd, "--seqid", seqid) + _add_optional_arg(cmd, "--start", start) + _add_optional_arg(cmd, "--end", end) + _add_optional_arg(cmd, "--strand", strand) + _add_flag(cmd, "--revcomp", revcomp) + _add_optional_arg(cmd, "--limit", limit) + _add_optional_arg(cmd, "--offset", offset) + _add_optional_arg(cmd, "--attribute-split-char", attribute_split_char) + _add_optional_arg(cmd, "--attributes", attributes) + _add_optional_arg(cmd, "--extra-attributes", extra_attributes) + _add_flag(cmd, "--no-attributes", no_attributes) + _add_flag(cmd, "--raw", raw) + _add_optional_arg(cmd, "--table", table) + _add_optional_arg(cmd, "--fasta", fasta) + _add_optional_arg(cmd, "--chrom-lookup", chrom_lookup) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli dump failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_gff( + db: Path, + ids: List[str], + output: Optional[Path] = None, + parents: bool = False, + children: bool = False, + ancestors: bool = False, + descendants: bool = False, + level: Optional[int] = None, + featuretype: Optional[str] = None, + reverse: bool = False, + limit: Optional[int] = None, +): + """ + Retrieves features and their relatives from a database in GFF format. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + if not ids: + raise ValueError("At least one feature ID must be provided.") + + cmd = ["gffutils-cli", "gff", str(db)] + cmd.extend(ids) + + _add_optional_arg(cmd, "--output", output) + _add_flag(cmd, "--parents", parents) + _add_flag(cmd, "--children", children) + _add_flag(cmd, "--ancestors", ancestors) + _add_flag(cmd, "--descendants", descendants) + _add_optional_arg(cmd, "--level", level) + _add_optional_arg(cmd, "--featuretype", featuretype) + _add_flag(cmd, "--reverse", reverse) + _add_optional_arg(cmd, "--limit", limit) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli gff failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_fasta( + db: Path, + fasta: Path, + ids: List[str], + output: Optional[Path] = None, + featuretype: Optional[str] = None, + attribute: Optional[str] = None, + use_strand: bool = False, + prefix: Optional[str] = None, +): + """ + Retrieves sequences for features from a database. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + if not fasta.exists(): + raise FileNotFoundError(f"Input FASTA file not found: {fasta}") + if not ids: + raise ValueError("At least one feature ID must be provided.") + + cmd = ["gffutils-cli", "fasta", str(db)] + cmd.extend(ids) + cmd.extend(["--fasta", str(fasta)]) + + _add_optional_arg(cmd, "--output", output) + _add_optional_arg(cmd, "--featuretype", featuretype) + _add_optional_arg(cmd, "--attribute", attribute) + _add_flag(cmd, "--use-strand", use_strand) + _add_optional_arg(cmd, "--prefix", prefix) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli fasta failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_bed( + db: Path, + output: Optional[Path] = None, + featuretype: Optional[str] = None, + name_attribute: str = "ID", + score_attribute: Optional[str] = None, + thick_attribute: Optional[str] = None, + block_attribute: Optional[str] = None, + track_line: Optional[str] = None, +): + """ + Converts features from a gffutils database to BED format. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + + cmd = ["gffutils-cli", "bed", str(db)] + _add_optional_arg(cmd, "--output", output) + _add_optional_arg(cmd, "--featuretype", featuretype) + _add_optional_arg(cmd, "--name-attribute", name_attribute) + _add_optional_arg(cmd, "--score-attribute", score_attribute) + _add_optional_arg(cmd, "--thick-attribute", thick_attribute) + _add_optional_arg(cmd, "--block-attribute", block_attribute) + _add_optional_arg(cmd, "--track-line", track_line) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli bed failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_stats( + db: Path, + output: Optional[Path] = None, + feature_counts: bool = False, + attribute_counts: Optional[str] = None, +): + """ + Prints statistics about a gffutils database. + """ + if not db.exists(): + raise FileNotFoundError(f"Input database not found: {db}") + + # Default behavior is to print feature counts + if not feature_counts and not attribute_counts: + feature_counts = True + + cmd = ["gffutils-cli", "stats", str(db)] + _add_optional_arg(cmd, "--output", output) + _add_flag(cmd, "--feature-counts", feature_counts) + _add_optional_arg(cmd, "--attribute-counts", attribute_counts) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli stats failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_sort( + gff_file: Path, + output: Optional[Path] = None, + force: bool = False, + disable_infer_transcripts: bool = False, + disable_infer_genes: bool = False, + disable_infer_extents: bool = False, + disable_infer_cds_extent: bool = False, + disable_infer_exon_extent: bool = False, + disable_infer_transcript_extent: bool = False, + disable_infer_gene_extent: bool = False, + disable_create_introns: bool = False, + disable_fix_strand: bool = False, + disable_fix_parent_child: bool = False, + disable_fix_start_stop: bool = False, + disable_fix_attributes: bool = False, + disable_fix_gff3_dialect: bool = False, + disable_fix_gtf_dialect: bool = False, + disable_dialect_check: bool = False, + disable_pragma_check: bool = False, + disable_order_check: bool = False, + disable_id_check: bool = False, + disable_feature_check: bool = False, + disable_relationship_check: bool = False, + disable_format_check: bool = False, + disable_all_fixes: bool = False, + disable_all_checks: bool = False, +): + """ + Sorts a GFF/GTF file. + """ + if str(gff_file) != "-" and not gff_file.exists(): + raise FileNotFoundError(f"Input GFF/GTF file not found: {gff_file}") + + cmd = ["gffutils-cli", "sort", str(gff_file)] + _add_optional_arg(cmd, "--output", output) + _add_flag(cmd, "--force", force) + + disable_flags = { + "--disable-infer-transcripts": disable_infer_transcripts, "--disable-infer-genes": disable_infer_genes, + "--disable-infer-extents": disable_infer_extents, "--disable-infer-cds-extent": disable_infer_cds_extent, + "--disable-infer-exon-extent": disable_infer_exon_extent, "--disable-infer-transcript-extent": disable_infer_transcript_extent, + "--disable-infer-gene-extent": disable_infer_gene_extent, "--disable-create-introns": disable_create_introns, + "--disable-fix-strand": disable_fix_strand, "--disable-fix-parent-child": disable_fix_parent_child, + "--disable-fix-start-stop": disable_fix_start_stop, "--disable-fix-attributes": disable_fix_attributes, + "--disable-fix-gff3-dialect": disable_fix_gff3_dialect, "--disable-fix-gtf-dialect": disable_fix_gtf_dialect, + "--disable-dialect-check": disable_dialect_check, "--disable-pragma-check": disable_pragma_check, + "--disable-order-check": disable_order_check, "--disable-id-check": disable_id_check, + "--disable-feature-check": disable_feature_check, "--disable-relationship-check": disable_relationship_check, + "--disable-format-check": disable_format_check, "--disable-all-fixes": disable_all_fixes, + "--disable-all-checks": disable_all_checks, + } + for flag, value in disable_flags.items(): + _add_flag(cmd, flag, value) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(output)] if output else [] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"gffutils-cli sort failed with exit code {e.returncode}\n" + f"Stderr: {e.stderr}\n" + f"Stdout: {e.stdout}" + ) + +@mcp.tool() +def gffutils_validate( + gff_file: Path, + limit: Optional[int] = None, +): + """ + Validates a GFF/GTF file, reporting any errors found. + """ + if str(gff_file) != "-" and not gff_file.exists(): + raise FileNotFoundError(f"Input GFF/GTF file not found: {gff_file}") + + cmd = ["gffutils-cli", "validate", str(gff_file)] + _add_optional_arg(cmd, "--limit", limit) + + # The 'validate' command returns a non-zero exit code upon finding validation errors. + # This is expected behavior, so we do not use check=True. + result = subprocess.run(cmd, capture_output=True, text=True) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "exit_code": result.returncode, + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_gffutils/app/gffutils_shim_server.py b/Biomni/mcp_generated/mcp_gffutils/app/gffutils_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c9346fd6eb7fa24faa27e8d7af04287e385679f3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/app/gffutils_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_gffutils/app/gffutils_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_gffutils' + + +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_gffutils/app/requirements.txt b/Biomni/mcp_generated/mcp_gffutils/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_gffutils/docker-compose.yml b/Biomni/mcp_generated/mcp_gffutils/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..67ae2cd8b332e0d6da26f913571041928167367a --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-gffutils: + build: . + image: mcp-gffutils:latest + container_name: mcp-gffutils + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=gffutils + 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_gffutils/environment.yaml b/Biomni/mcp_generated/mcp_gffutils/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..64b0b539cc71a8a18f8df34cb34b9ffd952c76ac --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - gffutils + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gffutils/requirements.txt b/Biomni/mcp_generated/mcp_gffutils/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffutils/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gmap/app/gmap_server.py b/Biomni/mcp_generated/mcp_gmap/app/gmap_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b5782e6526b7eeb30d92d65d0ce168cd2bb9cda3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gmap/app/gmap_server.py @@ -0,0 +1,317 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_gmap' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def gmap_build( + db_name: str, + fasta_files: List[str], + dest_dir: Optional[str] = None, + kmer: int = 15, + sampling: int = 3, +): + """ + Builds a genome database for GMAP and GSNAP. + + Args: + db_name: Name of the genome database to create. + fasta_files: List of paths to FASTA files containing the genome sequences. + dest_dir: Destination directory for the genome database. If not provided, uses default. + kmer: K-mer size for the genomic index (default 15). + sampling: Sampling interval for the genomic index (default 3). + """ + # Input validation + if not fasta_files: + return {"error": "At least one FASTA file must be provided."} + + for f in fasta_files: + if not Path(f).exists(): + return {"error": f"FASTA file not found: {f}"} + + cmd = ["gmap_build", "-d", db_name] + + if dest_dir: + dest_path = Path(dest_dir) + dest_path.mkdir(parents=True, exist_ok=True) + cmd.extend(["-D", str(dest_path)]) + + cmd.extend(["-k", str(kmer)]) + cmd.extend(["-s", str(sampling)]) + cmd.extend(fasta_files) + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def gmap( + db_name: str, + query_file: str, + genome_dir: Optional[str] = None, + nthreads: int = 1, + output_format: str = "samse", + align_fraction: float = 1.0, + npaths: int = 5, + min_intronlength: int = 20, + max_intronlength_middle: int = 500000, + max_intronlength_ends: int = 10000, +): + """ + Genomic mapping and alignment program for mRNA and EST sequences. + + Args: + db_name: Genome database name. + query_file: Path to the mRNA/EST sequence file (FASTA/FASTQ). + genome_dir: Directory where the genome database is located. + nthreads: Number of worker threads (default 1). + output_format: Output format (samse, sampe, gff3_gene, gff3_match_cdna, gff3_match_est, etc.). + align_fraction: Align only a fraction of the given reads, selected randomly (0.0 to 1.0). + npaths: Maximum number of paths to show (default 5). + min_intronlength: Minimum intron length (default 20). + max_intronlength_middle: Max intron length for internal introns (default 500000). + max_intronlength_ends: Max intron length for end introns (default 10000). + """ + if not Path(query_file).exists(): + return {"error": f"Query file not found: {query_file}"} + + if not (0.0 <= align_fraction <= 1.0): + return {"error": "align_fraction must be between 0.0 and 1.0"} + + cmd = ["gmap", "-d", db_name, "-t", str(nthreads), "-f", output_format] + + if genome_dir: + cmd.extend(["-D", genome_dir]) + + cmd.extend(["--align-fraction", str(align_fraction)]) + cmd.extend(["-n", str(npaths)]) + cmd.extend(["--min-intronlength", str(min_intronlength)]) + cmd.extend(["--max-intronlength-middle", str(max_intronlength_middle)]) + cmd.extend(["--max-intronlength-ends", str(max_intronlength_ends)]) + cmd.append(query_file) + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def gsnap( + db_name: str, + query_file1: str, + query_file2: Optional[str] = None, + genome_dir: Optional[str] = None, + nthreads: int = 1, + output_format: str = "sam", + two_pass: bool = False, + max_insertions: int = 3, + max_deletions: int = 3, + min_coverage: float = 0.5, + align_fraction: float = 1.0, + batch: int = 2, +): + """ + Genomic Short-read Nucleotide Alignment Program (GSNAP). + + Args: + db_name: Genome database name. + query_file1: Path to the first FASTQ/FASTA file (or single-end file). + query_file2: Path to the second FASTQ/FASTA file for paired-end reads. + genome_dir: Directory where the genome database is located. + nthreads: Number of worker threads (default 1). + output_format: Output format (sam, etc.). + two_pass: Use two-pass mode to learn splice sites and indels (default False). + max_insertions: Maximum number of insertions allowed (default 3). + max_deletions: Maximum number of deletions allowed (default 3). + min_coverage: Minimum coverage required for an alignment (default 0.5). + align_fraction: Align only a fraction of the given reads (0.0 to 1.0). + batch: Batch mode (0=offsets, 1=offsets+positions, 2=offsets+positions+genome, default 2). + """ + if not Path(query_file1).exists(): + return {"error": f"Query file 1 not found: {query_file1}"} + + cmd = ["gsnap", "-d", db_name, "-t", str(nthreads), "-A", output_format] + + if genome_dir: + cmd.extend(["-D", genome_dir]) + + if two_pass: + cmd.append("--two-pass") + + cmd.extend(["--max-insertions", str(max_insertions)]) + cmd.extend(["--max-deletions", str(max_deletions)]) + cmd.extend(["--min-coverage", str(min_coverage)]) + cmd.extend(["--align-fraction", str(align_fraction)]) + cmd.extend(["-B", str(batch)]) + + cmd.append(query_file1) + if query_file2: + if not Path(query_file2).exists(): + return {"error": f"Query file 2 not found: {query_file2}"} + cmd.append(query_file2) + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def gtf_exons(gtf_file: str): + """ + Extracts exons from a GTF file for use with GMAP/GSNAP. + + Args: + gtf_file: Path to the input GTF file. + """ + if not Path(gtf_file).exists(): + return {"error": f"GTF file not found: {gtf_file}"} + + cmd = ["gtf_exons", gtf_file] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def gff3_exons(gff3_file: str): + """ + Extracts exons from a GFF3 file for use with GMAP/GSNAP. + + Args: + gff3_file: Path to the input GFF3 file. + """ + if not Path(gff3_file).exists(): + return {"error": f"GFF3 file not found: {gff3_file}"} + + cmd = ["gff3_exons", gff3_file] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def gff3_genes(gff3_file: str): + """ + Extracts genes from a GFF3 file for use with GMAP/GSNAP. + Works on NCBI and Ensembl GFF3 files. + + Args: + gff3_file: Path to the input GFF3 file. + """ + if not Path(gff3_file).exists(): + return {"error": f"GFF3 file not found: {gff3_file}"} + + cmd = ["gff3_genes", gff3_file] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +@mcp.tool() +def iit_store(input_file: str, output_file: str): + """ + Creates an Interval Index Tree (IIT) file from a text file. + Used for handling SNPs and splices in GMAP/GSNAP. + + Args: + input_file: Path to the input text file (e.g., splices or SNPs). + output_file: Path to the output .iit file. + """ + if not Path(input_file).exists(): + return {"error": f"Input file not found: {input_file}"} + + cmd = ["iit_store", "-o", output_file, input_file] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [output_file], + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e) + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_gmap/app/requirements.txt b/Biomni/mcp_generated/mcp_gmap/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gmap/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_gmap/environment.yaml b/Biomni/mcp_generated/mcp_gmap/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e3b797d385792b1faf5aa43915e9e7a5352a681f --- /dev/null +++ b/Biomni/mcp_generated/mcp_gmap/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - gmap + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gmap/requirements.txt b/Biomni/mcp_generated/mcp_gmap/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gmap/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_hisat2/Dockerfile b/Biomni/mcp_generated/mcp_hisat2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0b86be0a97e6c7083f4146fef19a90fd1701373d --- /dev/null +++ b/Biomni/mcp_generated/mcp_hisat2/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 hisat2 via conda (e.g., from bioconda) +RUN conda install -c bioconda hisat2 -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/hisat2_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/hisat2_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/hisat2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_hisat2/app/hisat2_shim_server.py b/Biomni/mcp_generated/mcp_hisat2/app/hisat2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b8ffef0f318edd7e71e423211eeeef21a08f6607 --- /dev/null +++ b/Biomni/mcp_generated/mcp_hisat2/app/hisat2_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_hisat2/app/hisat2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_hisat2' + + +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_hisat2/docker-compose.yml b/Biomni/mcp_generated/mcp_hisat2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5745e33428aaa7cfe9b6f75dda93d76d14a6ff54 --- /dev/null +++ b/Biomni/mcp_generated/mcp_hisat2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-hisat2: + build: . + image: mcp-hisat2:latest + container_name: mcp-hisat2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=hisat2 + 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_hisat2/environment.yaml b/Biomni/mcp_generated/mcp_hisat2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b8e575dd31bac67b89dcbfe6a17b1744837fdfe4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_hisat2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - hisat2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_hisat2/requirements.txt b/Biomni/mcp_generated/mcp_hisat2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_hisat2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ngmlr/environment.yaml b/Biomni/mcp_generated/mcp_ngmlr/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..55d2c98d2f67182ac3d3da0b67690e241418bd7b --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngmlr/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - ngmlr + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-font-afm/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-font-afm/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-font-afm/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-font-afm/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-font-afm/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..be29ccf2d21611fdfa51ff0f2f3198fc39b658f1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-font-afm/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-font-afm: + build: . + image: mcp-perl-font-afm:latest + container_name: mcp-perl-font-afm + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-font-afm + 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-font-afm/environment.yaml b/Biomni/mcp_generated/mcp_perl-font-afm/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c3fc76220c21b2d3ab1caa23f6dfe0eb00c9a4b9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-font-afm/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-font-afm + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-font-afm/requirements.txt b/Biomni/mcp_generated/mcp_perl-font-afm/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-font-afm/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-html-tableextract/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-html-tableextract/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a531c9fb1d32a4f3ca9be78e47323b6381d9e014 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tableextract/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-html-tableextract: + build: . + image: mcp-perl-html-tableextract:latest + container_name: mcp-perl-html-tableextract + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-html-tableextract + 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-html-tableextract/environment.yaml b/Biomni/mcp_generated/mcp_perl-html-tableextract/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b119beb2fc45b0251f11e7e704b4ed988a8d192f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tableextract/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-html-tableextract + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-html-tableextract/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-tableextract/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tableextract/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/Dockerfile b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..579757d7023147b4e4c00eb8bb2ba13bf8d4e236 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/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-lwp-mediatypes via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-lwp-mediatypes -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-lwp-mediatypes_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-lwp-mediatypes_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-lwp-mediatypes_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_server.py b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_server.py new file mode 100644 index 0000000000000000000000000000000000000000..83691cefe7892b4402e9bf498a1c9b55646dd694 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_server.py @@ -0,0 +1,256 @@ +import subprocess +import json +from pathlib import Path +from typing import List, Optional, Dict, Union, Any + +# Helper function to run Perl scripts +def _run_perl_script(script_content: str, args: List[str]) -> Dict[str, Any]: + """ + Executes a Perl script with given arguments and captures output. + """ + command = ["perl", "-MJSON", "-e", script_content, *args] + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + + stdout_data = result.stdout.strip() + stderr_data = result.stderr.strip() + + # Attempt to parse JSON output if available + parsed_output = {} + if stdout_data: + try: + parsed_output = json.loads(stdout_data) + except json.JSONDecodeError: + # If stdout is not JSON, return it as raw_stdout + parsed_output = {"raw_stdout": stdout_data} + + return { + "command_executed": " ".join(command), + "stdout": parsed_output, + "stderr": stderr_data, + "output_files": [], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": str(e), + "returncode": e.returncode, + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: 'perl' command not found. Ensure Perl is installed and in your PATH.", + "error": "Perl executable not found", + "returncode": 127, + "output_files": [], + } + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_lwp_mediatypes' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def guess_media_type_for_file_or_url( + file_or_url: str, +) -> Dict[str, Any]: + """ + Guesses the media type and encoding for a given file path or URL. + + This function uses the LWP::MediaTypes Perl module's guess_media_type function + to determine the content type and any associated content encodings. + + Args: + file_or_url: The path to a file or a URL string for which to guess the media type. + + Returns: + A dictionary containing the command executed, stdout (parsed JSON with + 'content_type' and 'content_encodings'), stderr, and any output files. + The 'content_type' will be a string like "text/html", and 'content_encodings' + will be a list of strings (e.g., ["gzip"]). If the type cannot be deduced, + it might return "text/plain" or "application/octet-stream". + """ + if not file_or_url: + raise ValueError("file_or_url cannot be empty.") + + perl_script = """ + use LWP::MediaTypes qw(guess_media_type); + use JSON; + my $input = shift @ARGV; + my ($type, @encodings) = guess_media_type($input); + my %result = ( + "content_type" => $type, + "content_encodings" => \\@encodings + ); + print encode_json(\\%result) . "\\n"; + """ + return _run_perl_script(perl_script, [file_or_url]) + + +@mcp.tool() +def get_media_suffixes( + media_types: List[str], +) -> Dict[str, Any]: + """ + Returns all file suffixes that can be used to denote the specified media type(s). + + Wildcard types (e.g., 'image/*') are supported. In a scalar context (which is + how the Perl script will effectively operate for a single type), it would return + the first suffix found. This tool returns all suffixes found for all provided types. + + Args: + media_types: A list of media type strings (e.g., ["image/*", "audio/basic", "text/html"]). + + Returns: + A dictionary containing the command executed, stdout (parsed JSON with + a 'suffixes' list), stderr, and any output files. + """ + if not media_types: + raise ValueError("media_types list cannot be empty.") + if not all(isinstance(mt, str) and mt for mt in media_types): + raise ValueError("All media_types must be non-empty strings.") + + perl_script = """ + use LWP::MediaTypes qw(media_suffix); + use JSON; + my @types = @ARGV; + my @suffixes = media_suffix(@types); + my %result = ( + "suffixes" => \\@suffixes + ); + print encode_json(\\%result) . "\\n"; + """ + return _run_perl_script(perl_script, media_types) + + +@mcp.tool() +def add_media_type_mapping( + media_type: str, + extensions: List[str], +) -> Dict[str, Any]: + """ + Associates a list of file extensions with a given media type. + + This mapping is temporary and only applies to the current Perl process execution. + Subsequent calls to other LWP::MediaTypes functions within the same process + might use this new mapping, but each MCP tool call runs in a new process. + + Args: + media_type: The media type string (e.g., "x-world/x-vrml"). + extensions: A list of file extensions (e.g., ["wrl", "vrml"]). + + Returns: + A dictionary confirming the operation, including the command executed, + stdout (parsed JSON with 'status' and 'message'), stderr, and output files. + """ + if not media_type: + raise ValueError("media_type cannot be empty.") + if not isinstance(media_type, str): + raise TypeError("media_type must be a string.") + if not extensions: + raise ValueError("extensions list cannot be empty.") + if not all(isinstance(ext, str) and ext for ext in extensions): + raise ValueError("All extensions must be non-empty strings.") + + perl_script = """ + use LWP::MediaTypes qw(add_type); + use JSON; + my $type = shift @ARGV; + my @exts = @ARGV; + add_type($type => @exts); + print encode_json({"status" => "success", "message" => "Type added: $type with extensions " . join(", ", @exts)}) . "\\n"; + """ + return _run_perl_script(perl_script, [media_type, *extensions]) + + +@mcp.tool() +def add_encoding_mapping( + encoding_type: str, + extensions: List[str], +) -> Dict[str, Any]: + """ + Associates a list of file extensions with an encoding type. + + This mapping is temporary and only applies to the current Perl process execution. + Subsequent calls to other LWP::MediaTypes functions within the same process + might use this new mapping, but each MCP tool call runs in a new process. + + Args: + encoding_type: The encoding type string (e.g., "x-gzip"). + extensions: A list of file extensions (e.g., ["gz"]). + + Returns: + A dictionary confirming the operation, including the command executed, + stdout (parsed JSON with 'status' and 'message'), stderr, and output files. + """ + if not encoding_type: + raise ValueError("encoding_type cannot be empty.") + if not isinstance(encoding_type, str): + raise TypeError("encoding_type must be a string.") + if not extensions: + raise ValueError("extensions list cannot be empty.") + if not all(isinstance(ext, str) and ext for ext in extensions): + raise ValueError("All extensions must be non-empty strings.") + + perl_script = """ + use LWP::MediaTypes qw(add_encoding); + use JSON; + my $type = shift @ARGV; + my @exts = @ARGV; + add_encoding($type => @exts); + print encode_json({"status" => "success", "message" => "Encoding added: $type with extensions " . join(", ", @exts)}) . "\\n"; + """ + return _run_perl_script(perl_script, [encoding_type, *extensions]) + + +@mcp.tool() +def read_custom_media_types_files( + media_type_files: List[Path], +) -> Dict[str, Any]: + """ + Parses one or more media types definition files and adds the type mappings found there. + + This allows loading custom or additional media type definitions beyond the default + `media.types` or `~/.media.types` files. The effect is temporary for the current + Perl process execution. + + Args: + media_type_files: A list of Path objects pointing to media types definition files. + + Returns: + A dictionary confirming the operation, including the command executed, + stdout (parsed JSON with 'status' and 'message'), stderr, and output files. + """ + if not media_type_files: + raise ValueError("media_type_files list cannot be empty.") + + file_paths_str: List[str] = [] + for f_path in media_type_files: + if not isinstance(f_path, Path): + raise TypeError(f"Expected Path object, got {type(f_path)} for {f_path}") + if not f_path.is_file(): + raise FileNotFoundError(f"Media type file not found: {f_path}") + file_paths_str.append(str(f_path)) + + perl_script = """ + use LWP::MediaTypes qw(read_media_types); + use JSON; + my @files = @ARGV; + read_media_types(@files); + print encode_json({"status" => "success", "message" => "Read media types from files: " . join(", ", @files)}) . "\\n"; + """ + return _run_perl_script(perl_script, file_paths_str) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_shim_server.py b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..893e6d2a13f7ac14ddd8afe120280c29b91eda21 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_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-lwp-mediatypes/app/perl-lwp-mediatypes_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_lwp_mediatypes' + + +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-lwp-mediatypes/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..0a9e7c60dcd8146399c76ecff94eb39255914a6d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-lwp-mediatypes: + build: . + image: mcp-perl-lwp-mediatypes:latest + container_name: mcp-perl-lwp-mediatypes + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-lwp-mediatypes + 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-lwp-mediatypes/environment.yaml b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..be8064aa5baa3cef92236097504a1e81db09e832 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-lwp-mediatypes + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/requirements.txt b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-lwp-mediatypes/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-pod-escapes/Dockerfile b/Biomni/mcp_generated/mcp_perl-pod-escapes/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7ad644bd6a34be129a6c4253b75f5fa224205512 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/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-pod-escapes via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-pod-escapes -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-pod-escapes_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-pod-escapes_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-pod-escapes_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-pod-escapes/app/perl-pod-escapes_server.py b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/perl-pod-escapes_server.py new file mode 100644 index 0000000000000000000000000000000000000000..29efbd735de58d0867885bc39736999481b07efc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/perl-pod-escapes_server.py @@ -0,0 +1,248 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Dict, Any + +# No need to import mcp, as per instructions. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_pod_escapes' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perl_pod_escapes( + program_file: Optional[Path] = None, + program_arguments: Optional[List[str]] = None, + record_separator_octal: Optional[str] = None, + autosplit: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + debug_mode: Optional[str] = None, + debugging_flags: Optional[str] = None, + execute_program_string: Optional[List[str]] = None, + execute_program_string_extended: Optional[List[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, + module_operations: 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_on_parse: 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: Optional[str] = None, + disable_all_warnings: bool = False, +) -> Dict[str, Any]: + """ + Executes the Perl interpreter with specified options, running a Perl script + or one-liner. + + This tool provides access to the command-line switches of the 'perl' + interpreter, allowing for flexible execution of Perl programs. + Note: The tool name 'perl_pod_escapes' is used as per the prompt, + but this function actually wraps the 'perl' interpreter. + + Args: + program_file: Path to the Perl script to execute. Required if no + `execute_program_string` or `execute_program_string_extended` + is provided. + program_arguments: Arguments to pass to the Perl script. + record_separator_octal: Specify record separator (e.g., '0' for NUL, '0777' for paragraph mode). + Pass an empty string ('') for just '-0'. + autosplit: Enable autosplit mode with -n or -p (splits $_ into @F). + unicode_features: Enables listed Unicode features (e.g., 'io' for UTF-8 I/O). + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). + debug_mode: Run program under debugger. Optionally specify debugger (e.g., ':MyDebugger'). + debugging_flags: Set debugging flags (argument is a bit mask or alphabets). + execute_program_string: One line of program (can be specified multiple times). + execute_program_string_extended: Like -e, but enables all optional features (can be specified multiple times). + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. + split_pattern: split() pattern for -a switch (e.g., '/\\t/'). + edit_in_place_extension: Edit <> files in place. Makes backup if extension supplied (e.g., '.bak'). + Pass an empty string ('') for just '-i'. + include_directories: Specify @INC/#include directory (can be specified multiple times). + line_ending_processing: Enable line ending processing, specifies line terminator (e.g., '0' for chomp). + Pass an empty string ('') for just '-l'. + module_operations: Execute "use/no module..." before executing program (e.g., 'strict', '-warnings'). + Can be specified multiple times. If a module name is provided without '-M' or '-m' prefix, + '-M' will be prepended by default. + loop_around_program: Assume "while (<>) { ... }" loop around program. + loop_and_print: Assume loop like -n but print line also, like sed. + rudimentary_switch_parsing: Enable rudimentary parsing for switches after programfile. + search_path_for_program: Look for programfile using PATH environment variable. + tainting_warnings: Enable tainting warnings. + tainting_checks: Enable tainting checks. + dump_core_on_parse: Dump core after parsing program. + allow_unsafe_operations: Allow unsafe operations. + print_version: Print version, patchlevel and license. + print_config_summary: Print configuration summary (or a single Config.pm variable, e.g., 'archname'). + Pass an empty string ('') for just '-V'. + enable_warnings: Enable many useful warnings. + enable_all_warnings: Enable all warnings. + ignore_text_before_shebang: Ignore text before #!perl line (optionally cd to directory). + Pass an empty string ('') for just '-x'. If a directory is provided, + Perl will cd to that directory before running the script. + disable_all_warnings: Disable all warnings. + """ + command = ["perl"] + output_files: List[Path] = [] + + # Input validation + if not program_file and not execute_program_string and not execute_program_string_extended: + raise ValueError("Either 'program_file' or at least one of " + "'execute_program_string' or 'execute_program_string_extended' must be provided.") + + if program_file and not program_file.is_file(): + raise FileNotFoundError(f"Program file not found: {program_file}") + + if loop_around_program and loop_and_print: + raise ValueError("Options '-n' (loop_around_program) and '-p' (loop_and_print) are mutually exclusive.") + + warning_flags_set = sum([enable_warnings, enable_all_warnings, disable_all_warnings]) + if warning_flags_set > 1: + raise ValueError("Only one of '-w' (enable_warnings), '-W' (enable_all_warnings), " + "or '-X' (disable_all_warnings) can be set.") + + if include_directories: + for d in include_directories: + if not d.is_dir(): + raise NotADirectoryError(f"Include directory not found: {d}") + + if ignore_text_before_shebang is not None and ignore_text_before_shebang != "": + dir_path = Path(ignore_text_before_shebang) + if not dir_path.is_dir(): + raise NotADirectoryError(f"Directory for -x option not found: {dir_path}") + + # Build command + if record_separator_octal is not None: + command.append(f"-0{record_separator_octal}") + if autosplit: + command.append("-a") + if unicode_features is not None: + command.append(f"-C{unicode_features}") + if check_syntax_only: + command.append("-c") + if debug_mode is not None: + command.append(f"-d{debug_mode}") + if debugging_flags is not None: + command.append(f"-D{debugging_flags}") + if execute_program_string: + for prog_str in execute_program_string: + command.extend(["-e", prog_str]) + if execute_program_string_extended: + for prog_str in execute_program_string_extended: + command.extend(["-E", prog_str]) + if no_sitecustomize: + command.append("-f") + if split_pattern is not None: + command.append(f"-F{split_pattern}") + if edit_in_place_extension is not None: + command.append(f"-i{edit_in_place_extension}") + # If -i is used and program_arguments are files, they are modified. + if program_arguments: + for arg in program_arguments: + arg_path = Path(arg) + # Only add existing files to output_files, as non-existent files + # would typically be created by the script, not modified in-place. + if arg_path.is_file(): + output_files.append(arg_path) + if include_directories: + for d in include_directories: + command.extend(["-I", str(d)]) + if line_ending_processing is not None: + command.append(f"-l{line_ending_processing}") + if module_operations: + for mod_op in module_operations: + # If the user provides the full flag (e.g., '-Mstrict'), use it directly. + # Otherwise, default to '-M'. + if mod_op.startswith('-M') or mod_op.startswith('-m'): + command.append(mod_op) + else: + command.append(f"-M{mod_op}") + if loop_around_program: + command.append("-n") + if loop_and_print: + command.append("-p") + if rudimentary_switch_parsing: + command.append("-s") + if search_path_for_program: + command.append("-S") + if tainting_warnings: + command.append("-t") + if tainting_checks: + command.append("-T") + if dump_core_on_parse: + command.append("-u") + if allow_unsafe_operations: + command.append("-U") + if print_version: + command.append("-v") + if print_config_summary is not None: + command.append(f"-V{print_config_summary}") + if enable_warnings: + command.append("-w") + if enable_all_warnings: + command.append("-W") + if ignore_text_before_shebang is not None: + command.append(f"-x{ignore_text_before_shebang}") + if disable_all_warnings: + command.append("-X") + + # Add program file and arguments + if program_file: + # Add '--' if there are program arguments to prevent misinterpretation of arguments as perl switches + if program_arguments: + command.append("--") + command.append(str(program_file)) + + if program_arguments: + command.extend(program_arguments) + + # Execute command + command_executed = " ".join(map(str, command)) + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'perl' command not found. Please ensure Perl is installed and in your PATH.", + "error": "Perl executable not found", + "returncode": 127, + "output_files": [] + } + + 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_perl-pod-escapes/app/perl-pod-escapes_shim_server.py b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/perl-pod-escapes_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..21f8b68c43c9cd8633fe952ba83cdc79da9f2b1d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/perl-pod-escapes_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-pod-escapes/app/perl-pod-escapes_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_pod_escapes' + + +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-pod-escapes/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-pod-escapes/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-pod-escapes/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9f2efc1a4a764c8a94133d66afad60897554bdab --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-pod-escapes: + build: . + image: mcp-perl-pod-escapes:latest + container_name: mcp-perl-pod-escapes + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-pod-escapes + 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-pod-escapes/requirements.txt b/Biomni/mcp_generated/mcp_perl-pod-escapes/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-pod-escapes/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-uri/Dockerfile b/Biomni/mcp_generated/mcp_perl-uri/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7f4cb95d79eb70a08a3b354d161e2040f947bf29 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/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-uri via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-uri -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-uri_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-uri_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-uri_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_server.py b/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ccfeaa89074b40e415f8ed8c38049d83a6965756 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_server.py @@ -0,0 +1,330 @@ +import subprocess +import json +from typing import Optional, List, Dict, Any, Union +from pathlib import Path + +def _run_perl_uri(script: str, args: List[str]) -> Dict[str, Any]: + """ + Helper to execute Perl code using the URI module and return structured results. + """ + full_script = f"use URI; use URI::file; use URI::QueryParam; use JSON::PP; {script}" + cmd = ["perl", "-e", full_script, "--"] + args + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout.strip(), + "stderr": process.stderr.strip(), + "success": True + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "success": False, + "error": str(e) + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_uri' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def uri_new( + uri_str: str, + scheme: Optional[str] = None +) -> Dict[str, Any]: + """ + Constructs a new URI object and returns its basic components (scheme, opaque, path, fragment). + + :param uri_str: The URI string to parse. + :param scheme: Optional scheme specification (used if uri_str is relative). + """ + perl_code = """ + my $u = URI->new($ARGV[0], $ARGV[1]); + my %res = ( + scheme => $u->scheme, + opaque => $u->opaque, + path => $u->path, + fragment => $u->fragment, + as_string => $u->as_string, + as_iri => $u->as_iri, + is_secure => $u->secure ? JSON::PP::true : JSON::PP::false, + has_recognized_scheme => $u->has_recognized_scheme ? JSON::PP::true : JSON::PP::false + ); + print encode_json(\%res); + """ + args = [uri_str, scheme if scheme else ""] + result = _run_perl_uri(perl_code, args) + + if result["success"] and result["stdout"]: + result["parsed"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_new_abs( + uri_str: str, + base_uri: str +) -> Dict[str, Any]: + """ + Constructs a new absolute URI object by resolving a relative URI against a base URI. + + :param uri_str: The relative or absolute URI string. + :param base_uri: The absolute base URI. + """ + perl_code = """ + my $u = URI->new_abs($ARGV[0], $ARGV[1]); + print encode_json({ absolute_uri => $u->as_string }); + """ + result = _run_perl_uri(perl_code, [uri_str, base_uri]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_canonical( + uri_str: str +) -> Dict[str, Any]: + """ + Returns a normalized (canonical) version of the URI. + + :param uri_str: The URI string to normalize. + """ + perl_code = """ + my $u = URI->new($ARGV[0])->canonical; + print encode_json({ canonical_uri => $u->as_string }); + """ + result = _run_perl_uri(perl_code, [uri_str]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_abs_rel( + uri_str: str, + base_uri: str, + mode: str = "abs" +) -> Dict[str, Any]: + """ + Returns an absolute or relative URI reference relative to a base URI. + + :param uri_str: The URI to transform. + :param base_uri: The base URI for resolution or relativization. + :param mode: Either 'abs' to get absolute URI or 'rel' to get relative URI. + """ + if mode not in ["abs", "rel"]: + return {"error": "mode must be 'abs' or 'rel'"} + + perl_code = """ + my $u = URI->new($ARGV[0]); + my $base = URI->new($ARGV[1]); + my $res = ($ARGV[2] eq 'abs') ? $u->abs($base) : $u->rel($base); + print encode_json({ result => $res->as_string }); + """ + result = _run_perl_uri(perl_code, [uri_str, base_uri, mode]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_file_conversion( + path_or_uri: str, + to_uri: bool = True, + os: Optional[str] = None, + make_absolute: bool = False +) -> Dict[str, Any]: + """ + Converts between file system paths and file:// URIs. + + :param path_or_uri: The file path (if to_uri is True) or the file URI (if to_uri is False). + :param to_uri: If True, converts path to URI. If False, converts URI to path. + :param os: Optional OS type (e.g., 'Unix', 'Win32', 'Mac'). + :param make_absolute: If True and to_uri is True, creates an absolute file URI. + """ + if to_uri: + # Path to URI + perl_code = """ + my $u; + if ($ARGV[2] eq '1') { + $u = URI::file->new_abs($ARGV[0], $ARGV[1]); + } else { + $u = URI::file->new($ARGV[0], $ARGV[1]); + } + print encode_json({ uri => $u->as_string }); + """ + args = [path_or_uri, os if os else "", "1" if make_absolute else "0"] + else: + # URI to Path + perl_code = """ + my $u = URI->new($ARGV[0]); + my $path = $u->file($ARGV[1]); + print encode_json({ path => $path }); + """ + args = [path_or_uri, os if os else ""] + + result = _run_perl_uri(perl_code, args) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_query_params( + uri_str: str, + action: str = "get", + key: Optional[str] = None, + values: Optional[List[str]] = None +) -> Dict[str, Any]: + """ + Manages individual query parameters of a URI using URI::QueryParam. + + :param uri_str: The URI string. + :param action: 'get' (return values for key), 'set' (replace values for key), + 'append' (add values for key), 'delete' (remove key), + 'list_keys' (return all keys). + :param key: The parameter key to operate on. + :param values: List of values for 'set' or 'append' actions. + """ + perl_code = """ + my $u = URI->new($ARGV[0]); + my $action = $ARGV[1]; + my $key = $ARGV[2]; + my @vals = split('\\0', $ARGV[3] // ''); + + my %res; + if ($action eq 'get') { + my @v = $u->query_param($key); + $res{values} = \@v; + } elsif ($action eq 'set') { + $u->query_param($key, @vals); + $res{new_uri} = $u->as_string; + } elsif ($action eq 'append') { + $u->query_param_append($key, @vals); + $res{new_uri} = $u->as_string; + } elsif ($action eq 'delete') { + my @old = $u->query_param_delete($key); + $res{deleted_values} = \@old; + $res{new_uri} = $u->as_string; + } elsif ($action eq 'list_keys') { + my @keys = $u->query_param; + $res{keys} = \@keys; + } + print encode_json(\%res); + """ + val_str = "\0".join(values) if values else "" + args = [uri_str, action, key if key else "", val_str] + + result = _run_perl_uri(perl_code, args) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_query_form( + uri_str: str, + new_form: Optional[Dict[str, Union[str, List[str]]]] = None, + delimiter: str = "&" +) -> Dict[str, Any]: + """ + Sets or gets the entire query component as a form (application/x-www-form-urlencoded). + + :param uri_str: The URI string. + :param new_form: Optional dictionary to set as the new query form. + :param delimiter: Delimiter for the query string (usually '&' or ';'). + """ + perl_code = """ + my $u = URI->new($ARGV[0]); + my $json_form = $ARGV[1]; + my $delim = $ARGV[2] || '&'; + + my @old_form = $u->query_form; + + if ($json_form) { + my $hash = decode_json($json_form); + $u->query_form($hash, $delim); + } + + print encode_json({ + old_form => \@old_form, + current_uri => $u->as_string, + query_string => $u->query + }); + """ + form_json = json.dumps(new_form) if new_form else "" + result = _run_perl_uri(perl_code, [uri_str, form_json, delimiter]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_server_info( + uri_str: str +) -> Dict[str, Any]: + """ + Extracts server-related components from the URI (authority, userinfo, host, port). + + :param uri_str: The URI string. + """ + perl_code = """ + my $u = URI->new($ARGV[0]); + my %res; + eval { + $res{authority} = $u->authority; + $res{userinfo} = $u->userinfo; + $res{host} = $u->host; + $res{ihost} = $u->ihost; + $res{port} = $u->port; + $res{host_port} = $u->host_port; + }; + if ($@) { + $res{error} = "Scheme does not support server methods: $@"; + } + print encode_json(\%res); + """ + result = _run_perl_uri(perl_code, [uri_str]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +@mcp.tool() +def uri_path_segments( + uri_str: str, + new_segments: Optional[List[str]] = None +) -> Dict[str, Any]: + """ + Gets or sets the path segments of a hierarchical URI. + + :param uri_str: The URI string. + :param new_segments: Optional list of segments to set. + """ + perl_code = """ + my $u = URI->new($ARGV[0]); + my $json_segs = $ARGV[1]; + + if ($json_segs) { + my $segs = decode_json($json_segs); + $u->path_segments(@$segs); + } + + my @current = $u->path_segments; + print encode_json({ + segments => \@current, + path => $u->path, + uri => $u->as_string + }); + """ + segs_json = json.dumps(new_segments) if new_segments else "" + result = _run_perl_uri(perl_code, [uri_str, segs_json]) + if result["success"] and result["stdout"]: + result["data"] = json.loads(result["stdout"]) + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_shim_server.py b/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b4d4fff255cb2a94bf2825004c9d9e369159c855 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/app/perl-uri_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-uri/app/perl-uri_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_uri' + + +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-uri/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-uri/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-uri/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-uri/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..96c8b30739f5a06fae2111e4baff5261c386c608 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-uri: + build: . + image: mcp-perl-uri:latest + container_name: mcp-perl-uri + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-uri + 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-uri/environment.yaml b/Biomni/mcp_generated/mcp_perl-uri/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c03c680d489684cca387a6ab52f48a0316e246a7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-uri + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-uri/requirements.txt b/Biomni/mcp_generated/mcp_perl-uri/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-uri/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_scvis_galaxy/Dockerfile b/Biomni/mcp_generated/mcp_scvis_galaxy/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a66ddeedb152db99498e252fb2f4dbae9d8fb585 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/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 scvis_galaxy via conda (e.g., from bioconda) +RUN conda install -c bioconda scvis_galaxy -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/scvis_galaxy_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/scvis_galaxy_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/scvis_galaxy_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scvis_galaxy/app/requirements.txt b/Biomni/mcp_generated/mcp_scvis_galaxy/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_server.py b/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f753f97671464a776ec9e9115d11a3eb2d06221d --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_server.py @@ -0,0 +1,242 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def tool(*args, **kwargs): + def decorator(func): + return func + return decorator + +mcp = type("mcp", (), {"tool": tool}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_scvis_galaxy' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def scvis_train( + data_matrix_file: Path, + out_dir: Optional[Path] = None, + data_label_file: Optional[Path] = None, + config_file: Optional[Path] = None, + normalize: Optional[float] = None, + verbose: bool = False, + verbose_interval: int = 50, + show_plot: bool = False, +) -> Dict[str, Any]: + """ + Learns a probabilistic parametric mapping for dimension reduction using scvis. + + This function corresponds to the 'scvis train' command. It takes a high-dimensional + data matrix and learns a low-dimensional embedding. + + Args: + data_matrix_file: Path to a high-dimensional data matrix in tab-delimited format. + The first row should be column names. Each row represents a data point. + out_dir: Optional path for output files. If not provided, a temporary directory will be created. + data_label_file: Optional path to a one-column file (with header) providing cluster + information for each data point, used for coloring scatter plots. + config_file: Optional path to a custom YAML configuration file. If not provided, + scvis uses its default configuration. + normalize: Optional positive float number for normalization. If not set, scvis + normalizes by the maximum absolute value. + verbose: If True, the program will print progress information to the screen. + verbose_interval: The mini-batch interval to show running information. + show_plot: If True, plot intermediate embeddings during optimization. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output file paths. + """ + # --- Input Validation --- + if not data_matrix_file.is_file(): + raise FileNotFoundError(f"Input data matrix file not found: {data_matrix_file}") + if data_label_file and not data_label_file.is_file(): + raise FileNotFoundError(f"Data label file not found: {data_label_file}") + if config_file and not config_file.is_file(): + raise FileNotFoundError(f"Config file not found: {config_file}") + if normalize is not None and normalize <= 0: + raise ValueError("The 'normalize' value must be a positive float.") + if verbose_interval <= 0: + raise ValueError("'verbose_interval' must be a positive integer.") + + # --- Command Construction --- + work_dir_context = tempfile.TemporaryDirectory() if out_dir is None else nullcontext(out_dir) + + with work_dir_context as temp_dir: + output_directory = Path(temp_dir) + if out_dir: + output_directory.mkdir(parents=True, exist_ok=True) + + command = [ + "scvis", "train", + "--data_matrix_file", str(data_matrix_file), + "--out_dir", str(output_directory), + "--verbose_interval", str(verbose_interval), + ] + + if data_label_file: + command.extend(["--data_label_file", str(data_label_file)]) + if config_file: + command.extend(["--config_file", str(config_file)]) + if normalize is not None: + command.extend(["--normalize", str(normalize)]) + if verbose: + command.append("--verbose") + if show_plot: + command.append("--show_plot") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + stdout = process.stdout + stderr = process.stderr + + # --- Collect Output Files --- + output_files = [str(f) for f in output_directory.glob('**/*') if f.is_file()] + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"scvis train failed with exit code {e.returncode}", + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "scvis command not found. Please ensure it is installed and in your PATH.", + "error": "Executable not found.", + "output_files": [] + } + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + + +@mcp.tool() +def scvis_map( + data_matrix_file: Path, + pretrained_model_file: Path, + out_dir: Optional[Path] = None, + config_file: Optional[Path] = None, + normalize: Optional[float] = None, +) -> Dict[str, Any]: + """ + Maps new data to an existing embedding using a pre-trained scvis model. + + This function corresponds to the 'scvis map' command. It requires a pre-trained + model from 'scvis train' to project new high-dimensional data into the + learned low-dimensional space. + + Args: + data_matrix_file: Path to a new high-dimensional data matrix to be mapped. + pretrained_model_file: Path to a pre-trained scvis model checkpoint file prefix + (e.g., /path/to/model/model.ckpt). + out_dir: Optional path for output files. If not provided, a temporary directory will be created. + config_file: Optional path to a custom YAML configuration file. This should be the + same configuration used for training the model. + normalize: Optional positive float number for normalization. This should be consistent + with the normalization used during the training step. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output file paths. + """ + # --- Input Validation --- + if not data_matrix_file.is_file(): + raise FileNotFoundError(f"Input data matrix file not found: {data_matrix_file}") + + # The tool expects a checkpoint prefix. We check if the directory exists. + # A full check would involve looking for .meta, .index, .data files. + if not pretrained_model_file.parent.is_dir(): + raise FileNotFoundError(f"Directory for pretrained model not found: {pretrained_model_file.parent}") + + if config_file and not config_file.is_file(): + raise FileNotFoundError(f"Config file not found: {config_file}") + if normalize is not None and normalize <= 0: + raise ValueError("The 'normalize' value must be a positive float.") + + # --- Command Construction --- + work_dir_context = tempfile.TemporaryDirectory() if out_dir is None else nullcontext(out_dir) + + with work_dir_context as temp_dir: + output_directory = Path(temp_dir) + if out_dir: + output_directory.mkdir(parents=True, exist_ok=True) + + command = [ + "scvis", "map", + "--data_matrix_file", str(data_matrix_file), + "--pretrained_model_file", str(pretrained_model_file), + "--out_dir", str(output_directory), + ] + + if config_file: + command.extend(["--config_file", str(config_file)]) + if normalize is not None: + command.extend(["--normalize", str(normalize)]) + + # --- Subprocess Execution --- + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + stdout = process.stdout + stderr = process.stderr + + # --- Collect Output Files --- + output_files = [str(f) for f in output_directory.glob('**/*') if f.is_file()] + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"scvis map failed with exit code {e.returncode}", + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "scvis command not found. Please ensure it is installed and in your PATH.", + "error": "Executable not found.", + "output_files": [] + } + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +# Helper for context management +class nullcontext: + def __init__(self, enter_result=None): + self.enter_result = enter_result + def __enter__(self): + return self.enter_result + def __exit__(self, *exc): + pass + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_shim_server.py b/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4e63e3a03187477d09ec71f16d3660c5029397e7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/app/scvis_galaxy_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_scvis_galaxy/app/scvis_galaxy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_scvis_galaxy' + + +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_scvis_galaxy/docker-compose.yml b/Biomni/mcp_generated/mcp_scvis_galaxy/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a9bbb30336584dffbf8ce29bb8cb45fdd5b2bca7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-scvis_galaxy: + build: . + image: mcp-scvis_galaxy:latest + container_name: mcp-scvis_galaxy + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=scvis_galaxy + 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_scvis_galaxy/environment.yaml b/Biomni/mcp_generated/mcp_scvis_galaxy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3367b37e8ad7e0826d2f3e06461d89ddea471c24 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scvis_galaxy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - scvis_galaxy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_segemehl/Dockerfile b/Biomni/mcp_generated/mcp_segemehl/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c286972968f64ec6200f4b116e68db2f7e69f66c --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/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 segemehl via conda (e.g., from bioconda) +RUN conda install -c bioconda segemehl -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/segemehl_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/segemehl_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/segemehl_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_segemehl/app/requirements.txt b/Biomni/mcp_generated/mcp_segemehl/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_segemehl/app/segemehl_server.py b/Biomni/mcp_generated/mcp_segemehl/app/segemehl_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7650001dbed80e5d359ea1dff377335b37f71985 --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/app/segemehl_server.py @@ -0,0 +1,338 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Union + +# Helper function to validate input files +def _validate_input_files(files: Union[Path, List[Path]]): + """ + Validates that all provided input file paths exist and are files. + Raises FileNotFoundError if any file is not found. + """ + if isinstance(files, Path): + files = [files] + for f in files: + if not f.is_file(): + raise FileNotFoundError(f"Input file not found: {f}") + +# Helper function to validate output file paths +def _validate_output_path(file_path: Path): + """ + Ensures the parent directory for an output file exists, creating it if necessary. + """ + if file_path.parent: + file_path.parent.mkdir(parents=True, exist_ok=True) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_segemehl' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def segemehl_generate_index( + database_files: List[Path], + output_index_file: Path, + bisulfite_index: bool = False, + threads: int = 1, +) -> dict: + """ + Generates index structures for reference sequences using segemehl.x. + + This function builds the enhanced suffix array (ESA) index for one or more + reference FASTA files, which is required before mapping reads. It supports + both standard and bisulfite-specific index generation. + + Args: + database_files: A list of paths to reference genome FASTA files. + These files will be concatenated internally to build the index. + output_index_file: The path where the generated index file will be stored. + bisulfite_index: If True, generates a bisulfite-specific index using the '-y' option. + If False (default), generates a standard index using the '-x' option. + threads: The number of parallel threads to use for index generation (default: 1). + Must be a positive integer. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + In case of an error, it includes an 'error' key with details. + """ + _validate_input_files(database_files) + _validate_output_path(output_index_file) + + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + command = ["segemehl.x"] + if bisulfite_index: + command.extend(["-y", str(output_index_file)]) + else: + command.extend(["-x", str(output_index_file)]) + + command.extend(["-d"]) + command.extend([str(f) for f in database_files]) + + if threads > 1: + command.extend(["-t", str(threads)]) + + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(command), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_index_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e), + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "segemehl.x command not found. Please ensure segemehl is installed and in your PATH.", + "output_files": [], + "error": "segemehl.x not found", + } + + +@mcp.tool() +def segemehl_map_reads( + index_file: Path, + database_files: List[Path], + query_file: Path, + mate_file: Optional[Path] = None, + max_insert_size: Optional[int] = None, + read_group_file: Optional[Path] = None, + read_group_id: Optional[str] = None, + threads: int = 1, + full_name_in_sam: bool = False, + max_occurrences: Optional[int] = None, + e_value: Optional[float] = None, + differences: Optional[int] = None, + accuracy: Optional[float] = None, + hit_strategy: int = 1, # 0: all, 1: best-only + bisulfite_mode: int = 0, # 0: off, 1: methylC-seq, 2: bs-seq + split_read_basename: Optional[str] = None, + output_bam: bool = False, + show_progress: bool = False, + output_file: Optional[Path] = None, + unmatched_reads_file: Optional[Path] = None, + brief_cigar: bool = False, + output_meop: bool = False, + file_bins_basename: Optional[str] = None, # Deprecated + sort_output: bool = False, # Deprecated + min_fragment_score: Optional[int] = None, + min_fragment_length: Optional[int] = None, + min_splice_cover: Optional[int] = None, +) -> dict: + """ + Aligns short reads to a reference genome using segemehl.x. + + This function provides comprehensive options for mapping single-end or paired-end + reads, including support for bisulfite converted DNA and split-read alignments. + Output can be in SAM or BAM format. + + Args: + index_file: Path to the pre-built database index file (e.g., generated by segemehl_generate_index). + database_files: A list of paths to reference genome FASTA files. + Crucially, these must be provided in the exact same order as they were + used during index generation to avoid MD5 key mismatches. + query_file: Path to the query sequences (FASTA or FASTQ format). For paired-end data, + this should be the first mate file. + mate_file: Optional path to the second mate sequences (FASTA or FASTQ) for paired-end mapping. + max_insert_size: Optional maximum insert size for paired-end mapping. This parameter + primarily influences the internal algorithmics and mapping time, not the output. + Must be non-negative. + read_group_file: Optional path to a file containing a SAM format @RG header. + read_group_id: Optional user-defined read group ID string to assign to all aligned reads. + threads: The number of parallel threads to use for mapping (default: 1). Must be at least 1. + full_name_in_sam: If True, writes the full fastq/fasta name to the SAM output instead of just the ID. + max_occurrences: Optional maximum number of occurrences for seeds. Seeds with more occurrences + than this value will be discarded. Must be non-negative. + e_value: Optional maximum score-based E-value for seeds. Increasing this value can lead + to higher sensitivity. Must be non-negative. + differences: Optional maximum number of differences (mismatches, insertions, deletions) + allowed within a single seed. Higher values increase sensitivity but impact runtime. + For reads >=100bp, -D 0 is often sufficient. Must be non-negative. + accuracy: Optional minimum alignment accuracy (in percent). Reads with a best alignment + below this threshold will be discarded. Must be between 0.0 and 100.0. + hit_strategy: Controls which alignments are reported (0: all alignments that pass -A criterion, + 1: only the best-scoring alignment for each read). Default is 1 (best-only). + bisulfite_mode: Enables alignment of bisulfite converted DNA sequences (0: off, 1: methylC-seq protocol, + 2: bs-seq protocol). Default is 0. + split_read_basename: Optional basename string to trigger split read alignment mode. If provided, + additional BED files (*.sngl.bed, *.mult.bed) and a custom text file (*.trns.txt) + will be generated with this basename. + output_bam: If True, the output will be in BAM format instead of SAM. Note that BAM output + might take slightly longer. + show_progress: If True, a progress bar will be displayed on stderr during the alignment run. + output_file: Optional path to write the SAM or BAM output. If not provided, output goes to stdout. + unmatched_reads_file: Optional path to a file where unmapped FASTA or FASTQ reads will be dumped. + brief_cigar: If True, uses a brief cigar string ('M' for matches/mismatches) instead of the + extended cigar string ('=' for matches, 'X' for mismatches). + output_meop: If True, attaches a multi edit operation string (XE:Z:) as a custom key/value pair + to each SAM alignment, which can be useful for variance calling. + file_bins_basename: (Deprecated) Optional basename for temporary file bins. This option is deprecated. + sort_output: (Deprecated) If True, the output will be sorted. This option is deprecated. + min_fragment_score: Optional minimum score required for an individual split fragment + (only applicable when `split_read_basename` is used). Must be non-negative. + min_fragment_length: Optional minimum length required for an individual split fragment + (only applicable when `split_read_basename` is used). Must be non-negative. + min_splice_cover: Optional minimum percentage of the original read that must be covered by + a split alignment for it to be accepted (only applicable when `split_read_basename` is used). + Must be between 0 and 100. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + In case of an error, it includes an 'error' key with details. + """ + # Input file validation + _validate_input_files([index_file, query_file] + database_files) + if mate_file: + _validate_input_files(mate_file) + if read_group_file: + _validate_input_files(read_group_file) + + # Output path validation (ensure parent directories exist) + if output_file: + _validate_output_path(output_file) + if unmatched_reads_file: + _validate_output_path(unmatched_reads_file) + if split_read_basename: + _validate_output_path(Path(split_read_basename).parent) + if file_bins_basename: # Deprecated, but still validate path if used + _validate_output_path(Path(file_bins_basename).parent) + + # Parameter value validation + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + if max_insert_size is not None and max_insert_size < 0: + raise ValueError("Maximum insert size must be non-negative.") + if max_occurrences is not None and max_occurrences < 0: + raise ValueError("Maximum occurrences must be non-negative.") + if e_value is not None and e_value < 0.0: + raise ValueError("E-value must be non-negative.") + if differences is not None and differences < 0: + raise ValueError("Differences must be non-negative.") + if accuracy is not None and not (0.0 <= accuracy <= 100.0): + raise ValueError("Accuracy must be between 0.0 and 100.0.") + if hit_strategy not in [0, 1]: + raise ValueError("Hit strategy must be 0 (all) or 1 (best-only).") + if bisulfite_mode not in [0, 1, 2]: + raise ValueError("Bisulfite mode must be 0 (off), 1 (methylC-seq), or 2 (bs-seq).") + if min_fragment_score is not None and min_fragment_score < 0: + raise ValueError("Minimum fragment score must be non-negative.") + if min_fragment_length is not None and min_fragment_length < 0: + raise ValueError("Minimum fragment length must be non-negative.") + if min_splice_cover is not None and not (0 <= min_splice_cover <= 100): + raise ValueError("Minimum splice cover must be between 0 and 100.") + + command = ["segemehl.x"] + + command.extend(["-i", str(index_file)]) + command.extend(["-d"]) + command.extend([str(f) for f in database_files]) + command.extend(["-q", str(query_file)]) + + if mate_file: + command.extend(["-p", str(mate_file)]) + if max_insert_size is not None: + command.extend(["-I", str(max_insert_size)]) + if read_group_file: + command.extend(["-G", str(read_group_file)]) + if read_group_id: + command.extend(["-g", read_group_id]) + if threads > 1: + command.extend(["-t", str(threads)]) + if full_name_in_sam: + command.append("-f") + if max_occurrences is not None: + command.extend(["-M", str(max_occurrences)]) + if e_value is not None: + command.extend(["-E", str(e_value)]) + if differences is not None: + command.extend(["-D", str(differences)]) + if accuracy is not None: + command.extend(["-A", str(accuracy)]) + if hit_strategy == 0: # Default is 1 (best-only), so only add if 0 (all) + command.extend(["-H", str(hit_strategy)]) + if bisulfite_mode > 0: + command.extend(["-F", str(bisulfite_mode)]) + if split_read_basename: + command.extend(["-S", split_read_basename]) + if min_fragment_score is not None: + command.extend(["-U", str(min_fragment_score)]) + if min_fragment_length is not None: + command.extend(["-Z", str(min_fragment_length)]) + if min_splice_cover is not None: + command.extend(["-W", str(min_splice_cover)]) + if output_bam: + command.append("-b") + if show_progress: + command.append("-s") # Note: -s is also for --progressbar + if unmatched_reads_file: + command.extend(["-u", str(unmatched_reads_file)]) + if brief_cigar: + command.append("-e") + if output_meop: + command.append("-V") + if file_bins_basename: # Deprecated + command.extend(["-B", file_bins_basename]) + if sort_output: # Deprecated + command.append("-O") + + output_files = [] + if output_file: + command.extend(["-o", str(output_file)]) + output_files.append(str(output_file)) + + if unmatched_reads_file: + output_files.append(str(unmatched_reads_file)) + + if split_read_basename: + # These files are automatically generated by segemehl in split-read mode + output_files.append(str(Path(split_read_basename).with_suffix(".sngl.bed"))) + output_files.append(str(Path(split_read_basename).with_suffix(".mult.bed"))) + output_files.append(str(Path(split_read_basename).with_suffix(".trns.txt"))) + + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": " ".join(command), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e), + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "segemehl.x command not found. Please ensure segemehl is installed and in your PATH.", + "output_files": [], + "error": "segemehl.x not found", + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_segemehl/app/segemehl_shim_server.py b/Biomni/mcp_generated/mcp_segemehl/app/segemehl_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e91ddcc38f96bd9d2e236a253c963b3850c1782e --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/app/segemehl_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_segemehl/app/segemehl_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_segemehl' + + +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_segemehl/docker-compose.yml b/Biomni/mcp_generated/mcp_segemehl/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e3a912571ee0f167ac6ddc04c5bc9a2ec8666f71 --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-segemehl: + build: . + image: mcp-segemehl:latest + container_name: mcp-segemehl + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=segemehl + 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_segemehl/environment.yaml b/Biomni/mcp_generated/mcp_segemehl/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2aa2084a5e0a0dfd48b004a229d2bee538650c09 --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - segemehl + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_segemehl/requirements.txt b/Biomni/mcp_generated/mcp_segemehl/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_segemehl/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_sentieon/Dockerfile b/Biomni/mcp_generated/mcp_sentieon/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b89c074cefc89bbcce968d131a0c3ad6e941c9b1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/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 sentieon via conda (e.g., from bioconda) +RUN conda install -c bioconda sentieon -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/sentieon_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/sentieon_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/sentieon_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sentieon/app/requirements.txt b/Biomni/mcp_generated/mcp_sentieon/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_sentieon/app/sentieon_server.py b/Biomni/mcp_generated/mcp_sentieon/app/sentieon_server.py new file mode 100644 index 0000000000000000000000000000000000000000..15e21293875222a7f0990ac35e0bd89ea0c58eba --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/app/sentieon_server.py @@ -0,0 +1,164 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Dict, Any + +# No mcp import needed as per instructions + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_sentieon' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def sentieon_run_command( + subcommand_and_args: List[str], + sentieon_executable: Path = Path("sentieon"), + num_threads: Optional[int] = None, + license_server: Optional[str] = None, + log_file: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Executes a generic Sentieon command with specified subcommand and arguments. + + WARNING: The provided documentation (conda search --info) does not contain + details about Sentieon's specific subcommands (e.g., 'bwa', 'haplotyper', 'dedup') + or their individual parameters. This function serves as a generic wrapper + for the main 'sentieon' executable. + + To create more specific and type-safe MCP tools for Sentieon's subcommands, + detailed command-line interface documentation (e.g., `sentieon --help` + output or official Sentieon documentation) is required. + + Args: + subcommand_and_args: A list of strings representing the Sentieon subcommand + and its specific arguments. For example: + `["bwa", "-i", "input.fastq", "-o", "output.bam"]` + or `["haplotyper", "-r", "ref.fasta", "-i", "input.bam", "-o", "output.vcf"]`. + This parameter is mandatory and cannot be empty. + sentieon_executable: Path to the Sentieon executable. Defaults to "sentieon" + assuming it's in the system's PATH. The path will be + validated to ensure the executable exists. + num_threads: Number of threads to use for the Sentieon process. + Corresponds to the global `-t` option. Must be a positive integer. + license_server: License server address. Corresponds to the global `-l` option. + e.g., "127.0.0.1:8990" + log_file: Path to a file where Sentieon should write its log messages. + Corresponds to the global `-log` option. The parent directory + will be created if it doesn't exist. + verbose: Enable verbose output. Corresponds to the global `-v` option. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any + identified output files. In this generic function, `output_files` will + typically be empty as specific output paths cannot be inferred. + + Raises: + ValueError: If `subcommand_and_args` is empty or `num_threads` is invalid. + FileNotFoundError: If the `sentieon_executable` cannot be found or resolved. + """ + if not subcommand_and_args: + raise ValueError("subcommand_and_args cannot be empty. Please specify a subcommand and its arguments.") + + # Validate sentieon_executable path + resolved_sentieon_executable = sentieon_executable + if not sentieon_executable.is_absolute(): + try: + # Attempt to resolve the executable path if it's not absolute + which_process = subprocess.run( + ["which", str(sentieon_executable)], + capture_output=True, + text=True, + check=True + ) + resolved_path_str = which_process.stdout.strip() + if not resolved_path_str: + raise FileNotFoundError(f"Sentieon executable '{sentieon_executable}' not found in PATH.") + resolved_sentieon_executable = Path(resolved_path_str) + except (subprocess.CalledProcessError, FileNotFoundError): + # If 'which' fails or the path is not found, check if it's a relative path in current dir + if not sentieon_executable.is_file(): + raise FileNotFoundError(f"Sentieon executable '{sentieon_executable}' not found or not executable. " + "Please ensure it is in your PATH or provide an absolute path.") + elif not sentieon_executable.is_file(): + raise FileNotFoundError(f"Sentieon executable not found at: {sentieon_executable}") + + cmd = [str(resolved_sentieon_executable)] + + # Add global Sentieon options + if num_threads is not None: + if not isinstance(num_threads, int) or num_threads <= 0: + raise ValueError("num_threads must be a positive integer.") + cmd.extend(["-t", str(num_threads)]) + + if license_server: + cmd.extend(["-l", license_server]) + + if log_file: + # Ensure parent directory exists for log file + log_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["-log", str(log_file)]) + + if verbose: + cmd.append("-v") + + # Add the subcommand and its specific arguments + cmd.extend(subcommand_and_args) + + command_str = " ".join(map(str, cmd)) + stdout = "" + stderr = "" + output_files: List[Path] = [] + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + stdout = process.stdout + stderr = process.stderr + # It is impossible to reliably determine output files without knowing the + # specific subcommand and its output parameters. Users of this generic + # function would need to parse stdout/stderr or know the subcommand's + # behavior to identify outputs. + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Sentieon command failed with exit code {e.returncode}: {e.cmd}", + "returncode": e.returncode, + "output_files": output_files, + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": f"Error: Sentieon executable not found at '{resolved_sentieon_executable}'. " + "Please ensure it is installed and accessible in your PATH, " + "or provide the correct absolute path.", + "error": "Executable not found", + "returncode": 127, + "output_files": output_files, + } + except Exception as e: + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "error": f"An unexpected error occurred: {str(e)}", + "returncode": 1, # Generic error code for unexpected errors + "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_sentieon/app/sentieon_shim_server.py b/Biomni/mcp_generated/mcp_sentieon/app/sentieon_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..317a3358b9b67ed9b1a50e4f278bd5202810d384 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/app/sentieon_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_sentieon/app/sentieon_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_sentieon' + + +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_sentieon/docker-compose.yml b/Biomni/mcp_generated/mcp_sentieon/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..cc12d821d1f3088ffc73ffbec55c3c1f0f946320 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-sentieon: + build: . + image: mcp-sentieon:latest + container_name: mcp-sentieon + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=sentieon + 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_sentieon/environment.yaml b/Biomni/mcp_generated/mcp_sentieon/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0631d093c2411fe07dfd973e7486008e0256e27e --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - sentieon + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sentieon/requirements.txt b/Biomni/mcp_generated/mcp_sentieon/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sentieon/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_subread/docker-compose.yml b/Biomni/mcp_generated/mcp_subread/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..2a72be4099bbf2c7dd6a45f8962ce9367c62d310 --- /dev/null +++ b/Biomni/mcp_generated/mcp_subread/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-subread: + build: . + image: mcp-subread:latest + container_name: mcp-subread + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=subread + 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_subread/environment.yaml b/Biomni/mcp_generated/mcp_subread/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50fdad61e2e1694d33e81c1c4075e3ab0f881e55 --- /dev/null +++ b/Biomni/mcp_generated/mcp_subread/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - subread + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_zdb/Dockerfile b/Biomni/mcp_generated/mcp_zdb/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..474d8037e0cb9ea4316f91e2fa4e033ab9e6836a --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/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 zdb via conda (e.g., from bioconda) +RUN conda install -c bioconda zdb -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/zdb_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/zdb_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/zdb_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_zdb/app/requirements.txt b/Biomni/mcp_generated/mcp_zdb/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_zdb/app/zdb_shim_server.py b/Biomni/mcp_generated/mcp_zdb/app/zdb_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..34c46cf6cc3a9e4223c311ed020101e9d310c6e5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/app/zdb_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_zdb/app/zdb_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_zdb' + + +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_zdb/docker-compose.yml b/Biomni/mcp_generated/mcp_zdb/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3b4bd2d346216050cf0d1a74674c9af1baaccad1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-zdb: + build: . + image: mcp-zdb:latest + container_name: mcp-zdb + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=zdb + 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_zdb/environment.yaml b/Biomni/mcp_generated/mcp_zdb/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..01ab95d610b76375295d389eed0be96339224171 --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - zdb + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_zdb/requirements.txt b/Biomni/mcp_generated/mcp_zdb/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_zdb/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp