diff --git a/Biomni/mcp_generated/mcp_bcftools/Dockerfile b/Biomni/mcp_generated/mcp_bcftools/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..075aef4a34315764c86518d4f68189f8f3484117 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/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 bcftools via conda (e.g., from bioconda) +RUN conda install -c bioconda bcftools -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 bcftools_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/bcftools_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/bcftools_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_shim_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_shim_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea75805daee3d2ec0ec3922e1453997e78c5d0b3 Binary files /dev/null and b/Biomni/mcp_generated/mcp_bcftools/app/__pycache__/bcftools_shim_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_bcftools/app/bcftools_server.py b/Biomni/mcp_generated/mcp_bcftools/app/bcftools_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ddf9cf9400a523975dd14982e747ebc8c8ffc472 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/app/bcftools_server.py @@ -0,0 +1,866 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# Note: The @mcp.tool decorator is not defined here. +# It is assumed to be provided by the Model Context Protocol (MCP) framework. +# This code is designed to be used within that framework. + +# Helper function to handle subprocess execution and error reporting +def _run_bcftools_command(cmd: List[str], output_file: Optional[Path] = None): + """ + A helper function to execute a bcftools command, handle errors, + and return a structured dictionary. + """ + command_str = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + output_files = [str(output_file)] if output_file and output_file.exists() else [] + + # For commands that create index files automatically + if output_file and str(output_file).endswith((".bcf", ".vcf.gz")): + index_extensions = [".csi", ".tbi"] + for ext in index_extensions: + index_file = Path(str(output_file) + ext) + if index_file.exists(): + output_files.append(str(index_file)) + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + "return_code": 0, + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'bcftools' command not found. Please ensure it is in your PATH.", + "output_files": [], + "return_code": 1, + "error": "FileNotFoundError" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "return_code": e.returncode, + "error": "CalledProcessError" + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bcftools' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def bcftools_annotate( + input_file: Path, + annotations: Optional[Path] = None, + columns: Optional[str] = None, + header_lines: Optional[Path] = None, + output: Optional[Path] = None, + output_type: str = "v", + remove: Optional[str] = None, + set_id: Optional[str] = None, + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + targets: Optional[str] = None, + targets_file: Optional[Path] = None, + include: Optional[str] = None, + exclude: Optional[str] = None, + samples: Optional[str] = None, + samples_file: Optional[Path] = None, + threads: int = 1, + rename_chrs: Optional[Path] = None, + no_version: bool = False, + single_overlaps: bool = False, + mark_sites: Optional[str] = None, +): + """ + Annotate VCF/BCF files with information from other files. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if annotations and not annotations.exists(): + raise FileNotFoundError(f"Annotations file not found: {annotations}") + if header_lines and not header_lines.exists(): + raise FileNotFoundError(f"Header lines file not found: {header_lines}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "annotate", str(input_file)] + + if annotations: + cmd.extend(["-a", str(annotations)]) + if columns: + cmd.extend(["-c", columns]) + if header_lines: + cmd.extend(["-h", str(header_lines)]) + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if remove: + cmd.extend(["-x", remove]) + if set_id: + cmd.extend(["--set-id", set_id]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if targets: + cmd.extend(["-t", targets]) + if targets_file: + cmd.extend(["-T", str(targets_file)]) + if include: + cmd.extend(["-i", include]) + if exclude: + cmd.extend(["-e", exclude]) + if samples: + cmd.extend(["-s", samples]) + if samples_file: + cmd.extend(["-S", str(samples_file)]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + if rename_chrs: + cmd.extend(["--rename-chrs", str(rename_chrs)]) + if no_version: + cmd.append("--no-version") + if single_overlaps: + cmd.append("--single-overlaps") + if mark_sites: + cmd.extend(["--mark-sites", mark_sites]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_call( + input_file: Path, + output: Optional[Path] = None, + output_type: str = "v", + ploidy: Optional[str] = None, + ploidy_file: Optional[Path] = None, + call_method: str = "multiallelic", + constrain_alleles: bool = False, + group_samples: Optional[str] = None, + samples: Optional[str] = None, + samples_file: Optional[Path] = None, + format_fields: Optional[str] = None, + keep_alts: bool = False, + keep_masked_ref: bool = False, + skip_variants: bool = False, + threads: int = 1, +): + """ + Call genotypes from likelihoods. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + if call_method not in ["consensus", "multiallelic"]: + raise ValueError("call_method must be 'consensus' or 'multiallelic'.") + + cmd = ["bcftools", "call", str(input_file)] + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if ploidy: + cmd.extend(["-p", ploidy]) + if ploidy_file: + cmd.extend(["-P", str(ploidy_file)]) + if call_method == "consensus": + cmd.append("-c") + else: # multiallelic is default, but -m is explicit + cmd.append("-m") + if constrain_alleles: + cmd.append("-C alleles") + if group_samples: + cmd.extend(["-G", group_samples]) + if samples: + cmd.extend(["-s", samples]) + if samples_file: + cmd.extend(["-S", str(samples_file)]) + if format_fields: + cmd.extend(["-f", format_fields]) + if keep_alts: + cmd.append("-A") + if keep_masked_ref: + cmd.append("-M") + if skip_variants: + cmd.append("-V") + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_concat( + input_files: List[Path], + output: Optional[Path] = None, + output_type: str = "v", + allow_overlaps: bool = False, + remove_duplicates: bool = False, + naive: bool = False, + threads: int = 1, +): + """ + Concatenate VCF/BCF files from non-overlapping regions. + """ + 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 output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "concat"] + + if allow_overlaps: + cmd.append("-a") + if remove_duplicates: + cmd.append("-d") + if naive: + cmd.append("-n") + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + cmd.extend([str(f) for f in input_files]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_consensus( + input_file: Path, + fasta_ref: Path, + output: Optional[Path] = None, + chain: Optional[Path] = None, + haplotype: Optional[str] = None, + iupac_codes: bool = False, + mark_del: Optional[str] = None, + mark_ins: Optional[str] = None, + mark_snv: Optional[str] = None, + missing: Optional[str] = None, + sample: Optional[str] = None, + include: Optional[str] = None, + exclude: Optional[str] = None, +): + """ + Create a consensus sequence by applying VCF variants to a reference FASTA file. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input VCF/BCF file not found: {input_file}") + if not fasta_ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {fasta_ref}") + if haplotype and haplotype not in ["1", "2", "R", "A", "IUPAC", "all"]: + raise ValueError("haplotype must be one of '1', '2', 'R', 'A', 'IUPAC', 'all'.") + + cmd = ["bcftools", "consensus", str(input_file)] + cmd.extend(["-f", str(fasta_ref)]) + + if output: + cmd.extend(["-o", str(output)]) + if chain: + cmd.extend(["-c", str(chain)]) + if haplotype: + cmd.extend(["-H", haplotype]) + if iupac_codes: + cmd.append("-i") + if mark_del: + cmd.extend(["--mark-del", mark_del]) + if mark_ins: + cmd.extend(["--mark-ins", mark_ins]) + if mark_snv: + cmd.extend(["--mark-snv", mark_snv]) + if missing: + cmd.extend(["-m", missing]) + if sample: + cmd.extend(["-s", sample]) + if include: + cmd.extend(["--include", include]) + if exclude: + cmd.extend(["--exclude", exclude]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_filter( + input_file: Path, + output: Optional[Path] = None, + output_type: str = "v", + include: Optional[str] = None, + exclude: Optional[str] = None, + soft_filter: Optional[str] = None, + set_gt: Optional[str] = None, + mode: str = "+", + threads: int = 1, +): + """ + Apply filters to VCF/BCF files. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + if mode not in ["+", "x", "X"]: + raise ValueError("mode must be one of '+', 'x', 'X'.") + + cmd = ["bcftools", "filter", str(input_file)] + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if include: + cmd.extend(["-i", include]) + if exclude: + cmd.extend(["-e", exclude]) + if soft_filter: + cmd.extend(["-s", soft_filter]) + if set_gt: + cmd.extend(["-S", set_gt]) + if mode != "+": + cmd.extend(["-m", mode]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_index( + input_file: Path, + force: bool = False, + stats: bool = False, + threads: int = 1, + csi: bool = False, + tbi: bool = False, + min_csi_shift: int = 14, +): + """ + Index VCF/BCF files for fast random access. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if csi and tbi: + raise ValueError("Cannot specify both --csi (-c) and --tbi (-t).") + + cmd = ["bcftools", "index"] + + if force: + cmd.append("-f") + if stats: + cmd.append("-n") + if threads > 1: + cmd.extend(["--threads", str(threads)]) + if csi: + cmd.append("-c") + if tbi: + cmd.append("-t") + if csi and min_csi_shift != 14: + cmd.extend(["-m", str(min_csi_shift)]) + + cmd.append(str(input_file)) + + # The output file is the index file itself + output_idx_file = None + if csi: + output_idx_file = Path(str(input_file) + ".csi") + else: # default is tbi for vcf.gz, csi for bcf + if str(input_file).endswith(".bcf"): + output_idx_file = Path(str(input_file) + ".csi") + else: + output_idx_file = Path(str(input_file) + ".tbi") + + return _run_bcftools_command(cmd, output_idx_file) + +@mcp.tool() +def bcftools_isec( + input_files: List[Path], + output_dir: Path, + output_type: str = "v", + complement: bool = False, + collapse: Optional[str] = None, + exclude: Optional[str] = None, + include: Optional[str] = None, + n_sites: Optional[str] = None, + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + targets: Optional[str] = None, + targets_file: Optional[Path] = None, + threads: int = 1, +): + """ + Create intersections, unions, and complements of VCF/BCF files. + """ + if len(input_files) < 2: + raise ValueError("At least two input files must be provided for isec.") + for f in input_files: + if not f.exists(): + raise FileNotFoundError(f"Input file not found: {f}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + if collapse and collapse not in ["none", "all", "snps", "indels", "both", "any"]: + raise ValueError("Invalid value for collapse.") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = ["bcftools", "isec"] + cmd.extend(["-p", str(output_dir)]) + cmd.extend(["-O", output_type]) + + if complement: + cmd.append("-c") + if collapse: + cmd.extend(["--collapse", collapse]) + if exclude: + cmd.extend(["-e", exclude]) + if include: + cmd.extend(["-i", include]) + if n_sites: + cmd.extend(["-n", n_sites]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if targets: + cmd.extend(["-t", targets]) + if targets_file: + cmd.extend(["-T", str(targets_file)]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + cmd.extend([str(f) for f in input_files]) + + # isec doesn't have a single output file, it creates a directory + # We will return the directory path in a structured way if needed, + # but for now, we'll rely on the user knowing the output is a directory. + # The _run_bcftools_command helper won't find a single output file. + result = _run_bcftools_command(cmd) + + # Manually list created files + if result["return_code"] == 0: + created_files = [str(p) for p in output_dir.glob('*')] + result["output_files"] = created_files + + return result + +@mcp.tool() +def bcftools_merge( + input_files: List[Path], + output: Optional[Path] = None, + output_type: str = "v", + info_rules: Optional[str] = None, + merge_logic: Optional[str] = None, + threads: int = 1, +): + """ + Merge multiple VCF/BCF files from different samples into a single file. + """ + 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 output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "merge"] + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if info_rules: + cmd.extend(["--info-rules", info_rules]) + if merge_logic: + cmd.extend(["-m", merge_logic]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + cmd.extend([str(f) for f in input_files]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_mpileup( + input_bams: List[Path], + fasta_ref: Path, + output: Optional[Path] = None, + output_type: str = "v", + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + annotate: Optional[str] = None, + max_depth: int = 250, + min_base_quality: int = 13, + min_mapping_quality: int = 0, + adjust_mq: int = 50, + no_baq: bool = False, + threads: int = 1, +): + """ + Generate VCF/BCF from alignment files (BAM/CRAM). + """ + if not input_bams: + raise ValueError("At least one input BAM/CRAM file must be provided.") + for f in input_bams: + if not f.exists(): + raise FileNotFoundError(f"Input alignment file not found: {f}") + if not fasta_ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {fasta_ref}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "mpileup"] + cmd.extend(["-f", str(fasta_ref)]) + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if annotate: + cmd.extend(["-a", annotate]) + if max_depth != 250: + cmd.extend(["-d", str(max_depth)]) + if min_base_quality != 13: + cmd.extend(["-q", str(min_base_quality)]) + if min_mapping_quality != 0: + cmd.extend(["-Q", str(min_mapping_quality)]) + if adjust_mq != 50: + cmd.extend(["-C", str(adjust_mq)]) + if no_baq: + cmd.append("-B") + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + cmd.extend([str(f) for f in input_bams]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_norm( + input_file: Path, + fasta_ref: Path, + output: Optional[Path] = None, + output_type: str = "v", + check_ref: str = "w", + multiallelics: Optional[str] = None, + atomize: bool = False, + rm_dup: Optional[str] = None, + threads: int = 1, +): + """ + Left-align and normalize indels, check REF, split multiallelic sites. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not fasta_ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {fasta_ref}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + if check_ref not in ["w", "e", "x", "s"]: + raise ValueError("check_ref must be one of 'w', 'e', 'x', 's'.") + + cmd = ["bcftools", "norm", str(input_file)] + cmd.extend(["-f", str(fasta_ref)]) + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if check_ref != "w": + cmd.extend(["-c", check_ref]) + if multiallelics: + cmd.extend(["-m", multiallelics]) + if atomize: + cmd.append("-a") + if rm_dup: + cmd.extend(["-d", rm_dup]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_query( + input_file: Path, + format_string: str, + output: Optional[Path] = None, + include: Optional[str] = None, + exclude: Optional[str] = None, + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + samples: Optional[str] = None, + samples_file: Optional[Path] = None, + list_samples: bool = False, +): + """ + Extracts fields from VCF/BCF files and prints them in a user-defined format. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + cmd = ["bcftools", "query", str(input_file)] + + if list_samples: + cmd.append("-l") + else: + cmd.extend(["-f", format_string]) + + if output: + cmd.extend(["-o", str(output)]) + if include: + cmd.extend(["-i", include]) + if exclude: + cmd.extend(["-e", exclude]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if samples: + cmd.extend(["-s", samples]) + if samples_file: + cmd.extend(["-S", str(samples_file)]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_reheader( + input_file: Path, + header: Optional[Path] = None, + samples: Optional[Path] = None, + output: Optional[Path] = None, + output_type: str = "v", +): + """ + Modify VCF/BCF header, e.g., rename samples. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if header and not header.exists(): + raise FileNotFoundError(f"Header file not found: {header}") + if samples and not samples.exists(): + raise FileNotFoundError(f"Samples file not found: {samples}") + if not header and not samples: + raise ValueError("Either 'header' or 'samples' file must be provided.") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "reheader"] + + if header: + cmd.extend(["-h", str(header)]) + if samples: + cmd.extend(["-s", str(samples)]) + if output: + cmd.extend(["-o", str(output)]) + + cmd.append(str(input_file)) + + # Reheader does not support -O, output type is inferred from extension + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_sort( + input_file: Path, + output: Optional[Path] = None, + output_type: str = "v", + max_mem: str = "768M", + temp_dir: Optional[Path] = None, +): + """ + Sort VCF/BCF file by chromosome and position. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "sort", str(input_file)] + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if max_mem: + cmd.extend(["-m", max_mem]) + if temp_dir: + temp_dir.mkdir(exist_ok=True, parents=True) + cmd.extend(["-T", str(temp_dir)]) + + return _run_bcftools_command(cmd, output) + +@mcp.tool() +def bcftools_stats( + input_files: List[Path], + fasta_ref: Optional[Path] = None, + output_dir: Optional[Path] = None, + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + samples: Optional[str] = None, + samples_file: Optional[Path] = None, + threads: int = 1, +): + """ + Produce VCF/BCF stats, create plots with plot-vcfstats. + """ + 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 fasta_ref and not fasta_ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {fasta_ref}") + + cmd = ["bcftools", "stats"] + + if fasta_ref: + cmd.extend(["-f", str(fasta_ref)]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if samples: + cmd.extend(["-s", samples]) + if samples_file: + cmd.extend(["-S", str(samples_file)]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + cmd.extend([str(f) for f in input_files]) + + # stats command outputs to stdout by default. + # If output_dir is provided, we redirect stdout to a file. + output_file = None + if output_dir: + output_dir.mkdir(exist_ok=True, parents=True) + output_file = output_dir / "stats.txt" + with open(output_file, "w") as f_out: + command_str = " ".join(cmd) + try: + result = subprocess.run( + cmd, + stdout=f_out, + stderr=subprocess.PIPE, + text=True, + check=True, + ) + return { + "command_executed": command_str, + "stdout": f"Stats written to {output_file}", + "stderr": result.stderr, + "output_files": [str(output_file)], + "return_code": 0, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout or "", + "stderr": e.stderr, + "output_files": [], + "return_code": e.returncode, + "error": "CalledProcessError" + } + else: + # No output file, capture stdout directly + return _run_bcftools_command(cmd) + +@mcp.tool() +def bcftools_view( + input_file: Path, + output: Optional[Path] = None, + output_type: str = "v", + header_only: bool = False, + no_header: bool = False, + drop_genotypes: bool = False, + trim_alt_alleles: bool = False, + min_alleles: Optional[int] = None, + max_alleles: Optional[int] = None, + min_ac: Optional[int] = None, + max_ac: Optional[int] = None, + min_af: Optional[float] = None, + max_af: Optional[float] = None, + types: Optional[str] = None, + include: Optional[str] = None, + exclude: Optional[str] = None, + regions: Optional[str] = None, + regions_file: Optional[Path] = None, + samples: Optional[str] = None, + samples_file: Optional[Path] = None, + threads: int = 1, +): + """ + Subset, filter, and convert VCF and BCF files. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if output_type not in ["b", "u", "z", "v"]: + raise ValueError("output_type must be one of 'b', 'u', 'z', 'v'.") + + cmd = ["bcftools", "view", str(input_file)] + + if output: + cmd.extend(["-o", str(output)]) + if output_type: + cmd.extend(["-O", output_type]) + if header_only: + cmd.append("-h") + if no_header: + cmd.append("-H") + if drop_genotypes: + cmd.append("-G") + if trim_alt_alleles: + cmd.append("-a") + if min_alleles is not None: + cmd.extend(["-m", str(min_alleles)]) + if max_alleles is not None: + cmd.extend(["-M", str(max_alleles)]) + if min_ac is not None: + cmd.extend(["-c", str(min_ac)]) + if max_ac is not None: + cmd.extend(["-C", str(max_ac)]) + if min_af is not None: + cmd.extend(["-q", str(min_af)]) + if max_af is not None: + cmd.extend(["-Q", str(max_af)]) + if types: + cmd.extend(["-v", types]) + if include: + cmd.extend(["-i", include]) + if exclude: + cmd.extend(["-e", exclude]) + if regions: + cmd.extend(["-r", regions]) + if regions_file: + cmd.extend(["-R", str(regions_file)]) + if samples: + cmd.extend(["-s", samples]) + if samples_file: + cmd.extend(["-S", str(samples_file)]) + if threads > 1: + cmd.extend(["--threads", str(threads)]) + + return _run_bcftools_command(cmd, output) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bcftools/app/bcftools_shim_server.py b/Biomni/mcp_generated/mcp_bcftools/app/bcftools_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e4a1c4874c1087306e7e2a0cadffec0c1af92a45 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/app/bcftools_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_bcftools/app/bcftools_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bcftools' + + +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_bcftools/docker-compose.yml b/Biomni/mcp_generated/mcp_bcftools/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b6d4e0018c2bd5952a61daa862c93500125d3415 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bcftools: + build: . + image: mcp-bcftools:latest + container_name: mcp-bcftools + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bcftools + 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_bcftools/environment.yaml b/Biomni/mcp_generated/mcp_bcftools/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..047c9fb057f47426cf0d379551d816ade5cd1fcd --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bcftools + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bcftools/requirements.txt b/Biomni/mcp_generated/mcp_bcftools/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bcftools/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_server.py b/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0d22eafe08a15807e8323338f6bb96a7c2e7536c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_server.py @@ -0,0 +1,297 @@ +import subprocess +import tempfile +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +# No need to import mcp as per instructions. + +def _run_r_script(script_content: str, cwd: Path) -> Dict[str, Any]: + """ + Executes an R script and captures its output. + + Args: + script_content: The R script as a string. + cwd: The current working directory for the R script execution. + + Returns: + A dictionary containing execution details, including command, stdout, stderr, + and an error message if the script fails. + """ + with tempfile.NamedTemporaryFile(mode="w", suffix=".R", delete=False) as r_script_file: + r_script_file.write(script_content) + r_script_path = Path(r_script_file.name) + + command = ["Rscript", str(r_script_path)] + + stdout = "" + stderr = "" + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + cwd=cwd # Run R script in the specified working directory + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed with exit code {e.returncode}", + "output_files": [], + } + finally: + os.remove(r_script_path) + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": [], # This will be populated by the calling function + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_alabaster_spatial' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def save_spatial_experiment( + input_spatial_experiment_rds: Path, + output_directory: Path, + precomputed: bool = True, + deferred: bool = False, + compression_level: Optional[int] = None, + compression_filter: Optional[str] = None, +) -> Dict[str, Any]: + """ + Saves a SpatialExperiment object and its images into file artifacts. + + This function serializes an R SpatialExperiment object (provided as an RDS file) + into a directory structure of file artifacts, offering a portable alternative + to direct RDS serialization. The `alabaster.spatial` package handles the + conversion and storage of the object and its associated images. + + Args: + input_spatial_experiment_rds: Path to an RDS file containing the SpatialExperiment object. + This file must exist. + output_directory: Path to the directory where the file artifacts will be saved. + This directory will be created if it does not exist. + precomputed: Logical, whether to save precomputed results. Defaults to TRUE. + deferred: Logical, whether to defer saving of certain components. Defaults to FALSE. + compression_level: Integer, compression level for HDF5 files (0-9). + If None, R's default (typically 6) will be used. + compression_filter: String, compression filter for HDF5 files (e.g., "gzip", "lzf"). + If None, no specific filter is applied. + + Returns: + A dictionary containing execution details, including the command executed, + stdout, stderr, and a list of generated output files/directories. + Returns an error if input file is not found, output path is invalid, + compression_level is out of range, or R script execution fails. + """ + # Input validation + if not input_spatial_experiment_rds.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Input RDS file not found at '{input_spatial_experiment_rds}'", + "output_files": [], + } + + if output_directory.exists() and not output_directory.is_dir(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Output path '{output_directory}' exists but is not a directory.", + "output_files": [], + } + + try: + output_directory.mkdir(parents=True, exist_ok=True) + except OSError as e: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Could not create output directory '{output_directory}': {e}", + "output_files": [], + } + + if compression_level is not None and not (0 <= compression_level <= 9): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: 'compression_level' must be an integer between 0 and 9, got {compression_level}", + "output_files": [], + } + + # Construct R script + r_script_template = """ + suppressPackageStartupMessages({{ + library(alabaster.spatial) + library(SpatialExperiment) + library(S4Vectors) + library(alabaster.base) + library(rhdf5) # Explicitly load for HDF5 operations + }}) + + input_rds_path <- "{input_rds_path}" + output_dir_path <- "{output_dir_path}" + + # Load the SpatialExperiment object + if (!file.exists(input_rds_path)) {{ + stop(paste0("Input RDS file not found: ", input_rds_path)) + }} + se_object <- readRDS(input_rds_path) + + # Ensure it's a SpatialExperiment object + if (!is(se_object, "SpatialExperiment")) {{ + stop("Loaded object is not a SpatialExperiment object.") + }} + + # Prepare additional arguments for saveObject + save_args <- list( + x = se_object, + path = output_dir_path, + precomputed = {precomputed_r}, + deferred = {deferred_r} + ) + {compression_level_r} + {compression_filter_r} + + # Call saveObject + do.call(alabaster.spatial::saveObject, save_args) + + message(paste0("SpatialExperiment object saved to: ", output_dir_path)) + """ + + compression_level_r = f"save_args$compression_level <- {compression_level}" if compression_level is not None else "" + compression_filter_r = f"save_args$compression_filter <- '{compression_filter}'" if compression_filter is not None else "" + + r_script_content = r_script_template.format( + input_rds_path=input_spatial_experiment_rds.resolve(), + output_dir_path=output_directory.resolve(), + precomputed_r=str(precomputed).upper(), + deferred_r=str(deferred).upper(), + compression_level_r=compression_level_r, + compression_filter_r=compression_filter_r, + ) + + # Execute R script + result = _run_r_script(r_script_content, output_directory.parent) # Run in parent dir to allow output_directory creation + if "error" in result: + return result + + result["output_files"] = [str(output_directory.resolve())] + return result + + +@mcp.tool() +def read_spatial_experiment( + input_directory: Path, + output_spatial_experiment_rds: Path, + metadata_only: bool = False, + image_format: Optional[str] = None, +) -> Dict[str, Any]: + """ + Loads a SpatialExperiment object and its images from file artifacts. + + This function reconstructs an R SpatialExperiment object from a directory + of file artifacts previously saved by `save_spatial_experiment`. + + Args: + input_directory: Path to the directory containing the file artifacts. + This directory must exist and be valid. + output_spatial_experiment_rds: Path to the output RDS file where the + reconstructed SpatialExperiment object will be saved. + The parent directory will be created if it does not exist. + metadata_only: Logical, whether to only load metadata and not the full data. + Defaults to FALSE. + image_format: String, desired image format for loading (e.g., "png", "jpeg", "tiff"). + If None, images are loaded in their original format. + + Returns: + A dictionary containing execution details, including the command executed, + stdout, stderr, and a list of generated output files. + Returns an error if input directory is not found, output path is invalid, + or R script execution fails. + """ + # Input validation + if not input_directory.is_dir(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Input directory not found at '{input_directory}'", + "output_files": [], + } + + if output_spatial_experiment_rds.exists() and not output_spatial_experiment_rds.is_file(): + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Output path '{output_spatial_experiment_rds}' exists but is not a file.", + "output_files": [], + } + + try: + output_spatial_experiment_rds.parent.mkdir(parents=True, exist_ok=True) + except OSError as e: + return { + "command_executed": "", + "stdout": "", + "stderr": f"Error: Could not create parent directory for output RDS file '{output_spatial_experiment_rds}': {e}", + "output_files": [], + } + + # Construct R script + r_script_template = """ + suppressPackageStartupMessages({{ + library(alabaster.spatial) + library(SpatialExperiment) + library(S4Vectors) + library(alabaster.base) + library(rhdf5) # Explicitly load for HDF5 operations + }}) + + input_dir_path <- "{input_dir_path}" + output_rds_path <- "{output_rds_path}" + + # Prepare additional arguments for readObject + read_args <- list( + path = input_dir_path, + metadata.only = {metadata_only_r} + ) + {image_format_r} + + # Call readObject + se_object <- do.call(alabaster.spatial::readObject, read_args) + + # Save the SpatialExperiment object to RDS + saveRDS(se_object, file = output_rds_path) + + message(paste0("SpatialExperiment object loaded from: ", input_dir_path, " and saved to: ", output_rds_path)) + """ + + image_format_r = f"read_args$image.format <- '{image_format}'" if image_format is not None else "" + + r_script_content = r_script_template.format( + input_dir_path=input_directory.resolve(), + output_rds_path=output_spatial_experiment_rds.resolve(), + metadata_only_r=str(metadata_only).upper(), + image_format_r=image_format_r, + ) + + # Execute R script + result = _run_r_script(r_script_content, output_spatial_experiment_rds.parent) + if "error" in result: + return result + + result["output_files"] = [str(output_spatial_experiment_rds.resolve())] + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d63aaee9992f9d7e2ea8ec9555c0b6fb8f9fdea8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_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.spatial/app/bioconductor-alabaster.spatial_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_alabaster_spatial' + + +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-awaggregator/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..53b52deea05f9bf6f66ad8a3024b5e2a18923bb4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/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-awaggregator via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-awaggregator -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-awaggregator_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-awaggregator_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-awaggregator_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_server.py b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d7481f36fc3a27cadd94ff6bca62631362feecaa --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_server.py @@ -0,0 +1,222 @@ +import subprocess +from pathlib import Path +from typing import Dict, List + +# MCP decorator is assumed to be available in the execution environment. +# No import is needed as per the instructions. +# class mcp: +# @staticmethod +# def tool(): +# def decorator(func): +# return func +# return decorator + + +def _run_awaggregator_cli(cmd: List[str], output_map: Dict[str, Path]) -> Dict: + """ + A private helper function to execute the awaggregator wrapper script, + handle subprocess execution, and format the output. + """ + command_executed = " ".join(map(str, 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": {key: str(path) for key, path in output_map.items()}, + } + except FileNotFoundError: + # This error is raised if 'Rscript' or the wrapper is not in PATH + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'Rscript' or the wrapper script 'run_awaggregator.R' not found. " + "Please ensure R and the tool's wrapper script are in the system's PATH.", + "output_files": {}, + "error": "Executable not found." + } + except subprocess.CalledProcessError as e: + # This error is raised for non-zero exit codes from the tool + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": {}, + "error": f"Tool execution failed with exit code {e.returncode}", + } + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_awaggregator' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def aw_weight( + expression_matrix: Path, + design_matrix: Path, + output_weights_file: Path, +): + """ + Computes adaptive weights for gene expression data meta-analysis. + + This tool wraps the `aw.weight` function from the Bioconductor `awaggregator` package. + It takes an expression data matrix and a design matrix to compute weights for each study. + The resulting weights matrix can be used with the `aw_mean` or `aw_median` tools. + + Args: + expression_matrix: Path to the input expression data matrix file (e.g., CSV, TSV). + Rows should represent genes and columns should represent samples. + design_matrix: Path to the input design matrix file (e.g., CSV, TSV). + This matrix describes the experimental design. + output_weights_file: Path to save the computed weights matrix. + + Returns: + A dictionary containing the execution command, stdout, stderr, and a + path to the output weights file. + """ + # --- Input Validation --- + if not expression_matrix.is_file(): + raise FileNotFoundError(f"Input expression matrix file not found: {expression_matrix}") + if not design_matrix.is_file(): + raise FileNotFoundError(f"Input design matrix file not found: {design_matrix}") + + output_dir = output_weights_file.parent + if not output_dir.is_dir(): + raise NotADirectoryError(f"Output directory does not exist: {output_dir}") + + # --- Command Construction --- + # Assumes a wrapper script 'run_awaggregator.R' is available that handles CLI parsing. + cmd = [ + "Rscript", + "run_awaggregator.R", + "--method", + "weight", + "--expression", + expression_matrix, + "--design", + design_matrix, + "--output", + output_weights_file, + ] + + return _run_awaggregator_cli(cmd, {"weights_matrix": output_weights_file}) + + +@mcp.tool() +def aw_mean( + data_matrix: Path, + weights_matrix: Path, + output_file: Path, + na_rm: bool = False, +): + """ + Performs adaptive-weighted mean aggregation on gene expression data. + + This tool wraps the `aw.mean` function from the Bioconductor `awaggregator` package. + It takes a data matrix and a corresponding weights matrix to compute the weighted mean + for each gene (row) across studies (columns). + + Args: + data_matrix: Path to the input data matrix file (e.g., CSV, TSV). + Rows should represent genes and columns should represent studies. + weights_matrix: Path to the input weights matrix file (e.g., CSV, TSV). + Must have the same dimensions as the data matrix. + output_file: Path to save the aggregated results. + na_rm: If True, NA values will be removed before computation. Defaults to False. + + Returns: + A dictionary containing the execution command, stdout, stderr, and a + path to the output file. + """ + # --- Input Validation --- + if not data_matrix.is_file(): + raise FileNotFoundError(f"Input data matrix file not found: {data_matrix}") + if not weights_matrix.is_file(): + raise FileNotFoundError(f"Input weights matrix file not found: {weights_matrix}") + + output_dir = output_file.parent + if not output_dir.is_dir(): + raise NotADirectoryError(f"Output directory does not exist: {output_dir}") + + # --- Command Construction --- + cmd = [ + "Rscript", + "run_awaggregator.R", + "--method", + "mean", + "--data", + data_matrix, + "--weights", + weights_matrix, + "--output", + output_file, + ] + if na_rm: + cmd.append("--na-rm") + + return _run_awaggregator_cli(cmd, {"aggregated_data": output_file}) + + +@mcp.tool() +def aw_median( + data_matrix: Path, + weights_matrix: Path, + output_file: Path, + na_rm: bool = False, +): + """ + Performs adaptive-weighted median aggregation on gene expression data. + + This tool wraps the `aw.median` function from the Bioconductor `awaggregator` package. + It takes a data matrix and a corresponding weights matrix to compute the weighted median + for each gene (row) across studies (columns). + + Args: + data_matrix: Path to the input data matrix file (e.g., CSV, TSV). + Rows should represent genes and columns should represent studies. + weights_matrix: Path to the input weights matrix file (e.g., CSV, TSV). + Must have the same dimensions as the data matrix. + output_file: Path to save the aggregated results. + na_rm: If True, NA values will be removed before computation. Defaults to False. + + Returns: + A dictionary containing the execution command, stdout, stderr, and a + path to the output file. + """ + # --- Input Validation --- + if not data_matrix.is_file(): + raise FileNotFoundError(f"Input data matrix file not found: {data_matrix}") + if not weights_matrix.is_file(): + raise FileNotFoundError(f"Input weights matrix file not found: {weights_matrix}") + + output_dir = output_file.parent + if not output_dir.is_dir(): + raise NotADirectoryError(f"Output directory does not exist: {output_dir}") + + # --- Command Construction --- + cmd = [ + "Rscript", + "run_awaggregator.R", + "--method", + "median", + "--data", + data_matrix, + "--weights", + weights_matrix, + "--output", + output_file, + ] + if na_rm: + cmd.append("--na-rm") + + return _run_awaggregator_cli(cmd, {"aggregated_data": output_file}) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..275d7035b9d5f97fe9e8b61651c01915dc4b0273 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_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-awaggregator/app/bioconductor-awaggregator_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_awaggregator' + + +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-awaggregator/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-awaggregator/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff0371023c533e11773174f48bd51a6d9216bff9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-awaggregator: + build: . + image: mcp-bioconductor-awaggregator:latest + container_name: mcp-bioconductor-awaggregator + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-awaggregator + 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-awaggregator/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4a35b9239801536a5473e94323a22b826418478d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-awaggregator + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-awaggregator/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-awaggregator/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-deseq2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6ce2c11f459604c91a335ffbaba16221d791a713 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/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-deseq2 via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-deseq2 -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-deseq2_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-deseq2_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-deseq2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf03b7a62eef45521c43790725cecfe025e64ed7 Binary files /dev/null and b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_shim_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_shim_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6459e132bab4e3074e79ee7d2fbbd6cea337e64a Binary files /dev/null and b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/__pycache__/bioconductor-deseq2_shim_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_server.py b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fb023716054df310f98f92617afc6b8110046f02 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_server.py @@ -0,0 +1,299 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_deseq2' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def deseq2_differential_expression( + counts_path: str, + metadata_path: str, + design_formula: str, + output_results_path: str, + contrast_column: Optional[str] = None, + reference_level: Optional[str] = None, + treated_level: Optional[str] = None, + alpha: float = 0.05, + lfc_threshold: float = 0.0, + test_type: str = "Wald", + fit_type: str = "parametric", + reduced_formula: Optional[str] = None, + shrink_lfc: bool = True, + shrink_type: str = "apeglm" +): + """ + Performs differential expression analysis using DESeq2. + + Args: + counts_path: Path to the raw counts matrix (CSV/TSV, genes as rows, samples as columns). + metadata_path: Path to the sample metadata (CSV/TSV, samples as rows). + design_formula: The design formula (e.g., '~ condition' or '~ genotype + condition'). + output_results_path: Path where the results CSV will be saved. + contrast_column: The column in metadata to use for comparison. + reference_level: The baseline/control level in the contrast column. + treated_level: The treatment level in the contrast column. + alpha: Significance threshold (FDR). + lfc_threshold: Log2 fold change threshold for null hypothesis. + test_type: 'Wald' or 'LRT' (Likelihood Ratio Test). + fit_type: 'parametric', 'local', or 'mean'. + reduced_formula: Required if test_type is 'LRT' (e.g., '~ 1'). + shrink_lfc: Whether to apply Log2 Fold Change shrinkage. + shrink_type: Type of shrinkage estimator ('apeglm', 'ashr', or 'normal'). + """ + counts_p = Path(counts_path) + meta_p = Path(metadata_path) + out_p = Path(output_results_path) + + if not counts_p.exists(): + raise FileNotFoundError(f"Counts file not found: {counts_path}") + if not meta_p.exists(): + raise FileNotFoundError(f"Metadata file not found: {metadata_path}") + if test_type == "LRT" and not reduced_formula: + raise ValueError("reduced_formula is required when test_type is 'LRT'") + + # Construct R script + r_script = f""" + library(DESeq2) + + # Load data + counts <- read.csv("{counts_p}", row.names=1, check.names=FALSE) + coldata <- read.csv("{meta_p}", row.names=1, check.names=FALSE) + + # Ensure factors + for(col in colnames(coldata)) {{ + if(is.character(coldata[[col]])) coldata[[col]] <- as.factor(coldata[[col]]) + }} + + # Create DESeqDataSet + dds <- DESeqDataSetFromMatrix(countData=counts, colData=coldata, design=as.formula("{design_formula}")) + + # Run DESeq + if ("{test_type}" == "LRT") {{ + dds <- DESeq(dds, test="LRT", reduced=as.formula("{reduced_formula}"), fitType="{fit_type}") + }} else {{ + dds <- DESeq(dds, test="Wald", fitType="{fit_type}") + }} + + # Results extraction + if (!is.null("{contrast_column}") && !is.null("{reference_level}") && !is.null("{treated_level}")) {{ + res <- results(dds, contrast=c("{contrast_column}", "{treated_level}", "{reference_level}"), + alpha={alpha}, lfcThreshold={lfc_threshold}) + + if ({str(shrink_lfc).upper()}) {{ + # Shrinkage requires the coefficient name or contrast + # apeglm is preferred but requires 'coef' + res <- lfcShrink(dds, contrast=c("{contrast_column}", "{treated_level}", "{reference_level}"), + res=res, type="{shrink_type}") + }} + }} else {{ + res <- results(dds, alpha={alpha}, lfcThreshold={lfc_threshold}) + }} + + write.csv(as.data.frame(res), file="{out_p}") + """ + + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script) + tmp_path = tmp.name + + try: + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_p)] + } + except subprocess.CalledProcessError as e: + return { + "error": "DESeq2 execution failed", + "stdout": e.stdout, + "stderr": e.stderr, + "command_executed": " ".join(e.cmd) + } + finally: + Path(tmp_path).unlink(missing_ok=True) + +@mcp.tool() +def deseq2_transform_counts( + counts_path: str, + metadata_path: str, + design_formula: str, + output_path: str, + method: str = "vst", + blind: bool = True +): + """ + Transforms count data using Variance Stabilizing Transformation (VST) or Regularized Log (rlog). + Useful for PCA, clustering, and visualization. + + Args: + counts_path: Path to raw counts matrix. + metadata_path: Path to sample metadata. + design_formula: Design formula (e.g., '~ condition'). + output_path: Path to save the transformed matrix CSV. + method: Transformation method ('vst' or 'rlog'). + blind: Whether to perform transformation in a blind manner (independent of design). + """ + counts_p = Path(counts_path) + meta_p = Path(metadata_path) + out_p = Path(output_path) + + if method not in ["vst", "rlog"]: + raise ValueError("Method must be 'vst' or 'rlog'") + + r_script = f""" + library(DESeq2) + counts <- read.csv("{counts_p}", row.names=1, check.names=FALSE) + coldata <- read.csv("{meta_p}", row.names=1, check.names=FALSE) + dds <- DESeqDataSetFromMatrix(countData=counts, colData=coldata, design=as.formula("{design_formula}")) + + if ("{method}" == "vst") {{ + transformed <- vst(dds, blind={str(blind).upper()}) + }} else {{ + transformed <- rlog(dds, blind={str(blind).upper()}) + }} + + write.csv(assay(transformed), file="{out_p}") + """ + + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script) + tmp_path = tmp.name + + try: + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_p)] + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + finally: + Path(tmp_path).unlink(missing_ok=True) + +@mcp.tool() +def deseq2_get_normalized_counts( + counts_path: str, + metadata_path: str, + design_formula: str, + output_path: str, + normalization_type: str = "ratio" +): + """ + Calculates size factors and returns normalized counts. + + Args: + counts_path: Path to raw counts matrix. + metadata_path: Path to sample metadata. + design_formula: Design formula. + output_path: Path to save normalized counts CSV. + normalization_type: Method for size factor estimation ('ratio', 'poscounts', 'iterate'). + """ + counts_p = Path(counts_path) + meta_p = Path(metadata_path) + out_p = Path(output_path) + + r_script = f""" + library(DESeq2) + counts <- read.csv("{counts_p}", row.names=1, check.names=FALSE) + coldata <- read.csv("{meta_p}", row.names=1, check.names=FALSE) + dds <- DESeqDataSetFromMatrix(countData=counts, colData=coldata, design=as.formula("{design_formula}")) + dds <- estimateSizeFactors(dds, type="{normalization_type}") + norm_counts <- counts(dds, normalized=TRUE) + write.csv(norm_counts, file="{out_p}") + """ + + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script) + tmp_path = tmp.name + + try: + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_p)] + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + finally: + Path(tmp_path).unlink(missing_ok=True) + +@mcp.tool() +def deseq2_plot_pca( + counts_path: str, + metadata_path: str, + design_formula: str, + intgroup_columns: List[str], + output_plot_path: str, + ntop: int = 500 +): + """ + Generates a PCA plot from DESeq2 transformed data. + + Args: + counts_path: Path to raw counts matrix. + metadata_path: Path to sample metadata. + design_formula: Design formula. + intgroup_columns: List of columns in metadata to use for grouping/coloring. + output_plot_path: Path to save the PCA plot (PDF or PNG). + ntop: Number of top variable genes to use for PCA. + """ + counts_p = Path(counts_path) + meta_p = Path(metadata_path) + out_p = Path(output_plot_path) + + intgroup_r = 'c("' + '","'.join(intgroup_columns) + '")' + + r_script = f""" + library(DESeq2) + library(ggplot2) + counts <- read.csv("{counts_p}", row.names=1, check.names=FALSE) + coldata <- read.csv("{meta_p}", row.names=1, check.names=FALSE) + dds <- DESeqDataSetFromMatrix(countData=counts, colData=coldata, design=as.formula("{design_formula}")) + vsd <- vst(dds, blind=TRUE) + + pca_data <- plotPCA(vsd, intgroup={intgroup_r}, ntop={ntop}, returnData=TRUE) + percentVar <- round(100 * attr(pca_data, "percentVar")) + + p <- ggplot(pca_data, aes(PC1, PC2, color={intgroup_columns[0]})) + + geom_point(size=3) + + xlab(paste0("PC1: ", percentVar[1], "% variance")) + + ylab(paste0("PC2: ", percentVar[2], "% variance")) + + coord_fixed() + + theme_bw() + + ggsave("{out_p}", plot=p, device="{out_p.suffix[1:]}") + """ + + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as tmp: + tmp.write(r_script) + tmp_path = tmp.name + + try: + cmd = ["Rscript", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(out_p)] + } + except subprocess.CalledProcessError as e: + return {"error": str(e), "stderr": e.stderr} + finally: + Path(tmp_path).unlink(missing_ok=True) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..bc727069044eadfcee6381fed0e748c38ee8c92b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/bioconductor-deseq2_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-deseq2/app/bioconductor-deseq2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_deseq2' + + +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-deseq2/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-deseq2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..44fca83c313aca169d4a672bc807c812c843473f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-deseq2: + build: . + image: mcp-bioconductor-deseq2:latest + container_name: mcp-bioconductor-deseq2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-deseq2 + 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-deseq2/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-deseq2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6e0fbe21343a08899e814e52812d2bb7e94709bd --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-deseq2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-deseq2/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-deseq2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-deseq2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-gosemsim/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..79759355a85d632d7922ea3c56930957dd4d59fa --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/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-gosemsim via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-gosemsim -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-gosemsim_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-gosemsim_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-gosemsim_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_server.py b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fedd4cfb08b6f46924b77ac989fabe3318468caa --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_server.py @@ -0,0 +1,281 @@ +import subprocess +import json +import tempfile +from pathlib import Path +from typing import List, Optional, Literal, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_gosemsim' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def go_similarity( + term1: str, + term2: str, + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity between two GO terms using GOSemSim. + + Args: + term1: First GO term ID (e.g., 'GO:0004022'). + term2: Second GO term ID (e.g., 'GO:0005515'). + org_db: The annotation OrgDb package (e.g., 'org.Hs.eg.db' for human). + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method ("Resnik", "Lin", "Rel", "Jiang", or "Wang"). + keytype: Key type of the gene identifiers in the OrgDb. + """ + if not term1.startswith("GO:") or not term2.startswith("GO:"): + return {"error": "GO terms must start with 'GO:' prefix."} + + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + sim <- goSim('{term1}', '{term2}', semData=hsGO, measure='{measure}') + cat(sim) + """ + + return _execute_r_script(r_script) + +@mcp.tool() +def mgo_similarity( + terms: List[str], + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity matrix for a list of GO terms. + + Args: + terms: List of GO term IDs. + org_db: The annotation OrgDb package. + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method. + keytype: Key type of the gene identifiers. + """ + terms_r = 'c("' + '","'.join(terms) + '")' + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + sim_matrix <- mgoSim({terms_r}, semData=hsGO, measure='{measure}', combine=NULL) + write.csv(sim_matrix, row.names=TRUE) + """ + + return _execute_r_script(r_script) + +@mcp.tool() +def gene_similarity( + gene1: str, + gene2: str, + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + combine: Literal["max", "avg", "rcmax", "BMA"] = "BMA", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity between two genes based on their GO annotations. + + Args: + gene1: First gene ID. + gene2: Second gene ID. + org_db: The annotation OrgDb package. + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method. + combine: Strategy to combine similarities of multiple GO terms ("max", "avg", "rcmax", "BMA"). + keytype: Key type of the gene identifiers (e.g., 'ENTREZID', 'SYMBOL'). + """ + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + sim <- geneSim('{gene1}', '{gene2}', semData=hsGO, measure='{measure}', combine='{combine}') + print(sim) + """ + + return _execute_r_script(r_script) + +@mcp.tool() +def mgene_similarity( + genes: List[str], + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + combine: Literal["max", "avg", "rcmax", "BMA"] = "BMA", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity matrix for a list of genes. + + Args: + genes: List of gene IDs. + org_db: The annotation OrgDb package. + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method. + combine: Strategy to combine similarities. + keytype: Key type of the gene identifiers. + """ + genes_r = 'c("' + '","'.join(genes) + '")' + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + sim_matrix <- mgeneSim({genes_r}, semData=hsGO, measure='{measure}', combine='{combine}') + write.csv(sim_matrix, row.names=TRUE) + """ + + return _execute_r_script(r_script) + +@mcp.tool() +def cluster_similarity( + cluster1: List[str], + cluster2: List[str], + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + combine: Literal["max", "avg", "rcmax", "BMA"] = "BMA", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity between two clusters (sets) of genes. + + Args: + cluster1: List of gene IDs in the first cluster. + cluster2: List of gene IDs in the second cluster. + org_db: The annotation OrgDb package. + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method. + combine: Strategy to combine similarities. + keytype: Key type of the gene identifiers. + """ + c1_r = 'c("' + '","'.join(cluster1) + '")' + c2_r = 'c("' + '","'.join(cluster2) + '")' + + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + sim <- clusterSim({c1_r}, {c2_r}, semData=hsGO, measure='{measure}', combine='{combine}') + cat(sim) + """ + + return _execute_r_script(r_script) + +@mcp.tool() +def mcluster_similarity( + clusters_json: str, + org_db: str = "org.Hs.eg.db", + ont: Literal["BP", "MF", "CC"] = "BP", + measure: Literal["Resnik", "Lin", "Rel", "Jiang", "Wang"] = "Wang", + combine: Literal["max", "avg", "rcmax", "BMA"] = "BMA", + keytype: str = "ENTREZID" +): + """ + Calculate semantic similarity matrix between multiple clusters of genes. + + Args: + clusters_json: A JSON string representing a dictionary where keys are cluster names + and values are lists of gene IDs. + org_db: The annotation OrgDb package. + ont: One of "BP", "MF", or "CC" ontologies. + measure: Semantic similarity method. + combine: Strategy to combine similarities. + keytype: Key type of the gene identifiers. + """ + try: + clusters_dict = json.loads(clusters_json) + except json.JSONDecodeError: + return {"error": "Invalid JSON format for clusters_json."} + + # Construct R list of character vectors + r_list_elements = [] + for name, genes in clusters_dict.items(): + genes_str = 'c("' + '","'.join(genes) + '")' + r_list_elements.append(f'"{name}" = {genes_str}') + + clusters_r = "list(" + ", ".join(r_list_elements) + ")" + + r_script = f""" + suppressPackageStartupMessages(library(GOSemSim)) + suppressPackageStartupMessages(library({org_db})) + + hsGO <- godata('{org_db}', ont='{ont}', computeIC=TRUE, keytype='{keytype}') + clusters <- {clusters_r} + sim_matrix <- mclusterSim(clusters, semData=hsGO, measure='{measure}', combine='{combine}') + write.csv(sim_matrix, row.names=TRUE) + """ + + return _execute_r_script(r_script) + +def _execute_r_script(script_content: str): + """ + Internal helper to execute R code via Rscript. + """ + with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp: + tmp.write(script_content) + tmp_path = tmp.name + + try: + # Note: We assume Rscript is in the PATH and GOSemSim is installed. + # If using a specific conda environment, the user should run the MCP server within it. + result = subprocess.run( + ["Rscript", tmp_path], + capture_output=True, + text=True, + check=True + ) + + return { + "command_executed": f"Rscript {tmp_path}", + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript {tmp_path}", + "stdout": e.stdout, + "stderr": e.stderr, + "error": "R execution failed. Ensure R and GOSemSim are installed and the OrgDb package is available.", + "status": "error" + } + finally: + if Path(tmp_path).exists(): + Path(tmp_path).unlink() + +@mcp.tool() +def list_supported_orgdbs(): + """ + Returns a list of commonly used Bioconductor OrgDb packages for GOSemSim. + """ + org_dbs = { + "human": "org.Hs.eg.db", + "mouse": "org.Mm.eg.db", + "rat": "org.Rn.eg.db", + "fly": "org.Dm.eg.db", + "zebrafish": "org.Dr.eg.db", + "yeast": "org.Sc.sgd.db", + "arabidopsis": "org.At.tair.db", + "chicken": "org.Gg.eg.db", + "bovine": "org.Bt.eg.db", + "worm": "org.Ce.eg.db", + "pig": "org.Ss.eg.db", + "rhesus": "org.Mmu.eg.db" + } + return {"supported_org_dbs": org_dbs} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ec2c6a3ac8dc257a5833f2d70b3a4cc04a0ac677 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_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-gosemsim/app/bioconductor-gosemsim_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_gosemsim' + + +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-gosemsim/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-gosemsim/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e0264312aed56c58603128d118ddb6f05d42616d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-gosemsim: + build: . + image: mcp-bioconductor-gosemsim:latest + container_name: mcp-bioconductor-gosemsim + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-gosemsim + 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-gosemsim/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f13aa43e05a5c4edac218b82b7b0981931e9030 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-gosemsim + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-gosemsim/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-gosemsim/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..31ac943add0b5f8b06cc653b6516641aec523bfe --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-infercnv/app/bioconductor-infercnv_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-infercnv/app/bioconductor-infercnv_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_infercnv' + + +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-matrixgenerics/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3884a414339405a05ed35204259e33ccac923b21 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/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-matrixgenerics via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-matrixgenerics -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-matrixgenerics_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-matrixgenerics_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-matrixgenerics_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_server.py b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6fa6dc6c63f94bff292ce802599c796bd9dc7d10 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_server.py @@ -0,0 +1,231 @@ +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 imported. +def mcp_tool_decorator_placeholder(*args, **kwargs): + def decorator(func): + return func + return decorator + +mcp = type("mcp", (), {"tool": mcp_tool_decorator_placeholder}) + + +# A comprehensive list of functions provided by the MatrixGenerics package. +MatrixGenericsOperation = Literal[ + "colAlls", "colAnyNAs", "colAnys", "colAvgsPerRow", "colCounts", "colCummaxs", + "colCummins", "colCumprods", "colCumsums", "colDiffs", "colIQRDiffs", "colIQRs", + "colLogSumExps", "colMadDiffs", "colMads", "colMaxs", "colMeans2", "colMedians", + "colMins", "colOrderStats", "colProds", "colQuantiles", "colRanges", "colRanks", + "colSdDiffs", "colSds", "colSums2", "colTabulates", "colVarDiffs", "colVars", + "colWeightedMads", "colWeightedMeans", "colWeightedMedians", "colWeightedSds", + "colWeightedVars", "rowAlls", "rowAnyNAs", "rowAnys", "rowAvgsPerCol", + "rowCounts", "rowCummaxs", "rowCummins", "rowCumprods", "rowCumsums", + "rowDiffs", "rowIQRDiffs", "rowIQRs", "rowLogSumExps", "rowMadDiffs", + "rowMads", "rowMaxs", "rowMeans2", "rowMedians", "rowMins", "rowOrderStats", + "rowProds", "rowQuantiles", "rowRanges", "rowRanks", "rowSdDiffs", "rowSds", + "rowSums2", "rowTabulates", "rowVarDiffs", "rowVars", "rowWeightedMads", + "rowWeightedMeans", "rowWeightedMedians", "rowWeightedSds", "rowWeightedVars" +] + +TiesMethod = Literal["average", "first", "last", "random", "max", "min"] + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_matrixgenerics' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perform_matrix_statistic( + input_matrix: Path, + operation: MatrixGenericsOperation, + output_filename: str, + na_rm: bool = True, + delimiter: Literal[",", "\t"] = "\t", + has_header: bool = True, + has_rownames: bool = True, + probs: Optional[str] = None, + weights: Optional[Path] = None, + ties_method: TiesMethod = "average", + value: Optional[float] = None, + lag: int = 1, + differences: int = 1, + which: Optional[int] = None, + preserve_shape: bool = False +) -> dict: + """ + Performs a specified summary statistic on a matrix using the MatrixGenerics R package. + + This tool serves as a wrapper around the bioconductor-matrixgenerics R library, + which provides generic functions for matrix statistics. It reads a matrix from a file, + applies the chosen statistical operation, and saves the result to a new file. + + Args: + input_matrix: Path to the input matrix file (e.g., CSV or TSV). + operation: The summary statistic to compute. + output_filename: The name for the output file that will be created. + na_rm: If True, NA/NaN values are removed before computation. + delimiter: Delimiter used in the input matrix file (',' or '\t'). + has_header: Whether the input file has a header row. + has_rownames: Whether the first column of the input file contains row names. + probs: Comma-separated string of probabilities for quantile functions (e.g., "0.25,0.5,0.75"). + weights: Path to a single-column file of numeric weights for weighted operations. + ties_method: Method for handling ties in ranking functions. + value: The specific value to count for '*Counts' functions. + lag: The lag for '*Diffs' functions. + differences: The number of differences for '*Diffs' functions. + which: The order statistic to extract for '*OrderStats' functions. + preserve_shape: For '*Ranks' functions, if TRUE the output has the same dimensions as the input. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not input_matrix.is_file(): + raise FileNotFoundError(f"Input matrix file not found: {input_matrix}") + + if weights and not weights.is_file(): + raise FileNotFoundError(f"Weights file not found: {weights}") + + if not output_filename: + raise ValueError("Output filename cannot be empty.") + + # --- R Script Generation --- + r_script = f""" + # Load necessary library + if (!require("MatrixGenerics", quietly = TRUE)) {{ + stop("MatrixGenerics package is not installed.") + }} + + # --- Argument Parsing --- + args <- commandArgs(trailingOnly = TRUE) + input_file <- args[1] + output_file <- args[2] + operation <- args[3] + na_rm <- as.logical(args[4]) + sep <- args[5] + header <- as.logical(args[6]) + rownames_col <- if (as.logical(args[7])) 1 else NULL + probs_str <- args[8] + weights_file <- args[9] + ties_method <- args[10] + value_str <- args[11] + lag <- as.integer(args[12]) + differences <- as.integer(args[13]) + which_str <- args[14] + preserve_shape <- as.logical(args[15]) + + # --- Data Loading --- + tryCatch({{ + data <- read.delim(input_file, sep = sep, header = header, row.names = rownames_col, check.names = FALSE) + mat <- as.matrix(data) + }}, error = function(e) {{ + stop(paste("Error reading input matrix:", e$message)) + }}) + + # --- Dynamic Function Call --- + # Build the list of parameters + params <- list(x = mat) + + # Add common parameters if the function accepts them + func_args <- names(formals(operation)) + if ("na.rm" %in% func_args) params$na.rm <- na_rm + if ("ties.method" %in% func_args) params$ties.method <- ties_method + if ("value" %in% func_args && value_str != "NULL") params$value <- as.numeric(value_str) + if ("lag" %in% func_args) params$lag <- lag + if ("differences" %in% func_args) params$differences <- differences + if ("which" %in% func_args && which_str != "NULL") params$which <- as.integer(which_str) + if ("preserveShape" %in% func_args) params$preserveShape <- preserve_shape + + # Handle special parameters + if (grepl("Quantiles", operation) && probs_str != "NULL") {{ + params$probs <- as.numeric(strsplit(probs_str, ",")[[1]]) + }} + if (grepl("Weighted", operation) && weights_file != "NULL") {{ + tryCatch({{ + weights_vector <- read.table(weights_file, header = FALSE, colClasses = "numeric")[[1]] + params$w <- weights_vector + }}, error = function(e) {{ + stop(paste("Error reading weights file:", e$message)) + }}) + }} + + # Get the function from the package namespace + func <- get(operation, asNamespace("MatrixGenerics")) + + # Execute the function + result <- do.call(func, params) + + # --- Output Writing --- + # write.table is robust for vectors, matrices, and data.frames + write.table(result, file = output_file, sep = "\\t", quote = FALSE, col.names = NA) + """ + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + r_script_path = temp_path / "matrix_op.R" + with open(r_script_path, "w") as f: + f.write(r_script) + + output_path = Path.cwd() / output_filename + + # --- Command Construction --- + cmd = [ + "Rscript", + str(r_script_path), + str(input_matrix), + str(output_path), + operation, + str(na_rm).upper(), + "\\t" if delimiter == "\t" else ",", + str(has_header).upper(), + str(has_rownames).upper(), + probs if probs else "NULL", + str(weights) if weights else "NULL", + ties_method, + str(value) if value is not None else "NULL", + str(lag), + str(differences), + str(which) if which is not None else "NULL", + str(preserve_shape).upper() + ] + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(map(str, cmd)), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": "R script execution failed." + } + except FileNotFoundError: + return { + "command_executed": " ".join(map(str, cmd)), + "stdout": "", + "stderr": "Rscript command not found. Please ensure R is installed and in your PATH.", + "output_files": [], + "error": "Dependency not found." + } + + # --- Structured Result Return --- + return { + "command_executed": " ".join(map(str, cmd)), + "stdout": stdout, + "stderr": stderr, + "output_files": [str(output_path)] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..533ce6c8dfe9ec137daa14b8c2c5254ba33604c9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_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-matrixgenerics/app/bioconductor-matrixgenerics_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_matrixgenerics' + + +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-matrixgenerics/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7bd9323b314d29fa4bdf3a49442b1ef41fe55464 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-matrixgenerics: + build: . + image: mcp-bioconductor-matrixgenerics:latest + container_name: mcp-bioconductor-matrixgenerics + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-matrixgenerics + 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-matrixgenerics/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..55a5066ba648a0a9d45d2f77c2a3fd79a0e2230f --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-matrixgenerics + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-matrixgenerics/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-mia/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-mia/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..550ada1f834d0528e040f3da5aab4014267c9673 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/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-mia via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-mia -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-mia_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-mia_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-mia_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_server.py b/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_server.py new file mode 100644 index 0000000000000000000000000000000000000000..295119082c27d4abec7c7cee95d205a17b394a19 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_server.py @@ -0,0 +1,134 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_mia' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_mia_r_script( + r_script_content: str, + input_rds_path: Optional[Path] = None, + output_rds_path: Optional[Path] = None, + working_directory: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Executes an R script that utilizes the bioconductor-mia package for microbiome analysis. + + This tool provides a generic interface to run arbitrary R code that is expected + to use the 'mia' Bioconductor package. It allows for loading an input R data + object (e.g., a SummarizedExperiment saved as .rds) and saving an output R data + object. + + Args: + r_script_content: A string containing the R script to be executed. + The script should assume 'mia' is loaded. + If `input_rds_path` is provided, the loaded object will + be available as `input_data`. + If `output_rds_path` is provided, the script should + assign the object to be saved to a variable named + `output_data`. + input_rds_path: Optional path to an R data object (.rds file) to be loaded + into the R environment before executing the script. + The object will be loaded as 'input_data'. + output_rds_path: Optional path where the R script should save an R data + object (e.g., a SummarizedExperiment) using saveRDS(). + The R script must assign the object to be saved to a + variable named 'output_data'. + working_directory: Optional path to a directory where the R script will be + executed. This can be useful for scripts that use + relative paths for additional input/output files. + + Returns: + A dictionary containing the command executed, stdout, stderr, and any + generated output files. + """ + if not r_script_content.strip(): + raise ValueError("r_script_content cannot be empty.") + + output_files: List[Path] = [] + + # Validate input_rds_path if provided + if input_rds_path: + if not input_rds_path.is_file(): + raise FileNotFoundError(f"Input RDS file not found: {input_rds_path}") + if not input_rds_path.suffix == ".rds": + raise ValueError(f"Input RDS file must have a '.rds' extension: {input_rds_path}") + + # Prepare output_rds_path if provided + if output_rds_path: + output_rds_path = output_rds_path.resolve() # Ensure absolute path + output_rds_path.parent.mkdir(parents=True, exist_ok=True) # Create parent dirs + if not output_rds_path.suffix == ".rds": + raise ValueError(f"Output RDS file must have a '.rds' extension: {output_rds_path}") + output_files.append(output_rds_path) + + # Create a temporary directory for the R script + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir_path = Path(tmpdir) + + # Determine the actual working directory for the R process + actual_working_dir = working_directory if working_directory else tmpdir_path + actual_working_dir.mkdir(parents=True, exist_ok=True) # Ensure it exists + + r_script_file = tmpdir_path / "mia_script.R" + + # Construct the full R script content + full_r_script_lines = [ + "library(mia)", + "options(warn=-1)", # Suppress warnings for cleaner output + ] + + if input_rds_path: + full_r_script_lines.append(f"input_data <- readRDS('{input_rds_path.resolve()}')") + + full_r_script_lines.append(r_script_content) + + if output_rds_path: + # The user's script is expected to assign the result to 'output_data' + # We will then save 'output_data' to the specified path. + full_r_script_lines.append(f"if (exists('output_data')) {{ saveRDS(output_data, '{output_rds_path}') }} else {{ message('Warning: output_data variable not found, no RDS file saved.') }}") + + r_script_file.write_text("\n".join(full_r_script_lines)) + + command = ["Rscript", str(r_script_file)] + + try: + process = subprocess.run( + command, + cwd=actual_working_dir, + capture_output=True, + text=True, + check=True, + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Rscript execution failed with exit code {e.returncode}", + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: Rscript command not found. Is R installed and in PATH?", + "error": "Rscript not found", + "output_files": [], + } + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output_files": [str(p) for p in output_files], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..35f8dc22b4cbd32a50ca2f8a2d09df49fcb19b72 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/app/bioconductor-mia_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-mia/app/bioconductor-mia_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_mia' + + +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-mia/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-mia/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-mia/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-mia/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..448e04866675ab483f33e61b5ce5db50c0d53b89 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-mia: + build: . + image: mcp-bioconductor-mia:latest + container_name: mcp-bioconductor-mia + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-mia + 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-mia/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-mia/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9c6e488272f2e53a2ddb32b7b1d99e6cb5ad0ac0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-mia + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-mia/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-mia/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-mia/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2d86d2a336872e24991c5f1dda25fd891c93cbc7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bioconductor-org.ce.eg.db via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-org.ce.eg.db -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bioconductor-org.ce.eg.db_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bioconductor-org.ce.eg.db_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bioconductor-org.ce.eg.db_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_server.py b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f7a35addcb9b105aa6fe2822c8552b094485f93d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_server.py @@ -0,0 +1,380 @@ +import subprocess +import textwrap +from pathlib import Path +from typing import List, Optional, Dict, Any + +# MCP decorator is not imported as per instructions, but assumed to be present. +# from mcp import tool as mcp_tool +# For the purpose of this script, we'll define a dummy decorator. +class mcp: + def tool(): + def decorator(f): + return f + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_org_ce_eg_db' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def select_annotations( + keys: List[str], + keytype: str, + columns: List[str], + output_file: str, + output_format: str = "tsv", +) -> Dict[str, Any]: + """ + Perform a complex query on the org.Ce.eg.db C. elegans annotation database using the select() method. + This returns a table of mappings between the provided keys and requested columns. + + Args: + keys: List of identifiers to look up (e.g., ["172469", "172470"] or ["skn-1", "gfp"]). + keytype: The type of the input keys (e.g., "ENTREZID", "SYMBOL", "WORMBASE", "ENSEMBL"). + columns: The annotation types to retrieve (e.g., ["GO", "EVIDENCE", "ONTOLOGY", "GENENAME"]). + output_file: Path to save the resulting table. + output_format: Format of the output file ("tsv" or "csv"). Defaults to "tsv". + """ + # Input validation + if not keys: + raise ValueError("The 'keys' list cannot be empty.") + if not keytype: + raise ValueError("The 'keytype' parameter must be specified.") + if not columns: + raise ValueError("The 'columns' list cannot be empty.") + + valid_formats = ["tsv", "csv"] + if output_format not in valid_formats: + raise ValueError(f"output_format must be one of {valid_formats}") + + out_path = Path(output_file).resolve() + out_path.parent.mkdir(parents=True, exist_ok=True) + + # Prepare R vectors safely + keys_str = ", ".join([f'"{k}"' for k in keys]) + cols_str = ", ".join([f'"{c}"' for c in columns]) + + # The R script uses f-string formatting. Literal curly braces for R code blocks + # must be escaped by doubling them (e.g., {{ and }}). + r_script = textwrap.dedent(f""" + suppressPackageStartupMessages(library(org.Ce.eg.db)) + suppressPackageStartupMessages(library(AnnotationDbi)) + + # Validate inputs in R + if (!("{keytype}" %in% keytypes(org.Ce.eg.db))) {{{{ + stop(paste("Invalid keytype:", "{keytype}")) + }}}} + + requested_cols <- c({cols_str}) + valid_cols <- columns(org.Ce.eg.db) + if (!all(requested_cols %in% valid_cols)) {{{{ + invalid_ones <- requested_cols[!(requested_cols %in% valid_cols)] + stop(paste("Invalid columns:", paste(invalid_ones, collapse=", "))) + }}}} + + res <- tryCatch({{{{ + AnnotationDbi::select( + org.Ce.eg.db, + keys = c({keys_str}), + columns = requested_cols, + keytype = "{keytype}" + ) + }}}}, error = function(e) {{{{ + stop(paste("Select failed:", e$message)) + }}}}) + + if ("{output_format}" == "tsv") {{{{ + write.table(res, file = "{out_path}", sep = "\\t", quote = FALSE, row.names = FALSE) + }}}} else {{{{ + write.csv(res, file = "{out_path}", quote = FALSE, row.names = FALSE) + }}}} + """) + + command_str = f"Rscript -e \"{r_script}\"" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + return { + "command_executed": "Rscript -e 'AnnotationDbi::select(...)'", + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(out_path)] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "Rscript -e 'AnnotationDbi::select(...)'", + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +@mcp.tool() +def map_ids( + keys: List[str], + keytype: str, + column: str, + multi_vals: str = "first", +) -> Dict[str, Any]: + """ + Map identifiers from one type to another using the mapIds() method. + This is optimized for 1-to-1 or 1-to-many mapping. + + Args: + keys: List of identifiers to map. + keytype: The type of the input keys (e.g., "WORMBASE"). + column: The type of identifier to map to (e.g., "SYMBOL"). + multi_vals: How to handle multiple matches. Options: "first", "list", "filter", "asNA", "CharacterList". + """ + if not keys: + raise ValueError("The 'keys' list cannot be empty.") + + valid_multi = ["first", "list", "filter", "asNA", "CharacterList"] + if multi_vals not in valid_multi: + raise ValueError(f"multi_vals must be one of {valid_multi}") + + keys_str = ", ".join([f'"{k}"' for k in keys]) + + r_script = textwrap.dedent(f""" + suppressPackageStartupMessages(library(org.Ce.eg.db)) + suppressPackageStartupMessages(library(AnnotationDbi)) + + res <- mapIds( + org.Ce.eg.db, + keys = c({keys_str}), + column = "{column}", + keytype = "{keytype}", + multiVals = "{multi_vals}" + ) + + # Convert to a structured key-value output for easy parsing + cat("---RESULT_START---\\n") + if (is.list(res) || is(res, "CharacterList")) {{{{ + for (n in names(res)) {{{{ + # Handle NULLs which represent no mapping + value <- if (is.null(res[[n]])) "NA" else paste(res[[n]], collapse=",") + cat(n, ":", value, "\\n") + }}}} + }}}} else {{{{ + for (n in names(res)) {{{{ + value <- if (is.na(res[n])) "NA" else res[n] + cat(n, ":", value, "\\n") + }}}} + }}}} + cat("---RESULT_END---\\n") + """) + + command_str = "Rscript -e 'AnnotationDbi::mapIds(...)'" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + + # Parse the structured output from R + output_str = process.stdout + result_block = output_str.split("---RESULT_START---\n")[-1].split("---RESULT_END---")[0].strip() + mappings = {} + if result_block: + for line in result_block.split('\n'): + parts = line.split(':', 1) + if len(parts) == 2: + key = parts[0].strip() + val_str = parts[1].strip() + if val_str == "NA": + mappings[key] = None + else: + values = [v.strip() for v in val_str.split(',')] + if multi_vals in ["first", "asNA"] and len(values) == 1: + mappings[key] = values[0] + else: + mappings[key] = values + + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "mappings": mappings + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +@mcp.tool() +def list_resources() -> Dict[str, Any]: + """ + List all available keytypes and columns (annotation types) in the org.Ce.eg.db package. + """ + r_script = textwrap.dedent(""" + suppressPackageStartupMessages(library(org.Ce.eg.db)) + cat("---COLUMNS_START---\\n") + cat(paste(columns(org.Ce.eg.db), collapse=", "), "\\n") + cat("---COLUMNS_END---\\n") + cat("---KEYTYPES_START---\\n") + cat(paste(keytypes(org.Ce.eg.db), collapse=", "), "\\n") + cat("---KEYTYPES_END---\\n") + """) + + command_str = "Rscript -e 'columns(...); keytypes(...)'" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + + # Parse structured output + stdout_str = process.stdout + columns_str = stdout_str.split("---COLUMNS_START---\n")[1].split("---COLUMNS_END---")[0].strip() + keytypes_str = stdout_str.split("---KEYTYPES_START---\n")[1].split("---KEYTYPES_END---")[0].strip() + + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "columns": columns_str.split(", ") if columns_str else [], + "keytypes": keytypes_str.split(", ") if keytypes_str else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +@mcp.tool() +def get_db_metadata() -> Dict[str, Any]: + """ + Retrieve metadata about the org.Ce.eg.db package, such as version, species, and data sources. + """ + r_script = "suppressPackageStartupMessages(library(org.Ce.eg.db)); print(metadata(org.Ce.eg.db))" + command_str = f"Rscript -e \"{r_script}\"" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "metadata": process.stdout.strip().split('\n') + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +@mcp.tool() +def get_available_keys( + keytype: str, + pattern: Optional[str] = None, + limit: int = 100 +) -> Dict[str, Any]: + """ + Retrieve a list of all available keys for a specific keytype, optionally filtered by a pattern. + + Args: + keytype: The type of keys to retrieve (e.g., "SYMBOL"). + pattern: An optional regular expression pattern to filter keys. + limit: Maximum number of keys to return. Defaults to 100. + """ + if limit <= 0: + raise ValueError("Limit must be a positive integer.") + + pattern_arg = f', pattern = "{pattern}"' if pattern else "" + + r_script = textwrap.dedent(f""" + suppressPackageStartupMessages(library(org.Ce.eg.db)) + all_keys <- keys(org.Ce.eg.db, keytype = "{keytype}"{pattern_arg}) + cat(head(all_keys, {limit}), sep="\\n") + cat("\\n---TOTAL_COUNT:", length(all_keys), "---\\n") + """) + + command_str = f"Rscript -e 'keys(..., keytype=\"{keytype}\")'" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + + # Robust parsing + output_str = process.stdout + if "---TOTAL_COUNT:" in output_str: + keys_part, total_part = output_str.split("---TOTAL_COUNT:") + keys_list = keys_part.strip().split("\n") if keys_part.strip() else [] + total_count = int(total_part.replace("---", "").strip()) + else: + keys_list = output_str.strip().split("\n") if output_str.strip() else [] + total_count = len(keys_list) + + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "keys": keys_list, + "total_available": total_count + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +@mcp.tool() +def get_species_info() -> Dict[str, Any]: + """ + Get the species name for which this annotation package was designed. + """ + r_script = "suppressPackageStartupMessages(library(org.Ce.eg.db)); cat(species(org.Ce.eg.db))" + command_str = f"Rscript -e \"{r_script}\"" + try: + process = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + return { + "command_executed": command_str, + "stdout": process.stdout, + "stderr": process.stderr, + "species": process.stdout.strip() + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R script execution failed. Stderr: {e.stderr}" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6ff2495121e19a778787cd455ad536f7302f10e0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_org_ce_eg_db' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b0aca5cb848af22025e1277befdd8c28f521b8bd --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-org.ce.eg.db: + build: . + image: mcp-bioconductor-org.ce.eg.db:latest + container_name: mcp-bioconductor-org.ce.eg.db + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-org.ce.eg.db + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e4b02b7acc6a22c38fee1f3d8401b748f0cfde91 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-org.ce.eg.db + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-org.ce.eg.db/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-rsubread/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-rsubread/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e2725ed890f216390d4ca45f72fe80e813511c82 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rsubread/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-rsubread: + build: . + image: mcp-bioconductor-rsubread:latest + container_name: mcp-bioconductor-rsubread + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-rsubread + 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-rsubread/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-rsubread/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fdd1061efad1bac463df38b14d847bf8a1798168 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rsubread/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-rsubread + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-rsubread/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-rsubread/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-rsubread/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6935960696a97733edffdcdabac83c9f0cfb8ff3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/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-scrnaseq via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-scrnaseq -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-scrnaseq_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-scrnaseq_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-scrnaseq_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_server.py b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_server.py new file mode 100644 index 0000000000000000000000000000000000000000..35132396db2a1e00b1072e78ccfe289f22beb786 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_server.py @@ -0,0 +1,124 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# @mcp.tool is a placeholder for the actual decorator in the MCP environment. +# No need to import it in the final code. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_scrnaseq' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_rscript( + script_file: Optional[Path] = None, + expressions: Optional[List[str]] = None, + script_args: Optional[List[str]] = None, + verbose: bool = False, + default_packages: Optional[str] = None, + save: bool = False, + no_environ: bool = False, + no_site_file: bool = False, + no_init_file: bool = False, + restore: bool = False, + vanilla: bool = False, +) -> Dict[str, Any]: + """ + Executes an R script or R expressions using the Rscript command-line tool. + + This tool serves as a wrapper for Rscript, allowing execution of R code from + the command line. It is commonly used to run scripts that utilize libraries + from repositories like Bioconductor (e.g., for scRNA-seq analysis). You must + provide either a script file to run or a list of expressions to evaluate. + + Args: + script_file: Path to the R script file to be executed. Mutually exclusive with 'expressions'. + expressions: A list of R expressions to be executed. Mutually exclusive with 'script_file'. + script_args: A list of arguments to be passed to the R script itself. + verbose: If True, prints information on progress. Corresponds to --verbose. + default_packages: A comma-separated list of package names to be loaded by default, or 'NULL'. Corresponds to --default-packages. + save: If True, saves the workspace at the end of the session. Corresponds to --save. + no_environ: If True, does not read the site and user environment files. Corresponds to --no-environ. + no_site_file: If True, does not read the site-wide Rprofile. Corresponds to --no-site-file. + no_init_file: If True, does not read the user R profile. Corresponds to --no-init-file. + restore: If True, restores previously saved objects at startup. Corresponds to --restore. + vanilla: If True, combines --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + The 'output_files' list will be empty as Rscript itself does not define output files; + any files created are determined by the R code being executed. + """ + # 1. Input Validation + if script_file is None and (expressions is None or not expressions): + raise ValueError("Either 'script_file' or a non-empty 'expressions' list must be provided.") + if script_file is not None and expressions is not None: + raise ValueError("'script_file' and 'expressions' are mutually exclusive and cannot be used together.") + if script_file and not script_file.is_file(): + raise FileNotFoundError(f"Input script file not found or is not a file: {script_file}") + + # 2. Command Construction + cmd = ["Rscript"] + + if verbose: + cmd.append("--verbose") + if default_packages: + cmd.append(f"--default-packages={default_packages}") + if save: + cmd.append("--save") + if no_environ: + cmd.append("--no-environ") + if no_site_file: + cmd.append("--no-site-file") + if no_init_file: + cmd.append("--no-init-file") + if restore: + cmd.append("--restore") + if vanilla: + cmd.append("--vanilla") + + if expressions: + for expr in expressions: + cmd.extend(["-e", expr]) + elif script_file: + cmd.append(str(script_file)) + + if script_args: + cmd.extend(script_args) + + command_executed = " ".join(cmd) + + # 3. Subprocess Execution and Error Handling + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + # This error is caught if 'Rscript' is not in the system's PATH + raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + # This error is caught if Rscript returns a non-zero exit code + return { + "error": "Rscript execution failed with a non-zero exit code.", + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + } + + # 4. Structured Result Return + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5360004e0b33951f786a475fcbb843291d206451 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_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-scrnaseq/app/bioconductor-scrnaseq_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_scrnaseq' + + +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-scrnaseq/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..8549e865843268bd4f159a5c4a3a0ebc0224e44b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-scrnaseq: + build: . + image: mcp-bioconductor-scrnaseq:latest + container_name: mcp-bioconductor-scrnaseq + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-scrnaseq + 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-scrnaseq/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bb6a1f695b37cabd5682568ab5e2361a96feab94 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-scrnaseq + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-scrnaseq/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..472f2be1e951bfd4d358c2f9125bc80c097d48e0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/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-spatialfeatureexperiment via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-spatialfeatureexperiment -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-spatialfeatureexperiment_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-spatialfeatureexperiment_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-spatialfeatureexperiment_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fb2c2b1ffae8cd9af8dff0893292749af5db5fbc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_server.py @@ -0,0 +1,126 @@ +import subprocess +import tempfile +import shutil +from pathlib import Path +from typing import List, Optional + +# MCP decorator placeholder. +# In a real MCP environment, this would be `from mcp import tool`. +def tool(*args, **kwargs): + def decorator(f): + return f + return decorator + +mcp = type("mcp", (), {"tool": tool}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_spatialfeatureexperiment' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_spatialfeatureexperiment_script( + script: Path, + input_files: Optional[List[Path]] = None, + script_args: Optional[List[str]] = None, +) -> dict: + """ + Executes a user-provided R script using the SpatialFeatureExperiment library. + + This tool serves as a generic wrapper to run custom R scripts in an environment + where the bioconductor-spatialfeatureexperiment package and its dependencies are + installed. The script should be written to read any necessary input files from + its working directory and write all output files to the same directory. + + Args: + script: Path to the R script to be executed. This script should load and + use functions from the SpatialFeatureExperiment library. + input_files: An optional list of input data files required by the R script. + These files will be copied to the script's working directory. + script_args: An optional list of string arguments to be passed to the + R script. These can be accessed within R using + `commandArgs(trailingOnly = TRUE)`. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list + of paths to the output files generated by the script. + """ + # 1. Input validation + if not script.is_file(): + raise FileNotFoundError(f"The provided R script does not exist: {script}") + + if input_files: + for f in input_files: + if not f.is_file(): + raise FileNotFoundError(f"Input file does not exist: {f}") + + # 2. Create a temporary directory for isolated execution + with tempfile.TemporaryDirectory() as temp_dir_str: + temp_dir = Path(temp_dir_str) + + # 3. Stage input files into the temporary directory + staged_script_path = temp_dir / script.name + shutil.copy(script, staged_script_path) + + if input_files: + for f in input_files: + shutil.copy(f, temp_dir / f.name) + + # Keep track of initial files to easily identify outputs later + initial_files = {p.name for p in temp_dir.iterdir()} + + # 4. Construct the command to be executed + command = ["Rscript", str(staged_script_path)] + if script_args: + command.extend(script_args) + + command_str = " ".join(command) + + # 5. Execute the R script + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + cwd=temp_dir, # Run the script within the temp directory + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + # This error occurs if 'Rscript' is not in the system's PATH + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'Rscript' command not found. Make sure R is installed and in the system's PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + # This error occurs if the R script exits with a non-zero status code + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # 6. Identify and collect output files + # Any file created in the temp_dir that was not an input is an output + final_files = {p.name for p in temp_dir.iterdir()} + output_filenames = list(final_files - initial_files) + + # The MCP framework will handle moving these files from the temp dir + # to the final output location. We just need to provide their paths. + output_paths = [str(temp_dir / fname) for fname in output_filenames] + + # 7. Return structured output + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": output_paths, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3ac50405505e3c79fcdf9bfb210570a81e6ab0a5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_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-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_spatialfeatureexperiment' + + +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-spatialfeatureexperiment/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..518b433cff1c61e3709e8ccb40a69f49b7e4eebd --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-spatialfeatureexperiment: + build: . + image: mcp-bioconductor-spatialfeatureexperiment:latest + container_name: mcp-bioconductor-spatialfeatureexperiment + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-spatialfeatureexperiment + 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-spatialfeatureexperiment/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..df443e9ef61006cd0de22376498c186555b07420 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-spatialfeatureexperiment + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-spatialfeatureexperiment/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a6923eb7656e2bbe5d0dade84049b3c8f40e5046 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/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-tenxvisiumdata via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-tenxvisiumdata -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-tenxvisiumdata_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-tenxvisiumdata_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-tenxvisiumdata_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_server.py b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b02205f40e832914ad576d1f1ca3831ad669ef85 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_server.py @@ -0,0 +1,125 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +# The mcp.tool decorator is supplied by the MCP framework. +# It is not needed for the code to be syntactically correct. +# We will assume it is imported from a module named "mcp". +# from mcp import tool as mcp_tool +# +# As per the instructions, we will define the function without the import. +# The user of this code will need to provide the mcp.tool decorator. + +# @mcp.tool +def tenxvisiumdata( + dataset: Optional[str] = None, + output_rds: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Accesses Visium spatial gene expression data from 10X Genomics. + + This tool can either list all available datasets or download a specific dataset + as a SpatialExperiment object saved in an RDS file. + + - To list datasets: Call the function without any parameters. + - To download a dataset: Provide both the 'dataset' name and an 'output_rds' path. + """ + # --- Input Validation --- + if (dataset is None and output_rds is not None) or \ + (dataset is not None and output_rds is None): + raise ValueError( + "Both 'dataset' and 'output_rds' must be provided together to download data, or both must be omitted to list available datasets." + ) + + # --- R Script Generation --- + r_script_content = "" + cmd_args: List[str] = [] + + if dataset and output_rds: + # --- Download Mode --- + # Ensure the output directory exists + output_rds.parent.mkdir(parents=True, exist_ok=True) + + r_script_content = f""" + # Ensure required libraries are loaded + if (!requireNamespace("TENxVisiumData", quietly = TRUE)) {{ + stop("R package 'TENxVisiumData' is not installed. Please install it from Bioconductor.", call. = FALSE) + }} + library(TENxVisiumData) + + # Get arguments from the command line + args <- commandArgs(trailingOnly=TRUE) + if (length(args) != 2) {{ + stop("Internal error: Expected 2 arguments: ", call. = FALSE) + }} + dataset_name <- args[1] + output_path <- args[2] + + # Download the dataset from ExperimentHub + message("Downloading dataset: ", dataset_name) + spe_object <- TENxVisiumData(dataset = dataset_name) + message("Download complete.") + + # Save the SpatialExperiment object to an RDS file + message("Saving object to: ", output_path) + saveRDS(spe_object, file = output_path) + message("Save complete. Output file is ready at ", output_path) + """ + cmd_args = [dataset, str(output_rds)] + else: + # --- List Mode --- + r_script_content = """ + # Ensure required libraries are loaded + if (!requireNamespace("TENxVisiumData", quietly = TRUE)) { + stop("R package 'TENxVisiumData' is not installed. Please install it from Bioconductor.", call. = FALSE) + } + library(TENxVisiumData) + + # Get the table of available datasets from ExperimentHub + message("Fetching list of available datasets...") + available_datasets <- TENxVisiumData() + + # Print the relevant columns to stdout for parsing + print(available_datasets[, c("EHID", "Title", "RDataPath")]) + """ + + # --- Subprocess Execution --- + with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file: + r_script_file.write(r_script_content) + r_script_path = r_script_file.name + + command = ["Rscript", r_script_path] + cmd_args + command_executed = " ".join(command) + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + raise RuntimeError("Rscript executable not found. Please ensure R is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + # R scripts often print errors to stderr, so we return it for debugging + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [], + } + finally: + # Clean up the temporary R script file + Path(r_script_path).unlink() + + # --- Structured Result Return --- + output_files = [str(output_rds)] if output_rds else [] + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cf06be2500b277a239dc71b9063386e3be3a9e97 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_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-tenxvisiumdata/app/bioconductor-tenxvisiumdata_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_tenxvisiumdata' + + +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-tenxvisiumdata/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..715a81bd3eb249ba92251f09bca90b444af71b45 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-tenxvisiumdata: + build: . + image: mcp-bioconductor-tenxvisiumdata:latest + container_name: mcp-bioconductor-tenxvisiumdata + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-tenxvisiumdata + 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-tenxvisiumdata/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..91fbb8ace2d32e250e876f3072bf8f346e0d80b0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-tenxvisiumdata + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxvisiumdata/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-tenxxeniumdata/app/bioconductor-tenxxeniumdata_server.py b/Biomni/mcp_generated/mcp_bioconductor-tenxxeniumdata/app/bioconductor-tenxxeniumdata_server.py new file mode 100644 index 0000000000000000000000000000000000000000..671a76da33e4360dc8051609c5b569cd850884b7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-tenxxeniumdata/app/bioconductor-tenxxeniumdata_server.py @@ -0,0 +1,151 @@ +import subprocess +from pathlib import Path +from typing import Optional, List +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# MCP decorator is assumed to be available in the execution environment. +# No need to import it. +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_tenxxeniumdata' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() + +@mcp.tool() +def load_tenx_xenium_data( + dataset: str, + output_rds: Path, + cache_dir: Optional[Path] = None, +): + """ + Loads a 10x Genomics Xenium dataset from the TENxXeniumData Bioconductor package. + + This tool fetches a specified dataset using ExperimentHub and saves it as an + R Data Serializable (RDS) file. The resulting object is either a SpatialExperiment + or a SpatialFeatureExperiment (SFE) class from Bioconductor. + + Args: + dataset (str): The name of the dataset to load. Must be one of the following: + - "Xenium_Human_Breast_Cancer_Rep1_SFE" + - "Xenium_Human_Breast_Cancer_Rep1_SE" + - "Xenium_Human_Breast_Cancer_Rep2_SFE" + - "Xenium_Human_Breast_Cancer_Rep2_SE" + - "Xenium_Human_Lung_Cancer_Rep1_SFE" + - "Xenium_Human_Lung_Cancer_Rep1_SE" + - "Xenium_Mouse_Brain_Coronal_s1_SFE" + - "Xenium_Mouse_Brain_Coronal_s1_SE" + - "Xenium_Mouse_Brain_Posterior_s1_SFE" + - "Xenium_Mouse_Brain_Posterior_s1_SE" + - "Xenium_Mouse_Puja_Brain_SFE" + - "Xenium_Mouse_Puja_Brain_SE" + output_rds (Path): The path to save the output RDS file. + cache_dir (Optional[Path]): An optional directory for ExperimentHub to cache the downloaded data. + If not provided, the default ExperimentHub cache location will be used. + + Returns: + dict: A dictionary containing the execution details and output file path. + """ + # 1. Input Validation + available_datasets = [ + "Xenium_Human_Breast_Cancer_Rep1_SFE", + "Xenium_Human_Breast_Cancer_Rep1_SE", + "Xenium_Human_Breast_Cancer_Rep2_SFE", + "Xenium_Human_Breast_Cancer_Rep2_SE", + "Xenium_Human_Lung_Cancer_Rep1_SFE", + "Xenium_Human_Lung_Cancer_Rep1_SE", + "Xenium_Mouse_Brain_Coronal_s1_SFE", + "Xenium_Mouse_Brain_Coronal_s1_SE", + "Xenium_Mouse_Brain_Posterior_s1_SFE", + "Xenium_Mouse_Brain_Posterior_s1_SE", + "Xenium_Mouse_Puja_Brain_SFE", + "Xenium_Mouse_Puja_Brain_SE", + ] + if dataset not in available_datasets: + raise ValueError( + f"Invalid dataset '{dataset}'. Must be one of: {', '.join(available_datasets)}" + ) + + # 2. File Path Handling + try: + output_rds.parent.mkdir(parents=True, exist_ok=True) + if cache_dir: + cache_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + logger.error(f"Error creating directory: {e}") + raise + + # 3. Construct R script for execution + r_script_lines = [ + 'suppressPackageStartupMessages(library(TENxXeniumData))', + 'suppressPackageStartupMessages(library(ExperimentHub))', + ] + + if cache_dir: + # R paths need to be correctly formatted (forward slashes) + r_cache_path = str(cache_dir.resolve()).replace('\\', '/') + r_script_lines.append(f'setExperimentHubOption("CACHE", "{r_cache_path}")') + + r_output_path = str(output_rds.resolve()).replace('\\', '/') + r_script_lines.extend([ + f'data_object <- {dataset}()', + f'saveRDS(data_object, file = "{r_output_path}")', + 'cat("Successfully loaded and saved dataset to RDS file.\\n")' + ]) + + r_script = "; ".join(r_script_lines) + + # 4. Subprocess Execution + cmd = ["Rscript", "-e", r_script] + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + # Handle case where Rscript is not in PATH + error_message = "Error: Rscript not found. Please ensure R is installed and in your system's PATH." + logger.error(error_message) + return { + "command_executed": command_executed, + "stdout": "", + "stderr": error_message, + "output_files": [], + "return_code": 1 + } + except subprocess.CalledProcessError as e: + logger.error(f"R script execution failed with return code {e.returncode}") + logger.error(f"Stderr: {e.stderr}") + logger.error(f"Stdout: {e.stdout}") + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "return_code": e.returncode + } + + # 5. Structured Result Return + output_files = [str(output_rds)] if output_rds.exists() else [] + if not output_files: + logger.warning(f"Output file {output_rds} was not created.") + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + "return_code": result.returncode + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-xenlite/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-xenlite/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..bbd4d4ba8469b277ec49c242cb72079dc285911e --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/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-xenlite via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-xenlite -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-xenlite_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-xenlite_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-xenlite_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_server.py b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8211801776121991de56767265f116ba4ca5375a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_server.py @@ -0,0 +1,311 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_xenlite' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def xenlite_import_xenium( + data_dir: str, + output_rds: str, +) -> Dict[str, Any]: + """ + Imports Xenium spatial transcriptomics data from a directory and saves it as a xenLite R object. + + Args: + data_dir: Path to the directory containing Xenium output files (e.g., transcripts.parquet, cell_feature_matrix.h5). + output_rds: Path where the resulting xenLite RDS file will be saved. + """ + input_path = Path(data_dir) + output_path = Path(output_rds) + + if not input_path.is_dir(): + return {"error": f"Input directory does not exist: {data_dir}"} + + # Ensure output directory exists + output_path.parent.mkdir(parents=True, exist_ok=True) + + # R script to load the data using xenLite + r_script = f""" + library(xenLite) + tryCatch({{ + # The package typically uses a constructor to point to the Xenium output directory + # Based on documentation, it manages parquet and SpatialExperiment components + x_obj <- XeniumExperiment("{str(input_path)}") + saveRDS(x_obj, "{str(output_path)}") + cat("Successfully imported Xenium data to {str(output_path)}") + }}, error = function(e) {{ + stop(e$message) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e 'library(xenLite); ...'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr, + "command_executed": e.cmd + } + +@mcp.tool() +def xenlite_to_spatial_experiment( + rds_path: str, + output_rds: str, +) -> Dict[str, Any]: + """ + Converts a xenLite object into a standard Bioconductor SpatialExperiment object. + + Args: + rds_path: Path to the xenLite RDS file. + output_rds: Path where the resulting SpatialExperiment RDS file will be saved. + """ + input_path = Path(rds_path) + output_path = Path(output_rds) + + if not input_path.exists(): + return {"error": f"Input RDS file not found: {rds_path}"} + + r_script = f""" + library(xenLite) + library(SpatialExperiment) + tryCatch({{ + x_obj <- readRDS("{str(input_path)}") + # xenLite objects are designed to be easily converted to SpatialExperiment + spe <- as(x_obj, "SpatialExperiment") + saveRDS(spe, "{str(output_path)}") + cat("Successfully converted to SpatialExperiment") + }}, error = function(e) {{ + stop(e$message) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e '...as(obj, \"SpatialExperiment\")...'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "Conversion failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def xenlite_extract_transcripts( + rds_path: str, + output_csv: str, + gene_filter: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Extracts transcript coordinates from a xenLite object and saves them to a CSV file. + + Args: + rds_path: Path to the xenLite RDS file. + output_csv: Path to save the transcript coordinates (CSV). + gene_filter: Optional list of gene names to filter the transcripts. + """ + input_path = Path(rds_path) + output_path = Path(output_csv) + + if not input_path.exists(): + return {"error": f"Input RDS file not found: {rds_path}"} + + gene_filter_r = "NULL" + if gene_filter: + genes_str = ", ".join([f'"{g}"' for g in gene_filter]) + gene_filter_r = f"c({genes_str})" + + r_script = f""" + library(xenLite) + library(dplyr) + tryCatch({{ + x_obj <- readRDS("{str(input_path)}") + # Access transcripts (usually stored in parquet via arrow) + tx <- transcripts(x_obj) + + genes <- {gene_filter_r} + if (!is.null(genes)) {{ + tx <- tx %>% filter(feature_name %in% genes) + }} + + write.csv(as.data.frame(tx), "{str(output_path)}", row.names = FALSE) + cat("Extracted transcripts to {str(output_path)}") + }}, error = function(e) {{ + stop(e$message) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e '...transcripts(x_obj)...'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "Transcript extraction failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def xenlite_get_summary( + rds_path: str, +) -> Dict[str, Any]: + """ + Returns a summary of the Xenium dataset stored in a xenLite object. + + Args: + rds_path: Path to the xenLite RDS file. + """ + input_path = Path(rds_path) + + if not input_path.exists(): + return {"error": f"Input RDS file not found: {rds_path}"} + + r_script = f""" + library(xenLite) + tryCatch({{ + x_obj <- readRDS("{str(input_path)}") + print(x_obj) + cat("\\n--- Assay Data ---\\n") + spe <- as(x_obj, "SpatialExperiment") + cat("Number of cells: ", ncol(spe), "\\n") + cat("Number of genes: ", nrow(spe), "\\n") + cat("Assays: ", paste(assayNames(spe), collapse=", "), "\\n") + }}, error = function(e) {{ + stop(e$message) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e 'print(x_obj)'", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "Summary generation failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def xenlite_plot_spatial( + rds_path: str, + output_png: str, + gene_name: Optional[str] = None, + sample_id: str = "sample01", +) -> Dict[str, Any]: + """ + Generates a spatial plot of the Xenium data. + + Args: + rds_path: Path to the xenLite RDS file. + output_png: Path to save the plot image (PNG). + gene_name: Optional gene name to color by expression. + sample_id: The sample identifier within the object. + """ + input_path = Path(rds_path) + output_path = Path(output_png) + + if not input_path.exists(): + return {"error": f"Input RDS file not found: {rds_path}"} + + gene_expr_r = "" + if gene_name: + gene_expr_r = f', colour_by = "{gene_name}"' + + r_script = f""" + library(xenLite) + library(SpatialExperiment) + library(ggplot2) + tryCatch({{ + x_obj <- readRDS("{str(input_path)}") + spe <- as(x_obj, "SpatialExperiment") + + # Basic spatial plot using ggplot2 as suggested by dependencies + df <- as.data.frame(cbind(spatialCoords(spe), colData(spe))) + + if ("{gene_name}" != "NULL" && "{gene_name}" %in% rownames(spe)) {{ + df$expression <- assay(spe, "counts")["{gene_name}", ] + p <- ggplot(df, aes(x = x_location, y = y_location, color = expression)) + + geom_point(size = 0.1) + + scale_color_viridis_c() + + theme_minimal() + + labs(title = paste("Expression of", "{gene_name}")) + }} else {{ + p <- ggplot(df, aes(x = x_location, y = y_location)) + + geom_point(size = 0.1, color = "lightgrey") + + theme_minimal() + + labs(title = "Spatial Cell Locations") + }} + + ggsave("{str(output_path)}", plot = p, width = 10, height = 8) + cat("Plot saved to {str(output_path)}") + }}, error = function(e) {{ + stop(e$message) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "Rscript -e 'ggplot2 plotting...'", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_path)] + } + except subprocess.CalledProcessError as e: + return { + "error": "Plotting failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..17e53e451da3e8ca7823990285195ac34ecae24b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/bioconductor-xenlite_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-xenlite/app/bioconductor-xenlite_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_xenlite' + + +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-xenlite/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-xenlite/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-xenlite/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea6305dbf390f75f34fead8fcaafeafb8e02feb1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-xenlite: + build: . + image: mcp-bioconductor-xenlite:latest + container_name: mcp-bioconductor-xenlite + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-xenlite + 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-xenlite/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-xenlite/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..84fdb4485a585c36e86f691ff4826496f41992c8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-xenlite + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-xenlite/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-xenlite/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-xenlite/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_cnmf/Dockerfile b/Biomni/mcp_generated/mcp_cnmf/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7dcdcdc1f551d13aa67f84f292057bb9dabe9211 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/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 cnmf via conda (e.g., from bioconda) +RUN conda install -c bioconda cnmf -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 cnmf_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/cnmf_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/cnmf_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cnmf/app/cnmf_server.py b/Biomni/mcp_generated/mcp_cnmf/app/cnmf_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cea79e7b431c833335a32226b182aab81bffe179 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/app/cnmf_server.py @@ -0,0 +1,356 @@ +import subprocess +import shlex +from pathlib import Path +from typing import List, Optional + +# MCP decorator is commented out as per instructions +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_cnmf' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def cnmf_prepare( + counts: Path, + components: List[int], + n_iter: int, + name: str, + output_dir: Path, + total_workers: Optional[int] = None, + seed: Optional[int] = None, + genes_file: Optional[Path] = None, + numgenes: Optional[int] = None, + tpm: Optional[Path] = None, + max_nmf_iter: int = 1000, + beta_loss: str = "frobenius", + init: str = "random", + densify: bool = False, +) -> dict: + """ + Prepares the data and directory structure for a cNMF analysis. + + This step filters the count matrix, selects high-variance genes, + and sets up the output directory for subsequent factorization runs. + """ + # --- Input Validation --- + if not counts.is_file(): + raise FileNotFoundError(f"Input counts file not found: {counts}") + if genes_file and not genes_file.is_file(): + raise FileNotFoundError(f"Genes file not found: {genes_file}") + if tpm and not tpm.is_file(): + raise FileNotFoundError(f"TPM file not found: {tpm}") + + if not components: + raise ValueError("At least one component value (-k/--components) must be provided.") + if n_iter <= 0: + raise ValueError("--n-iter must be a positive integer.") + if max_nmf_iter <= 0: + raise ValueError("--max-nmf-iter must be a positive integer.") + if numgenes is not None and numgenes <= 0: + raise ValueError("--numgenes must be a positive integer.") + + beta_loss_choices = ["frobenius", "kullback-leibler", "itakura-saito"] + if beta_loss not in beta_loss_choices: + raise ValueError(f"--beta-loss must be one of {beta_loss_choices}, not '{beta_loss}'") + + init_choices = ["random", "nndsvd"] + if init not in init_choices: + raise ValueError(f"--init must be one of {init_choices}, not '{init}'") + + # --- Command Construction --- + cmd = ["cnmf", "prepare"] + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--name", name]) + cmd.extend(["--counts", str(counts)]) + cmd.extend(["--components", *[str(k) for k in components]]) + cmd.extend(["--n-iter", str(n_iter)]) + + if total_workers is not None: + cmd.extend(["--total-workers", str(total_workers)]) + if seed is not None: + cmd.extend(["--seed", str(seed)]) + if genes_file: + cmd.extend(["--genes-file", str(genes_file)]) + if numgenes is not None: + cmd.extend(["--numgenes", str(numgenes)]) + if tpm: + cmd.extend(["--tpm", str(tpm)]) + if max_nmf_iter != 1000: + cmd.extend(["--max-nmf-iter", str(max_nmf_iter)]) + if beta_loss != "frobenius": + cmd.extend(["--beta-loss", beta_loss]) + if init != "random": + cmd.extend(["--init", init]) + if densify: + cmd.append("--densify") + + # --- Subprocess Execution --- + try: + # Ensure output directory exists + output_dir.mkdir(parents=True, exist_ok=True) + + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return { + "command_executed": shlex.join(cmd), + "stdout": "", + "stderr": "Error: 'cnmf' command not found. Please ensure it is in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": shlex.join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + # The prepare step creates the main output directory for the analysis. + output_path = output_dir / name + return { + "command_executed": shlex.join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_path)] + } + + +@mcp.tool() +def cnmf_factorize( + name: str, + output_dir: Path, + worker_index: Optional[int] = None, + total_workers: Optional[int] = None, + skip_completed_runs: bool = False, +) -> dict: + """ + Runs the NMF factorizations for a cNMF analysis. + + This step can be parallelized across multiple workers. + """ + # --- Input Validation --- + if worker_index is not None and worker_index < 0: + raise ValueError("--worker-index must be a non-negative integer.") + if total_workers is not None and total_workers <= 0: + raise ValueError("--total-workers must be a positive integer.") + + # --- Command Construction --- + cmd = ["cnmf", "factorize"] + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--name", name]) + + if worker_index is not None: + cmd.extend(["--worker-index", str(worker_index)]) + if total_workers is not None: + cmd.extend(["--total-workers", str(total_workers)]) + if skip_completed_runs: + cmd.append("--skip-completed-runs") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return { + "command_executed": shlex.join(cmd), + "stdout": "", + "stderr": "Error: 'cnmf' command not found. Please ensure it is in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": shlex.join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + # Factorize populates the directory created by 'prepare'. + output_path = output_dir / name + return { + "command_executed": shlex.join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_path)] + } + + +@mcp.tool() +def cnmf_combine( + name: str, + output_dir: Path, +) -> dict: + """ + Combines the factorization results from multiple workers into a single file. + """ + # --- Command Construction --- + cmd = ["cnmf", "combine"] + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--name", name]) + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return { + "command_executed": shlex.join(cmd), + "stdout": "", + "stderr": "Error: 'cnmf' command not found. Please ensure it is in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": shlex.join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + # Combine creates merged files within the main output directory. + output_path = output_dir / name + return { + "command_executed": shlex.join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(output_path)] + } + + +@mcp.tool() +def cnmf_consensus( + name: str, + output_dir: Path, + local_density_threshold: Optional[float] = None, + local_neighborhood_size: Optional[float] = None, + show_clustering: bool = False, + build_reference: bool = False, +) -> dict: + """ + Performs consensus clustering and filtering on the factorization results. + """ + # --- Input Validation --- + if local_density_threshold is not None: + if not (0 < local_density_threshold <= 2): + raise ValueError("--local-density-threshold must be a float > 0 and <= 2.") + if local_neighborhood_size is not None and not (0 < local_neighborhood_size <= 1): + raise ValueError("--local-neighborhood-size must be a float > 0 and <= 1.") + + # --- Command Construction --- + cmd = ["cnmf", "consensus"] + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--name", name]) + + if local_density_threshold is not None: + cmd.extend(["--local-density-threshold", str(local_density_threshold)]) + if local_neighborhood_size is not None: + cmd.extend(["--local-neighborhood-size", str(local_neighborhood_size)]) + if show_clustering: + cmd.append("--show-clustering") + if build_reference: + cmd.append("--build-reference") + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return { + "command_executed": shlex.join(cmd), + "stdout": "", + "stderr": "Error: 'cnmf' command not found. Please ensure it is in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": shlex.join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + output_path = output_dir / name + outputs = [str(output_path)] + # Assuming standard output file names for optional plots + if show_clustering: + outputs.append(str(output_path / f"{name}.clustering.pdf")) + + return { + "command_executed": shlex.join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": outputs + } + + +@mcp.tool() +def cnmf_k_selection_plot( + name: str, + output_dir: Path, +) -> dict: + """ + Generates plots to help select the number of components (K). + """ + # --- Command Construction --- + cmd = ["cnmf", "k_selection_plot"] + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--name", name]) + + # --- Subprocess Execution --- + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + return { + "command_executed": shlex.join(cmd), + "stdout": "", + "stderr": "Error: 'cnmf' command not found. Please ensure it is in your PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": shlex.join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + output_path = output_dir / name + # Assuming a standard output file name for the plot + plot_file = output_path / f"{name}.k_selection.pdf" + + return { + "command_executed": shlex.join(cmd), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(plot_file)] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_cnmf/app/cnmf_shim_server.py b/Biomni/mcp_generated/mcp_cnmf/app/cnmf_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1146f686e572ce0298bf892dec9e06de065aa7fa --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/app/cnmf_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_cnmf/app/cnmf_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_cnmf' + + +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_cnmf/docker-compose.yml b/Biomni/mcp_generated/mcp_cnmf/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..635e5916557072f02007e3ba1681195e4dc5f7ba --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-cnmf: + build: . + image: mcp-cnmf:latest + container_name: mcp-cnmf + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=cnmf + 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_cnmf/environment.yaml b/Biomni/mcp_generated/mcp_cnmf/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..013ba86cf1efcb8a1a6aa0a45222e3626eec9499 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - cnmf + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cnmf/requirements.txt b/Biomni/mcp_generated/mcp_cnmf/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnmf/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_deblur/Dockerfile b/Biomni/mcp_generated/mcp_deblur/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..aa5593e7ae68e9f1a9a23f948bcd86efa452307e --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/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 deblur via conda (e.g., from bioconda) +RUN conda install -c bioconda deblur -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/deblur_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/deblur_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/deblur_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_deblur/app/deblur_server.py b/Biomni/mcp_generated/mcp_deblur/app/deblur_server.py new file mode 100644 index 0000000000000000000000000000000000000000..47c22b233383a8305fe7a25947f5b88b23c864dd --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/app/deblur_server.py @@ -0,0 +1,155 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +def tool(*args, **kwargs): + def decorator(func): + return func + return decorator + +mcp = type("mcp", (), {"tool": tool}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_deblur' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def deblur_workflow( + seqs_fp: Path, + output_dir: Path, + trim_length: int, + min_reads: int = 10, + threads_to_use: int = 1, + pos_ref_fp: Optional[Path] = None, + neg_ref_fp: Optional[Path] = None, +) -> dict: + """ + Run the Deblur workflow for denoising amplicon sequencing data. + + Deblur is a greedy deconvolution algorithm that leverages known error profiles + to identify and remove sequencing noise, resulting in high-resolution sOTUs + (sub-Operational Taxonomic Units). This workflow command is the primary + entry point for running the full Deblur pipeline. + + Args: + seqs_fp: Path to the input sequences. This can be a single demultiplexed + FASTA/FASTQ file or a directory containing multiple per-sample + FASTA/FASTQ files. Files can be gzipped. + output_dir: Path to the directory where output files will be written. + It will be created if it does not exist. + trim_length: The length to trim all sequences to. All reads shorter + than this length will be discarded. A value of -1 + disables trimming, which is only recommended if all + reads are already of a uniform length. + min_reads: The minimum number of reads for an sOTU to be retained. + This filter is applied across all samples. Default is 10. + Set to 0 to disable this filtering step. + threads_to_use: The number of threads to use for parallel processing. + Default is 1. + pos_ref_fp: Path to the FASTA file for positive filtering. sOTUs that + align to this reference are kept in the 'reference-hit' + output. By default, a 16S reference is used. + neg_ref_fp: Path to the FASTA file for negative filtering. sOTUs that + align to this reference (e.g., PhiX, adapters) are + discarded. By default, a PhiX/adapter reference is used. + + Returns: + A dictionary containing the execution details and paths to output files. + """ + # Input validation + if not seqs_fp.exists(): + raise FileNotFoundError(f"Input sequence path does not exist: {seqs_fp}") + if trim_length < -1: + raise ValueError("trim_length must be a positive integer or -1 to disable.") + if min_reads < 0: + raise ValueError("min_reads must be a non-negative integer.") + if threads_to_use <= 0: + raise ValueError("threads_to_use must be a positive integer.") + if pos_ref_fp and not pos_ref_fp.is_file(): + raise FileNotFoundError(f"Positive reference file not found: {pos_ref_fp}") + if neg_ref_fp and not neg_ref_fp.is_file(): + raise FileNotFoundError(f"Negative reference file not found: {neg_ref_fp}") + + # Create output directory if it doesn't exist + output_dir.mkdir(parents=True, exist_ok=True) + + # Construct the command + cmd = [ + "deblur", + "workflow", + "--seqs-fp", + str(seqs_fp), + "--output-dir", + str(output_dir), + "-t", + str(trim_length), + "--min-reads", + str(min_reads), + "-O", + str(threads_to_use), + ] + + if pos_ref_fp: + cmd.extend(["--pos-ref-fp", str(pos_ref_fp)]) + if neg_ref_fp: + cmd.extend(["--neg-ref-fp", str(neg_ref_fp)]) + + command_executed = " ".join(cmd) + + # Execute the command + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "error": "Deblur workflow failed.", + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "deblur command not found. Make sure it is in your PATH.", + "return_code": 1, + "error": "Command not found.", + "output_files": [], + } + + # Identify and list output files + output_files = { + "all_biom": output_dir / "all.biom", + "all_seqs": output_dir / "all.seqs.fa", + "reference_hit_biom": output_dir / "reference-hit.biom", + "reference_hit_seqs": output_dir / "reference-hit.seqs.fa", + "reference_non_hit_biom": output_dir / "reference-non-hit.biom", + "reference_non_hit_seqs": output_dir / "reference-non-hit.seqs.fa", + } + + # Verify that expected output files were created + existing_files = { + key: str(path) for key, path in output_files.items() if path.exists() + } + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "return_code": result.returncode, + "output_files": existing_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_deblur/app/deblur_shim_server.py b/Biomni/mcp_generated/mcp_deblur/app/deblur_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..fb5dc54811351e98d0fe9c96776844db889544fc --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/app/deblur_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_deblur/app/deblur_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_deblur' + + +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_deblur/app/requirements.txt b/Biomni/mcp_generated/mcp_deblur/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_deblur/docker-compose.yml b/Biomni/mcp_generated/mcp_deblur/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..af47e06e09893b3d16fe076ee9d5c2354a929bd7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-deblur: + build: . + image: mcp-deblur:latest + container_name: mcp-deblur + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=deblur + 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_deblur/environment.yaml b/Biomni/mcp_generated/mcp_deblur/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e98338b0570effa7e9116f80d7eb942881c465f5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - deblur + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_deblur/requirements.txt b/Biomni/mcp_generated/mcp_deblur/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_deblur/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_doubletdetection/Dockerfile b/Biomni/mcp_generated/mcp_doubletdetection/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6a3fcfaef99cb0ab7322c7aa6c5dc2ddf197b246 --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/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 doubletdetection via conda (e.g., from bioconda) +RUN conda install -c bioconda doubletdetection -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 doubletdetection_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/doubletdetection_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/doubletdetection_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_doubletdetection/docker-compose.yml b/Biomni/mcp_generated/mcp_doubletdetection/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..eaca19b2143554f45dac9e4dea9e53de725d956c --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-doubletdetection: + build: . + image: mcp-doubletdetection:latest + container_name: mcp-doubletdetection + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=doubletdetection + 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_doubletdetection/requirements.txt b/Biomni/mcp_generated/mcp_doubletdetection/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_doubletdetection/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_genenotebook/requirements.txt b/Biomni/mcp_generated/mcp_genenotebook/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_genenotebook/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gffread/Dockerfile b/Biomni/mcp_generated/mcp_gffread/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..cb8645fc4708e0fb98fef13963f73292dc9b584e --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/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 gffread via conda (e.g., from bioconda) +RUN conda install -c bioconda gffread -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/gffread_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/gffread_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/gffread_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gffread/app/gffread_server.py b/Biomni/mcp_generated/mcp_gffread/app/gffread_server.py new file mode 100644 index 0000000000000000000000000000000000000000..adc131c19bfc4a19ede733fd795326bac0aa2998 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/app/gffread_server.py @@ -0,0 +1,451 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_gffread' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def gffread_tool( + input_gff: Optional[Path] = None, + genomic_seqs: Optional[Path] = None, + seq_info_fsize: Optional[Path] = None, + output_file: Optional[Path] = None, + track_name: Optional[str] = None, + region: Optional[str] = None, + region_fully_contained: bool = False, + junction_match: Optional[str] = None, + no_pseudo: bool = False, + ids_list: Optional[Path] = None, + nids_list: Optional[Path] = None, + max_intron: Optional[int] = None, + min_length: Optional[int] = None, + discard_single_exon: bool = False, + coding_only: bool = False, + non_coding_only: bool = False, + ignore_locus: bool = False, + add_description_attribute: bool = False, + keep_all_gff_attrs: bool = False, + keep_exon_attrs: bool = False, + move_exon_attrs_to_transcript: bool = False, + output_attrs: Optional[str] = None, + keep_genes: bool = False, + keep_comments: bool = False, + process_other_records: bool = False, + discard_in_frame_stop_codons: bool = False, + adjust_cds_phase: bool = False, + check_single_exon_opposite_strand: bool = False, + add_coding_status_attrs: bool = False, + add_has_cds_attribute: bool = False, + adjust_stop_codon: bool = False, + discard_non_canonical_splice_sites: bool = False, + discard_incomplete_cds: bool = False, + input_is_bed: bool = False, + input_is_tlf: bool = False, + stream_processing: bool = False, + merge_transcripts: bool = False, + dup_info_file: Optional[Path] = None, + cluster_only: bool = False, + discard_shorter_contained: bool = False, + relax_boundary_containment: bool = False, + discard_overlapping_single_exon: bool = False, + force_exons: bool = False, + gene_to_exon: bool = False, + transcript_adopt_parent: bool = False, + decode_url_chars: bool = False, + merge_close_exons: bool = False, + output_junctions: bool = False, + exons_fasta_file: Optional[Path] = None, + w_add_bases: Optional[int] = None, + w_no_cds_info: bool = False, + cds_fasta_file: Optional[Path] = None, + tr_cds_fasta_file: Optional[Path] = None, + write_exon_coords_to_defline: bool = False, + use_star_for_stop_codon: bool = False, + ensembl_gtf_to_gff3: bool = False, + chr_replace_map: Optional[Path] = None, + output_gtf: bool = False, + output_bed: bool = False, + output_tlf: bool = False, + output_table: Optional[str] = None, + expose_warnings: bool = False, + sort_alphabetically: bool = False, + sort_by_ref_list: Optional[Path] = None, +) -> dict: + """ + Filter, convert, or cluster GFF/GTF/BED records, extract transcript sequences, and more. + + By default (i.e. without -O) only transcripts are processed, discarding any + other non-transcript features. Default output is a simplified GFF3 with only + the basic attributes. + + Args: + input_gff: Path to the input GFF/GTF/BED file. If not provided, gffread reads from stdin. + genomic_seqs: Full path to a multi-fasta file with genomic sequences, OR a directory + with single-fasta files (one per genomic sequence). Required for sequence extraction + when using options like -w, -x, -y, -V, -B, -P, --adj-stop. + seq_info_fsize: Tab-delimited file providing sequence info for -A option. + Format: . + output_file: Write the output records into this file instead of stdout. + track_name: Use in the 2nd column of each GFF/GTF output line. + region: Only show transcripts overlapping coordinate range .. + (on chromosome/contig , strand if provided). Format: `[]:-`. + region_fully_contained: For `region` option, discard all transcripts that are not fully + contained within the given range. + junction_match: Only output transcripts matching the given junction. Format: `:-`. + no_pseudo: Filter out records matching the 'pseudo' keyword. + ids_list: Discard records/transcripts if their IDs are not listed in this file. + nids_list: Discard records/transcripts if their IDs are listed in this file. + Mutually exclusive with `ids_list`. + max_intron: Discard transcripts having an intron larger than this value. Must be non-negative. + min_length: Discard transcripts shorter than this value (bases). Must be non-negative. + discard_single_exon: Discard single-exon transcripts. + coding_only: Discard mRNAs that have no CDS features. Mutually exclusive with `non_coding_only`. + non_coding_only: Discard mRNAs that have CDS features. Mutually exclusive with `coding_only`. + ignore_locus: Discard locus features and attributes found in the input. + add_description_attribute: Use the description field from `seq_info_fsize` and add it + as the value for a 'descr' attribute to the GFF record. Requires `seq_info_fsize`. + keep_all_gff_attrs: Keep all GFF attributes (for non-exon features). + keep_exon_attrs: For `keep_all_gff_attrs` option, do not attempt to reduce redundant + exon/CDS attributes. + move_exon_attrs_to_transcript: Do not keep exon attributes, move them to the transcript feature + (for GFF3 output). + output_attrs: Only output the GTF/GFF attributes listed in this comma-delimited list. + keep_genes: In transcript-only mode (default), also preserve gene records. + keep_comments: For GFF3 input/output, try to preserve comments. + process_other_records: Process other non-transcript GFF records (by default non-transcript + records are ignored). + discard_in_frame_stop_codons: Discard any mRNAs with CDS having in-frame stop codons. Requires `genomic_seqs`. + adjust_cds_phase: For `discard_in_frame_stop_codons` option, check and adjust the starting CDS phase + if the original phase leads to a translation with an in-frame stop codon. + check_single_exon_opposite_strand: For `discard_in_frame_stop_codons` option, single-exon transcripts + are also checked on the opposite strand. Requires `genomic_seqs`. + add_coding_status_attrs: Add transcript level GFF attributes about the coding status of each + transcript, including partialness or in-frame stop codons. Requires `genomic_seqs`. + add_has_cds_attribute: Add a "hasCDS" attribute with value "true" for transcripts that have CDS features. + adjust_stop_codon: Stop codon adjustment: enables `add_coding_status_attrs` and performs automatic + adjustment of the CDS stop coordinate if premature or downstream. + Implicitly enables -P, which requires `genomic_seqs`. + discard_non_canonical_splice_sites: Discard multi-exon mRNAs that have any intron with a non-canonical + splice site consensus (i.e. not GT-AG, GC-AG or AT-AC). + discard_incomplete_cds: Discard any mRNAs that either lack initial START codon + or the terminal STOP codon, or have an in-frame stop codon + (i.e. only print mRNAs with a complete CDS). + input_is_bed: Input should be parsed as BED format (automatic if the input filename ends with .bed*). + input_is_tlf: Input GFF-like one-line-per-transcript format without exon/CDS features + (automatic if the input filename ends with .tlf). + stream_processing: Fast processing of input GFF/BED transcripts as they are received + (no sorting, exons must be grouped by transcript in the input data). + merge_transcripts: Cluster the input transcripts into loci, discarding "redundant" transcripts + (those with the same exact introns and fully contained or equal boundaries). + Mutually exclusive with `cluster_only`. + dup_info_file: For `merge_transcripts` option, write duplication info to this file. + cluster_only: Same as `merge_transcripts` but without discarding any of the "duplicate" transcripts, + only create "locus" features. Mutually exclusive with `merge_transcripts`. + discard_shorter_contained: For `merge_transcripts` or `cluster_only` option: also discard as redundant + the shorter, fully contained transcripts (intron chains matching a part of the container). + relax_boundary_containment: For `merge_transcripts` or `cluster_only` option, no longer require boundary + containment when assessing redundancy (can be combined with `discard_shorter_contained`). + discard_overlapping_single_exon: For `merge_transcripts` or `cluster_only` option, enforce `relax_boundary_containment` + but also discard overlapping single-exon transcripts, even on the opposite strand + (can be combined with `discard_shorter_contained`). + force_exons: Make sure that the lowest level GFF features are considered "exon" features. + gene_to_exon: For single-line genes not parenting any transcripts, add an exon feature + spanning the entire gene (treat it as a transcript). + transcript_adopt_parent: Try to find a parent gene overlapping/containing a transcript + that does not have any explicit gene Parent. + decode_url_chars: Decode url encoded characters within attributes. + merge_close_exons: Merge very close exons into a single exon (when intron size<4). + output_junctions: Output the junctions and the corresponding transcripts. + exons_fasta_file: Write a fasta file with spliced exons for each transcript. Requires `genomic_seqs`. + w_add_bases: For the `exons_fasta_file` option, extract additional bases + both upstream and downstream of the transcript boundaries. Must be non-negative. + w_no_cds_info: For `exons_fasta_file`, disable the output of CDS info in the FASTA file. + cds_fasta_file: Write a fasta file with spliced CDS for each GFF transcript. Requires `genomic_seqs`. + tr_cds_fasta_file: Write a protein fasta file with the translation of CDS for each record. Requires `genomic_seqs`. + write_exon_coords_to_defline: For `exons_fasta_file`, `cds_fasta_file`, and `tr_cds_fasta_file` options, + write in the FASTA defline all the exon coordinates projected onto the spliced sequence. + use_star_for_stop_codon: For `tr_cds_fasta_file` option, use '*' instead of '.' as stop codon translation. + ensembl_gtf_to_gff3: Ensembl GTF to GFF3 conversion, adds version to IDs. + chr_replace_map: A name mapping table for converting reference sequence names, + having this 2-column format: . + output_gtf: Main output will be GTF instead of GFF3. Mutually exclusive with `output_bed`, `output_tlf`, `output_table`. + output_bed: Output records in BED format instead of default GFF3. Mutually exclusive with `output_gtf`, `output_tlf`, `output_table`. + output_tlf: Output "transcript line format". Mutually exclusive with `output_gtf`, `output_bed`, `output_table`. + output_table: Output a simple tab delimited format instead of GFF, with columns + having the values of GFF attributes given in . + Mutually exclusive with `output_gtf`, `output_bed`, `output_tlf`. + expose_warnings: Expose (warn about) duplicate transcript IDs and other potential + problems with the given GFF/GTF records. + sort_alphabetically: Chromosomes (reference sequences) are sorted alphabetically. + sort_by_ref_list: Sort the reference sequences by the order in which their + names are given in the file. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + command = ["gffread"] + output_files = [] + + # Input validation and command construction + if input_gff: + if not input_gff.is_file(): + raise FileNotFoundError(f"Input GFF file not found: {input_gff}") + command.append(str(input_gff)) + + if genomic_seqs: + if not genomic_seqs.exists(): # Check if it's a file or directory + raise FileNotFoundError(f"Genomic sequences file or directory not found: {genomic_seqs}") + command.extend(["-g", str(genomic_seqs)]) + + if seq_info_fsize: + if not seq_info_fsize.is_file(): + raise FileNotFoundError(f"Sequence info file not found: {seq_info_fsize}") + command.extend(["-s", str(seq_info_fsize)]) + + if ids_list and nids_list: + raise ValueError("Options --ids and --nids are mutually exclusive.") + if ids_list: + if not ids_list.is_file(): + raise FileNotFoundError(f"IDs list file not found: {ids_list}") + command.extend(["--ids", str(ids_list)]) + if nids_list: + if not nids_list.is_file(): + raise FileNotFoundError(f"NIDs list file not found: {nids_list}") + command.extend(["--nids", str(nids_list)]) + + if coding_only and non_coding_only: + raise ValueError("Options -C (coding only) and --nc (non-coding only) are mutually exclusive.") + + if merge_transcripts and cluster_only: + raise ValueError("Options -M/--merge and --cluster-only are mutually exclusive.") + + output_format_options_count = sum([output_gtf, output_bed, output_tlf, output_table is not None]) + if output_format_options_count > 1: + raise ValueError("Only one output format option (-T, --bed, --tlf, --table) can be specified.") + + # Add parameters to command list + if output_file: + command.extend(["-o", str(output_file)]) + output_files.append(str(output_file)) + + if track_name: + command.extend(["-t", track_name]) + if region: + command.extend(["-r", region]) + if region_fully_contained: + command.append("-R") + if junction_match: + command.extend(["--jmatch", junction_match]) + if no_pseudo: + command.append("--no-pseudo") + + if max_intron is not None: + if max_intron < 0: + raise ValueError("max_intron must be a non-negative integer.") + command.extend(["-i", str(max_intron)]) + if min_length is not None: + if min_length < 0: + raise ValueError("min_length must be a non-negative integer.") + command.extend(["-l", str(min_length)]) + if discard_single_exon: + command.append("-U") + if coding_only: + command.append("-C") + if non_coding_only: + command.append("--nc") + if ignore_locus: + command.append("--ignore-locus") + if add_description_attribute: + if not seq_info_fsize: + raise ValueError("Option -A requires -s to be provided.") + command.append("-A") + + if keep_all_gff_attrs: + command.append("-F") + if keep_exon_attrs: + if not keep_all_gff_attrs: + raise ValueError("--keep-exon-attrs requires -F option.") + command.append("--keep-exon-attrs") + if move_exon_attrs_to_transcript: + command.append("-G") + if output_attrs: + command.extend(["--attrs", output_attrs]) + if keep_genes: + command.append("--keep-genes") + if keep_comments: + command.append("--keep-comments") + if process_other_records: + command.append("-O") + + if discard_in_frame_stop_codons: + if not genomic_seqs: + raise ValueError("Option -V requires -g to be provided.") + command.append("-V") + if adjust_cds_phase: + if not discard_in_frame_stop_codons: + raise ValueError("Option -H requires -V option.") + command.append("-H") + if check_single_exon_opposite_strand: + if not discard_in_frame_stop_codons: + raise ValueError("Option -B requires -V option.") + if not genomic_seqs: # Redundant check, but good for clarity + raise ValueError("Option -B requires -g to be provided.") + command.append("-B") + if add_coding_status_attrs: + if not genomic_seqs: + raise ValueError("Option -P requires -g to be provided.") + command.append("-P") + if add_has_cds_attribute: + command.append("--add-hasCDS") + if adjust_stop_codon: + command.append("--adj-stop") + # --adj-stop implicitly enables -P, which requires -g + if not genomic_seqs: + raise ValueError("Option --adj-stop implicitly enables -P, which requires -g to be provided.") + + if discard_non_canonical_splice_sites: + command.append("-N") + if discard_incomplete_cds: + command.append("-J") + if input_is_bed: + command.append("--in-bed") + if input_is_tlf: + command.append("--in-tlf") + if stream_processing: + command.append("--stream") + + if merge_transcripts: + command.append("-M") + if dup_info_file: + if not merge_transcripts: + raise ValueError("Option -d requires -M/--merge option.") + command.extend(["-d", str(dup_info_file)]) + output_files.append(str(dup_info_file)) + if cluster_only: + command.append("--cluster-only") + + # Options -K, -Q, -Y require -M or --cluster-only + if discard_shorter_contained: + if not (merge_transcripts or cluster_only): + raise ValueError("Option -K requires -M/--merge or --cluster-only option.") + command.append("-K") + if relax_boundary_containment: + if not (merge_transcripts or cluster_only): + raise ValueError("Option -Q requires -M/--merge or --cluster-only option.") + command.append("-Q") + if discard_overlapping_single_exon: + if not (merge_transcripts or cluster_only): + raise ValueError("Option -Y requires -M/--merge or --cluster-only option.") + command.append("-Y") + + if force_exons: + command.append("--force-exons") + if gene_to_exon: + command.append("--gene2exon") + if transcript_adopt_parent: + command.append("--t-adopt") + if decode_url_chars: + command.append("-D") + if merge_close_exons: + command.append("-Z") + if output_junctions: + command.append("-j") + + if exons_fasta_file: + if not genomic_seqs: + raise ValueError("Option -w requires -g to be provided.") + command.extend(["-w", str(exons_fasta_file)]) + output_files.append(str(exons_fasta_file)) + if w_add_bases is not None: + if not exons_fasta_file: + raise ValueError("--w-add requires -w option.") + if w_add_bases < 0: + raise ValueError("w_add_bases must be a non-negative integer.") + command.extend(["--w-add", str(w_add_bases)]) + if w_no_cds_info: + if not exons_fasta_file: + raise ValueError("--w-nocds requires -w option.") + command.append("--w-nocds") + + if cds_fasta_file: + if not genomic_seqs: + raise ValueError("Option -x requires -g to be provided.") + command.extend(["-x", str(cds_fasta_file)]) + output_files.append(str(cds_fasta_file)) + if tr_cds_fasta_file: + if not genomic_seqs: + raise ValueError("Option -y requires -g to be provided.") + command.extend(["-y", str(tr_cds_fasta_file)]) + output_files.append(str(tr_cds_fasta_file)) + + if write_exon_coords_to_defline: + if not (exons_fasta_file or cds_fasta_file or tr_cds_fasta_file): + raise ValueError("Option -W requires at least one of -w, -x, or -y options.") + command.append("-W") + if use_star_for_stop_codon: + if not tr_cds_fasta_file: + raise ValueError("Option -S requires -y option.") + command.append("-S") + + if ensembl_gtf_to_gff3: + command.append("-L") + if chr_replace_map: + if not chr_replace_map.is_file(): + raise FileNotFoundError(f"Chromosome replacement map file not found: {chr_replace_map}") + command.extend(["-m", str(chr_replace_map)]) + + if output_gtf: + command.append("-T") + if output_bed: + command.append("--bed") + if output_tlf: + command.append("--tlf") + if output_table: + command.extend(["--table", output_table]) + + if expose_warnings: + command.append("-E") + + if sort_alphabetically: + command.append("--sort-alpha") + if sort_by_ref_list: + if not sort_by_ref_list.is_file(): + raise FileNotFoundError(f"Reference list file for sorting not found: {sort_by_ref_list}") + command.extend(["--sort-by", str(sort_by_ref_list)]) + + # Execute the command + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True + ) + return { + "command_executed": " ".join(command), + "stdout": process.stdout, + "stderr": process.stderr, + "output_files": [str(p) for p in output_files] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"gffread command failed with exit code {e.returncode}: {e}", + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "gffread command not found. Please ensure it is installed and in your PATH.", + "error": "gffread command not found.", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_gffread/app/gffread_shim_server.py b/Biomni/mcp_generated/mcp_gffread/app/gffread_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7a614a255b330f933cf4b68ccb335f8110f73508 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/app/gffread_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_gffread/app/gffread_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_gffread' + + +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_gffread/app/requirements.txt b/Biomni/mcp_generated/mcp_gffread/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_gffread/docker-compose.yml b/Biomni/mcp_generated/mcp_gffread/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..6f3f529a3ab14b57b391ae29bd92c8abf9c6f2db --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-gffread: + build: . + image: mcp-gffread:latest + container_name: mcp-gffread + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=gffread + 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_gffread/environment.yaml b/Biomni/mcp_generated/mcp_gffread/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d6ad926439cb11f969083bde55d9b2f0c0715aae --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - gffread + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_gffread/requirements.txt b/Biomni/mcp_generated/mcp_gffread/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gffread/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_insilicoseq/Dockerfile b/Biomni/mcp_generated/mcp_insilicoseq/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d1b7081a6afa7e0a7f9622dc5810dd6c55b9ba9e --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/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 insilicoseq via conda (e.g., from bioconda) +RUN conda install -c bioconda insilicoseq -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/insilicoseq_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/insilicoseq_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/insilicoseq_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_server.py b/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_server.py new file mode 100644 index 0000000000000000000000000000000000000000..34ac457f4caed2c426ff5e7fd9f0380f65c439fc --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_server.py @@ -0,0 +1,237 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# @mcp.tool() - This decorator is commented out as per the instructions. +def generate( + output: str, + genomes: Optional[List[Path]] = None, + ncbi: Optional[str] = None, + ncbi_n_genomes: int = 10, + n_reads: str = "1m", + model: str = "miseq", + abundance: Optional[str] = None, + coverage: Optional[str] = None, + draft: Optional[Path] = None, + gc_model: Optional[Path] = None, + mode: Optional[str] = "kde", + seed: Optional[int] = None, + cpus: int = 1, + compress: bool = False, + perfect: bool = False, + quiet: bool = False, +) -> dict: + """ + Generate simulated Illumina reads from reference genomes. + + This tool simulates sequencing reads, primarily for metagenomic samples, + but can also be used for single genomes. It supports various error models, + GC bias, and abundance distributions. + + Args: + output (str): Prefix for the output files. + genomes (Optional[List[Path]]): List of FASTA files containing reference genomes. + Mutually exclusive with 'ncbi'. + ncbi (Optional[str]): Download random genomes from NCBI. Provide a kingdom + (e.g., 'bacteria', 'viruses', 'archaea'). + Mutually exclusive with 'genomes'. + ncbi_n_genomes (int): Number of genomes to download from NCBI if 'ncbi' is used. + n_reads (str): Total number of reads to generate. Suffixes 'k', 'm', 'g' are supported (e.g., '10k', '1m'). + model (str): Error model to use. Can be a built-in model ('miseq', 'hiseq', 'novaseq') + or a path to a custom '.npz' model file. + abundance (Optional[str]): Abundance distribution. Can be a distribution name + ('uniform', 'lognormal', 'halfnormal', 'zero_inflated_lognormal') + or a path to an abundance file. Mutually exclusive with 'coverage'. + coverage (Optional[str]): Coverage distribution. Can be a distribution name or a path + to a coverage file. Mutually exclusive with 'abundance'. + draft (Optional[Path]): Path to a file indicating which sequences are draft quality. + gc_model (Optional[Path]): Path to a custom GC bias model file. + mode (Optional[str]): Sequencing simulation mode. Defaults to 'kde'. + seed (Optional[int]): Random seed for reproducibility. + cpus (int): Number of CPUs to use for simulation. + compress (bool): If True, compress the output FASTQ files with gzip. + perfect (bool): If True, generate perfect reads without sequencing errors. + quiet (bool): If True, suppress progress and informational messages. + + Returns: + dict: A dictionary containing the executed command, stdout, stderr, + and a list of generated output files. + """ + # Input validation + if not ((genomes is not None) ^ (ncbi is not None)): + raise ValueError("Either 'genomes' or 'ncbi' must be provided, but not both.") + + if abundance is not None and coverage is not None: + raise ValueError("'abundance' and 'coverage' are mutually exclusive.") + + if cpus <= 0: + raise ValueError("'cpus' must be a positive integer.") + + if ncbi_n_genomes <= 0: + raise ValueError("'ncbi_n_genomes' must be a positive integer.") + + cmd = ["iss", "generate", "--output", output] + + if genomes: + for genome_file in genomes: + if not genome_file.is_file(): + raise FileNotFoundError(f"Genome file not found: {genome_file}") + cmd.extend(["--genomes", str(genome_file)]) + elif ncbi: + cmd.extend(["--ncbi", ncbi, "--n_genomes", str(ncbi_n_genomes)]) + + cmd.extend(["--n_reads", n_reads]) + + if model.endswith(".npz"): + model_path = Path(model) + if not model_path.is_file(): + raise FileNotFoundError(f"Custom model file not found: {model_path}") + cmd.extend(["--model", model]) + + if abundance: + try: + abundance_path = Path(abundance) + if not abundance_path.is_file(): + # It's a distribution name, not a file + pass + except (TypeError, ValueError): + pass # It's a distribution name + cmd.extend(["--abundance", abundance]) + + if coverage: + try: + coverage_path = Path(coverage) + if not coverage_path.is_file(): + # It's a distribution name, not a file + pass + except (TypeError, ValueError): + pass # It's a distribution name + cmd.extend(["--coverage", coverage]) + + if draft: + if not draft.is_file(): + raise FileNotFoundError(f"Draft file not found: {draft}") + cmd.extend(["--draft", str(draft)]) + + if gc_model: + if not gc_model.is_file(): + raise FileNotFoundError(f"GC model file not found: {gc_model}") + cmd.extend(["--gc_model", str(gc_model)]) + + if mode: + cmd.extend(["--mode", mode]) + + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + cmd.extend(["--cpus", str(cpus)]) + + if compress: + cmd.append("--compress") + if perfect: + cmd.append("--perfect") + if quiet: + cmd.append("--quiet") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"InSilicoSeq generate failed with exit code {e.returncode}", + "output_files": [] + } + + # Define output files + ext = ".fastq.gz" if compress else ".fastq" + output_files = [ + f"{output}_R1{ext}", + f"{output}_R2{ext}", + f"{output}_abundance.txt", + ] + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + + +# @mcp.tool() - This decorator is commented out as per the instructions. +def model( + bam: Path, + output: str, + cpus: int = 1, + quiet: bool = False, +) -> dict: + """ + Create a sequencing error model from an aligned BAM file. + + This tool analyzes a BAM file containing reads aligned to a reference genome + to build a statistical model of sequencing errors, which can then be used + by the 'generate' tool. + + Args: + bam (Path): Path to the input sorted and indexed BAM file. + output (str): Prefix for the output model file. A '.npz' extension will be added. + cpus (int): Number of CPUs to use for model creation. + quiet (bool): If True, suppress progress and informational messages. + + Returns: + dict: A dictionary containing the executed command, stdout, stderr, + and the path to the generated model file. + """ + # Input validation + if not bam.is_file(): + raise FileNotFoundError(f"BAM file not found: {bam}") + + # Check for BAM index file + bai_file_1 = bam.with_suffix(".bam.bai") + bai_file_2 = bam.with_suffix(".bai") + if not (bai_file_1.is_file() or bai_file_2.is_file()): + raise FileNotFoundError( + f"BAM index file (.bai) not found for {bam}. Please index the BAM file." + ) + + if cpus <= 0: + raise ValueError("'cpus' must be a positive integer.") + + cmd = ["iss", "model", "--bam", str(bam), "--output", output] + cmd.extend(["--cpus", str(cpus)]) + + if quiet: + cmd.append("--quiet") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"InSilicoSeq model failed with exit code {e.returncode}", + "output_files": [] + } + + output_model_file = f"{output}.npz" + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_model_file], + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_shim_server.py b/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..638b4d522272fb48c7ca46e052555250dcae5df4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/app/insilicoseq_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_insilicoseq/app/insilicoseq_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_insilicoseq' + + +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_insilicoseq/app/requirements.txt b/Biomni/mcp_generated/mcp_insilicoseq/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_insilicoseq/docker-compose.yml b/Biomni/mcp_generated/mcp_insilicoseq/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb79cea3b04e786ce93b5d14a7cefdb4d2f38c36 --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-insilicoseq: + build: . + image: mcp-insilicoseq:latest + container_name: mcp-insilicoseq + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=insilicoseq + 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_insilicoseq/environment.yaml b/Biomni/mcp_generated/mcp_insilicoseq/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6fe4420616b68a726f5134e0090beaceb4fe1686 --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - insilicoseq + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_insilicoseq/requirements.txt b/Biomni/mcp_generated/mcp_insilicoseq/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_insilicoseq/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_libsequence/app/libsequence_shim_server.py b/Biomni/mcp_generated/mcp_libsequence/app/libsequence_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f6283c2aedbe820fad5bec20126bfc7bb2279e06 --- /dev/null +++ b/Biomni/mcp_generated/mcp_libsequence/app/libsequence_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_libsequence/app/libsequence_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_libsequence' + + +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_minimap2/Dockerfile b/Biomni/mcp_generated/mcp_minimap2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..793f6222d68748cc27ea2c54cd23ba34849f1636 --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/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 minimap2 via conda (e.g., from bioconda) +RUN conda install -c bioconda minimap2 -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/minimap2_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/minimap2_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/minimap2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_minimap2/app/minimap2_shim_server.py b/Biomni/mcp_generated/mcp_minimap2/app/minimap2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f57b97490e09ff581f47023da7c3e93cd8d89b81 --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/app/minimap2_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_minimap2/app/minimap2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_minimap2' + + +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_minimap2/app/requirements.txt b/Biomni/mcp_generated/mcp_minimap2/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_minimap2/docker-compose.yml b/Biomni/mcp_generated/mcp_minimap2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..cb3d2d3316d8323d63147ca1248b6fea882ad58b --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-minimap2: + build: . + image: mcp-minimap2:latest + container_name: mcp-minimap2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=minimap2 + 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_minimap2/environment.yaml b/Biomni/mcp_generated/mcp_minimap2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..154d9e7c7a9e6be3f612a2d2bd8a20950f903714 --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - minimap2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_minimap2/requirements.txt b/Biomni/mcp_generated/mcp_minimap2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_minimap2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ncbi-vdb/Dockerfile b/Biomni/mcp_generated/mcp_ncbi-vdb/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..479fdf7aa267fe3a9d71aa278f8ed3929cada861 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/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 ncbi-vdb via conda (e.g., from bioconda) +RUN conda install -c bioconda ncbi-vdb -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/ncbi-vdb_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/ncbi-vdb_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/ncbi-vdb_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_server.py b/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_server.py new file mode 100644 index 0000000000000000000000000000000000000000..aa76b7669670ad9dd25226c332d72fd455688ece --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_server.py @@ -0,0 +1,92 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any +import logging + +# Set up logging +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + +class MCP: + """ + A dummy class to mock the @mcp.tool decorator for standalone execution. + In a real MCP environment, this would be provided by the MCP framework. + """ + def tool(self): + def decorator(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + return decorator + +mcp = MCP() + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_ncbi_vdb' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_ncbi_vdb(args: Optional[str] = None) -> Dict[str, Any]: + """ + Executes the ncbi-vdb command with arbitrary arguments. + + NOTE: The provided documentation for `ncbi-vdb` does not specify any + command-line subcommands or parameters. It is described as a database + engine and library for the SRA toolkit. This function serves as a generic + wrapper for a hypothetical `ncbi-vdb` executable. It is likely that users + intending to interact with VDB data should use a specific tool from the + SRA-toolkit like `vdb-dump` or `vdb-validate` instead. + + Args: + args: A string of command-line arguments to pass to the ncbi-vdb command. + For example, "--version" or "--help". + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + command: List[str] = ["ncbi-vdb"] + if args: + command.extend(args.split()) + + command_str = " ".join(command) + log.info(f"Executing command: {command_str}") + + try: + result = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding='utf-8' + ) + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + error_message = ( + "Error: 'ncbi-vdb' executable not found. Please ensure the NCBI SRA " + "Toolkit is installed and its 'bin' directory is in the system's PATH." + ) + log.error(error_message) + return { + "command_executed": command_str, + "stdout": "", + "stderr": error_message, + "output_files": [] + } + except subprocess.CalledProcessError as e: + log.error(f"Command failed with exit code {e.returncode}") + log.error(f"Stderr: {e.stderr}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_shim_server.py b/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..615fe64fae2034fc42de87e4a51fb441fcbdc810 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/app/ncbi-vdb_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_ncbi-vdb/app/ncbi-vdb_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_ncbi_vdb' + + +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_ncbi-vdb/app/requirements.txt b/Biomni/mcp_generated/mcp_ncbi-vdb/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_ncbi-vdb/docker-compose.yml b/Biomni/mcp_generated/mcp_ncbi-vdb/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1cf3de8fe67fa65f840f25774a75c78332570f79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-ncbi-vdb: + build: . + image: mcp-ncbi-vdb:latest + container_name: mcp-ncbi-vdb + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=ncbi-vdb + 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_ncbi-vdb/environment.yaml b/Biomni/mcp_generated/mcp_ncbi-vdb/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..78c24c48a28ef7a9567940e9b7f2cfbe31365a56 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - ncbi-vdb + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ncbi-vdb/requirements.txt b/Biomni/mcp_generated/mcp_ncbi-vdb/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncbi-vdb/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ngs-smap/Dockerfile b/Biomni/mcp_generated/mcp_ngs-smap/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6dbd1659e88aaad7beea9fc4b379e3cab286d7ba --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/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 ngs-smap via conda (e.g., from bioconda) +RUN conda install -c bioconda ngs-smap -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/ngs-smap_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/ngs-smap_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/ngs-smap_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_server.py b/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9824bead0fd722251951e5b6a1e2b1c4edf78df9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_server.py @@ -0,0 +1,144 @@ +import subprocess +from pathlib import Path +from typing import Optional, Dict, List +import logging + +# Set up logging +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be provided by the MCP framework. +class mcp: + @staticmethod + def tool(): + def decorator(func): + return func + return decorator + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_ngs_smap' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def ngs_smap( + in_file: Path, + out_file: Path, + ref: Path, + in2_file: Optional[Path] = None, + mapper: str = "bwa-mem", + threads: int = 1, + rg: Optional[str] = None, + mark_dups: bool = False, + add_umi_bamtag: bool = False, + cleanup: bool = False, + verbose: bool = False, + force: bool = False, +) -> Dict: + """ + Maps short reads to a reference genome using BWA-MEM, BWA-MEM2, or Bowtie2. + + This tool is a wrapper for different mapping tools. It requires the reference + genome to be indexed by the selected mapping tool. + + Args: + in_file: Input FASTQ file for read 1. + out_file: Output BAM file. + ref: Reference genome FASTA file. The index files are expected to be in the same folder. + in2_file: Optional input FASTQ file for read 2 (for paired-end reads). + mapper: The mapping algorithm to use. Valid options are 'bwa-mem', 'bwa-mem2', 'bowtie2'. + threads: The number of threads to use for mapping. + rg: Read group header line in SAM format (e.g., '@RG\\tID:group1\\tSM:sample1'). + mark_dups: If True, mark duplicates using Sambamba. + add_umi_bamtag: If True, adds a UMI bamtag to each alignment using fgbio. + cleanup: If True, intermediate files are deleted. + verbose: If True, enables verbose mode for more detailed output. + force: If True, forces overwriting of existing output files. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not in_file.is_file(): + raise FileNotFoundError(f"Input file not found: {in_file}") + if not ref.is_file(): + raise FileNotFoundError(f"Reference genome file not found: {ref}") + if in2_file and not in2_file.is_file(): + raise FileNotFoundError(f"Paired-end input file not found: {in2_file}") + + allowed_mappers = {"bwa-mem", "bwa-mem2", "bowtie2"} + if mapper not in allowed_mappers: + raise ValueError(f"Invalid mapper '{mapper}'. Allowed values are: {', '.join(allowed_mappers)}") + + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + # Ensure output directory exists + try: + out_file.parent.mkdir(parents=True, exist_ok=True) + except Exception as e: + raise IOError(f"Could not create output directory {out_file.parent}: {e}") + + # --- Command Construction --- + cmd = [ + "ngs-smap", + "-in", str(in_file), + "-out", str(out_file), + "-ref", str(ref), + "-mapper", mapper, + "-threads", str(threads), + ] + + if in2_file: + cmd.extend(["-in2", str(in2_file)]) + + if rg: + cmd.extend(["-rg", rg]) + + # Add boolean flags if they are set to True + if mark_dups: + cmd.append("-mark_dups") + if add_umi_bamtag: + cmd.append("-add_umi_bamtag") + if cleanup: + cmd.append("-cleanup") + if verbose: + cmd.append("-v") + if force: + cmd.append("--force") + + command_executed = " ".join(cmd) + log.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + + output_files = [str(out_file)] if out_file.exists() else [] + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except FileNotFoundError: + error_message = "Error: 'ngs-smap' command not found. Please ensure the ngs-bits suite is installed and in your system's PATH." + log.error(error_message) + # Raising an exception is often better for MCP tools to signal a setup/environment issue + raise RuntimeError(error_message) + except subprocess.CalledProcessError as e: + log.error(f"ngs-smap execution failed with return code {e.returncode}") + log.error(f"STDOUT: {e.stdout}") + log.error(f"STDERR: {e.stderr}") + # Propagate the error information in a structured way + raise RuntimeError(f"ngs-smap failed: {e.stderr}") from e + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_shim_server.py b/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..05640e6ba40fe1844a0a387ea6af21c7bc70c42b --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/app/ngs-smap_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_ngs-smap/app/ngs-smap_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_ngs_smap' + + +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_ngs-smap/app/requirements.txt b/Biomni/mcp_generated/mcp_ngs-smap/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_ngs-smap/docker-compose.yml b/Biomni/mcp_generated/mcp_ngs-smap/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..68182a90ff75f92915bea2caf8d0465d13f95361 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-ngs-smap: + build: . + image: mcp-ngs-smap:latest + container_name: mcp-ngs-smap + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=ngs-smap + 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_ngs-smap/environment.yaml b/Biomni/mcp_generated/mcp_ngs-smap/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..694457530fe19fa211a3019510f80bbf4835218f --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - ngs-smap + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_ngs-smap/requirements.txt b/Biomni/mcp_generated/mcp_ngs-smap/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ngs-smap/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_opticlust/Dockerfile b/Biomni/mcp_generated/mcp_opticlust/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..cbc5b0d1e1178444c116ef406ddbc44ac6db12d1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/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 opticlust via conda (e.g., from bioconda) +RUN conda install -c bioconda opticlust -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/opticlust_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/opticlust_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/opticlust_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_opticlust/app/opticlust_server.py b/Biomni/mcp_generated/mcp_opticlust/app/opticlust_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ca3229e03d27604c2f76db3f9b2460c9491eb4f5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/app/opticlust_server.py @@ -0,0 +1,377 @@ +import logging +import subprocess +from pathlib import Path +from typing import Optional, Dict, Any + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Since opticlust is a library, we will import its functions directly. +# These imports would be placed within the execution environment. +try: + import numpy as np + import scanpy as sc + import matplotlib.pyplot as plt + from opticlust.clust import clustering as opticlust_clustering_func + from opticlust.clust import clustering_plot as opticlust_plot_func + from opticlust.recommend import recommend_resolutions as opticlust_recommend_func + from opticlust.recommend import score_resolutions as opticlust_score_func + from opticlust.tree import clustree as opticlust_clustree_func +except ImportError: + # This allows the file to be parsed without the libraries installed. + # The MCP runner will handle the actual environment setup. + pass + +# @mcp.tool() decorator would be used in a real MCP environment. + +def opticlust_clustering( + input_adata: Path, + output_adata: Path, + flavor: str = "leiden", + min_res: float = 0.1, + max_res: float = 2.0, + n_res: int = 20, + random_state: int = 0, + key_added: str = "opticlust", + n_neighbors: int = 15, + n_pcs: Optional[int] = None, +) -> Dict[str, Any]: + """ + Performs clustering on a single-cell dataset at various resolutions using opticlust. + + This function wraps the clustering functionality of opticlust, iterating through a range of + resolutions to generate multiple clustering solutions. It uses scanpy's neighborhood graph + and either the Leiden or Louvain algorithm. + + Args: + input_adata: Path to the input AnnData object file (.h5ad). + output_adata: Path to save the output AnnData object with clustering results. + flavor: Clustering algorithm to use. Must be 'leiden' or 'louvain'. + min_res: The minimum resolution for clustering. + max_res: The maximum resolution for clustering. + n_res: The number of resolutions to test between min_res and max_res. + random_state: Seed for the random number generator for reproducibility. + key_added: Prefix for the columns added to `adata.obs`. + n_neighbors: Number of neighbors to use for building the neighborhood graph. + n_pcs: Number of principal components to use for the neighborhood graph. If None, + uses existing PCA in `adata.obsm['X_pca']`. + + Returns: + A dictionary containing the command executed, stdout, stderr, the path to the + output AnnData file, and the list of new column names added. + """ + # Input validation + if not input_adata.is_file(): + raise FileNotFoundError(f"Input file not found: {input_adata}") + if flavor not in ["leiden", "louvain"]: + raise ValueError("`flavor` must be either 'leiden' or 'louvain'.") + if min_res >= max_res: + raise ValueError("`min_res` must be less than `max_res`.") + if n_res <= 0: + raise ValueError("`n_res` must be a positive integer.") + + output_adata.parent.mkdir(parents=True, exist_ok=True) + + command_str = ( + f"opticlust.clust.clustering(adata, flavor='{flavor}', min_res={min_res}, " + f"max_res={max_res}, n_res={n_res}, random_state={random_state}, " + f"key_added='{key_added}', n_neighbors={n_neighbors}, n_pcs={n_pcs})" + ) + + try: + adata = sc.read_h5ad(input_adata) + + if 'neighbors' not in adata.uns: + logging.info("Neighborhood graph not found. Computing neighbors...") + sc.pp.neighbors(adata, n_neighbors=n_neighbors, n_pcs=n_pcs) + + resolutions = np.linspace(min_res, max_res, n_res) + cluster_kwargs = { + "flavor": flavor, + "resolution": resolutions, + "random_state": random_state, + "key_added": key_added, + } + + new_columns = opticlust_clustering_func(adata, cluster_kwargs=cluster_kwargs) + adata.write_h5ad(output_adata) + + return { + "command_executed": command_str, + "stdout": f"Successfully performed clustering. Added columns: {new_columns}", + "stderr": "", + "output_files": {"output_adata": str(output_adata)}, + "new_columns": new_columns + } + except Exception as e: + logging.error(f"Error during opticlust clustering: {e}") + return { + "command_executed": command_str, + "stdout": "", + "stderr": str(e), + "output_files": {}, + } + +def opticlust_score_resolutions( + input_adata: Path, + output_adata: Path, + columns: str, + tests: str = "SH_CH_DB", + method: str = "mean", +) -> Dict[str, Any]: + """ + Scores clustering resolutions using silhouette, Calinski-Harabasz, and Davies-Bouldin scores. + + This function updates the `adata.uns['opticlust']` dictionary with the calculated scores + for each specified clustering resolution. + + Args: + input_adata: Path to an AnnData object file (.h5ad) with clustering results. + output_adata: Path to save the output AnnData object with scores. + columns: Comma-separated string of column names in `adata.obs` to be scored. + tests: A string indicating which tests to run. 'S' for Silhouette, 'H' for + Calinski-Harabasz, 'D' for Davies-Bouldin. + method: Method to combine scores ('mean' or 'median'). + + Returns: + A dictionary containing the command executed, stdout, stderr, and the path to the + output AnnData file. + """ + if not input_adata.is_file(): + raise FileNotFoundError(f"Input file not found: {input_adata}") + if not columns: + raise ValueError("`columns` parameter cannot be empty.") + if method not in ["mean", "median"]: + raise ValueError("`method` must be either 'mean' or 'median'.") + + output_adata.parent.mkdir(parents=True, exist_ok=True) + column_list = [c.strip() for c in columns.split(',')] + + command_str = ( + f"opticlust.recommend.score_resolutions(adata, columns={column_list}, " + f"tests='{tests}', method='{method}')" + ) + + try: + adata = sc.read_h5ad(input_adata) + + for col in column_list: + if col not in adata.obs.columns: + raise ValueError(f"Column '{col}' not found in input AnnData object.") + + opticlust_score_func(adata, column_list, tests=tests, method=method) + adata.write_h5ad(output_adata) + + return { + "command_executed": command_str, + "stdout": "Successfully scored resolutions. Updated adata.uns['opticlust'].", + "stderr": "", + "output_files": {"output_adata": str(output_adata)} + } + except Exception as e: + logging.error(f"Error during opticlust scoring: {e}") + return { + "command_executed": command_str, + "stdout": "", + "stderr": str(e), + "output_files": {}, + } + +def opticlust_clustering_plot( + input_adata: Path, + output_plot: Path, + columns: str, + method: str = "score", + min_n_resolutions: int = 1, +) -> Dict[str, Any]: + """ + Generates a plot of clustering scores vs. number of clusters and pre-selects promising resolutions. + + Args: + input_adata: Path to a scored AnnData object file (.h5ad). + output_plot: Path to save the output plot (e.g., .png, .svg). + columns: Comma-separated string of column names in `adata.obs` to be plotted. + method: Method used for plotting. Typically 'score'. + min_n_resolutions: Minimum number of resolutions to be selected and returned. + + Returns: + A dictionary containing the command executed, the path to the output plot file, + and the list of selected (promising) column names. + """ + if not input_adata.is_file(): + raise FileNotFoundError(f"Input file not found: {input_adata}") + if not columns: + raise ValueError("`columns` parameter cannot be empty.") + + output_plot.parent.mkdir(parents=True, exist_ok=True) + column_list = [c.strip() for c in columns.split(',')] + + command_str = ( + f"opticlust.clust.clustering_plot(adata, columns={column_list}, " + f"method='{method}', min_n_resolutions={min_n_resolutions})" + ) + + try: + adata = sc.read_h5ad(input_adata) + + for col in column_list: + if col not in adata.obs.columns: + raise ValueError(f"Column '{col}' not found in input AnnData object.") + + if 'opticlust' not in adata.uns: + raise ValueError("Scoring info not found in `adata.uns['opticlust']`. " + "Run `opticlust_score_resolutions` first.") + + plt.figure() + selected_columns = opticlust_plot_func( + adata, column_list, method=method, min_n_resolutions=min_n_resolutions + ) + plt.savefig(output_plot, bbox_inches='tight', dpi=150) + plt.close() + + return { + "command_executed": command_str, + "stdout": f"Plot generated successfully. Selected columns: {selected_columns}", + "stderr": "", + "output_files": {"plot": str(output_plot)}, + "selected_columns": selected_columns + } + except Exception as e: + logging.error(f"Error during opticlust plotting: {e}") + return { + "command_executed": command_str, + "stdout": "", + "stderr": str(e), + "output_files": {}, + } + +def opticlust_recommend_resolutions( + input_adata: Path, + columns: str, +) -> Dict[str, Any]: + """ + Recommends the best clustering resolution from low, medium, and high stability bins. + + This function analyzes scores in `adata.uns['opticlust']` and identifies the top + overall resolution, as well as top resolutions within three cluster-count bins. + + Args: + input_adata: Path to a scored AnnData object file (.h5ad). + columns: Comma-separated string of column names to consider for recommendations. + Typically the output from `opticlust_clustering_plot`. + + Returns: + A dictionary containing the command executed and the recommended resolution column names. + """ + if not input_adata.is_file(): + raise FileNotFoundError(f"Input file not found: {input_adata}") + if not columns: + raise ValueError("`columns` parameter cannot be empty.") + + column_list = [c.strip() for c in columns.split(',')] + command_str = f"opticlust.recommend.recommend_resolutions(adata, columns={column_list})" + + try: + adata = sc.read_h5ad(input_adata) + + for col in column_list: + if col not in adata.obs.columns: + raise ValueError(f"Column '{col}' not found in input AnnData object.") + + if 'opticlust' not in adata.uns: + raise ValueError("Scoring info not found in `adata.uns['opticlust']`. " + "Run `opticlust_score_resolutions` first.") + + top_overall, top_low, top_medium, top_high = opticlust_recommend_func(adata, column_list) + + return { + "command_executed": command_str, + "stdout": "Successfully recommended resolutions.", + "stderr": "", + "recommendations": { + "top_overall": top_overall, + "top_low": top_low, + "top_medium": top_medium, + "top_high": top_high, + } + } + except Exception as e: + logging.error(f"Error during opticlust recommendation: {e}") + return { + "command_executed": command_str, + "stdout": "", + "stderr": str(e), + "recommendations": {}, + } + +def opticlust_clustree( + input_adata: Path, + output_adata: Path, + output_plot: Path, + columns: str, + rename_clusters: bool = True, +) -> Dict[str, Any]: + """ + Builds and plots a clustering tree to visualize the relationship between resolutions. + + This function can also rename clusters for consistency across resolutions, which simplifies + downstream analysis and visualization. + + Args: + input_adata: Path to an AnnData object file (.h5ad) with clustering results. + output_adata: Path to save the output AnnData object (potentially with renamed clusters). + output_plot: Path to save the output clustree plot (e.g., .png, .svg). + columns: Comma-separated string of column names to include in the tree. + rename_clusters: If True, rename clusters to be consistent across resolutions. + + Returns: + A dictionary containing the command executed, paths to the output AnnData and plot files. + """ + if not input_adata.is_file(): + raise FileNotFoundError(f"Input file not found: {input_adata}") + if not columns: + raise ValueError("`columns` parameter cannot be empty.") + + output_adata.parent.mkdir(parents=True, exist_ok=True) + output_plot.parent.mkdir(parents=True, exist_ok=True) + column_list = [c.strip() for c in columns.split(',')] + + command_str = ( + f"opticlust.tree.clustree(adata, columns={column_list}, " + f"rename_clusters={rename_clusters})" + ) + + try: + adata = sc.read_h5ad(input_adata) + + for col in column_list: + if col not in adata.obs.columns: + raise ValueError(f"Column '{col}' not found in input AnnData object.") + + g = opticlust_clustree_func(adata, column_list, rename_clusters=rename_clusters) + + g.draw(output_plot, prog='dot') + adata.write_h5ad(output_adata) + + return { + "command_executed": command_str, + "stdout": "Clustree plot generated and AnnData object updated successfully.", + "stderr": "", + "output_files": { + "output_adata": str(output_adata), + "plot": str(output_plot), + } + } + except Exception as e: + error_msg = str(e) + if "failed to execute" in error_msg.lower() and "dot" in error_msg.lower(): + error_msg += ("\nThis error often indicates that the 'graphviz' package is not installed " + "or not in the system's PATH. Please install it (e.g., 'conda install graphviz').") + + logging.error(f"Error during opticlust clustree generation: {error_msg}") + return { + "command_executed": command_str, + "stdout": "", + "stderr": error_msg, + "output_files": {}, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_opticlust/app/opticlust_shim_server.py b/Biomni/mcp_generated/mcp_opticlust/app/opticlust_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ea9d91c492fcd7374c9492255e2586e30fac228f --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/app/opticlust_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_opticlust/app/opticlust_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_opticlust' + + +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_opticlust/app/requirements.txt b/Biomni/mcp_generated/mcp_opticlust/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..04df1ef21e18c3e09c6db9669a6854499f226b44 --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/app/requirements.txt @@ -0,0 +1,4 @@ +matplotlib==3.10.9 +numpy==2.4.4 +opticlust==0.5.0 +scanpy==1.12.1 diff --git a/Biomni/mcp_generated/mcp_opticlust/docker-compose.yml b/Biomni/mcp_generated/mcp_opticlust/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..55c7f26ed27cc955791acc32c314c0e176eeb10a --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-opticlust: + build: . + image: mcp-opticlust:latest + container_name: mcp-opticlust + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=opticlust + 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_opticlust/environment.yaml b/Biomni/mcp_generated/mcp_opticlust/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4658dd5df9df3a590c8c52c9b43875be87e53159 --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - opticlust + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_opticlust/requirements.txt b/Biomni/mcp_generated/mcp_opticlust/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_opticlust/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_orthofinder/Dockerfile b/Biomni/mcp_generated/mcp_orthofinder/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..00c5dbe3e58a49c4f6bc1cc6243a9dcc54d9fe77 --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/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 orthofinder via conda (e.g., from bioconda) +RUN conda install -c bioconda orthofinder -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/orthofinder_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/orthofinder_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/orthofinder_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_server.py b/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_server.py new file mode 100644 index 0000000000000000000000000000000000000000..69cb2124947bb1317dffff9d660b4286eec3be26 --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_server.py @@ -0,0 +1,207 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List +import tempfile +import os + +# It is assumed that 'mcp' is available in the execution environment. +# Since we are only providing the function code, we will not import it here. +# from mcp import tool as mcp_tool + +# Setup basic logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_orthofinder' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_orthofinder( + fasta_dir: Path, + output_dir: Optional[Path] = None, + species_tree: Optional[Path] = None, + mcl_inflation: float = 1.5, + threads: int = 16, + analysis_threads: int = 1, + is_dna: bool = False, + search_program: str = "diamond", + msa_program: str = "muscle", + tree_inference_program: str = "fasttree", + no_species_names: bool = False, + previous_results_dir: Optional[Path] = None, + orthogroup_inference_only: bool = False, + resume: bool = False, + species_tree_inference_program: str = "fasttree", + citations_file: Optional[Path] = None, + run_name: Optional[str] = None, + clean_intermediate: bool = False, + help_flag: bool = False, + version_flag: bool = False +) -> dict: + """ + Runs OrthoFinder, a tool for comparative genomics. + + This function wraps the main `orthofinder` command, providing a Pythonic interface + to its comprehensive set of features for finding orthogroups, orthologs, and inferring + gene and species trees. + + Args: + fasta_dir: Directory containing protein or DNA sequence files in FASTA format, one file per species. + output_dir: Directory to save the results. If not provided, a default directory will be created by OrthoFinder. + species_tree: Path to a user-provided species tree file. + mcl_inflation: The MCL inflation parameter (-I). Controls the granularity of orthogroup clustering. + threads: Number of parallel sequence search threads to use (-t). + analysis_threads: Number of parallel analysis threads to use (-a). + is_dna: Set to True if the input sequences are DNA, not protein (-d). + search_program: The sequence search program to use (-S). Options: 'diamond', 'diamond_ultra_sens', 'blast', 'blast_gz', 'mmseqs'. + msa_program: The multiple sequence alignment program to use (-msa). Options: 'muscle', 'mafft'. + tree_inference_program: The gene tree inference program to use (-T). Options: 'fasttree', 'raxml-ng', 'iqtree'. + no_species_names: If True, do not add species names to sequence IDs (-X). + previous_results_dir: Path to a directory of previous OrthoFinder results to reuse (-fg). + orthogroup_inference_only: If True, perform orthogroup inference only, requires a previous run (-og). + resume: If True, start from the previous point in an incomplete run (-op). + species_tree_inference_program: The tree inference program for the species tree (-Z). Options: 'fasttree', 'raxml-ng', 'iqtree'. + citations_file: Path to a file to write citations to (-c). + run_name: A name for this run, used for the results directory if `output_dir` is not set (-n). + clean_intermediate: If True, delete intermediate files to save space (-clean). + help_flag: If True, display the help message and exit (-h). + version_flag: If True, print the version number and exit (-v). + + Returns: + A dictionary containing the execution command, stdout, stderr, and a list of output files. + """ + # --- 1. Input Validation --- + if not Path("orthofinder").is_file() and not any(os.access(os.path.join(path, 'orthofinder'), os.X_OK) for path in os.environ["PATH"].split(os.pathsep)): + raise FileNotFoundError("orthofinder executable not found in PATH.") + + cmd = ["orthofinder"] + + if help_flag: + cmd.append("-h") + elif version_flag: + cmd.append("-v") + else: + # Validate required inputs for a normal run + if not fasta_dir.is_dir(): + raise ValueError(f"Input FASTA directory '{fasta_dir}' does not exist or is not a directory.") + + # Validate parameter choices + valid_search_programs = ["diamond", "diamond_ultra_sens", "blast", "blast_gz", "mmseqs"] + if search_program not in valid_search_programs: + raise ValueError(f"Invalid search_program '{search_program}'. Must be one of {valid_search_programs}.") + + valid_msa_programs = ["muscle", "mafft", "muscle3"] + if msa_program not in valid_msa_programs: + raise ValueError(f"Invalid msa_program '{msa_program}'. Must be one of {valid_msa_programs}.") + + valid_tree_programs = ["fasttree", "raxml-ng", "iqtree"] + if tree_inference_program not in valid_tree_programs: + raise ValueError(f"Invalid tree_inference_program '{tree_inference_program}'. Must be one of {valid_tree_programs}.") + if species_tree_inference_program not in valid_tree_programs: + raise ValueError(f"Invalid species_tree_inference_program '{species_tree_inference_program}'. Must be one of {valid_tree_programs}.") + + # Validate conditional requirements + if orthogroup_inference_only and not previous_results_dir: + raise ValueError("`previous_results_dir` must be provided when `orthogroup_inference_only` is True.") + + # --- 2. Command Construction --- + cmd.extend(["-f", str(fasta_dir)]) + cmd.extend(["-S", search_program]) + cmd.extend(["-M", search_program]) # Also set legacy -M for compatibility + cmd.extend(["-msa", msa_program]) + cmd.extend(["-A", msa_program]) # Also set legacy -A for compatibility + cmd.extend(["-T", tree_inference_program]) + cmd.extend(["-Z", species_tree_inference_program]) + cmd.extend(["-I", str(mcl_inflation)]) + cmd.extend(["-t", str(threads)]) + cmd.extend(["-a", str(analysis_threads)]) + + if output_dir: + output_dir.mkdir(parents=True, exist_ok=True) + cmd.extend(["-o", str(output_dir)]) + + if species_tree: + if not species_tree.is_file(): + raise FileNotFoundError(f"Species tree file '{species_tree}' not found.") + cmd.extend(["-s", str(species_tree)]) + + if is_dna: + cmd.append("-d") + if no_species_names: + cmd.append("-X") + if previous_results_dir: + if not previous_results_dir.is_dir(): + raise FileNotFoundError(f"Previous results directory '{previous_results_dir}' not found.") + cmd.extend(["-fg", str(previous_results_dir)]) + if orthogroup_inference_only: + cmd.append("-og") + if resume: + cmd.append("-op") + if citations_file: + cmd.extend(["-c", str(citations_file)]) + if run_name: + cmd.extend(["-n", run_name]) + if clean_intermediate: + cmd.append("-clean") + + # --- 3. Subprocess Execution --- + command_str = " ".join(cmd) + logging.info(f"Executing command: {command_str}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + logging.info("OrthoFinder executed successfully.") + + except subprocess.CalledProcessError as e: + logging.error(f"OrthoFinder failed with exit code {e.returncode}.") + logging.error(f"Stderr: {e.stderr}") + logging.error(f"Stdout: {e.stdout}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + except FileNotFoundError: + logging.error("orthofinder command not found. Please ensure it is in your system's PATH.") + return { + "command_executed": command_str, + "stdout": "", + "stderr": "orthofinder command not found. Please ensure it is in your system's PATH.", + "output_files": [] + } + + # --- 4. Structured Result Return --- + output_files = [] + final_output_dir = output_dir + if not final_output_dir: + # OrthoFinder creates a default dir, we need to find it from stdout + for line in stdout.splitlines(): + if "Results directory:" in line: + path_str = line.split("Results directory:")[1].strip() + final_output_dir = Path(path_str) + break + + if final_output_dir and final_output_dir.is_dir(): + for root, _, files in os.walk(final_output_dir): + for name in files: + output_files.append(str(Path(root) / name)) + + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_shim_server.py b/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..140ce78d5a0f5e6ad407ab18cc90ac2324fbc067 --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/app/orthofinder_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_orthofinder/app/orthofinder_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_orthofinder' + + +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_orthofinder/app/requirements.txt b/Biomni/mcp_generated/mcp_orthofinder/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_orthofinder/docker-compose.yml b/Biomni/mcp_generated/mcp_orthofinder/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..cd6a20861627cacb2346876609ce392eaadf1b5f --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-orthofinder: + build: . + image: mcp-orthofinder:latest + container_name: mcp-orthofinder + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=orthofinder + 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_orthofinder/environment.yaml b/Biomni/mcp_generated/mcp_orthofinder/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..148b0f16fc688842bcebff628aadd4c80d15bb19 --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - orthofinder + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_orthofinder/requirements.txt b/Biomni/mcp_generated/mcp_orthofinder/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_orthofinder/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-bio-phylo/requirements.txt b/Biomni/mcp_generated/mcp_perl-bio-phylo/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-phylo/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-bioperl-core/Dockerfile b/Biomni/mcp_generated/mcp_perl-bioperl-core/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..107c01e9906e11c89598b792cf00b9597b6aa7e0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/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-bioperl-core via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-bioperl-core -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-bioperl-core_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-bioperl-core_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-bioperl-core_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_server.py b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_server.py new file mode 100644 index 0000000000000000000000000000000000000000..80c78ee2e00d7fd7f26cb81a746e0518c352558a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_server.py @@ -0,0 +1,303 @@ +import subprocess +import tempfile +import os +from pathlib import Path +from typing import List, Optional, Dict, Any + +# Helper function to run subprocess and handle errors +def _run_command(cmd: List[str]) -> Dict[str, Any]: + """ + Helper function to execute a command and capture its output. + """ + stdout_data = "" + stderr_data = "" + output_files: List[str] = [] + + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + stdout_data = process.stdout + stderr_data = process.stderr + except FileNotFoundError as e: + # This typically means the command itself was not found in PATH + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": f"Command not found: {cmd[0]}. Please ensure BioPerl scripts are in your system's PATH. Error: {e}", + "error": "CommandNotFoundError", + "output_files": [], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with exit code {e.returncode}", + "output_files": [], + } + except Exception as e: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": str(e), + "error": "Unexpected error", + "output_files": [], + } + + return { + "command_executed": " ".join(cmd), + "stdout": stdout_data, + "stderr": stderr_data, + "output_files": output_files, + } + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_bioperl_core' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def bp_seqfeature_dump( + input_file: Path, + output_format: str = "GFF", + feature_types: Optional[List[str]] = None, + feature_sources: Optional[List[str]] = None, + feature_ids: Optional[List[str]] = None, + feature_tags: Optional[List[str]] = None, + output_file: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Dumps sequence features from a BioPerl-supported input file (e.g., GenBank, EMBL) + into a specified output format. This tool wraps the `bp_seqfeature_dump.pl` script. + + Args: + input_file: Path to the input sequence file (e.g., GenBank, EMBL). + output_format: Output format for features (e.g., GFF, EMBL, GenBank, Fasta, Raw). Default: GFF. + feature_types: List of feature types to dump (e.g., CDS, gene, mRNA). Can be specified multiple times. + feature_sources: List of feature sources to dump. Can be specified multiple times. + feature_ids: List of feature IDs to dump. Can be specified multiple times. + feature_tags: List of feature tags to dump. Can be specified multiple times. + output_file: Path to the output file. If not specified, output goes to stdout. + verbose: Enable verbose output. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not input_file.is_file(): + raise ValueError(f"Input path is not a file: {input_file}") + + valid_formats = ["GFF", "EMBL", "GenBank", "Fasta", "Raw"] + if output_format not in valid_formats: + raise ValueError(f"Invalid output format: {output_format}. Must be one of {valid_formats}") + + cmd = ["bp_seqfeature_dump.pl", str(input_file)] + + cmd.extend(["--format", output_format]) + + if feature_types: + for ftype in feature_types: + cmd.extend(["--type", ftype]) + if feature_sources: + for source in feature_sources: + cmd.extend(["--source", source]) + if feature_ids: + for fid in feature_ids: + cmd.extend(["--id", fid]) + if feature_tags: + for tag in feature_tags: + cmd.extend(["--tag", tag]) + + if verbose: + cmd.append("--verbose") + + temp_output_path: Optional[Path] = None + if output_file: + # Ensure parent directory exists + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--outfile", str(output_file)]) + else: + # If no output_file is specified, we'll capture stdout to a temp file + with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8') as tmp: + temp_output_path = Path(tmp.name) + cmd.extend(["--outfile", str(temp_output_path)]) + + result = _run_command(cmd) + + if result.get("error"): + # Clean up temp file if an error occurred before it was processed + if temp_output_path and temp_output_path.exists(): + os.remove(temp_output_path) + return result + + output_files_list: List[str] = [] + if output_file and output_file.exists(): + output_files_list.append(str(output_file)) + elif temp_output_path and temp_output_path.exists(): + # Read content from temp file and add to stdout, then delete temp file + with open(temp_output_path, 'r', encoding='utf-8') as f: + result["stdout"] = f.read() + os.remove(temp_output_path) + + result["output_files"] = output_files_list + return result + + +@mcp.tool() +def bp_genbank2gff3( + input_genbank_file: Path, + output_file: Optional[Path] = None, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Converts a GenBank format file into GFF3 format. This tool wraps the `bp_genbank2gff3.pl` script. + + Args: + input_genbank_file: Path to the input GenBank file. + output_file: Path to the output GFF3 file. If not specified, output goes to stdout. + verbose: Enable verbose output. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + if not input_genbank_file.exists(): + raise FileNotFoundError(f"Input GenBank file not found: {input_genbank_file}") + if not input_genbank_file.is_file(): + raise ValueError(f"Input path is not a file: {input_genbank_file}") + + cmd = ["bp_genbank2gff3.pl", str(input_genbank_file)] + + if verbose: + cmd.append("--verbose") + + temp_output_path: Optional[Path] = None + if output_file: + # Ensure parent directory exists + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--outfile", str(output_file)]) + else: + with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8') as tmp: + temp_output_path = Path(tmp.name) + cmd.extend(["--outfile", str(temp_output_path)]) + + result = _run_command(cmd) + + if result.get("error"): + if temp_output_path and temp_output_path.exists(): + os.remove(temp_output_path) + return result + + output_files_list: List[str] = [] + if output_file and output_file.exists(): + output_files_list.append(str(output_file)) + elif temp_output_path and temp_output_path.exists(): + with open(temp_output_path, 'r', encoding='utf-8') as f: + result["stdout"] = f.read() + os.remove(temp_output_path) + + result["output_files"] = output_files_list + return result + + +@mcp.tool() +def bp_sreformat( + output_format: str, + input_file: Optional[Path] = None, + output_file: Optional[Path] = None, + id_type: Optional[str] = None, + width: int = 60, + uppercase: bool = False, + lowercase: bool = False, + gap_char: str = "-", +) -> Dict[str, Any]: + """ + Reformats sequence files between various BioPerl-supported formats. + This tool wraps the `bp_sreformat.pl` script. + + Args: + output_format: The desired output format (e.g., fasta, embl, genbank, phylip, nexus, raw). Required. + input_file: Path to the input sequence file. If not specified, reads from stdin (not directly supported by this wrapper, + `--input` is always used if `input_file` is provided). + output_file: Path to the output file. If not specified, output goes to stdout. + id_type: How to generate sequence IDs (e.g., accession, description, primary_id). + width: Line width for sequence output (e.g., fasta). Default: 60. + uppercase: Convert sequences to uppercase. + lowercase: Convert sequences to lowercase. + gap_char: Character to use for gaps. Default: '-'. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + valid_formats = ["fasta", "embl", "genbank", "phylip", "nexus", "raw"] + if output_format not in valid_formats: + raise ValueError(f"Invalid output format: {output_format}. Must be one of {valid_formats}") + + if uppercase and lowercase: + raise ValueError("Cannot specify both 'uppercase' and 'lowercase'. Choose one or neither.") + + if width < 1: + raise ValueError("Width must be a positive integer.") + + cmd = ["bp_sreformat.pl"] + + cmd.extend(["--format", output_format]) + + if input_file: + if not input_file.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not input_file.is_file(): + raise ValueError(f"Input path is not a file: {input_file}") + cmd.extend(["--input", str(input_file)]) + + if id_type: + valid_id_types = ["accession", "description", "primary_id"] + if id_type not in valid_id_types: + raise ValueError(f"Invalid ID type: {id_type}. Must be one of {valid_id_types}") + cmd.extend(["--id", id_type]) + + cmd.extend(["--width", str(width)]) + + if uppercase: + cmd.append("--uppercase") + if lowercase: + cmd.append("--lowercase") + + cmd.extend(["--gap", gap_char]) + + temp_output_path: Optional[Path] = None + if output_file: + # Ensure parent directory exists + output_file.parent.mkdir(parents=True, exist_ok=True) + cmd.extend(["--outfile", str(output_file)]) + else: + with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8') as tmp: + temp_output_path = Path(tmp.name) + cmd.extend(["--outfile", str(temp_output_path)]) + + result = _run_command(cmd) + + if result.get("error"): + if temp_output_path and temp_output_path.exists(): + os.remove(temp_output_path) + return result + + output_files_list: List[str] = [] + if output_file and output_file.exists(): + output_files_list.append(str(output_file)) + elif temp_output_path and temp_output_path.exists(): + with open(temp_output_path, 'r', encoding='utf-8') as f: + result["stdout"] = f.read() + os.remove(temp_output_path) + + result["output_files"] = output_files_list + return result + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_shim_server.py b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6a0d754cf22ef34886b8b682c5870734016218c6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/perl-bioperl-core_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-bioperl-core/app/perl-bioperl-core_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_bioperl_core' + + +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-bioperl-core/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-bioperl-core/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-bioperl-core/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a4eaa7a7e251316e6421b5ba61a97410ae50248a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-bioperl-core: + build: . + image: mcp-perl-bioperl-core:latest + container_name: mcp-perl-bioperl-core + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-bioperl-core + 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-bioperl-core/environment.yaml b/Biomni/mcp_generated/mcp_perl-bioperl-core/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d632416e05f7b35d097d00e3281e1b396689a7f6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-bioperl-core + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-bioperl-core/requirements.txt b/Biomni/mcp_generated/mcp_perl-bioperl-core/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bioperl-core/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/perl-business-isbn-data_server.py b/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/perl-business-isbn-data_server.py new file mode 100644 index 0000000000000000000000000000000000000000..822492958fb4212d2fd6f766a066bf0e544675f7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/perl-business-isbn-data_server.py @@ -0,0 +1,273 @@ +import subprocess +from typing import Optional, List, Dict, Any +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_business_isbn_data' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def get_isbn_data_version() -> Dict[str, Any]: + """ + Retrieves the version of the installed Business::ISBN::Data pack. + This version indicates the currency of the ISBN range data. + """ + command = ["perl", "-MBusiness::ISBN::Data", "-e", "print $Business::ISBN::Data::VERSION"] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(command), + "version": result.stdout.strip(), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def list_isbn_groups() -> Dict[str, Any]: + """ + Lists all ISBN group identifiers (countries, regions, or language areas) + supported by the current data pack. + """ + # Use Perl to extract keys from the %CountryData hash + perl_code = "use Business::ISBN::Data; print join(',', sort keys %Business::ISBN::Data::CountryData)" + command = ["perl", "-e", perl_code] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + groups = result.stdout.strip().split(",") if result.stdout.strip() else [] + return { + "command_executed": " ".join(command), + "total_groups": len(groups), + "groups": groups, + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def get_isbn_group_ranges(group_id: str) -> Dict[str, Any]: + """ + Retrieves the valid publisher ranges for a specific ISBN group. + + Args: + group_id: The group identifier (e.g., '0' for English, '3' for German, '978-609' for Armenia). + """ + if not group_id: + return {"error": "group_id parameter is required"} + + # Perl script to extract range data for a specific group + # Returns data in format: start:end:length + perl_code = ( + "use Business::ISBN::Data; " + f"my $data = $Business::ISBN::Data::CountryData{{'{group_id}'}}; " + "if ($data) { foreach my $r (@$data) { print join(':', @$r) . '\\n'; } }" + ) + command = ["perl", "-e", perl_code] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + lines = result.stdout.strip().split("\n") + ranges = [] + for line in lines: + if line: + parts = line.split(":") + if len(parts) >= 3: + ranges.append({ + "start": parts[0], + "end": parts[1], + "length": int(parts[2]) + }) + + return { + "command_executed": " ".join(command), + "group_id": group_id, + "ranges": ranges, + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def validate_isbn_prefix(isbn_prefix: str) -> Dict[str, Any]: + """ + Checks if a given ISBN prefix (e.g., '978-0-596') is valid according to the + ranges defined in the data pack. + + Args: + isbn_prefix: The ISBN prefix to validate (can include hyphens). + """ + # Clean the prefix + clean_prefix = isbn_prefix.replace("-", "") + + # This logic simulates how Business::ISBN uses the data pack to validate segments + perl_code = ( + "use Business::ISBN::Data; " + f"my $input = '{clean_prefix}'; " + "my $found = 0; " + "foreach my $group (keys %Business::ISBN::Data::CountryData) { " + " my $clean_group = $group; $clean_group =~ s/-//g; " + " if ($input =~ /^$clean_group/) { " + " my $rest = substr($input, length($clean_group)); " + " foreach my $range (@{$Business::ISBN::Data::CountryData{$group}}) { " + " my $sub = substr($rest, 0, $range->[2]); " + " if ($sub >= $range->[0] && $sub <= $range->[1]) { " + " print \"MATCH:group=$group:range=$range->[0]-$range->[1]\"; " + " $found = 1; last; " + " } " + " } " + " } " + " last if $found; " + "} " + "print 'NO_MATCH' unless $found;" + ) + + command = ["perl", "-e", perl_code] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + output = result.stdout.strip() + is_valid = output.startswith("MATCH") + + return { + "command_executed": " ".join(command), + "input_prefix": isbn_prefix, + "is_valid_in_data_pack": is_valid, + "match_details": output if is_valid else None, + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def check_isbn_data_integrity() -> Dict[str, Any]: + """ + Performs an internal integrity check on the loaded ISBN data pack to ensure + the data structure is valid and accessible. + """ + perl_code = ( + "use Business::ISBN::Data; " + "my $count = scalar keys %Business::ISBN::Data::CountryData; " + "print \"Groups loaded: $count\\n\"; " + "if ($count > 0) { print \"Status: OK\"; } else { print \"Status: ERROR - No data loaded\"; }" + ) + command = ["perl", "-e", perl_code] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + status = "OK" if "Status: OK" in result.stdout else "FAILED" + + return { + "command_executed": " ".join(command), + "integrity_status": status, + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def export_isbn_data_to_json(output_file: str) -> Dict[str, Any]: + """ + Exports the entire ISBN range data pack to a JSON file for external use. + + Args: + output_file: Path where the JSON data should be saved. + """ + output_path = Path(output_file) + + # Use JSON::PP which is core in modern Perl + perl_code = ( + "use Business::ISBN::Data; " + "use JSON::PP; " + "print encode_json(\\%Business::ISBN::Data::CountryData);" + ) + command = ["perl", "-e", perl_code] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + + with open(output_path, "w") as f: + f.write(result.stdout) + + return { + "command_executed": " ".join(command), + "output_file": str(output_path.absolute()), + "file_size_bytes": output_path.stat().st_size, + "stdout": "Data exported successfully", + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + except Exception as e: + return { + "error": f"Failed to write file: {str(e)}" + } + +@mcp.tool() +def rebuild_isbn_data_from_xml(xml_source: str, output_module_path: Optional[str] = None) -> Dict[str, Any]: + """ + Internal utility to rebuild the Perl data module from an official ISBN Agency XML file. + Corresponds to the 'util/make_data.pl' functionality. + + Args: + xml_source: Path to the RangeMessage.xml file from the ISBN agency. + output_module_path: Optional path to save the generated .pm file. + """ + xml_path = Path(xml_source) + if not xml_path.exists(): + return {"error": f"XML source file not found: {xml_source}"} + + # This simulates the logic found in the package's util/make_data.pl + # It requires XML::LibXML or similar, which might not be in the base env, + # so we check for the script or implement a basic version. + + # For this MCP tool, we will attempt to run the logic via perl if the environment allows. + # Note: This is an advanced maintenance tool. + + command = ["perl", "-MBusiness::ISBN::Data", "-e", "print 'Rebuild logic requires util/make_data.pl from source'"] + + return { + "command_executed": " ".join(command), + "info": "This tool typically requires the source distribution's util/ directory. Use the data pack as provided by the package manager for standard use.", + "xml_provided": str(xml_path.absolute()) + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-business-isbn-data/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-business-isbn-data/requirements.txt b/Biomni/mcp_generated/mcp_perl-business-isbn-data/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-business-isbn-data/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-constant/Dockerfile b/Biomni/mcp_generated/mcp_perl-constant/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fca2cbe684d0a861ce72b4d3f5eff7b7c8d678ec --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/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-constant via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-constant -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-constant_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-constant_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-constant_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_server.py b/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_server.py new file mode 100644 index 0000000000000000000000000000000000000000..da0f02d10a091ec802709fdbbb856eace604ff53 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_server.py @@ -0,0 +1,197 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Union + +# No need to import mcp, as per instructions. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_constant' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl( + program_file: Optional[Path] = None, + program_arguments: List[str] = [], + program_string: Optional[str] = None, + program_string_extended: Optional[str] = None, + record_separator: Optional[str] = None, + autosplit_mode: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + no_sitecustomize: bool = False, + split_pattern: Optional[str] = None, + edit_in_place_extension: Optional[str] = None, + include_directories: List[Path] = [], + line_ending_terminator: Optional[str] = None, + use_modules: List[str] = [], + no_modules: List[str] = [], + loop_around_program: bool = False, + loop_and_print: bool = False, + rudimentary_switch_parsing: bool = False, + search_path_for_program: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + dump_core_after_parsing: bool = False, + allow_unsafe_operations: bool = False, + print_version: bool = False, + print_config_summary_variable: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + ignore_text_before_shebang_dir: Optional[Path] = None, + disable_all_warnings: bool = False, +) -> dict: + """ + Executes the Perl interpreter with specified options and a program. + + This tool provides access to the core Perl interpreter's command-line switches, + allowing users to run Perl scripts or one-liners with various configurations + for debugging, warnings, file processing, and module loading. + """ + command = ["perl"] + output_files = [] + + # Input validation + if not program_file and not program_string and not program_string_extended and not print_version and print_config_summary_variable is None: + raise ValueError( + "Either 'program_file', 'program_string', 'program_string_extended', " + "'print_version', or 'print_config_summary_variable' must be provided " + "to specify what Perl should execute or report." + ) + + if program_file and (program_string or program_string_extended): + raise ValueError("Cannot specify both 'program_file' and a program string ('program_string' or 'program_string_extended').") + + if program_string and program_string_extended: + raise ValueError("Cannot specify both 'program_string' (-e) and 'program_string_extended' (-E).") + + if autosplit_mode and not (loop_around_program or loop_and_print): + raise ValueError("'-a' (autosplit_mode) requires '-n' (loop_around_program) or '-p' (loop_and_print).") + + if split_pattern and not autosplit_mode: + raise ValueError("'-F' (split_pattern) requires '-a' (autosplit_mode).") + + if loop_around_program and loop_and_print: + raise ValueError("Cannot specify both '-n' (loop_around_program) and '-p' (loop_and_print).") + + if disable_all_warnings and (enable_warnings or enable_all_warnings): + raise ValueError("'-X' (disable_all_warnings) cannot be used with '-w' (enable_warnings) or '-W' (enable_all_warnings).") + + # Build the command + if record_separator is not None: + command.append(f"-0{record_separator}") + 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: + # -d:debugger or -d (if debugger is empty string) + command.append(f"-d:{debugger}") + if debugging_flags is not None: + command.append(f"-D{debugging_flags}") + if program_string: + command.extend(["-e", program_string]) + if program_string_extended: + command.extend(["-E", program_string_extended]) + 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: + # -i[extension] or -i (if extension is empty string) + command.append(f"-i{edit_in_place_extension}") + for inc_dir in include_directories: + if not inc_dir.is_dir(): + raise ValueError(f"Include directory '{inc_dir}' does not exist or is not a directory.") + command.extend(["-I", str(inc_dir)]) + if line_ending_terminator is not None: + # -l[octal] or -l (if terminator is empty string) + command.append(f"-l{line_ending_terminator}") + for module_name in use_modules: + command.extend(["-M", module_name]) + for module_name in no_modules: + command.extend(["-M-", module_name]) # -M-module for 'no module' + if loop_around_program: + command.append("-n") + if loop_and_print: + command.append("-p") + if rudimentary_switch_parsing: + command.append("-s") + if search_path_for_program: + command.append("-S") + if tainting_warnings: + command.append("-t") + if tainting_checks: + command.append("-T") + if dump_core_after_parsing: + command.append("-u") + if allow_unsafe_operations: + command.append("-U") + if print_version: + command.append("-v") + if print_config_summary_variable is not None: + # -V[:variable] or -V (if variable is empty string) + if print_config_summary_variable == "": + command.append("-V") + else: + command.append(f"-V:{print_config_summary_variable}") + if enable_warnings: + command.append("-w") + if enable_all_warnings: + command.append("-W") + if ignore_text_before_shebang_dir is not None: + # -x[directory] or -x (if directory is empty string) + if ignore_text_before_shebang_dir and not ignore_text_before_shebang_dir.is_dir(): + raise ValueError(f"Directory for '-x' '{ignore_text_before_shebang_dir}' does not exist or is not a directory.") + command.append(f"-x{ignore_text_before_shebang_dir}") + if disable_all_warnings: + command.append("-X") + + # Program file and arguments + if program_file: + if not program_file.is_file(): + raise FileNotFoundError(f"Program file '{program_file}' not found.") + command.append(str(program_file)) + + if program_arguments: + command.extend(program_arguments) + + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = process.stdout + stderr = process.stderr + 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, + "output_files": output_files, + "error": f"Perl execution failed with exit code {e.returncode}", + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: 'perl' command not found. Is Perl installed and in your PATH?", + "output_files": output_files, + "error": "Perl executable not found.", + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_shim_server.py b/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d24595b6c4eecf7b037091218c17a78f3fd3b2fa --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/app/perl-constant_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-constant/app/perl-constant_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_constant' + + +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-constant/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-constant/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-constant/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-constant/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7d75680c8fd563b60bfe4a0bad9dc67b2b942ce1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-constant: + build: . + image: mcp-perl-constant:latest + container_name: mcp-perl-constant + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-constant + 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-constant/requirements.txt b/Biomni/mcp_generated/mcp_perl-constant/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-constant/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-encode-locale/Dockerfile b/Biomni/mcp_generated/mcp_perl-encode-locale/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3b3d194dcff41dcb32832b9006eff5cb6b75971f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/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-encode-locale via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-encode-locale -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-encode-locale_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-encode-locale_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-encode-locale_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_server.py b/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cd1e61c4a7b90d33a4e2735fc1738a8a6459aafa --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_server.py @@ -0,0 +1,74 @@ +import subprocess +import logging +from typing import Optional, List, Dict, Any +from pathlib import Path + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Mock mcp decorator for standalone execution +class mcp: + @staticmethod + def tool(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + +@mcp.tool +def perl_encode_locale() -> Dict[str, Any]: + """ + Determines and returns the current system's locale encoding using the Perl Encode::Locale module. + + This tool executes a Perl one-liner that loads the Encode::Locale module and + prints the detected locale encoding to standard output. It is a simple utility + to inspect the execution environment's character encoding settings. + + Returns: + Dict[str, Any]: A dictionary containing the command executed, stdout, stderr, + and the detected locale encoding. + """ + command = [ + "perl", + "-MEncode::Locale", + "-e", + "print $Encode::Locale::ENCODING_LOCALE", + ] + + command_str = " ".join(command) + logger.info(f"Executing command: {command_str}") + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + + stdout_cleaned = result.stdout.strip() + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "locale_encoding": stdout_cleaned, + } + except FileNotFoundError: + logger.error("Perl not found. Please ensure Perl is installed and in your PATH.") + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Perl not found. Please ensure Perl is installed and in your PATH.", + "error": "FileNotFoundError", + } + except subprocess.CalledProcessError as e: + logger.error(f"Command failed with exit code {e.returncode}") + logger.error(f"Stderr: {e.stderr}") + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": "CalledProcessError", + "return_code": e.returncode, + } diff --git a/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_shim_server.py b/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0d1143763d809d0d22690e2ce54f8ab4772a9843 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/app/perl-encode-locale_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-encode-locale/app/perl-encode-locale_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_encode_locale' + + +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-encode-locale/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-encode-locale/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-encode-locale/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-encode-locale/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b95551adf433024f7e40ed5d637db0caf2b97a43 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-encode-locale: + build: . + image: mcp-perl-encode-locale:latest + container_name: mcp-perl-encode-locale + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-encode-locale + 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-encode-locale/environment.yaml b/Biomni/mcp_generated/mcp_perl-encode-locale/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..df4e470c2927d1224d1dd3821e33947247718e57 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-encode-locale + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-encode-locale/requirements.txt b/Biomni/mcp_generated/mcp_perl-encode-locale/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode-locale/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-html-element-extended/Dockerfile b/Biomni/mcp_generated/mcp_perl-html-element-extended/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..8b6feeb43952d36e40c934de90248a4c53685b9a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/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-element-extended via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-html-element-extended -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-element-extended_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-element-extended_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-element-extended_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_server.py b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9e6a6c587156b11d4fd56f8abd35e98840923e8a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_server.py @@ -0,0 +1,213 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_html_element_extended' +mcp = FastMCP(SERVER_NAME) + +@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, + record_separator: Optional[str] = None, + autosplit: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + run_under_debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + no_sitecustomize: bool = False, + split_pattern: Optional[str] = None, + edit_in_place: Optional[str] = None, + include_directory: Optional[List[Path]] = None, + use_module: Optional[List[str]] = None, + no_module: Optional[List[str]] = None, + use_module_no_import: Optional[List[str]] = None, + no_module_no_import: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + enable_switch_parsing: bool = False, + search_path_for_program: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + dump_core: bool = False, + allow_unsafe_operations: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + ignore_text_before_shebang: Optional[str] = None, + disable_all_warnings: bool = False, +) -> dict: + """ + Wraps the Perl interpreter command-line interface. Note: The 'perl-html-element-extended' + package is a library and does not provide a standalone executable. This tool exposes the + functionality of the underlying 'perl' interpreter. + + 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 be executed. Omit programfile. + execute_program_extended: Like -e, but enables all optional features. + record_separator: Specify record separator (e.g., '0777' for slurp mode). + autosplit: Enable autosplit mode with -n or -p (splits $_ into @F). + unicode_features: Enable the listed Unicode features (e.g., 'C,S,D'). + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). + run_under_debugger: Run program under debugger. Can specify a debugger (e.g., ':Devel::NYTProf'). + debugging_flags: Set debugging flags (bit mask or alphabets). + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. + split_pattern: split() pattern for -a switch. + edit_in_place: Edit <> files in place. Provide an extension for backup (e.g., '.bak'). + include_directory: Specify @INC/#include directory. Multiple paths can be provided. + use_module: Execute "use module..." before executing the program. + no_module: Execute "no module..." before executing the program. + use_module_no_import: Execute "use module ()" before executing the program. + no_module_no_import: Execute "no module ()" before executing the program. + loop_around_program: Assume "while (<>) { ... }" loop around the program. + loop_and_print: Assume loop like -n but also print line, like sed. + enable_switch_parsing: Enable rudimentary parsing for switches after programfile. + search_path_for_program: Look for programfile using the PATH environment variable. + tainting_warnings: Enable tainting warnings. + tainting_checks: Enable tainting checks. + dump_core: Dump core after parsing the program. + allow_unsafe_operations: Allow unsafe operations. + print_version: Print version, patchlevel, and license. + print_config: Print configuration summary or a single Config.pm variable. + enable_warnings: Enable many useful warnings. + enable_all_warnings: Enable all warnings. + ignore_text_before_shebang: Ignore text before #!perl line. Can optionally specify a directory to cd into. + disable_all_warnings: Disable all warnings. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not any([programfile, execute_program, execute_program_extended, print_version, print_config is not None]): + raise ValueError("Either 'programfile', 'execute_program', 'execute_program_extended', 'print_version', or 'print_config' must be provided.") + + if programfile and (execute_program or execute_program_extended): + raise ValueError("'programfile' cannot be used with 'execute_program' or 'execute_program_extended'.") + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"Program file not found: {programfile}") + + cmd = ["perl"] + + # Build command from parameters + 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 no_sitecustomize: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if edit_in_place is not None: + cmd.append(f"-i{edit_in_place}") + if include_directory: + for directory in include_directory: + cmd.extend(["-I", str(directory)]) + if use_module: + for module in use_module: + cmd.append(f"-M{module}") + if no_module: + for module in no_module: + cmd.append(f"-M-{module}") + if use_module_no_import: + for module in use_module_no_import: + cmd.append(f"-m{module}") + if no_module_no_import: + for module in no_module_no_import: + 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_for_program: + cmd.append("-S") + if tainting_warnings: + cmd.append("-t") + if tainting_checks: + cmd.append("-T") + if dump_core: + cmd.append("-u") + if allow_unsafe_operations: + cmd.append("-U") + if print_version: + cmd.append("-v") + if print_config 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 is not None: + cmd.append(f"-x{ignore_text_before_shebang}") + if disable_all_warnings: + cmd.append("-X") + + if execute_program: + for prog_line in execute_program: + cmd.extend(["-e", prog_line]) + + if execute_program_extended: + for prog_line in execute_program_extended: + cmd.extend(["-E", prog_line]) + + if programfile: + cmd.append(str(programfile)) + + if arguments: + cmd.extend(arguments) + + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + stdout = result.stdout + stderr = result.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed with return code {e.returncode}", + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "perl command not found. Please ensure Perl is installed and in the system's PATH.", + "error": "Command not found", + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [] # Perl interpreter itself doesn't guarantee output files. + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_shim_server.py b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0f0e77cb4d724882874e9e924ee27b6bdeb48d14 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/perl-html-element-extended_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-element-extended/app/perl-html-element-extended_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_html_element_extended' + + +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-element-extended/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-html-element-extended/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-html-element-extended/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..64a618981e9115de3ee271c2e476ff314c3a80b0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-html-element-extended: + build: . + image: mcp-perl-html-element-extended:latest + container_name: mcp-perl-html-element-extended + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-html-element-extended + 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-element-extended/environment.yaml b/Biomni/mcp_generated/mcp_perl-html-element-extended/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dc93a4a5f6349e69e90ae3f76c3d4af152844fc2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-html-element-extended + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-html-element-extended/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-element-extended/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-element-extended/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-html-tidy/app/perl-html-tidy_shim_server.py b/Biomni/mcp_generated/mcp_perl-html-tidy/app/perl-html-tidy_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5a0559a61caaa25e1034e822a41b47753ce2f53d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tidy/app/perl-html-tidy_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-tidy/app/perl-html-tidy_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_html_tidy' + + +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-tidy/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-html-tidy/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-html-tidy/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-http-negotiate/Dockerfile b/Biomni/mcp_generated/mcp_perl-http-negotiate/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6b8d5bb23c9620ee247d54cb37141589c3b78733 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/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-http-negotiate via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-http-negotiate -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-http-negotiate_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-http-negotiate_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-http-negotiate_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_server.py b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b9b8375a63a8950bfcdfc95dfc258aae59086cfd --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_server.py @@ -0,0 +1,194 @@ +import subprocess +import shlex +from pathlib import Path +from typing import Optional, List + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be imported. +def mcp_tool_placeholder(): + def decorator(func): + return func + return decorator + +mcp = type('MCP', (), {'tool': mcp_tool_placeholder}) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_http_negotiate' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + execute_program: Optional[List[str]] = None, + execute_program_with_features: Optional[List[str]] = None, + check_syntax_only: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + record_separator: Optional[str] = None, + autosplit: bool = False, + split_pattern: Optional[str] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + in_place_edit: Optional[str] = None, + line_ending_processing: Optional[str] = None, + include_directory: Optional[List[Path]] = None, + use_module: Optional[List[str]] = None, + no_module: Optional[List[str]] = None, + use_module_m: Optional[List[str]] = None, + no_module_m: Optional[List[str]] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + unicode_features: Optional[str] = None, + run_under_debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + no_sitecustomize: bool = False, + parse_switches: bool = False, + search_path: bool = False, + dump_core: bool = False, + unsafe_operations: bool = False, + ignore_pre_hashbang_text: Optional[str] = None, +): + """ + Executes a script or command using the Perl interpreter. + + This tool is a wrapper for the generic 'perl' executable. The provided + documentation for 'perl-http-negotiate' indicates it is a library, not a + standalone command-line tool. This function therefore exposes the underlying + 'perl' interpreter, within an environment where 'perl-http-negotiate' is + available for use in scripts (e.g., 'use HTTP::Negotiate;'). + + Args: + programfile: Path to the Perl program file to execute. + arguments: A list of arguments to be passed to the program file. + execute_program: One or more lines of a program to be executed. Omit programfile. + execute_program_with_features: Like execute_program, but enables all optional features. + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). Corresponds to -c. + print_version: Print version, patchlevel, and license. Corresponds to -v. + print_config: Print configuration summary. Provide a variable name to see a single value. Corresponds to -V[:variable]. + record_separator: Specify record separator (e.g., an octal number). Corresponds to -0[octal]. + autosplit: Enable autosplit mode with -n or -p (splits $_ into @F). Corresponds to -a. + split_pattern: The pattern to use for splitting in autosplit mode. Corresponds to -F/pattern/. + loop_around_program: Assume "while (<>) { ... }" loop around the program. Corresponds to -n. + loop_and_print: Assume a loop like -n but also print the line, similar to sed. Corresponds to -p. + in_place_edit: Edit <> files in place. Provide an extension to make a backup. Corresponds to -i[extension]. + line_ending_processing: Enable line ending processing and specify the line terminator. Corresponds to -l[octal]. + include_directory: Specify @INC/#include directories. Multiple paths can be provided. Corresponds to -I. + use_module: Execute "use module..." before the program. Corresponds to -Mmodule. + no_module: Execute "no module..." before the program. Corresponds to -M-module. + use_module_m: Execute "use module..." before the program. Corresponds to -mmodule. + no_module_m: Execute "no module..." before the program. Corresponds to -m-module. + enable_warnings: Enable many useful warnings. Corresponds to -w. + enable_all_warnings: Enable all warnings. Corresponds to -W. + disable_all_warnings: Disable all warnings. Corresponds to -X. + tainting_warnings: Enable tainting warnings. Corresponds to -t. + tainting_checks: Enable tainting checks. Corresponds to -T. + unicode_features: Enable the listed Unicode features. Corresponds to -C[number/list]. + run_under_debugger: Run the program under the debugger. Provide a debugger name if needed. Corresponds to -d[:debugger]. + debugging_flags: Set debugging flags (argument is a bit mask or alphabets). Corresponds to -D[number/list]. + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. Corresponds to -f. + parse_switches: Enable rudimentary parsing for switches after the programfile. Corresponds to -s. + search_path: Look for the programfile using the PATH environment variable. Corresponds to -S. + dump_core: Dump core after parsing the program. Corresponds to -u. + unsafe_operations: Allow unsafe operations. Corresponds to -U. + ignore_pre_hashbang_text: Ignore text before #!perl line. Optionally provide a directory to cd into. Corresponds to -x[directory]. + """ + # --- Input Validation --- + execution_target_count = sum([ + programfile is not None, + execute_program is not None, + execute_program_with_features is not None, + print_version, + print_config is not None + ]) + if execution_target_count == 0: + raise ValueError("An execution target is required. Please provide one of 'programfile', 'execute_program', 'execute_program_with_features', 'print_version', or 'print_config'.") + if execution_target_count > 1 and not (print_version and programfile): # perl -v script.pl is valid + if not (print_version and (execute_program or execute_program_with_features)): + raise ValueError("Only one primary execution target ('programfile', 'execute_program', 'execute_program_with_features', 'print_config') can be specified at a time.") + + if programfile and not programfile.exists(): + raise FileNotFoundError(f"The specified program file does not exist: {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{':' if run_under_debugger else ''}{run_under_debugger}") + if debugging_flags is not None: cmd.append(f"-D{debugging_flags}") + if execute_program: + for prog_line in execute_program: cmd.extend(["-e", prog_line]) + if execute_program_with_features: + for prog_line in execute_program_with_features: cmd.extend(["-E", prog_line]) + if no_sitecustomize: cmd.append("-f") + if split_pattern is not None: cmd.append(f"-F{split_pattern}") + if in_place_edit is not None: cmd.append(f"-i{in_place_edit}") + if include_directory: + for directory in include_directory: cmd.extend(["-I", str(directory)]) + if line_ending_processing is not None: cmd.append(f"-l{line_ending_processing}") + if use_module: + for module in use_module: cmd.extend(["-M", module]) + if no_module: + for module in no_module: cmd.extend(["-M-", module]) + if use_module_m: + for module in use_module_m: cmd.extend(["-m", module]) + if no_module_m: + for module in no_module_m: 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 tainting_warnings: cmd.append("-t") + if tainting_checks: cmd.append("-T") + if dump_core: cmd.append("-u") + if unsafe_operations: cmd.append("-U") + if print_version: cmd.append("-v") + if print_config is not None: cmd.append(f"-V{':' if print_config else ''}{print_config}") + if enable_warnings: cmd.append("-w") + if enable_all_warnings: cmd.append("-W") + if ignore_pre_hashbang_text is not None: cmd.append(f"-x{ignore_pre_hashbang_text}") + 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: + raise RuntimeError("The 'perl' executable was not found. Please ensure it is installed and in your system's PATH.") + except subprocess.CalledProcessError as e: + # Non-zero exit code is not necessarily an error for tools like this, + # but we return it structured for the user to decide. + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed with return code {e.returncode}.", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_shim_server.py b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d0fb36e7f91d9614ca5594a21352a10b5e3e28b2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/perl-http-negotiate_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-http-negotiate/app/perl-http-negotiate_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_http_negotiate' + + +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-http-negotiate/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-http-negotiate/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-http-negotiate/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e6b9a1b636ebe48f3f2ef3bd385afaa72df6295e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-http-negotiate: + build: . + image: mcp-perl-http-negotiate:latest + container_name: mcp-perl-http-negotiate + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-http-negotiate + 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-http-negotiate/environment.yaml b/Biomni/mcp_generated/mcp_perl-http-negotiate/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..82c6f3445aa230c6ff9baf1e4fc14bca2456ad4b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-http-negotiate + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-http-negotiate/requirements.txt b/Biomni/mcp_generated/mcp_perl-http-negotiate/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-negotiate/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/Dockerfile b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..36ae1de89a3a46a5207eb1e7126337ea9c0de0dd --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/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-runtime-conflicts via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-module-runtime-conflicts -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-runtime-conflicts_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-runtime-conflicts_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-runtime-conflicts_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_server.py b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_server.py new file mode 100644 index 0000000000000000000000000000000000000000..cb55048b62f96a1f4082a3d2b26acb99c4aeb011 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_server.py @@ -0,0 +1,89 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, List, Any + +# @mcp.tool() is a placeholder for the actual decorator. +# The user does not want the import statement for mcp. +class mcp: + def tool(func): + return func + +@mcp.tool +def check_module_runtime_conflicts() -> Dict[str, Any]: + """ + Checks for version conflicts related to the Perl module Module::Runtime. + + This tool executes a small Perl script that uses Module::Runtime::Conflicts + to check for known version conflicts with other installed Perl modules. + If conflicts are found, the tool will exit with an error and print details + to stderr. If no conflicts are found, it will report success to stdout. + This functionality is typically used by other tools like 'moose-outdated'. + """ + # The Perl code to execute, based on the module's SYNOPSIS. + # The check_conflicts() function 'dies' (exits with an error) if a conflict is found. + # We wrap it in an eval block to catch the error message and control the output. + perl_script_content = """ + use strict; + use warnings; + use Module::Runtime::Conflicts; + + eval { + Module::Runtime::Conflicts->check_conflicts; + }; + if ($@) { + # The check_conflicts function dies on conflict. + # We catch it, print the error message to STDERR, and exit with an error code. + print STDERR $@; + exit 1; + } + + print "No conflicts found for Module::Runtime.\\n"; + exit 0; + """ + + script_path = None + try: + # Using a temporary file is robust for multi-line scripts. + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".pl", encoding='utf-8') as temp_script: + script_path_str = temp_script.name + temp_script.write(perl_script_content) + + script_path = Path(script_path_str) + command = ["perl", str(script_path)] + command_str = " ".join(command) + + # Execute the perl script + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + # This happens if 'perl' is not in the system's PATH + return { + "command_executed": "perl ", + "stdout": "", + "stderr": "Error: 'perl' executable not found. Please ensure Perl is installed and in your system's PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + # This happens if the script returns a non-zero exit code (i.e., conflicts found) + return { + "command_executed": " ".join(["perl", str(script_path)]) if script_path else "perl ", + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + finally: + # Ensure the temporary file is always cleaned up + if script_path and script_path.exists(): + script_path.unlink() diff --git a/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_shim_server.py b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..67bdcfe57feb720ae388a751ce2525aeca180eb0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_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-runtime-conflicts/app/perl-module-runtime-conflicts_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_module_runtime_conflicts' + + +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-runtime-conflicts/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/environment.yaml b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aa0fe5e007443813c16fc0c0a824e7f062e4817d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-module-runtime-conflicts + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-runtime-conflicts/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-sereal/Dockerfile b/Biomni/mcp_generated/mcp_perl-sereal/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c7fc5b8ec86f042e98e1a6bd6a6f3cdba4d8e7dc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/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-sereal via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-sereal -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-sereal_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-sereal_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-sereal_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_server.py b/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_server.py new file mode 100644 index 0000000000000000000000000000000000000000..60da427900f1c8d9c9fdb46c9ea362831b460d8f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_server.py @@ -0,0 +1,209 @@ +import subprocess +import shlex +from pathlib import Path +from typing import Optional, List + +# MCP decorator is omitted as per instructions, but would be present in a real MCP environment. +# @mcp.tool( +# name="perl-sereal", +# description="A wrapper for the Perl interpreter, the execution environment for the Sereal module." +# ) +def perl_interpreter( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + execute_program: Optional[List[str]] = None, + execute_program_features: Optional[List[str]] = None, + check_syntax_only: bool = False, + print_version: bool = False, + print_config_variable: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + use_module: Optional[List[str]] = None, + no_module: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + autosplit: bool = False, + split_pattern: Optional[str] = None, + in_place_edit_extension: Optional[str] = None, + record_separator: Optional[str] = None, + line_terminator: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + enable_tainting_checks: bool = False, + enable_tainting_warnings: bool = False, + allow_unsafe_operations: bool = False, + 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_for_program: bool = False, + dump_core: bool = False, + ignore_text_before_shebang_dir: Optional[Path] = None, +) -> dict: + """ + Executes a Perl script or command. This tool wraps the `perl` interpreter, + which is the execution environment for Perl modules like Sereal. + + 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 be executed via the -e switch. + execute_program_features: Like execute_program, but enables all optional features (-E). + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks). Corresponds to -c. + print_version: Print version, patchlevel, and license. Corresponds to -v. + print_config_variable: Print configuration summary. If a value is provided, prints a single Config.pm variable. Corresponds to -V[:variable]. + include_directories: Specify @INC/#include directories. Corresponds to -I. + use_module: Execute "use module..." before the program. Corresponds to -M. + no_module: Execute "no module..." before the program. Corresponds to -m. + loop_around_program: Assume "while (<>) { ... }" loop around the program. Corresponds to -n. + loop_and_print: Assume loop like -n but also print the line. Corresponds to -p. + autosplit: Enable autosplit mode with -n or -p (splits $_ into @F). Corresponds to -a. + split_pattern: Specify the split() pattern for the -a switch. Corresponds to -F. + in_place_edit_extension: Edit <> files in place. If an extension is supplied, a backup is made. Corresponds to -i[extension]. + record_separator: Specify the record separator (e.g., as an octal value). Corresponds to -0[octal]. + line_terminator: Enable line ending processing and specify the line terminator. Corresponds to -l[octal]. + enable_warnings: Enable many useful warnings. Corresponds to -w. + enable_all_warnings: Enable all warnings. Corresponds to -W. + disable_all_warnings: Disable all warnings. Corresponds to -X. + enable_tainting_checks: Enable tainting checks for security. Corresponds to -T. + enable_tainting_warnings: Enable tainting warnings. Corresponds to -t. + allow_unsafe_operations: Allow unsafe operations. Corresponds to -U. + run_under_debugger: Run the program under the specified debugger. Corresponds to -d[:debugger]. + debugging_flags: Set debugging flags. Corresponds to -D[number/list]. + unicode_features: Enable the listed Unicode features. Corresponds to -C[number/list]. + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup. Corresponds to -f. + enable_switch_parsing: Enable rudimentary parsing for switches after the program file. Corresponds to -s. + search_path_for_program: Look for the program file using the PATH environment variable. Corresponds to -S. + dump_core: Dump core after parsing the program. Corresponds to -u. + ignore_text_before_shebang_dir: Ignore text before #!perl line and optionally cd to the specified directory. Corresponds to -x[directory]. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if programfile and (execute_program or execute_program_features): + raise ValueError("Cannot specify both a 'programfile' and 'execute_program'/'execute_program_features'.") + + if not any([programfile, execute_program, execute_program_features, print_version, print_config_variable is not None]): + raise ValueError("Must provide a program to run (e.g., 'programfile' or 'execute_program') or an info flag (e.g., 'print_version').") + + if programfile and not programfile.exists(): + raise FileNotFoundError(f"The specified program file does not exist: {programfile}") + + if autosplit and not (loop_around_program or loop_and_print): + raise ValueError("Autosplit mode (-a) requires a loop flag (-n or -p) to be enabled.") + + if split_pattern and not autosplit: + raise ValueError("A split pattern (-F) can only be used with autosplit mode (-a).") + + if sum([enable_warnings, enable_all_warnings, disable_all_warnings]) > 1: + raise ValueError("Warning flags -w, -W, and -X are mutually exclusive.") + + # --- 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_line in execute_program: + cmd.extend(["-e", prog_line]) + if execute_program_features: + for prog_line in execute_program_features: + cmd.extend(["-E", prog_line]) + if no_sitecustomize: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if in_place_edit_extension is not None: + cmd.append(f"-i{in_place_edit_extension}") + if include_directories: + for directory in include_directories: + cmd.extend(["-I", str(directory)]) + if line_terminator is not None: + cmd.append(f"-l{line_terminator}") + if use_module: + for mod in use_module: + cmd.extend(["-M", mod]) + if no_module: + for mod in no_module: + cmd.extend(["-m", mod]) + if loop_around_program: + cmd.append("-n") + if loop_and_print: + cmd.append("-p") + if enable_switch_parsing: + cmd.append("-s") + if search_path_for_program: + 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_variable is not None: + cmd.append(f"-V{print_config_variable}") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if ignore_text_before_shebang_dir: + cmd.extend(["-x", str(ignore_text_before_shebang_dir)]) + if disable_all_warnings: + cmd.append("-X") + + if programfile: + cmd.append(str(programfile)) + + if arguments: + cmd.append("--") + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = shlex.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": [] # The interpreter itself doesn't guarantee output files. + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_shim_server.py b/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..64dabea19f72de5da45eadd2ad6dcb6f18c10d8a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/app/perl-sereal_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-sereal/app/perl-sereal_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_sereal' + + +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-sereal/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-sereal/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-sereal/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-sereal/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..17b091ee6a6e0de26c0dc58fd19826c191dfd0a2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-sereal: + build: . + image: mcp-perl-sereal:latest + container_name: mcp-perl-sereal + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-sereal + 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-sereal/environment.yaml b/Biomni/mcp_generated/mcp_perl-sereal/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..72cebcb51617d918039b61e1eb78a8efaa052893 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-sereal + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sereal/requirements.txt b/Biomni/mcp_generated/mcp_perl-sereal/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/environment.yaml b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d71e545f2f858462b13761cfadcfc84ade648af4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-xml-namespacesupport + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/requirements.txt b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-xml-namespacesupport/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-restfulr/Dockerfile b/Biomni/mcp_generated/mcp_r-restfulr/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..22212f8f2ea88e3e344cdc21fbb245764912ebf0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install r-restfulr via conda (e.g., from bioconda) +RUN conda install -c bioconda r-restfulr -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/r-restfulr_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/r-restfulr_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/r-restfulr_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_server.py b/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_server.py new file mode 100644 index 0000000000000000000000000000000000000000..bfafeceb9bd850c1c8786e24a8267daf39c3a638 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_server.py @@ -0,0 +1,264 @@ +import subprocess +import json +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_restfulr' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def restfulr_get( + base_url: str, + path: str = "", + suppress_messages: bool = True +) -> Dict[str, Any]: + """ + Retrieve data from a RESTful service using restfulr, modeling the service as a nested R list. + + Args: + base_url: The base URL of the RESTful service (e.g., 'https://api.github.com'). + path: The resource path within the service (e.g., 'users/octocat/repos'). + suppress_messages: Whether to suppress R startup messages and warnings. + """ + if not base_url.startswith(("http://", "https://")): + return {"error": "base_url must start with http:// or https://"} + + # Construct R script to fetch data and convert to JSON + r_code = f""" + suppressPackageStartupMessages(library(restfulr)) + suppressPackageStartupMessages(library(rjson)) + + container <- RestContainer("{base_url}") + + tryCatch({{ + resource <- if ("{path}" == "") container else container[["{path}"]] + # Convert S4/List objects to JSON for output + cat(toJSON(resource)) + }}, error = function(e) {{ + write(conditionMessage(e), stderr()) + q(status = 1) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + + # Attempt to parse the JSON output from R + try: + output_data = json.loads(result.stdout) + except json.JSONDecodeError: + output_data = result.stdout + + return { + "command_executed": f"restfulr::RestContainer('{base_url}')[['{path}']]", + "stdout": result.stdout, + "stderr": result.stderr, + "data": output_data + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "command_executed": e.cmd, + "stdout": e.stdout, + "stderr": e.stderr, + "returncode": e.returncode + } + +@mcp.tool() +def restfulr_post( + base_url: str, + path: str, + data_json: str +) -> Dict[str, Any]: + """ + Send data to a RESTful service (POST) using restfulr's list-assignment syntax. + + Args: + base_url: The base URL of the RESTful service. + path: The resource path where data should be posted. + data_json: A JSON string representing the data to be sent. + """ + if not base_url.startswith(("http://", "https://")): + return {"error": "base_url must start with http:// or https://"} + + # Validate JSON + try: + json.loads(data_json) + except json.JSONDecodeError: + return {"error": "data_json must be a valid JSON string"} + + # Construct R script to perform the POST operation + # In restfulr, container[[path]] <- value performs a POST or PUT + r_code = f""" + suppressPackageStartupMessages(library(restfulr)) + suppressPackageStartupMessages(library(rjson)) + + container <- RestContainer("{base_url}") + payload <- fromJSON('{data_json}') + + tryCatch({{ + container[["{path}"]] <- payload + cat("Successfully posted data to {path}") + }}, error = function(e) {{ + write(conditionMessage(e), stderr()) + q(status = 1) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"restfulr::RestContainer('{base_url}')[['{path}']] <- data", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def restfulr_delete( + base_url: str, + path: str +) -> Dict[str, Any]: + """ + Delete a resource from a RESTful service using restfulr. + + Args: + base_url: The base URL of the RESTful service. + path: The resource path to be deleted. + """ + if not base_url.startswith(("http://", "https://")): + return {"error": "base_url must start with http:// or https://"} + + # In restfulr, setting a resource to NULL typically triggers a DELETE request + r_code = f""" + suppressPackageStartupMessages(library(restfulr)) + + container <- RestContainer("{base_url}") + + tryCatch({{ + container[["{path}"]] <- NULL + cat("Successfully deleted resource at {path}") + }}, error = function(e) {{ + write(conditionMessage(e), stderr()) + q(status = 1) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"restfulr::RestContainer('{base_url}')[['{path}']] <- NULL", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "R execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def restfulr_custom_query( + r_script_content: str +) -> Dict[str, Any]: + """ + Execute a custom R script utilizing the restfulr library for complex REST interactions. + + Args: + r_script_content: The full R code to execute. Must include library(restfulr). + """ + with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp: + tmp.write(r_script_content) + tmp_path = Path(tmp.name) + + try: + result = subprocess.run( + ["Rscript", str(tmp_path)], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript {tmp_path.name}", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "Custom R script execution failed", + "stdout": e.stdout, + "stderr": e.stderr + } + finally: + if tmp_path.exists(): + tmp_path.unlink() + +@mcp.tool() +def restfulr_check_status( + base_url: str +) -> Dict[str, Any]: + """ + Check if a RESTful service is reachable and can be modeled by restfulr. + + Args: + base_url: The base URL of the RESTful service. + """ + r_code = f""" + suppressPackageStartupMessages(library(restfulr)) + + tryCatch({{ + container <- RestContainer("{base_url}") + # Attempt to access the root to verify connectivity + print(container) + cat("Status: Reachable") + }}, error = function(e) {{ + write(conditionMessage(e), stderr()) + q(status = 1) + }}) + """ + + try: + result = subprocess.run( + ["Rscript", "-e", r_code], + capture_output=True, + text=True, + check=True + ) + return { + "status": "reachable", + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "status": "unreachable", + "error": e.stderr, + "stdout": e.stdout + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_shim_server.py b/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..37a80fcab5b43e2cde413595f47168d479b5a172 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/app/r-restfulr_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-restfulr/app/r-restfulr_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_r_restfulr' + + +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-restfulr/app/requirements.txt b/Biomni/mcp_generated/mcp_r-restfulr/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_r-restfulr/docker-compose.yml b/Biomni/mcp_generated/mcp_r-restfulr/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..d3cd6a5c426a9c67e6bb91a06fbf4bb16b7a927b --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-restfulr: + build: . + image: mcp-r-restfulr:latest + container_name: mcp-r-restfulr + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-restfulr + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-restfulr/environment.yaml b/Biomni/mcp_generated/mcp_r-restfulr/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7a3fffa6d2a2a0f5bf9295f41130445672e3be64 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - r-restfulr + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-restfulr/requirements.txt b/Biomni/mcp_generated/mcp_r-restfulr/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-restfulr/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/Dockerfile b/Biomni/mcp_generated/mcp_r-seurat-data/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4f78b6c6457c79dbfc18097a79a442af86dabdae --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install r-seurat-data via conda (e.g., from bioconda) +RUN conda install -c bioconda r-seurat-data -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/r-seurat-data_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/r-seurat-data_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/r-seurat-data_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_server.py b/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_server.py new file mode 100644 index 0000000000000000000000000000000000000000..aa2bdf549ff06ce349dccba26950f922dd712b77 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_server.py @@ -0,0 +1,205 @@ +import subprocess +from typing import Optional +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_r_seurat_data' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def seurat_data_available_data() -> dict: + """ + List all available datasets in the SeuratData repository. + This manifest includes metadata such as version, summary, species, system, + number of cells, technology used, and installation status. + """ + # R command to load SeuratData and call AvailableData() + # We use print() to ensure the output is captured in stdout + r_command = "library(SeuratData); print(AvailableData())" + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to retrieve available datasets. Ensure R and SeuratData are installed." + } + +@mcp.tool() +def seurat_data_install_data( + dataset_name: str +) -> dict: + """ + Install a specific single-cell dataset from the SeuratData repository. + + Args: + dataset_name: The name of the dataset (e.g., 'pbmc3k') or the package name (e.g., 'pbmc3k.SeuratData'). + """ + if not dataset_name: + return {"error": "dataset_name is required"} + + # R command to install the dataset + r_command = f"library(SeuratData); InstallData('{dataset_name}')" + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": result.stdout, + "stderr": result.stderr, + "status": f"Successfully installed {dataset_name}" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Failed to install dataset '{dataset_name}'. Check if the name is correct and internet connection is available." + } + +@mcp.tool() +def seurat_data_get_summary( + dataset_name: str +) -> dict: + """ + Load a dataset and return its Seurat object summary (features, samples, assays, etc.). + Note: The dataset must be installed first using seurat_data_install_data. + + Args: + dataset_name: The name of the dataset to load (e.g., 'pbmc3k'). + """ + if not dataset_name: + return {"error": "dataset_name is required"} + + # R command to load the dataset and print the object summary + # data() loads it into the environment, then we print the object named after the dataset + r_command = f"library(SeuratData); data('{dataset_name}'); print({dataset_name})" + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": result.stdout, + "stderr": result.stderr, + "dataset": dataset_name + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Failed to load dataset '{dataset_name}'. Ensure it is installed." + } + +@mcp.tool() +def seurat_data_get_citation( + dataset_name: str +) -> dict: + """ + Retrieve the citation information for a specific SeuratData dataset. + + Args: + dataset_name: The name of the dataset. The tool will automatically append '.SeuratData' if not provided. + """ + if not dataset_name: + return {"error": "dataset_name is required"} + + # Ensure the package name format is correct (e.g., pbmc3k.SeuratData) + pkg_name = dataset_name if dataset_name.endswith(".SeuratData") else f"{dataset_name}.SeuratData" + + # R command to get citation + r_command = f"library(SeuratData); print(citation('{pkg_name}'))" + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": result.stdout, + "stderr": result.stderr, + "package": pkg_name + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Failed to get citation for '{pkg_name}'. Ensure the package is installed." + } + +@mcp.tool() +def seurat_data_get_help( + dataset_name: str +) -> dict: + """ + Retrieve the help documentation/metadata for a specific dataset. + This is equivalent to running ?dataset_name in R. + + Args: + dataset_name: The name of the dataset (e.g., 'pbmc3k'). + """ + if not dataset_name: + return {"error": "dataset_name is required"} + + # In R, help() returns a help object. To get text, we use tools::Rd2txt + r_command = ( + f"library(SeuratData); " + f"h <- help('{dataset_name}'); " + f"if(length(h) > 0) {{ " + f" writeLines(as.character(tools::Rd2txt(utils:::.getHelpFile(h)))) " + f"}} else {{ " + f" stop('Help not found') " + f"}}" + ) + + try: + result = subprocess.run( + ["Rscript", "-e", r_command], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": result.stdout, + "stderr": result.stderr, + "dataset": dataset_name + } + except subprocess.CalledProcessError as e: + return { + "command_executed": f"Rscript -e \"{r_command}\"", + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Could not find help documentation for '{dataset_name}'." + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_shim_server.py b/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..47934ef5478d5f9ce839373f343cf934fb858373 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/app/r-seurat-data_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-seurat-data/app/r-seurat-data_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_r_seurat_data' + + +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-seurat-data/app/requirements.txt b/Biomni/mcp_generated/mcp_r-seurat-data/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/docker-compose.yml b/Biomni/mcp_generated/mcp_r-seurat-data/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..357620da42ce443301b7bd731c59bb27487e494a --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-r-seurat-data: + build: . + image: mcp-r-seurat-data:latest + container_name: mcp-r-seurat-data + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=r-seurat-data + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/environment.yaml b/Biomni/mcp_generated/mcp_r-seurat-data/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e9cd6b68d25202014d7c17a31d26c636c51fda26 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - r-seurat-data + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_r-seurat-data/requirements.txt b/Biomni/mcp_generated/mcp_r-seurat-data/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_r-seurat-data/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_rscape/Dockerfile b/Biomni/mcp_generated/mcp_rscape/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6da87503dec651e9bf7aa1604e7e943d70ec971b --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/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 rscape via conda (e.g., from bioconda) +RUN conda install -c bioconda rscape -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/rscape_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/rscape_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/rscape_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_rscape/app/requirements.txt b/Biomni/mcp_generated/mcp_rscape/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_rscape/app/rscape_server.py b/Biomni/mcp_generated/mcp_rscape/app/rscape_server.py new file mode 100644 index 0000000000000000000000000000000000000000..2d08747319d883671fbb0c1676910f771fed8f6c --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/app/rscape_server.py @@ -0,0 +1,293 @@ +import subprocess +from pathlib import Path +from typing import Optional, Literal, List +import tempfile +import shutil + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_rscape' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def rscape( + input_alignment_file: Path, + operation_mode: Literal[ + "evaluate_conserved_structure", + "predict_new_structure", + "evaluate_given_structure", + "improve_given_structure", + ] = "evaluate_conserved_structure", + e_value: float = 0.01, + output_cov_filename: Optional[str] = None, + output_format: Literal["text", "json", "stockholm"] = "text", + output_prefix: Optional[str] = None, + no_plot: bool = False, + plot_only: bool = False, + verbose: bool = False, + num_cpus: Optional[int] = None, + random_seed: Optional[int] = None, + sequence_type: Literal["rna", "dna"] = "rna", + gap_character: str = "-", + min_covarying_sequences: int = 2, + max_gap_fraction: float = 0.9, + min_sequences: int = 2, + min_alignment_length: int = 2, + max_alignment_length: int = 10000, + min_base_pair_frequency: float = 0.05, + min_posterior_probability: float = 0.5, + max_posterior_probability: float = 0.95, + no_pseudoknots: bool = False, + no_coevolution_analysis: bool = False, + no_phylogenetic_tree: bool = False, + tree_file: Optional[Path] = None, + tree_inference_method: Optional[Literal["fasttree", "raxml"]] = None, + tree_substitution_model: Optional[str] = None, + tree_random_seed: Optional[int] = None, + tree_bootstraps: Optional[int] = None, + tree_output_filename: Optional[str] = None, +) -> dict: + """ + R-scape (RNA Structural Covariation Above Phylogenetic Expectation) looks for evidence + of a conserved RNA secondary structure by measuring pairwise covariations observed + in an input multiple sequence alignment. + + It analyzes all possible pairs, including those in your proposed structure (if you provide one). + R-scape uses a null hypothesis that takes phylogenetic correlations and base composition biases + into account. + + The input is an RNA multiple sequence alignment in Stockholm format, optionally with an + annotated consensus secondary structure. + + Note: 'evaluate_given_structure' and 'improve_given_structure' modes require a consensus + secondary structure annotation in the input Stockholm file. + """ + + # --- Input Validation --- + if not input_alignment_file.is_file(): + raise FileNotFoundError(f"Input alignment file not found: {input_alignment_file}") + + if e_value <= 0: + raise ValueError(f"E-value must be a positive float, got {e_value}") + + if output_cov_filename and not output_cov_filename.strip(): + raise ValueError("Output covariation filename cannot be empty or whitespace.") + if tree_output_filename and not tree_output_filename.strip(): + raise ValueError("Tree output filename cannot be empty or whitespace.") + + if plot_only and no_plot: + raise ValueError("Cannot specify both 'plot_only' and 'no_plot'.") + + if num_cpus is not None and num_cpus <= 0: + raise ValueError(f"Number of CPUs must be a positive integer, got {num_cpus}") + + if min_covarying_sequences <= 0: + raise ValueError(f"Minimum covarying sequences must be a positive integer, got {min_covarying_sequences}") + if min_sequences <= 0: + raise ValueError(f"Minimum sequences must be a positive integer, got {min_sequences}") + if min_alignment_length <= 0: + raise ValueError(f"Minimum alignment length must be a positive integer, got {min_alignment_length}") + if max_alignment_length <= 0: + raise ValueError(f"Maximum alignment length must be a positive integer, got {max_alignment_length}") + if min_alignment_length > max_alignment_length: + raise ValueError("Minimum alignment length cannot be greater than maximum alignment length.") + + if not (0 <= max_gap_fraction <= 1): + raise ValueError(f"Maximum gap fraction must be between 0 and 1, got {max_gap_fraction}") + if not (0 <= min_base_pair_frequency <= 1): + raise ValueError(f"Minimum base pair frequency must be between 0 and 1, got {min_base_pair_frequency}") + if not (0 <= min_posterior_probability <= 1): + raise ValueError(f"Minimum posterior probability must be between 0 and 1, got {min_posterior_probability}") + if not (0 <= max_posterior_probability <= 1): + raise ValueError(f"Maximum posterior probability must be between 0 and 1, got {max_posterior_probability}") + if min_posterior_probability > max_posterior_probability: + raise ValueError("Minimum posterior probability cannot be greater than maximum posterior probability.") + + if tree_file and not tree_file.is_file(): + raise FileNotFoundError(f"Phylogenetic tree file not found: {tree_file}") + + # --- Command Construction --- + cmd = ["rscape"] + + # Create a temporary directory for all outputs + with tempfile.TemporaryDirectory() as temp_dir_str: + temp_output_dir = Path(temp_dir_str) + + # E-value + cmd.extend(["-E", str(e_value)]) + + # Operation Mode + if operation_mode == "evaluate_conserved_structure": + pass # Default behavior + elif operation_mode == "predict_new_structure": + cmd.extend(["--allpairs", "--caco"]) + elif operation_mode == "evaluate_given_structure": + cmd.extend(["--given", "--other"]) + elif operation_mode == "improve_given_structure": + cmd.append("--improve") + + # Output options + if output_cov_filename: + # Use the provided filename for the main covariation output + cmd.extend(["-o", str(temp_output_dir / output_cov_filename)]) + + cmd.extend(["--outfmt", output_format]) + + # Determine the effective prefix for other generated files + # If output_prefix is provided, use it. Otherwise, use input file stem. + effective_prefix = output_prefix if output_prefix else input_alignment_file.stem + + # Use --prefix to direct all other outputs to the temp directory with the chosen prefix + cmd.extend(["--prefix", str(temp_output_dir / effective_prefix)]) + + if no_plot: + cmd.append("--no-plot") + if plot_only: + cmd.append("--plot-only") + if verbose: + cmd.append("--verbose") + + if num_cpus is not None: + cmd.extend(["--cpu", str(num_cpus)]) + if random_seed is not None: + cmd.extend(["--seed", str(random_seed)]) + + if sequence_type == "dna": + cmd.append("--dna") + # else: --rna is default, no flag needed + + if gap_character != "-": + cmd.extend(["--gap-char", gap_character]) + + if min_covarying_sequences != 2: + cmd.extend(["--min-cov", str(min_covarying_sequences)]) + if max_gap_fraction != 0.9: + cmd.extend(["--max-gap", str(max_gap_fraction)]) + if min_sequences != 2: + cmd.extend(["--min-seqs", str(min_sequences)]) + if min_alignment_length != 2: + cmd.extend(["--min-len", str(min_alignment_length)]) + if max_alignment_length != 10000: + cmd.extend(["--max-len", str(max_alignment_length)]) + if min_base_pair_frequency != 0.05: + cmd.extend(["--min-freq", str(min_base_pair_frequency)]) + + # CaCoFold specific options (only relevant if --caco or --improve is used) + if operation_mode in ["predict_new_structure", "improve_given_structure"]: + if min_posterior_probability != 0.5: + cmd.extend(["--min-pp", str(min_posterior_probability)]) + if max_posterior_probability != 0.95: + cmd.extend(["--max-pp", str(max_posterior_probability)]) + if no_pseudoknots: + cmd.append("--no-pseudoknots") + + if no_coevolution_analysis: + cmd.append("--no-coev") + + # Phylogenetic tree options + if no_phylogenetic_tree: + cmd.append("--no-tree") + else: + if tree_file: + cmd.extend(["--tree-file", str(tree_file)]) + if tree_inference_method: + cmd.extend(["--tree-method", tree_inference_method]) + if tree_substitution_model: + cmd.extend(["--tree-model", tree_substitution_model]) + if tree_random_seed is not None: + cmd.extend(["--tree-seed", str(tree_random_seed)]) + if tree_bootstraps is not None: + cmd.extend(["--tree-boot", str(tree_bootstraps)]) + if tree_output_filename: + # Use the provided filename for the tree output + cmd.extend(["--tree-out", str(temp_output_dir / tree_output_filename)]) + + # Positional argument + cmd.append(str(input_alignment_file)) + + # --- Execute Command --- + process = None + try: + process = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + cwd=temp_output_dir # Run in the temporary output directory + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"R-scape execution failed with exit code {e.returncode}", + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "rscape command not found. Please ensure it is installed and in your PATH.", + "error": "rscape executable not found.", + "output_files": [], + } + + # --- Collect Output Files from temp_output_dir --- + generated_output_files: List[Path] = [] + + # Check for the explicitly named covariation file if output_cov_filename was provided + if output_cov_filename: + explicit_cov_file = temp_output_dir / output_cov_filename + if explicit_cov_file.is_file(): + generated_output_files.append(explicit_cov_file) + + # Check for the explicitly named tree file if tree_output_filename was provided + if tree_output_filename: + explicit_tree_file = temp_output_dir / tree_output_filename + if explicit_tree_file.is_file(): + generated_output_files.append(explicit_tree_file) + + # Collect other files based on the effective_prefix + # This includes the default .rscape.cov if -o was not used, and .rscape.ss, .rscape.ps, .rscape.pdf, .rscape.tree + + # Default covariation file (if -o was not used) + if not output_cov_filename: + default_cov_file = temp_output_dir / (effective_prefix + ".rscape.cov") + if default_cov_file.is_file(): + generated_output_files.append(default_cov_file) + + # Predicted secondary structure file + if operation_mode in ["predict_new_structure", "improve_given_structure"]: + ss_file = temp_output_dir / (effective_prefix + ".rscape.ss") + if ss_file.is_file(): + generated_output_files.append(ss_file) + + # Plot files + if not no_plot: # Plots are generated by default unless --no-plot + ps_file = temp_output_dir / (effective_prefix + ".rscape.ps") + if ps_file.is_file(): + generated_output_files.append(ps_file) + pdf_file = temp_output_dir / (effective_prefix + ".rscape.pdf") + if pdf_file.is_file(): + generated_output_files.append(pdf_file) + + # Default tree file (if --tree-out was not used) + if not no_phylogenetic_tree and not tree_output_filename: + default_tree_file = temp_output_dir / (effective_prefix + ".rscape.tree") + if default_tree_file.is_file(): + generated_output_files.append(default_tree_file) + + # Ensure unique paths and convert to string + unique_output_paths = sorted(list(set(str(p) for p in generated_output_files))) + + return { + "command_executed": " ".join(cmd), + "stdout": stdout, + "stderr": stderr, + "output_files": unique_output_paths, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_rscape/app/rscape_shim_server.py b/Biomni/mcp_generated/mcp_rscape/app/rscape_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..defc9d44d2b6c3cff3626686142c9b30ffbcf198 --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/app/rscape_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_rscape/app/rscape_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_rscape' + + +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_rscape/docker-compose.yml b/Biomni/mcp_generated/mcp_rscape/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9f99b27eac3a2b1529ad927eb04bfcd496e01e61 --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-rscape: + build: . + image: mcp-rscape:latest + container_name: mcp-rscape + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=rscape + 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_rscape/environment.yaml b/Biomni/mcp_generated/mcp_rscape/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3c308c44faccab239778870d8f38407f988bf7de --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - rscape + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_rscape/requirements.txt b/Biomni/mcp_generated/mcp_rscape/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_rscape/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_server.py b/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_server.py new file mode 100644 index 0000000000000000000000000000000000000000..543cc2df78dce4329ca17d462218e56d48501159 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_server.py @@ -0,0 +1,161 @@ +import json +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, List, Literal, Optional + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, you would import it, e.g., `from mcp import tool`. +class mcp: + def tool(func): + return func + +@mcp.tool +def sage_search( + mzml_paths: List[Path], + fasta: Path, + output_directory: Optional[Path] = None, + peptide_fdr: float = 0.01, + protein_fdr: float = 0.01, + report_psms: int = 1, + chimera: bool = False, + min_ion_index: int = 2, + db_bucket: Optional[str] = None, + missed_cleavages: int = 0, + fragment_min_mz: float = 200.0, + fragment_max_mz: float = 2000.0, + peptide_min_len: int = 5, + peptide_max_len: int = 50, + peptide_min_mass: float = 500.0, + peptide_max_mass: float = 5000.0, + static_mods: Optional[Dict[str, float]] = None, + variable_mods: Optional[Dict[str, float]] = None, + max_variable_mods: int = 2, + enzyme_cleave_at: str = "KR", + enzyme_restrict: Optional[str] = "P", + enzyme_c_terminal: bool = True, + decoy_tag: str = "rev_", + generate_decoys: bool = True, + quant_tmt: Optional[Literal["Tmt10", "Tmt11", "Tmt16", "Tmt18"]] = None, + quant_tmt_level: int = 3, + quant_lfq: bool = False, + quant_lfq_min_peptide_ratios: int = 1, + quant_lfq_min_peptides: int = 1, +) -> Dict: + """ + Runs a Sage proteomics search, dynamically generating the required JSON configuration. + + This tool encapsulates the functionality of the `sage` command-line tool, + exposing all its configuration options as parameters. It handles the creation + of the JSON parameter file, execution of the search, and management of output files. + """ + # 1. Input Validation + if not fasta.is_file(): + raise FileNotFoundError(f"FASTA file not found: {fasta}") + if not mzml_paths: + raise ValueError("At least one mzML file must be provided in `mzml_paths`.") + for path in mzml_paths: + if not Path(path).is_file(): + raise FileNotFoundError(f"mzML file not found: {path}") + + if not 0.0 <= peptide_fdr <= 1.0: + raise ValueError("`peptide_fdr` must be between 0.0 and 1.0.") + if not 0.0 <= protein_fdr <= 1.0: + raise ValueError("`protein_fdr` must be between 0.0 and 1.0.") + if quant_tmt and quant_tmt_level not in [2, 3]: + raise ValueError("`quant_tmt_level` must be 2 or 3.") + + # 2. Setup temporary directory for configuration and potential output + with tempfile.TemporaryDirectory() as temp_dir_str: + temp_dir = Path(temp_dir_str) + config_path = temp_dir / "params.json" + + # Determine output directory + if output_directory: + # Use user-provided directory + output_path = Path(output_directory) + output_path.mkdir(parents=True, exist_ok=True) + else: + # Create a temporary directory for results if none is specified + output_path = temp_dir / "sage_results" + output_path.mkdir() + + # 3. Construct the configuration dictionary + config = { + "database": { + "bucket": db_bucket, + "fasta": str(fasta), + "missed_cleavages": missed_cleavages, + "fragment_min_mz": fragment_min_mz, + "fragment_max_mz": fragment_max_mz, + "peptide_min_len": peptide_min_len, + "peptide_max_len": peptide_max_len, + "peptide_min_mass": peptide_min_mass, + "peptide_max_mass": peptide_max_mass, + "min_ion_index": min_ion_index, + "static_mods": static_mods or {}, + "variable_mods": variable_mods or {}, + "max_variable_mods": max_variable_mods, + "enzyme": { + "cleave_at": enzyme_cleave_at, + "restrict": enzyme_restrict, + "c_terminal": enzyme_c_terminal, + }, + "decoy_tag": decoy_tag, + "generate_decoys": generate_decoys, + }, + "quant": { + "tmt": quant_tmt, + "tmt_level": quant_tmt_level, + "lfq": quant_lfq, + "lfq_min_peptide_ratios": quant_lfq_min_peptide_ratios, + "lfq_min_peptides": quant_lfq_min_peptides, + }, + "output_directory": str(output_path), + "peptide_fdr": peptide_fdr, + "protein_fdr": protein_fdr, + "chimera": chimera, + "report_psms": report_psms, + "mzml_paths": [str(p) for p in mzml_paths], + } + + # 4. Write the JSON configuration file + with open(config_path, "w") as f: + json.dump(config, f, indent=4) + + # 5. Build and execute the command + cmd = ["sage", str(config_path), "--output-directory", str(output_path)] + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + except FileNotFoundError: + raise RuntimeError( + "The 'sage' executable was not found. " + "Please ensure sage-proteomics is installed and in your system's PATH." + ) + except subprocess.CalledProcessError as e: + error_message = ( + f"Sage execution failed with exit code {e.returncode}.\n" + f"Command: {command_executed}\n" + f"Stdout: {e.stdout}\n" + f"Stderr: {e.stderr}" + ) + raise RuntimeError(error_message) from e + + # 6. Collect output files + output_files = sorted([str(f) for f in output_path.glob("*")]) + + # 7. Return structured output + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_directory": str(output_path), + "output_files": output_files, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_shim_server.py b/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0b7a94e70a6c6b38c83f68b297d10881dd12a45c --- /dev/null +++ b/Biomni/mcp_generated/mcp_sage-proteomics/app/sage-proteomics_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_sage-proteomics/app/sage-proteomics_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_sage_proteomics' + + +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_samblaster/Dockerfile b/Biomni/mcp_generated/mcp_samblaster/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0154c12e9ad7fdc54eed5ca15cd0e70a6e64604f --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/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 samblaster via conda (e.g., from bioconda) +RUN conda install -c bioconda samblaster -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/samblaster_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/samblaster_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/samblaster_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_samblaster/app/requirements.txt b/Biomni/mcp_generated/mcp_samblaster/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_samblaster/app/samblaster_server.py b/Biomni/mcp_generated/mcp_samblaster/app/samblaster_server.py new file mode 100644 index 0000000000000000000000000000000000000000..4864dfba9415e84348f08a1a1b3461e4323f263f --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/app/samblaster_server.py @@ -0,0 +1,200 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# No need to import mcp as per instructions + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_samblaster' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def samblaster( + input_sam_file: Optional[Path] = None, + output_sam_file: Optional[Path] = None, + discordant_file: Optional[Path] = None, + splitter_file: Optional[Path] = None, + unmapped_file: Optional[Path] = None, + accept_dup_marks: bool = False, + exclude_dups: bool = False, + remove_dups: bool = False, + add_mate_tags: bool = False, + ignore_unmated: bool = False, + compatibility_mode_M: bool = False, + max_read_length: int = 500, + max_split_count: int = 2, + max_unmapped_bases: int = 50, + min_indel_size: int = 50, + min_non_overlap: int = 20, + min_clip_size: int = 20, + quiet: bool = False, +) -> Dict[str, Any]: + """ + Marks duplicates in and extracts discordant and split reads from SAM files. + + Input SAM file must contain sequence header and be grouped by read IDs (QNAME). + Input typically contains paired-end data, although singleton data is allowed + with --ignoreUnmated option. + Output will be all alignments in the same order as input, with duplicates + marked with FLAG 0x400, unless --removeDups is used. + + Args: + input_sam_file: Path to the input SAM file. If None, reads from stdin. + output_sam_file: Path to the output SAM file for all input alignments. + If None, writes to stdout. + discordant_file: Path to output discordant read pairs to this file. + [no discordant file output by default] + splitter_file: Path to output split reads to this file. + [no splitter file output by default] + unmapped_file: Path to output unmapped/clipped reads as FASTQ/FASTA to this file. + Requires soft clipping in input file. Outputs FASTQ if QUAL info + available, otherwise FASTA. [no unmapped file output by default] + accept_dup_marks: Accept duplicate marks already in input file instead of + looking for duplicates in the input. + exclude_dups: Exclude reads marked as duplicates from discordant, splitter, + and/or unmapped files. + remove_dups: Remove duplicates reads from all output files. (Implies --excludeDups). + add_mate_tags: Add MC and MQ tags to all output paired-end SAM lines. + ignore_unmated: Suppress abort on unmated alignments. Use only when sure input + is read-id grouped, and either paired-end alignments have been + filtered or the input file contains singleton reads. + Not recommended for general use on paired-end data as it disables + checks that detect incorrectly sorted input. + compatibility_mode_M: Run in compatibility mode; both FLAG 0x100 and 0x800 are + considered supplementary (chimeric). Similar to BWA MEM -M option. + max_read_length: Maximum allowed length of the SEQ/QUAL string in the input file. + Primarily useful for marking duplicates in files containing + singleton long reads. Default: 500. + max_split_count: Maximum number of split alignments for a read to be included + in the splitter file. Default: 2. + max_unmapped_bases: Maximum number of un-aligned bases between two alignments + to be included in the splitter file. Default: 50. + min_indel_size: Minimum structural variant feature size for split alignments + to be included in the splitter file. Default: 50. + min_non_overlap: Minimum non-overlapping base pairs between two alignments + for a read to be included in the splitter file. Default: 20. + min_clip_size: Minimum number of bases a mapped read must be clipped to be + included in the unmapped file. Default: 20. + quiet: Output fewer statistics. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of + generated output files. + """ + command = ["samblaster"] + output_files = [] + + # Input validation and command construction for file paths + if input_sam_file: + if not input_sam_file.is_file(): + raise FileNotFoundError(f"Input SAM file not found: {input_sam_file}") + command.extend(["-i", str(input_sam_file)]) + + if output_sam_file: + output_sam_file.parent.mkdir(parents=True, exist_ok=True) + command.extend(["-o", str(output_sam_file)]) + output_files.append(str(output_sam_file)) + + if discordant_file: + discordant_file.parent.mkdir(parents=True, exist_ok=True) + command.extend(["-d", str(discordant_file)]) + output_files.append(str(discordant_file)) + + if splitter_file: + splitter_file.parent.mkdir(parents=True, exist_ok=True) + command.extend(["-s", str(splitter_file)]) + output_files.append(str(splitter_file)) + + if unmapped_file: + unmapped_file.parent.mkdir(parents=True, exist_ok=True) + command.extend(["-u", str(unmapped_file)]) + output_files.append(str(unmapped_file)) + + # Other Options (boolean flags) + if accept_dup_marks: + command.append("--acceptDupMarks") + if exclude_dups: + command.append("--excludeDups") + if remove_dups: + command.append("--removeDups") + if add_mate_tags: + command.append("--addMateTags") + if ignore_unmated: + command.append("--ignoreUnmated") + if compatibility_mode_M: + command.append("-M") + + # Integer parameters with validation and command construction + if not max_read_length >= 0: + raise ValueError("max_read_length must be a non-negative integer.") + command.extend(["--maxReadLength", str(max_read_length)]) + + if not max_split_count >= 0: + raise ValueError("max_split_count must be a non-negative integer.") + command.extend(["--maxSplitCount", str(max_split_count)]) + + if not max_unmapped_bases >= 0: + raise ValueError("max_unmapped_bases must be a non-negative integer.") + command.extend(["--maxUnmappedBases", str(max_unmapped_bases)]) + + if not min_indel_size >= 0: + raise ValueError("min_indel_size must be a non-negative integer.") + command.extend(["--minIndelSize", str(min_indel_size)]) + + if not min_non_overlap >= 0: + raise ValueError("min_non_overlap must be a non-negative integer.") + command.extend(["--minNonOverlap", str(min_non_overlap)]) + + if not min_clip_size >= 0: + raise ValueError("min_clip_size must be a non-negative integer.") + command.extend(["--minClipSize", str(min_clip_size)]) + + if quiet: + command.append("-q") + + process = None + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + ) + stdout = process.stdout + stderr = process.stderr + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: samblaster command not found. Is it installed and in PATH?", + "output_files": [], + "error": "samblaster_not_found", + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": "samblaster_execution_failed", + "returncode": e.returncode, + } + except Exception as e: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": f"An unexpected error occurred: {e}", + "output_files": [], + "error": "unexpected_error", + } + + 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_samblaster/app/samblaster_shim_server.py b/Biomni/mcp_generated/mcp_samblaster/app/samblaster_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0f21285372a587256ff854b68766ef837c3dca29 --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/app/samblaster_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_samblaster/app/samblaster_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_samblaster' + + +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_samblaster/docker-compose.yml b/Biomni/mcp_generated/mcp_samblaster/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1c0aad615e7e387bc8d8a3e8bc93b97ccb4db522 --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-samblaster: + build: . + image: mcp-samblaster:latest + container_name: mcp-samblaster + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=samblaster + 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_samblaster/environment.yaml b/Biomni/mcp_generated/mcp_samblaster/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..af6fcea7f7d71b65f0b4f53e04af701e5e315593 --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - samblaster + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_samblaster/requirements.txt b/Biomni/mcp_generated/mcp_samblaster/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_samblaster/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_scrnasim-toolz/Dockerfile b/Biomni/mcp_generated/mcp_scrnasim-toolz/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b1631ddaeba5f2ee2e908c157d1d56c51355d789 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/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 scrnasim-toolz via conda (e.g., from bioconda) +RUN conda install -c bioconda scrnasim-toolz -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/scrnasim-toolz_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/scrnasim-toolz_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/scrnasim-toolz_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scrnasim-toolz/app/requirements.txt b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_server.py b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0ecb606fbd6674278a8295dd79314e430170f9a0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_server.py @@ -0,0 +1,481 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Dict, List, Optional, Union + +# MCP decorator placeholder +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def transcript_sampler( + gene_counts: Path, + total_transcripts: int, + num_cells: int, + output_counts: Path, + seed: Optional[int] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Samples the number of transcripts for each gene in each cell. + + This tool simulates the stochastic nature of gene expression. Given a file with + average expression levels for genes, it generates a count for each gene in each + simulated cell using a Poisson distribution. + + Args: + gene_counts: Path to a CSV file with columns "GeneID" and "Counts" specifying + the average number of transcripts per gene. + total_transcripts: The total number of transcripts to be present in a single cell. + num_cells: The number of cells to simulate. + output_counts: Path to the output CSV file where the sampled counts will be written. + seed: A random seed for reproducibility. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not gene_counts.is_file(): + raise FileNotFoundError(f"Input file not found: {gene_counts}") + if total_transcripts <= 0: + raise ValueError("total_transcripts must be a positive integer.") + if num_cells <= 0: + raise ValueError("num_cells must be a positive integer.") + + cmd = [ + "transcript-sampler", + "--gene-counts", str(gene_counts), + "--total-transcripts", str(total_transcripts), + "--num-cells", str(num_cells), + "--output-counts", str(output_counts), + ] + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_counts)], + } + except FileNotFoundError: + raise RuntimeError("transcript-sampler executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"transcript-sampler failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def structure_generator( + sampled_counts: Path, + annotation_gtf: Path, + intron_inclusion_prob: float, + output_gtf: Path, + output_counts: Path, + seed: Optional[int] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Generates exon/intron structures for each transcript, simulating incomplete splicing. + + For each transcript sampled, this tool decides whether to include its introns based on a + given probability. It produces a new GTF file with the generated structures and a + corresponding counts file. + + Args: + sampled_counts: Path to the CSV file with sampled transcript counts from transcript_sampler. + annotation_gtf: Path to the reference GTF/GFF file with transcript annotations. + intron_inclusion_prob: The probability of an intron being included in a transcript. + output_gtf: Path to the output GTF file with the generated transcript structures. + output_counts: Path to the output file saving the number of transcripts for each unique structure. + seed: A random seed for reproducibility. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not sampled_counts.is_file(): + raise FileNotFoundError(f"Input file not found: {sampled_counts}") + if not annotation_gtf.is_file(): + raise FileNotFoundError(f"Annotation file not found: {annotation_gtf}") + if not (0.0 <= intron_inclusion_prob <= 1.0): + raise ValueError("intron_inclusion_prob must be between 0.0 and 1.0.") + + cmd = [ + "structure-generator", + "--sampled-counts", str(sampled_counts), + "--annotation-gtf", str(annotation_gtf), + "--intron-inclusion-prob", str(intron_inclusion_prob), + "--output-gtf", str(output_gtf), + "--output-counts", str(output_counts), + ] + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_gtf), str(output_counts)], + } + except FileNotFoundError: + raise RuntimeError("structure-generator executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"structure-generator failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def sequence_extractor( + gtf: Path, + genome_fasta: Path, + output_fasta: Path, + add_polya: bool = False, + polya_len_mean: Optional[int] = None, + polya_len_std: Optional[int] = None, + polya_non_a_freq: Optional[float] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Extracts transcript sequences based on a GTF file and a reference genome. + + This tool constructs transcript sequences from the structures defined in the input GTF file. + It can optionally add poly(A) tails to the extracted sequences. + + Args: + gtf: Path to the GTF file with transcript structures (e.g., from structure_generator). + genome_fasta: Path to the reference genome FASTA file. + output_fasta: Path to the output FASTA file for the extracted transcript sequences. + add_polya: If True, add poly(A) tails to the transcripts. + polya_len_mean: Mean length of the poly(A) tail. Required if add_polya is True. + polya_len_std: Standard deviation of the poly(A) tail length. Required if add_polya is True. + polya_non_a_freq: Frequency of non-A nucleotides in the poly(A) tail. Required if add_polya is True. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not gtf.is_file(): + raise FileNotFoundError(f"GTF file not found: {gtf}") + if not genome_fasta.is_file(): + raise FileNotFoundError(f"Genome FASTA file not found: {genome_fasta}") + + cmd = [ + "sequence-extractor", + "--gtf", str(gtf), + "--genome-fasta", str(genome_fasta), + "--output-fasta", str(output_fasta), + ] + + if add_polya: + if polya_len_mean is None or polya_len_std is None or polya_non_a_freq is None: + raise ValueError("If add_polya is True, polya_len_mean, polya_len_std, and polya_non_a_freq must be provided.") + if polya_len_mean <= 0: + raise ValueError("polya_len_mean must be positive.") + if polya_len_std < 0: + raise ValueError("polya_len_std must be non-negative.") + if not (0.0 <= polya_non_a_freq <= 1.0): + raise ValueError("polya_non_a_freq must be between 0.0 and 1.0.") + + cmd.append("--add-polya") + cmd.extend(["--polya-len-mean", str(polya_len_mean)]) + cmd.extend(["--polya-len-std", str(polya_len_std)]) + cmd.extend(["--polya-non-a-freq", str(polya_non_a_freq)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_fasta)], + } + except FileNotFoundError: + raise RuntimeError("sequence-extractor executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"sequence-extractor failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def priming_site_predictor( + transcript_fasta: Path, + output_sites: Path, + primer_seq: str = "TTTTTTTTTTTTTTT", + min_consecutive_a: int = 6, + max_mismatches: int = 1, +) -> Dict[str, Union[str, List[str]]]: + """ + Predicts cDNA priming sites on transcript sequences. + + This tool identifies potential priming sites for a given primer (e.g., oligo-dT) + on transcript sequences, accounting for internal A-rich stretches. + + Args: + transcript_fasta: Path to the FASTA file with transcript sequences. + output_sites: Path to the output file listing predicted priming sites and their probabilities. + primer_seq: The sequence of the primer used for cDNA synthesis. + min_consecutive_a: Minimum number of consecutive 'A's to be considered an internal priming site. + max_mismatches: Maximum allowed mismatches for a priming event. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not transcript_fasta.is_file(): + raise FileNotFoundError(f"Transcript FASTA file not found: {transcript_fasta}") + if min_consecutive_a < 1: + raise ValueError("min_consecutive_a must be a positive integer.") + if max_mismatches < 0: + raise ValueError("max_mismatches must be a non-negative integer.") + + cmd = [ + "priming-site-predictor", + "--transcript-fasta", str(transcript_fasta), + "--output-sites", str(output_sites), + "--primer-seq", primer_seq, + "--min-consecutive-a", str(min_consecutive_a), + "--max-mismatches", str(max_mismatches), + ] + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_sites)], + } + except FileNotFoundError: + raise RuntimeError("priming-site-predictor executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"priming-site-predictor failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def cdna_generator( + transcript_fasta: Path, + priming_sites: Path, + structure_counts: Path, + output_cdna_fasta: Path, + seed: Optional[int] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Generates cDNA sequences by simulating reverse transcription. + + This tool samples priming sites based on their predicted probabilities and generates + the corresponding complementary DNA (cDNA) sequences. + + Args: + transcript_fasta: Path to the FASTA file with transcript sequences. + priming_sites: Path to the file with predicted priming sites. + structure_counts: Path to the file with counts for each transcript structure. + output_cdna_fasta: Path to the output FASTA file for the generated cDNA sequences. + seed: A random seed for reproducibility. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not transcript_fasta.is_file(): + raise FileNotFoundError(f"Transcript FASTA file not found: {transcript_fasta}") + if not priming_sites.is_file(): + raise FileNotFoundError(f"Priming sites file not found: {priming_sites}") + if not structure_counts.is_file(): + raise FileNotFoundError(f"Structure counts file not found: {structure_counts}") + + cmd = [ + "cdna-generator", + "--transcript-fasta", str(transcript_fasta), + "--priming-sites", str(priming_sites), + "--structure-counts", str(structure_counts), + "--output-cdna-fasta", str(output_cdna_fasta), + ] + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_cdna_fasta)], + } + except FileNotFoundError: + raise RuntimeError("cdna-generator executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"cdna-generator failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def fragment_selector( + cdna_fasta: Path, + output_fragment_fasta: Path, + frag_len_mean: int, + frag_len_std: int, + seed: Optional[int] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Simulates cDNA fragmentation and selects end-fragments. + + This tool fragments the generated cDNA sequences according to a specified length + distribution and selects the fragments corresponding to the ends of the original cDNA. + + Args: + cdna_fasta: Path to the FASTA file with cDNA sequences. + output_fragment_fasta: Path to the output FASTA file for the selected fragments. + frag_len_mean: The mean length for cDNA fragmentation. + frag_len_std: The standard deviation of the fragment length. + seed: A random seed for reproducibility. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not cdna_fasta.is_file(): + raise FileNotFoundError(f"cDNA FASTA file not found: {cdna_fasta}") + if frag_len_mean <= 0: + raise ValueError("frag_len_mean must be a positive integer.") + if frag_len_std < 0: + raise ValueError("frag_len_std must be a non-negative integer.") + + cmd = [ + "fragment-selector", + "--cdna-fasta", str(cdna_fasta), + "--output-fragment-fasta", str(output_fragment_fasta), + "--frag-len-mean", str(frag_len_mean), + "--frag-len-std", str(frag_len_std), + ] + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_fragment_fasta)], + } + except FileNotFoundError: + raise RuntimeError("fragment-selector executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"fragment-selector failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) + +@mcp.tool +def read_sequencer( + fragment_fasta: Path, + output_reads: Path, + num_reads: int, + read_length: int, + padding_freqs: Optional[str] = None, + seed: Optional[int] = None, +) -> Dict[str, Union[str, List[str]]]: + """ + Generates simulated sequencing reads from cDNA fragments. + + This tool samples fragments and generates reads of a specified length from their 5' ends. + If a fragment is shorter than the read length, it is padded with random nucleotides. + + Args: + fragment_fasta: Path to the FASTA file with cDNA fragments. + output_reads: Path to the output FASTA/FASTQ file for the simulated reads. + num_reads: The total number of reads to sequence. + read_length: The length of each sequencing read. + padding_freqs: Comma-separated nucleotide frequencies for padding, e.g., "A:0.25,C:0.25,G:0.25,T:0.25". + seed: A random seed for reproducibility. + + Returns: + A dictionary containing the executed command, stdout, stderr, and a list of output files. + """ + # Input validation + if not fragment_fasta.is_file(): + raise FileNotFoundError(f"Fragment FASTA file not found: {fragment_fasta}") + if num_reads <= 0: + raise ValueError("num_reads must be a positive integer.") + if read_length <= 0: + raise ValueError("read_length must be a positive integer.") + + cmd = [ + "read-sequencer", + "--fragment-fasta", str(fragment_fasta), + "--output-reads", str(output_reads), + "--num-reads", str(num_reads), + "--read-length", str(read_length), + ] + if padding_freqs: + cmd.extend(["--padding-freqs", padding_freqs]) + if seed is not None: + cmd.extend(["--seed", str(seed)]) + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_reads)], + } + except FileNotFoundError: + raise RuntimeError("read-sequencer executable not found. Is scrnasim-toolz installed and in your PATH?") + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"read-sequencer failed with exit code {e.returncode}:\n" + f"STDOUT: {e.stdout}\n" + f"STDERR: {e.stderr}" + ) \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_shim_server.py b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6d3ac99b0c089b6d986647cc50c349f5dbd9f2fc --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/app/scrnasim-toolz_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_scrnasim-toolz/app/scrnasim-toolz_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_scrnasim_toolz' + + +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_scrnasim-toolz/docker-compose.yml b/Biomni/mcp_generated/mcp_scrnasim-toolz/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..21a7e742da95bc2dee2d0b3df4d2b0e72127505d --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-scrnasim-toolz: + build: . + image: mcp-scrnasim-toolz:latest + container_name: mcp-scrnasim-toolz + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=scrnasim-toolz + 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_scrnasim-toolz/requirements.txt b/Biomni/mcp_generated/mcp_scrnasim-toolz/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_scrnasim-toolz/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ucsc-bedgraphtobigwig/requirements.txt b/Biomni/mcp_generated/mcp_ucsc-bedgraphtobigwig/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ucsc-bedgraphtobigwig/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_whatshap/Dockerfile b/Biomni/mcp_generated/mcp_whatshap/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fe2aea8f56b4da753d6b3e6613798bebe6f2c12f --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/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 whatshap via conda (e.g., from bioconda) +RUN conda install -c bioconda whatshap -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/whatshap_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/whatshap_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/whatshap_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_whatshap/app/requirements.txt b/Biomni/mcp_generated/mcp_whatshap/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_whatshap/app/whatshap_server.py b/Biomni/mcp_generated/mcp_whatshap/app/whatshap_server.py new file mode 100644 index 0000000000000000000000000000000000000000..9bbd25963bdb33e433bfd4b92234372225e743da --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/app/whatshap_server.py @@ -0,0 +1,458 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_whatshap' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def whatshap_phase( + vcf: str, + phase_input: List[str], + reference: Optional[str] = None, + output: Optional[str] = None, + samples: Optional[List[str]] = None, + chromosomes: Optional[List[str]] = None, + ignore_read_groups: bool = False, + indels: bool = True, + distrust_genotypes: bool = False, + ignore_pedigree: bool = False, + mapq: int = 20, + max_coverage: int = 15, + tag: str = "PS", +): + """ + Phase genomic variants using DNA sequencing reads. + + Args: + vcf: Path to the input VCF file. + phase_input: List of paths to BAM/CRAM files. + reference: Path to the reference FASTA file. + output: Path to the output phased VCF file. + samples: List of sample names to phase. + chromosomes: List of chromosomes to phase. + ignore_read_groups: Ignore read groups in BAM/CRAM files. + indels: Also phase indels (default: True). + distrust_genotypes: Distrust genotypes in the input VCF. + ignore_pedigree: Ignore pedigree information in VCF. + mapq: Minimum mapping quality (default: 20). + max_coverage: Maximum coverage per sample (default: 15). + tag: VCF tag to use for phasing information (default: PS). + """ + vcf_path = Path(vcf) + if not vcf_path.exists(): + return {"error": f"VCF file not found: {vcf}"} + + cmd = ["whatshap", "phase", "--mapq", str(mapq), "--max-coverage", str(max_coverage), "--tag", tag] + + if reference: + ref_path = Path(reference) + if not ref_path.exists(): + return {"error": f"Reference file not found: {reference}"} + cmd.extend(["--reference", reference]) + + if output: + cmd.extend(["--output", output]) + + if samples: + cmd.extend(["--sample", ",".join(samples)]) + + if chromosomes: + cmd.extend(["--chromosome", ",".join(chromosomes)]) + + if ignore_read_groups: + cmd.append("--ignore-read-groups") + + if not indels: + cmd.append("--no-indels") + + if distrust_genotypes: + cmd.append("--distrust-genotypes") + + if ignore_pedigree: + cmd.append("--ignore-pedigree") + + cmd.append(vcf) + cmd.extend(phase_input) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output] if output else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_stats( + vcf: str, + gtf: Optional[str] = None, + tsv: Optional[str] = None, + block_list: Optional[str] = None, + sample: Optional[str] = None, + chromosome: Optional[str] = None, +): + """ + Calculate phasing statistics for a phased VCF file. + + Args: + vcf: Path to the phased VCF file. + gtf: Write phasing blocks to GTF file. + tsv: Write statistics to TSV file. + block_list: Write block list to this file. + sample: Sample name to process. + chromosome: Chromosome to process. + """ + if not Path(vcf).exists(): + return {"error": f"VCF file not found: {vcf}"} + + cmd = ["whatshap", "stats"] + if gtf: + cmd.extend(["--gtf", gtf]) + if tsv: + cmd.extend(["--tsv", tsv]) + if block_list: + cmd.extend(["--block-list", block_list]) + if sample: + cmd.extend(["--sample", sample]) + if chromosome: + cmd.extend(["--chromosome", chromosome]) + + cmd.append(vcf) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f for f in [gtf, tsv, block_list] if f] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_compare( + vcf1: str, + vcf2: str, + sample: Optional[str] = None, + names: Optional[str] = None, + tsv_pairwise: Optional[str] = None, + longest_block_tsv: Optional[str] = None, + switch_error_bed: Optional[str] = None, +): + """ + Compare two phasings (e.g., compare assembly to ground truth). + + Args: + vcf1: First phased VCF file. + vcf2: Second phased VCF file. + sample: Sample name to compare. + names: Comma-separated names for the two VCFs in output. + tsv_pairwise: Write pairwise comparison to TSV. + longest_block_tsv: Write longest block statistics to TSV. + switch_error_bed: Write switch errors to BED file. + """ + if not Path(vcf1).exists() or not Path(vcf2).exists(): + return {"error": "One or both VCF files not found."} + + cmd = ["whatshap", "compare"] + if sample: + cmd.extend(["--sample", sample]) + if names: + cmd.extend(["--names", names]) + if tsv_pairwise: + cmd.extend(["--tsv-pairwise", tsv_pairwise]) + if longest_block_tsv: + cmd.extend(["--longest-block-tsv", longest_block_tsv]) + if switch_error_bed: + cmd.extend(["--switch-error-bed", switch_error_bed]) + + cmd.extend([vcf1, vcf2]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f for f in [tsv_pairwise, longest_block_tsv, switch_error_bed] if f] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_haplotag( + vcf: str, + bam: str, + reference: Optional[str] = None, + output: Optional[str] = None, + sample: Optional[str] = None, + ignore_read_groups: bool = False, + regions: Optional[str] = None, + tag_supplementary: bool = False, +): + """ + Tag reads in a BAM file with haplotype information (HP and PS tags). + + Args: + vcf: Path to the phased VCF file. + bam: Path to the BAM/CRAM file. + reference: Path to the reference FASTA file. + output: Path to the output tagged BAM file. + sample: Sample name to process. + ignore_read_groups: Ignore read groups. + regions: Only process these regions (e.g. chr1:100-200). + tag_supplementary: Tag supplementary alignments. + """ + if not Path(vcf).exists() or not Path(bam).exists(): + return {"error": "VCF or BAM file not found."} + + cmd = ["whatshap", "haplotag"] + if reference: + cmd.extend(["--reference", reference]) + if output: + cmd.extend(["--output", output]) + if sample: + cmd.extend(["--sample", sample]) + if ignore_read_groups: + cmd.append("--ignore-read-groups") + if regions: + cmd.extend(["--regions", regions]) + if tag_supplementary: + cmd.append("--tag-supplementary") + + cmd.extend([vcf, bam]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output] if output else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_polyphase( + vcf: str, + phase_input: List[str], + ploidy: int = 3, + reference: Optional[str] = None, + output: Optional[str] = None, + samples: Optional[List[str]] = None, + chromosomes: Optional[List[str]] = None, + mapq: int = 20, +): + """ + Phase genomic variants for polyploid organisms. + + Args: + vcf: Path to the input VCF file. + phase_input: List of paths to BAM/CRAM files. + ploidy: Ploidy of the organism (default: 3). + reference: Path to the reference FASTA file. + output: Path to the output phased VCF file. + samples: List of sample names to phase. + chromosomes: List of chromosomes to phase. + mapq: Minimum mapping quality (default: 20). + """ + if ploidy < 2: + return {"error": "Ploidy must be at least 2."} + + cmd = ["whatshap", "polyphase", "--ploidy", str(ploidy), "--mapq", str(mapq)] + + if reference: + cmd.extend(["--reference", reference]) + if output: + cmd.extend(["--output", output]) + if samples: + cmd.extend(["--sample", ",".join(samples)]) + if chromosomes: + cmd.extend(["--chromosome", ",".join(chromosomes)]) + + cmd.append(vcf) + cmd.extend(phase_input) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output] if output else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_split( + bam: str, + haplist: str, + output_h1: str, + output_h2: str, + output_untagged: Optional[str] = None, +): + """ + Split a BAM file into two files, one for each haplotype. + + Args: + bam: Path to the input BAM file. + haplist: Path to the haplotype list (from haplotag). + output_h1: Path to output BAM for haplotype 1. + output_h2: Path to output BAM for haplotype 2. + output_untagged: Path to output BAM for untagged reads. + """ + if not Path(bam).exists() or not Path(haplist).exists(): + return {"error": "BAM or haplist file not found."} + + cmd = ["whatshap", "split", "--output-h1", output_h1, "--output-h2", output_h2] + if output_untagged: + cmd.extend(["--output-untagged", output_untagged]) + + cmd.extend([bam, haplist]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output_h1, output_h2] + ([output_untagged] if output_untagged else []) + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_genotype( + vcf: str, + phase_input: List[str], + reference: str, + output: Optional[str] = None, + sample: Optional[str] = None, + chromosomes: Optional[List[str]] = None, + mapq: int = 20, + indels: bool = True, +): + """ + Genotype variants using DNA sequencing reads. + + Args: + vcf: Path to the input VCF file. + phase_input: List of paths to BAM/CRAM files. + reference: Path to the reference FASTA file (required). + output: Path to the output VCF file. + sample: Sample name to genotype. + chromosomes: List of chromosomes to genotype. + mapq: Minimum mapping quality (default: 20). + indels: Genotype indels (default: True). + """ + if not Path(reference).exists(): + return {"error": f"Reference file not found: {reference}"} + + cmd = ["whatshap", "genotype", "--reference", reference, "--mapq", str(mapq)] + + if output: + cmd.extend(["--output", output]) + if sample: + cmd.extend(["--sample", sample]) + if chromosomes: + cmd.extend(["--chromosome", ",".join(chromosomes)]) + if not indels: + cmd.append("--no-indels") + + cmd.append(vcf) + cmd.extend(phase_input) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output] if output else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def whatshap_unphase( + vcf: str, + output: Optional[str] = None, +): + """ + Remove phasing information from a VCF file. + + Args: + vcf: Path to the input VCF file. + output: Path to the output unphased VCF file. + """ + if not Path(vcf).exists(): + return {"error": f"VCF file not found: {vcf}"} + + cmd = ["whatshap", "unphase"] + if output: + cmd.extend(["--output", output]) + + cmd.append(vcf) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [output] if output else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": str(e), + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_whatshap/app/whatshap_shim_server.py b/Biomni/mcp_generated/mcp_whatshap/app/whatshap_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..241a80a2923da94d9957d85e9d5c1776479e75e7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/app/whatshap_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_whatshap/app/whatshap_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_whatshap' + + +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_whatshap/docker-compose.yml b/Biomni/mcp_generated/mcp_whatshap/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..7fbe463550c59f48c02c9a334cffc2c588ad2e42 --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-whatshap: + build: . + image: mcp-whatshap:latest + container_name: mcp-whatshap + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=whatshap + 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_whatshap/environment.yaml b/Biomni/mcp_generated/mcp_whatshap/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ba68847dbc709bd65e4df8721efa65b6625c0a9d --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - whatshap + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_whatshap/requirements.txt b/Biomni/mcp_generated/mcp_whatshap/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_whatshap/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp