diff --git a/Biomni/mcp_generated/mcp_ariba/Dockerfile b/Biomni/mcp_generated/mcp_ariba/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..64f32321bcd1440c4b47360773aa12ac582f949e --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/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 ariba via conda (e.g., from bioconda) +RUN conda install -c bioconda ariba -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/ariba_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/ariba_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/ariba_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ariba/app/ariba_server.py b/Biomni/mcp_generated/mcp_ariba/app/ariba_server.py new file mode 100644 index 0000000000000000000000000000000000000000..46ae5ebe025b76316d8b4866d8006084d8df4830 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/app/ariba_server.py @@ -0,0 +1,509 @@ +import subprocess +import tempfile +import os +from pathlib import Path +from typing import Optional, List, Literal + +# MCP decorator is not needed for the final code, as per instructions. +# Assuming it's provided by the MCP framework. +# from mcp import tool as mcp_tool +# For linting purposes, we can create a dummy decorator. +class mcp: + def tool(func): + return func + +@mcp.tool +def pubmlstget( + pubmlst_db: str, + outdir: Path, + force: bool = False, + noverify: bool = False, +): + """ + Download data from a pubmlst database. + + Args: + pubmlst_db: Name of the pubmlst database to download. + outdir: Output directory to save the downloaded data. + force: Overwrite existing files if they exist. + noverify: Do not verify the SSL certificate. + """ + # Input validation + if not pubmlst_db: + raise ValueError("pubmlst_db must be provided.") + + try: + outdir.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {outdir}: {e}") + + # Command construction + cmd = ["ariba", "pubmlstget"] + if force: + cmd.append("--force") + if noverify: + cmd.append("--noverify") + cmd.extend([pubmlst_db, str(outdir)]) + + # Subprocess execution + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + output_files = [str(p) for p in outdir.rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA pubmlstget failed", + "return_code": e.returncode, + } + +@mcp.tool +def getref( + ref_name: Literal[ + "argannot", "card", "megares", "plasmidfinder", "resfinder", + "srst2_argannot", "vfdb_core", "vfdb_full", "virfinder" + ], + outdir: Path, + force: bool = False, + metadata_only: bool = False, +): + """ + Download reference data from NCBI/RefSeq or other sources. + + Args: + ref_name: Name of reference data to download. + outdir: Output directory to save the reference data. + force: Overwrite existing files if they exist. + metadata_only: Only download metadata, not the sequences. + """ + # Input validation + try: + outdir.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {outdir}: {e}") + + # Command construction + cmd = ["ariba", "getref"] + if force: + cmd.append("--force") + if metadata_only: + cmd.append("--metadata_only") + cmd.extend([ref_name, str(outdir)]) + + # Subprocess execution + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + output_files = [str(p) for p in outdir.rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA getref failed", + "return_code": e.returncode, + } + +@mcp.tool +def prepareref( + fasta_in: Path, + metadata_in: Path, + outdir: Path, + min_length: int = 1, + max_length: int = 10000, + max_n: int = 5, + no_cdhit: bool = False, + cdhit_clusters: Optional[Path] = None, + cdhit_threads: int = 1, + cdhit_mem: int = 400, + force: bool = False, + tmp_dir: Optional[Path] = None, + verbose: bool = False, + debug: bool = False, +): + """ + Prepare reference data for use with ARIBA. + + Args: + fasta_in: Input FASTA file of sequences. + metadata_in: Input TSV file of metadata. + outdir: Output directory for prepared reference. + min_length: Minimum sequence length. + max_length: Maximum sequence length. + max_n: Maximum number of Ns allowed in a sequence. + no_cdhit: Do not run cd-hit. + cdhit_clusters: File of cd-hit clusters. Implies --no_cdhit. + cdhit_threads: Threads to use for cd-hit. + cdhit_mem: Memory for cd-hit in MB. + force: Overwrite existing files. + tmp_dir: Use this directory for temporary files. + verbose: Print STDOUT and STDERR from external programs. + debug: Print debugging messages. + """ + # Input validation + if not fasta_in.is_file(): + raise FileNotFoundError(f"Input FASTA file not found: {fasta_in}") + if not metadata_in.is_file(): + raise FileNotFoundError(f"Input metadata file not found: {metadata_in}") + if cdhit_clusters and not cdhit_clusters.is_file(): + raise FileNotFoundError(f"cd-hit clusters file not found: {cdhit_clusters}") + if min_length <= 0: + raise ValueError("min_length must be a positive integer.") + if cdhit_threads <= 0: + raise ValueError("cdhit_threads must be a positive integer.") + + try: + outdir.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {outdir}: {e}") + + # Command construction + cmd = ["ariba", "prepareref", + "--fasta_in", str(fasta_in), + "--metadata_in", str(metadata_in), + "--outdir", str(outdir), + "--min_length", str(min_length), + "--max_length", str(max_length), + "--max_N", str(max_n), + "--cdhit_threads", str(cdhit_threads), + "--cdhit_mem", str(cdhit_mem)] + + if no_cdhit: + cmd.append("--no_cdhit") + if cdhit_clusters: + cmd.extend(["--cdhit_clusters", str(cdhit_clusters)]) + if force: + cmd.append("--force") + if tmp_dir: + cmd.extend(["--tmp_dir", str(tmp_dir)]) + if verbose: + cmd.append("--verbose") + if debug: + cmd.append("--debug") + + # Subprocess execution + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + output_files = [str(p) for p in outdir.rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA prepareref failed", + "return_code": e.returncode, + } + +@mcp.tool +def run( + ref_dir: Path, + reads1: Path, + reads2: Path, + outdir: Path, + assembly_cov: int = 10, + gene_cov_cutoff: float = 0.95, + gene_nt_extend: int = 10, + local_th: float = 0.9, + min_gene_cov: int = 1, + assembled_threshold: float = 0.95, + nucmer_min_id: int = 90, + nucmer_min_len: int = 50, + nucmer_breaklen: int = 10, + nucmer_max_gap: int = 20, + assembler: Literal["fermilite", "spades"] = "fermilite", + spades_options: str = "--careful", + threads: int = 1, + force: bool = False, + tmp_dir: Optional[Path] = None, + verbose: bool = False, + debug: bool = False, + noclean: bool = False, + unique_map_only: bool = False, + other_ref_fasta: Optional[Path] = None, + filestem: Optional[str] = None, +): + """ + Run the main ARIBA pipeline to identify genes from paired sequencing reads. + + Args: + ref_dir: Reference directory, created by 'prepareref'. + reads1: First paired-end reads file. + reads2: Second paired-end reads file. + outdir: Output directory for results. + assembly_cov: Minimum coverage of internal contig flank. + gene_cov_cutoff: Minimum proportion of gene length covered by reads. + gene_nt_extend: Max nucleotides to extend assembly past gene ends. + local_th: Minimum proportion of bases in local assembly identical to reference. + min_gene_cov: Minimum read coverage depth on any base of a gene. + assembled_threshold: Minimum proportion of bases in a gene covered by local assembly. + nucmer_min_id: nucmer --minid option. + nucmer_min_len: nucmer --mincluster option. + nucmer_breaklen: nucmer --breaklen option. + nucmer_max_gap: nucmer --maxgap option. + assembler: Assembler to use. + spades_options: Options to pass to spades. + threads: Number of threads to use. + force: Overwrite existing output files. + tmp_dir: Use this directory for temporary files. + verbose: Print STDOUT and STDERR from external programs. + debug: Print debugging messages. + noclean: Do not clean up intermediate files. + unique_map_only: Only use uniquely mapping reads. + other_ref_fasta: FASTA file of other reference sequences (e.g., host genome). + filestem: Filestem to use for output files. + """ + # Input validation + if not ref_dir.is_dir(): + raise FileNotFoundError(f"Reference directory not found: {ref_dir}") + if not reads1.is_file(): + raise FileNotFoundError(f"Reads file 1 not found: {reads1}") + if not reads2.is_file(): + raise FileNotFoundError(f"Reads file 2 not found: {reads2}") + if other_ref_fasta and not other_ref_fasta.is_file(): + raise FileNotFoundError(f"Other reference FASTA not found: {other_ref_fasta}") + if threads <= 0: + raise ValueError("threads must be a positive integer.") + + try: + outdir.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {outdir}: {e}") + + # Command construction + cmd = ["ariba", "run", + "--assembly_cov", str(assembly_cov), + "--gene_cov_cutoff", str(gene_cov_cutoff), + "--gene_nt_extend", str(gene_nt_extend), + "--local_th", str(local_th), + "--min_gene_cov", str(min_gene_cov), + "--assembled_threshold", str(assembled_threshold), + "--nucmer_min_id", str(nucmer_min_id), + "--nucmer_min_len", str(nucmer_min_len), + "--nucmer_breaklen", str(nucmer_breaklen), + "--nucmer_max_gap", str(nucmer_max_gap), + "--assembler", assembler, + "--spades_options", spades_options, + "--threads", str(threads)] + + if force: cmd.append("--force") + if verbose: cmd.append("--verbose") + if debug: cmd.append("--debug") + if noclean: cmd.append("--noclean") + if unique_map_only: cmd.append("--unique_map_only") + if tmp_dir: cmd.extend(["--tmp_dir", str(tmp_dir)]) + if other_ref_fasta: cmd.extend(["--other_ref_fasta", str(other_ref_fasta)]) + if filestem: cmd.extend(["--filestem", filestem]) + + cmd.extend([str(ref_dir), str(reads1), str(reads2), str(outdir)]) + + # Subprocess execution + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + output_files = [str(p) for p in outdir.rglob("*") if p.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA run failed", + "return_code": e.returncode, + } + +@mcp.tool +def summary( + out_prefix: Path, + in_dirs: List[Path], + preset: Literal["all", "assembled", "flags", "variants"] = "all", + cluster_cols: Optional[str] = None, + col_sep: str = ".", + row_sep: str = ".", + force: bool = False, + verbose: bool = False, + debug: bool = False, +): + """ + Summarise multiple ARIBA reports into a single file. + + Args: + out_prefix: Output file prefix. + in_dirs: List of directories containing ARIBA results. + preset: Output preset. + cluster_cols: Columns to use for clustering. + col_sep: Separator between column names in output file. + row_sep: Separator between sample name and cluster name in output file. + force: Overwrite existing files. + verbose: Print STDOUT and STDERR from external programs. + debug: Print debugging messages. + """ + # Input validation + if not in_dirs: + raise ValueError("At least one input directory must be provided in in_dirs.") + for d in in_dirs: + if not d.is_dir(): + raise FileNotFoundError(f"Input directory not found: {d}") + report_path = d / "report.tsv" + if not report_path.is_file(): + raise FileNotFoundError(f"Expected report file not found in {d}: {report_path}") + + try: + out_prefix.parent.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {out_prefix.parent}: {e}") + + # Command construction + cmd = ["ariba", "summary", + "--preset", preset, + "--col_sep", col_sep, + "--row_sep", row_sep] + + if cluster_cols: + cmd.extend(["--cluster_cols", cluster_cols]) + if force: + cmd.append("--force") + if verbose: + cmd.append("--verbose") + if debug: + cmd.append("--debug") + + cmd.append(str(out_prefix)) + cmd.extend([str(d) for d in in_dirs]) + + # Subprocess execution + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + output_files = [str(p) for p in out_prefix.parent.glob(f"{out_prefix.name}*")] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA summary failed", + "return_code": e.returncode, + } + +@mcp.tool +def version(): + """ + Print the ARIBA version number and exit. + """ + cmd = ["ariba", "version"] + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "version": result.stdout.strip(), + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA version command failed", + "return_code": e.returncode, + } + +@mcp.tool +def test(): + """ + Run the ARIBA test suite to verify installation and functionality. + """ + cmd = ["ariba", "test"] + try: + # The test command can be long-running and produce a lot of output. + # We capture it as usual. + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "test_passed": "OK" in result.stderr, + } + except FileNotFoundError: + raise RuntimeError("ariba command not found. Please ensure it is in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "ARIBA test suite failed", + "return_code": e.returncode, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ariba/app/ariba_shim_server.py b/Biomni/mcp_generated/mcp_ariba/app/ariba_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b402e418257ea99a8e0256243a505c859a546202 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/app/ariba_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_ariba/app/ariba_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_ariba' + + +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_ariba/app/requirements.txt b/Biomni/mcp_generated/mcp_ariba/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_ariba/docker-compose.yml b/Biomni/mcp_generated/mcp_ariba/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3fe30327b9341b624c6029e37d388c193221cbca --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-ariba: + build: . + image: mcp-ariba:latest + container_name: mcp-ariba + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=ariba + 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_ariba/environment.yaml b/Biomni/mcp_generated/mcp_ariba/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..774ae8821bdba1c2e80cce68add26a6a867b0ce5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - ariba + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ariba/requirements.txt b/Biomni/mcp_generated/mcp_ariba/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ariba/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_arvados-python-client/Dockerfile b/Biomni/mcp_generated/mcp_arvados-python-client/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ecd637f0e9cb70c85569202ef0c48d5e81dfcbc2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/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 arvados-python-client via conda (e.g., from bioconda) +RUN conda install -c bioconda arvados-python-client -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/arvados-python-client_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/arvados-python-client_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/arvados-python-client_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_server.py b/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_server.py new file mode 100644 index 0000000000000000000000000000000000000000..49ccd9317dfb4f8f5a5169e850fab0212c5ff8c7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_server.py @@ -0,0 +1,306 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# MCP-standardized error handling +class MCPError(Exception): + def __init__(self, message, stderr): + self.message = message + self.stderr = stderr + super().__init__(self.message) + +def _run_command(cmd: List[str]): + """Helper function to run a command and handle exceptions.""" + try: + command_str = " ".join(cmd) + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + } + except FileNotFoundError: + raise MCPError(f"Command '{cmd[0]}' not found. Is arvados-python-client installed and in the PATH?", "") + except subprocess.CalledProcessError as e: + raise MCPError(f"Command '{' '.join(cmd)}' failed with exit code {e.returncode}", e.stderr) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_arvados_python_client' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def arv_get( + object_locator: str, + destination_path: Optional[Path] = None, + filename: Optional[str] = None, + force: bool = False, + recursive: bool = False, + progress: bool = False, +) -> dict: + """ + Downloads an object from Arvados Keep to a local path or standard output. + + Args: + object_locator: The UUID or portable data hash (PDH) of the object to download. + destination_path: Local path to save the object. If a directory, the original filename is used. If not provided, content is written to stdout. + filename: The name of the file to get from a collection. + force: Overwrite existing files if they exist. + recursive: Recursively download collections. + progress: Show a progress bar during download. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + cmd = ["arv-get"] + + if force: + cmd.append("--force") + if recursive: + cmd.append("--recursive") + if progress: + cmd.append("--progress") + if filename: + cmd.extend(["--filename", filename]) + + cmd.append(object_locator) + + output_files = [] + if destination_path: + # Ensure parent directory exists if a full path is given + if destination_path.parent and not destination_path.parent.is_dir(): + destination_path.parent.mkdir(parents=True, exist_ok=True) + cmd.append(str(destination_path)) + output_files.append(str(destination_path)) + + try: + result = _run_command(cmd) + result["output_files"] = output_files + return result + except MCPError as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": e.stderr, + "error": e.message, + "output_files": [], + } + +@mcp.tool() +def arv_put( + paths: List[Path], + name: Optional[str] = None, + owner: Optional[str] = None, + portable_data_hash: bool = False, + storage_class: Optional[str] = None, + num_retries: int = 5, + progress: bool = False, +) -> dict: + """ + Uploads local files or directories to Arvados Keep. + + Args: + paths: One or more local file or directory paths to upload. + name: Set the name of the new collection. + owner: Set the owner project UUID for the new collection. + portable_data_hash: Use portable data hash to identify the collection. + storage_class: The storage class for the data (e.g., 'default', 'trash'). + num_retries: Number of times to retry a failed API call. + progress: Show a progress bar during upload. + + Returns: + A dictionary containing the command executed, stdout, and stderr. + """ + if not paths: + raise ValueError("At least one path must be provided for upload.") + + for p in paths: + if not p.exists(): + raise FileNotFoundError(f"Input path does not exist: {p}") + + cmd = ["arv-put"] + + if name: + cmd.extend(["--name", name]) + if owner: + cmd.extend(["--owner", owner]) + if portable_data_hash: + cmd.append("--portable-data-hash") + if storage_class: + cmd.extend(["--storage-class", storage_class]) + if progress: + cmd.append("--progress") + + # num_retries is a standard arvados client option + cmd.extend(["--num-retries", str(num_retries)]) + + cmd.extend([str(p) for p in paths]) + + try: + result = _run_command(cmd) + result["output_files"] = [] + return result + except MCPError as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": e.stderr, + "error": e.message, + "output_files": [], + } + +@mcp.tool() +def arv_ls( + object_locator: str, + path: Optional[str] = None, + long_format: bool = False, + recursive: bool = False, + human_readable: bool = False, +) -> dict: + """ + Lists the contents of a collection in Arvados Keep. + + Args: + object_locator: The UUID or portable data hash (PDH) of the collection to list. + path: A path within the collection to list. + long_format: Use a long listing format (similar to ls -l). + recursive: List subdirectories recursively. + human_readable: With long_format, print sizes in human-readable format (e.g., 1K, 234M, 2G). + + Returns: + A dictionary containing the command executed, stdout (the listing), and stderr. + """ + cmd = ["arv-ls"] + + if long_format: + cmd.append("-l") + if recursive: + cmd.append("-R") + if human_readable: + cmd.append("-h") + + cmd.append(object_locator) + if path: + cmd.append(path) + + try: + result = _run_command(cmd) + result["output_files"] = [] + return result + except MCPError as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": e.stderr, + "error": e.message, + "output_files": [], + } + +@mcp.tool() +def arv_copy( + src_locators: List[str], + dest_project_uuid: str, + name: Optional[str] = None, + num_retries: int = 5, +) -> dict: + """ + Copies one or more objects to a destination project in Arvados. + + Args: + src_locators: A list of source object UUIDs or portable data hashes (PDHs). + dest_project_uuid: The UUID of the destination project. + name: New name for the object(s). If copying multiple objects, this is ignored. + num_retries: Number of times to retry a failed API call. + + Returns: + A dictionary containing the command executed, stdout, and stderr. + """ + if not src_locators: + raise ValueError("At least one source locator must be provided.") + if not dest_project_uuid: + raise ValueError("A destination project UUID must be provided.") + + cmd = ["arv-copy"] + + if name and len(src_locators) == 1: + cmd.extend(["--name", name]) + + cmd.extend(["--num-retries", str(num_retries)]) + + cmd.extend(src_locators) + cmd.append(dest_project_uuid) + + try: + result = _run_command(cmd) + result["output_files"] = [] + return result + except MCPError as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": e.stderr, + "error": e.message, + "output_files": [], + } + +@mcp.tool() +def arv_tag( + locator: str, + tags_to_add: Optional[List[str]] = None, + keys_to_remove: Optional[List[str]] = None, + list_tags: bool = False, + num_retries: int = 5, +) -> dict: + """ + Manipulates tags on an Arvados object. It can list, add, or remove tags. + + Args: + locator: The UUID or portable data hash (PDH) of the object to tag. + tags_to_add: A list of tags to add, in 'key' or 'key=value' format. + keys_to_remove: A list of tag keys to remove. + list_tags: If True, lists the existing tags on the object. + num_retries: Number of times to retry a failed API call. + + Returns: + A dictionary containing the command executed, stdout, and stderr. + """ + cmd = ["arv-tag"] + + cmd.extend(["--num-retries", str(num_retries)]) + + if list_tags: + cmd.append("--list") + + cmd.append(locator) + + if tags_to_add: + for tag in tags_to_add: + if "=" not in tag and " " in tag: + raise ValueError(f"Invalid tag format: '{tag}'. Use 'key=value' or 'key'.") + cmd.extend(tags_to_add) + + if keys_to_remove: + for key in keys_to_remove: + cmd.extend(["--remove", key]) + + try: + result = _run_command(cmd) + result["output_files"] = [] + return result + except MCPError as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": e.stderr, + "error": e.message, + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_shim_server.py b/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8df4c946872a757b45191d2727a5d93d5d4e582b --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/app/arvados-python-client_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_arvados-python-client/app/arvados-python-client_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_arvados_python_client' + + +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_arvados-python-client/app/requirements.txt b/Biomni/mcp_generated/mcp_arvados-python-client/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_arvados-python-client/docker-compose.yml b/Biomni/mcp_generated/mcp_arvados-python-client/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..02aad23170e4a610ba487a57362c075d0945db87 --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-arvados-python-client: + build: . + image: mcp-arvados-python-client:latest + container_name: mcp-arvados-python-client + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=arvados-python-client + 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_arvados-python-client/environment.yaml b/Biomni/mcp_generated/mcp_arvados-python-client/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..98a0ca44cd7cf90bff7b67d19f0e9e9da6b423e9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - arvados-python-client + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_arvados-python-client/requirements.txt b/Biomni/mcp_generated/mcp_arvados-python-client/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_arvados-python-client/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bbmap/docker-compose.yml b/Biomni/mcp_generated/mcp_bbmap/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..8bb1d6c1e02e727e07ab7705f2f875415e352426 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bbmap/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bbmap: + build: . + image: mcp-bbmap:latest + container_name: mcp-bbmap + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bbmap + 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_bbmap/requirements.txt b/Biomni/mcp_generated/mcp_bbmap/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bbmap/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-affy/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-affy/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..76b7331378a46587517e9b754671c8ce4eda02ba --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-affy/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-affy via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-affy -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-affy_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-affy_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-affy_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_server.py b/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e8e82b4691230816647bcdeb9564ec455cd332b6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_server.py @@ -0,0 +1,196 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, Dict, List + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def tool(func): + def wrapper(*args, **kwargs): + # In a real scenario, the MCP framework would handle + # registration and execution. + return func(*args, **kwargs) + return wrapper +mcp = type("mcp", (), {"tool": tool}) + + +@mcp.tool +def run_affy_analysis( + cel_files_dir: Path, + output_expression_file: Path, + method: str = "rma", + pheno_data_file: Optional[Path] = None, + generate_qc_plot: bool = False, + qc_plot_file: Optional[Path] = None, +) -> Dict: + """ + Performs exploratory analysis of Affymetrix oligonucleotide arrays. + + This tool reads raw Affymetrix data from .CEL files, performs background + correction, normalization, and probe summarization using either the RMA + (Robust Multi-array Average) or MAS 5.0 algorithm, and outputs a matrix + of expression values. + + Args: + cel_files_dir: Path to the directory containing the input .CEL files. + output_expression_file: Path to write the output expression matrix (tab-separated). + method: The processing method to use. Must be one of 'rma' or 'mas5'. + Defaults to 'rma'. + pheno_data_file: Optional path to a phenodata file describing the samples. + This should be a tab-delimited file with row names matching + the .CEL file names. + generate_qc_plot: If True, generate a quality control boxplot of the raw + probe intensities. Defaults to False. + qc_plot_file: Path to save the QC plot image (e.g., plot.png). + Required if generate_qc_plot is True. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a + list of output files. + """ + # 1. Input Validation + if not cel_files_dir.is_dir(): + raise ValueError(f"Input directory '{cel_files_dir}' does not exist or is not a directory.") + + if pheno_data_file and not pheno_data_file.is_file(): + raise FileNotFoundError(f"Phenodata file '{pheno_data_file}' not found.") + + method_lower = method.lower() + if method_lower not in ["rma", "mas5"]: + raise ValueError(f"Invalid method '{method}'. Must be 'rma' or 'mas5'.") + + if generate_qc_plot: + if qc_plot_file is None: + raise ValueError("qc_plot_file must be provided when generate_qc_plot is True.") + if not qc_plot_file.parent.exists(): + qc_plot_file.parent.mkdir(parents=True, exist_ok=True) + elif qc_plot_file is not None: + print(f"Warning: qc_plot_file '{qc_plot_file}' is specified but generate_qc_plot is False. The file will not be created.") + + if not output_expression_file.parent.exists(): + output_expression_file.parent.mkdir(parents=True, exist_ok=True) + + # 2. R Script Generation + # This script will be executed by Rscript to perform the analysis. + r_script_content = f""" + # Load necessary libraries + if (!requireNamespace("affy", quietly = TRUE)) {{ + stop("The 'affy' package is not installed. Please install it from Bioconductor.") + }} + library(affy) + + # Parse command-line arguments + args <- commandArgs(trailingOnly = TRUE) + if (length(args) < 3) {{ + stop("Usage: Rscript script.R [pheno_path_or_NA] [plot_path_or_NA]", call. = FALSE) + }} + + cel_dir <- args[1] + output_path <- args[2] + method <- args[3] + pheno_path <- if (length(args) >= 4 && args[4] != "NA") args[4] else NULL + plot_path <- if (length(args) >= 5 && args[5] != "NA") args[5] else NULL + + # --- Main Logic --- + tryCatch({{ + # Read the data + cat("Reading CEL files from:", cel_dir, "\\n") + if (!is.null(pheno_path)) {{ + cat("Using pheno data from:", pheno_path, "\\n") + # read.AnnotatedDataFrame is deprecated, using read.phenoData is more modern + pheno <- read.phenoData(pheno_path, header = TRUE, row.names = 1) + data <- ReadAffy(celfile.path = cel_dir, phenoData = pheno) + }} else {{ + data <- ReadAffy(celfile.path = cel_dir) + }} + cat("Finished reading data.\\n") + + # Process the data + cat("Processing data with method:", method, "\\n") + if (method == "rma") {{ + eset <- rma(data) + }} else if (method == "mas5") {{ + eset <- mas5(data) + }} else {{ + stop(paste("Invalid method specified:", method)) + }} + cat("Finished processing.\\n") + + # Extract and write expression values + cat("Writing expression matrix to:", output_path, "\\n") + expression_matrix <- exprs(eset) + write.table(expression_matrix, file = output_path, sep = "\\t", quote = FALSE, col.names = NA) + cat("Done writing expression matrix.\\n") + + # Generate QC plot if requested + if (!is.null(plot_path)) {{ + cat("Generating QC plot at:", plot_path, "\\n") + png(filename = plot_path, width = 800, height = 600) + boxplot(data, main = "Raw Probe Intensities", las = 2, col="lightblue") + dev.off() + cat("Done generating plot.\\n") + }} + + cat("Analysis complete.\\n") + }}, error = function(e) {{ + cat("An error occurred:\\n") + cat(conditionMessage(e), "\\n") + quit(status = 1) + }}) + """ + + # 3. Subprocess Execution + output_files = {"expression_matrix": str(output_expression_file)} + cmd = ["Rscript"] + + try: + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + # Prepare arguments for the R script + pheno_path_arg = str(pheno_data_file.resolve()) if pheno_data_file else "NA" + plot_path_arg = str(qc_plot_file.resolve()) if generate_qc_plot and qc_plot_file else "NA" + + cmd.extend([ + r_script_path, + str(cel_files_dir.resolve()), + str(output_expression_file.resolve()), + method_lower, + pheno_path_arg, + plot_path_arg + ]) + + # Execute the command + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True + ) + + if generate_qc_plot and qc_plot_file: + output_files["qc_plot"] = str(qc_plot_file) + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": output_files + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R script execution failed.", + "return_code": e.returncode, + "output_files": {} + } + except Exception as e: + raise e + finally: + # Clean up the temporary R script file + if 'r_script_path' in locals() and Path(r_script_path).exists(): + Path(r_script_path).unlink() diff --git a/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1d44de656e5ac49e55b5ac8b8a11681315bbd396 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-affy/app/bioconductor-affy_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-affy/app/bioconductor-affy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_affy' + + +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-affy/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-affy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a27582848106e01be3508dca13631cac633c09f8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-affy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-affy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-affy/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-affy/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-affy/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4749f6b50ebfb67cffb861305a1aa7d0b9f9427e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/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-alabaster.sfe via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-alabaster.sfe -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-alabaster.sfe_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-alabaster.sfe_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-alabaster.sfe_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_server.py b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fe9d29aef6867dfe7f96e52a77a7ba8cd40da0f7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_server.py @@ -0,0 +1,221 @@ +import subprocess +import logging +from pathlib import Path +from typing import Dict, Any, Optional + +# It is assumed that the mcp decorator is available in the execution environment. +# from mcp import tool + +# Mock decorator for local testing, not to be included in the final deliverable +# if 'mcp' not in globals(): +# class mcp: +# @staticmethod +# def tool(): +# def decorator(func): +# return func +# return decorator + +logger = logging.getLogger(__name__) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_alabaster_sfe' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def stage_sfe_object( + input_rds: Path, + output_dir: Path, + path: str = "sfe", +) -> Dict[str, Any]: + """ + Stages a SpatialFeatureExperiment (SFE) object from an RDS file into the alabaster format. + + This tool wraps the 'stageObject' function from the R package 'alabaster.sfe'. + It reads an SFE object from a specified RDS file and saves it into a structured + directory according to the alabaster specification. This serves as the primary + "save" or "serialize" function for SFE objects in the alabaster framework. + + Args: + input_rds: Path to the input RDS file containing the SpatialFeatureExperiment object. + output_dir: Path to the directory where the staged object assets will be stored. + path: The relative path within 'output_dir' to save the object. Defaults to "sfe". + + Returns: + A dictionary containing the execution details and the path to the main output metadata file. + """ + # 1. Input Validation + if not input_rds.is_file(): + raise FileNotFoundError(f"Input RDS file not found at: {input_rds}") + + if not path: + raise ValueError("'path' cannot be an empty string.") + + try: + output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + raise OSError(f"Failed to create output directory {output_dir}: {e}") + + # 2. R script construction + r_script_content = f""" + suppressPackageStartupMessages(library(alabaster.sfe)) + suppressPackageStartupMessages(library(SpatialFeatureExperiment)) + + tryCatch({{ + sfe_object <- readRDS('{input_rds.resolve()}') + info <- alabaster.base::stageObject(sfe_object, dir = '{output_dir.resolve()}', path = '{path}') + cat(file.path('{output_dir.resolve()}', info$path, "object.json")) + }}, error = function(e) {{ + message("Error during R script execution: ", e$message) + quit(status = 1) + }}) + """ + + # 3. Subprocess Execution + cmd = ["Rscript", "-e", r_script_content] + command_executed = " ".join(map(str, cmd)) + logger.info(f"Executing command: {command_executed}") + + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + encoding='utf-8' + ) + + stdout = process.stdout.strip() + stderr = process.stderr.strip() + + main_output_file = Path(stdout) + if not main_output_file.is_file(): + raise FileNotFoundError( + f"Expected output file not created: {main_output_file}. R stderr: {stderr}" + ) + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": { + "staged_object_json": str(main_output_file) + } + } + + except FileNotFoundError: + error_msg = "Error: 'Rscript' command not found. Please ensure R is installed and in your system's PATH." + logger.error(error_msg) + raise RuntimeError(error_msg) from None + except subprocess.CalledProcessError as e: + logger.error(f"Command failed with exit code {e.returncode}") + logger.error(f"STDOUT: {e.stdout}") + logger.error(f"STDERR: {e.stderr}") + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) from e + +@mcp.tool() +def load_sfe_object( + staged_dir: Path, + output_rds: Path, + path: str = "", +) -> Dict[str, Any]: + """ + Loads a SpatialFeatureExperiment (SFE) object from the alabaster format into an RDS file. + + This tool wraps the 'loadObject' function from the R package 'alabaster.base'. + It reads an SFE object from a staged directory, reconstructs it in memory, and + saves the complete object as a single RDS file. This is the "load" or "deserialize" + counterpart to the staging function. + + Args: + staged_dir: Path to the directory containing the staged object assets. + output_rds: Path for the output RDS file where the loaded SFE object will be saved. + path: The relative path within 'staged_dir' where the object's 'object.json' is located. + If empty, it's assumed to be at the root of 'staged_dir'. + + Returns: + A dictionary containing the execution details and the path to the output RDS file. + """ + # 1. Input Validation + if not staged_dir.is_dir(): + raise NotADirectoryError(f"Staged asset directory not found at: {staged_dir}") + + metadata_json_path = staged_dir.joinpath(path, "object.json") + if not metadata_json_path.is_file(): + raise FileNotFoundError(f"Metadata file 'object.json' not found at: {metadata_json_path}") + + try: + output_rds.parent.mkdir(parents=True, exist_ok=True) + except OSError as e: + raise OSError(f"Failed to create output directory {output_rds.parent}: {e}") + + # 2. R script construction + r_script_content = f""" + suppressPackageStartupMessages(library(alabaster.sfe)) + suppressPackageStartupMessages(library(SpatialFeatureExperiment)) + + tryCatch({{ + sfe_object <- alabaster.base::loadObject( + '{metadata_json_path.resolve()}', + project = '{staged_dir.resolve()}' + ) + saveRDS(sfe_object, file = '{output_rds.resolve()}') + cat('{output_rds.resolve()}') + }}, error = function(e) {{ + message("Error during R script execution: ", e$message) + quit(status = 1) + }}) + """ + + # 3. Subprocess Execution + cmd = ["Rscript", "-e", r_script_content] + command_executed = " ".join(map(str, cmd)) + logger.info(f"Executing command: {command_executed}") + + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + encoding='utf-8' + ) + + stdout = process.stdout.strip() + stderr = process.stderr.strip() + + output_file = Path(stdout) + if not output_file.is_file() or output_file != output_rds.resolve(): + raise FileNotFoundError( + f"Expected output RDS file not created at {output_rds}. R stderr: {stderr}" + ) + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": { + "output_rds": str(output_file) + } + } + + except FileNotFoundError: + error_msg = "Error: 'Rscript' command not found. Please ensure R is installed and in your system's PATH." + logger.error(error_msg) + raise RuntimeError(error_msg) from None + except subprocess.CalledProcessError as e: + logger.error(f"Command failed with exit code {e.returncode}") + logger.error(f"STDOUT: {e.stdout}") + logger.error(f"STDERR: {e.stderr}") + raise RuntimeError( + f"R script execution failed with exit code {e.returncode}.\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-alabaster.sfe/app/bioconductor-alabaster.sfe_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8a698ff9af30b6c985aefe1608d84dc83b3acf95 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_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-alabaster.sfe/app/bioconductor-alabaster.sfe_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_alabaster_sfe' + + +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-alabaster.sfe/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3284000038c5dc43daaf9459bdb8b4ce91a55a7b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-alabaster.sfe: + build: . + image: mcp-bioconductor-alabaster.sfe:latest + container_name: mcp-bioconductor-alabaster.sfe + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-alabaster.sfe + 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-alabaster.sfe/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eeed48ad69a70bf01f9b3ae9eb5e5a76284e7159 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-alabaster.sfe + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.sfe/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ca4a426d4d1955490a5931118107ef1e6defbf17 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-biocneighbors + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocneighbors/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocsingular/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dc043c8fcb94e3e8d18677417f0b4e0922ffc3f6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/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-biocsingular via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-biocsingular -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY bioconductor-biocsingular_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-biocsingular_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-biocsingular_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_server.py b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0fac1ae20f7bcc546b140231f57be2e5defaf11f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_server.py @@ -0,0 +1,224 @@ +import subprocess +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_biocsingular' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def biocsingular_run_svd( + input_path: str, + output_prefix: str, + k: int = 5, + nu: int = 5, + nv: int = 5, + algorithm: str = "Irlba", + center: bool = True, + scale: bool = False, + deferred: bool = False, +) -> Dict[str, Any]: + """ + Perform Singular Value Decomposition (SVD) on a matrix using BiocSingular. + Supports various algorithms including Exact, Irlba, and Random SVD. + + Args: + input_path: Path to the input matrix file (CSV, TSV, or RDS format). + output_prefix: Prefix for the output files (will create .rds and .csv files). + k: Number of singular values to compute. + nu: Number of left singular vectors to compute. + nv: Number of right singular vectors to compute. + algorithm: SVD algorithm to use ('Exact', 'Irlba', 'Random', 'FastDot'). + center: Whether to center the columns of the matrix. + scale: Whether to scale the columns of the matrix. + deferred: Whether to use deferred centering and scaling (memory efficient). + """ + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + # Validate algorithm + valid_algos = ["Exact", "Irlba", "Random", "FastDot"] + if algorithm not in valid_algos: + return {"error": f"Invalid algorithm. Must be one of: {', '.join(valid_algos)}"} + + # Construct R script + r_script = f""" + library(BiocSingular) + + # Load data + ext <- tolower(tools::file_ext("{input_path}")) + if (ext == "rds") {{ + mat <- readRDS("{input_path}") + }} else if (ext == "csv") {{ + mat <- as.matrix(read.csv("{input_path}", row.names=1)) + }} else {{ + mat <- as.matrix(read.table("{input_path}", sep="\\t", header=TRUE, row.names=1)) + }} + + # Set parameter object + param <- {algorithm}Param(deferred = {"TRUE" if deferred else "FALSE"}) + + # Run SVD + res <- runSVD( + mat, + k = {k}, + nu = {nu}, + nv = {nv}, + BSPARAM = param, + center = {"TRUE" if center else "FALSE"}, + scale = {"TRUE" if scale else "FALSE"} + ) + + # Save results + saveRDS(res, "{output_prefix}_svd.rds") + write.csv(res$u, "{output_prefix}_u.csv") + write.csv(res$v, "{output_prefix}_v.csv") + write.csv(data.frame(d=res$d), "{output_prefix}_d.csv") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + + output_files = [ + f"{output_prefix}_svd.rds", + f"{output_prefix}_u.csv", + f"{output_prefix}_v.csv", + f"{output_prefix}_d.csv" + ] + + return { + "command_executed": f"BiocSingular::runSVD via Rscript", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(Path(f).absolute()) for f in output_files if Path(f).exists()] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def biocsingular_run_pca( + input_path: str, + output_prefix: str, + rank: int = 5, + algorithm: str = "Irlba", + center: bool = True, + scale: bool = False, + deferred: bool = False, +) -> Dict[str, Any]: + """ + Perform Principal Component Analysis (PCA) on a matrix using BiocSingular. + Optimized for large-scale genomic data. + + Args: + input_path: Path to the input matrix file (CSV, TSV, or RDS format). + output_prefix: Prefix for the output files. + rank: Number of principal components to compute. + algorithm: PCA algorithm to use ('Exact', 'Irlba', 'Random', 'FastDot'). + center: Whether to center the columns. + scale: Whether to scale the columns. + deferred: Whether to use deferred centering and scaling. + """ + input_file = Path(input_path) + if not input_file.exists(): + return {"error": f"Input file not found: {input_path}"} + + valid_algos = ["Exact", "Irlba", "Random", "FastDot"] + if algorithm not in valid_algos: + return {"error": f"Invalid algorithm. Must be one of: {', '.join(valid_algos)}"} + + r_script = f""" + library(BiocSingular) + + # Load data + ext <- tolower(tools::file_ext("{input_path}")) + if (ext == "rds") {{ + mat <- readRDS("{input_path}") + }} else if (ext == "csv") {{ + mat <- as.matrix(read.csv("{input_path}", row.names=1)) + }} else {{ + mat <- as.matrix(read.table("{input_path}", sep="\\t", header=TRUE, row.names=1)) + }} + + # Set parameter object + param <- {algorithm}Param(deferred = {"TRUE" if deferred else "FALSE"}) + + # Run PCA + res <- runPCA( + mat, + rank = {rank}, + BSPARAM = param, + center = {"TRUE" if center else "FALSE"}, + scale = {"TRUE" if scale else "FALSE"} + ) + + # Save results + saveRDS(res, "{output_prefix}_pca.rds") + write.csv(res$x, "{output_prefix}_pcs.csv") + write.csv(res$rotation, "{output_prefix}_loadings.csv") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + + output_files = [ + f"{output_prefix}_pca.rds", + f"{output_prefix}_pcs.csv", + f"{output_prefix}_loadings.csv" + ] + + return { + "command_executed": f"BiocSingular::runPCA via Rscript", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(Path(f).absolute()) for f in output_files if Path(f).exists()] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def biocsingular_get_info() -> Dict[str, Any]: + """ + Check if BiocSingular is installed and return version information. + """ + r_script = "library(BiocSingular); cat(as.character(packageVersion('BiocSingular')))" + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "biocsingular_version": result.stdout.strip(), + "status": "installed" + } + except subprocess.CalledProcessError: + return { + "status": "not_found", + "error": "BiocSingular is not installed in the R environment." + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ce6f45bd0554decc355babe812b955f4ece00bda --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_biocsingular' + + +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-biocsingular/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..291654c99584d5dc3b38c51c7ad55775aac18c3a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-biocsingular: + build: . + image: mcp-bioconductor-biocsingular:latest + container_name: mcp-bioconductor-biocsingular + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-biocsingular + 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-biocsingular/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0df4768f7fb4bd8ff66c5c7dee9e78602b0872f0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-biocsingular + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocsingular/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocsingular/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-bulksignalr/app/bioconductor-bulksignalr_server.py b/Biomni/mcp_generated/mcp_bioconductor-bulksignalr/app/bioconductor-bulksignalr_server.py new file mode 100644 index 0000000000000000000000000000000000000000..be9a53af15c95c8eafd16b42e08c9381adc407e9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-bulksignalr/app/bioconductor-bulksignalr_server.py @@ -0,0 +1,473 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional + +# This is a placeholder for the MCP decorator. +# The actual MCP environment will provide this. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +# --- R Script Templates --- + +R_SCRIPT_BULK_DE = """ +# Load libraries +suppressPackageStartupMessages(library(argparse)) +suppressPackageStartupMessages(library(bulksignalr)) +suppressPackageStartupMessages(library(SingleCellExperiment)) +suppressPackageStartupMessages(library(Matrix)) + +# Create a parser +parser <- ArgumentParser(description = "Run bulksignalr bulk_de") + +# Add arguments +parser$add_argument("--counts", type = "character", required = TRUE, help = "Path to the counts matrix file (TSV format)") +parser$add_argument("--metadata", type = "character", required = TRUE, help = "Path to the metadata file (TSV format)") +parser$add_argument("--output", type = "character", required = TRUE, help = "Path to the output DE results file (TSV format)") +parser$add_argument("--design", type = "character", required = TRUE, help = "Design formula for the model (e.g., '~ 0 + group')") +parser$add_argument("--sample_col", type = "character", default = "sample", help = "Column name for samples in metadata") +parser$add_argument("--group_col", type = "character", default = "group", help = "Column name for groups in metadata") +parser$add_argument("--lfc_threshold", type = "double", default = 0, help = "Log-fold change threshold") +parser$add_argument("--fdr_threshold", type = "double", default = 0.05, help = "FDR threshold") +parser$add_argument("--min_cells", type = "integer", default = 3, help = "Minimum number of cells expressing a gene") +parser$add_argument("--min_samples", type = "integer", default = 2, help = "Minimum number of samples in a group") +parser$add_argument("--n_cores", type = "integer", default = 1, help = "Number of cores to use") + +# Parse arguments +args <- parser$parse_args() + +# Read data +counts <- read.csv(args$counts, sep = "\\t", row.names = 1, check.names = FALSE) +counts_matrix <- as.matrix(counts) +metadata <- read.csv(args$metadata, sep = "\\t", row.names = 1, check.names = FALSE) + +# Ensure metadata rows match counts columns +if (!all(colnames(counts_matrix) %in% rownames(metadata))) { + stop("Not all column names in the counts matrix are present as row names in the metadata.") +} +metadata <- metadata[colnames(counts_matrix), , drop = FALSE] + +# Create SingleCellExperiment object +sce <- SingleCellExperiment(assays = list(counts = counts_matrix), + colData = metadata) + +# Run bulk_de +de_results <- bulk_de( + object = sce, + design = as.formula(args$design), + sample_col = args$sample_col, + group_col = args$group_col, + lfc_threshold = args$lfc_threshold, + fdr_threshold = args$fdr_threshold, + min_cells = args$min_cells, + min_samples = args$min_samples, + n_cores = args$n_cores +) + +# Save results +write.table(de_results, file = args$output, sep = "\\t", row.names = FALSE, quote = FALSE) + +cat("bulk_de analysis completed successfully.\\n") +""" + +R_SCRIPT_BULK_WALK = """ +# Load libraries +suppressPackageStartupMessages(library(argparse)) +suppressPackageStartupMessages(library(bulksignalr)) +suppressPackageStartupMessages(library(SingleCellExperiment)) +suppressPackageStartupMessages(library(Matrix)) + +# Create a parser +parser <- ArgumentParser(description = "Run bulksignalr bulk_walk") + +# Add arguments +parser$add_argument("--counts", type = "character", required = TRUE, help = "Path to the counts matrix file (TSV format)") +parser$add_argument("--metadata", type = "character", required = TRUE, help = "Path to the metadata file (TSV format)") +parser$add_argument("--de_results", type = "character", required = TRUE, help = "Path to the DE results file from bulk_de (TSV format)") +parser$add_argument("--output", type = "character", required = TRUE, help = "Path to the output co-regulation results file (TSV format)") +parser$add_argument("--sample_col", type = "character", default = "sample", help = "Column name for samples in metadata") +parser$add_argument("--group_col", type = "character", default = "group", help = "Column name for groups in metadata") +parser$add_argument("--cor_method", type = "character", choices = c("spearman", "pearson"), default = "spearman", help = "Correlation method") +parser$add_argument("--cor_threshold", type = "double", default = 0.4, help = "Correlation threshold") +parser$add_argument("--n_cores", type = "integer", default = 1, help = "Number of cores to use") + +# Parse arguments +args <- parser$parse_args() + +# Read data +counts <- read.csv(args$counts, sep = "\\t", row.names = 1, check.names = FALSE) +counts_matrix <- as.matrix(counts) +metadata <- read.csv(args$metadata, sep = "\\t", row.names = 1, check.names = FALSE) +de_results <- read.csv(args$de_results, sep = "\\t", check.names = FALSE) + +# Ensure metadata rows match counts columns +if (!all(colnames(counts_matrix) %in% rownames(metadata))) { + stop("Not all column names in the counts matrix are present as row names in the metadata.") +} +metadata <- metadata[colnames(counts_matrix), , drop = FALSE] + +# Create SingleCellExperiment object +sce <- SingleCellExperiment(assays = list(counts = counts_matrix), + colData = metadata) +colData(sce)[[args$sample_col]] <- colData(sce)[[args$sample_col]] +colData(sce)[[args$group_col]] <- colData(sce)[[args$group_col]] + +# Run bulk_walk +walk_results <- bulk_walk( + object = sce, + de_results = de_results, + sample_col = args$sample_col, + group_col = args$group_col, + cor_method = args$cor_method, + cor_threshold = args$cor_threshold, + n_cores = args$n_cores +) + +# Save results +write.table(walk_results, file = args$output, sep = "\\t", row.names = FALSE, quote = FALSE) + +cat("bulk_walk analysis completed successfully.\\n") +""" + +R_SCRIPT_SIGNAL_WALK = """ +# Load libraries +suppressPackageStartupMessages(library(argparse)) +suppressPackageStartupMessages(library(bulksignalr)) +suppressPackageStartupMessages(library(SingleCellExperiment)) +suppressPackageStartupMessages(library(Matrix)) + +# Create a parser +parser <- ArgumentParser(description = "Run bulksignalr signal_walk") + +# Add arguments +parser$add_argument("--counts", type = "character", required = TRUE, help = "Path to the counts matrix file (TSV format)") +parser$add_argument("--metadata", type = "character", required = TRUE, help = "Path to the metadata file (TSV format)") +parser$add_argument("--bulk_walk_results", type = "character", required = TRUE, help = "Path to the bulk_walk results file (TSV format)") +parser$add_argument("--output", type = "character", required = TRUE, help = "Path to the output signaling results file (TSV format)") +parser$add_argument("--sample_col", type = "character", default = "sample", help = "Column name for samples in metadata") +parser$add_argument("--group_col", type = "character", default = "group", help = "Column name for groups in metadata") +parser$add_argument("--lr_db", type = "character", default = NULL, help = "Optional path to a custom ligand-receptor database file (TSV format)") +parser$add_argument("--n_perms", type = "integer", default = 1000, help = "Number of permutations for significance testing") +parser$add_argument("--n_cores", type = "integer", default = 1, help = "Number of cores to use") + +# Parse arguments +args <- parser$parse_args() + +# Read data +counts <- read.csv(args$counts, sep = "\\t", row.names = 1, check.names = FALSE) +counts_matrix <- as.matrix(counts) +metadata <- read.csv(args$metadata, sep = "\\t", row.names = 1, check.names = FALSE) +bulk_walk_results <- read.csv(args$bulk_walk_results, sep = "\\t", check.names = FALSE) + +# Load ligand-receptor database +if (!is.null(args$lr_db)) { + lr_db <- read.csv(args$lr_db, sep = "\\t", check.names = FALSE) +} else { + lr_db <- bulksignalr::lr_db +} + +# Ensure metadata rows match counts columns +if (!all(colnames(counts_matrix) %in% rownames(metadata))) { + stop("Not all column names in the counts matrix are present as row names in the metadata.") +} +metadata <- metadata[colnames(counts_matrix), , drop = FALSE] + +# Create SingleCellExperiment object +sce <- SingleCellExperiment(assays = list(counts = counts_matrix), + colData = metadata) +colData(sce)[[args$sample_col]] <- colData(sce)[[args$sample_col]] +colData(sce)[[args$group_col]] <- colData(sce)[[args$group_col]] + +# Run signal_walk +signal_results <- signal_walk( + object = sce, + bulk_walk_results = bulk_walk_results, + ligand_receptor_db = lr_db, + sample_col = args$sample_col, + group_col = args$group_col, + n_perms = args$n_perms, + n_cores = args$n_cores +) + +# Save results +write.table(signal_results, file = args$output, sep = "\\t", row.names = FALSE, quote = FALSE) + +cat("signal_walk analysis completed successfully.\\n") +""" + +# --- MCP Tools --- + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_bulksignalr' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def bulk_de( + counts_file: Path, + metadata_file: Path, + design: str, + output_file: Path, + sample_col: str = "sample", + group_col: str = "group", + lfc_threshold: float = 0.0, + fdr_threshold: float = 0.05, + min_cells: int = 3, + min_samples: int = 2, + n_cores: int = 1, +): + """ + Performs differential expression analysis using bulksignalr::bulk_de. + + This tool requires an R environment with the 'bulksignalr', 'SingleCellExperiment', + 'Matrix', and 'argparse' packages installed. + + Args: + counts_file: Path to the counts matrix file (TSV format, genes as rows, cells/samples as columns). + metadata_file: Path to the metadata file (TSV format, cells/samples as rows). + design: Design formula for the model (e.g., '~ 0 + group'). + output_file: Path to save the differential expression results (TSV format). + sample_col: Column name for samples in the metadata file. + group_col: Column name for groups in the metadata file. + lfc_threshold: Log-fold change threshold for differential expression. + fdr_threshold: False discovery rate (FDR) threshold. + min_cells: Minimum number of cells a gene must be expressed in to be included. + min_samples: Minimum number of samples required within a group for comparison. + n_cores: Number of CPU cores to use for parallel processing. + """ + # Input validation + if not counts_file.is_file(): + raise FileNotFoundError(f"Counts file not found: {counts_file}") + if not metadata_file.is_file(): + raise FileNotFoundError(f"Metadata file not found: {metadata_file}") + if not design.strip(): + raise ValueError("Design formula cannot be empty.") + if not (0 <= fdr_threshold <= 1): + raise ValueError("fdr_threshold must be between 0 and 1.") + if lfc_threshold < 0: + raise ValueError("lfc_threshold must be non-negative.") + if min_cells < 0: + raise ValueError("min_cells must be non-negative.") + if min_samples < 1: + raise ValueError("min_samples must be at least 1.") + if n_cores < 1: + raise ValueError("n_cores must be at least 1.") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_path = "" + try: + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file: + r_script_file.write(R_SCRIPT_BULK_DE) + r_script_path = r_script_file.name + + cmd = [ + "Rscript", r_script_path, + "--counts", str(counts_file), + "--metadata", str(metadata_file), + "--output", str(output_file), + "--design", design, + "--sample_col", sample_col, + "--group_col", group_col, + "--lfc_threshold", str(lfc_threshold), + "--fdr_threshold", str(fdr_threshold), + "--min_cells", str(min_cells), + "--min_samples", str(min_samples), + "--n_cores", str(n_cores), + ] + + process = subprocess.run(cmd, check=True, capture_output=True, text=True) + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_file)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with return code {e.returncode}.\n" + f"Command: {' '.join(e.cmd)}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + finally: + if r_script_path and Path(r_script_path).exists(): + Path(r_script_path).unlink() + + +@mcp.tool() +def bulk_walk( + counts_file: Path, + metadata_file: Path, + de_results_file: Path, + output_file: Path, + sample_col: str = "sample", + group_col: str = "group", + cor_method: str = "spearman", + cor_threshold: float = 0.4, + n_cores: int = 1, +): + """ + Identifies co-regulated gene clusters using bulksignalr::bulk_walk. + + This tool requires an R environment with the 'bulksignalr', 'SingleCellExperiment', + 'Matrix', and 'argparse' packages installed. + + Args: + counts_file: Path to the counts matrix file (TSV format). + metadata_file: Path to the metadata file (TSV format). + de_results_file: Path to the differential expression results file from `bulk_de`. + output_file: Path to save the co-regulation cluster results (TSV format). + sample_col: Column name for samples in the metadata file. + group_col: Column name for groups in the metadata file. + cor_method: Correlation method to use ('spearman' or 'pearson'). + cor_threshold: Correlation threshold for defining co-regulation. + n_cores: Number of CPU cores to use for parallel processing. + """ + # Input validation + if not counts_file.is_file(): + raise FileNotFoundError(f"Counts file not found: {counts_file}") + if not metadata_file.is_file(): + raise FileNotFoundError(f"Metadata file not found: {metadata_file}") + if not de_results_file.is_file(): + raise FileNotFoundError(f"DE results file not found: {de_results_file}") + if cor_method not in ["spearman", "pearson"]: + raise ValueError("cor_method must be either 'spearman' or 'pearson'.") + if not (0 <= cor_threshold <= 1): + raise ValueError("cor_threshold must be between 0 and 1.") + if n_cores < 1: + raise ValueError("n_cores must be at least 1.") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_path = "" + try: + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file: + r_script_file.write(R_SCRIPT_BULK_WALK) + r_script_path = r_script_file.name + + cmd = [ + "Rscript", r_script_path, + "--counts", str(counts_file), + "--metadata", str(metadata_file), + "--de_results", str(de_results_file), + "--output", str(output_file), + "--sample_col", sample_col, + "--group_col", group_col, + "--cor_method", cor_method, + "--cor_threshold", str(cor_threshold), + "--n_cores", str(n_cores), + ] + + process = subprocess.run(cmd, check=True, capture_output=True, text=True) + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_file)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with return code {e.returncode}.\n" + f"Command: {' '.join(e.cmd)}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + finally: + if r_script_path and Path(r_script_path).exists(): + Path(r_script_path).unlink() + + +@mcp.tool() +def signal_walk( + counts_file: Path, + metadata_file: Path, + bulk_walk_results_file: Path, + output_file: Path, + sample_col: str = "sample", + group_col: str = "group", + ligand_receptor_db_file: Optional[Path] = None, + n_perms: int = 1000, + n_cores: int = 1, +): + """ + Identifies potential signaling genes using bulksignalr::signal_walk. + + This tool requires an R environment with the 'bulksignalr', 'SingleCellExperiment', + 'Matrix', and 'argparse' packages installed. + + Args: + counts_file: Path to the counts matrix file (TSV format). + metadata_file: Path to the metadata file (TSV format). + bulk_walk_results_file: Path to the co-regulation results from `bulk_walk`. + output_file: Path to save the signaling analysis results (TSV format). + sample_col: Column name for samples in the metadata file. + group_col: Column name for groups in the metadata file. + ligand_receptor_db_file: Optional path to a custom ligand-receptor database (TSV). + If not provided, the built-in database is used. + n_perms: Number of permutations for significance testing. + n_cores: Number of CPU cores to use for parallel processing. + """ + # Input validation + if not counts_file.is_file(): + raise FileNotFoundError(f"Counts file not found: {counts_file}") + if not metadata_file.is_file(): + raise FileNotFoundError(f"Metadata file not found: {metadata_file}") + if not bulk_walk_results_file.is_file(): + raise FileNotFoundError(f"bulk_walk results file not found: {bulk_walk_results_file}") + if ligand_receptor_db_file and not ligand_receptor_db_file.is_file(): + raise FileNotFoundError(f"Ligand-receptor DB file not found: {ligand_receptor_db_file}") + if n_perms < 1: + raise ValueError("n_perms must be at least 1.") + if n_cores < 1: + raise ValueError("n_cores must be at least 1.") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_path = "" + try: + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file: + r_script_file.write(R_SCRIPT_SIGNAL_WALK) + r_script_path = r_script_file.name + + cmd = [ + "Rscript", r_script_path, + "--counts", str(counts_file), + "--metadata", str(metadata_file), + "--bulk_walk_results", str(bulk_walk_results_file), + "--output", str(output_file), + "--sample_col", sample_col, + "--group_col", group_col, + "--n_perms", str(n_perms), + "--n_cores", str(n_cores), + ] + if ligand_receptor_db_file: + cmd.extend(["--lr_db", str(ligand_receptor_db_file)]) + + process = subprocess.run(cmd, check=True, capture_output=True, text=True) + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_file)], + } + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"R script execution failed with return code {e.returncode}.\n" + f"Command: {' '.join(e.cmd)}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + finally: + if r_script_path and Path(r_script_path).exists(): + Path(r_script_path).unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-clusterprofiler/app/bioconductor-clusterprofiler_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-clusterprofiler/app/bioconductor-clusterprofiler_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..381143f76b3f056ac767affbde2959d1cbf7f4dc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-clusterprofiler/app/bioconductor-clusterprofiler_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-clusterprofiler/app/bioconductor-clusterprofiler_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_clusterprofiler' + + +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-genomeinfodb/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..da1928863b64adf79436afc2d7c96a25511e3b41 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/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-genomeinfodb via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-genomeinfodb -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-genomeinfodb_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-genomeinfodb_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-genomeinfodb_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_server.py b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_server.py new file mode 100644 index 0000000000000000000000000000000000000000..247ad74c244354ac05b0ea362a4966374284ace9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_server.py @@ -0,0 +1,325 @@ +import subprocess +import tempfile +import shutil +from pathlib import Path +from typing import Optional, List, Dict +import sys +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Mock the mcp.tool decorator for standalone execution +class mcp: + def tool(func): + return func + +def _check_rscript(): + """Checks if Rscript is available in the system PATH.""" + if not shutil.which("Rscript"): + raise EnvironmentError( + "Rscript not found in PATH. R and the GenomeInfoDb Bioconductor " + "package are required to run these tools." + ) + +@mcp.tool +def genomeinfodb_list_styles(species: str) -> Dict: + """ + Lists the available chromosome naming styles (e.g., UCSC, NCBI) for a given species. + + This tool wraps the `genomeStyles()` function from the R/Bioconductor GenomeInfoDb package. + + Args: + species: The scientific name of the species (e.g., "Homo sapiens", "Mus musculus"). + + Returns: + A dictionary containing the command executed, stdout (with the list of styles), + stderr, and a list of available styles. + """ + _check_rscript() + if not species: + raise ValueError("The 'species' argument cannot be empty.") + + r_script_content = f""" + suppressPackageStartupMessages(library(GenomeInfoDb)) + tryCatch({{ + styles <- genomeStyles('{species}') + if (length(styles) == 0) {{ + cat("No styles found for species: {species}\\n", file=stderr()) + }} else {{ + cat(paste(names(styles), collapse='\\n')) + }} + }}, error = function(e) {{ + cat(paste("Error:", e$message, "\\n"), file=stderr()) + quit(status = 1) + }}) + """ + + command = ["Rscript", "-e", r_script_content] + command_executed = " ".join(command) + logger.info(f"Executing command: {command_executed}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + styles_list = result.stdout.strip().split('\n') if result.stdout.strip() else [] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "styles": styles_list, + } + except subprocess.CalledProcessError as e: + logger.error(f"Error executing GenomeInfoDb R script: {e.stderr}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to list genome styles. Check if the species name is correct and GenomeInfoDb is installed.", + } + +@mcp.tool +def genomeinfodb_map_seqlevels( + target_style: str, + species: str, + input_file: Path, + output_file: Path, +) -> Dict: + """ + Maps chromosome names from an input file to a specified naming convention (style). + + This tool wraps the `mapSeqlevels()` function from the R/Bioconductor GenomeInfoDb package. + It produces a two-column TSV file: 'original' and 'mapped'. + + Args: + target_style: The target naming style (e.g., "UCSC", "NCBI", "Ensembl"). + species: The scientific name of the species (e.g., "Homo sapiens"). + input_file: Path to a file containing one chromosome name per line. + output_file: Path to write the output TSV file with original and mapped names. + + Returns: + A dictionary containing the command executed, stdout, stderr, and the path to the output file. + """ + _check_rscript() + if not target_style: + raise ValueError("The 'target_style' argument cannot be empty.") + if not species: + raise ValueError("The 'species' argument cannot be empty.") + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + suppressPackageStartupMessages(library(GenomeInfoDb)) + args <- commandArgs(trailingOnly=TRUE) + inputFile <- args[1] + targetStyle <- args[2] + species <- args[3] + outputFile <- args[4] + + tryCatch({{ + seqs <- readLines(inputFile) + if (length(seqs) == 0) {{ + stop("Input file is empty.") + }} + mapped_seqs <- mapSeqlevels(seqs, style=targetStyle, species=species) + output_df <- data.frame(original=seqs, mapped=mapped_seqs) + write.table(output_df, file=outputFile, sep='\\t', row.names=FALSE, col.names=TRUE, quote=FALSE) + }}, error = function(e) {{ + cat(paste("Error:", e$message, "\\n"), file=stderr()) + quit(status = 1) + }}) + """ + + command = [ + "Rscript", + "-e", + r_script_content, + str(input_file), + target_style, + species, + str(output_file), + ] + command_executed = " ".join(command) + logger.info(f"Executing command: {command_executed}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {"mapped_seqlevels": str(output_file)}, + } + except subprocess.CalledProcessError as e: + logger.error(f"Error executing GenomeInfoDb R script: {e.stderr}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to map seqlevels. Check parameters and ensure input file is correctly formatted.", + } + +@mcp.tool +def genomeinfodb_get_seqinfo( + genome: str, + output_file: Path, +) -> Dict: + """ + Fetches sequence information (lengths, circularity) for a given genome assembly. + + This tool wraps the `Seqinfo()` constructor from the R/Bioconductor GenomeInfoDb package. + It produces a TSV file containing seqnames, seqlengths, isCircular, and genome. + + Args: + genome: The genome assembly identifier (e.g., "hg19", "hg38", "mm10"). + output_file: Path to write the output TSV file with sequence information. + + Returns: + A dictionary containing the command executed, stdout, stderr, and the path to the output file. + """ + _check_rscript() + if not genome: + raise ValueError("The 'genome' argument cannot be empty.") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + suppressPackageStartupMessages(library(GenomeInfoDb)) + args <- commandArgs(trailingOnly=TRUE) + genome_id <- args[1] + outputFile <- args[2] + + tryCatch({{ + si <- Seqinfo(genome=genome_id) + si_df <- as.data.frame(si) + write.table(si_df, file=outputFile, sep='\\t', row.names=TRUE, col.names=NA, quote=FALSE) + }}, error = function(e) {{ + cat(paste("Error:", e$message, "\\n"), file=stderr()) + quit(status = 1) + }}) + """ + + command = ["Rscript", "-e", r_script_content, genome, str(output_file)] + command_executed = " ".join(command) + logger.info(f"Executing command: {command_executed}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {"seqinfo": str(output_file)}, + } + except subprocess.CalledProcessError as e: + logger.error(f"Error executing GenomeInfoDb R script: {e.stderr}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to get seqinfo. Check if the genome identifier is correct.", + } + +@mcp.tool +def genomeinfodb_rename_seqlevels( + input_file: Path, + mapping_file: Path, + output_file: Path, +) -> Dict: + """ + Renames chromosome names from an input file based on a provided mapping file. + + This tool wraps the `renameSeqlevels()` function from the R/Bioconductor GenomeInfoDb package. + It produces a two-column TSV file: 'original' and 'renamed'. + + Args: + input_file: Path to a file containing one chromosome name per line. + mapping_file: Path to a two-column TSV file (old_namenew_name) without a header. + output_file: Path to write the output TSV file with original and renamed names. + + Returns: + A dictionary containing the command executed, stdout, stderr, and the path to the output file. + """ + _check_rscript() + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not mapping_file.is_file(): + raise FileNotFoundError(f"Mapping file not found: {mapping_file}") + + output_file.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + suppressPackageStartupMessages(library(GenomeInfoDb)) + args <- commandArgs(trailingOnly=TRUE) + inputFile <- args[1] + mappingFile <- args[2] + outputFile <- args[3] + + tryCatch({{ + seqs <- readLines(inputFile) + if (length(seqs) == 0) {{ + stop("Input file is empty.") + }} + mapping_df <- read.table(mappingFile, sep='\\t', header=FALSE, stringsAsFactors=FALSE, col.names=c("old", "new")) + if (nrow(mapping_df) == 0) {{ + stop("Mapping file is empty.") + }} + value <- setNames(mapping_df$new, mapping_df$old) + renamed_seqs <- renameSeqlevels(seqs, value) + output_df <- data.frame(original=seqs, renamed=renamed_seqs) + write.table(output_df, file=outputFile, sep='\\t', row.names=FALSE, col.names=TRUE, quote=FALSE) + }}, error = function(e) {{ + cat(paste("Error:", e$message, "\\n"), file=stderr()) + quit(status = 1) + }}) + """ + + command = [ + "Rscript", + "-e", + r_script_content, + str(input_file), + str(mapping_file), + str(output_file), + ] + command_executed = " ".join(command) + logger.info(f"Executing command: {command_executed}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": {"renamed_seqlevels": str(output_file)}, + } + except subprocess.CalledProcessError as e: + logger.error(f"Error executing GenomeInfoDb R script: {e.stderr}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to rename seqlevels. Check file formats and content.", + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f38a077fe622112b1e7640568dec0e444b5abc9c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_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-genomeinfodb/app/bioconductor-genomeinfodb_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_genomeinfodb' + + +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-genomeinfodb/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..f45008fc4f9db6ed1b6aafd4d5c65aa0c5ac8c66 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-genomeinfodb: + build: . + image: mcp-bioconductor-genomeinfodb:latest + container_name: mcp-bioconductor-genomeinfodb + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-genomeinfodb + 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-genomeinfodb/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9340ec8e0823c9ed7de8c928916c2c625108234e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-genomeinfodb + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-genomeinfodb/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-ggtree/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-ggtree/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0399891b152181e15ed14a44c4e515af571b78aa --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/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-ggtree via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-ggtree -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-ggtree_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-ggtree_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-ggtree_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_server.py b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e6e0b6b9a9c93df09d826c53c202b9b1dbc33801 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_server.py @@ -0,0 +1,216 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any +import os + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_ggtree' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_ggtree_script( + r_script_content: str, + input_tree_file: Optional[Path] = None, + input_annotation_file: Optional[Path] = None, + output_image_format: str = "png", + image_width: int = 800, + image_height: int = 600, + output_filename_prefix: str = "ggtree_plot", +) -> Dict[str, Any]: + """ + Executes a custom R script using the bioconductor-ggtree package for phylogenetic tree visualization. + + This tool provides a flexible way to leverage ggtree's capabilities by allowing + users to provide arbitrary R code. It also facilitates common tasks like + loading tree and annotation files and saving plots. + + Note: The 'bioconductor-ggtree' package is an R library and does not expose + direct command-line functions or subcommands. This MCP tool provides a + generic R script execution environment where 'ggtree' and its dependencies + are available. Users are expected to provide R code that utilizes 'ggtree' + functions. + + Args: + r_script_content: A string containing the R code to execute. This script + should assume 'ggtree', 'treeio', 'ggplot2', and 'dplyr' + are loaded. It can access input files via variables + `input_tree_file` and `input_annotation_file` (which + will contain the POSIX paths as strings). The script + should write any desired output files to the current + working directory. + input_tree_file: Optional path to a phylogenetic tree file (e.g., Newick, Nexus). + If provided, its POSIX path will be available as the + `input_tree_file` variable in the R script. + input_annotation_file: Optional path to an annotation data file (e.g., CSV, TSV). + If provided, its POSIX path will be available as the + `input_annotation_file` variable in the R script. + output_image_format: The desired format for the output image. Supported: "png", "pdf", "svg", "jpeg", "tiff", "bmp". + This format will be used for the default `ggsave` call + if a ggplot object `p` is found and not explicitly saved + by `r_script_content`. + image_width: Width of the output image. For raster formats (png, jpeg, etc.), + this is in pixels. For vector formats (pdf, svg), this is + converted to inches (assuming 96 DPI for conversion from pixels). + image_height: Height of the output image. Similar to `image_width`. + output_filename_prefix: A prefix for the output image file. The full filename + will be '{prefix}.{format}'. This is used for the + default `ggsave` call. + + Returns: + A dictionary containing: + - command_executed: The Rscript command that was run. + - stdout: Standard output from the R script. + - stderr: Standard error from the R script. + - output_files: A list of paths to any generated output files. + - error: An error message if the R script execution failed. + """ + # Input validation + if not r_script_content.strip(): + raise ValueError("r_script_content cannot be empty.") + + if input_tree_file: + if not input_tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {input_tree_file}") + input_tree_file_posix = input_tree_file.as_posix() + else: + input_tree_file_posix = "" + + if input_annotation_file: + if not input_annotation_file.is_file(): + raise FileNotFoundError(f"Input annotation file not found: {input_annotation_file}") + input_annotation_file_posix = input_annotation_file.as_posix() + else: + input_annotation_file_posix = "" + + supported_formats = {"png", "pdf", "svg", "jpeg", "tiff", "bmp"} + if output_image_format.lower() not in supported_formats: + raise ValueError(f"Unsupported output image format: {output_image_format}. " + f"Supported formats are: {', '.join(supported_formats)}") + + if image_width <= 0: + raise ValueError("Image width must be a positive integer.") + if image_height <= 0: + raise ValueError("Image height must be a positive integer.") + if not output_filename_prefix: + raise ValueError("Output filename prefix cannot be empty.") + + with tempfile.TemporaryDirectory() as tmpdir: + work_dir = Path(tmpdir) + + # Define output image path for default ggsave + output_image_name = f"{output_filename_prefix}.{output_image_format.lower()}" + + # Construct the R script + r_code = f""" + # Load necessary libraries + suppressPackageStartupMessages(library(ggtree)) + suppressPackageStartupMessages(library(treeio)) + suppressPackageStartupMessages(library(ggplot2)) + suppressPackageStartupMessages(library(dplyr)) # Often useful for data manipulation + + # Set working directory for outputs + setwd("{work_dir.as_posix()}") + + # Make input file paths available as R variables + input_tree_file <- if ("{input_tree_file_posix}" != "") {{ + "{input_tree_file_posix}" + }} else {{ + NULL + }} + input_annotation_file <- if ("{input_annotation_file_posix}" != "") {{ + "{input_annotation_file_posix}" + }} else {{ + NULL + }} + + # Parameters for default ggsave + output_image_name_param <- "{output_image_name}" + image_width_param <- {image_width} + image_height_param <- {image_height} + output_image_format_param <- "{output_image_format.lower()}" + + # Determine units and DPI for ggsave based on format + units_val <- "in" # Default for vector graphics + dpi_val <- NULL # Default for vector graphics + + if (output_image_format_param %in% c("png", "jpeg", "tiff", "bmp")) {{ + units_val <- "px" + dpi_val <- 300 # Common DPI for raster images + }} else {{ # pdf, svg, etc. + # Convert pixels to inches for vector formats if input was in pixels + # Assuming 96 DPI for screen pixels to inches conversion if no specific DPI is given + image_width_param <- image_width_param / 96 + image_height_param <- image_height_param / 96 + }} + + # User-provided R script content + # This is where the user's custom ggtree logic should go. + # The user's script can use 'input_tree_file', 'input_annotation_file', + # 'output_image_name_param', 'image_width_param', 'image_height_param', + # 'units_val', 'dpi_val' if they wish to override or extend the default + # saving behavior. + {r_script_content} + + # Attempt to save the last ggplot object 'p' if it exists and + # the user's script hasn't explicitly saved a plot with the default name. + # This provides a fallback for simple plotting tasks. + if (exists("p") && inherits(p, "ggplot")) {{ + if (!file.exists(output_image_name_param)) {{ # Only save if user script didn't already save to this name + message(paste0("Saving ggplot object 'p' to ", output_image_name_param, " using default parameters.")) + ggsave(output_image_name_param, plot = p, width = image_width_param, height = image_height_param, units = units_val, dpi = dpi_val) + }} else {{ + message(paste0("File '", output_image_name_param, "' already exists. Skipping default ggsave.")) + }} + }} else {{ + message("No 'p' (ggplot object) found or saved by user script. No default image output will be generated.") + }} + """ + + r_script_file = work_dir / "run_ggtree.R" + r_script_file.write_text(r_code) + + command = ["Rscript", r_script_file.as_posix()] + + # Execute the R script + try: + process = subprocess.run( + command, + cwd=work_dir, + capture_output=True, + text=True, + check=True, + env=os.environ # Inherit environment to ensure R is found + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": f"Rscript execution failed with exit code {e.returncode}", + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Rscript command not found. Is R installed and in PATH?", + "output_files": [], + "error": "Rscript executable not found.", + } + + # Collect output files + output_files = [f.as_posix() for f in work_dir.iterdir() if f.is_file() and f != r_script_file] + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..110bd5c5960a4a016806eec7461988b2a17f973f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/bioconductor-ggtree_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-ggtree/app/bioconductor-ggtree_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_ggtree' + + +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-ggtree/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-ggtree/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-ggtree/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b26bd3cd63d1531de3d839f1af8f1c8289770276 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-ggtree: + build: . + image: mcp-bioconductor-ggtree:latest + container_name: mcp-bioconductor-ggtree + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-ggtree + 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-ggtree/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-ggtree/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f86f271d340f923d10117919ff269c0123223824 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-ggtree + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-ggtree/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-ggtree/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-ggtree/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-partcnv/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-partcnv/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2392c0fa8a90d8e6ef61d8d170fd1a152c4d14e8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-partcnv/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-partcnv via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-partcnv -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-partcnv_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-partcnv_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-partcnv_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-partcnv/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-partcnv/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a8803ce64509d3ab126b15479ead245bf8dbda6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-partcnv/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-partcnv: + build: . + image: mcp-bioconductor-partcnv:latest + container_name: mcp-bioconductor-partcnv + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-partcnv + 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-rhdf5/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..23b2b83e0e1daf22cc0c286cbebf06a5f2214728 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/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-rhdf5 via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-rhdf5 -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-rhdf5_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-rhdf5_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-rhdf5_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_server.py b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c55252dadd675d264d417fe1298bafbb74b31d8c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_server.py @@ -0,0 +1,192 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Note: The 'mcp' import is omitted as per instructions, but @mcp.tool() is used. + +def _check_rscript_available() -> None: + """Checks if Rscript is available in the system's PATH.""" + try: + subprocess.run(["Rscript", "--version"], check=True, capture_output=True, text=True) + except FileNotFoundError: + raise RuntimeError( + "Rscript command not found. Please ensure R is installed and Rscript is in your system's PATH." + ) + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"Rscript command found but returned an error: {e.stderr.strip()}" + ) + except Exception as e: + raise RuntimeError(f"An unexpected error occurred while checking Rscript: {e}") + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_rhdf5' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_rhdf5_r_script( + r_script_content: str, + input_files: Optional[List[Path]] = None, + output_dir: Optional[Path] = None, + r_args: Optional[List[str]] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Executes an R script that can utilize the bioconductor-rhdf5 package. + + Note: bioconductor-rhdf5 is an R package, not a standalone command-line tool. + This MCP tool provides a mechanism to run arbitrary R scripts that use rhdf5 + by executing them via the 'Rscript' command-line utility. + Users are expected to provide the R script content directly, ensuring it + includes `library(rhdf5)` and performs the desired HDF5 operations. + + Args: + r_script_content: A string containing the R script to be executed. + This script should include `library(rhdf5)` and + perform the desired HDF5 operations. + input_files: An optional list of Path objects pointing to input files + that the R script might need. These files will be symlinked + into the working directory where the R script is executed, + making them accessible by their base names. + output_dir: An optional Path object specifying the directory where + the R script should write its output files. If not provided, + a temporary directory will be created for outputs. + r_args: Optional list of additional arguments to pass to Rscript. + These arguments will be available in the R script via `commandArgs(trailingOnly=TRUE)`. + verbose: If True, Rscript will print verbose output during execution. + + Returns: + A dictionary containing execution details: + - command_executed: The full command string executed. + - stdout: Standard output from the Rscript execution. + - stderr: Standard error from the Rscript execution. + - output_files: A list of Path objects for any files found in the output directory. + - error: Optional error message if the Rscript execution fails. + """ + _check_rscript_available() + + if not r_script_content or not r_script_content.strip(): + raise ValueError("R script content cannot be empty.") + + # Validate input files + if input_files: + for f in input_files: + if not isinstance(f, Path): + raise TypeError(f"Input file path must be a Path object, got {type(f)}") + if not f.is_file(): + raise FileNotFoundError(f"Input file not found: {f}") + + # Prepare output directory + temp_output_dir_obj: Optional[tempfile.TemporaryDirectory] = None + if output_dir: + if not isinstance(output_dir, Path): + raise TypeError(f"Output directory path must be a Path object, got {type(output_dir)}") + output_dir.mkdir(parents=True, exist_ok=True) + else: + temp_output_dir_obj = tempfile.TemporaryDirectory() + output_dir = Path(temp_output_dir_obj.name) + + temp_r_script_path: Optional[Path] = None + stdout_data = "" + stderr_data = "" + output_files_list: List[Path] = [] + error_message: Optional[str] = None + command_executed_str = "" + + try: + # Write the R script content to a temporary file + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".R", encoding="utf-8") as temp_r_script_file: + temp_r_script_file.write(r_script_content) + temp_r_script_path = Path(temp_r_script_file.name) + + command = ["Rscript"] + if verbose: + command.append("--verbose") + command.append(str(temp_r_script_path)) + + if r_args: + if not isinstance(r_args, list) or not all(isinstance(arg, str) for arg in r_args): + raise TypeError("r_args must be a list of strings.") + command.extend(r_args) + + command_executed_str = " ".join(command) + + # Create symlinks for input files in the output directory + # This makes them easily accessible by their base name from the R script + for f in (input_files or []): + target_path = output_dir / f.name + if not target_path.exists(): # Avoid overwriting existing files or symlinks + try: + target_path.symlink_to(f.resolve()) # Use resolve() for absolute path + except OSError as e: + # Handle cases where symlinking might not be supported or fails + # Fallback to copying if symlink fails, or just log a warning + print(f"Warning: Could not symlink {f} to {target_path}. Error: {e}") + # Optionally, copy the file instead: + # import shutil + # shutil.copy(f, target_path) + + process = subprocess.run( + command, + cwd=output_dir, # Execute Rscript in the output directory + check=True, + capture_output=True, + text=True, + encoding="utf-8", + ) + stdout_data = process.stdout + stderr_data = process.stderr + + # Collect output files from the output directory + # Exclude the temporary R script and any symlinked input files + for f in output_dir.iterdir(): + if f.is_file() and f != temp_r_script_path and (input_files is None or f not in [output_dir / i.name for i in input_files]): + output_files_list.append(f) + + except FileNotFoundError: + error_message = "Rscript command not found. Please ensure R is installed and Rscript is in your system's PATH." + stderr_data = error_message + except subprocess.CalledProcessError as e: + error_message = f"Rscript execution failed with exit code {e.returncode}." + stdout_data = e.stdout + stderr_data = e.stderr + except ValueError as e: + error_message = f"Input validation error: {e}" + stderr_data = str(e) + except TypeError as e: + error_message = f"Type error: {e}" + stderr_data = str(e) + except Exception as e: + error_message = f"An unexpected error occurred: {e}" + stderr_data = str(e) + finally: + # Clean up the temporary R script file + if temp_r_script_path and temp_r_script_path.exists(): + try: + temp_r_script_path.unlink() + except OSError as e: + print(f"Warning: Failed to delete temporary R script {temp_r_script_path}: {e}") + + # Clean up temporary output directory if it was created by the tool + if temp_output_dir_obj: + try: + temp_output_dir_obj.cleanup() + except Exception as e: + # Log cleanup error if necessary, but don't fail the tool + print(f"Warning: Failed to clean up temporary directory {temp_output_dir_obj.name}: {e}") + + result = { + "command_executed": command_executed_str, + "stdout": stdout_data, + "stderr": stderr_data, + "output_files": [str(f) for f in output_files_list], + } + if error_message: + result["error"] = error_message + + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5f01840c51c6ab60e7c5e64891c7ff6d803397da --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_rhdf5' + + +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-rhdf5/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-rhdf5/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..371502a90b6b0e642754aebfdcfbed6956a61c7d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-rhdf5: + build: . + image: mcp-bioconductor-rhdf5:latest + container_name: mcp-bioconductor-rhdf5 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-rhdf5 + 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-rhdf5/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5cee49d16e0b5556057e50c59af4bc0cf5ee744b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-rhdf5 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-rhdf5/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rhdf5/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2a2ae6de9292099994cb6deedf1e0671bc84fe9c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/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-scarray.sat via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-scarray.sat -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-scarray.sat_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-scarray.sat_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-scarray.sat_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_server.py b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a64e65c8ea3e318e6c41dec8f5e6518aad7a83c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_server.py @@ -0,0 +1,146 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, Optional + +# MCP decorator is assumed to be available from the environment +# import mcp + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_scarray_sat' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def create_gds_seurat_object( + input_dir: Path, + output_rds: Path, + output_gds: Path, + project_name: str = "scproject", + min_cells: int = 3, + min_features: int = 200, +) -> Dict: + """ + Creates a GDS-backed Seurat object from 10x Genomics data using SCArray.sat. + + This tool wraps the bioconductor-scarray.sat R package to convert a standard + 10x Genomics count matrix directory (containing matrix.mtx, features.tsv, + and barcodes.tsv files) into a Seurat object. The key feature is that the + count matrix is stored in a memory-efficient Genomic Data Structure (GDS) file, + making it suitable for very large datasets. + + Args: + input_dir: Path to the input directory containing 10x Genomics data files + (e.g., 'matrix.mtx.gz', 'features.tsv.gz', 'barcodes.tsv.gz'). + output_rds: Path to save the output Seurat object (.rds file). + output_gds: Path to save the generated Genomic Data Structure (.gds file). + project_name: Name for the Seurat project. + min_cells: Include features detected in at least this many cells. + min_features: Include cells with at least this many detected features. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a + dictionary of output file paths. + """ + # --- Input Validation --- + if not input_dir.is_dir(): + raise ValueError(f"Input directory '{input_dir}' does not exist or is not a directory.") + + # Check for required 10x files (allowing for .gz extension and alternative names) + barcodes_path = next(input_dir.glob("barcodes.tsv*"), None) + features_path = next(input_dir.glob("features.tsv*"), None) or next(input_dir.glob("genes.tsv*"), None) + matrix_path = next(input_dir.glob("matrix.mtx*"), None) + + if not all([barcodes_path, features_path, matrix_path]): + raise FileNotFoundError( + "The input directory must contain 'barcodes.tsv', 'features.tsv' (or 'genes.tsv'), " + "and 'matrix.mtx'. Compressed versions (.gz) are accepted." + ) + + if min_cells < 0: + raise ValueError("min_cells must be a non-negative integer.") + if min_features < 0: + raise ValueError("min_features must be a non-negative integer.") + + # --- R Script Generation --- + # This script performs the core logic using the R libraries. + r_script_content = f""" + # Load required libraries + library(Seurat) + library(SCArray.sat) + library(SCArray) + + # Define parameters passed from the Python wrapper + input_dir_path <- "{input_dir.resolve()}" + output_rds_path <- "{output_rds.resolve()}" + output_gds_path <- "{output_gds.resolve()}" + project_id <- "{project_name}" + min_cell_count <- {min_cells} + min_feature_count <- {min_features} + + # Read the 10X data. Read10X handles compressed files automatically. + counts <- Seurat::Read10X(data.dir = input_dir_path) + + # Create the GDS file from the count matrix. This is the main step + # provided by the SCArray package. + SCArray::scarray_create(output_gds_path, counts, verbose = TRUE) + + # Create the Seurat object using the GDS-backed matrix. + # The scarray() function loads the GDS file as a DelayedMatrix object. + seurat_obj <- Seurat::CreateSeuratObject( + counts = SCArray::scarray(output_gds_path), + project = project_id, + min.cells = min_cell_count, + min.features = min_feature_count + ) + + # Save the final Seurat object to an RDS file for later use in R. + saveRDS(seurat_obj, file = output_rds_path) + + cat("Successfully created GDS-backed Seurat object and saved to RDS.\\n") + """ + + # --- Subprocess Execution --- + # Use a temporary file to hold the R script + r_script_path = None + try: + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file: + r_script_file.write(r_script_content) + r_script_path = Path(r_script_file.name) + + cmd = ["Rscript", str(r_script_path)] + command_executed = " ".join(cmd) + + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + + # --- Return Structured Output --- + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": { + "seurat_object_rds": str(output_rds.resolve()), + "gds_file": str(output_gds.resolve()) + } + } + except subprocess.CalledProcessError as e: + # --- Error Handling --- + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "error": "R script execution failed. Please check the logs for details from Seurat or SCArray." + } + finally: + # Ensure the temporary R script is cleaned up + if r_script_path and r_script_path.exists(): + r_script_path.unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..929731a485bdf666500546a79b7a218be493bb89 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_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-scarray.sat/app/bioconductor-scarray.sat_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_scarray_sat' + + +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-scarray.sat/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..db5d8105ceb8dd08e531d26a061c149becd2a8cb --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-scarray.sat: + build: . + image: mcp-bioconductor-scarray.sat:latest + container_name: mcp-bioconductor-scarray.sat + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-scarray.sat + 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-scarray.sat/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scarray.sat/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2646e20441013d03405080dbf0eeda22266d80e7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/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-tidyspatialexperiment via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-tidyspatialexperiment -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-tidyspatialexperiment_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-tidyspatialexperiment_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-tidyspatialexperiment_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1bdd07d85caa488071dbadd8c9a30f470618f111 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-tidyspatialexperiment: + build: . + image: mcp-bioconductor-tidyspatialexperiment:latest + container_name: mcp-bioconductor-tidyspatialexperiment + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-tidyspatialexperiment + 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-tidyspatialexperiment/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..82f1a8830f1c7e487050687adca4c80e48c83bad --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-tidyspatialexperiment + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tidyspatialexperiment/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..60978ac86d56ab84f8d652b5b20c6a6efbf5c372 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/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-vectrapolarisdata via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-vectrapolarisdata -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-vectrapolarisdata_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-vectrapolarisdata_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-vectrapolarisdata_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_server.py b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_server.py new file mode 100644 index 0000000000000000000000000000000000000000..85d22b61a106e6cc8e80cbb8e7111c5220292162 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_server.py @@ -0,0 +1,217 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_vectrapolarisdata' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def get_human_lung_cancer_vectra( + output_path: str +) -> Dict[str, Any]: + """ + Load the Human Lung Cancer dataset (Vectra 3) from the VectraPolarisData package + and save it as an R RDS file. + + This dataset contains multiplex imaging data from lung cancer tissue collected + on a Vectra 3 instrument. The data is provided as a SpatialExperiment object + containing segmented and phenotyped single-cell information. + + Args: + output_path: The file path where the SpatialExperiment object should be saved (e.g., 'lung_cancer.rds'). + """ + # Input validation + out_p = Path(output_path) + if out_p.suffix.lower() != ".rds": + out_p = out_p.with_suffix(".rds") + + # Ensure parent directory exists + out_p.parent.mkdir(parents=True, exist_ok=True) + abs_output_path = str(out_p.absolute()) + + # R script to load data and save to RDS + r_code = f""" + if (!requireNamespace("VectraPolarisData", quietly = TRUE)) {{ + stop("Package 'VectraPolarisData' is not installed.") + }} + suppressPackageStartupMessages(library(VectraPolarisData)) + message("Downloading/Loading HumanLungCancerVectra dataset...") + sce <- HumanLungCancerVectra() + saveRDS(sce, file = "{abs_output_path}") + message("Successfully saved to {abs_output_path}") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e 'HumanLungCancerVectra()'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [abs_output_path], + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": e.cmd, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to retrieve HumanLungCancerVectra dataset. Ensure R and VectraPolarisData are installed.", + "status": "error" + } + +@mcp.tool() +def get_human_ovarian_cancer_vectra_polaris( + output_path: str +) -> Dict[str, Any]: + """ + Load the Human Ovarian Cancer dataset (Vectra Polaris) from the VectraPolarisData package + and save it as an R RDS file. + + This dataset contains multiplex imaging data from ovarian cancer tissue collected + on a Vectra Polaris instrument. The data is provided as a SpatialExperiment object + containing segmented and phenotyped single-cell information. + + Args: + output_path: The file path where the SpatialExperiment object should be saved (e.g., 'ovarian_cancer.rds'). + """ + # Input validation + out_p = Path(output_path) + if out_p.suffix.lower() != ".rds": + out_p = out_p.with_suffix(".rds") + + # Ensure parent directory exists + out_p.parent.mkdir(parents=True, exist_ok=True) + abs_output_path = str(out_p.absolute()) + + # R script to load data and save to RDS + r_code = f""" + if (!requireNamespace("VectraPolarisData", quietly = TRUE)) {{ + stop("Package 'VectraPolarisData' is not installed.") + }} + suppressPackageStartupMessages(library(VectraPolarisData)) + message("Downloading/Loading HumanOvarianCancerVectraPolaris dataset...") + sce <- HumanOvarianCancerVectraPolaris() + saveRDS(sce, file = "{abs_output_path}") + message("Successfully saved to {abs_output_path}") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e 'HumanOvarianCancerVectraPolaris()'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [abs_output_path], + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": e.cmd, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to retrieve HumanOvarianCancerVectraPolaris dataset. Ensure R and VectraPolarisData are installed.", + "status": "error" + } + +@mcp.tool() +def list_vectra_polaris_datasets() -> Dict[str, Any]: + """ + List the available datasets provided by the VectraPolarisData package. + + This tool queries the package metadata to confirm which datasets are available + for download via ExperimentHub. + """ + r_code = """ + if (!requireNamespace("VectraPolarisData", quietly = TRUE)) { + stop("Package 'VectraPolarisData' is not installed.") + } + # The package documentation specifies these two main functions + cat("Available datasets in VectraPolarisData:\n") + cat("1. HumanLungCancerVectra (Vectra 3)\n") + cat("2. HumanOvarianCancerVectraPolaris (Vectra Polaris)\n") + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e 'list_datasets'", + "stdout": result.stdout, + "stderr": result.stderr, + "datasets": ["HumanLungCancerVectra", "HumanOvarianCancerVectraPolaris"], + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": e.cmd, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to list datasets. Ensure R and VectraPolarisData are installed.", + "status": "error" + } + +@mcp.tool() +def inspect_vectra_rds( + input_path: str +) -> Dict[str, Any]: + """ + Inspect a saved VectraPolarisData RDS file to view its dimensions and metadata. + + Args: + input_path: Path to the .rds file containing the SpatialExperiment object. + """ + in_p = Path(input_path) + if not in_p.exists(): + return {"error": f"File not found: {input_path}", "status": "error"} + + abs_input_path = str(in_p.absolute()) + + r_code = f""" + sce <- readRDS("{abs_input_path}") + print(sce) + cat("\n--- Metadata ---\n") + print(metadata(sce)) + cat("\n--- ColData (First 5 rows) ---\n") + print(head(colData(sce), 5)) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e 'readRDS(\"{abs_input_path}\")'", + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": e.cmd, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to inspect RDS file. Ensure the file is a valid SpatialExperiment object.", + "status": "error" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..12dfd150f6cfb59f2ce229bc8b2aab2bed93b39f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_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-vectrapolarisdata/app/bioconductor-vectrapolarisdata_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_vectrapolarisdata' + + +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-vectrapolarisdata/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e13bfc94b01eb2fbaca4f038d84e6c564e0ad203 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-vectrapolarisdata: + build: . + image: mcp-bioconductor-vectrapolarisdata:latest + container_name: mcp-bioconductor-vectrapolarisdata + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-vectrapolarisdata + 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-vectrapolarisdata/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e0cec4fe3a0d38e5eeb80b32fdea201ab2f0da37 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-vectrapolarisdata + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-vectrapolarisdata/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_blast-legacy/Dockerfile b/Biomni/mcp_generated/mcp_blast-legacy/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dbeffcb67be1f36a7856c80288b282fced3e8520 --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/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 blast-legacy via conda (e.g., from bioconda) +RUN conda install -c bioconda blast-legacy -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/blast-legacy_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/blast-legacy_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/blast-legacy_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_server.py b/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_server.py new file mode 100644 index 0000000000000000000000000000000000000000..348a7201b6417bcb04d8d264cd0af78508c2d4f1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_server.py @@ -0,0 +1,217 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any +import os +import shutil + +# Helper function for running commands +def _run_command(cmd: List[str], cwd: Optional[Path] = None) -> Dict[str, Any]: + """ + Helper function to run a shell command and capture its output. + """ + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + cwd=cwd + ) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [] # This will be populated by the calling function if applicable + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": [] + } + +# --- MCP Tool Definitions --- + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_blast_legacy' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def blastall( + program: str, + database: Path, + query: Path, + output_file: Path, + expect_value: float = 10.0, + output_format: int = 0, + matrix: Optional[str] = None, + gap_open: Optional[int] = None, + gap_extend: Optional[int] = None, + word_size: Optional[int] = None, + num_alignments: int = 500, + num_descriptions: int = 500, + filter_query: Optional[str] = None, + hitlist_size: Optional[int] = None, + remaining_args: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Performs a BLAST search using the legacy 'blastall' executable. + + NOTE: The provided documentation for 'blast-legacy' did not include + the command-line help for 'blastall'. Parameters are inferred based + on common legacy BLAST usage. The 'remaining_args' parameter allows + passing any additional, undocumented options directly to blastall. + + Args: + program: The BLAST program to use (e.g., 'blastn', 'blastp', 'blastx', 'tblastn', 'tblastx'). + database: Path to the BLAST database to search against. + query: Path to the query sequence file in FASTA format. + output_file: Path to the output file for BLAST results. + expect_value: Expectation value (E-value) threshold for reporting matches. Default is 10.0. + output_format: Alignment view options (0-11). Default is 0 (pairwise). + Common values: 0=pairwise, 1=query-anchored showing identities, + 2=query-anchored no identities, 3=flat query-anchored, + 4=flat query-anchored no identities, 5=XML, 6=tabular, + 7=tabular with comment lines, 8=text with hitlist, + 9=text with hitlist and gapped alignments, 10=ASN.1, 11=text with hitlist and ungapped alignments. + matrix: Scoring matrix name (e.g., 'BLOSUM62', 'PAM30'). + gap_open: Gap opening penalty. + gap_extend: Gap extension penalty. + word_size: Word size for the search. + num_alignments: Number of database sequences to show alignments for. Default is 500. + num_descriptions: Number of database sequences to show descriptions for. Default is 500. + filter_query: Filter query sequence (e.g., 'T' for DUST/SEG, 'F' for no filter). + hitlist_size: Maximum number of hits to return. + remaining_args: A list of additional command-line arguments to pass directly to blastall. + """ + # Input validation + valid_programs = ["blastn", "blastp", "blastx", "tblastn", "tblastx"] + if program not in valid_programs: + raise ValueError(f"Invalid program '{program}'. Must be one of {', '.join(valid_programs)}.") + if not database.exists(): + raise FileNotFoundError(f"Database file not found: {database}. Ensure the database files (.nhr, .nin, .nsq or .phr, .pin, .psq) are present.") + if not query.exists(): + raise FileNotFoundError(f"Query file not found: {query}") + if output_format < 0 or output_format > 11: + raise ValueError(f"Invalid output_format '{output_format}'. Must be between 0 and 11.") + + cmd = ["blastall", "-p", program, "-d", str(database), "-i", str(query), "-o", str(output_file)] + + cmd.extend(["-e", str(expect_value)]) + cmd.extend(["-m", str(output_format)]) + + if matrix: + cmd.extend(["-M", matrix]) + if gap_open is not None: + cmd.extend(["-G", str(gap_open)]) + if gap_extend is not None: + cmd.extend(["-E", str(gap_extend)]) + if word_size is not None: + cmd.extend(["-W", str(word_size)]) + cmd.extend(["-v", str(num_alignments)]) + cmd.extend(["-b", str(num_descriptions)]) + if filter_query: + cmd.extend(["-F", filter_query]) + if hitlist_size is not None: + cmd.extend(["-H", str(hitlist_size)]) + + if remaining_args: + cmd.extend(remaining_args) + + result = _run_command(cmd) + result["output_files"].append(str(output_file)) + return result + +@mcp.tool() +def formatdb( + input_file: Path, + database_type: str, + output_dir: Optional[Path] = None, + title: Optional[str] = None, + parse_seqids: bool = False, + remaining_args: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Formats a FASTA file into a BLAST legacy database using the 'formatdb' executable. + + NOTE: The provided documentation for 'blast-legacy' did not include + the command-line help for 'formatdb'. Parameters are inferred based + on common legacy BLAST usage. The 'remaining_args' parameter allows + passing any additional, undocumented options directly to formatdb. + + Args: + input_file: Path to the input FASTA file to be formatted. + database_type: Type of database ('F' for nucleotide, 'T' for protein). + output_dir: Optional directory where database files will be created. If not specified, + files are created in the same directory as input_file. + title: Optional title for the database. + parse_seqids: If True, parse sequence IDs from the FASTA headers. + remaining_args: A list of additional command-line arguments to pass directly to formatdb. + """ + # Input validation + if not input_file.exists(): + raise FileNotFoundError(f"Input FASTA file not found: {input_file}") + if database_type not in ["F", "T"]: + raise ValueError(f"Invalid database_type '{database_type}'. Must be 'F' (nucleotide) or 'T' (protein).") + if output_dir and not output_dir.is_dir(): + raise NotADirectoryError(f"Output directory not found or is not a directory: {output_dir}") + + cmd = ["formatdb", "-i", str(input_file), "-p", database_type] + + if title: + cmd.extend(["-t", title]) + if parse_seqids: + cmd.append("-L") # -L is a flag, no value + + if remaining_args: + cmd.extend(remaining_args) + + # Determine the directory where formatdb will create files + db_output_location = output_dir if output_dir else input_file.parent + + # Handle changing CWD if output_dir is specified + original_cwd = Path.cwd() + if output_dir: + # Ensure output_dir exists + output_dir.mkdir(parents=True, exist_ok=True) + # Change directory to output_dir to ensure database files are created there + os.chdir(output_dir) + # Adjust input_file path if it was relative to original_cwd + if not input_file.is_absolute(): + input_file = original_cwd / input_file + cmd[2] = str(input_file) # Update input file path in command if CWD changed + + result = _run_command(cmd, cwd=output_dir if output_dir else None) + + if output_dir: + # Change back to original CWD + os.chdir(original_cwd) + + # Identify output files. Legacy formatdb creates multiple files with extensions + # like .nhr, .nin, .nsq for nucleotide, or .phr, .pin, .psq for protein. + # The base name is derived from the input_file. + base_name = input_file.stem + db_files = [] + if database_type == "F": # Nucleotide + db_files.extend([ + db_output_location / f"{base_name}.nhr", + db_output_location / f"{base_name}.nin", + db_output_location / f"{base_name}.nsq", + ]) + elif database_type == "T": # Protein + db_files.extend([ + db_output_location / f"{base_name}.phr", + db_output_location / f"{base_name}.pin", + db_output_location / f"{base_name}.psq", + ]) + + # Filter for files that actually exist + result["output_files"] = [str(f) for f in db_files if f.exists()] + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_shim_server.py b/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f9970919e007820a928c36947bedc4182a5c6826 --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/app/blast-legacy_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_blast-legacy/app/blast-legacy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_blast_legacy' + + +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_blast-legacy/app/requirements.txt b/Biomni/mcp_generated/mcp_blast-legacy/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_blast-legacy/docker-compose.yml b/Biomni/mcp_generated/mcp_blast-legacy/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9863ccd7ac1b387f39e3a3a30ea5bff5fc3f26ca --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-blast-legacy: + build: . + image: mcp-blast-legacy:latest + container_name: mcp-blast-legacy + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=blast-legacy + 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_blast-legacy/environment.yaml b/Biomni/mcp_generated/mcp_blast-legacy/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a7fdf7a9e35edad8947dd27380149196b7b3b009 --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - blast-legacy + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_blast-legacy/requirements.txt b/Biomni/mcp_generated/mcp_blast-legacy/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_blast-legacy/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_crispritz/Dockerfile b/Biomni/mcp_generated/mcp_crispritz/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..385c856bdebe4a2aa208148161a85dc701075923 --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/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 crispritz via conda (e.g., from bioconda) +RUN conda install -c bioconda crispritz -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/crispritz_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/crispritz_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/crispritz_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_crispritz/app/crispritz_shim_server.py b/Biomni/mcp_generated/mcp_crispritz/app/crispritz_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..abc93f8954eda60ad1bb1fc8388e0055a240b1a2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/app/crispritz_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_crispritz/app/crispritz_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_crispritz' + + +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_crispritz/app/requirements.txt b/Biomni/mcp_generated/mcp_crispritz/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_crispritz/docker-compose.yml b/Biomni/mcp_generated/mcp_crispritz/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..44ea62305fa9e61f2e0e177e293af272cd7d6bdd --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-crispritz: + build: . + image: mcp-crispritz:latest + container_name: mcp-crispritz + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=crispritz + 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_crispritz/environment.yaml b/Biomni/mcp_generated/mcp_crispritz/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4e15b5739ae253bdba41c6c24139e2d57399f5e8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - crispritz + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_crispritz/requirements.txt b/Biomni/mcp_generated/mcp_crispritz/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_crispritz/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_decoupler/Dockerfile b/Biomni/mcp_generated/mcp_decoupler/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9377ee41676018cd45120e518daf4324c4695657 --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/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 decoupler via conda (e.g., from bioconda) +RUN conda install -c bioconda decoupler -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 decoupler_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/decoupler_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/decoupler_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_decoupler/app/decoupler_server.py b/Biomni/mcp_generated/mcp_decoupler/app/decoupler_server.py new file mode 100644 index 0000000000000000000000000000000000000000..73d38c3641d110b4dee7f96316fb2c0e49a3f683 --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/app/decoupler_server.py @@ -0,0 +1,692 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Literal, Optional + +# Mock mcp decorator for standalone execution +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def decoupler_mlm( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + verbose: bool = False, +): + """ + Runs transcription factor/pathway activities using the Multivariate Linear Model (mlm) method from decoupler. + + This method is equivalent to the `run_mlm` function in the decoupler library. + It models the relationship between a set of regulators and their target features using a linear model. + + Args: + mat: Path to the input matrix file (e.g., gene expression). Should be a long-format CSV/TSV. + net: Path to the input network file (e.g., TF-target interactions). Should be a long-format CSV/TSV. + output: Path to the output file where the inferred activities will be saved. + mat_source_col: Column name in the matrix file for the features (e.g., 'genes'). + mat_target_col: Column name in the matrix file for the samples (e.g., 'cells'). + mat_value_col: Column name in the matrix file for the values (e.g., 'expression'). + net_source_col: Column name in the network file for the regulators (e.g., 'tf'). + net_target_col: Column name in the network file for the targets (e.g., 'target'). + net_weight_col: Column name in the network file for the interaction weights (e.g., 'weight'). + min_n: Minimum number of targets per regulator in the network to be considered. + batch_size: Number of samples to process in each batch to manage memory usage. + verbose: If True, increases verbosity of the tool's output. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "mlm", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + ] + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_ulm( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + verbose: bool = False, +): + """ + Runs transcription factor/pathway activities using the Univariate Linear Model (ulm) method from decoupler. + + This method is equivalent to the `run_ulm` function in the decoupler library. + It models the relationship between each regulator and its target features independently. + + Args: + mat: Path to the input matrix file (e.g., gene expression). Should be a long-format CSV/TSV. + net: Path to the input network file (e.g., TF-target interactions). Should be a long-format CSV/TSV. + output: Path to the output file where the inferred activities will be saved. + mat_source_col: Column name in the matrix file for the features (e.g., 'genes'). + mat_target_col: Column name in the matrix file for the samples (e.g., 'cells'). + mat_value_col: Column name in the matrix file for the values (e.g., 'expression'). + net_source_col: Column name in the network file for the regulators (e.g., 'tf'). + net_target_col: Column name in the network file for the targets (e.g., 'target'). + net_weight_col: Column name in the network file for the interaction weights (e.g., 'weight'). + min_n: Minimum number of targets per regulator in the network to be considered. + batch_size: Number of samples to process in each batch to manage memory usage. + verbose: If True, increases verbosity of the tool's output. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "ulm", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + ] + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_wsum( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + nes: bool = False, + times: int = 100, + seed: int = 42, + verbose: bool = False, +): + """ + Runs transcription factor/pathway activities using the Weighted Sum (wsum) method from decoupler. + + This method calculates scores by multiplying the feature matrix by the network matrix. + It can optionally compute a normalized enrichment score (NES) through permutations. + + Args: + mat: Path to the input matrix file. + net: Path to the input network file. + output: Path to the output file. + mat_source_col: Column name for features in the matrix file. + mat_target_col: Column name for samples in the matrix file. + mat_value_col: Column name for values in the matrix file. + net_source_col: Column name for regulators in the network file. + net_target_col: Column name for targets in the network file. + net_weight_col: Column name for interaction weights in the network file. + min_n: Minimum number of targets per regulator. + batch_size: Number of samples to process per batch. + nes: If True, compute Normalized Enrichment Scores (NES) by permutation. + times: Number of permutations to perform if `nes` is True. + seed: Random seed for permutations. + verbose: If True, increases verbosity. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + if times < 1: + raise ValueError("times must be a positive integer.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "wsum", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + "--times", str(times), + "--seed", str(seed), + ] + if nes: + cmd.append("--nes") + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_ora( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + top: int = 100, + verbose: bool = False, +): + """ + Runs Over-Representation Analysis (ORA) using decoupler. + + This method performs a statistical test (Fisher's exact test) to determine if a set of features + (e.g., differentially expressed genes) is enriched in a given pathway/regulon. + + Args: + mat: Path to the input matrix file. + net: Path to the input network file. + output: Path to the output file. + mat_source_col: Column name for features in the matrix file. + mat_target_col: Column name for samples in the matrix file. + mat_value_col: Column name for values in the matrix file. + net_source_col: Column name for regulators in the network file. + net_target_col: Column name for targets in the network file. + net_weight_col: Column name for interaction weights in the network file. + min_n: Minimum number of targets per regulator. + batch_size: Number of samples to process per batch. + top: Number of top features from the matrix to use for the enrichment analysis. + verbose: If True, increases verbosity. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + if top < 1: + raise ValueError("top must be a positive integer.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "ora", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + "--top", str(top), + ] + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_gsva( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + mx_diff: bool = True, + abs_ranking: bool = False, + min_sz: int = 5, + max_sz: int = 500, + kcdf: Literal['Gaussian', 'Poisson'] = 'Gaussian', + seed: int = 42, + verbose: bool = False, +): + """ + Runs Gene Set Variation Analysis (GSVA) using decoupler. + + GSVA estimates variation of pathway activity over a sample population in an unsupervised manner. + + Args: + mat: Path to the input matrix file. + net: Path to the input network file. + output: Path to the output file. + mat_source_col: Column name for features in the matrix file. + mat_target_col: Column name for samples in the matrix file. + mat_value_col: Column name for values in the matrix file. + net_source_col: Column name for regulators in the network file. + net_target_col: Column name for targets in the network file. + net_weight_col: Column name for interaction weights in the network file. + min_n: Minimum number of targets per regulator. + batch_size: Number of samples to process per batch. + mx_diff: The maximum absolute difference between the minimum and maximum. + abs_ranking: If True, gene expression values are ranked by their absolute values. + min_sz: Minimum size of a gene set to be considered. + max_sz: Maximum size of a gene set to be considered. + kcdf: Kernel cumulative distribution function to use. + seed: Random seed. + verbose: If True, increases verbosity. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + if min_sz < 1: + raise ValueError("min_sz must be a positive integer.") + if max_sz < min_sz: + raise ValueError("max_sz must be greater than or equal to min_sz.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "gsva", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + "--min-sz", str(min_sz), + "--max-sz", str(max_sz), + "--kcdf", kcdf, + "--seed", str(seed), + ] + if mx_diff: + cmd.append("--mx-diff") + if abs_ranking: + cmd.append("--abs-ranking") + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_viper( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + nes: bool = True, + pleiotropy: bool = True, + eset_filter: bool = False, + seed: int = 42, + verbose: bool = False, +): + """ + Runs Virtual Inference of Protein-activity by Enriched Regulon analysis (VIPER). + + VIPER is a method to infer protein activity from gene expression data based on the enrichment + of its transcriptional targets (regulon). + + Args: + mat: Path to the input matrix file. + net: Path to the input network file. + output: Path to the output file. + mat_source_col: Column name for features in the matrix file. + mat_target_col: Column name for samples in the matrix file. + mat_value_col: Column name for values in the matrix file. + net_source_col: Column name for regulators in the network file. + net_target_col: Column name for targets in the network file. + net_weight_col: Column name for interaction weights in the network file. + min_n: Minimum number of targets per regulator. + batch_size: Number of samples to process per batch. + nes: If True, compute Normalized Enrichment Scores (NES). + pleiotropy: If True, correct for pleiotropy (regulators sharing targets). + eset_filter: If True, filter by eset size. + seed: Random seed. + verbose: If True, increases verbosity. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "viper", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + "--seed", str(seed), + ] + if nes: + cmd.append("--nes") + if pleiotropy: + cmd.append("--pleiotropy") + if eset_filter: + cmd.append("--eset-filter") + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } + +@mcp.tool +def decoupler_gsea( + mat: Path, + net: Path, + output: Path, + mat_source_col: str = 'genes', + mat_target_col: str = 'cells', + mat_value_col: str = 'expression', + net_source_col: str = 'tf', + net_target_col: str = 'target', + net_weight_col: str = 'weight', + min_n: int = 5, + batch_size: int = 10000, + times: int = 100, + min_sz: int = 5, + max_sz: int = 500, + seed: int = 42, + verbose: bool = False, +): + """ + Runs Gene Set Enrichment Analysis (GSEA) using decoupler. + + GSEA determines whether a defined set of genes shows statistically significant, concordant + differences between two biological states. + + Args: + mat: Path to the input matrix file. + net: Path to the input network file. + output: Path to the output file. + mat_source_col: Column name for features in the matrix file. + mat_target_col: Column name for samples in the matrix file. + mat_value_col: Column name for values in the matrix file. + net_source_col: Column name for regulators in the network file. + net_target_col: Column name for targets in the network file. + net_weight_col: Column name for interaction weights in the network file. + min_n: Minimum number of targets per regulator. + batch_size: Number of samples to process per batch. + times: Number of permutations to perform. + min_sz: Minimum size of a gene set to be considered. + max_sz: Maximum size of a gene set to be considered. + seed: Random seed for permutations. + verbose: If True, increases verbosity. + """ + # Input validation + if not mat.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {mat}") + if not net.is_file(): + raise FileNotFoundError(f"Input network file not found: {net}") + if min_n < 1: + raise ValueError("min_n must be a positive integer.") + if batch_size < 1: + raise ValueError("batch_size must be a positive integer.") + if times < 1: + raise ValueError("times must be a positive integer.") + if min_sz < 1: + raise ValueError("min_sz must be a positive integer.") + if max_sz < min_sz: + raise ValueError("max_sz must be greater than or equal to min_sz.") + + output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "python", "-m", "decoupler", "gsea", + "--mat", str(mat), + "--net", str(net), + "--output", str(output), + "--mat-source-col", mat_source_col, + "--mat-target-col", mat_target_col, + "--mat-value-col", mat_value_col, + "--net-source-col", net_source_col, + "--net-target-col", net_target_col, + "--net-weight-col", net_weight_col, + "--min-n", str(min_n), + "--batch-size", str(batch_size), + "--times", str(times), + "--min-sz", str(min_sz), + "--max-sz", str(max_sz), + "--seed", str(seed), + ] + if verbose: + cmd.append("--verbose") + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output)] + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_decoupler/app/decoupler_shim_server.py b/Biomni/mcp_generated/mcp_decoupler/app/decoupler_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ecc6ce334b83d30c57c7b6fa5608248991ca5786 --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/app/decoupler_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_decoupler/app/decoupler_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_decoupler' + + +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_decoupler/docker-compose.yml b/Biomni/mcp_generated/mcp_decoupler/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..22f3efd81dab73f998d976fd326fd2e380848eeb --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-decoupler: + build: . + image: mcp-decoupler:latest + container_name: mcp-decoupler + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=decoupler + 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_decoupler/environment.yaml b/Biomni/mcp_generated/mcp_decoupler/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6a5b7a5c89f50dc67d9a15ca2e32f2b1b7d5d2dc --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - decoupler + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_decoupler/requirements.txt b/Biomni/mcp_generated/mcp_decoupler/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_decoupler/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gdk-pixbuf/app/gdk-pixbuf_shim_server.py b/Biomni/mcp_generated/mcp_gdk-pixbuf/app/gdk-pixbuf_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ead5ad67399f5ed96adc9a41a8159dcb01a66ccc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gdk-pixbuf/app/gdk-pixbuf_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_gdk-pixbuf/app/gdk-pixbuf_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_gdk_pixbuf' + + +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_gdk-pixbuf/app/requirements.txt b/Biomni/mcp_generated/mcp_gdk-pixbuf/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gdk-pixbuf/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_gdk-pixbuf/environment.yaml b/Biomni/mcp_generated/mcp_gdk-pixbuf/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1e46bc91521db8ab30f0be25483ca64a1664bc79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gdk-pixbuf/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - gdk-pixbuf + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gdk-pixbuf/requirements.txt b/Biomni/mcp_generated/mcp_gdk-pixbuf/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gdk-pixbuf/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_mothur/Dockerfile b/Biomni/mcp_generated/mcp_mothur/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3082427cecb636384e6543c027a906cdf5f06ea9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_mothur/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 mothur via conda (e.g., from bioconda) +RUN conda install -c bioconda mothur -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/mothur_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/mothur_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/mothur_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ncls/Dockerfile b/Biomni/mcp_generated/mcp_ncls/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e2eae66a33fc40da22e1f8cd676fef3e436453f3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/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 ncls via conda (e.g., from bioconda) +RUN conda install -c bioconda ncls -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/ncls_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/ncls_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/ncls_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ncls/app/requirements.txt b/Biomni/mcp_generated/mcp_ncls/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_ncls/docker-compose.yml b/Biomni/mcp_generated/mcp_ncls/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5a93f5d18499eb489870cd636932eb94e5137a39 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-ncls: + build: . + image: mcp-ncls:latest + container_name: mcp-ncls + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=ncls + 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_ncls/environment.yaml b/Biomni/mcp_generated/mcp_ncls/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f61ae7257f086d3956af7d0a02a5dd12d9630b07 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - ncls + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ncls/requirements.txt b/Biomni/mcp_generated/mcp_ncls/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_openms-thirdparty/Dockerfile b/Biomni/mcp_generated/mcp_openms-thirdparty/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..582be6add6000183ecdc69784355fd44780eca07 --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/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 openms-thirdparty via conda (e.g., from bioconda) +RUN conda install -c bioconda openms-thirdparty -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/openms-thirdparty_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/openms-thirdparty_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/openms-thirdparty_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_server.py b/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4e2f66cbe25281bae6158cd6c3a54edc95272d6b --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_server.py @@ -0,0 +1,70 @@ +import subprocess +import json +from typing import Dict, Any, List + +# +# Analysis of openms-thirdparty +# +# The user has requested an MCP tool for 'openms-thirdparty'. +# Based on the provided documentation, 'openms-thirdparty' is not a command-line +# executable tool. Instead, it is a conda meta-package. Its sole purpose is to +# manage and install a specific, compatible set of dependencies (like Comet, +# Percolator, MSGF+, etc.) required by the OpenMS suite. +# +# Since it is not an executable, it has no subcommands, parameters, or a --help flag. +# Therefore, a traditional MCP wrapper that executes a command is not possible. +# +# The following function serves as an informational tool. It does not execute +# any subprocess but returns structured information about the purpose and +# dependencies of the 'openms-thirdparty' package, fulfilling the user's +# request in the most accurate way possible given the nature of the package. +# + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_openms_thirdparty' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def openms_thirdparty_info() -> Dict[str, Any]: + """ + Provides information about the openms-thirdparty conda meta-package. + + This package is not an executable tool but a helper to install OpenMS with a + consistent set of compatible third-party tools. This function returns metadata + about the package, including its purpose and list of dependencies. + """ + # No command to execute, as this is a meta-package. + # The information is derived from the provided documentation. + + info = { + "package_name": "openms-thirdparty", + "summary": "A helper package to install OpenMS TOPP tools with all their compatible and conda-available versions of adapted thirdparty tools.", + "description": "openms-thirdparty is a conda meta-package. Its purpose is to ensure that when you install OpenMS via conda, you also get a specific, compatible set of versions for other essential proteomics tools. It does not provide an executable command itself.", + "dependencies": [ + "comet-ms 2024011", + "gnuplot", + "luciphor2 2020_04_03", + "msgf_plus 2024.03.26", + "openms 3.5.0 h78fb946_0", + "percolator 3.7.1", + "r-gplots", + "sage-proteomics 0.14.7", + "sirius-ms >=6.1.0", + "thermorawfileparser 1.4.3" + ], + "home_url": "https://github.com/OpenMS/OpenMS", + "doc_url": "https://openms.readthedocs.io/en/latest/index.html" + } + + # Since no command is executed, we return the informational dictionary + # formatted into the standard MCP output structure. + return { + "command_executed": "N/A (informational tool for a meta-package)", + "stdout": json.dumps(info, indent=2), + "stderr": "", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_shim_server.py b/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9188bf52c54e577feac49a677dfa2e0c382f6c41 --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/app/openms-thirdparty_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_openms-thirdparty/app/openms-thirdparty_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_openms_thirdparty' + + +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_openms-thirdparty/app/requirements.txt b/Biomni/mcp_generated/mcp_openms-thirdparty/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_openms-thirdparty/docker-compose.yml b/Biomni/mcp_generated/mcp_openms-thirdparty/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..74f99beedbd51a21158519cc07c6ed892c7e13ab --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-openms-thirdparty: + build: . + image: mcp-openms-thirdparty:latest + container_name: mcp-openms-thirdparty + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=openms-thirdparty + 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_openms-thirdparty/environment.yaml b/Biomni/mcp_generated/mcp_openms-thirdparty/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0cbf1bbcf6692e3086558749d7dfa5987d6acde5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - openms-thirdparty + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_openms-thirdparty/requirements.txt b/Biomni/mcp_generated/mcp_openms-thirdparty/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_openms-thirdparty/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-algorithm-munkres/requirements.txt b/Biomni/mcp_generated/mcp_perl-algorithm-munkres/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-algorithm-munkres/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-app-cpanminus/Dockerfile b/Biomni/mcp_generated/mcp_perl-app-cpanminus/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..cca04a44580505150edf34829f2bd552b1738d13 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/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-app-cpanminus via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-app-cpanminus -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-app-cpanminus_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-app-cpanminus_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-app-cpanminus_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_server.py b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_server.py new file mode 100644 index 0000000000000000000000000000000000000000..34d161e48f4466d43c5d410271d20023d366cf50 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_server.py @@ -0,0 +1,377 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union +import os +import tempfile # Included as per general MCP tool requirements, even if not explicitly used here. + +# The tool name is 'perl-app-cpanminus'. The primary executable for this tool is 'cpanm'. +# The provided 'CLI Help Source' in the prompt was for 'perl -h', which describes +# the general Perl interpreter options. This implementation is based on the actual +# 'cpanm --help' output, which is relevant to the 'perl-app-cpanminus' tool. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_app_cpanminus' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def install_cpan_modules( + modules: Optional[List[str]] = None, + installdeps: bool = False, + installdeps_target: Optional[Path] = None, + local_lib: Optional[Path] = None, + save_config: bool = False, + show_config: bool = False, + self_upgrade: bool = False, + info: bool = False, + mirror: Optional[str] = None, + mirror_only: bool = False, + # Deprecated flags, included for completeness as per instructions + from_cpanfile: bool = False, # Deprecated, use --installdeps + from_yaml: bool = False, # Deprecated, use --installdeps + from_json: bool = False, # Deprecated, use --installdeps + skip_installed: bool = False, + skip_satisfied: bool = False, + force: bool = False, + test_only: bool = False, + notest: bool = False, + no_build_deps: bool = False, + no_develop_deps: bool = False, + no_recommends: bool = False, + no_suggests: bool = False, + no_configure_deps: bool = False, + configure_args: Optional[str] = None, + build_args: Optional[str] = None, + install_args: Optional[str] = None, + sudo: bool = False, + sudo_args: Optional[str] = None, + prompt: bool = False, + interactive: bool = False, + reinstall: bool = False, + cascade_to_build_tree: bool = False, + verbose: bool = False, + quiet: bool = False, + debug: bool = False, + trace: bool = False, + color: bool = False, + no_color: bool = False, + wait: Optional[int] = None, + retries: Optional[int] = None, + timeout: Optional[int] = None, + jobs: int = 1, # Default is 1 + max_trap_alloc: Optional[str] = None, + uninst_shadows: bool = False, + uninst_shadows_all: bool = False, + # Explicit 'with' options (defaults are usually 'with', but can be specified to override config) + with_recommends: bool = False, + with_suggests: bool = False, + with_develop_deps: bool = False, + with_configure_deps: bool = False, + with_build_deps: bool = False, + # Skip phases - these are numerous and some are mutually exclusive or redundant. + # All are included as individual booleans as per "cover EVERY internal functions and EVERY decorators". + skip_cpanfile: bool = False, + skip_makefile: bool = False, + skip_buildpl: bool = False, + skip_test_phase: bool = False, # Renamed from skip_test to avoid conflict with test_only/notest + skip_configure: bool = False, + skip_build: bool = False, + skip_install: bool = False, + skip_clean: bool = False, + skip_all: bool = False, + skip_all_but_test: bool = False, + skip_all_but_install: bool = False, + skip_all_but_build: bool = False, + skip_all_but_configure: bool = False, + skip_all_but_clean: bool = False, + skip_all_but_fetch: bool = False, + skip_all_but_unpack: bool = False, + skip_all_but_patch: bool = False, + skip_all_but_make: bool = False, + skip_all_but_test_and_install: bool = False, + skip_all_but_build_and_install: bool = False, + skip_all_but_configure_and_build: bool = False, + skip_all_but_configure_and_install: bool = False, + skip_all_but_configure_and_test: bool = False, + skip_all_but_build_and_test: bool = False, + skip_all_but_fetch_and_unpack: bool = False, + skip_all_but_fetch_and_patch: bool = False, + skip_all_but_unpack_and_patch: bool = False, + skip_all_but_fetch_and_make: bool = False, + skip_all_but_unpack_and_make: bool = False, + skip_all_but_patch_and_make: bool = False, + skip_all_but_fetch_and_test: bool = False, + skip_all_but_unpack_and_test: bool = False, + skip_all_but_patch_and_test: bool = False, + skip_all_but_make_and_test: bool = False, + skip_all_but_fetch_and_install: bool = False, + skip_all_but_unpack_and_install: bool = False, + skip_all_but_patch_and_install: bool = False, + skip_all_but_make_and_install: bool = False, + skip_all_but_fetch_and_clean: bool = False, + skip_all_but_unpack_and_clean: bool = False, + skip_all_but_patch_and_clean: bool = False, + skip_all_but_make_and_clean: bool = False, + skip_all_but_test_and_clean: bool = False, + skip_all_but_install_and_clean: bool = False, + skip_all_but_build_and_clean: bool = False, + skip_all_but_configure_and_clean: bool = False, +) -> dict: + """ + Installs Perl modules from CPAN using cpanm. + + This tool wraps the 'cpanm' command, which is part of the 'perl-app-cpanminus' distribution. + It allows fetching, unpacking, building, and installing modules from CPAN or local sources. + """ + command = ["cpanm"] + output_files: List[Path] = [] + + # Input validation and command construction + # Determine the primary action + action_flags = [installdeps, save_config, show_config, self_upgrade, info] + num_actions = sum(1 for flag in action_flags if flag) + + if num_actions > 1: + raise ValueError( + "Only one of --installdeps, --save-config, --show-config, --self-upgrade, --info can be used at a time." + ) + + if num_actions == 0 and not modules: + raise ValueError( + "No action specified. Please provide modules to install, or use --installdeps, --save-config, --show-config, --self-upgrade, or --info." + ) + + if installdeps: + command.append("--installdeps") + if installdeps_target: + if not installdeps_target.exists(): + raise FileNotFoundError(f"installdeps_target '{installdeps_target}' does not exist.") + command.append(str(installdeps_target)) + elif modules: + raise ValueError("Cannot specify explicit modules when --installdeps is used without a target.") + else: # If installdeps is true, and no target or modules, it implies '.' + command.append(".") + elif info: + command.append("--info") + if not modules or len(modules) != 1: + raise ValueError("When --info is used, exactly one module name must be provided.") + elif save_config: + command.append("--save-config") + elif show_config: + command.append("--show-config") + elif self_upgrade: + command.append("--self-upgrade") + + if local_lib: + command.append(f"--local-lib={local_lib}") + if mirror: + command.append(f"--mirror={mirror}") + if mirror_only: + command.append("--mirror-only") + + # Deprecated flags + if from_cpanfile: + command.append("--from-cpanfile") + if from_yaml: + command.append("--from-yaml") + if from_json: + command.append("--from-json") + + if skip_installed: + command.append("--skip-installed") + if skip_satisfied: + command.append("--skip-satisfied") + if force: + command.append("--force") + if test_only: + command.append("--test-only") + if notest: + command.append("--notest") + if no_build_deps: + command.append("--no-build-deps") + if no_develop_deps: + command.append("--no-develop-deps") + if no_recommends: + command.append("--no-recommends") + if no_suggests: + command.append("--no-suggests") + if no_configure_deps: + command.append("--no-configure-deps") + + if configure_args: + command.append(f"--configure-args={configure_args}") + if build_args: + command.append(f"--build-args={build_args}") + if install_args: + command.append(f"--install-args={install_args}") + + if sudo: + command.append("--sudo") + if sudo_args: + command.append(f"--sudo-args={sudo_args}") + + if prompt: + command.append("--prompt") + if interactive: + command.append("--interactive") + if reinstall: + command.append("--reinstall") + if cascade_to_build_tree: + command.append("--cascade-to-build-tree") + + # Verbosity + if verbose: + command.append("--verbose") + if quiet: + command.append("--quiet") + if debug: + command.append("--debug") + if trace: + command.append("--trace") + + # Color + if color: + command.append("--color") + if no_color: + command.append("--no-color") + + # Network/Concurrency + if wait is not None: + if wait < 0: + raise ValueError("Wait time must be non-negative.") + command.append(f"--wait={wait}") + if retries is not None: + if retries < 0: + raise ValueError("Number of retries must be non-negative.") + command.append(f"--retries={retries}") + if timeout is not None: + if timeout < 0: + raise ValueError("Timeout must be non-negative.") + command.append(f"--timeout={timeout}") + if jobs < 1: + raise ValueError("Number of jobs must be at least 1.") + command.append(f"--jobs={jobs}") # Always append, as it has a default + + if max_trap_alloc: + command.append(f"--max-trap-alloc={max_trap_alloc}") + + if uninst_shadows: + command.append("--uninst-shadows") + if uninst_shadows_all: + command.append("--uninst-shadows-all") + + # Explicit 'with' options (usually defaults, but can be specified) + if with_recommends: + command.append("--with-recommends") + if with_suggests: + command.append("--with-suggests") + if with_develop_deps: + command.append("--with-develop-deps") + if with_configure_deps: + command.append("--with-configure-deps") + if with_build_deps: + command.append("--with-build-deps") + + # Skip phases - these are numerous and some are mutually exclusive or redundant + skip_phase_flags = { + "skip_cpanfile": "--skip-cpanfile", + "skip_makefile": "--skip-makefile", + "skip_buildpl": "--skip-buildpl", + "skip_test_phase": "--skip-test", + "skip_configure": "--skip-configure", + "skip_build": "--skip-build", + "skip_install": "--skip-install", + "skip_clean": "--skip-clean", + "skip_all": "--skip-all", + "skip_all_but_test": "--skip-all-but-test", + "skip_all_but_install": "--skip-all-but-install", + "skip_all_but_build": "--skip-all-but-build", + "skip_all_but_configure": "--skip-all-but-configure", + "skip_all_but_clean": "--skip-all-but-clean", + "skip_all_but_fetch": "--skip-all-but-fetch", + "skip_all_but_unpack": "--skip-all-but-unpack", + "skip_all_but_patch": "--skip-all-but-patch", + "skip_all_but_make": "--skip-all-but-make", + "skip_all_but_test_and_install": "--skip-all-but-test-and-install", + "skip_all_but_build_and_install": "--skip-all-but-build-and-install", + "skip_all_but_configure_and_build": "--skip-all-but-configure-and-build", + "skip_all_but_configure_and_install": "--skip-all-but-configure-and-install", + "skip_all_but_configure_and_test": "--skip-all-but-configure-and-test", + "skip_all_but_build_and_test": "--skip-all-but-build-and-test", + "skip_all_but_fetch_and_unpack": "--skip-all-but-fetch-and-unpack", + "skip_all_but_fetch_and_patch": "--skip-all-but-fetch-and-patch", + "skip_all_but_unpack_and_patch": "--skip-all-but-unpack-and-patch", + "skip_all_but_fetch_and_make": "--skip-all-but-fetch-and-make", + "skip_all_but_unpack_and_make": "--skip-all-but-unpack-and-make", + "skip_all_but_patch_and_make": "--skip-all-but-patch-and-make", + "skip_all_but_fetch_and_test": "--skip-all-but-fetch-and-test", + "skip_all_but_unpack_and_test": "--skip-all-but-unpack-and-test", + "skip_all_but_patch_and_test": "--skip-all-but-patch-and-test", + "skip_all_but_make_and_test": "--skip-all-but-make-and-test", + "skip_all_but_fetch_and_install": "--skip-all-but-fetch-and-install", + "skip_all_but_unpack_and_install": "--skip-all-but-unpack-and-install", + "skip_all_but_patch_and_install": "--skip-all-but-patch-and-install", + "skip_all_but_make_and_install": "--skip-all-but-make-and-install", + "skip_all_but_fetch_and_clean": "--skip-all-but-fetch-and-clean", + "skip_all_but_unpack_and_clean": "--skip-all-but-unpack-and-clean", + "skip_all_but_patch_and_clean": "--skip-all-but-patch-and-clean", + "skip_all_but_make_and_clean": "--skip-all-but-make-and-clean", + "skip_all_but_test_and_clean": "--skip-all-but-test-and-clean", + "skip_all_but_install_and_clean": "--skip-all-but-install-and-clean", + "skip_all_but_build_and_clean": "--skip-all-but-build-and-clean", + "skip_all_but_configure_and_clean": "--skip-all-but-configure-and-clean", + } + + for flag_name, cli_opt in skip_phase_flags.items(): + if locals()[flag_name]: + command.append(cli_opt) + + # Positional arguments (modules to install) + # Only append modules if no other primary action (like installdeps, info, etc.) was taken + # or if info was taken and modules are the target for info. + if modules and not installdeps: # If installdeps is true, modules are handled above or disallowed. + command.extend(modules) + + # Execute the command + command_str = " ".join(str(arg) for arg in command) + try: + # PERL_MM_USE_DEFAULT=1 is often useful for non-interactive installs + # to prevent prompts for configuration questions. + env = os.environ.copy() + env["PERL_MM_USE_DEFAULT"] = "1" + + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + env=env + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": f"cpanm command failed with exit code {e.returncode}", + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: cpanm executable not found. Please ensure perl-app-cpanminus is installed and in your PATH.", + "output_files": [], + "error": "cpanm executable not found", + } + + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, # cpanm installs modules into the Perl library path, not typically to specific output files in CWD. + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_shim_server.py b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..73ce4217fbe6ac5cb21bb7b23f0c4a66a816b68b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/perl-app-cpanminus_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-app-cpanminus/app/perl-app-cpanminus_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_app_cpanminus' + + +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-app-cpanminus/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-app-cpanminus/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-app-cpanminus/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..45d8a829fe47e1c7fa8fda22fb1ad88ff2b4ec80 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-app-cpanminus: + build: . + image: mcp-perl-app-cpanminus:latest + container_name: mcp-perl-app-cpanminus + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-app-cpanminus + 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-app-cpanminus/environment.yaml b/Biomni/mcp_generated/mcp_perl-app-cpanminus/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f9ced4fa9bfe9c1c0d4dc7b643b2f15f341d3819 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-app-cpanminus + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-app-cpanminus/requirements.txt b/Biomni/mcp_generated/mcp_perl-app-cpanminus/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-app-cpanminus/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-class-inspector/Dockerfile b/Biomni/mcp_generated/mcp_perl-class-inspector/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..731adda7c78769ef471d0507666960154c1cfca5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/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-class-inspector via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-class-inspector -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-class-inspector_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-class-inspector_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-class-inspector_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_server.py b/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f4b2ffa4e42f5ac308afe1982747c6d6f08460c1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_server.py @@ -0,0 +1,126 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, Optional, List +import shlex + +# Note: The 'mcp' import is not needed as per the instructions. +# This is a placeholder for the actual decorator. +class Mcp: + def tool(self, *args, **kwargs): + def decorator(f): + return f + return decorator + +mcp = Mcp() + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_class_inspector' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def inspect_class( + class_name: str +) -> Dict[str, any]: + """ + Uses the Perl Class::Inspector module to get information about a Perl class. + + This tool dynamically creates and executes a Perl script to inspect the specified class, + reporting its file path and public methods. It requires that Perl and the + Class::Inspector module are available in the execution environment. + + Args: + class_name: The name of the Perl class to inspect (e.g., 'Data::Dumper', 'Bio::SeqIO'). + + Returns: + A dictionary containing the command executed, stdout with the inspection results, + stderr, and a list of output files (which is always empty). + """ + if not class_name or not isinstance(class_name, str): + raise ValueError("class_name must be a non-empty string.") + + # A robust Perl script that uses Class::Inspector to get class details. + # It includes error handling for missing modules or classes. + perl_script_content = f""" + use strict; + use warnings; + + # Try to load the module, exit gracefully if not found. + eval "use Class::Inspector;"; + if ($@) {{ + print STDERR "Error: The 'Class::Inspector' Perl module is not installed in the environment.\\n"; + exit 1; + }} + + my $class_to_inspect = '{class_name}'; + + # Check if the class is installed/loadable + unless (Class::Inspector->installed($class_to_inspect)) {{ + print STDERR "Error: Class '$class_to_inspect' is not installed or could not be loaded.\\n"; + exit 1; + }} + + # Get information + my $filename = Class::Inspector->filename($class_to_inspect) || 'N/A'; + my @methods = Class::Inspector->methods($class_to_inspect, 'public'); + + # Print information in a structured, human-readable format + print "Class: $class_to_inspect\\n"; + print "File: $filename\\n"; + print "Public Methods:\\n"; + if (@methods) {{ + foreach my $method (sort @methods) {{ + print " - $method\\n"; + }} + }} else {{ + print " (No public methods found)\\n"; + }} + """ + + script_path = None + try: + # Use a temporary file for the Perl script to avoid command line length issues + # and to handle complex scripts cleanly. + with tempfile.NamedTemporaryFile(mode='w', suffix='.pl', delete=False) as tmp_script: + script_path = Path(tmp_script.name) + tmp_script.write(perl_script_content) + + command = ['perl', str(script_path)] + command_str = shlex.join(command) + + result = subprocess.run( + command, + capture_output=True, + text=True, + check=False # The script has internal error handling, so we check returncode manually. + ) + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + # This error occurs if 'perl' is not in the system's PATH. + return { + "command_executed": f"perl {class_name}", + "stdout": "", + "stderr": "Error: 'perl' command not found. Is Perl installed and in the system's PATH?", + "output_files": [] + } + except Exception as e: + return { + "command_executed": f"perl {class_name}", + "stdout": "", + "stderr": f"An unexpected error occurred: {str(e)}", + "output_files": [] + } + finally: + # Ensure the temporary script file is always cleaned up. + if script_path and script_path.exists(): + script_path.unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_shim_server.py b/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ecce7e9f451609f27418a0e9a73085d9019aadff --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/app/perl-class-inspector_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-class-inspector/app/perl-class-inspector_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_class_inspector' + + +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-class-inspector/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-class-inspector/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-class-inspector/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-class-inspector/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c6cc102c27062cc1dc8058b812a7e244bff80bd2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-class-inspector: + build: . + image: mcp-perl-class-inspector:latest + container_name: mcp-perl-class-inspector + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-class-inspector + 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-class-inspector/environment.yaml b/Biomni/mcp_generated/mcp_perl-class-inspector/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0f1437910a6a0d25770e5a801af4459da5effeaf --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-class-inspector + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-class-inspector/requirements.txt b/Biomni/mcp_generated/mcp_perl-class-inspector/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-inspector/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-class-load/requirements.txt b/Biomni/mcp_generated/mcp_perl-class-load/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-class-load/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/Dockerfile b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..71094489eefe1042d4498d0bca44f3b5cc6ee415 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/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-compress-raw-zlib via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-compress-raw-zlib -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-compress-raw-zlib_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-compress-raw-zlib_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-compress-raw-zlib_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_server.py b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_server.py new file mode 100644 index 0000000000000000000000000000000000000000..615cf5500ee4b7be93f5f78ff94308e464ecc235 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_server.py @@ -0,0 +1,292 @@ +import subprocess +from pathlib import Path +from typing import List, Optional + +# In a real MCP environment, the 'mcp' object would be provided. +# This is a mock for demonstration and to make the code runnable standalone. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_compress_raw_zlib' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def pigz_compress( + input_files: List[Path], + output_file: Optional[Path] = None, + level: int = 6, + processes: Optional[int] = None, + blocksize: int = 128, + keep: bool = False, + force: bool = False, + independent: bool = False, + no_name: bool = False, + name: bool = False, + recursive: bool = False, + zlib: bool = False, + rsyncable: bool = False, + suffix: Optional[str] = None, + quiet: bool = False, + verbose: bool = False, +) -> dict: + """ + Compresses files using pigz, a parallel implementation of gzip. + This tool serves as a command-line equivalent for tasks that might use the + perl-compress-raw-zlib library, especially when using the 'zlib=True' option + to create zlib-formatted archives. + """ + if not input_files: + raise ValueError("At least one input file must be provided.") + for f in input_files: + if not f.exists(): + raise FileNotFoundError(f"Input file not found: {f}") + + if not (0 <= level <= 9 or level == 11): + raise ValueError("Compression level must be between 0 and 9, or 11 for zopfli.") + + if processes is not None and processes < 1: + raise ValueError("Number of processes must be a positive integer.") + + if blocksize < 1: + raise ValueError("Blocksize must be a positive integer.") + + if name and no_name: + raise ValueError("Cannot use --name (-N) and --no-name (-n) simultaneously.") + + cmd = ["pigz"] + cmd.append(f"-{level}") + + if zlib: cmd.append("-z") + if keep: cmd.append("-k") + if force: cmd.append("-f") + if independent: cmd.append("-i") + if no_name: cmd.append("-n") + if name: cmd.append("-N") + if recursive: cmd.append("-r") + if rsyncable: cmd.append("--rsyncable") + if quiet: cmd.append("-q") + if verbose: cmd.append("-v") + if processes: cmd.extend(["-p", str(processes)]) + if blocksize != 128: cmd.extend(["-b", str(blocksize)]) + if suffix: cmd.extend(["-S", suffix.lstrip('.')]) + if output_file: cmd.append("-c") + + cmd.extend(map(str, input_files)) + command_executed = " ".join(cmd) + output_files_list = [] + + try: + if output_file: + with open(output_file, "wb") as f_out: + result = subprocess.run(cmd, check=True, stdout=f_out, stderr=subprocess.PIPE) + stdout_str = f"Output successfully written to {output_file}" + stderr_str = result.stderr.decode() + output_files_list.append(str(output_file)) + else: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + stdout_str = result.stdout + stderr_str = result.stderr + + if not recursive: + default_suffix = ".zz" if zlib else ".gz" + used_suffix = suffix if suffix else default_suffix + if not used_suffix.startswith('.'): + used_suffix = '.' + used_suffix + for in_file in input_files: + out_path = in_file.with_name(in_file.name + used_suffix) + if out_path.exists(): + output_files_list.append(str(out_path)) + + except FileNotFoundError: + raise RuntimeError("pigz not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "error": "pigz execution failed.", + "command_executed": command_executed, + "stdout": e.stdout.decode() if e.stdout else "", + "stderr": e.stderr.decode() if e.stderr else "", + "return_code": e.returncode, + } + + return { + "command_executed": command_executed, + "stdout": stdout_str, + "stderr": stderr_str, + "output_files": output_files_list, + } + +@mcp.tool() +def pigz_decompress( + input_files: List[Path], + output_file: Optional[Path] = None, + processes: Optional[int] = None, + keep: bool = False, + force: bool = False, + no_name: bool = False, + name: bool = False, + recursive: bool = False, + zlib: bool = False, + suffix: Optional[str] = None, + quiet: bool = False, + verbose: bool = False, +) -> dict: + """ + Decompresses files using pigz. + Use 'zlib=True' for decompressing zlib-formatted files, which corresponds to + the functionality of the perl-compress-raw-zlib library. + """ + if not input_files: + raise ValueError("At least one input file must be provided.") + for f in input_files: + if not f.exists(): + raise FileNotFoundError(f"Input file not found: {f}") + + if processes is not None and processes < 1: + raise ValueError("Number of processes must be a positive integer.") + + if name and no_name: + raise ValueError("Cannot use --name (-N) and --no-name (-n) simultaneously.") + + cmd = ["pigz", "-d"] + if zlib: cmd.append("-z") + if keep: cmd.append("-k") + if force: cmd.append("-f") + if no_name: cmd.append("-n") + if name: cmd.append("-N") + if recursive: cmd.append("-r") + if quiet: cmd.append("-q") + if verbose: cmd.append("-v") + if processes: cmd.extend(["-p", str(processes)]) + if suffix: cmd.extend(["-S", suffix.lstrip('.')]) + if output_file: cmd.append("-c") + + cmd.extend(map(str, input_files)) + command_executed = " ".join(cmd) + output_files_list = [] + + try: + if output_file: + with open(output_file, "wb") as f_out: + result = subprocess.run(cmd, check=True, stdout=f_out, stderr=subprocess.PIPE) + stdout_str = f"Output successfully written to {output_file}" + stderr_str = result.stderr.decode() + output_files_list.append(str(output_file)) + else: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + stdout_str = result.stdout + stderr_str = result.stderr + + if not recursive: + suffix_to_remove = ".zz" if zlib else ".gz" + if suffix: + suffix_to_remove = suffix if suffix.startswith('.') else '.' + suffix + for in_file in input_files: + if in_file.name.endswith(suffix_to_remove): + out_path = in_file.with_name(in_file.name[:-len(suffix_to_remove)]) + if out_path.exists(): + output_files_list.append(str(out_path)) + + except FileNotFoundError: + raise RuntimeError("pigz not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "error": "pigz execution failed.", + "command_executed": command_executed, + "stdout": e.stdout.decode() if e.stdout else "", + "stderr": e.stderr.decode() if e.stderr else "", + "return_code": e.returncode, + } + + return { + "command_executed": command_executed, + "stdout": stdout_str, + "stderr": stderr_str, + "output_files": output_files_list, + } + +@mcp.tool() +def pigz_list( + input_files: List[Path], + verbose: bool = False, +) -> dict: + """ + Lists the contents of compressed files using 'pigz -l'. + """ + if not input_files: + raise ValueError("At least one input file must be provided.") + for f in input_files: + if not f.exists(): + raise FileNotFoundError(f"Input file not found: {f}") + + cmd = ["pigz", "-l"] + if verbose: cmd.append("-v") + cmd.extend(map(str, input_files)) + command_executed = " ".join(cmd) + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + except FileNotFoundError: + raise RuntimeError("pigz not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "error": "pigz execution failed.", + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + } + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [], + } + +@mcp.tool() +def pigz_test( + input_files: List[Path], + zlib: bool = False, +) -> dict: + """ + Tests the integrity of compressed files using 'pigz -t'. + """ + if not input_files: + raise ValueError("At least one input file must be provided.") + for f in input_files: + if not f.exists(): + raise FileNotFoundError(f"Input file not found: {f}") + + cmd = ["pigz", "-t"] + if zlib: cmd.append("-z") + cmd.extend(map(str, input_files)) + command_executed = " ".join(cmd) + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + except FileNotFoundError: + raise RuntimeError("pigz not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "error": "pigz execution failed.", + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + } + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_shim_server.py b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e1659b8ad186555add78009e6d96409b614c945c --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_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-compress-raw-zlib/app/perl-compress-raw-zlib_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_compress_raw_zlib' + + +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-compress-raw-zlib/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1363a2656f0a90483b156c19bd3bb54ae22b9ad3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-compress-raw-zlib: + build: . + image: mcp-perl-compress-raw-zlib:latest + container_name: mcp-perl-compress-raw-zlib + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-compress-raw-zlib + 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-compress-raw-zlib/environment.yaml b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a0a58200f60035fbe61fa1433da5a7004ab0281d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-compress-raw-zlib + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/requirements.txt b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-compress-raw-zlib/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-graphviz/requirements.txt b/Biomni/mcp_generated/mcp_perl-graphviz/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-html-tree/Dockerfile b/Biomni/mcp_generated/mcp_perl-html-tree/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0d4ae4907cb40a7596071ff28383323ea960fe70 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/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-html-tree via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-html-tree -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-html-tree_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-html-tree_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-html-tree_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_server.py b/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8a7cf11972df699db826967710d128897883bbc8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_server.py @@ -0,0 +1,143 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, Dict, Any, List + +# from mcp import mcp # This import would be present in a real MCP environment + +class mcp: + """Placeholder for the MCP decorator for standalone execution.""" + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_html_tree' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def html_tree_parser( + html_file: Optional[Path] = None, + html_content: Optional[str] = None, + url: Optional[str] = None, +) -> Dict[str, Any]: + """ + Parses HTML from a file, content string, or URL and dumps the DOM-like tree structure. + + This tool is a wrapper around the Perl HTML::TreeBuilder module from the + perl-html-tree distribution. Since perl-html-tree is a library and not a + standalone CLI tool, this function provides a command-line-like interface to its + core parsing functionality. You must provide exactly one input source. + """ + # 1. Input validation + inputs = [html_file, html_content, url] + if sum(1 for i in inputs if i is not None) != 1: + raise ValueError("Exactly one of 'html_file', 'html_content', or 'url' must be provided.") + + # 2. Prepare the Perl script and command + # This script uses HTML::TreeBuilder to parse from a file, stdin, or URL + # based on the mode passed as the first argument. + perl_script_content = """ + use strict; + use warnings; + use HTML::TreeBuilder; + + # The first argument will be the mode: 'file', 'content', or 'url' + my $mode = shift @ARGV; + my $tree; + + if ($mode eq 'file') { + my $filename = shift @ARGV; + die "No filename provided for 'file' mode.\\n" unless defined $filename; + $tree = HTML::TreeBuilder->new_from_file($filename); + } elsif ($mode eq 'content') { + # Read all of stdin for content + my $content = do { local $/; }; + $tree = HTML::TreeBuilder->new_from_content($content); + } elsif ($mode eq 'url') { + # This requires LWP::UserAgent, which is usually a dependency + my $url = shift @ARGV; + die "No URL provided for 'url' mode.\\n" unless defined $url; + $tree = HTML::TreeBuilder->new_from_url($url); + die "Could not fetch or parse URL: $url\\n" unless defined $tree; + } else { + die "Invalid mode specified: '$mode'. Must be 'file', 'content', or 'url'.\\n"; + } + + if (defined $tree) { + $tree->dump(); + $tree->delete(); + } else { + die "Failed to create HTML tree from the provided source.\\n"; + } + """ + + cmd: List[str] = [] + input_data = None + + with tempfile.TemporaryDirectory() as temp_dir: + script_path = Path(temp_dir) / "parser.pl" + script_path.write_text(perl_script_content) + + cmd = ["perl", str(script_path)] + + if html_file: + if not html_file.is_file(): + raise FileNotFoundError(f"Input file not found: {html_file}") + cmd.extend(["file", str(html_file)]) + elif html_content: + cmd.append("content") + input_data = html_content.encode('utf-8') + elif url: + cmd.extend(["url", url]) + + # 3. Subprocess execution + command_executed = " ".join(cmd) + if input_data: + # Represent the piped input for clarity in the command log + command_executed = f"echo '' | {command_executed}" + + try: + result = subprocess.run( + cmd, + input=input_data, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + # This error is raised if 'perl' is not in the system's PATH + return { + "error": "Perl executable not found. Please ensure Perl is installed and in your PATH.", + "command_executed": command_executed, + "stdout": "", + "stderr": "FileNotFoundError: 'perl' not found.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + # This handles non-zero exit codes from the Perl script + return { + "error": "Tool execution failed with a non-zero exit code.", + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + # 4. Structured result return + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [] # This tool prints to stdout; no output files are created. + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_shim_server.py b/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6667f6c13f2fc817d40983740c7f740e2992edab --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/app/perl-html-tree_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-html-tree/app/perl-html-tree_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_html_tree' + + +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-html-tree/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-tree/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-html-tree/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-html-tree/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7bc5f69a1fff42cc88cf5856404578e113ff24fd --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-html-tree: + build: . + image: mcp-perl-html-tree:latest + container_name: mcp-perl-html-tree + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-html-tree + 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-tree/environment.yaml b/Biomni/mcp_generated/mcp_perl-html-tree/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..62b367bd6a76db986b2545da909d0cf1171b8797 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-html-tree + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-html-tree/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-tree/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tree/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-io-stringy/app/perl-io-stringy_shim_server.py b/Biomni/mcp_generated/mcp_perl-io-stringy/app/perl-io-stringy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f9701aeb063eae89ed86163a4972d6500203dc62 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-io-stringy/app/perl-io-stringy_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-io-stringy/app/perl-io-stringy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_io_stringy' + + +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-io-stringy/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-io-stringy/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ab8d326c85a0bbdb6e546956235f1656ab1fce44 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-io-stringy/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-io-stringy: + build: . + image: mcp-perl-io-stringy:latest + container_name: mcp-perl-io-stringy + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-io-stringy + 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-ipc-sharelite/Dockerfile b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fc5746aecc6df4b80b7769aaf1371af3117e0749 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/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-ipc-sharelite via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-ipc-sharelite -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-ipc-sharelite_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-ipc-sharelite_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-ipc-sharelite_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-ipc-sharelite/app/perl-ipc-sharelite_shim_server.py b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/app/perl-ipc-sharelite_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..17d9e35a1321339b401d11327579bae62ce1193e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/app/perl-ipc-sharelite_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-ipc-sharelite/app/perl-ipc-sharelite_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_ipc_sharelite' + + +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-ipc-sharelite/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-ipc-sharelite/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..bca5e10f6e7e872a9026dfaf110dd03b8c3f376d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-ipc-sharelite: + build: . + image: mcp-perl-ipc-sharelite:latest + container_name: mcp-perl-ipc-sharelite + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-ipc-sharelite + 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-ipc-sharelite/requirements.txt b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ipc-sharelite/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-mailtools/Dockerfile b/Biomni/mcp_generated/mcp_perl-mailtools/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e2420b9059a39b61e9327929d1aea2421e3e3416 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/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-mailtools via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-mailtools -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-mailtools_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-mailtools_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-mailtools_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_server.py b/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5475dbf81b905efeee9208c62f12574c0095e925 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_server.py @@ -0,0 +1,220 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# MCP decorator is not imported as per the instructions. +# It is assumed to be available in the execution environment. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_mailtools' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + program: Optional[str] = None, + enable_features_program: Optional[str] = None, + check_syntax_only: bool = False, + record_separator: Optional[str] = None, + autosplit: bool = False, + split_pattern: Optional[str] = None, + unicode_features: Optional[str] = None, + run_under_debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + disable_sitelib: bool = False, + edit_in_place_extension: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + line_ending_processing: Optional[str] = None, + use_modules: Optional[List[str]] = None, + no_modules: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + parse_switches: bool = False, + search_path: bool = False, + enable_tainting_warnings: bool = False, + enable_tainting_checks: bool = False, + dump_core: bool = False, + allow_unsafe_operations: bool = False, + print_version: bool = False, + print_config_summary: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + ignore_text_before_shebang: Optional[Path] = None, +) -> dict: + """ + Executes the Perl interpreter. While the package is 'perl-mailtools', it is a library + and does not provide a direct command-line tool. This function wraps the 'perl' + executable itself, allowing execution of scripts that may use the MailTools modules. + + Args: + programfile: The Perl script file to execute. + arguments: Arguments to pass to the programfile. + program: A one-line program string to execute (-e). + enable_features_program: Like -e, but enables all optional features (-E). + check_syntax_only: Check syntax only; runs BEGIN and CHECK blocks (-c). + record_separator: Specify record separator (-0[octal]). + autosplit: Autosplit mode with -n or -p; splits $_ into @F (-a). + split_pattern: split() pattern for -a switch (-F/pattern/). + unicode_features: Enables the listed Unicode features (-C[number/list]). + run_under_debugger: Run program under a specified debugger (-d[:debugger]). + debugging_flags: Set debugging flags (-D[number/list]). + disable_sitelib: Don't do $sitelib/sitecustomize.pl at startup (-f). + edit_in_place_extension: Edit <> files in place; makes backup if extension is supplied (-i[extension]). + include_directories: Specify @INC/#include directory (-I). Can be specified multiple times. + line_ending_processing: Enable line ending processing, specifies line terminator (-l[octal]). + use_modules: Execute "use module..." before executing the program (-M). + no_modules: Execute "no module..." before executing the program (-m). + loop_around_program: Assume "while (<>) { ... }" loop around program (-n). + loop_and_print: Assume loop like -n but also print line, like sed (-p). + parse_switches: Enable rudimentary parsing for switches after programfile (-s). + search_path: Look for programfile using the PATH environment variable (-S). + enable_tainting_warnings: Enable tainting warnings (-t). + enable_tainting_checks: Enable tainting checks (-T). + dump_core: Dump core after parsing the program (-u). + allow_unsafe_operations: Allow unsafe operations (-U). + print_version: Print version, patchlevel, and license (-v). + print_config_summary: Print configuration summary or a single Config.pm variable (-V[:variable]). + enable_warnings: Enable many useful warnings (-w). + enable_all_warnings: Enable all warnings (-W). + disable_all_warnings: Disable all warnings (-X). + ignore_text_before_shebang: Ignore text before #!perl line, optionally cd to directory (-x[directory]). + + Returns: + A dictionary containing the command executed, stdout, stderr, and output files. + """ + # --- Input Validation --- + program_sources = [programfile, program, enable_features_program] + if sum(p is not None for p in program_sources) > 1: + raise ValueError("Only one of 'programfile', 'program', or 'enable_features_program' can be specified.") + if all(p is None for p in program_sources) and not print_version and print_config_summary is None: + raise ValueError("Either 'programfile', 'program', 'enable_features_program', 'print_version', or 'print_config_summary' must be provided.") + + if arguments and not programfile: + raise ValueError("'arguments' can only be provided when 'programfile' is specified.") + + warning_flags = [enable_warnings, enable_all_warnings, disable_all_warnings] + if sum(warning_flags) > 1: + raise ValueError("Only one of 'enable_warnings', 'enable_all_warnings', or 'disable_all_warnings' can be set.") + + if loop_around_program and loop_and_print: + raise ValueError("'loop_around_program' (-n) and 'loop_and_print' (-p) are mutually exclusive.") + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"The program file '{programfile}' does not exist.") + + if include_directories: + for directory in include_directories: + if not directory.is_dir(): + raise NotADirectoryError(f"The include directory '{directory}' does not exist or is not a directory.") + + # --- Command Construction --- + cmd = ["perl"] + + if record_separator is not None: + cmd.append(f"-0{record_separator}") + if autosplit: + cmd.append("-a") + if unicode_features is not None: + cmd.append(f"-C{unicode_features}") + if check_syntax_only: + cmd.append("-c") + if run_under_debugger is not None: + cmd.append(f"-d{run_under_debugger}") + if debugging_flags is not None: + cmd.append(f"-D{debugging_flags}") + if disable_sitelib: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if edit_in_place_extension is not None: + cmd.append(f"-i{edit_in_place_extension}") + if include_directories: + for directory in include_directories: + cmd.extend(["-I", str(directory)]) + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if use_modules: + for module in use_modules: + cmd.extend(["-M", module]) + if no_modules: + for module in no_modules: + cmd.extend(["-m", module]) + if loop_around_program: + cmd.append("-n") + if loop_and_print: + cmd.append("-p") + if parse_switches: + cmd.append("-s") + if search_path: + cmd.append("-S") + if enable_tainting_warnings: + cmd.append("-t") + if enable_tainting_checks: + cmd.append("-T") + if dump_core: + cmd.append("-u") + if allow_unsafe_operations: + cmd.append("-U") + if print_version: + cmd.append("-v") + if print_config_summary is not None: + cmd.append(f"-V{print_config_summary}") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if disable_all_warnings: + cmd.append("-X") + if ignore_text_before_shebang is not None: + cmd.append(f"-x{ignore_text_before_shebang}") + + if program: + cmd.extend(["-e", program]) + elif enable_features_program: + cmd.extend(["-E", enable_features_program]) + elif programfile: + cmd.append(str(programfile)) + + if arguments: + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'perl' command not found. Please ensure Perl is installed and in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [] # Perl execution itself doesn't guarantee output files + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-mailtools/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-mailtools/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..13d77c601c7e4d7d7763d49b3c545a5cb1c0fb39 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-mailtools: + build: . + image: mcp-perl-mailtools:latest + container_name: mcp-perl-mailtools + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-mailtools + 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-mailtools/requirements.txt b/Biomni/mcp_generated/mcp_perl-mailtools/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-module-metadata/Dockerfile b/Biomni/mcp_generated/mcp_perl-module-metadata/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..542d3b3a1f2fc69c5630c24c2d5143f5ebb14fbf --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/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-module-metadata via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-module-metadata -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-module-metadata_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-module-metadata_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-module-metadata_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_server.py b/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6054bdbb57776fa8cc1f20da37e2007e05d7d608 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_server.py @@ -0,0 +1,249 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Dict, Any + +# This tool is based on the `module-metadata` command-line utility, +# which is part of the Perl Module::Metadata distribution. +# The user specified the tool name as `perl-module-metadata`, which will be used +# as the executable name. + +def _run_module_metadata_command( + base_args: List[str], + files: List[Path], + no_index: bool, + inc: Optional[List[Path]] +) -> Dict[str, Any]: + """ + A helper function to build and execute the perl-module-metadata command. + + Args: + base_args: A list of primary command arguments (e.g., ['--determine-packages']). + files: A list of input file paths to analyze. + no_index: If True, adds the --no-index flag. + inc: An optional list of directories to add to the library search path. + + Returns: + A dictionary containing the execution details and results. + """ + # 1. Input Validation + if not files: + raise ValueError("At least one input file must be provided.") + for file_path in files: + if not file_path.is_file(): + raise FileNotFoundError(f"Input file not found: {file_path}") + + if inc: + for dir_path in inc: + if not dir_path.is_dir(): + raise NotADirectoryError(f"Include path is not a directory: {dir_path}") + + # 2. Command Construction + command = ["perl-module-metadata"] + command.extend(base_args) + + if no_index: + command.append("--no-index") + + if inc: + for dir_path in inc: + command.extend(["--inc", str(dir_path)]) + + command.extend([str(f) for f in files]) + command_str = " ".join(command) + + # 3. Subprocess Execution and Error Handling + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": command_str, + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "output_files": [] # This tool primarily outputs to stdout + } + except FileNotFoundError: + raise RuntimeError( + "Error: 'perl-module-metadata' command not found. " + "Please ensure it is installed and in your system's PATH." + ) from None + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Command failed with exit code {e.returncode}" + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_module_metadata' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def determine_packages( + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines packages from given Perl module files. Corresponds to the --determine-packages flag. + """ + base_args = ["--determine-packages"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_package_version( + package: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the version of a given package from Perl module files. Corresponds to the --package-version flag. + """ + if not package: + raise ValueError("Package name cannot be empty.") + base_args = [f"--package-version={package}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_version( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the version of a given module from Perl module files. Corresponds to the --module-version flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--module-version={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_requires( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the requirements of a given module. Corresponds to the --requires flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--requires={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_optional_requires( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the optional requirements of a given module. Corresponds to the --optional-requires flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--optional-requires={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_recommends( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the recommended requirements of a given module. Corresponds to the --recommends flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--recommends={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_suggests( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the suggested requirements of a given module. Corresponds to the --suggests flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--suggests={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_conflicts( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the conflicting requirements of a given module. Corresponds to the --conflicts flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--conflicts={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_build_requires( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the build requirements of a given module. Corresponds to the --build-requires flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--build-requires={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_configure_requires( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the configure requirements of a given module. Corresponds to the --configure-requires flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--configure-requires={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +@mcp.tool() +def get_module_test_requires( + module: str, + files: List[Path], + no_index: bool = False, + inc: Optional[List[Path]] = None, +): + """ + Determines the test requirements of a given module. Corresponds to the --test-requires flag. + """ + if not module: + raise ValueError("Module name cannot be empty.") + base_args = [f"--test-requires={module}"] + return _run_module_metadata_command(base_args, files, no_index, inc) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_shim_server.py b/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0601858c78ffd054c190a54d39a1f93f10c4c858 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/app/perl-module-metadata_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-module-metadata/app/perl-module-metadata_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_module_metadata' + + +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-module-metadata/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-metadata/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-module-metadata/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-module-metadata/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..48a6aede419a5d961268890933fcaabc97b5713d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-module-metadata: + build: . + image: mcp-perl-module-metadata:latest + container_name: mcp-perl-module-metadata + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-module-metadata + 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-module-metadata/environment.yaml b/Biomni/mcp_generated/mcp_perl-module-metadata/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1db954feb1a1ccb1070351d5bbafb5d901b41b03 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-module-metadata + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-metadata/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-metadata/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-metadata/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-params-validate/Dockerfile b/Biomni/mcp_generated/mcp_perl-params-validate/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ae63329f61cc4dcb40fb646433f52e223e028287 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/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-params-validate via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-params-validate -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-params-validate_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-params-validate_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-params-validate_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_server.py b/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_server.py new file mode 100644 index 0000000000000000000000000000000000000000..80faaab0472733813d2208e6254613e43a93647b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_server.py @@ -0,0 +1,228 @@ +import subprocess +import shlex +from pathlib import Path +from typing import Optional, List, Dict + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be imported. +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def perl_run( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + execute_program: Optional[List[str]] = None, + execute_program_extended: Optional[List[str]] = None, + check_syntax_only: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + tainting_checks: bool = False, + tainting_warnings: bool = False, + include_directories: Optional[List[Path]] = None, + use_modules: Optional[List[str]] = None, + no_modules: Optional[List[str]] = None, + use_modules_m: Optional[List[str]] = None, + no_modules_m: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + autosplit: bool = False, + split_pattern: Optional[str] = None, + edit_in_place_extension: Optional[str] = None, + line_ending_processing: Optional[str] = None, + record_separator: Optional[str] = None, + run_under_debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + unicode_features: Optional[str] = None, + no_sitecustomize: bool = False, + enable_switch_parsing: bool = False, + search_path: bool = False, + dump_core: bool = False, + allow_unsafe: bool = False, + ignore_text_before_shebang: bool = False, + shebang_directory: Optional[Path] = None, +) -> Dict: + """ + Executes a Perl program using the standard Perl interpreter. + + Note: 'perl-params-validate' is a Perl module, not a standalone executable. + This tool wraps the 'perl' interpreter, which is used to run scripts that + may utilize the Params::Validate module. + + Args: + programfile: Path to the Perl program file to execute. + arguments: A list of arguments to pass to the program file. + execute_program: One or more lines of a program to execute (-e). Omit programfile. + execute_program_extended: Like -e, but enables all optional features (-E). + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks) (-c). + print_version: Print version, patchlevel, and license (-v). + print_config: Print configuration summary or a single variable (-V[:variable]). + enable_warnings: Enable many useful warnings (-w). + enable_all_warnings: Enable all warnings (-W). + disable_all_warnings: Disable all warnings (-X). + tainting_checks: Enable tainting checks (-T). + tainting_warnings: Enable tainting warnings (-t). + include_directories: Specify @INC/#include directories (-I). + use_modules: Execute "use module..." before the program (-Mmodule). + no_modules: Execute "no module..." before the program (-M-module). + use_modules_m: A variant of -M for executing "use module..." (-mmodule). + no_modules_m: A variant of -M for executing "no module..." (-m-module). + loop_around_program: Assume "while (<>) { ... }" loop around the program (-n). + loop_and_print: Assume loop like -n but also print line, like sed (-p). + autosplit: Enable autosplit mode with -n or -p (splits $_ into @F) (-a). + split_pattern: Specify the split() pattern for the -a switch (-F/pattern/). + edit_in_place_extension: Edit <> files in place, with an optional backup extension (-i[extension]). + line_ending_processing: Enable line ending processing, specifies line terminator (-l[octal]). + record_separator: Specify the record separator (\\0, if no argument) (-0[octal]). + run_under_debugger: Run the program under a debugger (-d[:debugger]). + debugging_flags: Set debugging flags (-D[number/list]). + unicode_features: Enable the listed Unicode features (-C[number/list]). + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup (-f). + enable_switch_parsing: Enable rudimentary parsing for switches after programfile (-s). + search_path: Look for programfile using the PATH environment variable (-S). + dump_core: Dump core after parsing the program (-u). + allow_unsafe: Allow unsafe operations (-U). + ignore_text_before_shebang: Ignore text before #!perl line (-x). + shebang_directory: Change directory to the specified one before running script with -x. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + has_programfile = programfile is not None + has_e_prog = execute_program is not None + has_E_prog = execute_program_extended is not None + + if not any([has_programfile, has_e_prog, has_E_prog, print_version, print_config is not None]): + raise ValueError("A program source must be provided via 'programfile', 'execute_program', 'execute_program_extended', 'print_version', or 'print_config'.") + + if has_programfile and (has_e_prog or has_E_prog): + raise ValueError("'programfile' cannot be used with 'execute_program' or 'execute_program_extended'.") + + if loop_around_program and loop_and_print: + raise ValueError("'loop_around_program' (-n) and 'loop_and_print' (-p) are mutually exclusive.") + + if tainting_warnings and tainting_checks: + raise ValueError("'tainting_warnings' (-t) and 'tainting_checks' (-T) are mutually exclusive.") + + if shebang_directory and not ignore_text_before_shebang: + raise ValueError("'shebang_directory' can only be used when 'ignore_text_before_shebang' is True.") + + if has_programfile and not programfile.is_file(): + raise FileNotFoundError(f"Program file not found: {programfile}") + + # --- Command Construction --- + cmd = ["perl"] + + if record_separator is not None: + cmd.append(f"-0{record_separator}") + if autosplit: + cmd.append("-a") + if unicode_features is not None: + cmd.append(f"-C{unicode_features}") + if check_syntax_only: + cmd.append("-c") + if run_under_debugger is not None: + cmd.append(f"-d{run_under_debugger}") + if debugging_flags is not None: + cmd.append(f"-D{debugging_flags}") + if execute_program: + for prog in execute_program: + cmd.extend(["-e", prog]) + if execute_program_extended: + for prog in execute_program_extended: + cmd.extend(["-E", prog]) + if no_sitecustomize: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if edit_in_place_extension is not None: + cmd.append(f"-i{edit_in_place_extension}") + if include_directories: + for directory in include_directories: + cmd.extend(["-I", str(directory)]) + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if use_modules: + for module in use_modules: + cmd.append(f"-M{module}") + if no_modules: + for module in no_modules: + cmd.append(f"-M-{module}") + if use_modules_m: + for module in use_modules_m: + cmd.append(f"-m{module}") + if no_modules_m: + for module in no_modules_m: + cmd.append(f"-m-{module}") + if loop_around_program: + cmd.append("-n") + if loop_and_print: + cmd.append("-p") + if enable_switch_parsing: + cmd.append("-s") + if search_path: + cmd.append("-S") + if tainting_warnings: + cmd.append("-t") + if tainting_checks: + cmd.append("-T") + if dump_core: + cmd.append("-u") + if allow_unsafe: + cmd.append("-U") + if print_version: + cmd.append("-v") + if print_config is not None: + cmd.append(f"-V{print_config}") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if ignore_text_before_shebang: + cmd.append(f"-x{shebang_directory}" if shebang_directory else "-x") + if disable_all_warnings: + cmd.append("-X") + + if programfile: + cmd.append(str(programfile)) + + if arguments: + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = shlex.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, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'perl' command not found. Is Perl installed and in the system's PATH?", + "error": "Command not found", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed with return code {e.returncode}", + "output_files": [] + } diff --git a/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_shim_server.py b/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e8e45cb34e5a516c398300c6cb622e46afdfb59a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/app/perl-params-validate_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-params-validate/app/perl-params-validate_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_params_validate' + + +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-params-validate/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-params-validate/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-params-validate/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-params-validate/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..36bc3e6873e579908a1a2073f488d9129a8fcf97 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-params-validate: + build: . + image: mcp-perl-params-validate:latest + container_name: mcp-perl-params-validate + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-params-validate + 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-params-validate/environment.yaml b/Biomni/mcp_generated/mcp_perl-params-validate/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cd368fdf7d5d5bcdb1516aaba5f80ba5a6d5a2a7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-params-validate + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-params-validate/requirements.txt b/Biomni/mcp_generated/mcp_perl-params-validate/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-params-validate/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-set-intspan/requirements.txt b/Biomni/mcp_generated/mcp_perl-set-intspan/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-set-intspan/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-sort-naturally/Dockerfile b/Biomni/mcp_generated/mcp_perl-sort-naturally/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..087f4f752018a2a44d076efcb3b797af82740a45 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/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-sort-naturally via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-sort-naturally -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-sort-naturally_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-sort-naturally_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-sort-naturally_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_server.py b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5a19ad5da7b6527ce2d3fd3f29caa1e9185ff542 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_server.py @@ -0,0 +1,129 @@ +from typing import Optional +import subprocess +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_sort_naturally' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def nsort( + input_file: str, + output_file: Optional[str] = None, +) -> dict: + """ + Performs a case-insensitive natural sort on the lines of a file. + Natural sorting treats numeral parts numerically (e.g., "10" comes after "2"). + + Args: + input_file: Path to the input file to be sorted. + output_file: Optional path to save the sorted output. If not provided, output is returned in stdout. + """ + input_path = Path(input_file) + if not input_path.exists(): + return {"error": f"Input file not found: {input_file}"} + + # Construct the Perl one-liner using the Sort::Naturally module + # nsort is the case-insensitive version + perl_script = "print nsort(<>)" + command = ["perl", "-MSort::Naturally", "-e", perl_script, str(input_path)] + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + + stdout_content = result.stdout + + if output_file: + output_path = Path(output_file) + output_path.write_text(stdout_content) + return { + "command_executed": " ".join(command), + "stdout": f"Output written to {output_file}", + "stderr": result.stderr, + "output_files": [str(output_path)] + } + + return { + "command_executed": " ".join(command), + "stdout": stdout_content, + "stderr": result.stderr, + "output_files": [] + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed. Ensure 'perl-sort-naturally' is installed. Error: {str(e)}" + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def sort_naturally( + input_file: str, + output_file: Optional[str] = None, +) -> dict: + """ + Performs a case-sensitive natural sort on the lines of a file. + Natural sorting treats numeral parts numerically (e.g., "10" comes after "2"). + + Args: + input_file: Path to the input file to be sorted. + output_file: Optional path to save the sorted output. If not provided, output is returned in stdout. + """ + input_path = Path(input_file) + if not input_path.exists(): + return {"error": f"Input file not found: {input_file}"} + + # Construct the Perl one-liner using the Sort::Naturally module + # sort_naturally is the case-sensitive version + perl_script = "print sort_naturally(<>)" + command = ["perl", "-MSort::Naturally", "-e", perl_script, str(input_path)] + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + + stdout_content = result.stdout + + if output_file: + output_path = Path(output_file) + output_path.write_text(stdout_content) + return { + "command_executed": " ".join(command), + "stdout": f"Output written to {output_file}", + "stderr": result.stderr, + "output_files": [str(output_path)] + } + + return { + "command_executed": " ".join(command), + "stdout": stdout_content, + "stderr": result.stderr, + "output_files": [] + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed. Ensure 'perl-sort-naturally' is installed. Error: {str(e)}" + } + except Exception as e: + return {"error": str(e)} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_shim_server.py b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..be0536e39c62cadc29a200f40205468962baa18f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/perl-sort-naturally_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-sort-naturally/app/perl-sort-naturally_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_sort_naturally' + + +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-sort-naturally/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-sort-naturally/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-sort-naturally/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..111c06639193290d0cd78a16c2a5793aa1edd559 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-sort-naturally: + build: . + image: mcp-perl-sort-naturally:latest + container_name: mcp-perl-sort-naturally + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-sort-naturally + 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-sort-naturally/environment.yaml b/Biomni/mcp_generated/mcp_perl-sort-naturally/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f39ce296e8a3c580c3cceebb69a85f41e6d8c54 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-sort-naturally + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sort-naturally/requirements.txt b/Biomni/mcp_generated/mcp_perl-sort-naturally/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sort-naturally/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-sub-name/Dockerfile b/Biomni/mcp_generated/mcp_perl-sub-name/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2ea019a50318d1e76a20b8e5226133f7e774dd17 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/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-sub-name via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-sub-name -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-sub-name_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-sub-name_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-sub-name_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_server.py b/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1a7f7f7c6dfe45a351651fef1124411aa7834783 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_server.py @@ -0,0 +1,41 @@ +import subprocess +from typing import Dict + +# This is a placeholder. mcp is not a real library. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_sub_name' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def sub_name() -> Dict[str, str]: + """ + Note: `perl-sub-name` is a Perl library (`Sub::Name`) for assigning names to subroutines at runtime. + It does not provide a command-line executable. This MCP tool is a placeholder and does not + execute any command, as there is no underlying tool to run. + """ + # Since this is a library and not a CLI tool, we cannot execute it. + # The return value indicates that no command was run and provides an + # informative message in stderr. + + error_message = ( + "Error: perl-sub-name is a Perl library, not a command-line tool. " + "It cannot be executed directly." + ) + + return { + "command_executed": "", + "stdout": "", + "stderr": error_message, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_shim_server.py b/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e4806dfa1cce9c37acbe808c13f7299c69fb07f0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/app/perl-sub-name_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-sub-name/app/perl-sub-name_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_sub_name' + + +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-sub-name/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-sub-name/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-sub-name/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-sub-name/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e6217daeff1812d7e154ae7728b9c47cc780b56 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-sub-name: + build: . + image: mcp-perl-sub-name:latest + container_name: mcp-perl-sub-name + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-sub-name + 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-sub-name/environment.yaml b/Biomni/mcp_generated/mcp_perl-sub-name/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..437d4688d41f800d48d6dbd9361b176b0b6fc4ad --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-sub-name + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sub-name/requirements.txt b/Biomni/mcp_generated/mcp_perl-sub-name/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-name/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-unicode-map/Dockerfile b/Biomni/mcp_generated/mcp_perl-unicode-map/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..881eb3c306d946ae0a04dce0442703d999f893f8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/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-unicode-map via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-unicode-map -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-unicode-map_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-unicode-map_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-unicode-map_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_server.py b/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8015da28123651284b1783ab0a0a3a836cd1bd8e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_server.py @@ -0,0 +1,272 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union +import tempfile + +# No need to import mcp as per instructions + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_unicode_map' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl_script( + program_file: Optional[Path] = None, + program_lines: Optional[List[str]] = None, + script_arguments: Optional[List[str]] = None, + record_separator_octal: Optional[str] = None, + autosplit_mode: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + program_lines_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_terminator_octal: Optional[str] = None, + modules_to_use: Optional[List[str]] = None, + modules_to_no_use: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + rudimentary_parsing_for_switches: bool = False, + search_path_for_program_file: bool = False, + enable_tainting_warnings: bool = False, + enable_tainting_checks: bool = False, + dump_core_after_parsing: bool = False, + allow_unsafe_operations: bool = False, + print_version: bool = False, + print_config_variable: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + ignore_text_before_perl_line_dir: Optional[Path] = None, + disable_all_warnings: bool = False, +) -> dict: + """ + Executes a Perl script or program lines with specified Perl interpreter switches. + + This tool provides a robust interface to the Perl interpreter, allowing the execution + of Perl scripts with fine-grained control over various runtime options. It is suitable + for running any Perl program, including those that utilize modules like `perl-unicode-map` + for character set mapping and other Unicode functionalities. + + Args: + program_file: Path to the Perl program file to execute. + Required if `program_lines` or `program_lines_extended` are not provided, + and if not requesting version/config info. + program_lines: A list of Perl code lines to execute directly via the `-e` switch. + Cannot be used with `program_file` or `program_lines_extended`. + script_arguments: Arguments to pass to the Perl script. These are appended after + the program file or `-e`/`-E` lines. + record_separator_octal: Specify the input record separator. If an octal string + (e.g., '0' for NUL, '0777' for paragraph mode) is provided, + it sets `$/`. If an empty string, it defaults to `\0`. + autosplit_mode: Enable autosplit mode (`-a`) with `-n` or `-p`. This splits `$_` + into `@F` based on the pattern specified by `split_pattern` or `/\s+/`. + unicode_features: Enables listed Unicode features (`-C`). Can be a number or a list + (e.g., 'all', 'io', 'say'). + check_syntax_only: Check syntax only (`-c`). Runs `BEGIN` and `CHECK` blocks but + does not execute the main program. + debugger: Run the program under a debugger (`-d`). Optionally specify a debugger + (e.g., 'DB'). + debugging_flags: Set debugging flags (`-D`). Argument is a bit mask or alphabets. + program_lines_extended: A list of Perl code lines to execute directly via the `-E` switch. + Similar to `-e`, but enables all optional features (e.g., `say`, `state`). + Cannot be used with `program_file` or `program_lines`. + no_sitecustomize: Don't do `$sitelib/sitecustomize.pl` at startup (`-f`). + split_pattern: The `split()` pattern for the `-a` switch (`-F/pattern/`). + The slashes are optional in the argument string (e.g., '/\s+/' or '\s+'). + edit_in_place_extension: Edit `<>` files in place (`-i`). If an extension is supplied + (e.g., '.bak'), a backup file will be made with that extension. + If an empty string `""` is provided, files are edited without backup. + include_directories: Specify `@INC` directories (`-I`). These directories are added + to Perl's search path for modules. + line_ending_terminator_octal: Enable line ending processing (`-l`). Specifies the line + terminator (e.g., '012' for newline). If an octal string + is provided, it sets `$\`. If an empty string, it enables + line ending processing without changing `$\`. + modules_to_use: List of modules to `use` before executing the program (`-M`). + Each string should be a module name (e.g., 'strict', 'warnings'). + modules_to_no_use: List of modules to `no` before executing the program (`-M-`). + Each string should be a module name. + loop_around_program: Assume "while (<>) { ... }" loop around the program (`-n`). + Reads input line by line. + loop_and_print: Assume loop like `-n` but print line also, like `sed` (`-p`). + rudimentary_parsing_for_switches: Enable rudimentary parsing for switches after the + program file (`-s`). + search_path_for_program_file: Look for the program file using the `PATH` environment + variable (`-S`). + enable_tainting_warnings: Enable tainting warnings (`-t`). + enable_tainting_checks: Enable tainting checks (`-T`). + dump_core_after_parsing: Dump core after parsing the program (`-u`). + allow_unsafe_operations: Allow unsafe operations (`-U`). + print_version: Print Perl version, patchlevel, and license (`-v`). + Mutually exclusive with program execution options. + print_config_variable: Print configuration summary (`-V`) or a single `Config.pm` + variable (`-V:variable`). Mutually exclusive with program + execution options. + enable_warnings: Enable many useful warnings (`-w`). + enable_all_warnings: Enable all warnings (`-W`). + ignore_text_before_perl_line_dir: Ignore text before the `#!perl` line (`-x`). + Optionally `cd` to the specified directory. + disable_all_warnings: Disable all warnings (`-X`). + """ + command = ["perl"] + output_files: List[Path] = [] + + # Input validation + has_program_source = program_file is not None or program_lines is not None or program_lines_extended is not None + has_info_request = print_version or print_config_variable is not None + + if has_program_source and has_info_request: + raise ValueError( + "Cannot specify program source (file or lines) along with " + "version/config print options (-v or -V)." + ) + if not has_program_source and not has_info_request: + raise ValueError( + "Either a program source (program_file, program_lines, or program_lines_extended) " + "or an info request (print_version, print_config_variable) must be provided." + ) + + if program_file and (program_lines is not None or program_lines_extended is not None): + raise ValueError("Cannot specify both 'program_file' and 'program_lines' or 'program_lines_extended'.") + if program_lines is not None and program_lines_extended is not None: + raise ValueError("Cannot specify both 'program_lines' (-e) and 'program_lines_extended' (-E).") + + if program_file: + if not program_file.is_file(): + raise FileNotFoundError(f"Program file not found: {program_file}") + + 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_perl_line_dir: + if not ignore_text_before_perl_line_dir.is_dir(): + raise NotADirectoryError(f"Directory for -x option not found: {ignore_text_before_perl_line_dir}") + + # Build command + if record_separator_octal is not None: + command.append(f"-0{record_separator_octal}") + if autosplit_mode: + command.append("-a") + if unicode_features is not None: + command.append(f"-C{unicode_features}") + if check_syntax_only: + command.append("-c") + if debugger is not None: + command.append(f"-d:{debugger}") + if debugging_flags is not None: + command.append(f"-D{debugging_flags}") + if program_lines: + for line in program_lines: + command.extend(["-e", line]) + if program_lines_extended: + for line in program_lines_extended: + command.extend(["-E", line]) + 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 include_directories: + for d in include_directories: + command.extend(["-I", str(d)]) + if line_ending_terminator_octal is not None: + command.append(f"-l{line_ending_terminator_octal}") + if modules_to_use: + for module in modules_to_use: + command.extend(["-M", module]) # Using -M for 'use module' + if modules_to_no_use: + for module in modules_to_no_use: + command.extend(["-M-", module]) # Using -M- for 'no module' + if loop_around_program: + command.append("-n") + if loop_and_print: + command.append("-p") + if rudimentary_parsing_for_switches: + command.append("-s") + if search_path_for_program_file: + command.append("-S") + if enable_tainting_warnings: + command.append("-t") + if enable_tainting_checks: + command.append("-T") + if dump_core_after_parsing: + command.append("-u") + if allow_unsafe_operations: + command.append("-U") + if print_version: + command.append("-v") + if print_config_variable is not None: + command.append(f"-V:{print_config_variable}") + # Note: The perl -V switch without an argument is covered by print_config_variable=None + # if the user explicitly wanted to pass -V. However, the current signature + # only allows -V:variable. If print_config_variable is None, -V is not added. + # For simplicity, we assume if print_config_variable is None, the user doesn't want -V. + # If they want -V (no arg), they would set print_config_variable = "". + # Let's adjust this to allow print_config_variable = "" for -V. + # The current code `if print_config_variable is not None:` handles both string and empty string. + + if enable_warnings: + command.append("-w") + if enable_all_warnings: + command.append("-W") + if ignore_text_before_perl_line_dir is not None: + command.append(f"-x{ignore_text_before_perl_line_dir}") + if disable_all_warnings: + command.append("-X") + + # Add program file and its arguments + if program_file: + command.append(str(program_file)) + if script_arguments: + command.extend(script_arguments) + + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = process.stdout + stderr = process.stderr + + # If -i was used, Perl modifies files in place and potentially creates backups. + # Identifying these output files robustly without knowing the input files + # passed to Perl's diamond operator (<>) is challenging. + # For this tool, we will not attempt to automatically track files modified by -i. + # Users should manage these files based on their script's input. + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl command failed with exit code {e.returncode}: {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 system's PATH.", + "error": "Perl executable not found", + "returncode": 127, + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_shim_server.py b/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c76d24147dbaef6f0013642220e7d7d0917eb744 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/app/perl-unicode-map_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-unicode-map/app/perl-unicode-map_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_unicode_map' + + +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-unicode-map/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-unicode-map/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-unicode-map/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-unicode-map/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e7ef8d7a2a344f77de3733d64cd9be1450518f00 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-unicode-map: + build: . + image: mcp-perl-unicode-map:latest + container_name: mcp-perl-unicode-map + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-unicode-map + 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-unicode-map/environment.yaml b/Biomni/mcp_generated/mcp_perl-unicode-map/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dc152987b03c644554279b75fbde4a293e6e1758 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-unicode-map + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-unicode-map/requirements.txt b/Biomni/mcp_generated/mcp_perl-unicode-map/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-unicode-map/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_prokka/app/prokka_server.py b/Biomni/mcp_generated/mcp_prokka/app/prokka_server.py new file mode 100644 index 0000000000000000000000000000000000000000..83c8d24a9e9aff498cbbbc6226d604ec859bd11e --- /dev/null +++ b/Biomni/mcp_generated/mcp_prokka/app/prokka_server.py @@ -0,0 +1,282 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union +import os + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_prokka' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def prokka( + fasta_file: str, + outdir: Optional[str] = None, + force: bool = False, + prefix: Optional[str] = None, + addgenes: bool = False, + locustag: str = "PROKKA", + increment: int = 1, + gffver: int = 3, + compliant: bool = False, + centre: Optional[str] = None, + genus: str = "Genus", + species: str = "species", + strain: str = "strain", + plasmid: Optional[str] = None, + kingdom: str = "Bacteria", + gcode: int = 0, + prodigaltf: Optional[str] = None, + gram: Optional[str] = None, + usegenus: bool = False, + proteins: Optional[str] = None, + hmms: Optional[str] = None, + metagenome: bool = False, + rawproduct: bool = False, + fast: bool = False, + cpus: int = 8, + mincontiglen: int = 1, + evalue: float = 1e-06, + rfam: bool = False, + norrna: bool = False, + notrna: bool = False, + rnammer: bool = False, + quiet: bool = False, + debug: bool = False +): + """ + Rapid prokaryotic genome annotation. + + Args: + fasta_file: Input contigs in FASTA format. + outdir: Output folder [auto]. + force: Force overwriting existing output folder. + prefix: Filename output prefix [auto]. + addgenes: Add 'gene' features for each 'CDS' feature. + locustag: Locus tag prefix. + increment: Locus tag counter increment. + gffver: GFF version. + compliant: Force Genbank/ENA/DDJB compliance: --addgenes --mincontiglen 200. + centre: Sequencing centre ID. + genus: Genus name. + species: Species name. + strain: Strain name. + plasmid: Plasmid name or identifier. + kingdom: Annotation mode: Archaea|Bacteria|Mitochondria|Viruses. + gcode: Genetic code / Translation table (0 = use kingdom default). + prodigaltf: Prodigal training file. + gram: Gram: -/neg +/pos. + usegenus: Use genus-specific BLAST databases (needs --genus). + proteins: Fasta file of trusted proteins to first annotate from. + hmms: Trusted HMM to first annotate from. + metagenome: Improve gene predictions for highly fragmented genomes. + rawproduct: Do not clean up /product annotation. + fast: Fast mode - skip CDS /product searching. + cpus: Number of CPUs to use [0=all]. + mincontiglen: Minimum contig size [NCBI needs 200]. + evalue: Similarity e-value cut-off. + rfam: Enable searching for ncRNAs with Infernal+Rfam (SLOW!). + norrna: Don't run rRNA search. + notrna: Don't run tRNA search. + rnammer: Prefer RNAmmer over Barrnap for rRNA prediction. + quiet: No screen output. + debug: Debug mode: keep all temporary files. + """ + # Input validation + input_path = Path(fasta_file) + if not input_path.exists(): + return {"error": f"Input FASTA file not found: {fasta_file}"} + + if kingdom not in ["Archaea", "Bacteria", "Mitochondria", "Viruses"]: + return {"error": "Kingdom must be one of: Archaea, Bacteria, Mitochondria, Viruses"} + + if gram and gram not in ["-", "neg", "+", "pos"]: + return {"error": "Gram must be one of: -, neg, +, pos"} + + cmd = ["prokka"] + + # General options + if quiet: cmd.append("--quiet") + if debug: cmd.append("--debug") + + # Output options + if outdir: cmd.extend(["--outdir", outdir]) + if force: cmd.append("--force") + if prefix: cmd.extend(["--prefix", prefix]) + if addgenes: cmd.append("--addgenes") + cmd.extend(["--locustag", locustag]) + cmd.extend(["--increment", str(increment)]) + cmd.extend(["--gffver", str(gffver)]) + if compliant: cmd.append("--compliant") + if centre: cmd.extend(["--centre", centre]) + + # Organism details + cmd.extend(["--genus", genus]) + cmd.extend(["--species", species]) + cmd.extend(["--strain", strain]) + if plasmid: cmd.extend(["--plasmid", plasmid]) + + # Annotation options + cmd.extend(["--kingdom", kingdom]) + cmd.extend(["--gcode", str(gcode)]) + if prodigaltf: + if not Path(prodigaltf).exists(): + return {"error": f"Prodigal training file not found: {prodigaltf}"} + cmd.extend(["--prodigaltf", prodigaltf]) + if gram: cmd.extend(["--gram", gram]) + if usegenus: cmd.append("--usegenus") + if proteins: + if not Path(proteins).exists(): + return {"error": f"Proteins file not found: {proteins}"} + cmd.extend(["--proteins", proteins]) + if hmms: + if not Path(hmms).exists(): + return {"error": f"HMM file not found: {hmms}"} + cmd.extend(["--hmms", hmms]) + if metagenome: cmd.append("--metagenome") + if rawproduct: cmd.append("--rawproduct") + + # Computation options + if fast: cmd.append("--fast") + cmd.extend(["--cpus", str(cpus)]) + cmd.extend(["--mincontiglen", str(mincontiglen)]) + cmd.extend(["--evalue", str(evalue)]) + if rfam: cmd.append("--rfam") + if norrna: cmd.append("--norrna") + if notrna: cmd.append("--notrna") + if rnammer: cmd.append("--rnammer") + + # Positional argument + cmd.append(fasta_file) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Determine output directory to list files + actual_outdir = outdir if outdir else f"PROKKA_{Path(fasta_file).stem}" + output_files = [] + if Path(actual_outdir).exists(): + output_files = [str(f) for f in Path(actual_outdir).iterdir() if f.is_file()] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def prokka_listdb(): + """List all configured Prokka databases.""" + cmd = ["prokka", "--listdb"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +@mcp.tool() +def prokka_setupdb(): + """Index all installed Prokka databases.""" + cmd = ["prokka", "--setupdb"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +@mcp.tool() +def prokka_cleandb(): + """Remove all Prokka database indices.""" + cmd = ["prokka", "--cleandb"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +@mcp.tool() +def prokka_depends(): + """List all software dependencies for Prokka.""" + cmd = ["prokka", "--depends"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +@mcp.tool() +def prokka_genbank_to_fasta_db(gbk_files: List[str]): + """ + Convert Genbank files to Prokka-compatible FASTA database. + + Args: + gbk_files: List of paths to Genbank (.gbk or .gb) files. + """ + valid_files = [] + for f in gbk_files: + p = Path(f) + if p.exists(): + valid_files.append(str(p)) + else: + return {"error": f"Genbank file not found: {f}"} + + cmd = ["prokka-genbank_to_fasta_db"] + valid_files + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "info": "Output is typically redirected to a file to create a database." + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +@mcp.tool() +def prokka_uniprot_to_fasta_db(uniprot_dat: str): + """ + Convert UniProt .dat file to Prokka-compatible FASTA database. + + Args: + uniprot_dat: Path to uniprot_sprot.dat or similar file. + """ + if not Path(uniprot_dat).exists(): + return {"error": f"UniProt file not found: {uniprot_dat}"} + + cmd = ["prokka-uniprot_to_fasta_db", uniprot_dat] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pyrodigal/Dockerfile b/Biomni/mcp_generated/mcp_pyrodigal/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..21d1c07b2ea3cc48c8af72ab26f74591f3e29681 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/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 pyrodigal via conda (e.g., from bioconda) +RUN conda install -c bioconda pyrodigal -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/pyrodigal_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/pyrodigal_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/pyrodigal_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_qualimap/app/qualimap_shim_server.py b/Biomni/mcp_generated/mcp_qualimap/app/qualimap_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3832921feda7bce4ed3e9b48a43ec5d550d93412 --- /dev/null +++ b/Biomni/mcp_generated/mcp_qualimap/app/qualimap_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_qualimap/app/qualimap_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_qualimap' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_server.py b/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f3aa37b3326400e73be43c596c95b457b0030f5c --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-epitrace/app/r-epitrace_server.py @@ -0,0 +1,499 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Literal, Optional + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def tool(func): + """A dummy decorator to mimic @mcp.tool() for standalone execution.""" + return func + +mcp = type("mcp", (), {"tool": tool}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_epitrace' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def epitrace_prepare_object( + peak_by_cell_matrix: Path, + fragment_file: Path, + ref_genome: Literal["hg19", "hg38", "mm9", "mm10", "dm6"], + output_rds: Path, + assay_name: str = "ATAC", + min_cells: int = 5, + min_features: int = 200, +) -> dict: + """ + Prepares a Seurat object for EpiTrace analysis from scATAC-seq data. + + This function takes a peak-by-cell matrix and a fragment file, creates a + Seurat object with a ChromatinAssay, and performs basic filtering. + + Args: + peak_by_cell_matrix: Path to the peak-by-cell matrix file (e.g., matrix.mtx, h5). + fragment_file: Path to the scATAC-seq fragment file (fragments.tsv.gz). + ref_genome: Reference genome version. Must be one of 'hg19', 'hg38', 'mm9', 'mm10', 'dm6'. + output_rds: Path to save the output Seurat object as an RDS file. + assay_name: Name for the new assay in the Seurat object. + min_cells: Minimum number of cells a feature must be detected in. + min_features: Minimum number of features a cell must have. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file path. + """ + # --- Input Validation --- + if not peak_by_cell_matrix.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {peak_by_cell_matrix}") + if not fragment_file.is_file(): + raise FileNotFoundError(f"Input fragment file not found: {fragment_file}") + if min_cells < 0: + raise ValueError("min_cells must be a non-negative integer.") + if min_features < 0: + raise ValueError("min_features must be a non-negative integer.") + + output_rds.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + # Load necessary libraries + library(EpiTrace) + library(Seurat) + library(Signac) + + # Log versions + cat("EpiTrace version:", as.character(packageVersion("EpiTrace")), "\\n") + cat("Seurat version:", as.character(packageVersion("Seurat")), "\\n") + cat("Signac version:", as.character(packageVersion("Signac")), "\\n") + + # Call the EpiTrace_prepare_object function + # Note: The original EpiTrace doesn't have a single function for this. + # We are creating a Seurat object as per standard Signac/Seurat workflows, + # which is the expected input for other EpiTrace functions. + + cat("Reading peak-by-cell matrix...\\n") + counts <- Read10X(data.dir = "{peak_by_cell_matrix.parent}") + + cat("Creating chromatin assay...\\n") + chrom_assay <- CreateChromatinAssay( + counts = counts, + sep = c(":", "-"), + genome = "{ref_genome}", + fragments = "{fragment_file}", + min.cells = {min_cells}, + min.features = {min_features} + ) + + cat("Creating Seurat object...\\n") + seurat_obj <- CreateSeuratObject( + counts = chrom_assay, + assay = "{assay_name}" + ) + + cat("Saving prepared Seurat object to {output_rds}\\n") + saveRDS(seurat_obj, file = "{output_rds}") + + cat("Object preparation complete.\\n") + """ + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script: + r_script.write(r_script_content) + script_path = r_script.name + + command = ["Rscript", script_path] + command_str = " ".join(command) + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + # Clean up the temporary script file + Path(script_path).unlink() + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_rds)], + } + except subprocess.CalledProcessError as e: + Path(script_path).unlink() + raise RuntimeError( + f"EpiTrace object preparation failed with exit code {e.returncode}.\n" + f"Command: {command_str}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def run_epitrace_age( + seurat_object_rds: Path, + ref_genome: Literal["hg19", "hg38", "mm9", "mm10", "dm6"], + output_rds: Path, + clock_name: str = "ClockDML_mm10", + solo_wcgw_clock: bool = False, + n_cores: int = 1, + output_plot_pdf: Optional[Path] = None, +) -> dict: + """ + Estimates cell age using the EpiTrace algorithm on a prepared Seurat object. + + Args: + seurat_object_rds: Path to a prepared Seurat object in RDS format. + ref_genome: Reference genome version. Must match the object. + output_rds: Path to save the output Seurat object with age metadata. + clock_name: Name of the clock set to use (e.g., 'ClockDML_mm10', 'ClockDML_hg19'). + solo_wcgw_clock: If TRUE, use the solo_WCGW clock instead of the default. + n_cores: Number of cores for parallel processing. + output_plot_pdf: Optional path to save a diagnostic plot as a PDF. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file paths. + """ + # --- Input Validation --- + if not seurat_object_rds.is_file(): + raise FileNotFoundError(f"Input Seurat object RDS not found: {seurat_object_rds}") + if n_cores <= 0: + raise ValueError("n_cores must be a positive integer.") + + output_rds.parent.mkdir(parents=True, exist_ok=True) + if output_plot_pdf: + output_plot_pdf.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + library(EpiTrace) + library(Seurat) + library(ggplot2) + + cat("Loading Seurat object from {seurat_object_rds}\\n") + seurat_obj <- readRDS("{seurat_object_rds}") + + cat("Running EpiTraceAge...\\n") + seurat_obj_with_age <- RunEpiTraceAge( + object = seurat_obj, + ref_genome = "{ref_genome}", + clock_name = "{clock_name}", + solo_WCGW_clock = {str(solo_wcgw_clock).upper()}, + n_cores = {n_cores} + ) + + cat("Saving updated Seurat object to {output_rds}\\n") + saveRDS(seurat_obj_with_age, file = "{output_rds}") + + """ + + if output_plot_pdf: + r_script_content += f""" + cat("Generating and saving diagnostic plot to {output_plot_pdf}\\n") + p <- FeaturePlot(seurat_obj_with_age, features = "EpiTraceAge") + ggsave("{output_plot_pdf}", plot = p, device = "pdf", width = 7, height = 7) + """ + + r_script_content += "\ncat('EpiTraceAge analysis complete.\\n')\n" + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script: + r_script.write(r_script_content) + script_path = r_script.name + + command = ["Rscript", script_path] + command_str = " ".join(command) + output_files = [str(output_rds)] + if output_plot_pdf: + output_files.append(str(output_plot_pdf)) + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + Path(script_path).unlink() + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + Path(script_path).unlink() + raise RuntimeError( + f"RunEpiTraceAge failed with exit code {e.returncode}.\n" + f"Command: {command_str}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def epitrace_age_convergence( + seurat_object_with_age_rds: Path, + ref_genome: Literal["hg19", "hg38", "mm9", "mm10", "dm6"], + output_csv: Path, + output_plot_pdf: Path, + n_cores: int = 1, +) -> dict: + """ + Performs a convergence analysis on the EpiTraceAge results. + + This function assesses the stability of the age estimation by iteratively + removing peaks and recalculating the age. + + Args: + seurat_object_with_age_rds: Path to an RDS file of a Seurat object after running RunEpiTraceAge. + ref_genome: Reference genome version. + output_csv: Path to save the convergence data as a CSV file. + output_plot_pdf: Path to save the convergence plot as a PDF file. + n_cores: Number of cores for parallel processing. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file paths. + """ + # --- Input Validation --- + if not seurat_object_with_age_rds.is_file(): + raise FileNotFoundError(f"Input Seurat object RDS not found: {seurat_object_with_age_rds}") + if n_cores <= 0: + raise ValueError("n_cores must be a positive integer.") + + output_csv.parent.mkdir(parents=True, exist_ok=True) + output_plot_pdf.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + library(EpiTrace) + library(Seurat) + library(ggplot2) + + cat("Loading Seurat object from {seurat_object_with_age_rds}\\n") + seurat_obj <- readRDS("{seurat_object_with_age_rds}") + + cat("Running EpiTraceAge_Convergence...\\n") + convergence_results <- EpiTraceAge_Convergence( + object = seurat_obj, + ref_genome = "{ref_genome}", + n_cores = {n_cores} + ) + + cat("Saving convergence data to {output_csv}\\n") + write.csv(convergence_results$conv_df, file = "{output_csv}", row.names = FALSE) + + cat("Saving convergence plot to {output_plot_pdf}\\n") + ggsave("{output_plot_pdf}", plot = convergence_results$conv_plot, device = "pdf") + + cat("Convergence analysis complete.\\n") + """ + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script: + r_script.write(r_script_content) + script_path = r_script.name + + command = ["Rscript", script_path] + command_str = " ".join(command) + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + Path(script_path).unlink() + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_csv), str(output_plot_pdf)], + } + except subprocess.CalledProcessError as e: + Path(script_path).unlink() + raise RuntimeError( + f"EpiTraceAge_Convergence failed with exit code {e.returncode}.\n" + f"Command: {command_str}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def association_of_peaks_to_age( + seurat_object_with_age_rds: Path, + ref_genome: Literal["hg19", "hg38", "mm9", "mm10", "dm6"], + output_csv: Path, + n_cores: int = 1, +) -> dict: + """ + Identifies genomic peaks whose accessibility is associated with EpiTraceAge. + + Args: + seurat_object_with_age_rds: Path to an RDS file of a Seurat object after running RunEpiTraceAge. + ref_genome: Reference genome version. + output_csv: Path to save the peak association results as a CSV file. + n_cores: Number of cores for parallel processing. + + Returns: + A dictionary containing the execution command, stdout, stderr, and the output file path. + """ + # --- Input Validation --- + if not seurat_object_with_age_rds.is_file(): + raise FileNotFoundError(f"Input Seurat object RDS not found: {seurat_object_with_age_rds}") + if n_cores <= 0: + raise ValueError("n_cores must be a positive integer.") + + output_csv.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + library(EpiTrace) + library(Seurat) + + cat("Loading Seurat object from {seurat_object_with_age_rds}\\n") + seurat_obj <- readRDS("{seurat_object_with_age_rds}") + + cat("Running AssociationOfPeaksToAge...\\n") + association_results <- AssociationOfPeaksToAge( + object = seurat_obj, + ref_genome = "{ref_genome}", + n_cores = {n_cores} + ) + + cat("Saving association results to {output_csv}\\n") + write.csv(association_results, file = "{output_csv}", row.names = TRUE) + + cat("Peak association analysis complete.\\n") + """ + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script: + r_script.write(r_script_content) + script_path = r_script.name + + command = ["Rscript", script_path] + command_str = " ".join(command) + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + Path(script_path).unlink() + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_csv)], + } + except subprocess.CalledProcessError as e: + Path(script_path).unlink() + raise RuntimeError( + f"AssociationOfPeaksToAge failed with exit code {e.returncode}.\n" + f"Command: {command_str}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) from e + + +@mcp.tool() +def run_epitrace_phylogeny( + seurat_object_with_age_rds: Path, + ref_genome: Literal["hg19", "hg38", "mm9", "mm10", "dm6"], + output_newick: Path, + group_by: Optional[str] = None, + n_cores: int = 1, + output_plot_pdf: Optional[Path] = None, +) -> dict: + """ + Infers cell phylogeny based on chromatin accessibility and EpiTraceAge. + + Args: + seurat_object_with_age_rds: Path to an RDS file of a Seurat object after running RunEpiTraceAge. + ref_genome: Reference genome version. + output_newick: Path to save the resulting phylogenetic tree in Newick format. + group_by: Metadata column in the Seurat object to group cells for phylogeny. If None, runs on individual cells. + n_cores: Number of cores for parallel processing. + output_plot_pdf: Optional path to save a plot of the phylogenetic tree as a PDF. + + Returns: + A dictionary containing the execution command, stdout, stderr, and output file paths. + """ + # --- Input Validation --- + if not seurat_object_with_age_rds.is_file(): + raise FileNotFoundError(f"Input Seurat object RDS not found: {seurat_object_with_age_rds}") + if n_cores <= 0: + raise ValueError("n_cores must be a positive integer.") + + output_newick.parent.mkdir(parents=True, exist_ok=True) + if output_plot_pdf: + output_plot_pdf.parent.mkdir(parents=True, exist_ok=True) + + group_by_r = f'"{group_by}"' if group_by else "NULL" + + r_script_content = f""" + library(EpiTrace) + library(Seurat) + library(ape) + + cat("Loading Seurat object from {seurat_object_with_age_rds}\\n") + seurat_obj <- readRDS("{seurat_object_with_age_rds}") + + cat("Running RunEpiTracePhylogeny...\\n") + phylo_result <- RunEpiTracePhylogeny( + object = seurat_obj, + ref_genome = "{ref_genome}", + group.by = {group_by_r}, + n_cores = {n_cores} + ) + + cat("Saving phylogenetic tree to {output_newick}\\n") + ape::write.tree(phylo_result, file = "{output_newick}") + + """ + + if output_plot_pdf: + r_script_content += f""" + cat("Generating and saving phylogeny plot to {output_plot_pdf}\\n") + pdf("{output_plot_pdf}", width = 10, height = 10) + plot(phylo_result, cex = 0.6) + dev.off() + """ + + r_script_content += "\ncat('Phylogeny analysis complete.\\n')\n" + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script: + r_script.write(r_script_content) + script_path = r_script.name + + command = ["Rscript", script_path] + command_str = " ".join(command) + output_files = [str(output_newick)] + if output_plot_pdf: + output_files.append(str(output_plot_pdf)) + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + Path(script_path).unlink() + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + Path(script_path).unlink() + raise RuntimeError( + f"RunEpiTracePhylogeny failed with exit code {e.returncode}.\n" + f"Command: {command_str}\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_raxml/app/raxml_server.py b/Biomni/mcp_generated/mcp_raxml/app/raxml_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a35fc2dcae65fdbe2e1808efae1f53eaf562476d --- /dev/null +++ b/Biomni/mcp_generated/mcp_raxml/app/raxml_server.py @@ -0,0 +1,290 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union +import os + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_raxml' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def raxml_rapid_bootstrap( + alignment_file: str, + model: str, + output_name: str, + parsimony_seed: int = 12345, + bootstrap_seed: int = 12345, + num_replicates: int = 100, + partition_file: Optional[str] = None, + outgroup: Optional[str] = None, + working_dir: Optional[str] = None, + threads: int = 1 +) -> dict: + """ + Perform a full analysis (Rapid Bootstrap + Best-scoring ML tree search) using the -f a algorithm. + + Args: + alignment_file: Path to the input alignment file (PHYLIP or FASTA). + model: Substitution model (e.g., GTRGAMMA, GTRCAT, PROTGAMMALG). + output_name: Suffix for output files. + parsimony_seed: Random number seed for the initial parsimony inferences. + bootstrap_seed: Random number seed for the rapid bootstrapping. + num_replicates: Number of bootstrap replicates or 'autoMRE' for convergence test. + partition_file: Optional path to a partition file. + outgroup: Name of the outgroup taxon (or comma-separated list). + working_dir: Directory to save output files. + threads: Number of threads to use (requires PTHREADS version of RAxML). + """ + # Input validation + align_path = Path(alignment_file) + if not align_path.exists(): + return {"error": f"Alignment file not found: {alignment_file}"} + + if parsimony_seed <= 0 or bootstrap_seed <= 0: + return {"error": "Seeds must be positive integers."} + + # Determine executable (prefer PTHREADS if threads > 1) + executable = "raxmlHPC-PTHREADS" if threads > 1 else "raxmlHPC" + + # Build command + cmd = [ + executable, + "-f", "a", + "-s", str(align_path), + "-m", model, + "-n", output_name, + "-p", str(parsimony_seed), + "-x", str(bootstrap_seed), + "-N", str(num_replicates) + ] + + if threads > 1: + cmd.extend(["-T", str(threads)]) + + if partition_file: + part_path = Path(partition_file) + if not part_path.exists(): + return {"error": f"Partition file not found: {partition_file}"} + cmd.extend(["-q", str(part_path)]) + + if outgroup: + cmd.extend(["-o", outgroup]) + + if working_dir: + work_path = Path(working_dir) + work_path.mkdir(parents=True, exist_ok=True) + cmd.extend(["-w", str(work_path.absolute())]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Identify output files based on suffix + output_prefix = working_dir if working_dir else "." + expected_files = list(Path(output_prefix).glob(f"RAxML_*{output_name}")) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(f) for f in expected_files] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": "RAxML execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + except FileNotFoundError: + return {"error": f"RAxML executable '{executable}' not found in PATH."} + +@mcp.tool() +def raxml_standard_ml( + alignment_file: str, + model: str, + output_name: str, + parsimony_seed: int = 12345, + num_runs: int = 10, + partition_file: Optional[str] = None, + starting_tree: Optional[str] = None, + threads: int = 1 +) -> dict: + """ + Perform a standard ML tree search (algorithm -f d). + + Args: + alignment_file: Path to the input alignment file. + model: Substitution model. + output_name: Suffix for output files. + parsimony_seed: Random number seed for parsimony. + num_runs: Number of ML searches from different starting trees. + partition_file: Optional path to a partition file. + starting_tree: Optional path to a user-defined starting tree (Newick). + threads: Number of threads to use. + """ + align_path = Path(alignment_file) + if not align_path.exists(): + return {"error": f"Alignment file not found: {alignment_file}"} + + executable = "raxmlHPC-PTHREADS" if threads > 1 else "raxmlHPC" + + cmd = [ + executable, + "-f", "d", + "-s", str(align_path), + "-m", model, + "-n", output_name, + "-p", str(parsimony_seed), + "-N", str(num_runs) + ] + + if threads > 1: + cmd.extend(["-T", str(threads)]) + if partition_file: + cmd.extend(["-q", partition_file]) + if starting_tree: + cmd.extend(["-t", starting_tree]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + expected_files = list(Path(".").glob(f"RAxML_*{output_name}")) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(f) for f in expected_files] + } + except subprocess.CalledProcessError as e: + return {"command_executed": " ".join(cmd), "error": e.stderr, "stdout": e.stdout} + +@mcp.tool() +def raxml_bootstrap_convergence( + bootstrap_trees_file: str, + output_name: str, + criterion: str = "autoMRE", + model: str = "GTRGAMMA" +) -> dict: + """ + Test for bootstrap convergence using the -f I algorithm. + + Args: + bootstrap_trees_file: Path to a file containing multiple bootstrap trees. + output_name: Suffix for output files. + criterion: Convergence criterion: 'autoMRE' (extended majority rule), 'autoMR', 'autoFC'. + model: A model must be specified even if not used for this algorithm. + """ + tree_path = Path(bootstrap_trees_file) + if not tree_path.exists(): + return {"error": f"Bootstrap trees file not found: {bootstrap_trees_file}"} + + if criterion not in ["autoMRE", "autoMR", "autoFC", "autoMRE_IGN"]: + return {"error": "Invalid criterion. Choose from autoMRE, autoMR, autoFC, autoMRE_IGN."} + + cmd = [ + "raxmlHPC", + "-f", "I", + "-z", str(tree_path), + "-m", model, + "-n", output_name, + "-I", criterion + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"command_executed": " ".join(cmd), "error": e.stderr} + +@mcp.tool() +def raxml_site_likelihoods( + alignment_file: str, + tree_file: str, + model: str, + output_name: str, + partition_file: Optional[str] = None +) -> dict: + """ + Compute per-site log likelihoods for a given tree (algorithm -f g). + + Args: + alignment_file: Path to the input alignment. + tree_file: Path to the tree file. + model: Substitution model. + output_name: Suffix for output files. + partition_file: Optional partition file. + """ + if not Path(alignment_file).exists() or not Path(tree_file).exists(): + return {"error": "Input alignment or tree file missing."} + + cmd = [ + "raxmlHPC", + "-f", "g", + "-s", alignment_file, + "-t", tree_file, + "-m", model, + "-n", output_name + ] + + if partition_file: + cmd.extend(["-q", partition_file]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + expected_files = list(Path(".").glob(f"RAxML_perSiteLLs.{output_name}")) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "output_files": [str(f) for f in expected_files] + } + except subprocess.CalledProcessError as e: + return {"command_executed": " ".join(cmd), "error": e.stderr} + +@mcp.tool() +def raxml_generic_custom( + alignment_file: str, + model: str, + output_name: str, + algorithm: str, + parsimony_seed: int = 12345, + extra_args: Optional[List[str]] = None +) -> dict: + """ + A generic wrapper for RAxML to handle advanced or less common flags. + + Args: + alignment_file: Path to the input alignment. + model: Substitution model. + output_name: Suffix for output files. + algorithm: The -f flag value (e.g., 'b', 'c', 'e'). + parsimony_seed: Random seed. + extra_args: List of additional command line arguments (e.g., ["-o", "Outgroup", "-c", "25"]). + """ + cmd = [ + "raxmlHPC", + "-f", algorithm, + "-s", alignment_file, + "-m", model, + "-n", output_name, + "-p", str(parsimony_seed) + ] + + if extra_args: + cmd.extend(extra_args) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return {"command_executed": " ".join(cmd), "error": e.stderr, "stdout": e.stdout} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_rgi/app/requirements.txt b/Biomni/mcp_generated/mcp_rgi/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_rgi/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_rgi/app/rgi_shim_server.py b/Biomni/mcp_generated/mcp_rgi/app/rgi_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4d5fc32e84228dc2948a02551344c7907e01dd1a --- /dev/null +++ b/Biomni/mcp_generated/mcp_rgi/app/rgi_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_rgi/app/rgi_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_rgi' + + +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_rgi/environment.yaml b/Biomni/mcp_generated/mcp_rgi/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..128d42b76a9ca8dfa3bb90e56ee6036ced81d271 --- /dev/null +++ b/Biomni/mcp_generated/mcp_rgi/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - rgi + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_rgi/requirements.txt b/Biomni/mcp_generated/mcp_rgi/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_rgi/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_salmon/requirements.txt b/Biomni/mcp_generated/mcp_salmon/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_salmon/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_scmidas/Dockerfile b/Biomni/mcp_generated/mcp_scmidas/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..32334eb5813b8a670dd1eee54aec4ab05cc5ece2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/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 scmidas via conda (e.g., from bioconda) +RUN conda install -c bioconda scmidas -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 scmidas_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/scmidas_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/scmidas_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scmidas/app/scmidas_server.py b/Biomni/mcp_generated/mcp_scmidas/app/scmidas_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9834dcf16d574bdd2c1ab3a26739452b421d60d0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/app/scmidas_server.py @@ -0,0 +1,257 @@ +import subprocess +from pathlib import Path +from typing import Optional, List + +# MCP decorator is assumed to be available in the execution environment. +# No import is needed as per the instructions. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_scmidas' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def scmidas_run( + input_file: Path, + outdir: Path, + k_clones: int, + min_cell_per_clone: int = 10, + min_mut_per_clone: int = 1, + min_vaf_for_var: float = 0.01, + max_missing_rate_for_var: float = 0.9, + n_run: int = 10, + n_jobs: int = 1, + seed: Optional[int] = None, + device: str = "cpu", + verbose: bool = False, +) -> dict: + """ + Run the scMIDAS algorithm to infer clonal architecture from mitochondrial mutations. + + This is the main function of the scMIDAS tool, performing the core analysis. + """ + # --- Input Validation --- + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if k_clones <= 0: + raise ValueError("k_clones must be a positive integer.") + if min_cell_per_clone < 0: + raise ValueError("min_cell_per_clone cannot be negative.") + if min_mut_per_clone < 0: + raise ValueError("min_mut_per_clone cannot be negative.") + if not (0.0 <= min_vaf_for_var <= 1.0): + raise ValueError("min_vaf_for_var must be between 0.0 and 1.0.") + if not (0.0 <= max_missing_rate_for_var <= 1.0): + raise ValueError("max_missing_rate_for_var must be between 0.0 and 1.0.") + if n_run <= 0: + raise ValueError("n_run must be a positive integer.") + if n_jobs <= 0: + raise ValueError("n_jobs must be a positive integer.") + if device not in ["cpu", "gpu"]: + raise ValueError("device must be either 'cpu' or 'gpu'.") + + # --- File Path Handling --- + outdir.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "scmidas", "run", + "--input_file", str(input_file), + "--outdir", str(outdir), + "--k_clones", str(k_clones), + "--min_cell_per_clone", str(min_cell_per_clone), + "--min_mut_per_clone", str(min_mut_per_clone), + "--min_vaf_for_var", str(min_vaf_for_var), + "--max_missing_rate_for_var", str(max_missing_rate_for_var), + "--n_run", str(n_run), + "--n_jobs", str(n_jobs), + "--device", device, + ] + + if seed is not None: + cmd.extend(["--seed", str(seed)]) + if verbose: + cmd.append("--verbose") + + command_executed = " ".join(cmd) + + # --- Subprocess Execution and Error Handling --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + + # --- Structured Result Return --- + output_files = [str(p) for p in outdir.rglob('*') if p.is_file()] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("scmidas command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + # Return structured error info if the command fails + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"scmidas run failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool() +def scmidas_simulate( + outdir: Path, + nsamples: int = 1, + ncells: int = 1000, + ngenes: int = 100, + nclones: int = 3, + mut_p: float = 0.1, + seed: Optional[int] = None, + verbose: bool = False, +) -> dict: + """ + Simulate single-cell data with mitochondrial mutations for testing scMIDAS. + """ + # --- Input Validation --- + if nsamples <= 0: + raise ValueError("nsamples must be a positive integer.") + if ncells <= 0: + raise ValueError("ncells must be a positive integer.") + if ngenes <= 0: + raise ValueError("ngenes must be a positive integer.") + if nclones <= 0: + raise ValueError("nclones must be a positive integer.") + if not (0.0 < mut_p < 1.0): + raise ValueError("mut_p (mutation probability) must be between 0.0 and 1.0.") + + # --- File Path Handling --- + outdir.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "scmidas", "simulate", + "--outdir", str(outdir), + "--nsamples", str(nsamples), + "--ncells", str(ncells), + "--ngenes", str(ngenes), + "--nclones", str(nclones), + "--mut_p", str(mut_p), + ] + + if seed is not None: + cmd.extend(["--seed", str(seed)]) + if verbose: + cmd.append("--verbose") + + command_executed = " ".join(cmd) + + # --- Subprocess Execution and Error Handling --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + + # --- Structured Result Return --- + output_files = [str(p) for p in outdir.rglob('*') if p.is_file()] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("scmidas command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"scmidas simulate failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool() +def scmidas_plot( + input_dir: Path, + outdir: Path, + plot_type: str, + dpi: int = 300, + format: str = "png", + verbose: bool = False, +) -> dict: + """ + Generate plots from the output of an scMIDAS run. + """ + # --- Input Validation --- + if not input_dir.is_dir(): + raise FileNotFoundError(f"Input directory not found: {input_dir}") + + # Assuming a set of valid plot types based on common bioinformatics tools + valid_plot_types = ["heatmap", "umap", "clone_tree", "vaf_dist", "all"] + if plot_type not in valid_plot_types: + raise ValueError(f"plot_type must be one of {valid_plot_types}") + + valid_formats = ["png", "pdf", "svg", "jpg"] + if format not in valid_formats: + raise ValueError(f"format must be one of {valid_formats}") + + if dpi <= 0: + raise ValueError("dpi must be a positive integer.") + + # --- File Path Handling --- + outdir.mkdir(parents=True, exist_ok=True) + + # --- Command Construction --- + cmd = [ + "scmidas", "plot", + "--input_dir", str(input_dir), + "--outdir", str(outdir), + "--plot_type", plot_type, + "--dpi", str(dpi), + "--format", format, + ] + + if verbose: + cmd.append("--verbose") + + command_executed = " ".join(cmd) + + # --- Subprocess Execution and Error Handling --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + + # --- Structured Result Return --- + output_files = [str(p) for p in outdir.glob(f'*.{format}')] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + raise RuntimeError("scmidas command not found. Please ensure it is installed and in your PATH.") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"scmidas plot failed with exit code {e.returncode}", + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_scmidas/app/scmidas_shim_server.py b/Biomni/mcp_generated/mcp_scmidas/app/scmidas_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4deca28323e09e6eff0af268d7867e38ac3c9239 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/app/scmidas_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_scmidas/app/scmidas_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_scmidas' + + +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_scmidas/docker-compose.yml b/Biomni/mcp_generated/mcp_scmidas/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7e2086390a11aa67e724c57768792fa9155819ec --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-scmidas: + build: . + image: mcp-scmidas:latest + container_name: mcp-scmidas + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=scmidas + 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_scmidas/environment.yaml b/Biomni/mcp_generated/mcp_scmidas/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6a7ef0a9b56319ba78af0110bf4ab384de5b0126 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - scmidas + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scmidas/requirements.txt b/Biomni/mcp_generated/mcp_scmidas/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scmidas/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_scstem/Dockerfile b/Biomni/mcp_generated/mcp_scstem/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1ab284b7e93ef6079a87c41b66fdedb016346b1e --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/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 scstem via conda (e.g., from bioconda) +RUN conda install -c bioconda scstem -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/scstem_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/scstem_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/scstem_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scstem/app/requirements.txt b/Biomni/mcp_generated/mcp_scstem/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b2560887e3cfc68166a999ccdab70d77d835972 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/app/requirements.txt @@ -0,0 +1,5 @@ +matplotlib==3.10.9 +pandas==3.0.2 +scanpy==1.12.1 +STEM==1.8.2 +torch==2.8.0 diff --git a/Biomni/mcp_generated/mcp_scstem/app/scstem_server.py b/Biomni/mcp_generated/mcp_scstem/app/scstem_server.py new file mode 100644 index 0000000000000000000000000000000000000000..53f8a4fefce501aa0974acd6115980a56ecd15b7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/app/scstem_server.py @@ -0,0 +1,246 @@ +import subprocess +import tempfile +import logging +from pathlib import Path +from typing import Optional, Dict, List + +# Set up logging +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + +# Mock mcp decorator for standalone execution +class mcp: + def tool(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + +# Since scstem is a Python library, we need to import its components. +# This code assumes that the scstem package is installed in the execution environment. +try: + import pandas as pd + import scanpy as sc + import torch + import torch.nn.functional as F + import matplotlib.pyplot as plt + from STEM.model import SOmodel + from STEM.utils import seed_all, all_coord +except ImportError as e: + log.error(f"Missing required Python packages. Please install scstem dependencies. Error: {e}") + # In a real MCP environment, this would prevent the tool from loading. + # For this script, we'll raise the error to halt execution. + raise + +class StemSettings: + """A helper class to mimic the 'setting' object used in the scstem documentation.""" + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + +@mcp.tool +def run_stem( + sc_counts: Path, + st_counts: Path, + st_coordinates: Path, + sc_metadata: Path, + epochs: int = 400, + learning_rate: float = 0.002, + sigma: float = 3.0, + alpha: float = 0.8, + mmd_batch_size: int = 1000, + disable_batch_norm: bool = False, + seed: int = 2022, + device: str = "cuda:0", + verbose: bool = True, +) -> Dict: + """ + Runs the STEM workflow to map single-cell and spatial transcriptomics data. + + This tool encapsulates the entire STEM process, from data loading and preprocessing + to model training and generation of embeddings and spatial mappings. + + Args: + sc_counts: Path to the single-cell counts CSV file (genes as columns, cells as rows). + st_counts: Path to the spatial transcriptomics counts CSV file (genes as columns, spots as rows). + st_coordinates: Path to the spatial coordinates CSV file for ST data. + sc_metadata: Path to the single-cell metadata CSV file. + epochs: Number of training epochs. + learning_rate: Learning rate for the optimizer. + sigma: The spatial variance parameter in the Gaussian function. The documentation + suggests using half of the ST spot adjacent distance for best performance. + alpha: Weight for the Maximum Mean Discrepancy (MMD) loss. + mmd_batch_size: Batch size for calculating MMD loss. + disable_batch_norm: If True, disables batch normalization in the model. + seed: Random seed for reproducibility. + device: The device to use for training (e.g., 'cpu', 'cuda:0'). + verbose: If True, enables verbose output during training. + + Returns: + A dictionary containing the execution details and paths to output files. + """ + # --- 1. Input Validation --- + if not sc_counts.is_file(): + raise FileNotFoundError(f"Single-cell counts file not found: {sc_counts}") + if not st_counts.is_file(): + raise FileNotFoundError(f"Spatial transcriptomics counts file not found: {st_counts}") + if not st_coordinates.is_file(): + raise FileNotFoundError(f"Spatial coordinates file not found: {st_coordinates}") + if not sc_metadata.is_file(): + raise FileNotFoundError(f"Single-cell metadata file not found: {sc_metadata}") + + if epochs <= 0: + raise ValueError("Number of epochs must be positive.") + if learning_rate <= 0: + raise ValueError("Learning rate must be positive.") + if not (0.0 <= alpha <= 1.0): + raise ValueError("Alpha (MMD loss weight) must be between 0.0 and 1.0.") + if not (device == "cpu" or device.startswith("cuda")): + raise ValueError("Device must be 'cpu' or a valid CUDA device string (e.g., 'cuda:0').") + if device.startswith("cuda") and not torch.cuda.is_available(): + log.warning(f"Device '{device}' was requested, but CUDA is not available. Falling back to 'cpu'.") + device = "cpu" + + with tempfile.TemporaryDirectory() as temp_dir: + output_dir = Path(temp_dir) + + # Define output file paths + st_sc_mapping_path = output_dir / "st_to_sc_mapping_matrix.csv" + sc_st_mapping_path = output_dir / "sc_to_st_mapping_matrix.csv" + sc_spatial_coords_path = output_dir / "reconstructed_sc_spatial_coordinates.csv" + loss_curve_path = output_dir / "loss_curve.png" + log_path = output_dir / "training.log" + + try: + # --- 2. Data Loading and Preprocessing --- + log.info("Loading and preprocessing data...") + # The documentation example transposes the data after reading. + # We assume the input format is cells/spots as rows, genes as columns. + scdata_df = pd.read_csv(sc_counts, index_col=0) + stdata_df = pd.read_csv(st_counts, index_col=0) + spcoor_df = pd.read_csv(st_coordinates, index_col=0) + scmetadata_df = pd.read_csv(sc_metadata, index_col=0) + + # Ensure gene sets are aligned + common_genes = scdata_df.columns.intersection(stdata_df.columns) + if len(common_genes) == 0: + raise ValueError("No common genes found between single-cell and spatial data.") + log.info(f"Found {len(common_genes)} common genes.") + scdata_df = scdata_df[common_genes] + stdata_df = stdata_df[common_genes] + + # Create AnnData objects + adata = sc.AnnData(scdata_df, obs=scmetadata_df) + stadata = sc.AnnData(stdata_df, obs=spcoor_df) + + # Normalization and log-scaling + sc.pp.normalize_total(adata) + sc.pp.log1p(adata) + scdata_processed = pd.DataFrame(adata.X, index=adata.obs_names, columns=adata.var_names) + + sc.pp.normalize_total(stadata) + sc.pp.log1p(stadata) + stdata_processed = pd.DataFrame(stadata.X, index=stadata.obs_names, columns=stadata.var_names) + + # Calculate dropout rate + sc.pp.calculate_qc_metrics(adata, percent_top=None, log1p=False, inplace=True) + sc.pp.calculate_qc_metrics(stadata, percent_top=None, log1p=False, inplace=True) + dp = 1 - adata.obs['n_genes_by_counts'].median() / stadata.obs['n_genes_by_counts'].median() + log.info(f"Calculated dropout rate (dp): {dp:.4f}") + + # --- 3. Model Configuration and Training --- + log.info("Configuring and training the STEM model...") + seed_all(seed) + + opt = StemSettings( + device=device, + outf=str(output_dir), + n_genes=len(common_genes), + no_bn=disable_batch_norm, + lr=learning_rate, + sigma=sigma, + alpha=alpha, + verbose=verbose, + mmdbatch=mmd_batch_size, + dp=dp + ) + + model = SOmodel(opt) + if device != "cpu": + model.togpu() + + loss_curve = model.train_wholedata( + epochs, + torch.tensor(scdata_processed.values).float(), + torch.tensor(stdata_processed.values).float(), + torch.tensor(spcoor_df.values).float() + ) + + # Plot and save loss curve + plt.figure() + plt.plot(loss_curve) + plt.title("Training Loss Curve") + plt.xlabel("Epoch") + plt.ylabel("Loss") + plt.grid(True) + plt.savefig(loss_curve_path) + plt.close() + + # --- 4. Generate Embeddings and Mappings --- + log.info("Generating embeddings and reconstructing spatial adjacency...") + model.modeleval() + + sc_tensor = torch.tensor(scdata_processed.values, dtype=torch.float32) + st_tensor = torch.tensor(stdata_processed.values, dtype=torch.float32) + + if device != "cpu": + sc_tensor = sc_tensor.cuda() + st_tensor = st_tensor.cuda() + + scembedding = model.netE(sc_tensor) + stembedding = model.netE(st_tensor) + + # Calculate mapping matrices + netst2sc = F.softmax(stembedding.mm(scembedding.t()), dim=1).detach().cpu().numpy() + netsc2st = F.softmax(scembedding.mm(stembedding.t()), dim=1).detach().cpu().numpy() + + # Get spatial coordinates for single cells + spatialDA = all_coord( + pd.DataFrame(netsc2st, index=adata.obs_names, columns=stadata.obs_names), + spcoor_df + ) + + # --- 5. Save Outputs --- + log.info(f"Saving output files to {output_dir}...") + pd.DataFrame(netst2sc, index=stadata.obs_names, columns=adata.obs_names).to_csv(st_sc_mapping_path) + pd.DataFrame(netsc2st, index=adata.obs_names, columns=stadata.obs_names).to_csv(sc_st_mapping_path) + pd.DataFrame(spatialDA, index=adata.obs_names, columns=['x_reconstructed', 'y_reconstructed']).to_csv(sc_spatial_coords_path) + + # Since this is a library call, stdout/stderr are not captured like a subprocess. + # We can simulate a command for logging purposes. + command_str = ( + f"run_stem(sc_counts='{sc_counts}', st_counts='{st_counts}', ..., " + f"epochs={epochs}, learning_rate={learning_rate}, sigma={sigma}, " + f"alpha={alpha}, seed={seed}, device='{device}')" + ) + + return { + "command_executed": command_str, + "stdout": "STEM library execution completed successfully. See logs for details.", + "stderr": "", + "output_files": { + "st_to_sc_mapping_matrix": str(st_sc_mapping_path), + "sc_to_st_mapping_matrix": str(sc_st_mapping_path), + "reconstructed_sc_coordinates": str(sc_spatial_coords_path), + "loss_curve_plot": str(loss_curve_path), + } + } + + except Exception as e: + log.error(f"An error occurred during STEM execution: {e}", exc_info=True) + # In case of an error, we still return a structured output + return { + "command_executed": "run_stem(...)", + "stdout": "", + "stderr": f"Error in STEM execution: {e}", + "output_files": {} + } diff --git a/Biomni/mcp_generated/mcp_scstem/app/scstem_shim_server.py b/Biomni/mcp_generated/mcp_scstem/app/scstem_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4b212c8a9530ea72368bf997d1127b13a3d92530 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/app/scstem_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_scstem/app/scstem_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_scstem' + + +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_scstem/docker-compose.yml b/Biomni/mcp_generated/mcp_scstem/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c72d3010639a96fa1e06020aa9ed49924ef0a308 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-scstem: + build: . + image: mcp-scstem:latest + container_name: mcp-scstem + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=scstem + 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_scstem/environment.yaml b/Biomni/mcp_generated/mcp_scstem/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cb54511cfb2960310fd1ba45508e124a15350378 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - scstem + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scstem/requirements.txt b/Biomni/mcp_generated/mcp_scstem/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..928ccbac2b21f63d3f1611a6953962bdfd6f59e1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scstem/requirements.txt @@ -0,0 +1,7 @@ +matplotlib==3.10.9 +pandas==3.0.2 +scanpy==1.12.1 +STEM==1.8.2 +torch==2.8.0 +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_seqcluster/app/requirements.txt b/Biomni/mcp_generated/mcp_seqcluster/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_server.py b/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_server.py new file mode 100644 index 0000000000000000000000000000000000000000..72ee9a47ea97d37a58cad0eca7d9d8633e3c4e08 --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_server.py @@ -0,0 +1,197 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Dict, Any + +# NOTE: The provided documentation for 'seqcluster' primarily consists of +# metadata and conda package information. It *does not* include the +# command-line interface (CLI) usage details, such as the output of +# `seqcluster --help` or specific subcommands and their parameters. +# +# As a result, this MCP tool implementation is based on *hypothetical* +# parameters and a generic command structure, inferred from the tool's +# summary ("small RNA analysis from NGS data") and common patterns in +# bioinformatics tools. +# +# A complete and accurate MCP tool would require parsing the actual +# CLI documentation to identify all subcommands, their specific parameters, +# types, default values, and constraints. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_seqcluster' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def seqcluster_process( + input_files: List[Path], + output_dir: Path = Path("./seqcluster_results"), + min_length: int = 18, + max_length: int = 25, + min_reads: int = 5, + reference_genome: Optional[Path] = None, + threads: int = 1, + dry_run: bool = False, + log_file: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Processes small RNA sequencing data using the seqcluster tool. + + NOTE: This function uses *hypothetical* parameters due to the absence + of actual command-line documentation for seqcluster in the provided + materials. The parameters are designed to be representative of common + small RNA analysis workflows. + + Args: + input_files: A list of paths to input FASTQ or FASTA files. + At least one file must be provided and exist. + output_dir: Directory to store all output files. Will be created if it doesn't exist. + min_length: Minimum length of small RNA sequences to consider for clustering/analysis. + Must be a positive integer. + max_length: Maximum length of small RNA sequences to consider for clustering/analysis. + Must be a positive integer and greater than or equal to min_length. + min_reads: Minimum number of reads required for a cluster or sequence to be reported. + Must be a positive integer. + reference_genome: Optional path to a reference genome FASTA file for alignment or annotation. + If provided, the file must exist. + threads: Number of CPU threads to use for processing. Must be a positive integer. + dry_run: If True, the command will be constructed and returned, but not executed. + log_file: Optional path to a file where stdout and stderr will be redirected. + verbose: If True, enable verbose output from the seqcluster tool. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + Includes an 'error' key if the command fails. + """ + # Input validation + if not input_files: + raise ValueError("At least one input file must be provided.") + for f in input_files: + if not isinstance(f, Path): + raise TypeError(f"Input file '{f}' must be a Path object.") + if not f.is_file(): + raise FileNotFoundError(f"Input file not found: {f}") + + if not isinstance(output_dir, Path): + raise TypeError("output_dir must be a Path object.") + if not isinstance(min_length, int) or min_length <= 0: + raise ValueError("min_length must be a positive integer.") + if not isinstance(max_length, int) or max_length <= 0: + raise ValueError("max_length must be a positive integer.") + if min_length > max_length: + raise ValueError("min_length cannot be greater than max_length.") + if not isinstance(min_reads, int) or min_reads <= 0: + raise ValueError("min_reads must be a positive integer.") + if not isinstance(threads, int) or threads <= 0: + raise ValueError("threads must be a positive integer.") + + if reference_genome: + if not isinstance(reference_genome, Path): + raise TypeError("reference_genome must be a Path object.") + if not reference_genome.is_file(): + raise FileNotFoundError(f"Reference genome FASTA file not found: {reference_genome}") + + if log_file and not isinstance(log_file, Path): + raise TypeError("log_file must be a Path object.") + + output_dir.mkdir(parents=True, exist_ok=True) + + command = ["seqcluster"] + # Hypothetical common parameters based on typical sRNA analysis tools + command.extend(["--input", *[str(f) for f in input_files]]) + command.extend(["--output-dir", str(output_dir)]) + command.extend(["--min-len", str(min_length)]) + command.extend(["--max-len", str(max_length)]) + command.extend(["--min-reads", str(min_reads)]) + command.extend(["--threads", str(threads)]) + + if reference_genome: + command.extend(["--reference", str(reference_genome)]) + if verbose: + command.append("--verbose") + + command_str = " ".join(command) + if dry_run: + return { + "command_executed": command_str, + "stdout": "Dry run: Command not executed.", + "stderr": "", + "output_files": [], + } + + stdout_data = "" + stderr_data = "" + output_files_generated: List[str] = [] + + try: + if log_file: + with open(log_file, "w") as log_f: + process = subprocess.run( + command, + stdout=log_f, + stderr=log_f, + text=True, + check=True + ) + stdout_data = f"Output redirected to {log_file}" + stderr_data = f"Errors redirected to {log_file}" + else: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout_data = process.stdout + stderr_data = process.stderr + + # Hypothetical output files based on common seqcluster outputs + # A real implementation would parse stdout/stderr or consult documentation + # to identify actual output files. + potential_output_files = [ + output_dir / "clusters.tsv", + output_dir / "summary.html", + output_dir / "report.pdf", + ] + if reference_genome: + potential_output_files.append(output_dir / "aligned_reads.bam") + + for f in potential_output_files: + if f.exists(): + output_files_generated.append(str(f)) + + return { + "command_executed": command_str, + "stdout": stdout_data, + "stderr": stderr_data, + "output_files": output_files_generated, + } + except subprocess.CalledProcessError as e: + # If log_file was used, stdout/stderr are in the log file, not e.stdout/e.stderr + if log_file: + with open(log_file, "r") as log_f: + log_content = log_f.read() + stdout_data = f"See log file: {log_file}" + stderr_data = f"See log file: {log_file}\nError details from log:\n{log_content}" + else: + stdout_data = e.stdout + stderr_data = e.stderr + + return { + "command_executed": command_str, + "stdout": stdout_data, + "stderr": stderr_data, + "error": f"seqcluster process failed with exit code {e.returncode}", + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'seqcluster' command not found. Is it installed and in your PATH?", + "error": "Tool not found", + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_shim_server.py b/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9e4b6241b506bcb4170002fc34927a1b0d75ccad --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/app/seqcluster_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_seqcluster/app/seqcluster_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_seqcluster' + + +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_seqcluster/docker-compose.yml b/Biomni/mcp_generated/mcp_seqcluster/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7d390b9e1fd3ea601888e051b88eb6e500a2ac3b --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-seqcluster: + build: . + image: mcp-seqcluster:latest + container_name: mcp-seqcluster + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=seqcluster + 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_seqcluster/environment.yaml b/Biomni/mcp_generated/mcp_seqcluster/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..40e7a4081608b00e258e4334356fab9782443ba4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - seqcluster + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_seqcluster/requirements.txt b/Biomni/mcp_generated/mcp_seqcluster/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_seqcluster/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_star-fusion/Dockerfile b/Biomni/mcp_generated/mcp_star-fusion/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..8c5964cd1bde1a7895636c7dfe7882a0ab970dfd --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/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 star-fusion via conda (e.g., from bioconda) +RUN conda install -c bioconda star-fusion -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/star-fusion_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/star-fusion_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/star-fusion_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_star-fusion/app/requirements.txt b/Biomni/mcp_generated/mcp_star-fusion/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_server.py b/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a71f9c0914da0e9506d227e0c3c57b27f64ac17c --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_server.py @@ -0,0 +1,243 @@ +from pathlib import Path +import subprocess +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_star_fusion' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def star_fusion_from_fastq( + genome_lib_dir: str, + left_fq: str, + output_dir: str, + right_fq: Optional[str] = None, + cpu: int = 4, + fusion_inspector: Optional[str] = None, + examine_coding_effect: bool = False, + min_ffpm: float = 0.1, + no_annotation_filter: bool = False, + max_promiscuity: int = 10, + min_junction_reads: int = 1, + min_spanning_frags: int = 1, + min_sum_frags: int = 2, +): + """ + Run STAR-Fusion starting from FASTQ files to identify candidate fusion transcripts. + + Args: + genome_lib_dir: Path to the CTAT resource library directory. + left_fq: Path to the left (or single-end) FASTQ file. + output_dir: Path to the output directory. + right_fq: Path to the right FASTQ file (optional, for paired-end reads). + cpu: Number of threads to use for STAR alignment. + fusion_inspector: Optional mode for FusionInspector (choices: 'inspect', 'validate'). + examine_coding_effect: If True, explores the impact of the fusion event on coding regions. + min_ffpm: Minimum fusion fragments per million total reads (default: 0.1). + no_annotation_filter: If True, disables annotation-based filtering. + max_promiscuity: Maximum number of partners allowed for a gene (default: 10). + min_junction_reads: Minimum number of junction-supporting reads (default: 1). + min_spanning_frags: Minimum number of spanning fragments (default: 1). + min_sum_frags: Minimum total support (junction + spanning) (default: 2). + """ + # Input validation + genome_path = Path(genome_lib_dir) + if not genome_path.is_dir(): + return {"error": f"genome_lib_dir not found: {genome_lib_dir}"} + + left_path = Path(left_fq) + if not left_path.exists(): + return {"error": f"left_fq file not found: {left_fq}"} + + out_path = Path(output_dir) + out_path.mkdir(parents=True, exist_ok=True) + + # Build command + cmd = [ + "STAR-Fusion", + "--genome_lib_dir", str(genome_path), + "--left_fq", str(left_path), + "--output_dir", str(out_path), + "--CPU", str(cpu), + "--min_FFPM", str(min_ffpm), + "--max_promiscuity", str(max_promiscuity), + "--min_junction_reads", str(min_junction_reads), + "--min_spanning_frags", str(min_spanning_frags), + "--min_sum_frags", str(min_sum_frags) + ] + + if right_fq: + right_path = Path(right_fq) + if not right_path.exists(): + return {"error": f"right_fq file not found: {right_fq}"} + cmd += ["--right_fq", str(right_path)] + + if fusion_inspector: + if fusion_inspector not in ["inspect", "validate"]: + return {"error": "fusion_inspector must be 'inspect' or 'validate'"} + cmd += ["--FusionInspector", fusion_inspector] + + if examine_coding_effect: + cmd.append("--examine_coding_effect") + + if no_annotation_filter: + cmd.append("--no_annotation_filter") + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + output_files = [ + str(out_path / "star-fusion.fusion_predictions.tsv"), + str(out_path / "star-fusion.fusion_predictions.abridged.tsv") + ] + if fusion_inspector: + output_files.append(str(out_path / "FusionInspector/")) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f for f in output_files if Path(f).exists()] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def star_fusion_kickstart( + genome_lib_dir: str, + chimeric_junction: str, + output_dir: str, + fusion_inspector: Optional[str] = None, + examine_coding_effect: bool = False, + min_ffpm: float = 0.1, + no_annotation_filter: bool = False, + max_promiscuity: int = 10, + min_junction_reads: int = 1, + min_spanning_frags: int = 1, + min_sum_frags: int = 2, +): + """ + Kickstart STAR-Fusion using an existing 'Chimeric.out.junction' file from a previous STAR run. + + Args: + genome_lib_dir: Path to the CTAT resource library directory. + chimeric_junction: Path to the Chimeric.out.junction file generated by STAR. + output_dir: Path to the output directory. + fusion_inspector: Optional mode for FusionInspector (choices: 'inspect', 'validate'). + examine_coding_effect: If True, explores the impact of the fusion event on coding regions. + min_ffpm: Minimum fusion fragments per million total reads (default: 0.1). + no_annotation_filter: If True, disables annotation-based filtering. + max_promiscuity: Maximum number of partners allowed for a gene (default: 10). + min_junction_reads: Minimum number of junction-supporting reads (default: 1). + min_spanning_frags: Minimum number of spanning fragments (default: 1). + min_sum_frags: Minimum total support (junction + spanning) (default: 2). + """ + # Input validation + genome_path = Path(genome_lib_dir) + if not genome_path.is_dir(): + return {"error": f"genome_lib_dir not found: {genome_lib_dir}"} + + junction_path = Path(chimeric_junction) + if not junction_path.exists(): + return {"error": f"Chimeric junction file not found: {chimeric_junction}"} + + out_path = Path(output_dir) + out_path.mkdir(parents=True, exist_ok=True) + + # Build command + cmd = [ + "STAR-Fusion", + "--genome_lib_dir", str(genome_path), + "-J", str(junction_path), + "--output_dir", str(out_path), + "--min_FFPM", str(min_ffpm), + "--max_promiscuity", str(max_promiscuity), + "--min_junction_reads", str(min_junction_reads), + "--min_spanning_frags", str(min_spanning_frags), + "--min_sum_frags", str(min_sum_frags) + ] + + if fusion_inspector: + if fusion_inspector not in ["inspect", "validate"]: + return {"error": "fusion_inspector must be 'inspect' or 'validate'"} + cmd += ["--FusionInspector", fusion_inspector] + + if examine_coding_effect: + cmd.append("--examine_coding_effect") + + if no_annotation_filter: + cmd.append("--no_annotation_filter") + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + output_files = [ + str(out_path / "star-fusion.fusion_predictions.tsv"), + str(out_path / "star-fusion.fusion_predictions.abridged.tsv") + ] + if fusion_inspector: + output_files.append(str(out_path / "FusionInspector/")) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f for f in output_files if Path(f).exists()] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def star_fusion_list_annotations( + genome_lib_dir: str, +): + """ + List the annotation filter rules and resources used by STAR-Fusion from the genome library. + + Args: + genome_lib_dir: Path to the CTAT resource library directory. + """ + genome_path = Path(genome_lib_dir) + pm_file = genome_path / "AnnotFilterRule.pm" + + if not pm_file.exists(): + return {"error": f"Annotation filter rules not found at {pm_file}"} + + try: + content = pm_file.read_text() + return { + "genome_lib_dir": str(genome_path), + "annotation_rules_path": str(pm_file), + "content_preview": content[:2000] + "..." if len(content) > 2000 else content + } + except Exception as e: + return {"error": f"Failed to read annotation rules: {str(e)}"} + +@mcp.tool() +def star_fusion_get_version(): + """ + Get the version information for STAR-Fusion. + """ + try: + # STAR-Fusion doesn't always have a simple --version, + # but we can try to run it without args or check the script + result = subprocess.run(["STAR-Fusion", "--version"], capture_output=True, text=True) + return { + "version_output": result.stdout.strip() or result.stderr.strip() + } + except Exception as e: + return {"error": f"Could not determine version: {str(e)}"} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_shim_server.py b/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..087756520d90e274a1b1c5f7fb7eacdab45bcf89 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/app/star-fusion_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_star-fusion/app/star-fusion_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_star_fusion' + + +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_star-fusion/docker-compose.yml b/Biomni/mcp_generated/mcp_star-fusion/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b6181183bbf9161664a7778ff7069c76c1e3be7d --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-star-fusion: + build: . + image: mcp-star-fusion:latest + container_name: mcp-star-fusion + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=star-fusion + 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_star-fusion/environment.yaml b/Biomni/mcp_generated/mcp_star-fusion/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..624ec48282b70fbddfbcf58b3a507a41593d3451 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - star-fusion + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_star-fusion/requirements.txt b/Biomni/mcp_generated/mcp_star-fusion/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star-fusion/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_star/Dockerfile b/Biomni/mcp_generated/mcp_star/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..efc2733db6f86fcc49921739e7fb9e8a54d63e7c --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/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 star via conda (e.g., from bioconda) +RUN conda install -c bioconda star -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/star_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/star_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/star_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_star/app/__pycache__/star_shim_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_star/app/__pycache__/star_shim_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f9f410eb6afc48edf796777f5dfeac7294335a7 Binary files /dev/null and b/Biomni/mcp_generated/mcp_star/app/__pycache__/star_shim_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_star/app/requirements.txt b/Biomni/mcp_generated/mcp_star/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_star/app/star_server.py b/Biomni/mcp_generated/mcp_star/app/star_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b1e4539082f3a68150ca31beb157ef5c1f58c671 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/app/star_server.py @@ -0,0 +1,238 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union +import tempfile + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_star' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def star_genome_generate( + genome_dir: str, + genome_fasta_files: List[str], + sjdb_gtf_file: Optional[str] = None, + sjdb_overhang: int = 100, + run_thread_n: int = 1, + genome_sa_index_nbases: int = 14, +): + """ + Generates genome indices for STAR alignment. + + Args: + genome_dir: Path to the directory where the genome indices are stored. + genome_fasta_files: List of paths to the genome FASTA files. + sjdb_gtf_file: Optional path to the annotation file (GTF/GFF). + sjdb_overhang: Length of the genomic sequence around the annotated junction (usually read_length - 1). + run_thread_n: Number of threads to use. + genome_sa_index_nbases: Length (bases) of the SA pre-indexing string. For small genomes, reduce this (e.g., min(14, log2(GenomeLength)/2 - 1)). + """ + # Input validation + genome_path = Path(genome_dir) + if not genome_path.exists(): + genome_path.mkdir(parents=True, exist_ok=True) + + for fasta in genome_fasta_files: + if not Path(fasta).exists(): + return {"error": f"Genome FASTA file not found: {fasta}"} + + cmd = [ + "STAR", + "--runMode", "genomeGenerate", + "--genomeDir", str(genome_path), + "--genomeFastaFiles" + ] + cmd.extend(genome_fasta_files) + cmd.extend(["--runThreadN", str(run_thread_n)]) + cmd.extend(["--sjdbOverhang", str(sjdb_overhang)]) + cmd.extend(["--genomeSAindexNbases", str(genome_sa_index_nbases)]) + + if sjdb_gtf_file: + gtf_path = Path(sjdb_gtf_file) + if not gtf_path.exists(): + return {"error": f"GTF file not found: {sjdb_gtf_file}"} + cmd.extend(["--sjdbGTFfile", str(gtf_path)]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(genome_path) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "STAR genome generation failed" + } + +@mcp.tool() +def star_align_reads( + genome_dir: str, + read_files_in: List[str], + out_file_name_prefix: str = "./Aligned_", + run_thread_n: int = 1, + read_files_command: Optional[str] = None, + out_sam_type: List[str] = ["BAM", "Unsorted"], + out_sam_unmapped: str = "None", + quant_mode: str = "-", + twopass_mode: str = "None", + out_filter_multimap_n_max: int = 10, + out_filter_mismatch_n_max: int = 999, + align_intron_min: int = 20, + align_intron_max: int = 1000000, + align_mates_gap_max: int = 1000000, +): + """ + Aligns RNA-seq reads to a reference genome using STAR. + + Args: + genome_dir: Path to the directory containing STAR genome indices. + read_files_in: List of paths to input read files (FASTQ). One file for single-end, two for paired-end. + out_file_name_prefix: Prefix for all output files. + run_thread_n: Number of threads to use. + read_files_command: Command to uncompress files (e.g., 'zcat' for .gz, 'bunzip2 -c' for .bz2). + out_sam_type: Type of SAM/BAM output. Options: 'SAM', 'BAM Unsorted', 'BAM SortedByCoordinate'. + out_sam_unmapped: Output unmapped reads. Options: 'None', 'Within', 'Within KeepPairs'. + quant_mode: Quantification mode. Options: '-', 'TranscriptomeSAM', 'GeneCounts'. + twopass_mode: 2-pass mapping mode. Options: 'None', 'Basic'. + out_filter_multimap_n_max: Maximum number of loci the read is allowed to map to. + out_filter_mismatch_n_max: Alignment will be output only if it has fewer mismatches than this. + align_intron_min: Minimum intron size. + align_intron_max: Maximum intron size. + align_mates_gap_max: Maximum gap between two mates. + """ + # Validation + genome_path = Path(genome_dir) + if not (genome_path / "Genome").exists(): + return {"error": f"Genome index not found in {genome_dir}. Run star_genome_generate first."} + + for read_file in read_files_in: + if not Path(read_file).exists(): + return {"error": f"Read file not found: {read_file}"} + + # Ensure output directory exists + out_prefix_path = Path(out_file_name_prefix) + out_prefix_path.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + "STAR", + "--runMode", "alignReads", + "--genomeDir", str(genome_path), + "--readFilesIn" + ] + cmd.extend(read_files_in) + cmd.extend(["--runThreadN", str(run_thread_n)]) + cmd.extend(["--outFileNamePrefix", out_file_name_prefix]) + cmd.extend(["--outSAMtype"]) + cmd.extend(out_sam_type) + cmd.extend(["--outSAMunmapped", out_sam_unmapped]) + cmd.extend(["--quantMode", quant_mode]) + cmd.extend(["--twopassMode", twopass_mode]) + cmd.extend(["--outFilterMultimapNmax", str(out_filter_multimap_n_max)]) + cmd.extend(["--outFilterMismatchNmax", str(out_filter_mismatch_n_max)]) + cmd.extend(["--alignIntronMin", str(align_intron_min)]) + cmd.extend(["--alignIntronMax", str(align_intron_max)]) + cmd.extend(["--alignMatesGapMax", str(align_mates_gap_max)]) + + if read_files_command: + cmd.extend(["--readFilesCommand", read_files_command]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Identify output files based on prefix + output_files = [str(f) for f in Path(out_file_name_prefix).parent.glob(f"{Path(out_file_name_prefix).name}*")] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "STAR alignment failed" + } + +@mcp.tool() +def star_lift_over( + genome_dir: str, + sjdb_file_chrs: List[str], + sjdb_gtf_file: str, + run_thread_n: int = 1, +): + """ + STAR internal tool for lifting over splice junctions. + + Args: + genome_dir: Path to the genome indices. + sjdb_file_chrs: Splice junction file. + sjdb_gtf_file: GTF file for lift over. + run_thread_n: Number of threads. + """ + if not Path(genome_dir).exists(): + return {"error": "Genome directory does not exist"} + + cmd = [ + "STAR", + "--runMode", "liftOver", + "--genomeDir", genome_dir, + "--sjdbFileChrs", + ] + cmd.extend(sjdb_file_chrs) + cmd.extend(["--sjdbGTFfile", sjdb_gtf_file]) + cmd.extend(["--runThreadN", str(run_thread_n)]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "STAR liftOver failed" + } + +@mcp.tool() +def star_generic_command( + args: List[str] +): + """ + Execute a STAR command with arbitrary arguments. + Use this for advanced parameters not covered by specific tools. + + Args: + args: List of command line arguments to pass to STAR (e.g., ["--runMode", "inputAlign", "--parameter", "value"]). + """ + cmd = ["STAR"] + args + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "STAR generic command failed" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_star/app/star_shim_server.py b/Biomni/mcp_generated/mcp_star/app/star_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ca283ce6abac358f1bb64e1a8e27f172b34837d0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/app/star_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_star/app/star_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_star' + + +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_star/docker-compose.yml b/Biomni/mcp_generated/mcp_star/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a11e7026f4b4e3969bbc2591b72b19c73ee2cba --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-star: + build: . + image: mcp-star:latest + container_name: mcp-star + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=star + 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_star/environment.yaml b/Biomni/mcp_generated/mcp_star/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7fa48494a6b532bb8478507e9b8467a4677314e0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_star/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - star + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_treetime/app/treetime_shim_server.py b/Biomni/mcp_generated/mcp_treetime/app/treetime_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4c3a2e5e2a5236f9e01e90fde2d355e860f928b0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/app/treetime_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_treetime/app/treetime_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_treetime' + + +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_treetime/docker-compose.yml b/Biomni/mcp_generated/mcp_treetime/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..517adc295ff45c96ce6cac198f2cf86d18645557 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-treetime: + build: . + image: mcp-treetime:latest + container_name: mcp-treetime + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=treetime + 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_trim-galore/Dockerfile b/Biomni/mcp_generated/mcp_trim-galore/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..38b4a6e2d38032f743dd189b2c72aeca4193fad5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/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 trim-galore via conda (e.g., from bioconda) +RUN conda install -c bioconda trim-galore -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/trim-galore_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/trim-galore_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/trim-galore_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_trim-galore/app/requirements.txt b/Biomni/mcp_generated/mcp_trim-galore/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_server.py b/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7b2a872e03e03efefb5bf458df672614e6676521 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_server.py @@ -0,0 +1,232 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union +import os + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_trim_galore' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def trim_galore( + input_files: List[str], + quality: int = 20, + phred33: bool = True, + phred64: bool = False, + fastqc: bool = False, + fastqc_args: Optional[str] = None, + adapter: Optional[str] = None, + adapter2: Optional[str] = None, + illumina: bool = False, + nextera: bool = False, + small_rna: bool = False, + cores: int = 1, + paired: bool = False, + retain_unpaired: bool = False, + length: int = 20, + max_length: Optional[int] = None, + output_dir: Optional[str] = None, + basename: Optional[str] = None, + gzip: bool = False, + dont_gzip: bool = False, + clip_R1: int = 0, + clip_R2: int = 0, + three_prime_clip_R1: int = 0, + three_prime_clip_R2: int = 0, + rrbs: bool = False, + non_directional: bool = False, + keep: bool = False, + trim1: bool = False, +): + """ + Trim Galore! is a wrapper script to automate quality and adapter trimming as well as quality control. + It uses Cutadapt for trimming and FastQC for quality control. + + Args: + input_files: List of FastQ files to process. If --paired is set, provide two files. + quality: Trim low-quality ends from reads in addition to adapter removal (default: 20). + phred33: Use ASCII+33 quality scores (Sanger/Illumina 1.8+ encoding). Default is True. + phred64: Use ASCII+64 quality scores (Illumina 1.5 encoding). + fastqc: Run FastQC in the end of the run. + fastqc_args: Extra arguments for FastQC (must be in quotes). + adapter: Adapter sequence to be trimmed. + adapter2: Adapter sequence to be trimmed from read 2 (paired-end only). + illumina: Use the first 13bp of the Illumina universal adapter 'AGATCGGAAGAGC'. + nextera: Use the first 12bp of the Nextera adapter 'CTGTCTCTTATA'. + small_rna: Use the first 12bp of the Illumina Small RNA adapter 'TGGAATTCTCGG'. + cores: Number of cores to use for cutadapt (default: 1). + paired: Perform paired-end trimming. + retain_unpaired: Retain unpaired reads if only one read of a pair survives trimming. + length: Discard reads that become shorter than X bp (default: 20). + max_length: Discard reads that are longer than X bp. + output_dir: Path to the output directory. + basename: Prefix for output files. + gzip: Compress the output file with gzip. + dont_gzip: Do not compress the output file. + clip_R1: Remove X bp from the 5' end of read 1. + clip_R2: Remove X bp from the 5' end of read 2. + three_prime_clip_R1: Remove X bp from the 3' end of read 1. + three_prime_clip_R2: Remove X bp from the 3' end of read 2. + rrbs: Specific for RRBS (Reduced Representation Bisulfite Sequencing). + non_directional: For non-directional RRBS libraries. + keep: Keep intermediate files. + trim1: Trims 1 bp off every read from the 3' end. + """ + # Input validation + if not input_files: + return {"error": "No input files provided."} + + for f in input_files: + if not Path(f).exists(): + return {"error": f"Input file {f} does not exist."} + + if paired and len(input_files) != 2: + return {"error": "Paired-end mode requires exactly two input files."} + + cmd = ["trim_galore"] + + # Quality and Phred + cmd.extend(["--quality", str(quality)]) + if phred64: + cmd.append("--phred64") + elif phred33: + cmd.append("--phred33") + + # FastQC + if fastqc: + cmd.append("--fastqc") + if fastqc_args: + cmd.extend(["--fastqc_args", fastqc_args]) + + # Adapters + if adapter: + cmd.extend(["--adapter", adapter]) + if adapter2: + cmd.extend(["--adapter2", adapter2]) + if illumina: + cmd.append("--illumina") + if nextera: + cmd.append("--nextera") + if small_rna: + cmd.append("--small_rna") + + # Performance + if cores > 1: + cmd.extend(["--cores", str(cores)]) + + # Paired-end + if paired: + cmd.append("--paired") + if retain_unpaired: + cmd.append("--retain_unpaired") + + # Length and Clipping + cmd.extend(["--length", str(length)]) + if max_length is not None: + cmd.extend(["--max_length", str(max_length)]) + + if clip_R1 > 0: + cmd.extend(["--clip_R1", str(clip_R1)]) + if clip_R2 > 0: + cmd.extend(["--clip_R2", str(clip_R2)]) + if three_prime_clip_R1 > 0: + cmd.extend(["--three_prime_clip_R1", str(three_prime_clip_R1)]) + if three_prime_clip_R2 > 0: + cmd.extend(["--three_prime_clip_R2", str(three_prime_clip_R2)]) + + # Output options + if output_dir: + out_path = Path(output_dir) + out_path.mkdir(parents=True, exist_ok=True) + cmd.extend(["--output_dir", str(out_path)]) + + if basename: + cmd.extend(["--basename", basename]) + + if gzip: + cmd.append("--gzip") + if dont_gzip: + cmd.append("--dont_gzip") + + # RRBS + if rrbs: + cmd.append("--rrbs") + if non_directional: + cmd.append("--non_directional") + + # Misc + if keep: + cmd.append("--keep") + if trim1: + cmd.append("--trim1") + + # Add input files at the end + cmd.extend(input_files) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Determine output files (heuristic based on trim-galore naming conventions) + output_files = [] + search_dir = Path(output_dir) if output_dir else Path.cwd() + # Trim Galore usually appends _trimmed.fq or _val_1.fq + for ext in ["*_trimmed.fq*", "*_val_*.fq*", "*.txt", "*.html", "*.zip"]: + output_files.extend([str(p) for p in search_dir.glob(ext)]) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": list(set(output_files)) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Trim Galore failed with exit code {e.returncode}" + } + +@mcp.tool() +def trim_galore_version(): + """ + Check the version of Trim Galore! and its dependencies (Cutadapt and FastQC). + """ + try: + result = subprocess.run(["trim_galore", "--version"], capture_output=True, text=True, check=True) + return { + "command_executed": "trim_galore --version", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "trim_galore --version", + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to retrieve version information." + } + +@mcp.tool() +def trim_galore_help(): + """ + Display the full help documentation for Trim Galore!. + """ + try: + result = subprocess.run(["trim_galore", "--help"], capture_output=True, text=True, check=True) + return { + "command_executed": "trim_galore --help", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "trim_galore --help", + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to retrieve help documentation." + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_shim_server.py b/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5b691fd4a78fc740c72d0df5b4419778704f40d6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/app/trim-galore_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_trim-galore/app/trim-galore_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_trim_galore' + + +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_trim-galore/docker-compose.yml b/Biomni/mcp_generated/mcp_trim-galore/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c38f83eb3b49e5666d9d536f45dd7c8b3e63e7b4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-trim-galore: + build: . + image: mcp-trim-galore:latest + container_name: mcp-trim-galore + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=trim-galore + 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_trim-galore/environment.yaml b/Biomni/mcp_generated/mcp_trim-galore/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c57800aec563c9a0e5cf27abf5e21f76a60580c3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - trim-galore + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_trim-galore/requirements.txt b/Biomni/mcp_generated/mcp_trim-galore/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trim-galore/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_server.py b/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cf3fa8d9cc5981d56b6e5171ebb0d0ea407ed22f --- /dev/null +++ b/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_server.py @@ -0,0 +1,159 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_ucsc_liftover' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def ucsc_liftover( + old_file: str, + chain_file: str, + new_file: str, + unmapped_file: str, + min_match: float = 0.95, + gff: bool = False, + gene_pred: bool = False, + sample: bool = False, + bed_plus: Optional[int] = None, + positions: bool = False, + multiple: bool = False, + min_blocks: float = 1.0, + fudge_thick: bool = False, + min_chain_t: Optional[int] = None, + min_chain_q: Optional[int] = None, + tab: bool = False, + ends: Optional[int] = None, + swap: bool = False, +) -> Dict[str, Any]: + """ + Move annotations from one genome assembly to another using the UCSC liftOver tool. + + Args: + old_file: Path to the input file to be lifted over (e.g., BED format). + chain_file: Path to the UCSC chain file (mapping between assemblies). + new_file: Path to the output file for successfully mapped regions. + unmapped_file: Path to the output file for regions that could not be mapped. + min_match: Minimum ratio of bases that must remap. Default is 0.95. + gff: Input is in GFF format. + gene_pred: Input is in genePred format. + sample: Input is in sample format. + bed_plus: Input is BED N plus other fields (e.g., 4, 6, 12). + positions: Input is in chrom:start-end format. + multiple: Allow multiple output regions for a single input region. + min_blocks: Minimum ratio of alignment blocks or exons that must be mapped. Default 1.00. + fudge_thick: If thickStart/thickEnd is not mapped, use the closest mapped base. + min_chain_t: Minimum chain score for target. + min_chain_q: Minimum chain score for query. + tab: Input is tab-separated. + ends: Lift over only N bases at each end. + swap: Swap query and target in the chain file. + """ + # Input validation + old_path = Path(old_file) + chain_path = Path(chain_file) + + if not old_path.exists(): + return {"error": f"Input file not found: {old_file}"} + if not chain_path.exists(): + return {"error": f"Chain file not found: {chain_file}"} + + if not (0.0 <= min_match <= 1.0): + return {"error": "min_match must be between 0.0 and 1.0"} + + if not (0.0 <= min_blocks <= 1.0): + return {"error": "min_blocks must be between 0.0 and 1.0"} + + # Construct command + cmd = ["liftOver"] + + # Add optional flags/parameters + cmd.append(f"-minMatch={min_match}") + cmd.append(f"-minBlocks={min_blocks}") + + if gff: + cmd.append("-gff") + if gene_pred: + cmd.append("-genePred") + if sample: + cmd.append("-sample") + if bed_plus is not None: + cmd.append(f"-bedPlus={bed_plus}") + if positions: + cmd.append("-positions") + if multiple: + cmd.append("-multiple") + if fudge_thick: + cmd.append("-fudgeThick") + if min_chain_t is not None: + cmd.append(f"-minChainT={min_chain_t}") + if min_chain_q is not None: + cmd.append(f"-minChainQ={min_chain_q}") + if tab: + cmd.append("-tab") + if ends is not None: + cmd.append(f"-ends={ends}") + if swap: + cmd.append("-swap") + + # Positional arguments + cmd.extend([str(old_path), str(chain_path), new_file, unmapped_file]) + + try: + # Execute the tool + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [new_file, unmapped_file] + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": "liftOver execution failed", + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode + } + except Exception as e: + return { + "command_executed": " ".join(cmd), + "error": str(e) + } + +@mcp.tool() +def ucsc_liftover_help() -> Dict[str, Any]: + """ + Display the help message for the UCSC liftOver tool to see all available options. + """ + try: + # liftOver usually shows help when run without arguments + result = subprocess.run( + ["liftOver"], + capture_output=True, + text=True, + check=False # It returns non-zero when showing help usually + ) + + return { + "command_executed": "liftOver", + "stdout": result.stdout, + "stderr": result.stderr + } + except Exception as e: + return { + "error": f"Could not retrieve help: {str(e)}" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_shim_server.py b/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..c9469ca6c84c7ab8a21b0a116ca9a329cd45dfd8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ucsc-liftover/app/ucsc-liftover_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_ucsc-liftover/app/ucsc-liftover_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_ucsc_liftover' + + +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_unicycler/Dockerfile b/Biomni/mcp_generated/mcp_unicycler/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6f00fdd1f19066d612b638094a0543d346e9eab3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_unicycler/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 unicycler via conda (e.g., from bioconda) +RUN conda install -c bioconda unicycler -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/unicycler_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/unicycler_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/unicycler_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_unicycler/app/requirements.txt b/Biomni/mcp_generated/mcp_unicycler/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_unicycler/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_unicycler/app/unicycler_server.py b/Biomni/mcp_generated/mcp_unicycler/app/unicycler_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a3223b111ff4df4302abc4a829e890e863b3e1c7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_unicycler/app/unicycler_server.py @@ -0,0 +1,195 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_unicycler' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def unicycler( + out: str, + short1: Optional[str] = None, + short2: Optional[str] = None, + unpaired: Optional[str] = None, + long: Optional[str] = None, + verbosity: int = 1, + min_fasta_length: int = 100, + keep: int = 1, + threads: int = 8, + mode: str = "normal", + linear_seqs: int = 0, +) -> Dict[str, Any]: + """ + Hybrid assembly pipeline for bacterial genomes. + Unicycler can assemble Illumina-only, long-read-only, or hybrid read sets. + + Args: + out: Output directory (required). + short1: FASTQ file of first short reads in each pair. + short2: FASTQ file of second short reads in each pair. + unpaired: FASTQ file of unpaired short reads. + long: FASTQ or FASTA file of long reads. + verbosity: Level of stdout and log file information (0=none, 1=basic, 2=extra, 3=debug). Default: 1. + min_fasta_length: Exclude contigs shorter than this length from the final FASTA. Default: 100. + keep: Level of file retention (0=final only, 1=graphs, 2=SAM, 3=all temp). Default: 1. + threads: Number of threads to use. Default: 8. + mode: Bridging mode (conservative, normal, bold). Default: normal. + linear_seqs: Expected number of linear (non-circular) sequences. Default: 0. + """ + # Input validation + if not out: + return {"error": "Output directory (--out) is required."} + + if not any([short1, unpaired, long]): + return {"error": "At least one input read set (short1/2, unpaired, or long) must be provided."} + + if short1 and not short2: + # Unicycler can handle this, but usually paired reads come in pairs. + # We'll allow it but it's worth noting. + pass + + # Validate bridging mode + valid_modes = ["conservative", "normal", "bold"] + if mode not in valid_modes: + return {"error": f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}"} + + # Validate numeric ranges + if not (0 <= verbosity <= 3): + return {"error": "Verbosity must be between 0 and 3."} + if not (0 <= keep <= 3): + return {"error": "Keep level must be between 0 and 3."} + if threads < 1: + return {"error": "Threads must be at least 1."} + if min_fasta_length < 0: + return {"error": "min_fasta_length cannot be negative."} + if linear_seqs < 0: + return {"error": "linear_seqs cannot be negative."} + + # File path handling + input_files = { + "--short1": short1, + "--short2": short2, + "--unpaired": unpaired, + "--long": long + } + + for flag, path_str in input_files.items(): + if path_str: + path_obj = Path(path_str) + if not path_obj.exists(): + return {"error": f"Input file for {flag} not found: {path_str}"} + + # Construct command + cmd = ["unicycler", "--out", out] + + if short1: + cmd.extend(["--short1", short1]) + if short2: + cmd.extend(["--short2", short2]) + if unpaired: + cmd.extend(["--unpaired", unpaired]) + if long: + cmd.extend(["--long", long]) + + cmd.extend(["--verbosity", str(verbosity)]) + cmd.extend(["--min_fasta_length", str(min_fasta_length)]) + cmd.extend(["--keep", str(keep)]) + cmd.extend(["--threads", str(threads)]) + cmd.extend(["--mode", mode]) + cmd.extend(["--linear_seqs", str(linear_seqs)]) + + # Subprocess execution + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + # Identify output files + output_path = Path(out) + output_files = [] + if output_path.exists(): + output_files = [str(p) for p in output_path.rglob("*") if p.is_file()] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + "status": "success" + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Unicycler failed with return code {e.returncode}", + "status": "error" + } + except Exception as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "status": "error" + } + + +@mcp.tool() +def unicycler_version() -> Dict[str, Any]: + """ + Show Unicycler's version number. + """ + try: + result = subprocess.run( + ["unicycler", "--version"], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "unicycler --version", + "stdout": result.stdout.strip(), + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "unicycler --version", + "error": f"Failed to get version: {e.stderr}", + "status": "error" + } + + +@mcp.tool() +def unicycler_help_all() -> Dict[str, Any]: + """ + Show a help message with all program options for Unicycler. + """ + try: + result = subprocess.run( + ["unicycler", "--help_all"], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "unicycler --help_all", + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "unicycler --help_all", + "error": f"Failed to get full help: {e.stderr}", + "status": "error" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_unicycler/app/unicycler_shim_server.py b/Biomni/mcp_generated/mcp_unicycler/app/unicycler_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..86bfc572d88ed27c0e41a22fe445a91d3f30587b --- /dev/null +++ b/Biomni/mcp_generated/mcp_unicycler/app/unicycler_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_unicycler/app/unicycler_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_unicycler' + + +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")