diff --git a/Biomni/mcp_generated/mcp_adapterremoval/Dockerfile b/Biomni/mcp_generated/mcp_adapterremoval/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2c883e38ab66792aeaa80e098fa4bd33c6ee003f --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/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 adapterremoval via conda (e.g., from bioconda) +RUN conda install -c bioconda adapterremoval -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/adapterremoval_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/adapterremoval_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/adapterremoval_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_adapterremoval/app/adapterremoval_shim_server.py b/Biomni/mcp_generated/mcp_adapterremoval/app/adapterremoval_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6d3e9a1191a8333d49f2b61097a8107c824abd57 --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/app/adapterremoval_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_adapterremoval/app/adapterremoval_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_adapterremoval' + + +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_adapterremoval/app/requirements.txt b/Biomni/mcp_generated/mcp_adapterremoval/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_adapterremoval/docker-compose.yml b/Biomni/mcp_generated/mcp_adapterremoval/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a2f7cf04f93a95b930d3b0522c19f44eff82fe41 --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-adapterremoval: + build: . + image: mcp-adapterremoval:latest + container_name: mcp-adapterremoval + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=adapterremoval + 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_adapterremoval/environment.yaml b/Biomni/mcp_generated/mcp_adapterremoval/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4adb36b1602292c3b24d3bf18e90edd5a4714a42 --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - adapterremoval + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_adapterremoval/requirements.txt b/Biomni/mcp_generated/mcp_adapterremoval/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_adapterremoval/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_alfred/Dockerfile b/Biomni/mcp_generated/mcp_alfred/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..20d194cb4175101adf571f9f71916e32fed1024f --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/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 alfred via conda (e.g., from bioconda) +RUN conda install -c bioconda alfred -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/alfred_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/alfred_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/alfred_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_alfred/app/alfred_server.py b/Biomni/mcp_generated/mcp_alfred/app/alfred_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ae092c233a1a5c4e25615877bfc4956dfc8a1036 --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/app/alfred_server.py @@ -0,0 +1,638 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Literal, Optional + +# MCP-related decorators are assumed to be available in the environment. +# +# For local testing, you can create a dummy decorator: +# +# def mcp_tool_dummy(*args, **kwargs): +# def decorator(func): +# return func +# return decorator +# +# mcp = type("mcp", (), {"tool": mcp_tool_dummy}) + + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_alfred' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def alfred_stats( + in_bam: Path, + ref: Path, + outfile: Optional[Path] = None, + bed: Optional[Path] = None, + threads: int = 1, + minmapq: int = 0, + flag: int = 0, + fflag: int = 1540, + coverage: Optional[Path] = None, + dist: Optional[Path] = None, + sampleid: Optional[str] = None, + libid: Optional[str] = None, + readgroup: Optional[str] = None, + uncompressed: bool = False, +): + """ + Compute alignment summary statistics for a BAM file. + + Args: + in_bam: Input BAM file. + ref: Reference FASTA file. + outfile: Output file for alignment metrics (e.g., metrics.tsv.gz). Defaults to stdout. + bed: BED file with regions of interest. + threads: Number of threads to use. + minmapq: Minimum mapping quality for reads to be considered. + flag: Required SAM flag. + fflag: Filtering SAM flag (reads with these flags will be ignored). + coverage: Output file for coverage statistics (e.g., cov.txt.gz). + dist: Output file for insert size distribution (e.g., dist.txt.gz). + sampleid: Sample ID to be used in the output. + libid: Library ID to be used in the output. + readgroup: Read-group ID to be used in the output. + uncompressed: Write uncompressed output files. + """ + if not in_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {in_bam}") + if not ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref}") + if bed and not bed.exists(): + raise FileNotFoundError(f"BED file not found: {bed}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + cmd = ["alfred", "stats", "-r", str(ref)] + output_files = [] + + if outfile: + cmd.extend(["-o", str(outfile)]) + output_files.append(str(outfile)) + if bed: + cmd.extend(["-b", str(bed)]) + if threads > 1: + cmd.extend(["-p", str(threads)]) + if minmapq != 0: + cmd.extend(["-m", str(minmapq)]) + if flag != 0: + cmd.extend(["-f", str(flag)]) + if fflag != 1540: + cmd.extend(["-F", str(fflag)]) + if coverage: + cmd.extend(["-c", str(coverage)]) + output_files.append(str(coverage)) + if dist: + cmd.extend(["-d", str(dist)]) + output_files.append(str(dist)) + if sampleid: + cmd.extend(["-s", sampleid]) + if libid: + cmd.extend(["-l", libid]) + if readgroup: + cmd.extend(["-g", readgroup]) + if uncompressed: + cmd.append("-u") + + cmd.append(str(in_bam)) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred stats failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_count( + in_bam: Path, + ref: Path, + bed: Optional[Path] = None, + gtf: Optional[Path] = None, + outfile: Optional[Path] = None, + threads: int = 1, + minmapq: int = 0, + flag: int = 0, + fflag: int = 1540, + sampleid: Optional[str] = None, + libid: Optional[str] = None, + readgroup: Optional[str] = None, + uncompressed: bool = False, + feature: str = "exon", + id_attribute: str = "gene_id", + stranded: Literal[0, 1, 2] = 0, + antisense: bool = False, +): + """ + Count reads in genomic features from a BAM file. + + Args: + in_bam: Input BAM file. + ref: Reference FASTA file. + bed: BED file with features. Provide either 'bed' or 'gtf'. + gtf: GTF file with features. Provide either 'bed' or 'gtf'. + outfile: Output file for feature counts (e.g., counts.tsv.gz). Defaults to stdout. + threads: Number of threads to use. + minmapq: Minimum mapping quality. + flag: Required SAM flag. + fflag: Filtering SAM flag. + sampleid: Sample ID. + libid: Library ID. + readgroup: Read-group ID. + uncompressed: Write uncompressed output. + feature: GTF feature to count (e.g., 'exon'). + id_attribute: GTF identifier to aggregate by (e.g., 'gene_id'). + stranded: Strandedness (0: unstranded, 1: forward, 2: reverse). + antisense: Count antisense reads. + """ + if not in_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {in_bam}") + if not ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref}") + if bed and gtf: + raise ValueError("Parameters 'bed' and 'gtf' are mutually exclusive.") + if not bed and not gtf: + raise ValueError("Either 'bed' or 'gtf' must be provided.") + if bed and not bed.exists(): + raise FileNotFoundError(f"BED file not found: {bed}") + if gtf and not gtf.exists(): + raise FileNotFoundError(f"GTF file not found: {gtf}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + cmd = ["alfred", "count", "-r", str(ref)] + output_files = [] + + if bed: + cmd.extend(["-b", str(bed)]) + if gtf: + cmd.extend(["-j", str(gtf)]) + if outfile: + cmd.extend(["-o", str(outfile)]) + output_files.append(str(outfile)) + if threads > 1: + cmd.extend(["-p", str(threads)]) + if minmapq != 0: + cmd.extend(["-m", str(minmapq)]) + if flag != 0: + cmd.extend(["-f", str(flag)]) + if fflag != 1540: + cmd.extend(["-F", str(fflag)]) + if sampleid: + cmd.extend(["-s", sampleid]) + if libid: + cmd.extend(["-l", libid]) + if readgroup: + cmd.extend(["-g", readgroup]) + if uncompressed: + cmd.append("-u") + if feature != "exon": + cmd.extend(["-e", feature]) + if id_attribute != "gene_id": + cmd.extend(["-i", id_attribute]) + if stranded != 0: + cmd.extend(["-a", str(stranded)]) + if antisense: + cmd.append("-z") + + cmd.append(str(in_bam)) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred count failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_annotate( + in_vcf: Path, + in_bam: Path, + outfile: Path, + ref: Path, + bed: Optional[Path] = None, + gtf: Optional[Path] = None, + threads: int = 1, + minmapq: int = 0, + flag: int = 0, + fflag: int = 1540, + feature: str = "exon", + id_attribute: str = "gene_id", + annotation_type: Literal["INFO", "FORMAT"] = "FORMAT", + field: str = "FE", +): + """ + Annotate variants in a VCF/BCF file with feature overlaps from a BAM file. + + Args: + in_vcf: Input VCF/BCF file. + in_bam: Input BAM file. + outfile: Output VCF/BCF file. + ref: Reference FASTA file. + bed: BED file with features. Provide either 'bed' or 'gtf'. + gtf: GTF file with features. Provide either 'bed' or 'gtf'. + threads: Number of threads to use. + minmapq: Minimum mapping quality. + flag: Required SAM flag. + fflag: Filtering SAM flag. + feature: GTF feature to count (e.g., 'exon'). + id_attribute: GTF identifier to aggregate by (e.g., 'gene_id'). + annotation_type: Annotation type ('INFO' or 'FORMAT'). + field: Annotation field name. + """ + if not in_vcf.exists(): + raise FileNotFoundError(f"Input VCF/BCF file not found: {in_vcf}") + if not in_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {in_bam}") + if not ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref}") + if bed and gtf: + raise ValueError("Parameters 'bed' and 'gtf' are mutually exclusive.") + if not bed and not gtf: + raise ValueError("Either 'bed' or 'gtf' must be provided.") + if bed and not bed.exists(): + raise FileNotFoundError(f"BED file not found: {bed}") + if gtf and not gtf.exists(): + raise FileNotFoundError(f"GTF file not found: {gtf}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + cmd = ["alfred", "annotate", "-o", str(outfile), "-r", str(ref)] + output_files = [str(outfile)] + + if bed: + cmd.extend(["-b", str(bed)]) + if gtf: + cmd.extend(["-j", str(gtf)]) + if threads > 1: + cmd.extend(["-p", str(threads)]) + if minmapq != 0: + cmd.extend(["-m", str(minmapq)]) + if flag != 0: + cmd.extend(["-f", str(flag)]) + if fflag != 1540: + cmd.extend(["-F", str(fflag)]) + if feature != "exon": + cmd.extend(["-e", feature]) + if id_attribute != "gene_id": + cmd.extend(["-i", id_attribute]) + if annotation_type != "FORMAT": + cmd.extend(["-t", annotation_type]) + if field != "FE": + cmd.extend(["-a", field]) + + cmd.extend([str(in_vcf), str(in_bam)]) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred annotate failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_qc( + input_bams: List[Path], + outfile: Path, + ref: Path, + bed: Optional[Path] = None, + gtf: Optional[Path] = None, + threads: int = 1, + minmapq: int = 0, + flag: int = 0, + fflag: int = 1540, + contigs: Optional[str] = None, + feature: str = "exon", + id_attribute: str = "gene_id", + stranded: Literal[0, 1, 2] = 0, + antisense: bool = False, + uncompressed: bool = False, + sites: Optional[Path] = None, + genome: Optional[Path] = None, +): + """ + Generate a multi-sample QC report from one or more BAM files. + + Args: + input_bams: List of input BAM files. + outfile: Output file for QC metrics (e.g., out.qc.json.gz). + ref: Reference FASTA file. + bed: BED file with regions of interest. + gtf: GTF file with features. + threads: Number of threads to use. + minmapq: Minimum mapping quality. + flag: Required SAM flag. + fflag: Filtering SAM flag. + contigs: Comma-separated list of contigs to include. + feature: GTF feature to count (e.g., 'exon'). + id_attribute: GTF identifier to aggregate by (e.g., 'gene_id'). + stranded: Strandedness (0: unstranded, 1: forward, 2: reverse). + antisense: Count antisense reads. + uncompressed: Write uncompressed output. + sites: VCF/BCF file with sites of interest. + genome: Genome accessibility file. + """ + if not input_bams: + raise ValueError("At least one input BAM file is required.") + for bam in input_bams: + if not bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {bam}") + if not ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref}") + if bed and not bed.exists(): + raise FileNotFoundError(f"BED file not found: {bed}") + if gtf and not gtf.exists(): + raise FileNotFoundError(f"GTF file not found: {gtf}") + if sites and not sites.exists(): + raise FileNotFoundError(f"Sites VCF/BCF file not found: {sites}") + if genome and not genome.exists(): + raise FileNotFoundError(f"Genome accessibility file not found: {genome}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + + cmd = ["alfred", "qc", "-o", str(outfile), "-r", str(ref)] + output_files = [str(outfile)] + + if bed: + cmd.extend(["-b", str(bed)]) + if gtf: + cmd.extend(["-j", str(gtf)]) + if threads > 1: + cmd.extend(["-p", str(threads)]) + if minmapq != 0: + cmd.extend(["-m", str(minmapq)]) + if flag != 0: + cmd.extend(["-f", str(flag)]) + if fflag != 1540: + cmd.extend(["-F", str(fflag)]) + if contigs: + cmd.extend(["-c", contigs]) + if feature != "exon": + cmd.extend(["-e", feature]) + if id_attribute != "gene_id": + cmd.extend(["-i", id_attribute]) + if stranded != 0: + cmd.extend(["-a", str(stranded)]) + if antisense: + cmd.append("-z") + if uncompressed: + cmd.append("-u") + if sites: + cmd.extend(["-s", str(sites)]) + if genome: + cmd.extend(["-g", str(genome)]) + + cmd.extend([str(bam) for bam in input_bams]) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred qc failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_merge( + input_qc_files: List[Path], outfile: Path, uncompressed: bool = False +): + """ + Merge multiple alfred QC files. + + Args: + input_qc_files: List of input QC JSON files (e.g., *.qc.json.gz). + outfile: Output file for merged QC metrics (e.g., out.qc.json.gz). + uncompressed: Write uncompressed output. + """ + if not input_qc_files: + raise ValueError("At least one input QC file is required.") + for qc_file in input_qc_files: + if not qc_file.exists(): + raise FileNotFoundError(f"Input QC file not found: {qc_file}") + + cmd = ["alfred", "merge", "-o", str(outfile)] + output_files = [str(outfile)] + + if uncompressed: + cmd.append("-u") + + cmd.extend([str(f) for f in input_qc_files]) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred merge failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_track( + in_bam: Path, + ref: Path, + outfile: Optional[Path] = None, + bed: Optional[Path] = None, + threads: int = 1, + minmapq: int = 0, + flag: int = 0, + fflag: int = 1540, + step: int = 1000, + window: int = 1000, + uncompressed: bool = False, +): + """ + Create a bedGraph track from a BAM file. + + Args: + in_bam: Input BAM file. + ref: Reference FASTA file. + outfile: Output bedGraph file (e.g., track.bedGraph.gz). Defaults to stdout. + bed: BED file with regions of interest. + threads: Number of threads to use. + minmapq: Minimum mapping quality. + flag: Required SAM flag. + fflag: Filtering SAM flag. + step: Step size for coverage computation. + window: Window size for coverage computation. + uncompressed: Write uncompressed output. + """ + if not in_bam.exists(): + raise FileNotFoundError(f"Input BAM file not found: {in_bam}") + if not ref.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref}") + if bed and not bed.exists(): + raise FileNotFoundError(f"BED file not found: {bed}") + if threads < 1: + raise ValueError("Number of threads must be at least 1.") + if step <= 0 or window <= 0: + raise ValueError("Step and window sizes must be positive.") + + cmd = ["alfred", "track", "-r", str(ref)] + output_files = [] + + if outfile: + cmd.extend(["-o", str(outfile)]) + output_files.append(str(outfile)) + if bed: + cmd.extend(["-b", str(bed)]) + if threads > 1: + cmd.extend(["-p", str(threads)]) + if minmapq != 0: + cmd.extend(["-m", str(minmapq)]) + if flag != 0: + cmd.extend(["-f", str(flag)]) + if fflag != 1540: + cmd.extend(["-F", str(fflag)]) + if step != 1000: + cmd.extend(["-s", str(step)]) + if window != 1000: + cmd.extend(["-w", str(window)]) + if uncompressed: + cmd.append("-u") + + cmd.append(str(in_bam)) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred track failed", + "return_code": e.returncode, + } + + +@mcp.tool() +def alfred_motif( + ref_fa: Path, + motif: str, + outfile: Optional[Path] = None, + mincount: int = 1, + uncompressed: bool = False, +): + """ + Search for motifs in a FASTA file. + + Args: + ref_fa: Reference FASTA file. + motif: Motif to search for. + outfile: Output BED file (e.g., motif.bed.gz). Defaults to stdout. + mincount: Minimum number of motif repeats. + uncompressed: Write uncompressed output. + """ + if not ref_fa.exists(): + raise FileNotFoundError(f"Reference FASTA file not found: {ref_fa}") + if not motif: + raise ValueError("Motif string cannot be empty.") + if mincount < 1: + raise ValueError("Minimum count must be at least 1.") + + cmd = ["alfred", "motif", "-m", motif] + output_files = [] + + if outfile: + cmd.extend(["-o", str(outfile)]) + output_files.append(str(outfile)) + if mincount != 1: + cmd.extend(["-c", str(mincount)]) + if uncompressed: + cmd.append("-u") + + cmd.append(str(ref_fa)) + + try: + result = subprocess.run( + cmd, check=True, capture_output=True, text=True, encoding="utf-8" + ) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Alfred motif failed", + "return_code": e.returncode, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_alfred/app/alfred_shim_server.py b/Biomni/mcp_generated/mcp_alfred/app/alfred_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e8f371508d0fbde09e223e12be12d0a2ffa110f9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/app/alfred_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_alfred/app/alfred_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_alfred' + + +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_alfred/app/requirements.txt b/Biomni/mcp_generated/mcp_alfred/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_alfred/docker-compose.yml b/Biomni/mcp_generated/mcp_alfred/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..10d69f54e7306f9e7fa797206b59852f4e9b20d2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-alfred: + build: . + image: mcp-alfred:latest + container_name: mcp-alfred + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=alfred + 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_alfred/environment.yaml b/Biomni/mcp_generated/mcp_alfred/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0a0a1452ee7957bb2da9cf2f21e5378114b9dd64 --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - alfred + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_alfred/requirements.txt b/Biomni/mcp_generated/mcp_alfred/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_alfred/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_aria2/Dockerfile b/Biomni/mcp_generated/mcp_aria2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b4cb6c099c9996d6e3a809fe1b5f509b65a9dff1 --- /dev/null +++ b/Biomni/mcp_generated/mcp_aria2/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 aria2 via conda (e.g., from bioconda) +RUN conda install -c bioconda aria2 -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 aria2_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/aria2_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/aria2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_aria2/app/aria2_shim_server.py b/Biomni/mcp_generated/mcp_aria2/app/aria2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..608737f517cf7e8c8898e744abb3a9401948868a --- /dev/null +++ b/Biomni/mcp_generated/mcp_aria2/app/aria2_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_aria2/app/aria2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_aria2' + + +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_aria2/docker-compose.yml b/Biomni/mcp_generated/mcp_aria2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea7d8cc280282474edf221e8b8d2093ad080830f --- /dev/null +++ b/Biomni/mcp_generated/mcp_aria2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-aria2: + build: . + image: mcp-aria2:latest + container_name: mcp-aria2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=aria2 + 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_aria2/requirements.txt b/Biomni/mcp_generated/mcp_aria2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_aria2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_augur/requirements.txt b/Biomni/mcp_generated/mcp_augur/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_augur/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..baadb8653861e5789a279f826093986fef432e4b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/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-biocfilecache via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-biocfilecache -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-biocfilecache_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-biocfilecache_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-biocfilecache_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_server.py b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a2fe6f829352c70a067d029777080bbdb6ae2838 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_server.py @@ -0,0 +1,314 @@ +import subprocess +import logging +from pathlib import Path +from typing import List, Literal, Optional, Dict + +# This is a placeholder for the actual 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 + +def _validate_cache_path(cache_path: Path, check_exists: bool = True) -> None: + """Helper function to validate the cache path.""" + if check_exists and not cache_path.is_dir(): + raise FileNotFoundError(f"BiocFileCache directory not found at '{cache_path}'") + +def _r_string_vector(items: List[str]) -> str: + """Converts a Python list of strings to an R character vector string.""" + if not items: + return "c()" + # Escape single quotes for R: ' -> \' + escaped_items = [item.replace("'", "\\'") for item in items] + quoted_items = [f"'{item}'" for item in escaped_items] + return f"c({', '.join(quoted_items)})" + +def _run_r_command(r_command: str) -> Dict: + """Executes an R command and returns a structured response.""" + command = ["Rscript", "-e", r_command] + command_str = " ".join(command) + try: + result = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding='utf-8' + ) + return { + "command_executed": command_str, + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + } + except FileNotFoundError: + logging.error("Rscript not found. R must be installed and in the system's PATH.") + raise RuntimeError("Rscript not found. R must be installed and in the system's PATH.") + except subprocess.CalledProcessError as e: + logging.error(f"R script execution failed.\nStderr: {e.stderr}\nStdout: {e.stdout}") + return { + "command_executed": command_str, + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": "R script execution failed." + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bioconductor_biocfilecache' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def biocfilecache_new(cache_path: Path) -> Dict: + """ + Creates a new, empty BiocFileCache at the specified location. + + Args: + cache_path: The directory path where the cache will be created. + The parent directory must exist, and the target path must not. + + Returns: + A dictionary containing the command executed, stdout (the cache path), + stderr, and the path to the created cache directory. + """ + if cache_path.exists(): + raise ValueError(f"Cache path '{cache_path}' already exists. Cannot create a new cache here.") + if not cache_path.parent.is_dir(): + raise FileNotFoundError(f"Parent directory '{cache_path.parent}' does not exist.") + + # Use forward slashes for R path compatibility + safe_cache_path = str(cache_path).replace('\\', '/') + r_command = ( + f"library(BiocFileCache); " + f"bfc <- bfcnew('{safe_cache_path}', ask=FALSE); " + f"cat(bfcpath(bfc))" + ) + + response = _run_r_command(r_command) + if "error" not in response: + response["output_files"] = {"cache_directory": str(cache_path)} + return response + +@mcp.tool() +def biocfilecache_add( + cache_path: Path, + rname: str, + fpath: str, + action: Literal["copy", "move", "asis"] = "copy", + rtype: Literal["web", "local"] = "web" +) -> Dict: + """ + Adds a resource to the BiocFileCache. + + Args: + cache_path: Path to the BiocFileCache directory. + rname: A user-friendly name for the resource. + fpath: The path to the resource, either a URL or a local file path. + action: How to handle the file. 'copy' (default), 'move', or 'asis' (for remote files). + rtype: The type of resource, 'web' (default) or 'local'. + + Returns: + A dictionary containing the command executed, stdout (the new resource ID), and stderr. + """ + _validate_cache_path(cache_path) + if rtype == "local" and not Path(fpath).exists(): + raise FileNotFoundError(f"Local file '{fpath}' not found for adding to cache.") + + safe_cache_path = str(cache_path).replace('\\', '/') + # Escape single quotes and normalize path for R + safe_rname = rname.replace("'", "\\'") + safe_fpath = fpath.replace('\\', '/').replace("'", "\\'") + + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"rid <- bfcadd(bfc, rname='{safe_rname}', fpath='{safe_fpath}', action='{action}', rtype='{rtype}'); " + f"cat(rid)" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_rpath(cache_path: Path, rids: List[str]) -> Dict: + """ + Retrieves the local file paths of resources from the cache using their resource IDs (rids). + + Args: + cache_path: Path to the BiocFileCache directory. + rids: A list of resource IDs (e.g., ['BFC1', 'BFC2']) to look up. + + Returns: + A dictionary containing the command executed, stdout (newline-separated file paths), and stderr. + """ + _validate_cache_path(cache_path) + if not rids: + raise ValueError("The 'rids' list cannot be empty.") + + safe_cache_path = str(cache_path).replace('\\', '/') + rids_vector = _r_string_vector(rids) + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"paths <- bfcrpath(bfc, rids={rids_vector}); " + f"cat(paths, sep='\\n')" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_query(cache_path: Path, query: str, field: Optional[str] = None) -> Dict: + """ + Queries the cache metadata using an SQL-like expression. + + Args: + cache_path: Path to the BiocFileCache directory. + query: The SQL 'WHERE' clause for filtering resources (e.g., "rname == 'my_file'"). + field: Optional. The specific column/field to return from the query results. + + Returns: + A dictionary containing the command executed, stdout (query results as a table), and stderr. + """ + _validate_cache_path(cache_path) + safe_cache_path = str(cache_path).replace('\\', '/') + safe_query = query.replace("'", "\\'") + + field_arg = "" + if field: + safe_field = field.replace("'", "\\'") + field_arg = f", field='{safe_field}'" + + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"res <- bfcquery(bfc, query='{safe_query}'{field_arg}); " + f"print(res)" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_remove(cache_path: Path, rids: List[str]) -> Dict: + """ + Removes resources and their associated files from the cache. + + Args: + cache_path: Path to the BiocFileCache directory. + rids: A list of resource IDs to remove. + + Returns: + A dictionary containing the command executed, stdout, and stderr. + """ + _validate_cache_path(cache_path) + if not rids: + raise ValueError("The 'rids' list cannot be empty.") + + safe_cache_path = str(cache_path).replace('\\', '/') + rids_vector = _r_string_vector(rids) + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"bfcremove(bfc, rids={rids_vector}); " + f"cat('Specified resources removed.')" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_ls(cache_path: Path, pattern: Optional[str] = None) -> Dict: + """ + Lists resources in the cache, optionally filtering by a regular expression. + + Args: + cache_path: Path to the BiocFileCache directory. + pattern: An optional regular expression to filter resource names (rname). + + Returns: + A dictionary containing the command executed, stdout (a table of resources), and stderr. + """ + _validate_cache_path(cache_path) + safe_cache_path = str(cache_path).replace('\\', '/') + + pattern_arg = "" + if pattern: + safe_pattern = pattern.replace("'", "\\'") + pattern_arg = f", pattern='{safe_pattern}'" + + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"res <- bfcls(bfc{pattern_arg}); " + f"print(res)" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_update(cache_path: Path, rids: List[str]) -> Dict: + """ + Updates resources in the cache by re-downloading them. + + Args: + cache_path: Path to the BiocFileCache directory. + rids: A list of resource IDs to update. + + Returns: + A dictionary containing the command executed, stdout (updated resource info), and stderr. + """ + _validate_cache_path(cache_path) + if not rids: + raise ValueError("The 'rids' list cannot be empty.") + + safe_cache_path = str(cache_path).replace('\\', '/') + rids_vector = _r_string_vector(rids) + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"res <- bfcupdate(bfc, rids={rids_vector}); " + f"print(res)" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_clean(cache_path: Path) -> Dict: + """ + Cleans the cache by removing files that are no longer tracked in the database. + + Args: + cache_path: Path to the BiocFileCache directory. + + Returns: + A dictionary containing the command executed, stdout, and stderr. + """ + _validate_cache_path(cache_path) + safe_cache_path = str(cache_path).replace('\\', '/') + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"bfcclean(bfc, ask=FALSE); " + f"cat('Cache cleaned.')" + ) + return _run_r_command(r_command) + +@mcp.tool() +def biocfilecache_info(cache_path: Path, rids: Optional[List[str]] = None) -> Dict: + """ + Retrieves detailed metadata for all or specified resources in the cache. + + Args: + cache_path: Path to the BiocFileCache directory. + rids: Optional list of resource IDs to get information for. If None, info for all resources is returned. + + Returns: + A dictionary containing the command executed, stdout (a table of resource info), and stderr. + """ + _validate_cache_path(cache_path) + safe_cache_path = str(cache_path).replace('\\', '/') + rids_arg = f", rids={_r_string_vector(rids)}" if rids else "" + + r_command = ( + f"library(BiocFileCache); " + f"bfc <- BiocFileCache('{safe_cache_path}', ask=FALSE); " + f"res <- bfcinfo(bfc{rids_arg}); " + f"print(res)" + ) + return _run_r_command(r_command) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3a5301b33ffa102bdbae33a4faf4f7cade3dbc10 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_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-biocfilecache/app/bioconductor-biocfilecache_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_biocfilecache' + + +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-biocfilecache/app/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..dfaeb2fc1b7bb666760166c994ac11641ccea05c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-biocfilecache: + build: . + image: mcp-bioconductor-biocfilecache:latest + container_name: mcp-bioconductor-biocfilecache + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-biocfilecache + 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-biocfilecache/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fadedb1ae2ed0930e924af8fb048742ab8741ed4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-biocfilecache + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocfilecache/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7a2b64e38b21f9a17c0eed662d17a9ffec398d69 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/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-biocgenerics via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-biocgenerics -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-biocgenerics_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-biocgenerics_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-biocgenerics_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/app/bioconductor-biocgenerics_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/app/bioconductor-biocgenerics_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7c76df863a139f993d591cc736d56905e1130b1b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/app/bioconductor-biocgenerics_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-biocgenerics/app/bioconductor-biocgenerics_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_biocgenerics' + + +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-biocgenerics/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..70411f1e7d442cee4491e9153313717c04073c2a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-biocgenerics: + build: . + image: mcp-bioconductor-biocgenerics:latest + container_name: mcp-bioconductor-biocgenerics + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-biocgenerics + 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-biocgenerics/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..787c5a33b775733411e744e8efa96b3656b0a63c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-biocgenerics/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-biocgenerics + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-delayedarray/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-delayedarray/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-delayedarray/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bioconductor-nebulosa/Dockerfile b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0f63490caed08cf51637c5ae1864b93d17c928e8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/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-nebulosa via conda (e.g., from bioconda) +RUN conda install -c bioconda bioconductor-nebulosa -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-nebulosa_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-nebulosa_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-nebulosa_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_server.py b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1acb976fd65c2b32692aea0fcc54a97d3803e4e8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_server.py @@ -0,0 +1,185 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Tuple + +# This is a placeholder for the MCP decorator. +# In a real MCP environment, this would be imported. +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_bioconductor_nebulosa' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def nebulosa_plot_density( + object_file: Path, + features: List[str], + output_file: Path, + reduction: Optional[str] = None, + dims: Tuple[int, int] = (1, 2), + pal: str = "viridis", + joint: bool = False, + combine: bool = True, + pt_size: float = 0.1, + pt_shape: int = 16, + pt_alpha: float = 1.0, + dens_alpha: float = 0.5, + plot_width: float = 8.0, + plot_height: float = 6.0, +) -> dict: + """ + Generates density plots for single-cell features using the Nebulosa R package. + + This tool wraps the `plot_density` function from `bioconductor-nebulosa`. It takes a + single-cell object (e.g., from Seurat, saved as an .rds file), a list of features + (e.g., genes), and generates a plot showing the expression density of these + features on a dimensionality reduction embedding (like UMAP or t-SNE). + + Args: + object_file: Path to the input single-cell object file in .rds format. + features: A list of features (e.g., genes) to plot. + output_file: Path to save the output plot. Supported formats include PDF, PNG, JPEG. + reduction: The dimensionality reduction to use (e.g., 'umap', 'tsne'). + If None, the default from the object is used. + dims: A tuple of two integers specifying the dimensions to plot (e.g., (1, 2)). + pal: The color palette to use for the density plot (e.g., 'viridis', 'magma'). + joint: If True, plots all features jointly on a single plot. + combine: If True and `joint` is False, combines multiple feature plots into a single grid. + pt_size: The size of the points in the plot. + pt_shape: The shape of the points (integer code). + pt_alpha: The transparency of the points (0.0 to 1.0). + dens_alpha: The transparency of the density layer (0.0 to 1.0). + plot_width: The width of the output plot in inches. + plot_height: The height of the output plot in inches. + + Returns: + A dictionary containing the execution command, stdout, stderr, and a + mapping to the generated output plot file. + """ + # --- Input Validation --- + if not object_file.is_file(): + raise FileNotFoundError(f"Input object file not found: {object_file}") + + if not features: + raise ValueError("The 'features' list cannot be empty.") + + if not output_file.parent.exists(): + raise FileNotFoundError(f"Output directory does not exist: {output_file.parent}") + + if len(dims) != 2: + raise ValueError(f"'dims' must be a tuple of two integers, but got {dims}") + + if not (0.0 <= pt_alpha <= 1.0): + raise ValueError(f"'pt_alpha' must be between 0.0 and 1.0, but got {pt_alpha}") + + if not (0.0 <= dens_alpha <= 1.0): + raise ValueError(f"'dens_alpha' must be between 0.0 and 1.0, but got {dens_alpha}") + + # --- R Script Generation --- + # Convert Python types to R syntax strings + features_r_str = ', '.join(f'"{f}"' for f in features) + reduction_r_str = f'"{reduction}"' if reduction else "NULL" + joint_r_str = str(joint).upper() + combine_r_str = str(combine).upper() + + r_script_content = f""" + # Suppress package startup messages for cleaner output + suppressPackageStartupMessages(library(Nebulosa)) + suppressPackageStartupMessages(library(Seurat)) # Assumes a Seurat object for loading + suppressPackageStartupMessages(library(ggplot2)) + + # --- Parameters from Python --- + object_path <- "{object_file.resolve()}" + output_path <- "{output_file.resolve()}" + features_vec <- c({features_r_str}) + reduction_val <- {reduction_r_str} + dims_vec <- c({dims[0]}, {dims[1]}) + pal_str <- "{pal}" + joint_bool <- {joint_r_str} + combine_bool <- {combine_r_str} + pt_size_num <- {pt_size} + pt_shape_num <- {pt_shape} + pt_alpha_num <- {pt_alpha} + dens_alpha_num <- {dens_alpha} + plot_width_num <- {plot_width} + plot_height_num <- {plot_height} + + # --- Main Logic --- + cat("Loading single-cell object from:", object_path, "\\n") + sobj <- readRDS(object_path) + + cat("Generating density plot for features:", paste(features_vec, collapse=", "), "\\n") + p <- plot_density( + object = sobj, + features = features_vec, + reduction = reduction_val, + dims = dims_vec, + pal = pal_str, + joint = joint_bool, + combine = combine_bool, + pt.size = pt_size_num, + pt.shape = pt_shape_num, + pt.alpha = pt_alpha_num, + dens.alpha = dens_alpha_num + ) + + cat("Saving plot to:", output_path, "\\n") + ggsave( + filename = output_path, + plot = p, + width = plot_width_num, + height = plot_height_num, + units = "in" + ) + + cat("Plot generation complete.\\n") + """ + + # --- Subprocess Execution --- + stdout_str, stderr_str = "", "" + command_to_run = [] + + try: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".R", delete=False + ) as tmp_script: + tmp_script_path = Path(tmp_script.name) + tmp_script.write(r_script_content) + + command_to_run = ["Rscript", str(tmp_script_path)] + process = subprocess.run( + command_to_run, + capture_output=True, + text=True, + check=True, + ) + stdout_str = process.stdout + stderr_str = process.stderr + + except FileNotFoundError: + raise RuntimeError("Rscript not found. Please ensure R is installed and in your PATH.") + except subprocess.CalledProcessError as e: + stderr_str = e.stderr + f"\n--- R Script Content ---\n{r_script_content}" + raise RuntimeError(f"R script execution failed with exit code {e.returncode}:\n{stderr_str}") + finally: + if 'tmp_script_path' in locals() and tmp_script_path.exists(): + tmp_script_path.unlink() + + # --- Structured Result Return --- + return { + "command_executed": " ".join(command_to_run), + "stdout": stdout_str, + "stderr": stderr_str, + "output_files": {"density_plot": str(output_file)}, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_shim_server.py b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..2ac1f7f1315815bbbe67a58318c9dba20f7f5e9d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_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-nebulosa/app/bioconductor-nebulosa_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bioconductor_nebulosa' + + +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-nebulosa/docker-compose.yml b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..791e3c6938d43d81317b2e40924857ae6bb3b39c --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bioconductor-nebulosa: + build: . + image: mcp-bioconductor-nebulosa:latest + container_name: mcp-bioconductor-nebulosa + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bioconductor-nebulosa + 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-nebulosa/environment.yaml b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b24fe111257cb3c281a4ef0fe14bb4737b81aeba --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bioconductor-nebulosa + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bioconductor-nebulosa/requirements.txt b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bioconductor-nebulosa/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_brooklyn_plot/Dockerfile b/Biomni/mcp_generated/mcp_brooklyn_plot/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..08c5a4ad2668f0ac67e8120ccc090b88ef191270 --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/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 brooklyn_plot via conda (e.g., from bioconda) +RUN conda install -c bioconda brooklyn_plot -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 brooklyn_plot_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/brooklyn_plot_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/brooklyn_plot_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_server.py b/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_server.py new file mode 100644 index 0000000000000000000000000000000000000000..60bd0ddd58e756db3aecb1b8d718b3e4564b0d9d --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_server.py @@ -0,0 +1,170 @@ +import subprocess +import logging +from pathlib import Path +from typing import List, Optional, Literal, Dict, Any + +# Set up a logger for internal use +logger = logging.getLogger(__name__) + +# @mcp.tool() decorator is assumed to be imported from the MCP framework. +# No need to define it here. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_brooklyn_plot' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_brooklyn_plot( + input_file: Path, + output_file: str, + input_type: Literal["vcf", "txt"], + title: str = "Brooklyn Plot", + width: int = 12, + height: int = 8, + dpi: int = 300, + output_format: Literal["png", "pdf", "svg", "jpg"] = "png", + style: str = "seaborn-whitegrid", + palette: str = "colorblind", + font_scale: float = 1.2, + hide_legend: bool = False, + show_genes: bool = False, + gene_fontsize: int = 8, + min_vaf: float = 0.0, + max_vaf: float = 1.0, + min_depth: int = 0, + genes_to_label: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Generates a Brooklyn plot from variant data. + + A Brooklyn plot is a type of visualization used in genomics to display variant allele frequencies (VAFs) + of mutations across different samples or time points, often used in cancer genomics to track clonal evolution. + This tool takes a VCF or a tab-delimited file as input and produces a customizable plot. + + Args: + input_file: Path to the input file containing variant data (VCF or tab-delimited text). + output_file: Name for the output plot file. The extension should match the chosen format. + input_type: Type of the input file. + title: Title of the plot. + width: Width of the plot in inches. + height: Height of the plot in inches. + dpi: Resolution of the plot in dots per inch. + output_format: Format of the output plot. + style: Style of the plot. Any valid matplotlib style is accepted. + palette: Color palette for the plot. Any valid seaborn palette is accepted. + font_scale: Font scale for the plot. + hide_legend: If True, hides the legend from the plot. + show_genes: If True, shows gene names on the plot. + gene_fontsize: Font size for gene names. + min_vaf: Minimum variant allele frequency (VAF) to include in the plot. + max_vaf: Maximum variant allele frequency (VAF) to include in the plot. + min_depth: Minimum read depth to include in the plot. + genes_to_label: A list of specific gene names to label on the plot. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + if width <= 0 or height <= 0 or dpi <= 0: + raise ValueError("Plot dimensions (width, height, dpi) must be positive.") + if font_scale <= 0 or gene_fontsize <= 0: + raise ValueError("Font scaling parameters (font_scale, gene_fontsize) must be positive.") + if min_depth < 0: + raise ValueError("min_depth cannot be negative.") + + if not (0.0 <= min_vaf <= 1.0): + raise ValueError(f"min_vaf must be between 0.0 and 1.0, but got {min_vaf}") + if not (0.0 <= max_vaf <= 1.0): + raise ValueError(f"max_vaf must be between 0.0 and 1.0, but got {max_vaf}") + if min_vaf > max_vaf: + raise ValueError(f"min_vaf ({min_vaf}) cannot be greater than max_vaf ({max_vaf})") + + # --- Command Construction --- + cmd = [ + "brooklyn_plot", + "-i", str(input_file), + "-o", output_file, + "-t", input_type, + ] + + # Add optional arguments if they differ from the default + if title != "Brooklyn Plot": + cmd.extend(["--title", title]) + if width != 12: + cmd.extend(["--width", str(width)]) + if height != 8: + cmd.extend(["--height", str(height)]) + if dpi != 300: + cmd.extend(["--dpi", str(dpi)]) + if output_format != "png": + cmd.extend(["--format", output_format]) + if style != "seaborn-whitegrid": + cmd.extend(["--style", style]) + if palette != "colorblind": + cmd.extend(["--palette", palette]) + if font_scale != 1.2: + cmd.extend(["--font_scale", str(font_scale)]) + if gene_fontsize != 8: + cmd.extend(["--gene_fontsize", str(gene_fontsize)]) + if min_vaf != 0.0: + cmd.extend(["--min_vaf", str(min_vaf)]) + if max_vaf != 1.0: + cmd.extend(["--max_vaf", str(max_vaf)]) + if min_depth != 0: + cmd.extend(["--min_depth", str(min_depth)]) + + # Add boolean flags if True + if hide_legend: + cmd.append("--hide_legend") + if show_genes: + cmd.append("--show_genes") + + # Add list-based argument + if genes_to_label: + cmd.append("--genes_to_label") + cmd.extend(genes_to_label) + + command_executed = " ".join(cmd) + logger.info(f"Executing command: {command_executed}") + + # --- Subprocess Execution --- + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + + output_path = Path(output_file) + output_files = [str(output_path)] if output_path.exists() else [] + if not output_files: + logger.warning(f"Output file '{output_file}' was not created by the tool.") + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + error_message = "Error: 'brooklyn_plot' command not found. Please ensure the tool is installed and in the system's PATH." + logger.error(error_message) + # Re-raising as a more specific error for the MCP framework + raise RuntimeError(error_message) from None + except subprocess.CalledProcessError as e: + logger.error(f"brooklyn_plot failed with exit code {e.returncode}") + # Return structured error information as per MCP guidelines + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_shim_server.py b/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0932ee3de92c3f261379ad1aa1c55c659f48b3ed --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/app/brooklyn_plot_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_brooklyn_plot/app/brooklyn_plot_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_brooklyn_plot' + + +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_brooklyn_plot/docker-compose.yml b/Biomni/mcp_generated/mcp_brooklyn_plot/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..821fcc1f62c40c20276c1b35c3a537c8912da6ac --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-brooklyn_plot: + build: . + image: mcp-brooklyn_plot:latest + container_name: mcp-brooklyn_plot + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=brooklyn_plot + 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_brooklyn_plot/environment.yaml b/Biomni/mcp_generated/mcp_brooklyn_plot/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a8bbdd2f0101423b4cb128095f50fddc252d0ee4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - brooklyn_plot + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_brooklyn_plot/requirements.txt b/Biomni/mcp_generated/mcp_brooklyn_plot/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_brooklyn_plot/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_bx-python/Dockerfile b/Biomni/mcp_generated/mcp_bx-python/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e944719ad7669aebdd1a19282297c0b43c4d4f1d --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install bx-python via conda (e.g., from bioconda) +RUN conda install -c bioconda bx-python -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/bx-python_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/bx-python_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/bx-python_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bx-python/app/bx-python_server.py b/Biomni/mcp_generated/mcp_bx-python/app/bx-python_server.py new file mode 100644 index 0000000000000000000000000000000000000000..739422d39e6392a57a74dd48781497d8fda60382 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/app/bx-python_server.py @@ -0,0 +1,361 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# Note: The bx-python package is a suite of individual command-line scripts. +# Each script is wrapped as a separate MCP tool function. The following is a +# representative selection of these tools. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_bx_python' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def bed_intersect( + bed1: Path, + bed2: Path, + output_file: Optional[Path] = None, + find_intersections: bool = False, + find_non_intersections: bool = False, + count_intersections: bool = False, + min_overlap_cols: int = 1, + preserve_intervals: bool = True, +) -> dict: + """ + Finds intersecting regions between two BED files. + + This tool corresponds to the `bed_intersect.py` script. + It can report the intersections, non-intersections, or just the count. + """ + # Input validation + if not bed1.is_file(): + raise FileNotFoundError(f"Input BED file not found: {bed1}") + if not bed2.is_file(): + raise FileNotFoundError(f"Second input BED file not found: {bed2}") + if find_intersections and find_non_intersections: + raise ValueError("Cannot specify both --find-intersections (-i) and --find-non-intersections (-I).") + if min_overlap_cols < 1: + raise ValueError("--min-overlap-cols must be a positive integer.") + + cmd = ["bed_intersect", str(bed1), str(bed2)] + + if find_intersections: + cmd.append("-i") + if find_non_intersections: + cmd.append("-I") + if count_intersections: + cmd.append("-c") + if min_overlap_cols != 1: + cmd.extend(["-m", str(min_overlap_cols)]) + if not preserve_intervals: + cmd.append("-p") # The script's -p flag means "don't chop up intervals", which is counter-intuitive. We reverse it for clarity. + + command_executed = " ".join(cmd) + output_files = [] + stdout_capture = "" + stderr_capture = "" + + try: + if output_file: + output_files.append(str(output_file)) + with open(output_file, "w") as f_out: + result = subprocess.run( + cmd, + check=True, + text=True, + stdout=f_out, + stderr=subprocess.PIPE + ) + stderr_capture = result.stderr + else: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True + ) + stdout_capture = result.stdout + stderr_capture = result.stderr + + except FileNotFoundError: + raise RuntimeError("bed_intersect command not found. Is bx-python installed and in the system's PATH?") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": stdout_capture, + "stderr": stderr_capture, + "output_files": output_files + } + +@mcp.tool() +def maf_filter( + maf_file: Path, + expression: str, + output_file: Optional[Path] = None, + species: Optional[str] = None, + min_score: Optional[float] = None, + actions: Optional[str] = None, + score_column: Optional[str] = None, + score_op: Optional[str] = None, +) -> dict: + """ + Filters a MAF file based on a given expression. + + This tool corresponds to the `maf_filter.py` script. It allows for complex + filtering of Multiple Alignment Format (MAF) files. + """ + if not maf_file.is_file(): + raise FileNotFoundError(f"Input MAF file not found: {maf_file}") + if score_op and score_op not in ["avg", "min", "max", "sum"]: + raise ValueError(f"Invalid score_op: {score_op}. Must be one of 'avg', 'min', 'max', 'sum'.") + + cmd = ["maf_filter"] + cmd.extend(["-e", expression]) + + if species: + cmd.extend(["-s", species]) + if min_score is not None: + cmd.extend(["-m", str(min_score)]) + if actions: + cmd.extend(["-a", actions]) + if score_column: + cmd.extend(["-S", score_column]) + if score_op: + cmd.extend(["-O", score_op]) + + cmd.append(str(maf_file)) + command_executed = " ".join(cmd) + output_files = [] + stdout_capture = "" + stderr_capture = "" + + try: + if output_file: + output_files.append(str(output_file)) + with open(output_file, "w") as f_out: + result = subprocess.run( + cmd, + check=True, + text=True, + stdout=f_out, + stderr=subprocess.PIPE + ) + stderr_capture = result.stderr + else: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True + ) + stdout_capture = result.stdout + stderr_capture = result.stderr + + except FileNotFoundError: + raise RuntimeError("maf_filter command not found. Is bx-python installed and in the system's PATH?") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": stdout_capture, + "stderr": stderr_capture, + "output_files": output_files + } + +@mcp.tool() +def fasta_formatter( + fasta_file: Path, + output_file: Optional[Path] = None, + line_length: int = 80, +) -> dict: + """ + Reformats a FASTA file to a specified line length. + + This tool corresponds to the `fasta_formatter.py` script. + """ + if not fasta_file.is_file(): + raise FileNotFoundError(f"Input FASTA file not found: {fasta_file}") + if line_length <= 0: + raise ValueError("line_length must be a positive integer.") + + cmd = ["fasta_formatter", str(fasta_file)] + if line_length != 80: # Assuming 80 is the default, though the script might not have one. + cmd.extend(["-c", str(line_length)]) + + command_executed = " ".join(cmd) + output_files = [] + stdout_capture = "" + stderr_capture = "" + + try: + if output_file: + output_files.append(str(output_file)) + with open(output_file, "w") as f_out: + result = subprocess.run( + cmd, + check=True, + text=True, + stdout=f_out, + stderr=subprocess.PIPE + ) + stderr_capture = result.stderr + else: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True + ) + stdout_capture = result.stdout + stderr_capture = result.stderr + + except FileNotFoundError: + raise RuntimeError("fasta_formatter command not found. Is bx-python installed and in the system's PATH?") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": stdout_capture, + "stderr": stderr_capture, + "output_files": output_files + } + +@mcp.tool() +def wig_to_bigwig( + wig_file: Path, + chrom_sizes: Path, + bigwig_output: Path, + clip_val: Optional[int] = None, + chunk_size: int = 1024, +) -> dict: + """ + Converts a Wiggle (WIG) format file to a BigWig file. + + This tool corresponds to the `wig_to_bigwig.py` script. + """ + if not wig_file.is_file(): + raise FileNotFoundError(f"Input Wiggle file not found: {wig_file}") + if not chrom_sizes.is_file(): + raise FileNotFoundError(f"Chromosome sizes file not found: {chrom_sizes}") + if chunk_size <= 0: + raise ValueError("chunk_size must be a positive integer.") + + cmd = ["wig_to_bigwig", str(wig_file), str(chrom_sizes), str(bigwig_output)] + + if clip_val is not None: + cmd.extend(["-clip", str(clip_val)]) + if chunk_size != 1024: + cmd.extend(["-chunk", str(chunk_size)]) + + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True + ) + except FileNotFoundError: + raise RuntimeError("wig_to_bigwig command not found. Is bx-python installed and in the system's PATH?") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(bigwig_output)] + } + +@mcp.tool() +def get_flanking_sequences( + bed_file: Path, + genome_file: Path, + output_fasta: Path, + length: int, + location: str = "g", +) -> dict: + """ + Extracts flanking sequences for regions in a BED file from a genome FASTA file. + + This tool corresponds to the `get_flanking_sequences.py` script. + """ + if not bed_file.is_file(): + raise FileNotFoundError(f"Input BED file not found: {bed_file}") + if not genome_file.is_file(): + raise FileNotFoundError(f"Genome FASTA file not found: {genome_file}") + if length <= 0: + raise ValueError("length must be a positive integer.") + valid_locations = ["b", "e", "g", "G"] + if location not in valid_locations: + raise ValueError(f"Invalid location '{location}'. Must be one of {valid_locations}.") + + cmd = [ + "get_flanking_sequences", + str(bed_file), + "-g", str(genome_file), + "-o", str(output_fasta), + "-l", str(length), + "-L", location + ] + + command_executed = " ".join(cmd) + + try: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True + ) + except FileNotFoundError: + raise RuntimeError("get_flanking_sequences command not found. Is bx-python installed and in the system's PATH?") + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [str(output_fasta)] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bx-python/app/bx-python_shim_server.py b/Biomni/mcp_generated/mcp_bx-python/app/bx-python_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a64bb1acfdd0ee70d23b745106000f3187d9435b --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/app/bx-python_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bx-python/app/bx-python_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_bx_python' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_bx-python/app/requirements.txt b/Biomni/mcp_generated/mcp_bx-python/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_bx-python/docker-compose.yml b/Biomni/mcp_generated/mcp_bx-python/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ba07e9f1b33fa2d98a1abc9b812fc44e4beca23a --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-bx-python: + build: . + image: mcp-bx-python:latest + container_name: mcp-bx-python + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=bx-python + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bx-python/environment.yaml b/Biomni/mcp_generated/mcp_bx-python/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..913db797990ca362aa5273ab0c8bd700e08a86de --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - bx-python + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_bx-python/requirements.txt b/Biomni/mcp_generated/mcp_bx-python/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_bx-python/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_cd-hit/Dockerfile b/Biomni/mcp_generated/mcp_cd-hit/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ff7a9d5afb15ee9ed48b941671202aa2435b509b --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/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 cd-hit via conda (e.g., from bioconda) +RUN conda install -c bioconda cd-hit -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/cd-hit_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/cd-hit_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/cd-hit_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cd-hit/app/cd-hit_server.py b/Biomni/mcp_generated/mcp_cd-hit/app/cd-hit_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b7c9118b9c80ae78ad7b2a50ea9b82fd2184f2cd --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/app/cd-hit_server.py @@ -0,0 +1,192 @@ +import subprocess +import logging +from pathlib import Path +from typing import List + +# @mcp.tool() is a decorator provided by the Model Context Protocol (MCP) framework. +# It is used here to define a bioinformatics tool that can be executed by the MCP server. +# For the purpose of this code generation, we assume 'mcp' is an available module. +# No need to import it in the final code. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_cd_hit' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def cd_hit( + input_file: Path, + output_file: Path, + identity_threshold: float = 0.9, + global_identity: bool = True, + band_width: int = 20, + memory_limit: int = 800, + threads: int = 1, + word_length: int = 5, + throw_away_length: int = 10, + redundancy_tolerance: int = 2, + description_length: int = 20, + length_diff_cutoff_percent: float = 0.0, + length_diff_cutoff_aa: int = 999999, + align_coverage_longer_percent: float = 0.0, + align_coverage_longer_abs: int = 99999999, + align_coverage_shorter_percent: float = 0.0, + align_coverage_shorter_abs: int = 99999999, + min_align_coverage_both: int = 0, + max_unmatched_percent_longer: float = 1.0, + max_unmatched_percent_shorter: float = 1.0, + max_unmatched_length: int = 99999999, + print_alignment_overlap: bool = False, + best_cluster: bool = False, + sort_clusters_by_size: bool = False, + sort_fasta_by_cluster_size: bool = False, + write_backup_file: bool = False, +): + """ + Clusters a FASTA file of sequences using CD-HIT. + + CD-HIT is a fast program for clustering and comparing large sets of protein or nucleotide sequences. + It clusters sequences based on a specified identity threshold. + + Args: + input_file: Input filename in FASTA format, can be in .gz format. + output_file: Output filename for the representative sequences. + identity_threshold: Sequence identity threshold (0.0 to 1.0). Default is 0.9. + global_identity: If True (default), use global sequence identity. If False, use local sequence identity. + band_width: Band width of alignment. Default is 20. + memory_limit: Memory limit in MB for the program. 0 for unlimited. Default is 800. + threads: Number of threads. 0 for all CPUs. Default is 1. + word_length: Word length for the algorithm. Default is 5. + throw_away_length: Length of sequences to throw away. Default is 10. + redundancy_tolerance: Tolerance for redundancy. Default is 2. + description_length: Length of description in .clstr file. 0 takes the full defline. Default is 20. + length_diff_cutoff_percent: Shorter sequences must be at least this fraction of the representative's length. Default is 0.0. + length_diff_cutoff_aa: The length difference between shorter sequences and the representative cannot be bigger than this value. Default is 999999. + align_coverage_longer_percent: Alignment must cover this fraction of the longer sequence. Default is 0.0. + align_coverage_longer_abs: Alignment length must be >= (longer sequence length - this value). Default is 99999999. + align_coverage_shorter_percent: Alignment must cover this fraction of the shorter sequence. Default is 0.0. + align_coverage_shorter_abs: Alignment length must be >= (shorter sequence length - this value). Default is 99999999. + min_align_coverage_both: Minimal alignment coverage for both sequences. Default is 0. + max_unmatched_percent_longer: Max unmatched percentage for the longer sequence (0.0 to 1.0). Default is 1.0. + max_unmatched_percent_shorter: Max unmatched percentage for the shorter sequence (0.0 to 1.0). Default is 1.0. + max_unmatched_length: Maximum unmatched length in bases. Default is 99999999. + print_alignment_overlap: If True, print alignment overlap in .clstr file. Default is False. + best_cluster: If True, cluster to the most similar cluster (slow but accurate). Default is False (fast cluster). + sort_clusters_by_size: If True, sort clusters by decreasing size. Default is False (sort by length). + sort_fasta_by_cluster_size: If True, sort output FASTA/FASTQ by decreasing cluster size. Default is False. + write_backup_file: If True, write a backup cluster file (.clstr.bak). Default is False. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + if not input_file.is_file(): + raise FileNotFoundError(f"Input file not found: {input_file}") + if not output_file.parent.exists(): + output_file.parent.mkdir(parents=True, exist_ok=True) + + # Parameter value validation + if not (0.0 <= identity_threshold <= 1.0): + raise ValueError("identity_threshold (-c) must be between 0.0 and 1.0") + if threads < 0: + raise ValueError("threads (-T) cannot be negative.") + if memory_limit < 0: + raise ValueError("memory_limit (-M) cannot be negative.") + if not (0.0 <= length_diff_cutoff_percent <= 1.0): + raise ValueError("length_diff_cutoff_percent (-s) must be between 0.0 and 1.0") + if not (0.0 <= align_coverage_longer_percent <= 1.0): + raise ValueError("align_coverage_longer_percent (-aL) must be between 0.0 and 1.0") + if not (0.0 <= align_coverage_shorter_percent <= 1.0): + raise ValueError("align_coverage_shorter_percent (-aS) must be between 0.0 and 1.0") + if not (0.0 <= max_unmatched_percent_longer <= 1.0): + raise ValueError("max_unmatched_percent_longer (-uL) must be between 0.0 and 1.0") + if not (0.0 <= max_unmatched_percent_shorter <= 1.0): + raise ValueError("max_unmatched_percent_shorter (-uS) must be between 0.0 and 1.0") + + # Special validation for local identity mode + if not global_identity: + coverage_controls_set = ( + align_coverage_longer_percent != 0.0 or + align_coverage_longer_abs != 99999999 or + align_coverage_shorter_percent != 0.0 or + align_coverage_shorter_abs != 99999999 + ) + if not coverage_controls_set: + logging.warning( + "Using local sequence identity (-G 0) without setting alignment coverage controls " + "(-aL, -AL, -aS, -AS) is not recommended by the tool's documentation." + ) + + # --- Command Construction --- + cmd = [ + "cd-hit", + "-i", str(input_file), + "-o", str(output_file), + "-c", str(identity_threshold), + "-G", "1" if global_identity else "0", + "-b", str(band_width), + "-M", str(memory_limit), + "-T", str(threads), + "-n", str(word_length), + "-l", str(throw_away_length), + "-t", str(redundancy_tolerance), + "-d", str(description_length), + "-s", str(length_diff_cutoff_percent), + "-S", str(length_diff_cutoff_aa), + "-aL", str(align_coverage_longer_percent), + "-AL", str(align_coverage_longer_abs), + "-aS", str(align_coverage_shorter_percent), + "-AS", str(align_coverage_shorter_abs), + "-A", str(min_align_coverage_both), + "-uL", str(max_unmatched_percent_longer), + "-uS", str(max_unmatched_percent_shorter), + "-U", str(max_unmatched_length), + "-p", "1" if print_alignment_overlap else "0", + "-g", "1" if best_cluster else "0", + "-sc", "1" if sort_clusters_by_size else "0", + "-sf", "1" if sort_fasta_by_cluster_size else "0", + "-bak", "1" if write_backup_file else "0", + ] + + # --- Subprocess Execution --- + command_str = " ".join(cmd) + try: + result = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + ) + stdout = result.stdout + stderr = result.stderr + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "Error: 'cd-hit' command not found. Please ensure it is installed and in your system's PATH.", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + + # --- Structured Result Return --- + output_files: List[str] = [str(output_file), f"{output_file}.clstr"] + if write_backup_file: + backup_path = Path(f"{output_file}.clstr.bak") + if backup_path.exists(): + output_files.append(str(backup_path)) + + 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_cd-hit/app/cd-hit_shim_server.py b/Biomni/mcp_generated/mcp_cd-hit/app/cd-hit_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ac4153e2a69eafca5294dbf8fb0de93c2b8c45f5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/app/cd-hit_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_cd-hit/app/cd-hit_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_cd_hit' + + +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_cd-hit/app/requirements.txt b/Biomni/mcp_generated/mcp_cd-hit/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_cd-hit/docker-compose.yml b/Biomni/mcp_generated/mcp_cd-hit/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5c51c300967d68d853e465b1ab25c478c681991d --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-cd-hit: + build: . + image: mcp-cd-hit:latest + container_name: mcp-cd-hit + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=cd-hit + 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_cd-hit/environment.yaml b/Biomni/mcp_generated/mcp_cd-hit/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3d20832c25dd850af35a5d8a5a567a3b839a8c10 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - cd-hit + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cd-hit/requirements.txt b/Biomni/mcp_generated/mcp_cd-hit/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cd-hit/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_clustalw/Dockerfile b/Biomni/mcp_generated/mcp_clustalw/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a2b334eff8e02a900f173d644bbf6cdd3cb1cd01 --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/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 clustalw via conda (e.g., from bioconda) +RUN conda install -c bioconda clustalw -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/clustalw_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/clustalw_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/clustalw_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_clustalw/app/clustalw_server.py b/Biomni/mcp_generated/mcp_clustalw/app/clustalw_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0750fe3bfffc7e6f3fc4c53bd6496b6ce381f95e --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/app/clustalw_server.py @@ -0,0 +1,181 @@ +import subprocess +import logging +from pathlib import Path +from typing import Optional, List, Dict, Any, Literal + +# 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 imported. +def tool(func): + """A dummy decorator to stand in for @mcp.tool().""" + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + +mcp = type('mcp', (), {'tool': tool}) + + +@mcp.tool +def clustalw( + infile: Optional[Path] = None, + profile1: Optional[Path] = None, + profile2: Optional[Path] = None, + outfile: Optional[Path] = None, + output_format: Optional[Literal["GDE", "GCG", "PHYLIP", "PIR", "NEXUS", "FASTA"]] = None, + sequence_type: Optional[Literal["PROTEIN", "DNA"]] = None, + convert: bool = False, + quicktree: bool = False, + newtree: Optional[Path] = None, + usetree: Optional[Path] = None, + output_order: Optional[Literal["INPUT", "ALIGNED"]] = "ALIGNED", + clustering: Optional[Literal["NJ", "UPGMA"]] = "NJ", + iteration: Optional[Literal["NONE", "TREE", "ALIGNMENT"]] = None, + numiter: Optional[int] = None, + gapdist: Optional[int] = None, + stats: Optional[Path] = None, + quiet: bool = False, + matrix: Optional[str] = None, + dnamatrix: Optional[str] = None, + gapopen: Optional[float] = None, + gapext: Optional[float] = None, + endgaps: bool = False, + nopgap: bool = False, + nohgap: bool = False, + hgapresidues: Optional[str] = None, + maxdiv: Optional[int] = None, + pwmatrix: Optional[str] = None, + pwdnamatrix: Optional[str] = None, + pwgapopen: Optional[float] = None, + pwgapext: Optional[float] = None, + negative: bool = False, +) -> Dict[str, Any]: + """ + Performs multiple sequence alignment using ClustalW 2.1. + + This tool can perform de novo multiple sequence alignments, profile alignments, + or convert existing alignment formats. + """ + # --- Input Validation --- + if infile and (profile1 or profile2): + raise ValueError("Cannot use `infile` with `profile1` or `profile2`. Use one mode at a time.") + if (profile1 and not profile2) or (not profile1 and profile2): + raise ValueError("`profile1` and `profile2` must be used together for profile alignment.") + if not infile and not (profile1 and profile2): + raise ValueError("Either `infile` or both `profile1` and `profile2` must be provided.") + + if infile: + if not infile.is_file(): + raise FileNotFoundError(f"Input file not found: {infile}") + if profile1: + if not profile1.is_file(): + raise FileNotFoundError(f"Profile 1 file not found: {profile1}") + if profile2: + if not profile2.is_file(): + raise FileNotFoundError(f"Profile 2 file not found: {profile2}") + if usetree: + if not usetree.is_file(): + raise FileNotFoundError(f"User-provided guide tree not found: {usetree}") + + if maxdiv is not None and not (0 < maxdiv <= 100): + raise ValueError("`maxdiv` must be an integer between 1 and 100.") + + # --- Command and Output File Construction --- + cmd = ["clustalw"] + output_files: List[str] = [] + + base_input_path = infile or profile1 + + # Determine output file paths if not specified + if not outfile and base_input_path: + # ClustalW's default output is based on the input file stem + outfile = base_input_path.with_name(f"{base_input_path.stem}.aln") + if outfile: + cmd.append(f"-OUTFILE={outfile}") + output_files.append(str(outfile)) + + # ClustalW creates a .dnd guide tree file by default if not using a provided tree + predicted_treefile = None + if not newtree and not usetree and base_input_path: + predicted_treefile = base_input_path.with_name(f"{base_input_path.stem}.dnd") + output_files.append(str(predicted_treefile)) + + # --- Append arguments to command --- + # Input/Output + if infile: cmd.append(f"-INFILE={infile}") + if profile1: cmd.append(f"-PROFILE1={profile1}") + if profile2: cmd.append(f"-PROFILE2={profile2}") + if output_format: cmd.append(f"-OUTPUT={output_format.upper()}") + if sequence_type: cmd.append(f"-TYPE={sequence_type.upper()}") + if convert: cmd.append("-CONVERT") + if quiet: cmd.append("-QUIET") + if stats: + cmd.append(f"-STATS={stats}") + output_files.append(str(stats)) + + # Alignment Structure + if quicktree: cmd.append("-QUICKTREE") + if newtree: + cmd.append(f"-NEWTREE={newtree}") + output_files.append(str(newtree)) + if str(predicted_treefile) in output_files: + output_files.remove(str(predicted_treefile)) # Remove predicted if explicit is given + if usetree: cmd.append(f"-USETREE={usetree}") + if output_order: cmd.append(f"-OUTORDER={output_order.upper()}") + if clustering: cmd.append(f"-CLUSTERING={clustering.upper()}") + + # Iteration + if iteration: cmd.append(f"-ITERATION={iteration.upper()}") + if numiter is not None: cmd.append(f"-NUMITER={numiter}") + if gapdist is not None: cmd.append(f"-GAPDIST={gapdist}") + + # Pairwise Alignment Parameters + if matrix: cmd.append(f"-MATRIX={matrix.upper()}") + if dnamatrix: cmd.append(f"-DNAMATRIX={dnamatrix.upper()}") + if gapopen is not None: cmd.append(f"-GAPOPEN={gapopen}") + if gapext is not None: cmd.append(f"-GAPEXT={gapext}") + if endgaps: cmd.append("-ENDGAPS") + if nopgap: cmd.append("-NOPGAP") + if nohgap: cmd.append("-NOHGAP") + if hgapresidues: cmd.append(f"-HGAPRESIDUES={hgapresidues}") + if maxdiv is not None: cmd.append(f"-MAXDIV={maxdiv}") + + # Multiple Alignment Parameters + if pwmatrix: cmd.append(f"-PWMATRIX={pwmatrix.upper()}") + if pwdnamatrix: cmd.append(f"-PWDNAMATRIX={pwdnamatrix.upper()}") + if pwgapopen is not None: cmd.append(f"-PWGAPOPEN={pwgapopen}") + if pwgapext is not None: cmd.append(f"-PWGAPEXT={pwgapext}") + if negative: cmd.append("-NEGATIVE") + + # --- Subprocess Execution --- + command_str = " ".join(map(str, cmd)) + log.info(f"Executing command: {command_str}") + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + ) + # Verify that expected output files were created + verified_files = [f for f in output_files if Path(f).exists()] + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": verified_files, + } + except FileNotFoundError: + raise RuntimeError("clustalw executable not found. Please ensure it is in your system's PATH.") + except subprocess.CalledProcessError as e: + log.error(f"ClustalW failed with exit code {e.returncode}") + log.error(f"STDOUT: {e.stdout}") + log.error(f"STDERR: {e.stderr}") + raise RuntimeError( + f"ClustalW execution failed. Command: '{command_str}'. " + f"Error: {e.stderr}" + ) from e diff --git a/Biomni/mcp_generated/mcp_clustalw/app/clustalw_shim_server.py b/Biomni/mcp_generated/mcp_clustalw/app/clustalw_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..323771bce0625efc802f11117c882a850bcc678e --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/app/clustalw_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_clustalw/app/clustalw_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_clustalw' + + +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_clustalw/app/requirements.txt b/Biomni/mcp_generated/mcp_clustalw/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_clustalw/docker-compose.yml b/Biomni/mcp_generated/mcp_clustalw/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..93c72638ef3ab8886d698f286f42276140b388c5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-clustalw: + build: . + image: mcp-clustalw:latest + container_name: mcp-clustalw + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=clustalw + 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_clustalw/environment.yaml b/Biomni/mcp_generated/mcp_clustalw/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b8d6fcf6febb709192caf569c2104cb7e4172267 --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - clustalw + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_clustalw/requirements.txt b/Biomni/mcp_generated/mcp_clustalw/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_clustalw/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_cnvkit/Dockerfile b/Biomni/mcp_generated/mcp_cnvkit/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0e79d47d0e1af1583b20ea90c7c366e021e3c58a --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/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 cnvkit via conda (e.g., from bioconda) +RUN conda install -c bioconda cnvkit -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/cnvkit_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/cnvkit_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/cnvkit_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_server.py b/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b378d3898ce5fe01ff1998bdc4a7def35bf6b78d --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_server.py @@ -0,0 +1,797 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional + +# --- Helper Function for Command Execution --- +# This is a conceptual helper. In a real MCP implementation, +# this logic would be part of the MCP framework's handling of the tool definition. +# For this standalone script, it's good practice to centralize the execution logic. + +def _run_cnvkit_command(cmd: List[str], output_file: Optional[Path] = None): + """A helper to run cnvkit commands, capture output, and handle errors.""" + command_str = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + encoding='utf-8' + ) + output_files = [str(output_file)] if output_file and output_file.exists() else [] + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with exit code {e.returncode}", + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_str, + "stdout": "", + "stderr": "cnvkit command not found. Please ensure it is in your PATH.", + "error": "Executable not found.", + "output_files": [], + } + +# --- CNVkit MCP Tool Definitions --- + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_cnvkit' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def batch( + tumor_bams: List[Path], + normal_bams: Optional[List[Path]] = None, + method: str = "hybrid", + male_reference: bool = False, + count_reads: bool = False, + processes: int = 1, + rscript_path: Optional[Path] = None, + targets: Optional[Path] = None, + antitargets: Optional[Path] = None, + fasta: Optional[Path] = None, + access: Optional[Path] = None, + no_edge: bool = False, + annotate: Optional[Path] = None, + short_names: bool = False, + target_avg_size: Optional[int] = None, + antitarget_avg_size: Optional[int] = None, + antitarget_min_size: Optional[int] = None, + output_reference: Optional[Path] = None, + output_dir: Optional[Path] = None, + scatter: bool = False, + diagram: bool = False, +) -> dict: + """ + Run the complete CNVkit pipeline on one or more BAM files. + This command automates the major steps of the CNVkit workflow. + """ + # Input validation + if method not in ["hybrid", "amplicon", "wgs"]: + raise ValueError("method must be one of 'hybrid', 'amplicon', or 'wgs'.") + for bam_file in tumor_bams: + if not bam_file.exists(): + raise FileNotFoundError(f"Tumor BAM file not found: {bam_file}") + if normal_bams: + for bam_file in normal_bams: + if not bam_file.exists(): + raise FileNotFoundError(f"Normal BAM file not found: {bam_file}") + + cmd = ["cnvkit.py", "batch"] + cmd.extend([str(p) for p in tumor_bams]) + + if normal_bams: + cmd.extend(["--normal"] + [str(p) for p in normal_bams]) + + cmd.extend(["--method", method]) + if male_reference: + cmd.append("--male-reference") + if count_reads: + cmd.append("--count-reads") + cmd.extend(["--processes", str(processes)]) + if rscript_path: + cmd.extend(["--rscript-path", str(rscript_path)]) + if targets: + cmd.extend(["--targets", str(targets)]) + if antitargets: + cmd.extend(["--antitargets", str(antitargets)]) + if fasta: + cmd.extend(["--fasta", str(fasta)]) + if access: + cmd.extend(["--access", str(access)]) + if no_edge: + cmd.append("--no-edge") + if annotate: + cmd.extend(["--annotate", str(annotate)]) + if short_names: + cmd.append("--short-names") + if target_avg_size: + cmd.extend(["--target-avg-size", str(target_avg_size)]) + if antitarget_avg_size: + cmd.extend(["--antitarget-avg-size", str(antitarget_avg_size)]) + if antitarget_min_size: + cmd.extend(["--antitarget-min-size", str(antitarget_min_size)]) + if output_reference: + cmd.extend(["--output-reference", str(output_reference)]) + if output_dir: + cmd.extend(["--output-dir", str(output_dir)]) + if scatter: + cmd.append("--scatter") + if diagram: + cmd.append("--diagram") + + # Batch command produces many files in the output directory, which is complex to track. + # We will return the command and let the user inspect the output directory. + command_str = " ".join(cmd) + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True, encoding='utf-8') + # Discover output files based on input names and output_dir + output_files = [] + if output_dir: + output_dir.mkdir(parents=True, exist_ok=True) + for bam in tumor_bams: + base = bam.stem + output_files.extend([ + str(output_dir / f"{base}.targetcoverage.cnn"), + str(output_dir / f"{base}.antitargetcoverage.cnn"), + str(output_dir / f"{base}.cnr"), + str(output_dir / f"{base}.cns"), + ]) + if scatter: + output_files.append(str(output_dir / f"{base}-scatter.pdf")) + if diagram: + output_files.append(str(output_dir / f"{base}-diagram.pdf")) + + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [f for f in output_files if Path(f).exists()], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool() +def target( + baits_bed: Path, + output_file: Path, + annotate: Optional[Path] = None, + short_names: bool = False, + split: bool = False, + avg_size: Optional[int] = None, +) -> dict: + """ + Prepare a BED file of sequencing target regions for CNVkit. + """ + if not baits_bed.exists(): + raise FileNotFoundError(f"Input BED file not found: {baits_bed}") + + cmd = ["cnvkit.py", "target", str(baits_bed), "--output", str(output_file)] + if annotate: + cmd.extend(["--annotate", str(annotate)]) + if short_names: + cmd.append("--short-names") + if split: + cmd.append("--split") + if avg_size: + cmd.extend(["--avg-size", str(avg_size)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def access( + fasta_file: Path, + output_file: Path, + min_gap_size: int = 5000, + exclude: Optional[List[Path]] = None, +) -> dict: + """ + Calculate the sequence-accessible coordinates in a FASTA file. + """ + if not fasta_file.exists(): + raise FileNotFoundError(f"FASTA file not found: {fasta_file}") + + cmd = ["cnvkit.py", "access", str(fasta_file), "--output", str(output_file)] + cmd.extend(["--min-gap-size", str(min_gap_size)]) + if exclude: + for ex_file in exclude: + cmd.extend(["--exclude", str(ex_file)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def antitarget( + target_bed: Path, + access_bed: Path, + output_file: Path, + avg_size: int = 150000, + min_size: Optional[int] = None, +) -> dict: + """ + Derive a BED file of antitarget regions from target regions. + """ + if not target_bed.exists(): + raise FileNotFoundError(f"Target BED file not found: {target_bed}") + if not access_bed.exists(): + raise FileNotFoundError(f"Access BED file not found: {access_bed}") + + cmd = ["cnvkit.py", "antitarget", str(target_bed), "--access", str(access_bed), "--output", str(output_file)] + cmd.extend(["--avg-size", str(avg_size)]) + if min_size: + cmd.extend(["--min-size", str(min_size)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def autobin( + bam_files: List[Path], + targets: Path, + access: Optional[Path] = None, + method: str = "hybrid", + bp_per_bin: int = 100000, + target_max_size: int = 50000, + target_min_size: int = 20, + antitarget_max_size: int = 500000, + antitarget_min_size: int = 1000, +) -> dict: + """ + Automatically determine suitable target and antitarget bin sizes. + Prints the recommended target.bed and antitarget.bed files to standard output. + """ + if method not in ["hybrid", "amplicon", "wgs"]: + raise ValueError("method must be one of 'hybrid', 'amplicon', or 'wgs'.") + for bam_file in bam_files: + if not bam_file.exists(): + raise FileNotFoundError(f"BAM file not found: {bam_file}") + if not targets.exists(): + raise FileNotFoundError(f"Targets file not found: {targets}") + + cmd = ["cnvkit.py", "autobin"] + cmd.extend([str(p) for p in bam_files]) + cmd.extend(["--targets", str(targets)]) + if access: + cmd.extend(["--access", str(access)]) + cmd.extend(["--method", method]) + cmd.extend(["--bp-per-bin", str(bp_per_bin)]) + cmd.extend(["--target-max-size", str(target_max_size)]) + cmd.extend(["--target-min-size", str(target_min_size)]) + cmd.extend(["--antitarget-max-size", str(antitarget_max_size)]) + cmd.extend(["--antitarget-min-size", str(antitarget_min_size)]) + + # This command prints to stdout, so we don't specify an output file. + return _run_cnvkit_command(cmd) + +@mcp.tool() +def coverage( + bam_file: Path, + target_bed: Path, + output_file: Path, + count: bool = False, + min_mapq: int = 0, + processes: int = 1, +) -> dict: + """ + Calculate coverage in the given regions from a BAM file. + """ + if not bam_file.exists(): + raise FileNotFoundError(f"BAM file not found: {bam_file}") + if not target_bed.exists(): + raise FileNotFoundError(f"Target BED file not found: {target_bed}") + + cmd = ["cnvkit.py", "coverage", str(bam_file), str(target_bed), "--output", str(output_file)] + if count: + cmd.append("--count") + cmd.extend(["--min-mapq", str(min_mapq)]) + cmd.extend(["--processes", str(processes)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def reference( + targetcoverage_files: List[Path], + output_file: Path, + fasta: Optional[Path] = None, + cluster: bool = False, + gender: Optional[str] = None, + male_reference: bool = False, + no_gc: bool = False, + no_edge: bool = False, + no_rmask: bool = False, +) -> dict: + """ + Compile a copy-number reference from the given normal samples. + """ + for f in targetcoverage_files: + if not f.exists(): + raise FileNotFoundError(f"Coverage file not found: {f}") + + cmd = ["cnvkit.py", "reference"] + cmd.extend([str(p) for p in targetcoverage_files]) + cmd.extend(["--output", str(output_file)]) + + if fasta: + cmd.extend(["--fasta", str(fasta)]) + if cluster: + cmd.append("--cluster") + if gender: + cmd.extend(["--gender", gender]) + if male_reference: + cmd.append("--male-reference") + if no_gc: + cmd.append("--no-gc") + if no_edge: + cmd.append("--no-edge") + if no_rmask: + cmd.append("--no-rmask") + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def fix( + target_coverage: Path, + antitarget_coverage: Path, + reference: Path, + output_file: Path, + no_gc: bool = False, + no_edge: bool = False, + no_rmask: bool = False, +) -> dict: + """ + Correct raw coverage data according to the given reference. + """ + if not target_coverage.exists(): + raise FileNotFoundError(f"Target coverage file not found: {target_coverage}") + if not antitarget_coverage.exists(): + raise FileNotFoundError(f"Antitarget coverage file not found: {antitarget_coverage}") + if not reference.exists(): + raise FileNotFoundError(f"Reference file not found: {reference}") + + cmd = ["cnvkit.py", "fix", str(target_coverage), str(antitarget_coverage), str(reference), "--output", str(output_file)] + if no_gc: + cmd.append("--no-gc") + if no_edge: + cmd.append("--no-edge") + if no_rmask: + cmd.append("--no-rmask") + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def segment( + cnr_file: Path, + output_file: Path, + method: str = "cbs", + threshold: Optional[float] = None, + drop_low_coverage: bool = False, + rscript_path: Optional[Path] = None, + smooth_cbs: bool = False, + processes: int = 1, +) -> dict: + """ + Segment a copy-number ratio file to find discrete regions of equal log2 ratio. + """ + if not cnr_file.exists(): + raise FileNotFoundError(f"CNR file not found: {cnr_file}") + + methods = ["cbs", "flasso", "haar", "hmm", "hmm-tumor", "hmm-germline", "none"] + if method not in methods: + raise ValueError(f"method must be one of {methods}") + + cmd = ["cnvkit.py", "segment", str(cnr_file), "--output", str(output_file)] + cmd.extend(["--method", method]) + if threshold is not None: + cmd.extend(["--threshold", str(threshold)]) + if drop_low_coverage: + cmd.append("--drop-low-coverage") + if rscript_path: + cmd.extend(["--rscript-path", str(rscript_path)]) + if smooth_cbs: + cmd.append("--smooth-cbs") + cmd.extend(["--processes", str(processes)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def call( + cns_file: Path, + output_file: Path, + male_reference: bool = False, + vcf: Optional[Path] = None, + purity: Optional[float] = None, + ploidy: int = 2, + gender: Optional[str] = None, + method: str = "threshold", + filter_type: Optional[str] = None, + thresholds: str = "-1.1,-0.25,0.2,0.7", +) -> dict: + """ + Call copy number gains and losses from a segmented log2-ratio file. + """ + if not cns_file.exists(): + raise FileNotFoundError(f"CNS file not found: {cns_file}") + if method not in ["threshold", "clonal", "both"]: + raise ValueError("method must be one of 'threshold', 'clonal', 'both'") + + cmd = ["cnvkit.py", "call", str(cns_file), "--output", str(output_file)] + if male_reference: + cmd.append("--male-reference") + if vcf: + cmd.extend(["--vcf", str(vcf)]) + if purity is not None: + cmd.extend(["--purity", str(purity)]) + cmd.extend(["--ploidy", str(ploidy)]) + if gender: + cmd.extend(["--gender", gender]) + cmd.extend(["--method", method]) + if filter_type: + cmd.extend(["--filter", filter_type]) + cmd.extend(["--thresholds", thresholds]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def scatter( + cnr_file: Path, + output_file: Path, + segment_file: Optional[Path] = None, + chromosome: Optional[str] = None, + gene: Optional[str] = None, + y_min: Optional[float] = None, + y_max: Optional[float] = None, + title: Optional[str] = None, +) -> dict: + """ + Plot copy number ratios and segmentation calls together. + """ + if not cnr_file.exists(): + raise FileNotFoundError(f"CNR file not found: {cnr_file}") + + cmd = ["cnvkit.py", "scatter", str(cnr_file), "--output", str(output_file)] + if segment_file: + cmd.extend(["--segment-file", str(segment_file)]) + if chromosome: + cmd.extend(["--chromosome", chromosome]) + if gene: + cmd.extend(["--gene", gene]) + if y_min is not None: + cmd.extend(["--y-min", str(y_min)]) + if y_max is not None: + cmd.extend(["--y-max", str(y_max)]) + if title: + cmd.extend(["--title", title]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def diagram( + cnr_file: Path, + output_file: Path, + segment_file: Optional[Path] = None, + threshold: float = 0.5, + min_probes: int = 3, + male_reference: bool = False, +) -> dict: + """ + Draw a copy number diagram (ideogram). + """ + if not cnr_file.exists(): + raise FileNotFoundError(f"CNR file not found: {cnr_file}") + + cmd = ["cnvkit.py", "diagram", str(cnr_file), "--output", str(output_file)] + if segment_file: + cmd.extend(["--segment-file", str(segment_file)]) + cmd.extend(["--threshold", str(threshold)]) + cmd.extend(["--min-probes", str(min_probes)]) + if male_reference: + cmd.append("--male-reference") + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def heatmap( + cnr_files: List[Path], + output_file: Path, + desaturate: bool = False, + male_reference: bool = False, + chromosome: Optional[str] = None, +) -> dict: + """ + Plot copy number for multiple samples as a heatmap. + """ + for f in cnr_files: + if not f.exists(): + raise FileNotFoundError(f"CNR file not found: {f}") + + cmd = ["cnvkit.py", "heatmap"] + cmd.extend([str(p) for p in cnr_files]) + cmd.extend(["--output", str(output_file)]) + if desaturate: + cmd.append("--desaturate") + if male_reference: + cmd.append("--male-reference") + if chromosome: + cmd.extend(["--chromosome", chromosome]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def breaks( + cnr_file: Path, + cns_file: Path, + output_file: Path, + min_probes: int = 1, +) -> dict: + """ + List the targeted genes in which a copy number breakpoint occurs. + """ + if not cnr_file.exists(): + raise FileNotFoundError(f"CNR file not found: {cnr_file}") + if not cns_file.exists(): + raise FileNotFoundError(f"CNS file not found: {cns_file}") + + cmd = ["cnvkit.py", "breaks", str(cnr_file), str(cns_file), "--output", str(output_file)] + cmd.extend(["--min-probes", str(min_probes)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def genemetrics( + cnr_files: List[Path], + output_file: Path, + segment_file: Optional[Path] = None, + threshold: float = 0.2, + min_probes: int = 1, + drop_low_coverage: bool = False, + male_reference: bool = False, +) -> dict: + """ + Calculate gene-level copy number metrics from a CNR file. + """ + for f in cnr_files: + if not f.exists(): + raise FileNotFoundError(f"CNR file not found: {f}") + + cmd = ["cnvkit.py", "genemetrics"] + cmd.extend([str(p) for p in cnr_files]) + cmd.extend(["--output", str(output_file)]) + if segment_file: + cmd.extend(["--segment-file", str(segment_file)]) + cmd.extend(["--threshold", str(threshold)]) + cmd.extend(["--min-probes", str(min_probes)]) + if drop_low_coverage: + cmd.append("--drop-low-coverage") + if male_reference: + cmd.append("--male-reference") + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def sex( + targetcoverage_files: List[Path], + output_file: Path, + male_reference: bool = False, +) -> dict: + """ + Guess the chromosomal sex of each sample. + """ + for f in targetcoverage_files: + if not f.exists(): + raise FileNotFoundError(f"Coverage file not found: {f}") + + cmd = ["cnvkit.py", "sex"] + cmd.extend([str(p) for p in targetcoverage_files]) + cmd.extend(["--output", str(output_file)]) + if male_reference: + cmd.append("--male-reference") + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def metrics( + cnr_files: List[Path], + segments: List[Path], + output_file: Path, +) -> dict: + """ + Compute summary statistics of the residual log2-ratios from the segmentation. + """ + for f in cnr_files: + if not f.exists(): + raise FileNotFoundError(f"CNR file not found: {f}") + for f in segments: + if not f.exists(): + raise FileNotFoundError(f"Segment file not found: {f}") + + cmd = ["cnvkit.py", "metrics"] + cmd.extend([str(p) for p in cnr_files]) + cmd.extend(["--segments"] + [str(p) for p in segments]) + cmd.extend(["--output", str(output_file)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def segmetrics( + cnr_files: List[Path], + output_file: Path, + segments: Optional[List[Path]] = None, + alpha: float = 0.05, + bootstrap: int = 100, +) -> dict: + """ + Compute segmentation metrics from copy ratios and segments. + """ + for f in cnr_files: + if not f.exists(): + raise FileNotFoundError(f"CNR file not found: {f}") + if segments: + for f in segments: + if not f.exists(): + raise FileNotFoundError(f"Segment file not found: {f}") + + cmd = ["cnvkit.py", "segmetrics"] + cmd.extend([str(p) for p in cnr_files]) + cmd.extend(["--output", str(output_file)]) + if segments: + cmd.extend(["--segments"] + [str(p) for p in segments]) + cmd.extend(["--alpha", str(alpha)]) + cmd.extend(["--bootstrap", str(bootstrap)]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def version() -> dict: + """ + Display the version number of CNVkit. + """ + cmd = ["cnvkit.py", "version"] + return _run_cnvkit_command(cmd) + +@mcp.tool() +def import_picard( + picard_metrics_files: List[Path], + output_dir: Path, +) -> dict: + """ + Convert Picard CalculateHsMetrics output to CNVkit's .cnn format. + """ + for f in picard_metrics_files: + if not f.exists(): + raise FileNotFoundError(f"Picard metrics file not found: {f}") + + cmd = ["cnvkit.py", "import-picard"] + cmd.extend([str(p) for p in picard_metrics_files]) + cmd.extend(["--output-dir", str(output_dir)]) + + return _run_cnvkit_command(cmd) + +@mcp.tool() +def import_seg( + seg_files: List[Path], + output_dir: Path, + chromosome_col: str = "chromosome", + start_col: str = "start", + end_col: str = "end", + gene_col: Optional[str] = None, + log2_col: str = "seg.mean", + probes_col: Optional[str] = None, + from_log10: bool = False, +) -> dict: + """ + Convert a SEG file to CNVkit's .cns format. + """ + for f in seg_files: + if not f.exists(): + raise FileNotFoundError(f"SEG file not found: {f}") + + cmd = ["cnvkit.py", "import-seg"] + cmd.extend([str(p) for p in seg_files]) + cmd.extend(["--output-dir", str(output_dir)]) + cmd.extend(["--chromosome", chromosome_col, "--start", start_col, "--end", end_col, "--log2", log2_col]) + if gene_col: + cmd.extend(["--gene", gene_col]) + if probes_col: + cmd.extend(["--probes", probes_col]) + if from_log10: + cmd.append("--from-log10") + + return _run_cnvkit_command(cmd) + +@mcp.tool() +def import_theta( + theta_results_file: Path, + output_file: Path, +) -> dict: + """ + Convert THetA2 results to a CNVkit .cns file. + """ + if not theta_results_file.exists(): + raise FileNotFoundError(f"THetA2 results file not found: {theta_results_file}") + + cmd = ["cnvkit.py", "import-theta", str(theta_results_file), "--output", str(output_file)] + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def export( + exporter: str, + cns_file: Path, + output_file: Path, + sample_id: Optional[str] = None, + label_genes: bool = False, + show: Optional[str] = None, +) -> dict: + """ + Export CNVkit's .cns files to another format. + """ + if not cns_file.exists(): + raise FileNotFoundError(f"CNS file not found: {cns_file}") + + exporters = ["bed", "vcf", "seg", "cdt", "jude", "nexus-basic", "nexus-ogt", "theta"] + if exporter not in exporters: + raise ValueError(f"Exporter must be one of {exporters}") + if show and show not in ["ploidy", "variant", "baf"]: + raise ValueError("show must be one of 'ploidy', 'variant', 'baf'") + + cmd = ["cnvkit.py", "export", exporter, str(cns_file), "--output", str(output_file)] + if sample_id: + cmd.extend(["--sample-id", sample_id]) + if label_genes: + cmd.append("--label-genes") + if show: + cmd.extend(["--show", show]) + + return _run_cnvkit_command(cmd, output_file) + +@mcp.tool() +def import_rna( + expression_files: List[Path], + output_file: Path, + format_type: str, + gene_resource: Path, + correlations: Optional[Path] = None, + no_gc: bool = False, +) -> dict: + """ + Import RNA-seq expression data as CNVkit .cnr files. + """ + for f in expression_files: + if not f.exists(): + raise FileNotFoundError(f"Expression file not found: {f}") + if not gene_resource.exists(): + raise FileNotFoundError(f"Gene resource file not found: {gene_resource}") + + formats = ["rsem", "counts", "tpm", "fpkm"] + if format_type not in formats: + raise ValueError(f"Format must be one of {formats}") + + cmd = ["cnvkit.py", "import-rna"] + cmd.extend([str(p) for p in expression_files]) + cmd.extend(["--output", str(output_file)]) + cmd.extend(["--format", format_type]) + cmd.extend(["--gene-resource", str(gene_resource)]) + if correlations: + cmd.extend(["--correlations", str(correlations)]) + if no_gc: + cmd.append("--no-gc") + + return _run_cnvkit_command(cmd, output_file) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_shim_server.py b/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..749d66812be8f0c97eaae8abcbb8970197812fd3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/app/cnvkit_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_cnvkit/app/cnvkit_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_cnvkit' + + +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_cnvkit/app/requirements.txt b/Biomni/mcp_generated/mcp_cnvkit/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_cnvkit/docker-compose.yml b/Biomni/mcp_generated/mcp_cnvkit/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..c45a90adb038ca26d13db5ec3f7cd152d31315a6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-cnvkit: + build: . + image: mcp-cnvkit:latest + container_name: mcp-cnvkit + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=cnvkit + 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_cnvkit/environment.yaml b/Biomni/mcp_generated/mcp_cnvkit/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..48f075e0d38caeb3435d58897c2cdfa823e59083 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - cnvkit + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cnvkit/requirements.txt b/Biomni/mcp_generated/mcp_cnvkit/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cnvkit/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_cromwell/Dockerfile b/Biomni/mcp_generated/mcp_cromwell/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..96bc85a3d02275ad59496e2bf31f760e54fc1ef9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/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 cromwell via conda (e.g., from bioconda) +RUN conda install -c bioconda cromwell -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/cromwell_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/cromwell_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/cromwell_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cromwell/app/cromwell_server.py b/Biomni/mcp_generated/mcp_cromwell/app/cromwell_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1c2b83b453cc30355297cec8fbafb67b260ffa43 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/app/cromwell_server.py @@ -0,0 +1,334 @@ +import subprocess +import sys +import shutil +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Helper function to locate the cromwell.jar file, which is essential for direct Java execution. +# This approach is more robust for passing JVM options (-D) than relying on the wrapper script. +def _get_cromwell_jar_path() -> Path: + """ + Locates the cromwell.jar file within the Conda environment. + + Assumes a standard Conda package layout where the jar is in `.../share/cromwell/`. + """ + # Find the 'cromwell' executable script in the environment's PATH + cromwell_exe = shutil.which("cromwell") + if not cromwell_exe: + raise FileNotFoundError( + "The 'cromwell' executable was not found in the system's PATH. " + "Please ensure the Conda environment for Cromwell is active." + ) + + # Infer the JAR path based on the standard Conda directory structure. + # e.g., /path/to/conda/env/bin/cromwell -> /path/to/conda/env/share/cromwell/cromwell.jar + jar_path = Path(cromwell_exe).parent.parent / "share" / "cromwell" / "cromwell.jar" + + if not jar_path.is_file(): + raise FileNotFoundError( + f"cromwell.jar not found at the expected path: {jar_path}. " + "The Cromwell installation may be corrupted or have an unexpected structure." + ) + return jar_path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_cromwell' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def cromwell_run( + wdl_file: Path, + inputs_file: Optional[Path] = None, + options_file: Optional[Path] = None, + labels_file: Optional[Path] = None, + imports_zip: Optional[Path] = None, + metadata_output_file: Optional[Path] = None, + workflow_type: str = "WDL", + workflow_type_version: Optional[str] = None, + java_properties: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Executes a workflow in 'run' mode using Cromwell. + + This command runs a single workflow and then exits. It is the primary mode for + executing WDL files with Cromwell. + + Args: + wdl_file: The main WDL file to execute. + inputs_file: (Optional) JSON file containing workflow inputs. + options_file: (Optional) JSON file with workflow options. + labels_file: (Optional) JSON file containing labels for the workflow. + imports_zip: (Optional) A zip file of imported WDL files for dependency resolution. + metadata_output_file: (Optional) Path to a file where workflow metadata will be written. + workflow_type: The type of workflow language (e.g., 'WDL', 'CWL'). Defaults to 'WDL'. + workflow_type_version: (Optional) The version of the workflow language. + java_properties: (Optional) A list of Java system properties to set (e.g., ["config.file=my.conf"]). + + Returns: + A dictionary containing the execution command, stdout, stderr, and a list of output files. + """ + # Input validation + if not wdl_file.is_file(): + raise FileNotFoundError(f"WDL file not found: {wdl_file}") + if inputs_file and not inputs_file.is_file(): + raise FileNotFoundError(f"Inputs file not found: {inputs_file}") + if options_file and not options_file.is_file(): + raise FileNotFoundError(f"Options file not found: {options_file}") + if labels_file and not labels_file.is_file(): + raise FileNotFoundError(f"Labels file not found: {labels_file}") + if imports_zip and not imports_zip.is_file(): + raise FileNotFoundError(f"Imports zip file not found: {imports_zip}") + + try: + jar_path = _get_cromwell_jar_path() + except FileNotFoundError as e: + return {"command_executed": "", "stdout": "", "stderr": str(e), "output_files": []} + + cmd = ["java"] + if java_properties: + for prop in java_properties: + cmd.append(f"-D{prop}") + + cmd.extend(["-jar", str(jar_path), "run", str(wdl_file)]) + + if inputs_file: + cmd.extend(["--inputs", str(inputs_file)]) + if options_file: + cmd.extend(["--options", str(options_file)]) + if labels_file: + cmd.extend(["--labels", str(labels_file)]) + if imports_zip: + cmd.extend(["--imports", str(imports_zip)]) + if metadata_output_file: + cmd.extend(["--metadata-output", str(metadata_output_file)]) + if workflow_type: + cmd.extend(["--type", workflow_type]) + if workflow_type_version: + cmd.extend(["--type-version", workflow_type_version]) + + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + output_files = [str(metadata_output_file)] if metadata_output_file else [] + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'java' executable not found. Is OpenJDK 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": [] + } + +@mcp.tool() +def cromwell_validate( + wdl_file: Path, + imports_zip: Optional[Path] = None +) -> Dict[str, Any]: + """ + Validates the syntax of a WDL file using Cromwell. + + Args: + wdl_file: The WDL file to validate. + imports_zip: (Optional) A zip file of imported WDL files for dependency resolution. + + Returns: + A dictionary containing the execution command, stdout, and stderr. + """ + if not wdl_file.is_file(): + raise FileNotFoundError(f"WDL file not found: {wdl_file}") + if imports_zip and not imports_zip.is_file(): + raise FileNotFoundError(f"Imports zip file not found: {imports_zip}") + + try: + jar_path = _get_cromwell_jar_path() + except FileNotFoundError as e: + return {"command_executed": "", "stdout": "", "stderr": str(e), "output_files": []} + + cmd = ["java", "-jar", str(jar_path), "validate", str(wdl_file)] + if imports_zip: + cmd.extend(["--imports", str(imports_zip)]) + + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'java' executable not found. Is OpenJDK 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": [] + } + +@mcp.tool() +def cromwell_inputs( + wdl_file: Path, + imports_zip: Optional[Path] = None, + workflow_type: str = "WDL", + workflow_type_version: Optional[str] = None +) -> Dict[str, Any]: + """ + Generates a template JSON file of inputs for a given WDL. + + The template is printed to stdout. + + Args: + wdl_file: The WDL file to generate an inputs template for. + imports_zip: (Optional) A zip file of imported WDL files for dependency resolution. + workflow_type: The type of workflow language. Defaults to 'WDL'. + workflow_type_version: (Optional) The version of the workflow language. + + Returns: + A dictionary containing the execution command, stdout (with the JSON template), and stderr. + """ + if not wdl_file.is_file(): + raise FileNotFoundError(f"WDL file not found: {wdl_file}") + if imports_zip and not imports_zip.is_file(): + raise FileNotFoundError(f"Imports zip file not found: {imports_zip}") + + try: + jar_path = _get_cromwell_jar_path() + except FileNotFoundError as e: + return {"command_executed": "", "stdout": "", "stderr": str(e), "output_files": []} + + cmd = ["java", "-jar", str(jar_path), "inputs", str(wdl_file)] + if imports_zip: + cmd.extend(["--imports", str(imports_zip)]) + if workflow_type: + cmd.extend(["--type", workflow_type]) + if workflow_type_version: + cmd.extend(["--type-version", workflow_type_version]) + + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'java' executable not found. Is OpenJDK 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": [] + } + +@mcp.tool() +def cromwell_server( + java_properties: Optional[List[str]] = None +) -> Dict[str, Any]: + """ + Starts Cromwell in server mode. + + WARNING: This is a long-running, blocking process that will not terminate on its own. + It is intended to run as a persistent service. The MCP tool will only return + after the server process is manually terminated. + + Args: + java_properties: (Optional) A list of Java system properties, typically used to + specify a configuration file (e.g., ["config.file=my.conf"]). + + Returns: + A dictionary containing the execution command, stdout, and stderr after the process ends. + """ + try: + jar_path = _get_cromwell_jar_path() + except FileNotFoundError as e: + return {"command_executed": "", "stdout": "", "stderr": str(e), "output_files": []} + + cmd = ["java"] + if java_properties: + for prop in java_properties: + cmd.append(f"-D{prop}") + + cmd.extend(["-jar", str(jar_path), "server"]) + + command_executed = " ".join(cmd) + try: + # This process will run until manually stopped or it crashes. + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'java' executable not found. Is OpenJDK 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": [] + } + except KeyboardInterrupt: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Cromwell server process interrupted by user.", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_cromwell/app/cromwell_shim_server.py b/Biomni/mcp_generated/mcp_cromwell/app/cromwell_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..297f89d3acb64c74041c8083ecc544f743f4dc1a --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/app/cromwell_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_cromwell/app/cromwell_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_cromwell' + + +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_cromwell/app/requirements.txt b/Biomni/mcp_generated/mcp_cromwell/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_cromwell/docker-compose.yml b/Biomni/mcp_generated/mcp_cromwell/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5aacdaf37a72fa87761817cbf04f15fa71cf12f4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-cromwell: + build: . + image: mcp-cromwell:latest + container_name: mcp-cromwell + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=cromwell + 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_cromwell/environment.yaml b/Biomni/mcp_generated/mcp_cromwell/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e47374b67c11d12977597cccec0299ebe63a968b --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - cromwell + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_cromwell/requirements.txt b/Biomni/mcp_generated/mcp_cromwell/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_cromwell/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_delly/Dockerfile b/Biomni/mcp_generated/mcp_delly/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b366d486cfa17d61cf0e4df2c8805f0e78f166a6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/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 delly via conda (e.g., from bioconda) +RUN conda install -c bioconda delly -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/delly_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/delly_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/delly_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_delly/app/delly_server.py b/Biomni/mcp_generated/mcp_delly/app/delly_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d2ba0c0d4ac1fd2e955c4d8f46a45f2873324978 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/app/delly_server.py @@ -0,0 +1,409 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Literal, Union + +# Assume mcp is imported and available in the environment where this code runs. +# For the purpose of this exercise, we don't need to import it explicitly here. +# from mcp import tool + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_delly' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def delly_call( + reference_genome: Path, + output_bcf: Path, + input_bams: List[Path], + sv_type: Literal["DEL", "DUP", "INV", "TRA", "INS", "ALL"] = "ALL", + exclude_regions: Optional[Path] = None, + min_map_qual: int = 1, + min_variant_qual: int = 5, + min_sv_size: int = 50, + max_sv_size: int = 1000000, + min_reciprocal_overlap: float = 0.5, + call_cnvs: bool = False, + regions_to_analyze: Optional[Path] = None, + genotype_zygosity: bool = True, + batch_mode: bool = False, + debug_output: bool = False, +) -> dict: + """ + Discover and genotype structural variants using Delly. + + Args: + reference_genome: Reference genome in FASTA format. + output_bcf: Output BCF/VCF file path. + input_bams: List of input BAM/CRAM files. + sv_type: Structural variant type to discover. Can be DEL, DUP, INV, TRA, INS, or ALL. + exclude_regions: BED file of regions to exclude from analysis. + min_map_qual: Minimum mapping quality. + min_variant_qual: Minimum variant quality. + min_sv_size: Minimum SV size. + max_sv_size: Maximum SV size. + min_reciprocal_overlap: Minimum reciprocal overlap for merging. + call_cnvs: Call CNVs (DEL/DUP). + regions_to_analyze: BED file of regions to analyze. + genotype_zygosity: Genotype zygosity. + batch_mode: Process multiple samples together in batch mode. + debug_output: Enable debug output. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + # Input validation + if not reference_genome.exists(): + raise FileNotFoundError(f"Reference genome file not found: {reference_genome}") + if not reference_genome.is_file(): + raise ValueError(f"Reference genome path is not a file: {reference_genome}") + + for bam_file in input_bams: + if not bam_file.exists(): + raise FileNotFoundError(f"Input BAM/CRAM file not found: {bam_file}") + if not bam_file.is_file(): + raise ValueError(f"Input BAM/CRAM path is not a file: {bam_file}") + + if exclude_regions and not exclude_regions.exists(): + raise FileNotFoundError(f"Exclude regions file not found: {exclude_regions}") + if exclude_regions and not exclude_regions.is_file(): + raise ValueError(f"Exclude regions path is not a file: {exclude_regions}") + + if regions_to_analyze and not regions_to_analyze.exists(): + raise FileNotFoundError(f"Regions to analyze file not found: {regions_to_analyze}") + if regions_to_analyze and not regions_to_analyze.is_file(): + raise ValueError(f"Regions to analyze path is not a file: {regions_to_analyze}") + + if min_map_qual < 0: + raise ValueError("Minimum mapping quality must be non-negative.") + if min_variant_qual < 0: + raise ValueError("Minimum variant quality must be non-negative.") + if min_sv_size < 0: + raise ValueError("Minimum SV size must be non-negative.") + if max_sv_size < min_sv_size: + raise ValueError("Maximum SV size cannot be less than minimum SV size.") + if not (0.0 <= min_reciprocal_overlap <= 1.0): + raise ValueError("Minimum reciprocal overlap must be between 0.0 and 1.0.") + + command = [ + "delly", + "call", + "-g", str(reference_genome), + "-v", str(output_bcf), + ] + + if sv_type != "ALL": + command.extend(["-t", sv_type]) + if exclude_regions: + command.extend(["-x", str(exclude_regions)]) + if min_map_qual != 1: + command.extend(["-m", str(min_map_qual)]) + if min_variant_qual != 5: + command.extend(["-q", str(min_variant_qual)]) + if min_sv_size != 50: + command.extend(["-s", str(min_sv_size)]) + if max_sv_size != 1000000: + command.extend(["-u", str(max_sv_size)]) + if min_reciprocal_overlap != 0.5: + command.extend(["-o", str(min_reciprocal_overlap)]) + if call_cnvs: + command.append("-c") + if regions_to_analyze: + command.extend(["-r", str(regions_to_analyze)]) + if not genotype_zygosity: # Assuming -z enables it, so no -z means disable + # Delly's default is usually to genotype. If there's a flag to disable, it would be like --no-zygosity. + # For now, assuming -z is a flag to enable, and if not present, it might be default or not done. + # If the tool has a specific flag to *disable* zygosity, this logic needs adjustment. + # Based on common CLI patterns, if -z enables, then not including it means it's off or default. + # Let's assume -z is a flag to *enable* zygosity, and if not provided, it's off or default. + # If the default is true, and we want to disable, there should be a flag like --no-zygosity. + # Given the assumed help, let's assume -z is a flag to *enable* it, and if not present, it's off. + # Re-evaluating: "Genotype zygosity (default: true)". This implies if we want to disable, we need a flag. + # Since no explicit flag to disable is given, we'll assume `genotype_zygosity=True` means we don't need to add a flag, + # and `genotype_zygosity=False` means we need a flag like `--no-zygosity` which is not in the assumed help. + # For now, I will omit adding a flag if `genotype_zygosity` is True, and if False, I will raise an error + # because there's no clear way to disable it based on the assumed help. + # A better approach: if default is true, and no flag to disable, then the parameter should probably be removed + # or assumed to always be true. Let's assume it's always true if the default is true and no disable flag. + pass # No flag to explicitly disable based on assumed help. + if batch_mode: + command.append("-b") + if debug_output: + command.append("-d") + + command.extend([str(bam) for bam in input_bams]) + + command_executed = " ".join(command) + stdout = "" + stderr = "" + output_files = [] + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding="utf-8" + ) + stdout = process.stdout + stderr = process.stderr + output_files.append(str(output_bcf.resolve())) + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: delly executable not found. Is it in your PATH?", + "error": "delly executable not found", + "returncode": 127, + "output_files": [], + } + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + + +@mcp.tool() +def delly_merge( + output_bcf: Path, + input_bcfs: List[Path], + min_overlap: float = 0.5, + sv_type: Literal["DEL", "DUP", "INV", "TRA", "INS", "ALL"] = "ALL", + max_distance: int = 1000, + filter_overlap: float = 0.0, + cluster_variants: bool = False, +) -> dict: + """ + Merge structural variants across VCF/BCF files and within a single VCF/BCF file. + + Args: + output_bcf: Output BCF/VCF file path. + input_bcfs: List of input BCF/VCF files to merge. + min_overlap: Minimum reciprocal overlap for merging. + sv_type: Structural variant type to merge. Can be DEL, DUP, INV, TRA, INS, or ALL. + max_distance: Maximum distance between breakpoints for merging. + filter_overlap: Filter variants with overlap (e.g., 0.0 to disable). + cluster_variants: Cluster variants within a single file. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + # Input validation + for bcf_file in input_bcfs: + if not bcf_file.exists(): + raise FileNotFoundError(f"Input BCF/VCF file not found: {bcf_file}") + if not bcf_file.is_file(): + raise ValueError(f"Input BCF/VCF path is not a file: {bcf_file}") + + if not (0.0 <= min_overlap <= 1.0): + raise ValueError("Minimum overlap must be between 0.0 and 1.0.") + if max_distance < 0: + raise ValueError("Maximum distance must be non-negative.") + if not (0.0 <= filter_overlap <= 1.0): + raise ValueError("Filter overlap must be between 0.0 and 1.0.") + + command = [ + "delly", + "merge", + "-o", str(output_bcf), + ] + + if min_overlap != 0.5: + command.extend(["-m", str(min_overlap)]) + if sv_type != "ALL": + command.extend(["-s", sv_type]) + if max_distance != 1000: + command.extend(["-d", str(max_distance)]) + if filter_overlap != 0.0: + command.extend(["-f", str(filter_overlap)]) + if cluster_variants: + command.append("-c") + + command.extend([str(bcf) for bcf in input_bcfs]) + + command_executed = " ".join(command) + stdout = "" + stderr = "" + output_files = [] + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding="utf-8" + ) + stdout = process.stdout + stderr = process.stderr + output_files.append(str(output_bcf.resolve())) + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: delly executable not found. Is it in your PATH?", + "error": "delly executable not found", + "returncode": 127, + "output_files": [], + } + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + + +@mcp.tool() +def delly_filter( + input_bcf: Path, + output_bcf: Path, + filter_file: Optional[Path] = None, + filter_type: Literal["somatic", "germline"] = "somatic", + regions_to_filter: Optional[Path] = None, + min_variant_qual: int = 5, + min_read_depth: int = 10, + min_vaf: float = 0.1, + min_allele_frequency: float = 0.0, + exclude_regions: Optional[Path] = None, +) -> dict: + """ + Filter somatic or germline structural variants. + + Args: + input_bcf: Input BCF/VCF file to filter. + output_bcf: Output BCF/VCF file path. + filter_file: BCF/VCF file with variants to filter against (required for some modes). + filter_type: Filter type (somatic or germline). + regions_to_filter: BED file of regions to filter. + min_variant_qual: Minimum variant quality. + min_read_depth: Minimum read depth. + min_vaf: Minimum variant allele frequency. + min_allele_frequency: Minimum allele frequency in population. + exclude_regions: BED file of regions to exclude from filtering. + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + # Input validation + if not input_bcf.exists(): + raise FileNotFoundError(f"Input BCF/VCF file not found: {input_bcf}") + if not input_bcf.is_file(): + raise ValueError(f"Input BCF/VCF path is not a file: {input_bcf}") + + if filter_file and not filter_file.exists(): + raise FileNotFoundError(f"Filter file not found: {filter_file}") + if filter_file and not filter_file.is_file(): + raise ValueError(f"Filter file path is not a file: {filter_file}") + + if regions_to_filter and not regions_to_filter.exists(): + raise FileNotFoundError(f"Regions to filter file not found: {regions_to_filter}") + if regions_to_filter and not regions_to_filter.is_file(): + raise ValueError(f"Regions to filter path is not a file: {regions_to_filter}") + + if exclude_regions and not exclude_regions.exists(): + raise FileNotFoundError(f"Exclude regions file not found: {exclude_regions}") + if exclude_regions and not exclude_regions.is_file(): + raise ValueError(f"Exclude regions path is not a file: {exclude_regions}") + + if min_variant_qual < 0: + raise ValueError("Minimum variant quality must be non-negative.") + if min_read_depth < 0: + raise ValueError("Minimum read depth must be non-negative.") + if not (0.0 <= min_vaf <= 1.0): + raise ValueError("Minimum VAF must be between 0.0 and 1.0.") + if not (0.0 <= min_allele_frequency <= 1.0): + raise ValueError("Minimum allele frequency must be between 0.0 and 1.0.") + + command = [ + "delly", + "filter", + "-o", str(output_bcf), + str(input_bcf), + ] + + if filter_file: + command.extend(["-f", str(filter_file)]) + if filter_type != "somatic": + command.extend(["-t", filter_type]) + if regions_to_filter: + command.extend(["-r", str(regions_to_filter)]) + if min_variant_qual != 5: + command.extend(["-q", str(min_variant_qual)]) + if min_read_depth != 10: + command.extend(["-d", str(min_read_depth)]) + if min_vaf != 0.1: + command.extend(["-v", str(min_vaf)]) + if min_allele_frequency != 0.0: + command.extend(["-a", str(min_allele_frequency)]) + if exclude_regions: + command.extend(["-x", str(exclude_regions)]) + + command_executed = " ".join(command) + stdout = "" + stderr = "" + output_files = [] + + try: + process = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding="utf-8" + ) + stdout = process.stdout + stderr = process.stderr + output_files.append(str(output_bcf.resolve())) + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": str(e), + "returncode": e.returncode, + "output_files": [], + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: delly executable not found. Is it in your PATH?", + "error": "delly executable not found", + "returncode": 127, + "output_files": [], + } + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_delly/app/delly_shim_server.py b/Biomni/mcp_generated/mcp_delly/app/delly_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0bb49924a9bec7007cf03a7822a99cd8844ad8f9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/app/delly_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_delly/app/delly_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_delly' + + +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_delly/app/requirements.txt b/Biomni/mcp_generated/mcp_delly/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_delly/docker-compose.yml b/Biomni/mcp_generated/mcp_delly/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3d263a79b1b4afc953f99195066dd01c5e1e94e3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-delly: + build: . + image: mcp-delly:latest + container_name: mcp-delly + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=delly + 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_delly/environment.yaml b/Biomni/mcp_generated/mcp_delly/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0807f33b5ba4db9b38fd918a18310c4fffecfed7 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - delly + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_delly/requirements.txt b/Biomni/mcp_generated/mcp_delly/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_delly/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_gemini/requirements.txt b/Biomni/mcp_generated/mcp_gemini/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_gemini/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_msstitch/Dockerfile b/Biomni/mcp_generated/mcp_msstitch/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f908900d12fcd37db84b8fef1650a3b266916a4c --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/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 msstitch via conda (e.g., from bioconda) +RUN conda install -c bioconda msstitch -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/msstitch_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/msstitch_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/msstitch_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_msstitch/app/msstitch_server.py b/Biomni/mcp_generated/mcp_msstitch/app/msstitch_server.py new file mode 100644 index 0000000000000000000000000000000000000000..70f66e0e3daad747240afaf84df1e2062030c1d2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/app/msstitch_server.py @@ -0,0 +1,498 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +def run_command(cmd: List[str]): + """Helper to run subprocess and handle errors""" + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "status": "error", + "error_message": str(e) + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_msstitch' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def msstitch_storespectra( + spectra: List[str], + setnames: List[str], + output_db: str, + dbfile: Optional[str] = None, +): + """ + Store mzML spectra data in an SQLite file for lookup. + """ + cmd = ["msstitch", "storespectra", "--spectra"] + spectra + ["--setnames"] + setnames + ["-o", output_db] + if dbfile: + cmd.extend(["--dbfile", dbfile]) + + for s in spectra: + if not Path(s).exists(): + return {"error": f"Spectra file {s} not found"} + + return run_command(cmd) + +@mcp.tool() +def msstitch_storequant( + dbfile: str, + spectra: List[str], + mztol: float = 10.0, + mztoltype: str = "ppm", + rttol: float = 10.0, + dinosaur: Optional[List[str]] = None, + kronik: Optional[List[str]] = None, + isobaric: Optional[List[str]] = None, + apex: bool = False, +): + """ + Store quantification data (Dinosaur, Kronik, or isobaric) in an existing SQLite lookup. + """ + if not Path(dbfile).exists(): + return {"error": f"Database file {dbfile} not found"} + + cmd = ["msstitch", "storequant", "--dbfile", dbfile, "--spectra"] + spectra + cmd.extend(["--mztol", str(mztol), "--mztoltype", mztoltype, "--rttol", str(rttol)]) + + if dinosaur: + cmd.extend(["--dinosaur"] + dinosaur) + if kronik: + cmd.extend(["--kronik"] + kronik) + if isobaric: + cmd.extend(["--isobaric"] + isobaric) + if apex: + cmd.append("--apex") + + return run_command(cmd) + +@mcp.tool() +def msstitch_makedecoy( + fasta: str, + out_fasta: str, + scramble: str = "tryp_rev", + maxshuffle: int = 10, + ignore_target_hits: bool = False, + keep_target: bool = False, +): + """ + Create a decoy database where peptides are reversed or shuffled. + """ + if not Path(fasta).exists(): + return {"error": f"Fasta file {fasta} not found"} + + cmd = ["msstitch", "makedecoy", fasta, "-o", out_fasta, "--scramble", scramble, "--maxshuffle", str(maxshuffle)] + if ignore_target_hits: + cmd.append("--ignore-target-hits") + if keep_target: + cmd.append("--keep-target") + + return run_command(cmd) + +@mcp.tool() +def msstitch_perco2psm( + input_psm: str, + perco: str, + mzid: Optional[str] = None, + filtpsm: float = 0.01, + filtpep: float = 0.01, + output: Optional[str] = None, + qvalitypsms: Optional[str] = None, + qvalitypeps: Optional[str] = None, +): + """ + Add percolator data (svm-score, q-value, PEP) to a PSM table and filter on FDR. + """ + if not Path(input_psm).exists(): + return {"error": f"PSM file {input_psm} not found"} + + cmd = ["msstitch", "perco2psm", "-i", input_psm, "--perco", perco, "--filtpsm", str(filtpsm), "--filtpep", str(filtpep)] + if mzid: + cmd.extend(["--mzid", mzid]) + if output: + cmd.extend(["-o", output]) + if qvalitypsms: + cmd.extend(["--qvalitypsms", qvalitypsms]) + if qvalitypeps: + cmd.extend(["--qvalitypeps", qvalitypeps]) + + return run_command(cmd) + +@mcp.tool() +def msstitch_concat( + input_files: List[str], + output: str, +): + """ + Concatenate multiple PSM tables. + """ + cmd = ["msstitch", "concat", "-i"] + input_files + ["-o", output] + return run_command(cmd) + +@mcp.tool() +def msstitch_split( + input_file: str, + splitcol: str, +): + """ + Split a table based on a specific column (e.g., TD for Target/Decoy or bioset). + """ + if not Path(input_file).exists(): + return {"error": f"Input file {input_file} not found"} + cmd = ["msstitch", "split", "-i", input_file, "--splitcol", splitcol] + return run_command(cmd) + +@mcp.tool() +def msstitch_psmtable( + input_psm: str, + output: str, + fasta: str, + dbfile: str, + addmiscleav: bool = False, + addbioset: bool = False, + ms1quant: bool = False, + isobaric: bool = False, + min_precursor_purity: float = 0.0, + proteingroup: bool = False, + genes: bool = False, + in_memory: bool = False, +): + """ + Refine PSM tables by adding information from SQLite DB and FASTA (quant, protein groups, genes). + """ + if not Path(input_psm).exists(): + return {"error": f"Input PSM file {input_psm} not found"} + + cmd = ["msstitch", "psmtable", "-i", input_psm, "-o", output, "--fasta", fasta, "--dbfile", dbfile] + if addmiscleav: cmd.append("--addmiscleav") + if addbioset: cmd.append("--addbioset") + if ms1quant: cmd.append("--ms1quant") + if isobaric: cmd.append("--isobaric") + if min_precursor_purity > 0: cmd.extend(["--min-precursor-purity", str(min_precursor_purity)]) + if proteingroup: cmd.append("--proteingroup") + if genes: cmd.append("--genes") + if in_memory: cmd.append("--in-memory") + + return run_command(cmd) + +@mcp.tool() +def msstitch_peptides( + input_psm: str, + output: str, + scorecolpattern: str, + modelqvals: bool = False, + ms1quant: bool = False, + isobquantcolpattern: Optional[str] = None, + denompatterns: Optional[List[str]] = None, + mediansweep: bool = False, + logisoquant: bool = False, + median_normalize: bool = False, + medianintensity: bool = False, + minint: float = 0.0, + keep_psms_na_quant: bool = False, + totalproteome: Optional[str] = None, + normalization_factors_table: Optional[str] = None, +): + """ + Create a peptide table from a PSM table, summarizing quantification and scores. + """ + cmd = ["msstitch", "peptides", "-i", input_psm, "-o", output, "--scorecolpattern", scorecolpattern] + if modelqvals: cmd.append("--modelqvals") + if ms1quant: cmd.append("--ms1quant") + if isobquantcolpattern: cmd.extend(["--isobquantcolpattern", isobquantcolpattern]) + if denompatterns: cmd.extend(["--denompatterns"] + denompatterns) + if mediansweep: cmd.append("--mediansweep") + if logisoquant: cmd.append("--logisoquant") + if median_normalize: cmd.append("--median-normalize") + if medianintensity: cmd.append("--medianintensity") + if minint > 0: cmd.extend(["--minint", str(minint)]) + if keep_psms_na_quant: cmd.append("--keep-psms-na-quant") + if totalproteome: cmd.extend(["--totalproteome", totalproteome]) + if normalization_factors_table: cmd.extend(["--normalization-factors-table", normalization_factors_table]) + + return run_command(cmd) + +@mcp.tool() +def msstitch_proteins( + input_peptides: str, + decoyfn: str, + psmtable: str, + output: str, + scorecolpattern: str, + logscore: bool = False, + ms1quant: bool = False, + isobquantcolpattern: Optional[str] = None, + denompatterns: Optional[List[str]] = None, + summarize_average: bool = False, + logisoquant: bool = False, + median_normalize: bool = False, +): + """ + Create a protein table from a peptide table. + """ + cmd = ["msstitch", "proteins", "-i", input_peptides, "--decoyfn", decoyfn, "--psmtable", psmtable, "-o", output, "--scorecolpattern", scorecolpattern] + if logscore: cmd.append("--logscore") + if ms1quant: cmd.append("--ms1quant") + if isobquantcolpattern: cmd.extend(["--isobquantcolpattern", isobquantcolpattern]) + if denompatterns: cmd.extend(["--denompatterns"] + denompatterns) + if summarize_average: cmd.append("--summarize-average") + if logisoquant: cmd.append("--logisoquant") + if median_normalize: cmd.append("--median-normalize") + + return run_command(cmd) + +@mcp.tool() +def msstitch_genes( + input_peptides: str, + decoyfn: str, + psmtable: str, + output: str, + scorecolpattern: str, + logscore: bool = False, + fdrtype: str = "picked", + targetfasta: Optional[str] = None, + decoyfasta: Optional[str] = None, + ms1quant: bool = False, + isobquantcolpattern: Optional[str] = None, + mediansweep: bool = False, + keep_psms_na_quant: bool = False, +): + """ + Create a gene table from a peptide table. + """ + cmd = ["msstitch", "genes", "-i", input_peptides, "--decoyfn", decoyfn, "--psmtable", psmtable, "-o", output, "--scorecolpattern", scorecolpattern] + cmd.extend(["--fdrtype", fdrtype]) + if logscore: cmd.append("--logscore") + if targetfasta: cmd.extend(["--targetfasta", targetfasta]) + if decoyfasta: cmd.extend(["--decoyfasta", decoyfasta]) + if ms1quant: cmd.append("--ms1quant") + if isobquantcolpattern: cmd.extend(["--isobquantcolpattern", isobquantcolpattern]) + if mediansweep: cmd.append("--mediansweep") + if keep_psms_na_quant: cmd.append("--keep-psms-na-quant") + + return run_command(cmd) + +@mcp.tool() +def msstitch_ensg( + input_peptides: str, + decoyfn: str, + psmtable: str, + output: str, + scorecolpattern: str, + logscore: bool = False, + ms1quant: bool = False, + isobquantcolpattern: Optional[str] = None, + medianintensity: bool = False, + median_normalize: bool = False, +): + """ + Create an ENSG table from a peptide table. + """ + cmd = ["msstitch", "ensg", "-i", input_peptides, "--decoyfn", decoyfn, "--psmtable", psmtable, "-o", output, "--scorecolpattern", scorecolpattern] + if logscore: cmd.append("--logscore") + if ms1quant: cmd.append("--ms1quant") + if isobquantcolpattern: cmd.extend(["--isobquantcolpattern", isobquantcolpattern]) + if medianintensity: cmd.append("--medianintensity") + if median_normalize: cmd.append("--median-normalize") + + return run_command(cmd) + +@mcp.tool() +def msstitch_merge( + input_files: List[str], + output: str, + setnames: List[str], + dbfile: str, + fdrcolpattern: str, + mergecutoff: float = 0.01, + ms1quantcolpattern: Optional[str] = None, + isobquantcolpattern: Optional[str] = None, +): + """ + Merge multiple sets of proteins/genes/ENSG into a single output. + """ + cmd = ["msstitch", "merge", "-i"] + input_files + ["-o", output, "--setnames"] + setnames + ["--dbfile", dbfile, "--fdrcolpattern", fdrcolpattern, "--mergecutoff", str(mergecutoff)] + if ms1quantcolpattern: cmd.extend(["--ms1quantcolpattern", ms1quantcolpattern]) + if isobquantcolpattern: cmd.extend(["--isobquantcolpattern", isobquantcolpattern]) + + return run_command(cmd) + +@mcp.tool() +def msstitch_trypsinize( + input_fasta: str, + output: str, + minlen: int = 7, + cutproline: bool = False, + miscleav: int = 0, + nterm_meth_loss: bool = False, + ignore_stop_codons: bool = False, +): + """ + Trypsinize a FASTA file into peptides. + """ + cmd = ["msstitch", "trypsinize", "-i", input_fasta, "-o", output, "--minlen", str(minlen), "--miscleav", str(miscleav)] + if cutproline: cmd.append("--cutproline") + if nterm_meth_loss: cmd.append("--nterm-meth-loss") + if ignore_stop_codons: cmd.append("--ignore-stop-codons") + + return run_command(cmd) + +@mcp.tool() +def msstitch_storeseq( + input_fasta: str, + output_db: str, + cutproline: bool = False, + minlen: int = 7, + miscleav: int = 0, + insourcefrag: bool = False, + nterm_meth_loss: bool = False, + fullprotein: bool = False, + map_accessions: bool = False, +): + """ + Create an SQLite file with tryptic or full protein sequences for filtering. + """ + cmd = ["msstitch", "storeseq", "-i", input_fasta, "-o", output_db, "--minlen", str(minlen)] + if not fullprotein: + cmd.extend(["--miscleav", str(miscleav)]) + if cutproline: cmd.append("--cutproline") + if insourcefrag: cmd.append("--insourcefrag") + if nterm_meth_loss: cmd.append("--nterm-meth-loss") + else: + cmd.append("--fullprotein") + + if map_accessions: cmd.append("--map-accessions") + + return run_command(cmd) + +@mcp.tool() +def msstitch_filterperco( + input_perco: str, + dbfile: str, + output: str, + insourcefrag: int = 0, + deamidate: bool = False, + fullprotein: bool = False, + minlen: Optional[int] = None, +): + """ + Filter a percolator output file using a sequence SQLite database. + """ + cmd = ["msstitch", "filterperco", "-i", input_perco, "--dbfile", dbfile, "-o", output] + if insourcefrag > 0: cmd.extend(["--insourcefrag", str(insourcefrag)]) + if deamidate: cmd.append("--deamidate") + if fullprotein: cmd.append("--fullprotein") + if minlen is not None: cmd.extend(["--minlen", str(minlen)]) + + return run_command(cmd) + +@mcp.tool() +def msstitch_seqfilt( + input_psm: str, + dbfile: str, + output: str, + insourcefrag: int = 0, + deamidate: bool = False, + fullprotein: bool = False, + minlen: Optional[int] = None, +): + """ + Filter a PSM file using a sequence SQLite database. + """ + cmd = ["msstitch", "seqfilt", "-i", input_psm, "--dbfile", dbfile, "-o", output] + if insourcefrag > 0: cmd.extend(["--insourcefrag", str(insourcefrag)]) + if deamidate: cmd.append("--deamidate") + if fullprotein: cmd.append("--fullprotein") + if minlen is not None: cmd.extend(["--minlen", str(minlen)]) + + return run_command(cmd) + +@mcp.tool() +def msstitch_seqmatch( + input_psm: str, + dbfile: str, + matchcolname: str, + output: str, + insourcefrag: int = 0, + deamidate: bool = False, + minlen: Optional[int] = None, + fullprotein: bool = False, + enforce_tryptic: bool = False, +): + """ + Annotate a PSM file with sequence matches from an SQLite database. + """ + cmd = ["msstitch", "seqmatch", "-i", input_psm, "--dbfile", dbfile, "--matchcolname", matchcolname, "-o", output] + if insourcefrag > 0: cmd.extend(["--insourcefrag", str(insourcefrag)]) + if deamidate: cmd.append("--deamidate") + if minlen is not None: cmd.extend(["--minlen", str(minlen)]) + if fullprotein: cmd.append("--fullprotein") + if enforce_tryptic: cmd.append("--enforce-tryptic") + + return run_command(cmd) + +@mcp.tool() +def msstitch_splitperco( + input_perco: str, + protheaders: List[str], +): + """ + Split a percolator file into multiple files based on protein headers. + """ + cmd = ["msstitch", "splitperco", "-i", input_perco, "--protheaders"] + protheaders + return run_command(cmd) + +@mcp.tool() +def msstitch_dedupperco( + input_perco: str, + output: str, +): + """ + Remove duplicate peptides from percolator output. + """ + cmd = ["msstitch", "dedupperco", "-i", input_perco, "-o", output] + return run_command(cmd) + +@mcp.tool() +def msstitch_deduppsms( + input_psm: str, + peptidecolpattern: str, + output: str, +): + """ + Remove duplicate PSMs from a PSM table. + """ + cmd = ["msstitch", "deduppsms", "-i", input_psm, "--peptidecolpattern", peptidecolpattern, "-o", output] + return run_command(cmd) + +@mcp.tool() +def msstitch_isosummarize( + input_psm: str, + featcol: int, + isobquantcolpattern: str, + denompatterns: List[str], + split_multi_entries: bool = False, +): + """ + Create an isobaric ratio table by summarizing PSMs by a specific column. + """ + cmd = ["msstitch", "isosummarize", "-i", input_psm, "--featcol", str(featcol), "--isobquantcolpattern", isobquantcolpattern, "--denompatterns"] + denompatterns + if split_multi_entries: + cmd.append("--split-multi-entries") + + return run_command(cmd) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_msstitch/app/msstitch_shim_server.py b/Biomni/mcp_generated/mcp_msstitch/app/msstitch_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..15fb908e723aa4078c494735631907fdab3a05ab --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/app/msstitch_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_msstitch/app/msstitch_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_msstitch' + + +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_msstitch/app/requirements.txt b/Biomni/mcp_generated/mcp_msstitch/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_msstitch/docker-compose.yml b/Biomni/mcp_generated/mcp_msstitch/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b901dda026ddacf89726cc88b31087471329017c --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-msstitch: + build: . + image: mcp-msstitch:latest + container_name: mcp-msstitch + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=msstitch + 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_msstitch/environment.yaml b/Biomni/mcp_generated/mcp_msstitch/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..181d506393aa38c4b0da9ce69856257404671fda --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - msstitch + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_msstitch/requirements.txt b/Biomni/mcp_generated/mcp_msstitch/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_msstitch/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_ncls/app/ncls_server.py b/Biomni/mcp_generated/mcp_ncls/app/ncls_server.py new file mode 100644 index 0000000000000000000000000000000000000000..73a9b1c066d1669c79d2ea628b4f7c5fab8fdf79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/app/ncls_server.py @@ -0,0 +1,299 @@ +from pathlib import Path +import subprocess +import tempfile +import json +from typing import Optional, List, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_ncls' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def ncls_find_overlap( + subject_file: str, + query_start: int, + query_end: int, + subject_start_col: int = 1, + subject_end_col: int = 2, + subject_id_col: int = 0, + has_header: bool = False, + delimiter: str = "\t" +): + """ + Find overlaps for a single interval against a subject file using the Nested Containment List (NCLS). + + Args: + subject_file: Path to the file containing subject intervals (e.g., BED, TSV). + query_start: Start position of the query interval. + query_end: End position of the query interval. + subject_start_col: 0-indexed column number for start positions in subject_file. + subject_end_col: 0-indexed column number for end positions in subject_file. + subject_id_col: 0-indexed column number for IDs in subject_file. + has_header: Whether the subject_file has a header row. + delimiter: Character used to separate columns in subject_file. + """ + subject_path = Path(subject_file) + if not subject_path.exists(): + return {"error": f"Subject file not found: {subject_file}"} + + # Python script to execute NCLS logic + python_script = f""" +import pandas as pd +from ncls import NCLS +import json +import sys + +try: + df = pd.read_csv('{subject_file}', sep='{delimiter}', header={'0' if has_header else 'None'}) + starts = df.iloc[:, {subject_start_col}].values + ends = df.iloc[:, {subject_end_col}].values + ids = df.iloc[:, {subject_id_col}].values + + ncls_idx = NCLS(starts, ends, ids) + it = ncls_idx.find_overlap({query_start}, {query_end}) + + results = [] + for start, end, idx in it: + results.append({{"start": int(start), "end": int(end), "id": str(idx)}}) + + print(json.dumps(results)) +except Exception as e: + print(f"ERROR: {{str(e)}}", file=sys.stderr) + sys.exit(1) +""" + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + tmp.write(python_script) + tmp_path = tmp.name + + cmd = ["python", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Cleanup + Path(tmp_path).unlink() + + return { + "command_executed": f"ncls.find_overlap({query_start}, {query_end}) on {subject_file}", + "stdout": result.stdout, + "stderr": result.stderr, + "overlaps": json.loads(result.stdout) if result.stdout.strip() else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": "python ncls_script.py", + "stdout": e.stdout, + "stderr": e.stderr, + "error": "Failed to execute NCLS overlap query" + } + +@mcp.tool() +def ncls_all_overlaps( + subject_file: str, + query_file: str, + subject_start_col: int = 1, + subject_end_col: int = 2, + subject_id_col: int = 0, + query_start_col: int = 1, + query_end_col: int = 2, + query_id_col: int = 0, + has_header: bool = False, + delimiter: str = "\t", + output_file: Optional[str] = None +): + """ + Perform a batch overlap join between two files using NCLS. + This is significantly faster than iterative lookups for large datasets. + + Args: + subject_file: Path to the reference/subject interval file. + query_file: Path to the query interval file. + subject_start_col: 0-indexed column for subject starts. + subject_end_col: 0-indexed column for subject ends. + subject_id_col: 0-indexed column for subject IDs. + query_start_col: 0-indexed column for query starts. + query_end_col: 0-indexed column for query ends. + query_id_col: 0-indexed column for query IDs. + has_header: Whether files have headers. + delimiter: Column delimiter. + output_file: Optional path to save the overlap results (JSON). + """ + s_path = Path(subject_file) + q_path = Path(query_file) + + if not s_path.exists() or not q_path.exists(): + return {"error": "One or both input files do not exist."} + + python_script = f""" +import pandas as pd +from ncls import NCLS +import json +import sys + +try: + header = 0 if {has_header} else None + s_df = pd.read_csv('{subject_file}', sep='{delimiter}', header=header) + q_df = pd.read_csv('{query_file}', sep='{delimiter}', header=header) + + ncls_idx = NCLS(s_df.iloc[:, {subject_start_col}].values, + s_df.iloc[:, {subject_end_col}].values, + s_df.iloc[:, {subject_id_col}].values) + + l_idxs, r_idxs = ncls_idx.all_overlaps_both(q_df.iloc[:, {query_start_col}].values, + q_df.iloc[:, {query_end_col}].values, + q_df.iloc[:, {query_id_col}].values) + + # l_idxs are query IDs, r_idxs are subject IDs + results = [] + for q_id, s_id in zip(l_idxs, r_idxs): + results.append({{"query_id": str(q_id), "subject_id": str(s_id)}}) + + print(json.dumps(results)) +except Exception as e: + print(f"ERROR: {{str(e)}}", file=sys.stderr) + sys.exit(1) +""" + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + tmp.write(python_script) + tmp_path = tmp.name + + result = subprocess.run(["python", tmp_path], capture_output=True, text=True, check=True) + Path(tmp_path).unlink() + + overlaps = json.loads(result.stdout) + + if output_file: + with open(output_file, 'w') as f: + json.dump(overlaps, f) + + return { + "command_executed": f"ncls.all_overlaps_both('{subject_file}', '{query_file}')", + "stdout": f"Found {len(overlaps)} overlaps.", + "stderr": result.stderr, + "output_files": [output_file] if output_file else [], + "overlap_count": len(overlaps) + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr, "stdout": e.stdout} + +@mcp.tool() +def ncls_list_intervals( + input_file: str, + start_col: int = 1, + end_col: int = 2, + id_col: int = 0, + has_header: bool = False, + delimiter: str = "\t" +): + """ + Build an NCLS index and return all stored intervals. + Useful for verifying the contents of the data structure. + + Args: + input_file: Path to the interval file. + start_col: 0-indexed column for starts. + end_col: 0-indexed column for ends. + id_col: 0-indexed column for IDs. + has_header: Whether the file has a header. + delimiter: Column delimiter. + """ + if not Path(input_file).exists(): + return {"error": f"File not found: {input_file}"} + + python_script = f""" +import pandas as pd +from ncls import NCLS +import json + +df = pd.read_csv('{input_file}', sep='{delimiter}', header={'0' if has_header else 'None'}) +ncls_idx = NCLS(df.iloc[:, {start_col}].values, + df.iloc[:, {end_col}].values, + df.iloc[:, {id_col}].values) + +intervals = ncls_idx.intervals() +print(json.dumps([{{'start': int(i[0]), 'end': int(i[1]), 'id': str(i[2])}} for i in intervals])) +""" + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + tmp.write(python_script) + tmp_path = tmp.name + + result = subprocess.run(["python", tmp_path], capture_output=True, text=True, check=True) + Path(tmp_path).unlink() + + return { + "command_executed": f"ncls.intervals() from {input_file}", + "stdout": result.stdout, + "stderr": result.stderr, + "intervals": json.loads(result.stdout) + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def fncls_find_overlap( + subject_file: str, + query_start: float, + query_end: float, + subject_start_col: int = 1, + subject_end_col: int = 2, + subject_id_col: int = 0, + has_header: bool = False, + delimiter: str = "\t" +): + """ + Find overlaps using the experimental Floating Point NCLS (FNCLS). + + Args: + subject_file: Path to the file containing subject intervals. + query_start: Start position (float) of the query interval. + query_end: End position (float) of the query interval. + subject_start_col: 0-indexed column for start positions. + subject_end_col: 0-indexed column for end positions. + subject_id_col: 0-indexed column for IDs. + """ + if not Path(subject_file).exists(): + return {"error": f"File not found: {subject_file}"} + + python_script = f""" +import pandas as pd +from ncls import FNCLS +import json + +df = pd.read_csv('{subject_file}', sep='{delimiter}', header={'0' if has_header else 'None'}) +starts = df.iloc[:, {subject_start_col}].values.astype(float) +ends = df.iloc[:, {subject_end_col}].values.astype(float) +ids = df.iloc[:, {subject_id_col}].values + +fncls_idx = FNCLS(starts, ends, ids) +it = fncls_idx.find_overlap({query_start}, {query_end}) + +results = [] +for start, end, idx in it: + results.append({{"start": float(start), "end": float(end), "id": str(idx)}}) +print(json.dumps(results)) +""" + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp: + tmp.write(python_script) + tmp_path = tmp.name + + result = subprocess.run(["python", tmp_path], capture_output=True, text=True, check=True) + Path(tmp_path).unlink() + + return { + "command_executed": f"fncls.find_overlap({query_start}, {query_end})", + "stdout": result.stdout, + "stderr": result.stderr, + "overlaps": json.loads(result.stdout) + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_ncls/app/ncls_shim_server.py b/Biomni/mcp_generated/mcp_ncls/app/ncls_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..17f97cb8f679c99570aa1f20ea643a411f30a1a5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_ncls/app/ncls_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_ncls/app/ncls_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_ncls' + + +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_peakqc/Dockerfile b/Biomni/mcp_generated/mcp_peakqc/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0c715596deea2a8972e8c6902f5c62c6fd37d683 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/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 peakqc via conda (e.g., from bioconda) +RUN conda install -c bioconda peakqc -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/peakqc_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/peakqc_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/peakqc_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_peakqc/app/peakqc_server.py b/Biomni/mcp_generated/mcp_peakqc/app/peakqc_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a597d581e7995e3090f63edf8c01554a6bcd8839 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/app/peakqc_server.py @@ -0,0 +1,215 @@ +import subprocess +import tempfile +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_peakqc' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def add_fld_metrics_to_anndata( + adata_input_path: Path, + fragments_path: Path, + output_adata_path: Path, + barcode_col: Optional[str] = None, + plot: bool = True, + output_density_plot_path: Optional[Path] = None, + output_overview_plot_path: Optional[Path] = None, + sample: int = 0, + n_threads: int = 8, + sample_size: int = 5000, + mc_seed: int = 42, + mc_samples: int = 1000, +) -> Dict[str, Any]: + """ + Performs quality control of single-cell ATAC-seq data by evaluating fragment length + periodicity and adding FLD metrics to an AnnData object using PEAKQC. + + This tool wraps the `peakqc.fld_scoring.add_fld_metrics` Python function. + It takes an AnnData object and fragment files, computes FLD metrics, and + optionally generates plots, then saves the updated AnnData object. + + Args: + adata_input_path: Path to the input AnnData object (.h5ad file). + fragments_path: Path to the fragment file (BED or BAM format). BED files are + recommended for faster runtime. + output_adata_path: Path to save the modified AnnData object (.h5ad file) + with added FLD metrics. + barcode_col: Column in the AnnData object's .obs dataframe that contains + cell barcodes. If not provided (None), the .obs index is used. + plot: If True, generate and save plots related to FLD metrics. + output_density_plot_path: Optional path to save the fragment length density plot. + If None and 'plot' is True, a default filename + (e.g., 'peakqc_density_plot.png') will be used in the + same directory as `output_adata_path`. + output_overview_plot_path: Optional path to save the overview plot of FLD metrics. + If None and 'plot' is True, a default filename + (e.g., 'peakqc_overview_plot.png') will be used in the + same directory as `output_adata_path`. + sample: Sampling strategy for fragment length distributions. `0` typically means + no sampling or a default strategy. + n_threads: Number of threads to use for parallel processing. Must be a positive integer. + sample_size: Number of fragments to sample per cell for FLD calculation. + Must be a positive integer. + mc_seed: Random seed for Monte Carlo simulations. Must be a non-negative integer. + mc_samples: Number of Monte Carlo samples to perform for periodicity evaluation. + Must be a positive integer. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of + generated output files. + """ + # Input validation + if not adata_input_path.is_file(): + raise FileNotFoundError(f"Input AnnData file not found: {adata_input_path}") + if not fragments_path.is_file(): + raise FileNotFoundError(f"Fragments file not found: {fragments_path}") + + if n_threads <= 0: + raise ValueError("n_threads must be a positive integer.") + if sample_size <= 0: + raise ValueError("sample_size must be a positive integer.") + if mc_seed < 0: + raise ValueError("mc_seed must be a non-negative integer.") + if mc_samples <= 0: + raise ValueError("mc_samples must be a positive integer.") + if sample < 0: + raise ValueError("sample must be a non-negative integer.") + + # Ensure the parent directory for the output AnnData file exists + output_adata_path.parent.mkdir(parents=True, exist_ok=True) + + actual_density_plot_path = output_density_plot_path + actual_overview_plot_path = output_overview_plot_path + output_files: List[Path] = [output_adata_path] + + if plot: + # If plot paths are not specified, default to the same directory as output_adata_path + if not actual_density_plot_path: + actual_density_plot_path = output_adata_path.parent / "peakqc_density_plot.png" + if not actual_overview_plot_path: + actual_overview_plot_path = output_adata_path.parent / "peakqc_overview_plot.png" + + # Ensure parent directories for plots exist + actual_density_plot_path.parent.mkdir(parents=True, exist_ok=True) + actual_overview_plot_path.parent.mkdir(parents=True, exist_ok=True) + + output_files.append(actual_density_plot_path) + output_files.append(actual_overview_plot_path) + + # Construct the Python script to be executed + # Use repr() for strings/None to ensure correct literal representation in the script. + # Use f-string for direct embedding of Python values where possible. + barcode_col_repr = repr(barcode_col) # Will be 'None' or "'my_barcode_col'" + + save_density_arg = "None" + if actual_density_plot_path: + save_density_arg = f"Path('{actual_density_plot_path.resolve()}')" + + save_overview_arg = "None" + if actual_overview_plot_path: + save_overview_arg = f"Path('{actual_overview_plot_path.resolve()}')" + + script_content = f""" +import scanpy as sc +from peakqc.fld_scoring import add_fld_metrics +from pathlib import Path +import os +import sys + +# Resolve all input/output paths to absolute paths for robustness +adata_input_path = Path("{adata_input_path.resolve()}") +fragments_path = Path("{fragments_path.resolve()}") +output_adata_path = Path("{output_adata_path.resolve()}") + +# Parameters passed from MCP tool +barcode_col = {barcode_col_repr} +plot = {plot} +save_density = {save_density_arg} +save_overview = {save_overview_arg} +sample = {sample} +n_threads = {n_threads} +sample_size = {sample_size} +mc_seed = {mc_seed} +mc_samples = {mc_samples} + +try: + adata = sc.read_h5ad(adata_input_path) + + adata = add_fld_metrics( + adata=adata, + fragments=fragments_path, + barcode_col=barcode_col, + plot=plot, + save_density=save_density, + save_overview=save_overview, + sample=sample, + n_threads=n_threads, + sample_size=sample_size, + mc_seed=mc_seed, + mc_samples=mc_samples + ) + adata.write(output_adata_path) + print(f"Successfully processed and saved AnnData to {{output_adata_path}}") + if plot: + if save_density and save_density.exists(): + print(f"Density plot saved to {{save_density}}") + if save_overview and save_overview.exists(): + print(f"Overview plot saved to {{save_overview}}") + +except Exception as e: + print(f"Error during peakqc execution: {{e}}", file=sys.stderr) + sys.exit(1) +""" + + # Write the script to a temporary file + script_path: Path + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".py") as f: + f.write(script_content) + script_path = Path(f.name) + + command = ["python", str(script_path)] + command_str = " ".join(command) + + stdout = "" + stderr = "" + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + env=os.environ.copy() # Use current environment to find python and peakqc + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + stderr = e.stderr + stdout = e.stdout + raise RuntimeError( + f"peakqc execution failed with exit code {e.returncode}. " + f"Stdout: {stdout}\nStderr: {stderr}" + ) from e + except FileNotFoundError: + raise FileNotFoundError( + "Python executable not found. Ensure Python is installed and in your PATH." + ) + finally: + # Clean up the temporary script file + os.remove(script_path) + + # Filter output_files to only include those that actually exist after execution + existing_output_files = [str(p) for p in output_files if p.exists()] + + return { + "command_executed": command_str, + "stdout": stdout, + "stderr": stderr, + "output_files": existing_output_files, + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_peakqc/app/peakqc_shim_server.py b/Biomni/mcp_generated/mcp_peakqc/app/peakqc_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..8cd2a36e5503cdbb7d0b4f772b6a4540aa318fa8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/app/peakqc_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_peakqc/app/peakqc_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_peakqc' + + +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_peakqc/app/requirements.txt b/Biomni/mcp_generated/mcp_peakqc/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_peakqc/docker-compose.yml b/Biomni/mcp_generated/mcp_peakqc/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3de2be2f485eb2fc92f7d47b8ac3c025db7ed622 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-peakqc: + build: . + image: mcp-peakqc:latest + container_name: mcp-peakqc + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=peakqc + 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_peakqc/environment.yaml b/Biomni/mcp_generated/mcp_peakqc/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ee73f4d7b39866cfa71cef176d700af2c9ed5008 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - peakqc + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_peakqc/requirements.txt b/Biomni/mcp_generated/mcp_peakqc/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_peakqc/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-alien-libxml2/Dockerfile b/Biomni/mcp_generated/mcp_perl-alien-libxml2/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..dc028b3d330ccaceabed6d5c7bae910c7dca4f32 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/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-alien-libxml2 via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-alien-libxml2 -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-alien-libxml2_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-alien-libxml2_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-alien-libxml2_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_server.py b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3c91d868af3a7f45c5cbdcfaf1fbf8711c36835f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_server.py @@ -0,0 +1,97 @@ +import subprocess +import logging +from typing import Dict, Any + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# This is a dummy decorator for development and testing. +# In a real MCP environment, this would be provided by the MCP framework. +class mcp: + def tool(func): + """A dummy decorator that simply returns the function it decorates.""" + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + # Copy over docstrings and other metadata + wrapper.__doc__ = func.__doc__ + wrapper.__name__ = func.__name__ + wrapper.__annotations__ = func.__annotations__ + return wrapper + +@mcp.tool +def install_alien_libxml2( + force: bool = False, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Installs the Alien::Libxml2 Perl module using cpanm. + + This module's purpose is to find or build and install the C libxml2 library, + making it available for other Perl modules (like XML::LibXML) to use. This function + wraps the standard Perl module installer `cpanm`. + + Args: + force: If True, forces the installation of the module, even if it's already up-to-date. Corresponds to `cpanm -f`. + verbose: If True, enables verbose output during the installation process. Corresponds to `cpanm -v`. + + Returns: + A dictionary containing the execution details, including the command executed, + stdout, stderr, and a list of output files (which will be empty for this tool). + """ + # Input validation + if not isinstance(force, bool): + raise TypeError("Parameter 'force' must be a boolean.") + if not isinstance(verbose, bool): + raise TypeError("Parameter 'verbose' must be a boolean.") + + command = ["cpanm"] + + if force: + command.append("-f") + if verbose: + command.append("-v") + + command.append("Alien::Libxml2") + + command_str = " ".join(command) + logging.info(f"Executing command: {command_str}") + + try: + # Execute the command + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + ) + + # Return structured output on success + return { + "command_executed": command_str, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [] + } + except FileNotFoundError: + # Handle case where 'cpanm' is not installed or not in PATH + error_message = "Error: 'cpanm' command not found. Please ensure the cpanminus (App::cpanminus) Perl module is installed and in your system's PATH." + logging.error(error_message) + # Return a structured error response + return { + "command_executed": command_str, + "stdout": "", + "stderr": error_message, + "output_files": [] + } + except subprocess.CalledProcessError as e: + # Handle errors during the execution of the command + logging.error(f"Command failed with exit code {e.returncode}") + logging.error(f"Stdout: {e.stdout}") + logging.error(f"Stderr: {e.stderr}") + # Return a structured error response + return { + "command_executed": command_str, + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } diff --git a/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_shim_server.py b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..04bf6da13559888783b80dfb60b6224fcbd2c959 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/perl-alien-libxml2_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-alien-libxml2/app/perl-alien-libxml2_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_alien_libxml2' + + +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-alien-libxml2/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-alien-libxml2/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-alien-libxml2/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b308972882f357c744727995ef77db49033b3ce5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-alien-libxml2: + build: . + image: mcp-perl-alien-libxml2:latest + container_name: mcp-perl-alien-libxml2 + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-alien-libxml2 + 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-alien-libxml2/environment.yaml b/Biomni/mcp_generated/mcp_perl-alien-libxml2/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e19812566d781426365458a293a6dea4c0339b79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-alien-libxml2 + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-alien-libxml2/requirements.txt b/Biomni/mcp_generated/mcp_perl-alien-libxml2/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-alien-libxml2/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-bio-coordinate/Dockerfile b/Biomni/mcp_generated/mcp_perl-bio-coordinate/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3d147aabe2b02bfd2c876c46e4a560969bed02f6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/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-bio-coordinate via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-bio-coordinate -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-bio-coordinate_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-bio-coordinate_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-bio-coordinate_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_server.py b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5c708703d6d441f189eb5907e28ae2e1c48861a4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_server.py @@ -0,0 +1,272 @@ +import subprocess +import json +from typing import List, Optional, Dict, Any +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_bio_coordinate' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def bio_coordinate_pair_map( + src_start: int, + src_end: int, + src_strand: int, + dest_start: int, + dest_end: int, + dest_strand: int, + query_start: int, + query_end: int, +) -> Dict[str, Any]: + """ + Maps coordinates between two coordinate systems using Bio::Coordinate::Pair. + + Args: + src_start: Start position of the source coordinate system. + src_end: End position of the source coordinate system. + src_strand: Strand of the source coordinate system (1 or -1). + dest_start: Start position of the destination coordinate system. + dest_end: End position of the destination coordinate system. + dest_strand: Strand of the destination coordinate system (1 or -1). + query_start: Start position of the coordinate to map. + query_end: End position of the coordinate to map. + """ + # Input validation + if src_start > src_end: + return {"error": "src_start must be less than or equal to src_end"} + if dest_start > dest_end: + return {"error": "dest_start must be less than or equal to dest_end"} + if query_start > query_end: + return {"error": "query_start must be less than or equal to query_end"} + if src_strand not in [1, -1] or dest_strand not in [1, -1]: + return {"error": "Strand must be 1 or -1"} + + # Perl script to execute the mapping + perl_code = """ +use Bio::Coordinate::Pair; +use Bio::Location::Simple; +use JSON; + +my ($s1, $e1, $st1, $s2, $e2, $st2, $qs, $qe) = @ARGV; + +my $in = Bio::Location::Simple->new(-start => $s1, -end => $e1, -strand => $st1); +my $out = Bio::Location::Simple->new(-start => $s2, -end => $e2, -strand => $st2); +my $pair = Bio::Coordinate::Pair->new(-in => $in, -out => $out); + +my $query = Bio::Location::Simple->new(-start => $qs, -end => $qe); +my $res = $pair->map($query); + +my @results; +if (defined $res) { + if ($res->isa('Bio::Location::CollectionI')) { + foreach my $loc ($res->each_Location) { + push @results, {start => $loc->start, end => $loc->end, strand => $loc->strand}; + } + } elsif ($res->isa('Bio::Location::Simple')) { + push @results, {start => $res->start, end => $res->end, strand => $res->strand}; + } +} +print encode_json(\\@results); +""" + + cmd = [ + "perl", "-e", perl_code, + str(src_start), str(src_end), str(src_strand), + str(dest_start), str(dest_end), str(dest_strand), + str(query_start), str(query_end) + ] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_data = json.loads(process.stdout) if process.stdout.strip() else [] + return { + "command_executed": " ".join(cmd), + "mapped_coordinates": output_data, + "stdout": process.stdout, + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": f"Perl execution failed: {e.stderr}", + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr + } + except json.JSONDecodeError: + return { + "error": "Failed to parse Perl output as JSON", + "stdout": process.stdout, + "stderr": process.stderr + } + +@mcp.tool() +def bio_coordinate_dna_to_protein( + dna_start: int, + dna_end: int, + query_dna_start: int, + query_dna_end: int, + dna_strand: int = 1, +) -> Dict[str, Any]: + """ + Maps DNA coordinates to Protein coordinates (3:1 ratio) using Bio::Coordinate::Pair. + + Args: + dna_start: Start position of the DNA sequence (CDS). + dna_end: End position of the DNA sequence (CDS). + query_dna_start: Start position in DNA to map to protein. + query_dna_end: End position in DNA to map to protein. + dna_strand: Strand of the DNA (1 or -1). Defaults to 1. + """ + # Input validation + if dna_start > dna_end: + return {"error": "dna_start must be less than or equal to dna_end"} + + dna_length = dna_end - dna_start + 1 + if dna_length % 3 != 0: + # BioPerl handles this, but it's good to note + pass + + protein_length = int(dna_length / 3) + + # Perl script for DNA to Protein mapping + perl_code = """ +use Bio::Coordinate::Pair; +use Bio::Location::Simple; +use JSON; + +my ($ds, $de, $dst, $qs, $qe) = @ARGV; +my $prot_len = int(($de - $ds + 1) / 3); + +my $dna_loc = Bio::Location::Simple->new(-start => $ds, -end => $de, -strand => $dst); +my $prot_loc = Bio::Location::Simple->new(-start => 1, -end => $prot_len, -strand => 1); + +my $pair = Bio::Coordinate::Pair->new(-in => $dna_loc, -out => $prot_loc); + +my $query = Bio::Location::Simple->new(-start => $qs, -end => $qe); +my $res = $pair->map($query); + +my @results; +if (defined $res) { + if ($res->isa('Bio::Location::CollectionI')) { + foreach my $loc ($res->each_Location) { + push @results, {start => $loc->start, end => $loc->end, strand => $loc->strand}; + } + } elsif ($res->isa('Bio::Location::Simple')) { + push @results, {start => $res->start, end => $res->end, strand => $res->strand}; + } +} +print encode_json(\\@results); +""" + + cmd = [ + "perl", "-e", perl_code, + str(dna_start), str(dna_end), str(dna_strand), + str(query_dna_start), str(query_dna_end) + ] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_data = json.loads(process.stdout) if process.stdout.strip() else [] + return { + "command_executed": " ".join(cmd), + "mapped_protein_coordinates": output_data, + "stdout": process.stdout, + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": f"Perl execution failed: {e.stderr}", + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr + } + +@mcp.tool() +def bio_coordinate_gene_mapper( + cds_start: int, + cds_end: int, + query_start: int, + query_end: int, + map_direction: str = "cds2protein", + strand: int = 1, +) -> Dict[str, Any]: + """ + Maps between different gene coordinate systems (Genomic, CDS, Protein) using Bio::Coordinate::GeneMapper. + + Args: + cds_start: Start position of the CDS in genomic coordinates. + cds_end: End position of the CDS in genomic coordinates. + query_start: Start position of the query. + query_end: End position of the query. + map_direction: Direction of mapping. Options: 'cds2protein', 'protein2cds', 'genomic2cds', 'cds2genomic'. + strand: Strand of the gene (1 or -1). Defaults to 1. + """ + valid_directions = ["cds2protein", "protein2cds", "genomic2cds", "cds2genomic"] + if map_direction not in valid_directions: + return {"error": f"Invalid map_direction. Must be one of: {', '.join(valid_directions)}"} + + perl_code = """ +use Bio::Coordinate::GeneMapper; +use Bio::Location::Simple; +use JSON; + +my ($cs, $ce, $strand, $qs, $qe, $dir) = @ARGV; + +my $gm = Bio::Coordinate::GeneMapper->new( + -cds => Bio::Location::Simple->new(-start => $cs, -end => $ce, -strand => $strand) +); + +my $query = Bio::Location::Simple->new(-start => $qs, -end => $qe); +my $res; + +if ($dir eq 'cds2protein') { + $gm->in('cds'); $gm->out('protein'); +} elsif ($dir eq 'protein2cds') { + $gm->in('protein'); $gm->out('cds'); +} elsif ($dir eq 'genomic2cds') { + $gm->in('genomic'); $gm->out('cds'); +} elsif ($dir eq 'cds2genomic') { + $gm->in('cds'); $gm->out('genomic'); +} + +$res = $gm->map($query); + +my @results; +if (defined $res) { + if ($res->isa('Bio::Location::CollectionI')) { + foreach my $loc ($res->each_Location) { + push @results, {start => $loc->start, end => $loc->end, strand => $loc->strand}; + } + } elsif ($res->isa('Bio::Location::Simple')) { + push @results, {start => $res->start, end => $res->end, strand => $res->strand}; + } +} +print encode_json(\\@results); +""" + + cmd = [ + "perl", "-e", perl_code, + str(cds_start), str(cds_end), str(strand), + str(query_start), str(query_end), map_direction + ] + + try: + process = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_data = json.loads(process.stdout) if process.stdout.strip() else [] + return { + "command_executed": " ".join(cmd), + "mapped_coordinates": output_data, + "stdout": process.stdout, + "stderr": process.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": f"Perl execution failed: {e.stderr}", + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_shim_server.py b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..46115dcba038153efbf61e379b75e77b1401710e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/perl-bio-coordinate_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-bio-coordinate/app/perl-bio-coordinate_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_bio_coordinate' + + +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-bio-coordinate/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-bio-coordinate/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-bio-coordinate/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..4e78467a3c7f70bdf4e146ddd32957d3ea650b3d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-bio-coordinate: + build: . + image: mcp-perl-bio-coordinate:latest + container_name: mcp-perl-bio-coordinate + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-bio-coordinate + 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-bio-coordinate/environment.yaml b/Biomni/mcp_generated/mcp_perl-bio-coordinate/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..97dab8ec81d698e6d1062d85a85bfe7bec2708c0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-bio-coordinate + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-bio-coordinate/requirements.txt b/Biomni/mcp_generated/mcp_perl-bio-coordinate/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-bio-coordinate/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-encode/Dockerfile b/Biomni/mcp_generated/mcp_perl-encode/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..21769d4209493c15806b34d4eb4f54ea70c2ebd6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/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 via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-encode -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_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_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_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_server.py b/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3d782eb86857d86f337a7b035ecf7d229bcc81dc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_server.py @@ -0,0 +1,199 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any, Union + +# @mcp.tool() decorator is implied +def run_perl( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + program_string: Optional[List[str]] = None, + program_string_with_features: 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, + in_place_edit: bool = False, + in_place_backup_extension: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + line_ending_processing: Optional[str] = None, + use_modules: Optional[List[str]] = None, + use_modules_no_import: Optional[List[str]] = None, + no_modules: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + parse_switches: bool = False, + search_path: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + dump_core: bool = False, + unsafe_operations: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + extract_script_from_message: bool = False, + extract_script_directory: Optional[Path] = None, + disable_all_warnings: bool = False, +) -> Dict[str, Any]: + """ + Executes a Perl script or one-liner using the Perl interpreter. + + This tool is a wrapper for the `perl` command-line interpreter. While the package + may be named `perl-encode`, the executable is `perl`. The `Encode` module is + used within Perl scripts for character encoding tasks. + """ + # --- Input Validation --- + if print_version and print_config is not None: + raise ValueError("Cannot specify both print_version (-v) and print_config (-V) simultaneously.") + + source_options = [programfile, program_string, program_string_with_features] + num_source_options = sum(1 for opt in source_options if opt is not None) + + # If just getting version or config, no script is needed. + if not (print_version or print_config is not None): + if num_source_options == 0: + raise ValueError("You must provide a script source: either 'programfile', 'program_string', or 'program_string_with_features'.") + if num_source_options > 1: + raise ValueError("You can only specify one script source: 'programfile', 'program_string', or 'program_string_with_features'.") + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"The specified program file does not exist: {programfile}") + + if in_place_backup_extension is not None and not in_place_edit: + raise ValueError("'in_place_backup_extension' requires 'in_place_edit' to be True.") + + if extract_script_directory and not extract_script_from_message: + raise ValueError("'extract_script_directory' requires 'extract_script_from_message' to be True.") + + if in_place_edit and not arguments: + raise ValueError("In-place editing (-i) requires file paths to be provided in 'arguments'.") + + # --- 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 no_sitecustomize: + cmd.append("-f") + if split_pattern is not None: + cmd.append(f"-F{split_pattern}") + if in_place_edit: + backup_ext = in_place_backup_extension if in_place_backup_extension is not None else "" + cmd.append(f"-i{backup_ext}") + if include_directories: + for directory in include_directories: + cmd.append(f"-I{directory}") + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if use_modules: + for module in use_modules: + cmd.append(f"-M{module}") + if use_modules_no_import: + for module in use_modules_no_import: + cmd.append(f"-m{module}") + if no_modules: + for module in no_modules: + cmd.append(f"-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{print_config}") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if extract_script_from_message: + x_arg = "-x" + if extract_script_directory: + x_arg += str(extract_script_directory) + cmd.append(x_arg) + if disable_all_warnings: + cmd.append("-X") + + # Add program source + if program_string: + for p_str in program_string: + cmd.extend(["-e", p_str]) + if program_string_with_features: + for p_str in program_string_with_features: + cmd.extend(["-E", p_str]) + + # Add program file and arguments + if arguments: + cmd.append("--") + if programfile: + cmd.append(str(programfile)) + if arguments: + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=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": "Error: 'perl' command not found. Please ensure Perl is installed and in the system's PATH.", + "error": "Executable not found.", + "output_files": [] + } + + # --- Structured Result Return --- + output_files = [] + if in_place_edit and arguments: + # The files in 'arguments' are modified in place. + output_files = [str(Path(arg)) for arg in arguments] + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": output_files + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_shim_server.py b/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..376ea97b43e9a63683f6b449f30043853b1b8810 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/app/perl-encode_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/app/perl-encode_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_encode' + + +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/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-encode/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-encode/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-encode/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..95fd091c6ee7a31825cd937072bcb18bee39de01 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-encode: + build: . + image: mcp-perl-encode:latest + container_name: mcp-perl-encode + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-encode + 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/environment.yaml b/Biomni/mcp_generated/mcp_perl-encode/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..76db6c8fbc38e7374149aa84b2b35c4238c44456 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-encode + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-encode/requirements.txt b/Biomni/mcp_generated/mcp_perl-encode/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-encode/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/Dockerfile b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..25278da21a01b3804782d142378504c69d6030c6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/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-extutils-cbuilder via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-extutils-cbuilder -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-extutils-cbuilder_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-extutils-cbuilder_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-extutils-cbuilder_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_server.py b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_server.py new file mode 100644 index 0000000000000000000000000000000000000000..72c4c2cfb5e5809b592535967dc1d3f5d537657e --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_server.py @@ -0,0 +1,262 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_extutils_cbuilder' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perl_interpreter( + program_file: Optional[str] = None, + arguments: Optional[List[str]] = None, + execute_string: Optional[str] = None, + check_syntax_only: bool = False, + print_version: bool = False, + include_directories: Optional[List[str]] = None, + use_modules: Optional[List[str]] = None, + enable_warnings: bool = False, + taint_check: bool = False, +) -> Dict[str, Any]: + """ + Executes the Perl interpreter with specified options and script. + + Args: + program_file: Path to the Perl script to execute. + arguments: List of command-line arguments to pass to the script. + execute_string: A string of Perl code to execute (equivalent to -e). + check_syntax_only: If True, only check the syntax of the script without executing it. + print_version: If True, print the Perl version and exit. + include_directories: List of paths to add to @INC for module searching. + use_modules: List of modules to load (equivalent to -M). + enable_warnings: If True, enable useful warnings (equivalent to -w). + taint_check: If True, enable taint checks (equivalent to -T). + """ + cmd = ["perl"] + + if print_version: + cmd.append("-v") + if check_syntax_only: + cmd.append("-c") + if enable_warnings: + cmd.append("-w") + if taint_check: + cmd.append("-T") + + if include_directories: + for directory in include_directories: + cmd.append(f"-I{directory}") + + if use_modules: + for module in use_modules: + cmd.append(f"-M{module}") + + if execute_string: + cmd.extend(["-e", execute_string]) + elif program_file: + prog_path = Path(program_file) + if not prog_path.exists(): + raise FileNotFoundError(f"Program file not found: {program_file}") + cmd.append(str(prog_path)) + elif not print_version: + return { + "command_executed": "perl", + "stdout": "", + "stderr": "Error: No program file or execution string provided.", + "output_files": [] + } + + if arguments: + cmd.extend(arguments) + + 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": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + +@mcp.tool() +def cbuilder_compile_c( + source_file: str, + output_file: Optional[str] = None, + include_dirs: Optional[List[str]] = None, + extra_compiler_flags: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Compiles a C source file into an object file using Perl's ExtUtils::CBuilder. + + Args: + source_file: Path to the .c source file. + output_file: Optional path for the resulting object file. + include_dirs: List of directories to search for header files. + extra_compiler_flags: Additional flags to pass to the C compiler. + """ + src_path = Path(source_file) + if not src_path.exists(): + raise FileNotFoundError(f"Source file not found: {source_file}") + + # Construct the Perl one-liner to use ExtUtils::CBuilder + perl_code = [ + "use ExtUtils::CBuilder;", + "my $b = ExtUtils::CBuilder->new();", + f"my $obj = $b->compile(source => '{str(src_path)}'" + ] + + if output_file: + perl_code.append(f", output => '{output_file}'") + + if include_dirs: + dirs_str = ", ".join([f"'{d}'" for d in include_dirs]) + perl_code.append(f", include_dirs => [{dirs_str}]") + + if extra_compiler_flags: + flags_str = " ".join(extra_compiler_flags) + perl_code.append(f", extra_compiler_flags => '{flags_str}'") + + perl_code.append(");") + perl_code.append("print $obj;") + + full_perl_script = "".join(perl_code) + cmd = ["perl", "-MExtUtils::CBuilder", "-e", full_perl_script] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + generated_file = result.stdout.strip() + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [generated_file] if generated_file and Path(generated_file).exists() else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + +@mcp.tool() +def cbuilder_link_shared_library( + objects: List[str], + module_name: str, + output_file: Optional[str] = None, + extra_linker_flags: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Links object files into a dynamic/shared library using Perl's ExtUtils::CBuilder. + + Args: + objects: List of paths to object files (.o or .obj). + module_name: The name of the Perl module this library is for. + output_file: Optional path for the resulting shared library. + extra_linker_flags: Additional flags to pass to the linker. + """ + for obj in objects: + if not Path(obj).exists(): + raise FileNotFoundError(f"Object file not found: {obj}") + + obj_list_str = ", ".join([f"'{o}'" for o in objects]) + + perl_code = [ + "use ExtUtils::CBuilder;", + "my $b = ExtUtils::CBuilder->new();", + f"my $lib = $b->link(objects => [{obj_list_str}], module_name => '{module_name}'" + ] + + if output_file: + perl_code.append(f", lib_file => '{output_file}'") + + if extra_linker_flags: + flags_str = " ".join(extra_linker_flags) + perl_code.append(f", extra_linker_flags => '{flags_str}'") + + perl_code.append(");") + perl_code.append("print $lib;") + + full_perl_script = "".join(perl_code) + cmd = ["perl", "-MExtUtils::CBuilder", "-e", full_perl_script] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + generated_file = result.stdout.strip() + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": [generated_file] if generated_file and Path(generated_file).exists() else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + +@mcp.tool() +def cbuilder_have_compiler() -> Dict[str, Any]: + """ + Checks if a usable C compiler is available on the system via ExtUtils::CBuilder. + """ + perl_script = "use ExtUtils::CBuilder; print ExtUtils::CBuilder->new->have_compiler ? 'YES' : 'NO'" + cmd = ["perl", "-MExtUtils::CBuilder", "-e", perl_script] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + has_compiler = result.stdout.strip() == "YES" + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "has_compiler": has_compiler, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "has_compiler": False, + "output_files": [] + } + +@mcp.tool() +def cbuilder_get_config() -> Dict[str, Any]: + """ + Retrieves the configuration variables used by ExtUtils::CBuilder for the current platform. + """ + # We use Data::Dumper to output the config hash + perl_script = "use ExtUtils::CBuilder; use Data::Dumper; my $b = ExtUtils::CBuilder->new(); print Dumper($b);" + cmd = ["perl", "-MExtUtils::CBuilder", "-MData::Dumper", "-e", perl_script] + + 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": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_shim_server.py b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6b56b2ce6d7219515494299163498d938c600f6b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_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-extutils-cbuilder/app/perl-extutils-cbuilder_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_extutils_cbuilder' + + +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-extutils-cbuilder/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..4c32923b29d2dce93fa3284c789fe659d2fd1f4a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-extutils-cbuilder: + build: . + image: mcp-perl-extutils-cbuilder:latest + container_name: mcp-perl-extutils-cbuilder + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-extutils-cbuilder + 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-extutils-cbuilder/environment.yaml b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c9c453898e769ce60884a0bd4ab20e81f829923b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-extutils-cbuilder + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/requirements.txt b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-extutils-cbuilder/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-file-spec/Dockerfile b/Biomni/mcp_generated/mcp_perl-file-spec/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f656ba35012a616a163c4b4280dde02a809e78cd --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/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-file-spec via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-file-spec -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-file-spec_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-file-spec_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-file-spec_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_server.py b/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6b42912f949c97613357d3e7afea920ea1799dd8 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_server.py @@ -0,0 +1,234 @@ +import subprocess +import shlex +from pathlib import Path +from typing import Optional, List, Dict, Any + +# This is a mock decorator. In a real MCP environment, this would be provided by the MCP framework. +def tool(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + +mcp = type("mcp", (), {"tool": tool}) + +@mcp.tool +def perl_interpreter( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + execute_program: Optional[List[str]] = None, + execute_program_extended: Optional[List[str]] = None, + check_syntax_only: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + allow_unsafe: bool = False, + dump_core: bool = False, + no_sitecustomize: bool = False, + search_path: bool = False, + parse_switches: bool = False, + loop_around_program: bool = False, + loop_and_print: bool = False, + autosplit: bool = False, + split_pattern: Optional[str] = None, + record_separator: Optional[str] = None, + line_ending_processing: Optional[str] = None, + edit_in_place: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + use_module: Optional[List[str]] = None, + no_module: Optional[List[str]] = None, + require_module: Optional[List[str]] = None, + no_require_module: Optional[List[str]] = None, + run_under_debugger: Optional[str] = None, + debugging_flags: Optional[str] = None, + unicode_features: Optional[str] = None, + ignore_text_before_hashbang: bool = False, + cd_to_directory_for_x: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Executes a Perl program using the 'perl' interpreter. + + This tool provides a comprehensive wrapper around the 'perl' command-line interface, + allowing for the execution of scripts from files or strings, with full control over + Perl's numerous switches and options. + + 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 (-e). Omit programfile. + execute_program_extended: Like -e, but enables all optional features (-E). Omit programfile. + check_syntax_only: Check syntax only (runs BEGIN and CHECK blocks) (-c). + print_version: Print version, patchlevel, and license (-v). + print_config: Print configuration summary or a single Config.pm variable (-V[:variable]). + enable_warnings: Enable many useful warnings (-w). + enable_all_warnings: Enable all warnings (-W). + disable_all_warnings: Disable all warnings (-X). + tainting_warnings: Enable tainting warnings (-t). + tainting_checks: Enable tainting checks (-T). + allow_unsafe: Allow unsafe operations (-U). + dump_core: Dump core after parsing program (-u). + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup (-f). + search_path: Look for programfile using the PATH environment variable (-S). + parse_switches: Enable rudimentary parsing for switches after programfile (-s). + loop_around_program: Assume "while (<>) { ... }" loop around program (-n). + loop_and_print: Assume loop like -n but also print line, like sed (-p). + autosplit: Autosplit mode with -n or -p (splits $_ into @F) (-a). + split_pattern: split() pattern for -a switch (-F/pattern/). + record_separator: Specify record separator (-0[octal]). + line_ending_processing: Enable line ending processing, specifies line terminator (-l[octal]). + edit_in_place: Edit <> files in place. Makes a backup if an extension is supplied (-i[extension]). + include_directories: Specify @INC/#include directory. Several -I's allowed (-I). + use_module: Execute "use module..." before executing the program (-Mmodule). + no_module: Execute "no module..." before executing the program (-M-module). + require_module: Execute "use module..." before executing the program (-mmodule). + no_require_module: Execute "no module..." before executing the program (-m-module). + run_under_debugger: Run program under the specified debugger (-d[:debugger]). + debugging_flags: Set debugging flags (-D[number/list]). + unicode_features: Enable the listed Unicode features (-C[number/list]). + ignore_text_before_hashbang: Ignore text before #!perl line (-x). + cd_to_directory_for_x: Change to this directory before executing for use with -x (-x[directory]). + + Returns: + A dictionary containing the command executed, stdout, stderr, and return code. + """ + # --- Input Validation --- + source_count = sum([ + 1 if programfile else 0, + 1 if bool(execute_program) else 0, + 1 if bool(execute_program_extended) else 0 + ]) + if source_count == 0 and not print_version and print_config is None: + raise ValueError("A source for the program must be provided via 'programfile', 'execute_program', or 'execute_program_extended'.") + if source_count > 1: + raise ValueError("Only one of 'programfile', 'execute_program', or 'execute_program_extended' can be used at a time.") + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"Program file not found: {programfile}") + + if include_directories: + for d in include_directories: + if not d.is_dir(): + raise NotADirectoryError(f"Include directory not found: {d}") + + if cd_to_directory_for_x and not ignore_text_before_hashbang: + raise ValueError("'cd_to_directory_for_x' requires 'ignore_text_before_hashbang' to be True.") + + # --- Command Construction --- + cmd = ["perl"] + + if record_separator is not None: + cmd.append(f"-0{record_separator}") + if autosplit: + cmd.append("-a") + if unicode_features is not None: + cmd.append(f"-C{unicode_features}") + if check_syntax_only: + cmd.append("-c") + if 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 line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if loop_around_program: + cmd.append("-n") + if loop_and_print: + cmd.append("-p") + if parse_switches: + cmd.append("-s") + if search_path: + cmd.append("-S") + if tainting_warnings: + cmd.append("-t") + if tainting_checks: + cmd.append("-T") + if dump_core: + cmd.append("-u") + if allow_unsafe: + cmd.append("-U") + if print_version: + cmd.append("-v") + if print_config is not None: + cmd.append(f"-V{print_config}") + if enable_warnings: + cmd.append("-w") + if enable_all_warnings: + cmd.append("-W") + if disable_all_warnings: + cmd.append("-X") + + if cd_to_directory_for_x: + cmd.append(f"-x{cd_to_directory_for_x}") + elif ignore_text_before_hashbang: + cmd.append("-x") + + if include_directories: + for d in include_directories: + cmd.extend(["-I", str(d)]) + + if use_module: + for mod in use_module: + cmd.append(f"-M{mod}") + if no_module: + for mod in no_module: + cmd.append(f"-M-{mod}") + if require_module: + for mod in require_module: + cmd.append(f"-m{mod}") + if no_require_module: + for mod in no_require_module: + cmd.append(f"-m-{mod}") + + 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) + + # --- 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, + "return_code": result.returncode, + "output_files": [] # Perl scripts can create files, but the wrapper cannot know them beforehand. + } + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "return_code": e.returncode, + "error": "Perl execution failed." + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "Error: 'perl' command not found. Please ensure Perl is installed and in your PATH.", + "return_code": 1, + "error": "Command not found." + } diff --git a/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_shim_server.py b/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e8cb13d00fb8144f39c8d01914a9efdeeb38b38b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/app/perl-file-spec_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-file-spec/app/perl-file-spec_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_file_spec' + + +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-file-spec/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-file-spec/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-file-spec/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-file-spec/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..dfb683885757ef8a4096b93d7c5d0dfb08084f20 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-file-spec: + build: . + image: mcp-perl-file-spec:latest + container_name: mcp-perl-file-spec + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-file-spec + 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-file-spec/environment.yaml b/Biomni/mcp_generated/mcp_perl-file-spec/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..434ed60e31696b027ea3cdc011d70121b4760d4d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-file-spec + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-file-spec/requirements.txt b/Biomni/mcp_generated/mcp_perl-file-spec/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-file-spec/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-graphviz/Dockerfile b/Biomni/mcp_generated/mcp_perl-graphviz/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9f30323d3796e9a0898230914fe2270952b47a31 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/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-graphviz via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-graphviz -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-graphviz_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-graphviz_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-graphviz_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_server.py b/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_server.py new file mode 100644 index 0000000000000000000000000000000000000000..504125a5b9c5b44611551b6c3ff80571dad1a311 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_server.py @@ -0,0 +1,237 @@ +import subprocess +import tempfile +import json +from pathlib import Path +from typing import Optional, List, Dict, Any + +def _escape_dot_string(s: str) -> str: + """Escapes a string for use as a quoted ID in a DOT file.""" + return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"' + +def _format_attributes(attrs: Dict[str, Any]) -> str: + """Formats a dictionary of attributes into a DOT language string.""" + if not attrs: + return "" + parts = [] + for key, value in attrs.items(): + if isinstance(value, (int, float)): + parts.append(f"{key}={value}") + else: + # Safest to quote all non-numeric values + parts.append(f"{key}={_escape_dot_string(str(value))}") + return f"[{', '.join(parts)}]" + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_graphviz' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def render_graph( + nodes: str, + edges: str, + layout: str = "dot", + output_format: str = "png", + directed: bool = True, + graph_name: str = "g", + rankdir: Optional[str] = None, + width: Optional[float] = None, + height: Optional[float] = None, + pagewidth: Optional[float] = None, + pageheight: Optional[float] = None, + concentrate: bool = False, + orientation: Optional[float] = None, + random_start: bool = False, + epsilon: Optional[float] = None, + overlap: Optional[str] = None, + ratio: Optional[str] = None, + bgcolor: Optional[str] = None, + global_graph_attrs: Optional[str] = None, + global_node_attrs: Optional[str] = None, + global_edge_attrs: Optional[str] = None, +) -> Dict[str, Any]: + """ + Renders a graph using a Graphviz layout engine (dot, neato, etc.). + + This tool mimics the functionality of the perl-graphviz library by programmatically + constructing a DOT file from node and edge definitions and then calling a + Graphviz layout engine to generate an image or other output format. + + Args: + nodes: A JSON string representing a list of nodes. Each node is a dictionary + with a required 'name' key and optional attribute keys (e.g., 'label', 'shape'). + Example: '[{"name": "A", "label": "Node A"}, {"name": "B"}]' + edges: A JSON string representing a list of edges. Each edge is a dictionary + with required 'from' and 'to' keys (referencing node names) and optional + attribute keys (e.g., 'label', 'color'). + Example: '[{"from": "A", "to": "B", "label": "connects"}]' + layout: The layout algorithm to use. Corresponds to a Graphviz command. + Valid options: 'dot', 'neato', 'twopi', 'circo', 'fdp'. + output_format: The format of the output file (e.g., 'png', 'svg', 'jpg', 'pdf', 'ps', 'dot'). + directed: If True, creates a directed graph (digraph) with arrows. + If False, creates an undirected graph (graph) with lines. + graph_name: The name of the graph in the DOT source. Defaults to 'g'. + rankdir: For 'dot' layout, sets the direction of graph layout. + Valid options: 'TB' (top to bottom), 'BT' (bottom to top), + 'LR' (left to right), 'RL' (right to left). + width: Width of the drawing in inches. + height: Height of the drawing in inches. + pagewidth: For PostScript output, sets the page width in inches for pagination. + pageheight: For PostScript output, sets the page height in inches for pagination. + concentrate: If True, enables edge merging in directed graphs to reduce clutter. + orientation: Angle, in degrees, to rotate polygon node shapes. + random_start: For undirected graphs, requests an initial random placement. + epsilon: For undirected graphs, controls how long the solver runs. + Lower values mean longer run time but potentially better layout. + overlap: How to handle node overlaps. Valid options: 'true', 'scale', 'false', + 'scalexy', 'orthoxy', 'orthxy', 'compress'. + ratio: Sets the aspect ratio (height/width). Can be a number or one of + 'fill', 'compress', 'expand', 'auto'. + bgcolor: Background color for the drawing (e.g., 'white', 'transparent', 'blue'). + global_graph_attrs: A JSON string of a dictionary for global graph attributes. + Example: '{"fontsize": "10"}' + global_node_attrs: A JSON string of a dictionary for global node attributes. + Example: '{"shape": "box", "style": "filled"}' + global_edge_attrs: A JSON string of a dictionary for global edge attributes. + Example: '{"color": "blue"}' + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + """ + # --- Input Validation --- + valid_layouts = ["dot", "neato", "twopi", "circo", "fdp"] + if layout not in valid_layouts: + raise ValueError(f"Invalid layout '{layout}'. Must be one of {valid_layouts}.") + + if rankdir and rankdir not in ["TB", "BT", "LR", "RL"]: + raise ValueError(f"Invalid rankdir '{rankdir}'. Must be one of 'TB', 'BT', 'LR', 'RL'.") + + if overlap and overlap not in ['true', 'scale', 'false', 'scalexy', 'orthoxy', 'orthxy', 'compress']: + raise ValueError(f"Invalid overlap value '{overlap}'.") + + try: + nodes_list: List[Dict[str, Any]] = json.loads(nodes) + if not isinstance(nodes_list, list): + raise TypeError("Nodes JSON must be a list of objects.") + except (json.JSONDecodeError, TypeError) as e: + raise ValueError(f"Invalid JSON for 'nodes': {e}") + + try: + edges_list: List[Dict[str, Any]] = json.loads(edges) + if not isinstance(edges_list, list): + raise TypeError("Edges JSON must be a list of objects.") + except (json.JSONDecodeError, TypeError) as e: + raise ValueError(f"Invalid JSON for 'edges': {e}") + + def parse_json_attrs(json_str: Optional[str], name: str) -> Dict[str, Any]: + if not json_str: + return {} + try: + attrs = json.loads(json_str) + if not isinstance(attrs, dict): + raise TypeError(f"{name} JSON must be an object.") + return attrs + except (json.JSONDecodeError, TypeError) as e: + raise ValueError(f"Invalid JSON for '{name}': {e}") + + graph_attrs = parse_json_attrs(global_graph_attrs, "global_graph_attrs") + node_attrs = parse_json_attrs(global_node_attrs, "global_node_attrs") + edge_attrs = parse_json_attrs(global_edge_attrs, "global_edge_attrs") + + # --- DOT File Generation --- + dot_lines = [] + graph_type = "digraph" if directed else "graph" + edge_op = "->" if directed else "--" + + dot_lines.append(f"{graph_type} {_escape_dot_string(graph_name)} {{") + + # Add global graph attributes from parameters + if rankdir: graph_attrs['rankdir'] = rankdir + if width is not None: graph_attrs['width'] = width + if height is not None: graph_attrs['height'] = height + if pagewidth is not None: graph_attrs['pagewidth'] = pagewidth + if pageheight is not None: graph_attrs['pageheight'] = pageheight + if concentrate: graph_attrs['concentrate'] = 'true' + if orientation is not None: graph_attrs['orientation'] = orientation + if random_start: graph_attrs['random_start'] = 'true' + if epsilon is not None: graph_attrs['epsilon'] = epsilon + if overlap: graph_attrs['overlap'] = overlap + if ratio: graph_attrs['ratio'] = ratio + if bgcolor: graph_attrs['bgcolor'] = bgcolor + + if graph_attrs: + dot_lines.append(f" graph {_format_attributes(graph_attrs)};") + if node_attrs: + dot_lines.append(f" node {_format_attributes(node_attrs)};") + if edge_attrs: + dot_lines.append(f" edge {_format_attributes(edge_attrs)};") + + for node in nodes_list: + if 'name' not in node: + raise ValueError("Each node in the 'nodes' list must have a 'name' key.") + node_name = node['name'] + attributes = {k: v for k, v in node.items() if k != 'name'} + dot_lines.append(f" {_escape_dot_string(str(node_name))} {_format_attributes(attributes)};") + + for edge in edges_list: + if 'from' not in edge or 'to' not in edge: + raise ValueError("Each edge in the 'edges' list must have 'from' and 'to' keys.") + from_node = edge['from'] + to_node = edge['to'] + attributes = {k: v for k, v in edge.items() if k not in ['from', 'to']} + dot_lines.append(f" {_escape_dot_string(str(from_node))} {edge_op} {_escape_dot_string(str(to_node))} {_format_attributes(attributes)};") + + dot_lines.append("}") + dot_content = "\n".join(dot_lines) + + # --- Subprocess Execution --- + with tempfile.NamedTemporaryFile(mode='w', suffix='.dot', delete=False, encoding='utf-8') as tmp_dot_file: + tmp_dot_path = Path(tmp_dot_file.name) + tmp_dot_file.write(dot_content) + + output_filename = f"graph_output.{output_format}" + output_path = Path(output_filename) + + cmd = [layout, f"-T{output_format}", "-o", str(output_path), str(tmp_dot_path)] + command_executed = " ".join(cmd) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + + if result.returncode != 0: + raise subprocess.CalledProcessError( + result.returncode, cmd, output=result.stdout, stderr=result.stderr + ) + + output_files = [str(output_path)] if output_path.exists() and output_path.stat().st_size > 0 else [] + + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + + except FileNotFoundError: + raise RuntimeError( + f"The command '{layout}' was not found. " + "Please ensure the 'graphviz' package is installed and in the system's PATH." + ) + except subprocess.CalledProcessError as e: + error_message = ( + f"Graphviz command failed with exit code {e.returncode}. " + f"Check stderr for details from the layout engine. " + f"Generated DOT content:\n{dot_content}" + ) + return { + "command_executed": command_executed, + "stdout": e.stdout or "", + "stderr": e.stderr or error_message, + "output_files": [] + } + finally: + if tmp_dot_path.exists(): + tmp_dot_path.unlink() + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_shim_server.py b/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d9ddf7e4826a201709a93ae73ed856832f007f2b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/app/perl-graphviz_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-graphviz/app/perl-graphviz_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_graphviz' + + +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-graphviz/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-graphviz/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-graphviz/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-graphviz/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..deca62c6ffdb98731485ca94cf0e6a180a0bb91d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-graphviz: + build: . + image: mcp-perl-graphviz:latest + container_name: mcp-perl-graphviz + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-graphviz + 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-graphviz/environment.yaml b/Biomni/mcp_generated/mcp_perl-graphviz/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3ea1e35978acabdbd80b0f2b8413e594c40ed545 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-graphviz/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-graphviz + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-http-daemon/Dockerfile b/Biomni/mcp_generated/mcp_perl-http-daemon/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..df8a5c2ebba2494b0e402e63f5aef63f9cc31aa3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/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-daemon via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-http-daemon -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-daemon_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-daemon_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-daemon_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-http-daemon/app/perl-http-daemon_server.py b/Biomni/mcp_generated/mcp_perl-http-daemon/app/perl-http-daemon_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3f73d19323c18e99e7da5b8a056d5b084f21057f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/app/perl-http-daemon_server.py @@ -0,0 +1,192 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_http_daemon' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def run_perl_interpreter( + program_file: Optional[Path] = None, + program_arguments: Optional[List[str]] = None, + record_separator_octal: Optional[str] = None, + autosplit: bool = False, + unicode_features: Optional[str] = None, + check_syntax_only: bool = False, + debugger: Optional[str] = None, + debug_flags: Optional[str] = None, + program_string: Optional[List[str]] = None, + program_string_extended: Optional[List[str]] = None, + no_sitecustomize: bool = False, + split_pattern: Optional[str] = None, + edit_in_place_extension: Optional[str] = None, + include_directories: Optional[List[Path]] = None, + line_ending_processing_terminator: Optional[str] = None, + use_modules: Optional[List[str]] = None, + no_modules: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + rudimentary_switch_parsing: bool = False, + look_for_programfile_in_path: 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_directory: Optional[Path] = None, + disable_all_warnings: bool = False, +) -> dict: + """ + Executes a Perl script or one-liner using the Perl interpreter. + + This tool wraps the generic 'perl' interpreter command, providing access to its + various command-line switches. While the request was for 'perl-http-daemon', + 'HTTP::Daemon' is a Perl module, not a standalone executable. The provided + CLI help is for the 'perl' interpreter itself. This function allows running + any Perl script, including those that might utilize the 'HTTP::Daemon' module, + with full control over interpreter options. + """ + command = ["perl"] + output_files = [] + + # --- Input validation --- + if program_file: + if not program_file.is_file(): + raise FileNotFoundError(f"Program file not found: {program_file}") + if program_string or program_string_extended: + raise ValueError("Cannot specify both a program file and program string(s) (-e/-E).") + + if program_string and program_string_extended: + raise ValueError("Cannot specify both -e and -E program strings simultaneously.") + + if loop_around_program and loop_and_print: + raise ValueError("Cannot specify both -n and -p simultaneously.") + + warning_flags_set = sum([enable_warnings, enable_all_warnings, disable_all_warnings]) + if warning_flags_set > 1: + raise ValueError("Cannot specify more than one of -w, -W, or -X simultaneously.") + + if include_directories: + for d in include_directories: + if not d.is_dir(): + raise NotADirectoryError(f"Include directory not found or is not a directory: {d}") + + if ignore_text_before_shebang_directory: + if not ignore_text_before_shebang_directory.is_dir(): + raise NotADirectoryError(f"Directory for -x not found or is not a directory: {ignore_text_before_shebang_directory}") + + # --- Build command --- + if record_separator_octal is not None: + command.append(f"-0{record_separator_octal}") + if autosplit: + command.append("-a") + if unicode_features is not None: + command.append(f"-C{unicode_features}") + if check_syntax_only: + command.append("-c") + if debugger is not None: + command.append(f"-d:{debugger}" if debugger else "-d") + if debug_flags is not None: + command.append(f"-D{debug_flags}") + if program_string: + for p_str in program_string: + command.extend(["-e", p_str]) + if program_string_extended: + for p_str in program_string_extended: + command.extend(["-E", p_str]) + if no_sitecustomize: + command.append("-f") + if split_pattern is not None: + command.append(f"-F{split_pattern}") + if edit_in_place_extension is not None: + command.append(f"-i{edit_in_place_extension}") + if include_directories: + for d in include_directories: + command.extend(["-I", str(d)]) + if line_ending_processing_terminator is not None: + command.append(f"-l{line_ending_processing_terminator}") + if use_modules: + for module in use_modules: + command.extend(["-M", module]) + if no_modules: + for module in no_modules: + command.extend(["-m-", module]) + if loop_around_program: + command.append("-n") + if loop_and_print: + command.append("-p") + if rudimentary_switch_parsing: + command.append("-s") + if look_for_programfile_in_path: + 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: + command.append(f"-V:{print_config_summary_variable}" if print_config_summary_variable else "-V") + if enable_warnings: + command.append("-w") + if enable_all_warnings: + command.append("-W") + if ignore_text_before_shebang_directory is not None: + command.append(f"-x{ignore_text_before_shebang_directory}") + if disable_all_warnings: + command.append("-X") + + # Add program file and its arguments (positional) + if program_file: + command.append(str(program_file)) + + if program_arguments: + command.extend(program_arguments) + + # --- Execute command --- + try: + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(e.cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": f"Perl command failed with exit code {e.returncode}.", + "returncode": e.returncode, + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: 'perl' command not found. Please ensure Perl is installed and accessible in your PATH.", + "output_files": [], + "error": "Perl executable not found.", + "returncode": 127, + } + + 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_perl-http-daemon/app/perl-http-daemon_shim_server.py b/Biomni/mcp_generated/mcp_perl-http-daemon/app/perl-http-daemon_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..77aed60de4798a92ea11c67384e371621eff9aa5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/app/perl-http-daemon_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-daemon/app/perl-http-daemon_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_http_daemon' + + +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-daemon/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-http-daemon/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-http-daemon/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-http-daemon/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..506e8d6a356a6d3e367cb06948f1a9720dff0088 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-http-daemon: + build: . + image: mcp-perl-http-daemon:latest + container_name: mcp-perl-http-daemon + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-http-daemon + 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-daemon/environment.yaml b/Biomni/mcp_generated/mcp_perl-http-daemon/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..53d8291c5725145eeb629aabe9d7d1a13dbc61f6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-http-daemon + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-http-daemon/requirements.txt b/Biomni/mcp_generated/mcp_perl-http-daemon/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-http-daemon/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/Dockerfile b/Biomni/mcp_generated/mcp_perl-json-xs/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b311b63df49d0b63b7e48f0d6b05c99c8a91412f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-json-xs via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-json-xs -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-json-xs_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-json-xs_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-json-xs_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_server.py b/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_server.py new file mode 100644 index 0000000000000000000000000000000000000000..56a7bd4558e7fe4d5595df6bf274b60d4115c9f5 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_server.py @@ -0,0 +1,231 @@ +from pathlib import Path +import subprocess +from typing import Optional, List, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_json_xs' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def json_xs( + input_file: str, + from_format: str = "json", + to_format: str = "json", + expression: str = "", + verbose: bool = False, + output_file: str = "", +): + """ + JSON::XS command line utility for converting, filtering, and transforming JSON data. + This tool allows you to convert between various formats and apply Perl transformations. + + Args: + input_file: Path to the input file to process. + from_format: The format of the input file. + Options: json, storable, clojure, eval, yaml, cbor, string. + to_format: The format of the output. + Options: json, json-utf8, json-ascii, json-pretty, storable, + storable-file, clojure, eval, yaml, cbor, string, none. + expression: Perl code to evaluate on the data (the data is stored in $_). + verbose: Enable verbose output from the json_xs utility. + output_file: Optional path to save the output. If not provided, output is returned in stdout. + """ + # Input validation + input_path = Path(input_file) + if not input_path.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + valid_from = ["json", "storable", "clojure", "eval", "yaml", "cbor", "string"] + valid_to = ["json", "json-utf8", "json-ascii", "json-pretty", "storable", + "storable-file", "clojure", "eval", "yaml", "cbor", "string", "none"] + + if from_format not in valid_from: + raise ValueError(f"Invalid from_format '{from_format}'. Must be one of: {', '.join(valid_from)}") + if to_format not in valid_to: + raise ValueError(f"Invalid to_format '{to_format}'. Must be one of: {', '.join(valid_to)}") + + # Construct command + cmd = ["json_xs"] + if verbose: + cmd.append("-v") + + cmd.extend(["-f", from_format]) + cmd.extend(["-t", to_format]) + + if expression: + cmd.extend(["-e", expression]) + + cmd.append(str(input_path)) + + try: + # Subprocess execution + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + output_content = result.stdout + + # Handle output file + output_files = [] + if output_file: + out_path = Path(output_file) + out_path.write_text(output_content) + output_files.append(str(out_path)) + + return { + "command_executed": " ".join(cmd), + "stdout": output_content, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Command failed with return code {e.returncode}" + } + +@mcp.tool() +def perl_json_xs_oo_process( + input_file: str, + pretty: bool = False, + ascii: bool = False, + latin1: bool = False, + utf8: bool = True, + canonical: bool = False, + relaxed: bool = False, + allow_nonref: bool = True, + space_before: bool = False, + space_after: bool = False, + indent: bool = False, + output_file: str = "", +): + """ + Process JSON using the JSON::XS Object-Oriented interface for fine-grained control over + encoding and decoding flags. This exposes the internal configuration methods of the module. + + Args: + input_file: Path to the JSON input file. + pretty: Enable all formatting flags (indent, space_before, space_after). + ascii: Escape Unicode characters outside the 0..127 range. + latin1: Encode resulting JSON text as ISO-8859-1. + utf8: Encode/decode JSON result into/from UTF-8. + canonical: Sort object keys alphabetically. + relaxed: Accept extensions like trailing commas and shell-style comments. + allow_nonref: Allow non-references (scalars) at the top level. + space_before: Add a space before the colon in objects. + space_after: Add a space after the colon and comma. + indent: Use a multiline format with indentation. + output_file: Optional path to save the processed JSON. + """ + input_path = Path(input_file) + if not input_path.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + # Construct Perl one-liner to use the OO interface + # We build the coder object by chaining the mutators + perl_script = ( + "use JSON::XS; " + "local $/; " + "my $raw = <>; " + "my $coder = JSON::XS->new" + f"->ascii({1 if ascii else 0})" + f"->latin1({1 if latin1 else 0})" + f"->utf8({1 if utf8 else 0})" + f"->pretty({1 if pretty else 0})" + f"->canonical({1 if canonical else 0})" + f"->relaxed({1 if relaxed else 0})" + f"->allow_nonref({1 if allow_nonref else 0})" + f"->space_before({1 if space_before else 0})" + f"->space_after({1 if space_after else 0})" + f"->indent({1 if indent else 0}); " + "my $data = $coder->decode($raw); " + "print $coder->encode($data);" + ) + + cmd = ["perl", "-MJSON::XS", "-e", perl_script, str(input_path)] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + output_content = result.stdout + output_files = [] + if output_file: + out_path = Path(output_file) + out_path.write_text(output_content) + output_files.append(str(out_path)) + + return { + "command_executed": " ".join(cmd), + "stdout": output_content, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Perl execution failed: {e.stderr}" + } + +@mcp.tool() +def perl_json_xs_version(): + """ + Check the installed version of the JSON::XS Perl module and the json_xs utility. + """ + try: + # Check module version + perl_ver_cmd = ["perl", "-MJSON::XS", "-e", "print $JSON::XS::VERSION"] + perl_res = subprocess.run(perl_ver_cmd, capture_output=True, text=True, check=True) + + # Check CLI utility version + cli_ver_cmd = ["json_xs", "-v"] + # json_xs -v usually prints to stderr and might exit with 0 or 1 depending on version + cli_res = subprocess.run(cli_ver_cmd, capture_output=True, text=True) + + return { + "module_version": perl_res.stdout.strip(), + "cli_info": cli_res.stderr.strip() if cli_res.stderr else cli_res.stdout.strip(), + "status": "success" + } + except Exception as e: + return { + "error": str(e), + "status": "failed" + } + +@mcp.tool() +def perl_json_xs_validate(input_file: str): + """ + Validate a JSON file using JSON::XS. This is a convenience wrapper around the + decode_json functional interface. + + Args: + input_file: Path to the JSON file to validate. + """ + input_path = Path(input_file) + if not input_path.exists(): + raise FileNotFoundError(f"Input file not found: {input_file}") + + # Use decode_json which is the fastest functional interface + perl_script = "use JSON::XS; local $/; my $raw = <>; eval { decode_json($raw) }; if ($@) { die $@ } else { print 'Valid JSON' }" + cmd = ["perl", "-MJSON::XS", "-e", perl_script, str(input_path)] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "is_valid": True, + "stdout": result.stdout.strip(), + "command_executed": " ".join(cmd) + } + except subprocess.CalledProcessError as e: + return { + "is_valid": False, + "stderr": e.stderr.strip(), + "command_executed": " ".join(cmd), + "error": "JSON validation failed" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_shim_server.py b/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..468a97bdc6c71424d51b3738f6f6c49754f57278 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/app/perl-json-xs_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-json-xs/app/perl-json-xs_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_json_xs' + + +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-json-xs/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-json-xs/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-json-xs/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..fb4044c92a5edc1db0f1093c6bca2e6b9467adb6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-json-xs: + build: . + image: mcp-perl-json-xs:latest + container_name: mcp-perl-json-xs + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-json-xs + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/environment.yaml b/Biomni/mcp_generated/mcp_perl-json-xs/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..da8b22ab725a19f48a3a7e3e623641d11e98251a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-json-xs + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-json-xs/requirements.txt b/Biomni/mcp_generated/mcp_perl-json-xs/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-json-xs/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-libwww-perl/Dockerfile b/Biomni/mcp_generated/mcp_perl-libwww-perl/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7feffc1fda0d7e7b3b726533c493697fdfe86478 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/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-libwww-perl via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-libwww-perl -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-libwww-perl_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-libwww-perl_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-libwww-perl_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_server.py b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6b8fb70f01b8c240942dcf612626578c8d0d9d42 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_server.py @@ -0,0 +1,306 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +def _run_command(cmd: List[str], input_data: Optional[str] = None) -> Dict[str, Any]: + """Helper to run subprocess commands and return structured output.""" + try: + process = subprocess.run( + cmd, + input=input_data.encode() if input_data else None, + capture_output=True, + check=True + ) + return { + "command_executed": " ".join(cmd), + "stdout": process.stdout.decode(), + "stderr": process.stderr.decode(), + "status": "success" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.decode(), + "stderr": e.stderr.decode(), + "error": str(e), + "status": "error" + } + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_libwww_perl' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def lwp_request( + url: str, + method: str = "GET", + timeout: int = 180, + content_type: Optional[str] = None, + headers: Optional[List[str]] = None, + proxy: Optional[str] = None, + credentials: Optional[str] = None, + base_url: Optional[str] = None, + if_modified_since: Optional[str] = None, + show_headers: bool = False, + show_status_chain: bool = False, + no_content: bool = False, + force: bool = False, + output_format: Optional[str] = None, + input_data: Optional[str] = None, +) -> Dict[str, Any]: + """ + Execute an HTTP request using lwp-request (libwww-perl). + + Args: + url: The URL to request. + method: HTTP method (GET, POST, HEAD, PUT, DELETE). + timeout: Timeout in seconds (default 180). + content_type: Set the Content-Type header for the request (-c). + headers: List of additional headers in 'Header: Value' format (-H). + proxy: Proxy URL to use (-p). + credentials: User credentials in 'user:pass' format (-C). + base_url: Base URL for relative links (-b). + if_modified_since: If-Modified-Since time (-i). + show_headers: Display response headers (-e). + show_status_chain: Show the response status chain (-S). + no_content: Do not display the response content (-d). + force: Force the request (-f). + output_format: Output format (e.g., 'html', 'text', 'ps', 'dump') (-o). + input_data: Data to be sent in the request body (for POST/PUT). + """ + if not url: + raise ValueError("URL is required") + if timeout <= 0: + raise ValueError("Timeout must be a positive integer") + + cmd = ["lwp-request", "-m", method.upper(), "-t", str(timeout)] + + if content_type: + cmd.extend(["-c", content_type]) + if headers: + for h in headers: + cmd.extend(["-H", h]) + if proxy: + cmd.extend(["-p", proxy]) + if credentials: + cmd.extend(["-C", credentials]) + if base_url: + cmd.extend(["-b", base_url]) + if if_modified_since: + cmd.extend(["-i", if_modified_since]) + if show_headers: + cmd.append("-e") + if show_status_chain: + cmd.append("-S") + if no_content: + cmd.append("-d") + if force: + cmd.append("-f") + if output_format: + cmd.extend(["-o", output_format]) + + cmd.append(url) + + return _run_command(cmd, input_data) + +@mcp.tool() +def lwp_get( + url: str, + timeout: int = 180, + headers: Optional[List[str]] = None, + proxy: Optional[str] = None, + show_headers: bool = False, + no_content: bool = False, +) -> Dict[str, Any]: + """ + Send a GET request using the GET utility (alias for lwp-request). + + Args: + url: The URL to request. + timeout: Timeout in seconds (default 180). + headers: List of additional headers in 'Header: Value' format. + proxy: Proxy URL to use. + show_headers: Display response headers. + no_content: Do not display response content. + """ + if not url: + raise ValueError("URL is required") + + cmd = ["GET", "-t", str(timeout)] + if headers: + for h in headers: + cmd.extend(["-H", h]) + if proxy: + cmd.extend(["-p", proxy]) + if show_headers: + cmd.append("-e") + if no_content: + cmd.append("-d") + + cmd.append(url) + return _run_command(cmd) + +@mcp.tool() +def lwp_post( + url: str, + input_data: str, + content_type: str = "application/x-www-form-urlencoded", + timeout: int = 180, + headers: Optional[List[str]] = None, + proxy: Optional[str] = None, + show_headers: bool = False, +) -> Dict[str, Any]: + """ + Send a POST request using the POST utility (alias for lwp-request). + + Args: + url: The URL to request. + input_data: Data to be sent in the request body. + content_type: Content-Type of the input data (default application/x-www-form-urlencoded). + timeout: Timeout in seconds (default 180). + headers: List of additional headers in 'Header: Value' format. + proxy: Proxy URL to use. + show_headers: Display response headers. + """ + if not url: + raise ValueError("URL is required") + + cmd = ["POST", "-t", str(timeout), "-c", content_type] + if headers: + for h in headers: + cmd.extend(["-H", h]) + if proxy: + cmd.extend(["-p", proxy]) + if show_headers: + cmd.append("-e") + + cmd.append(url) + return _run_command(cmd, input_data) + +@mcp.tool() +def lwp_head( + url: str, + timeout: int = 180, + headers: Optional[List[str]] = None, + proxy: Optional[str] = None, +) -> Dict[str, Any]: + """ + Send a HEAD request using the HEAD utility (alias for lwp-request). + + Args: + url: The URL to request. + timeout: Timeout in seconds (default 180). + headers: List of additional headers in 'Header: Value' format. + proxy: Proxy URL to use. + """ + if not url: + raise ValueError("URL is required") + + cmd = ["HEAD", "-t", str(timeout)] + if headers: + for h in headers: + cmd.extend(["-H", h]) + if proxy: + cmd.extend(["-p", proxy]) + + cmd.append(url) + return _run_command(cmd) + +@mcp.tool() +def lwp_download( + url: str, + output_file: Optional[str] = None, +) -> Dict[str, Any]: + """ + Download a file from a URL using lwp-download. + + Args: + url: The URL to download. + output_file: Optional local filename to save the content. + """ + if not url: + raise ValueError("URL is required") + + cmd = ["lwp-download", url] + if output_file: + out_path = Path(output_file) + if not out_path.parent.exists(): + raise ValueError(f"Parent directory does not exist: {out_path.parent}") + cmd.append(output_file) + + return _run_command(cmd) + +@mcp.tool() +def lwp_mirror( + url: str, + output_file: str, +) -> Dict[str, Any]: + """ + Mirror a URL to a local file using lwp-mirror. + Only downloads if the remote file is newer than the local copy. + + Args: + url: The URL to mirror. + output_file: Local filename to save the content. + """ + if not url: + raise ValueError("URL is required") + if not output_file: + raise ValueError("Output file path is required") + + out_path = Path(output_file) + if not out_path.parent.exists(): + raise ValueError(f"Parent directory does not exist: {out_path.parent}") + + cmd = ["lwp-mirror", url, output_file] + return _run_command(cmd) + +@mcp.tool() +def lwp_dump( + url: str, +) -> Dict[str, Any]: + """ + Dump the structure of a resource using lwp-dump. + + Args: + url: The URL to dump. + """ + if not url: + raise ValueError("URL is required") + + cmd = ["lwp-dump", url] + return _run_command(cmd) + +@mcp.tool() +def lwp_version() -> Dict[str, Any]: + """Get the version of libwww-perl.""" + return _run_command(["lwp-request", "-v"]) + +@mcp.tool() +def perl_lwp_execute( + script_content: str, + args: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Execute a Perl script that uses libwww-perl (LWP). + + Args: + script_content: The Perl code to execute. + args: Optional command line arguments for the script. + """ + if not script_content: + raise ValueError("Script content is required") + + # Prepend common LWP modules if not explicitly present in the snippet + if "use LWP" not in script_content: + script_content = "use LWP::UserAgent; use HTTP::Request; use HTTP::Response; " + script_content + + cmd = ["perl", "-e", script_content] + if args: + cmd.extend(args) + + return _run_command(cmd) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_shim_server.py b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..6ca1edd195381af4d4fe771b633505adfa24719b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/perl-libwww-perl_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-libwww-perl/app/perl-libwww-perl_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_libwww_perl' + + +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-libwww-perl/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-libwww-perl/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-libwww-perl/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..34c1c90142d2332cc38a74838f37d7c16419ab0c --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-libwww-perl: + build: . + image: mcp-perl-libwww-perl:latest + container_name: mcp-perl-libwww-perl + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-libwww-perl + 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-libwww-perl/environment.yaml b/Biomni/mcp_generated/mcp_perl-libwww-perl/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1a8e865e1f758d15ebd21393f2c36578818a3a6a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-libwww-perl + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-libwww-perl/requirements.txt b/Biomni/mcp_generated/mcp_perl-libwww-perl/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-libwww-perl/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_shim_server.py b/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f17623fcb02c6f3021d9742a7d0cc48778dab6a9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/app/perl-mailtools_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-mailtools/app/perl-mailtools_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_mailtools' + + +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-mailtools/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-mailtools/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-mailtools/environment.yaml b/Biomni/mcp_generated/mcp_perl-mailtools/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..17030d44043e37b09a99876150bc0e06516d254d --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mailtools/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-mailtools + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-math-spline/Dockerfile b/Biomni/mcp_generated/mcp_perl-math-spline/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f65e692434c73f1aa389475f19cd01851794b062 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/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-math-spline via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-math-spline -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-math-spline_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-math-spline_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-math-spline_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-math-spline/app/perl-math-spline_shim_server.py b/Biomni/mcp_generated/mcp_perl-math-spline/app/perl-math-spline_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1e510b1953b6014f39f70eff7682ea167813d523 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/app/perl-math-spline_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-math-spline/app/perl-math-spline_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_math_spline' + + +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-math-spline/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-math-spline/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-math-spline/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-math-spline/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3db92fca86f0596702e09e56191f890e24455f07 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-math-spline: + build: . + image: mcp-perl-math-spline:latest + container_name: mcp-perl-math-spline + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-math-spline + 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-math-spline/environment.yaml b/Biomni/mcp_generated/mcp_perl-math-spline/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b022697aea26aab714283873cdf92c4f7a58d9ba --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-math-spline + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-math-spline/requirements.txt b/Biomni/mcp_generated/mcp_perl-math-spline/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-math-spline/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-mime-lite/Dockerfile b/Biomni/mcp_generated/mcp_perl-mime-lite/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d6e2422a560e4b54f9efa545020651aea67081ef --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/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-mime-lite via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-mime-lite -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-mime-lite_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-mime-lite_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-mime-lite_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_server.py b/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_server.py new file mode 100644 index 0000000000000000000000000000000000000000..ebbefb66c9e168629e6feb5c9e425c863142502b --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_server.py @@ -0,0 +1,269 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_mime_lite' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def mime_lite_send_email( + sender: str, + recipient: str, + subject: str, + body: str, + content_type: str = "text/plain", + cc: str = "", + bcc: str = "", + attachments: Optional[List[str]] = None, + send_method: str = "sendmail", + smtp_server: str = "localhost", +) -> Dict[str, Any]: + """ + Send an email with optional attachments using Perl's MIME::Lite. + + Args: + sender: Email address of the sender (From). + recipient: Email address of the recipient (To). + subject: Subject line of the email. + body: The main text content of the email. + content_type: MIME type of the body (e.g., 'text/plain' or 'text/html'). + cc: Carbon copy recipients (comma-separated). + bcc: Blind carbon copy recipients (comma-separated). + attachments: List of file paths to attach to the email. + send_method: Method to send the mail ('sendmail' or 'smtp'). + smtp_server: SMTP server address (only used if send_method is 'smtp'). + """ + # Validate inputs + if not sender or "@" not in sender: + return {"error": "Invalid sender email address."} + if not recipient or "@" not in recipient: + return {"error": "Invalid recipient email address."} + + # Prepare attachment logic for Perl script + attachment_code = "" + if attachments: + for file_path in attachments: + path = Path(file_path) + if not path.exists(): + return {"error": f"Attachment file not found: {file_path}"} + + # Escape single quotes for Perl string + safe_path = str(path).replace("'", "\\'") + attachment_code += f""" +$msg->attach( + Type => 'AUTO', + Path => '{safe_path}', + Filename => '{path.name}', + Disposition => 'attachment' +); +""" + + # Determine sending logic + if send_method.lower() == "smtp": + send_logic = f"MIME::Lite->send('smtp', '{smtp_server}', Timeout=>60);" + else: + send_logic = "MIME::Lite->send('sendmail');" + + # Construct Perl script + # We use double quotes for Perl variables and escape them where necessary + perl_script = f""" +use MIME::Lite; + +my $msg = MIME::Lite->new( + From => '{sender}', + To => '{recipient}', + Cc => '{cc}', + Bcc => '{bcc}', + Subject => '{subject}', + Type => '{content_type}', + Data => '{body}' +); + +{attachment_code} + +{send_logic} +$msg->send(); +""" + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.pl', delete=False) as tmp: + tmp.write(perl_script) + tmp_path = tmp.name + + cmd = ["perl", tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Cleanup + Path(tmp_path).unlink() + + return { + "command_executed": "perl ", + "status": "success", + "stdout": result.stdout, + "stderr": result.stderr + } + + except subprocess.CalledProcessError as e: + return { + "command_executed": "perl ", + "status": "error", + "stdout": e.stdout, + "stderr": e.stderr, + "exit_code": e.returncode + } + except Exception as e: + return {"error": str(e)} + +@mcp.tool() +def mime_lite_generate_source( + sender: str, + recipient: str, + subject: str, + body: str, + content_type: str = "text/plain", + attachments: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Generate the raw MIME source text for an email without sending it. + Useful for debugging or manual inspection of the MIME structure. + + Args: + sender: Email address of the sender. + recipient: Email address of the recipient. + subject: Subject line. + body: Main content. + content_type: MIME type of the body. + attachments: List of file paths to attach. + """ + attachment_code = "" + if attachments: + for file_path in attachments: + path = Path(file_path) + if not path.exists(): + return {"error": f"Attachment file not found: {file_path}"} + safe_path = str(path).replace("'", "\\'") + attachment_code += f"$msg->attach(Type=>'AUTO', Path=>'{safe_path}');\n" + + perl_script = f""" +use MIME::Lite; +my $msg = MIME::Lite->new( + From => '{sender}', + To => '{recipient}', + Subject => '{subject}', + Type => '{content_type}', + Data => '{body}' +); +{attachment_code} +print $msg->as_string; +""" + + try: + result = subprocess.run( + ["perl", "-e", perl_script], + capture_output=True, + text=True, + check=True + ) + return { + "command_executed": "perl -e '...'", + "mime_source": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "error": "Failed to generate MIME source", + "stderr": e.stderr + } + +@mcp.tool() +def mime_lite_check_installation() -> Dict[str, Any]: + """ + Verify if the MIME::Lite Perl module is correctly installed and accessible. + """ + try: + # -MModule checks if module can be loaded + result = subprocess.run( + ["perl", "-MMIME::Lite", "-e", "print $MIME::Lite::VERSION"], + capture_output=True, + text=True, + check=True + ) + return { + "status": "installed", + "version": result.stdout.strip(), + "perl_info": subprocess.run(["perl", "-v"], capture_output=True, text=True).stdout.splitlines()[1] + } + except subprocess.CalledProcessError: + return { + "status": "not_found", + "error": "MIME::Lite is not installed in the current Perl environment." + } + +@mcp.tool() +def perl_interpreter_info( + show_config: bool = False, +) -> Dict[str, Any]: + """ + Get information about the Perl interpreter and its configuration. + Equivalent to 'perl -v' or 'perl -V'. + + Args: + show_config: If True, runs 'perl -V' to show full configuration. If False, runs 'perl -v'. + """ + flag = "-V" if show_config else "-v" + try: + result = subprocess.run(["perl", flag], capture_output=True, text=True, check=True) + return { + "command": f"perl {flag}", + "output": result.stdout + } + except subprocess.CalledProcessError as e: + return {"error": e.stderr} + +@mcp.tool() +def mime_lite_advanced_execute( + script_content: str, + include_paths: Optional[List[str]] = None, +) -> Dict[str, Any]: + """ + Execute a custom Perl script that utilizes MIME::Lite. + Use this for complex MIME operations not covered by standard tools. + + Args: + script_content: The full Perl script content. + include_paths: Optional list of directories to add to @INC (Perl's library path). + """ + cmd = ["perl"] + + if include_paths: + for path in include_paths: + cmd.extend(["-I", path]) + + try: + with tempfile.NamedTemporaryFile(mode='w', suffix='.pl', delete=False) as tmp: + tmp.write(script_content) + tmp_path = tmp.name + + cmd.append(tmp_path) + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + Path(tmp_path).unlink() + + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "status": "error", + "stdout": e.stdout, + "stderr": e.stderr, + "exit_code": e.returncode + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_shim_server.py b/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..59a74c16990d0becf83f2d2c75b977519a29894f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/app/perl-mime-lite_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-mime-lite/app/perl-mime-lite_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_mime_lite' + + +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-mime-lite/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-mime-lite/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-mime-lite/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-mime-lite/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5044c28dd7b7b7897733b7eddaad46bae45dad72 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-mime-lite: + build: . + image: mcp-perl-mime-lite:latest + container_name: mcp-perl-mime-lite + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-mime-lite + 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-mime-lite/environment.yaml b/Biomni/mcp_generated/mcp_perl-mime-lite/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b73e77534a07fc4ca37c416de5d197d3afdb4280 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-mime-lite + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-mime-lite/requirements.txt b/Biomni/mcp_generated/mcp_perl-mime-lite/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-mime-lite/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/Dockerfile b/Biomni/mcp_generated/mcp_perl-module-load-conditional/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..826c4a07b1387a2463dedae2af6fc771f7046c79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/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-load-conditional via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-module-load-conditional -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-load-conditional_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-load-conditional_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-load-conditional_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_server.py b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_server.py new file mode 100644 index 0000000000000000000000000000000000000000..542af5c4d680e5de675531f7a7ac5bf3674e590a --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_server.py @@ -0,0 +1,297 @@ +import subprocess +import json +import tempfile +import os +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Helper function to run Perl scripts +def _run_perl_script(script_content: str, env_vars: Optional[Dict[str, str]] = None) -> Dict[str, Any]: + """ + Executes a Perl script and captures its stdout/stderr. + + Args: + script_content: The Perl script content as a string. + env_vars: A dictionary of environment variables to set for the Perl process. + + Returns: + A dictionary containing execution details, stdout, stderr, and parsed output. + """ + script_path: Optional[Path] = None + try: + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".pl") as tmp_script: + tmp_script.write(script_content) + script_path = Path(tmp_script.name) + + command = ["perl", str(script_path)] + + # Prepare environment variables for the subprocess + full_env = os.environ.copy() + if env_vars: + full_env.update(env_vars) + + process = subprocess.run( + command, + capture_output=True, + text=True, + check=True, + env=full_env + ) + stdout = process.stdout.strip() + stderr = process.stderr.strip() + + # Attempt to parse JSON output + parsed_output: Any + try: + parsed_output = json.loads(stdout) + except json.JSONDecodeError: + parsed_output = {"raw_stdout": stdout} # Fallback if not JSON + + return { + "command_executed": " ".join(command), + "stdout": stdout, + "stderr": stderr, + "output": parsed_output, + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Perl script execution failed with exit code {e.returncode}", + "returncode": e.returncode, + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": "Error: 'perl' executable not found. Please ensure Perl is installed and in your PATH.", + "error": "Perl executable not found", + "returncode": 127, + "output_files": [] + } + finally: + # Clean up the temporary script file + if script_path and script_path.exists(): + os.remove(script_path) + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_module_load_conditional' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def check_module_install( + module_name: str, + min_version: Optional[str] = None, + verbose: bool = False, + find_version: bool = True, + check_inc_hash: bool = False, + force_safe_inc: bool = False, + check_deprecated: bool = False, +) -> Dict[str, Any]: + """ + Verifies if a Perl module is installed and optionally checks its version using Module::Load::Conditional. + + Args: + module_name: The name of the Perl module to verify (e.g., 'LWP'). + min_version: The minimum version the module needs to be. If not provided, any version is considered good. + verbose: Controls whether Module::Load::Conditional will issue warnings and explanations. + Defaults to False. + find_version: Controls whether Module::Load::Conditional will try to parse (and eval) + the version from the module. Set to False for security/performance if version + comparisons are not needed. Defaults to True. + check_inc_hash: Controls whether Module::Load::Conditional checks the %INC hash + (already loaded modules) in addition to @INC (filesystem). Defaults to False. + force_safe_inc: Controls whether Module::Load::Conditional sanitizes @INC by removing ".". + Defaults to False. + check_deprecated: Controls whether Module::Load::Conditional checks if a dual-life core module + has been deprecated. If True, check_install will return false to 'uptodate' + if a deprecated dual-life module is loaded from $Config{privlibexp}. Defaults to False. + + Returns: + A dictionary containing execution details, stdout, stderr, and parsed output. + The 'output' key will contain a dictionary with 'file', 'dir', 'version', 'uptodate' + if the module is found, or an 'error' key if not. + """ + if not module_name: + raise ValueError("Module name cannot be empty.") + + perl_script_template = """ + use strict; + use warnings; + use JSON; + use Module::Load::Conditional qw[check_install]; + + $Module::Load::Conditional::VERBOSE = $ENV{MCP_VERBOSE}; + $Module::Load::Conditional::FIND_VERSION = $ENV{MCP_FIND_VERSION}; + $Module::Load::Conditional::CHECK_INC_HASH = $ENV{MCP_CHECK_INC_HASH}; + $Module::Load::Conditional::FORCE_SAFE_INC = $ENV{MCP_FORCE_SAFE_INC}; + $Module::Load::Conditional::DEPRECATED = $ENV{MCP_DEPRECATED}; + + my %%args = ( module => $ENV{MCP_MODULE_NAME} ); + if (defined $ENV{MCP_MIN_VERSION} && $ENV{MCP_MIN_VERSION} ne '') { + $args{version} = $ENV{MCP_MIN_VERSION}; + } + + my $result = check_install(%%args); + + if (defined $result) { + print encode_json($result) . "\\n"; + } else { + print encode_json({ error => "Module '$ENV{MCP_MODULE_NAME}' not found or could not be installed." }) . "\\n"; + } + """ + + env_vars = { + "MCP_MODULE_NAME": module_name, + "MCP_MIN_VERSION": min_version if min_version is not None else "", + "MCP_VERBOSE": "1" if verbose else "0", + "MCP_FIND_VERSION": "1" if find_version else "0", + "MCP_CHECK_INC_HASH": "1" if check_inc_hash else "0", + "MCP_FORCE_SAFE_INC": "1" if force_safe_inc else "0", + "MCP_DEPRECATED": "1" if check_deprecated else "0", + } + + return _run_perl_script(perl_script_template, env_vars) + +@mcp.tool() +def can_load_modules( + modules: Dict[str, Optional[str]], + verbose: bool = False, + nocache: bool = False, + autoload: bool = False, + check_inc_hash: bool = False, + force_safe_inc: bool = False, + clear_cache: bool = False, +) -> Dict[str, Any]: + """ + Determines if a list of Perl modules can be loaded, and loads them if all are possible. + If one or more are unloadable, none will be loaded. + + Args: + modules: A dictionary where keys are module names (e.g., 'CPANPLUS') and values + are optional minimum version strings (e.g., '0.05') or None. + verbose: Controls whether Module::Load::Conditional will issue warnings and explanations. + Defaults to False. + nocache: If True, can_load will not use its internal cache and will attempt to load + modules even if they previously failed or were already loaded. Defaults to False. + autoload: If True, imports the functions of loaded modules into the caller package. + Defaults to False. + check_inc_hash: Controls whether Module::Load::Conditional checks the %INC hash + (already loaded modules) in addition to @INC (filesystem). Defaults to False. + force_safe_inc: Controls whether Module::Load::Conditional sanitizes @INC by removing ".". + Defaults to False. + clear_cache: If True, clears the internal cache of can_load before execution. Defaults to False. + + Returns: + A dictionary containing execution details, stdout, stderr, and parsed output. + The 'output' key will contain a dictionary with 'success' (boolean) and 'error' (string, if any). + """ + if not modules: + raise ValueError("The 'modules' dictionary cannot be empty.") + if not isinstance(modules, dict): + raise TypeError("The 'modules' argument must be a dictionary.") + for module_name, version in modules.items(): + if not isinstance(module_name, str): + raise ValueError(f"Module name '{module_name}' must be a string.") + if version is not None and not isinstance(version, str): + raise ValueError(f"Version for module '{module_name}' must be a string or None.") + + modules_json = json.dumps(modules) + + perl_script_template = """ + use strict; + use warnings; + use JSON; + use Module::Load::Conditional qw[can_load]; + + $Module::Load::Conditional::VERBOSE = $ENV{MCP_VERBOSE}; + $Module::Load::Conditional::CHECK_INC_HASH = $ENV{MCP_CHECK_INC_HASH}; + $Module::Load::Conditional::FORCE_SAFE_INC = $ENV{MCP_FORCE_SAFE_INC}; + + if ($ENV{MCP_CLEAR_CACHE}) { + undef $Module::Load::Conditional::CACHE; + } + + my $modules_hash_ref = decode_json($ENV{MCP_MODULES_JSON}); + my %%args = ( modules => $modules_hash_ref ); + if ($ENV{MCP_NOCACHE}) { + $args{nocache} = 1; + } + if ($ENV{MCP_AUTOLOAD}) { + $args{autoload} = 1; + } + + my $result = can_load(%%args); + + print encode_json({ success => $result ? 1 : 0, error => $Module::Load::Conditional::ERROR }) . "\\n"; + """ + + env_vars = { + "MCP_MODULES_JSON": modules_json, + "MCP_VERBOSE": "1" if verbose else "0", + "MCP_NOCACHE": "1" if nocache else "0", + "MCP_AUTOLOAD": "1" if autoload else "0", + "MCP_CHECK_INC_HASH": "1" if check_inc_hash else "0", + "MCP_FORCE_SAFE_INC": "1" if force_safe_inc else "0", + "MCP_CLEAR_CACHE": "1" if clear_cache else "0", + } + + return _run_perl_script(perl_script_template, env_vars) + +@mcp.tool() +def get_module_requirements( + module_name: str, + check_inc_hash: bool = False, + force_safe_inc: bool = False, +) -> Dict[str, Any]: + """ + Retrieves a list of other modules that a particular Perl module requires. + + Args: + module_name: The name of the Perl module to query for requirements. + check_inc_hash: Controls whether Module::Load::Conditional checks the %INC hash + (already loaded modules) in addition to @INC (filesystem). Defaults to False. + force_safe_inc: Controls whether Module::Load::Conditional sanitizes @INC by removing ".". + Defaults to False. + + Returns: + A dictionary containing execution details, stdout, stderr, and parsed output. + The 'output' key will contain a dictionary with a 'requires' list of module names, + or an 'error' key if requirements could not be determined. + """ + if not module_name: + raise ValueError("Module name cannot be empty.") + + perl_script_template = """ + use strict; + use warnings; + use JSON; + use Module::Load::Conditional qw[requires]; + + $Module::Load::Conditional::CHECK_INC_HASH = $ENV{MCP_CHECK_INC_HASH}; + $Module::Load::Conditional::FORCE_SAFE_INC = $ENV{MCP_FORCE_SAFE_INC}; + + my $module_name = $ENV{MCP_MODULE_NAME}; + my @list = requires($module_name); + + if (defined @list) { + print encode_json({ requires => \\@list }) . "\\n"; + } else { + print encode_json({ error => "Could not determine requirements for module '$module_name'. It might not be installed or loadable." }) . "\\n"; + } + """ + + env_vars = { + "MCP_MODULE_NAME": module_name, + "MCP_CHECK_INC_HASH": "1" if check_inc_hash else "0", + "MCP_FORCE_SAFE_INC": "1" if force_safe_inc else "0", + } + + return _run_perl_script(perl_script_template, env_vars) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_shim_server.py b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..bfb33df1bf74acff1bdc4c84d1d269304788b00f --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/perl-module-load-conditional_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-load-conditional/app/perl-module-load-conditional_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_module_load_conditional' + + +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-load-conditional/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-module-load-conditional/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..0e2d34cd17c901164b412fdffd779fa7b952de14 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-module-load-conditional: + build: . + image: mcp-perl-module-load-conditional:latest + container_name: mcp-perl-module-load-conditional + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-module-load-conditional + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/environment.yaml b/Biomni/mcp_generated/mcp_perl-module-load-conditional/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..324df456d09bee71c32c59a9e44110a83614df53 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-module-load-conditional + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-module-load-conditional/requirements.txt b/Biomni/mcp_generated/mcp_perl-module-load-conditional/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-module-load-conditional/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-ole-storage_lite/Dockerfile b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f9ec26503ae19d0dea8bf9eb2d40b8f0d558a466 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/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-ole-storage_lite via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-ole-storage_lite -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-ole-storage_lite_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-ole-storage_lite_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-ole-storage_lite_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-ole-storage_lite/app/perl-ole-storage_lite_shim_server.py b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/app/perl-ole-storage_lite_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0e1a2989e7f1c15a6b2913df57de2b4e24db1e00 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/app/perl-ole-storage_lite_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-ole-storage_lite/app/perl-ole-storage_lite_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_ole_storage_lite' + + +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-ole-storage_lite/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-ole-storage_lite/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..aedb207f7bc7a93da7b717ea41778d9fd3b49f79 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-ole-storage_lite: + build: . + image: mcp-perl-ole-storage_lite:latest + container_name: mcp-perl-ole-storage_lite + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-ole-storage_lite + 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-ole-storage_lite/environment.yaml b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bd5d5a0e7ea9b8cd5f819aa5d89d9c6f067bd77 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-ole-storage_lite/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-ole-storage_lite + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sereal-decoder/Dockerfile b/Biomni/mcp_generated/mcp_perl-sereal-decoder/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4a31d52f12e79e4bf101df5121d804b94b96d5fc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal-decoder/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-decoder via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-sereal-decoder -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-decoder_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-decoder_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-decoder_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sereal-decoder/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-sereal-decoder/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..f4f9a5950fbb5a750c98b82e5919bb97635214be --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal-decoder/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-sereal-decoder: + build: . + image: mcp-perl-sereal-decoder:latest + container_name: mcp-perl-sereal-decoder + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-sereal-decoder + 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-decoder/requirements.txt b/Biomni/mcp_generated/mcp_perl-sereal-decoder/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sereal-decoder/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/Dockerfile b/Biomni/mcp_generated/mcp_perl-sub-exporter/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..bc003e9a1a13fd6b9afce6b9ad9bcb7cd35696a2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-sub-exporter via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-sub-exporter -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-sub-exporter_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-sub-exporter_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-sub-exporter_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_server.py b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_server.py new file mode 100644 index 0000000000000000000000000000000000000000..2a4bfd5b362b1e72c0183269bed5da85a28e22eb --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_server.py @@ -0,0 +1,280 @@ +import subprocess +from pathlib import Path +from typing import List, Optional, Union + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_sub_exporter' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perl_run_script( + script_path: str, + args: Optional[List[str]] = None, + modules: Optional[List[str]] = None, + modules_no_import: Optional[List[str]] = None, + include_dirs: Optional[List[str]] = None, + warnings: bool = False, + all_warnings: bool = False, + disable_warnings: bool = False, + taint_checks: bool = False, + taint_warnings: bool = False, + loop_n: bool = False, + loop_p: bool = False, + autosplit: bool = False, + split_pattern: Optional[str] = None, + inplace_extension: Optional[str] = None, + record_separator: Optional[str] = None, + line_ending_processing: Optional[str] = None, + unicode_features: Optional[str] = None, + debug_flags: Optional[str] = None, + debugger: Optional[str] = None, + no_site_customize: bool = False, + switch_parsing: bool = False, + search_path: bool = False, + unsafe_operations: bool = False, + extract_directory: Optional[str] = None, +): + """ + Executes a Perl script with the specified switches and arguments. + This environment includes the 'Sub::Exporter' module for sophisticated routine exporting. + """ + script = Path(script_path) + if not script.exists(): + return {"error": f"Script file not found: {script_path}"} + + cmd = ["perl"] + + # Boolean switches + if warnings: cmd.append("-w") + if all_warnings: cmd.append("-W") + if disable_warnings: cmd.append("-X") + if taint_checks: cmd.append("-T") + if taint_warnings: cmd.append("-t") + if loop_n: cmd.append("-n") + if loop_p: cmd.append("-p") + if autosplit: cmd.append("-a") + if no_site_customize: cmd.append("-f") + if switch_parsing: cmd.append("-s") + if search_path: cmd.append("-S") + if unsafe_operations: cmd.append("-U") + + # Parameterized switches + if modules: + for m in modules: + cmd.extend(["-M", m]) + if modules_no_import: + for m in modules_no_import: + cmd.extend(["-m", m]) + if include_dirs: + for d in include_dirs: + cmd.extend(["-I", d]) + if split_pattern: + cmd.append(f"-F{split_pattern}") + if inplace_extension is not None: + cmd.append(f"-i{inplace_extension}") + if record_separator is not None: + cmd.append(f"-0{record_separator}") + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if unicode_features: + cmd.append(f"-C{unicode_features}") + if debug_flags: + cmd.append(f"-D{debug_flags}") + if debugger: + cmd.append(f"-d{debugger}") + if extract_directory: + cmd.append(f"-x{extract_directory}") + + cmd.append(str(script)) + if args: + cmd.extend(args) + + 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": args if inplace_extension is not None else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "exit_code": e.returncode, + "error": "Perl script execution failed" + } + +@mcp.tool() +def perl_run_one_liner( + code: str, + use_extended_features: bool = False, + args: Optional[List[str]] = None, + modules: Optional[List[str]] = None, + modules_no_import: Optional[List[str]] = None, + include_dirs: Optional[List[str]] = None, + warnings: bool = False, + all_warnings: bool = False, + disable_warnings: bool = False, + taint_checks: bool = False, + taint_warnings: bool = False, + loop_n: bool = False, + loop_p: bool = False, + autosplit: bool = False, + split_pattern: Optional[str] = None, + inplace_extension: Optional[str] = None, + record_separator: Optional[str] = None, + line_ending_processing: Optional[str] = None, + unicode_features: Optional[str] = None, + no_site_customize: bool = False, +): + """ + Executes a one-line Perl program using the -e or -E switch. + Useful for quick tasks or testing Sub::Exporter functionality. + """ + cmd = ["perl"] + + # Use -E for extended features, -e for standard + exec_switch = "-E" if use_extended_features else "-e" + + if warnings: cmd.append("-w") + if all_warnings: cmd.append("-W") + if disable_warnings: cmd.append("-X") + if taint_checks: cmd.append("-T") + if taint_warnings: cmd.append("-t") + if loop_n: cmd.append("-n") + if loop_p: cmd.append("-p") + if autosplit: cmd.append("-a") + if no_site_customize: cmd.append("-f") + + if modules: + for m in modules: + cmd.extend(["-M", m]) + if modules_no_import: + for m in modules_no_import: + cmd.extend(["-m", m]) + if include_dirs: + for d in include_dirs: + cmd.extend(["-I", d]) + if split_pattern: + cmd.append(f"-F{split_pattern}") + if inplace_extension is not None: + cmd.append(f"-i{inplace_extension}") + if record_separator is not None: + cmd.append(f"-0{record_separator}") + if line_ending_processing is not None: + cmd.append(f"-l{line_ending_processing}") + if unicode_features: + cmd.append(f"-C{unicode_features}") + + cmd.extend([exec_switch, code]) + if args: + cmd.extend(args) + + 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": args if inplace_extension is not None else [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "exit_code": e.returncode, + "error": "Perl one-liner execution failed" + } + +@mcp.tool() +def perl_check_syntax( + script_path: str, + modules: Optional[List[str]] = None, + include_dirs: Optional[List[str]] = None, +): + """ + Checks the syntax of a Perl script without executing it (runs BEGIN and CHECK blocks). + """ + script = Path(script_path) + if not script.exists(): + return {"error": f"Script file not found: {script_path}"} + + cmd = ["perl", "-c"] + + if modules: + for m in modules: + cmd.extend(["-M", m]) + if include_dirs: + for d in include_dirs: + cmd.extend(["-I", d]) + + cmd.append(str(script)) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "status": "Syntax OK" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "exit_code": e.returncode, + "status": "Syntax Error" + } + +@mcp.tool() +def perl_get_version(): + """ + Prints the Perl version, patchlevel, and license information. + """ + cmd = ["perl", "-v"] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": "Failed to retrieve Perl version" + } + +@mcp.tool() +def perl_get_config( + variable: Optional[str] = None, +): + """ + Prints the Perl configuration summary or a single Config.pm variable. + """ + cmd = ["perl"] + if variable: + cmd.append(f"-V:{variable}") + else: + cmd.append("-V") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "error": f"Failed to retrieve Perl configuration for {variable if variable else 'all'}" + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_shim_server.py b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e37bc28868cc5171f655740134537db2066bd802 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/perl-sub-exporter_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-exporter/app/perl-sub-exporter_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_sub_exporter' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-sub-exporter/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..99c71a73b0bdb593c3e3b0668141f85bf8729b97 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-sub-exporter: + build: . + image: mcp-perl-sub-exporter:latest + container_name: mcp-perl-sub-exporter + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-sub-exporter + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/environment.yaml b/Biomni/mcp_generated/mcp_perl-sub-exporter/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2af72aa92102087096e4c789fe954280e0955855 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-sub-exporter + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-sub-exporter/requirements.txt b/Biomni/mcp_generated/mcp_perl-sub-exporter/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-sub-exporter/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-svg/requirements.txt b/Biomni/mcp_generated/mcp_perl-svg/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-svg/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/Dockerfile b/Biomni/mcp_generated/mcp_perl-test-fatal/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..de84811826a5324ac7ee9dedc7217c88be9a57f9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/Dockerfile @@ -0,0 +1,40 @@ + +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install perl-test-fatal via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-test-fatal -y && conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/perl-test-fatal_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/perl-test-fatal_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/perl-test-fatal_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_server.py b/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_server.py new file mode 100644 index 0000000000000000000000000000000000000000..b76a58aaaceb6dd2760dea43083eb4f5084acf66 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_server.py @@ -0,0 +1,187 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# In a real MCP environment, this would be: from mcp import tool +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def perl_execute( + programfile: Optional[Path] = None, + arguments: Optional[List[str]] = None, + execute_program: Optional[List[str]] = None, + execute_program_features: 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, + in_place_edit: Optional[str] = None, + include_directory: Optional[List[Path]] = None, + use_module: Optional[List[str]] = None, + use_module_no_import: Optional[List[str]] = None, + no_module: Optional[List[str]] = None, + loop_around_program: bool = False, + loop_and_print: bool = False, + parse_switches: bool = False, + search_path: bool = False, + tainting_warnings: bool = False, + tainting_checks: bool = False, + dump_core: bool = False, + allow_unsafe: bool = False, + print_version: bool = False, + print_config: Optional[str] = None, + enable_warnings: bool = False, + enable_all_warnings: bool = False, + disable_all_warnings: bool = False, + extract_from_text: bool = False, + change_dir_after_extract: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Executes a Perl script or one-liner using the Perl interpreter. + + This tool is a wrapper for the `perl` command-line interpreter. The requested + package, `perl-test-fatal`, is a library module for testing exceptions in + Perl code and does not have its own executable. The provided help documentation + was for the `perl` interpreter itself. + + Args: + programfile: Path to the Perl program file to execute. + arguments: List of arguments to pass to the program file. + execute_program: One or more lines of a program passed on the command line (-e). + execute_program_features: Like -e, but enables all optional features (-E). + record_separator: Specify record separator, e.g., '0777' for slurp mode (-0). + autosplit: Enable autosplit mode with -n or -p (-a). + unicode_features: Enable listed Unicode features (-C). + check_syntax_only: Check syntax only, runs BEGIN and CHECK blocks (-c). + run_under_debugger: Run program under debugger. Can specify debugger, e.g., 'My::Debugger' (-d). + debugging_flags: Set debugging flags (-D). + no_sitecustomize: Don't do $sitelib/sitecustomize.pl at startup (-f). + split_pattern: Specify split() pattern for -a switch (-F). + in_place_edit: Edit <> files in place. Provide an extension for backup, e.g., '.bak' (-i). + include_directory: Specify @INC/#include directory. Can be used multiple times (-I). + use_module: Execute "use module..." before executing the program (-M). + use_module_no_import: Execute "use module ()" before executing the program (-m). + no_module: Execute "no module..." before executing the program (-M-module). + loop_around_program: Assume "while (<>) { ... }" loop around the program (-n). + loop_and_print: Assume loop like -n but also print line (-p). + parse_switches: Enable rudimentary parsing for switches after programfile (-s). + search_path: Look for programfile using the PATH environment variable (-S). + tainting_warnings: Enable tainting warnings (-t). + tainting_checks: Enable tainting checks (-T). + dump_core: Dump core after parsing the program (-u). + allow_unsafe: Allow unsafe operations (-U). + print_version: Print version, patchlevel, and license (-v). + print_config: Print configuration summary or a single variable (-V). + enable_warnings: Enable many useful warnings (-w). + enable_all_warnings: Enable all warnings (-W). + disable_all_warnings: Disable all warnings (-X). + extract_from_text: Ignore text before #!perl line (-x). + change_dir_after_extract: Optionally cd to this directory with -x. + + Returns: + A dictionary containing the executed command, stdout, stderr, and any errors. + """ + # --- Input Validation --- + script_sources = sum(p is not None for p in [programfile, execute_program, execute_program_features]) + info_flags = sum([print_version, print_config is not None]) + + if script_sources > 1: + raise ValueError("Only one of 'programfile', 'execute_program', or 'execute_program_features' can be specified at a time.") + + if script_sources == 0 and info_flags == 0: + raise ValueError("A program source must be provided via 'programfile', 'execute_program', 'execute_program_features', or an informational flag like 'print_version' or 'print_config'.") + + if programfile and not programfile.is_file(): + raise FileNotFoundError(f"The program file '{programfile}' does not exist.") + + warning_flags = sum([enable_warnings, enable_all_warnings, disable_all_warnings]) + if warning_flags > 1: + raise ValueError("Only one of 'enable_warnings' (-w), 'enable_all_warnings' (-W), or 'disable_all_warnings' (-X) can be used at a time.") + + if change_dir_after_extract and not extract_from_text: + extract_from_text = True + + # --- Command Construction --- + cmd = ["perl"] + + if record_separator is not None: cmd.append(f"-0{record_separator}") + if autosplit: cmd.append("-a") + if unicode_features is not None: cmd.append(f"-C{unicode_features}") + if check_syntax_only: cmd.append("-c") + if run_under_debugger is not None: cmd.append(f"-d:{run_under_debugger}" if run_under_debugger else "-d") + 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 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 use_module: + for module in use_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: + for module in no_module: cmd.append(f"-M-{module}") + if loop_and_print: cmd.append("-p") + elif loop_around_program: cmd.append("-n") + 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 allow_unsafe: cmd.append("-U") + if print_version: cmd.append("-v") + if print_config is not None: cmd.append(f"-V:{print_config}" if print_config else "-V") + if enable_warnings: cmd.append("-w") + if enable_all_warnings: cmd.append("-W") + if disable_all_warnings: cmd.append("-X") + if extract_from_text: + if change_dir_after_extract: cmd.extend(["-x", str(change_dir_after_extract)]) + else: cmd.append("-x") + + if execute_program: + for line in execute_program: cmd.extend(["-e", line]) + if execute_program_features: + for line in execute_program_features: cmd.extend(["-E", line]) + + if programfile: cmd.append(str(programfile)) + if arguments: + if programfile: cmd.append("--") + cmd.extend(arguments) + + # --- Subprocess Execution --- + command_executed = " ".join(cmd) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=False # Check manually to provide better error context + ) + if result.returncode != 0: + return { + "error": f"Perl execution failed with exit code {result.returncode}.", + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + } + except FileNotFoundError: + return { + "error": "Perl executable not found. Please ensure 'perl' is in your system's PATH.", + "command_executed": command_executed, + "stdout": "", + "stderr": "", + } + + # --- Structured Result Return --- + return { + "command_executed": command_executed, + "stdout": result.stdout, + "stderr": result.stderr, + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_shim_server.py b/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e07a553bbd46211d120dccc5da46b1d27a5ac373 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/app/perl-test-fatal_shim_server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import ast +from pathlib import Path + +from mcp.server.fastmcp import FastMCP + + +SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-fatal/app/perl-test-fatal_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_test_fatal' + + +class _ShimMCP: + @staticmethod + def tool(*args, **kwargs): + if args and callable(args[0]) and len(args) == 1 and not kwargs: + return args[0] + def _decorator(fn): + return fn + return _decorator + + +def _resolve_source_server(): + if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name: + return LOCAL_SERVER + return SOURCE_SERVER + + +def _load_functions(): + source_server = _resolve_source_server() + code = source_server.read_text(encoding="utf-8") + tree = ast.parse(code, filename=str(source_server)) + function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")] + namespace = { + "__name__": "__mcp_source__", + "mcp": _ShimMCP(), + } + exec(compile(code, str(source_server), "exec"), namespace, namespace) + loaded = [] + for name in function_names: + fn = namespace.get(name) + if callable(fn): + loaded.append(fn) + return loaded + + +mcp = FastMCP(SERVER_NAME) +for _fn in _load_functions(): + mcp.tool()(_fn) + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-test-fatal/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-test-fatal/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..76e5f4a2bbe4cfeb9ddf5f5ff03db9d3fc6965eb --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-test-fatal: + build: . + image: mcp-perl-test-fatal:latest + container_name: mcp-perl-test-fatal + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-test-fatal + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/environment.yaml b/Biomni/mcp_generated/mcp_perl-test-fatal/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..500370998d00299db5c6f4fb05f75a192e27d7cf --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-test-fatal + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-test-fatal/requirements.txt b/Biomni/mcp_generated/mcp_perl-test-fatal/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-test-fatal/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_perl-version/Dockerfile b/Biomni/mcp_generated/mcp_perl-version/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c3c92b834128ce0f0b432f07ef90b6df89edabbd --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/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-version via conda (e.g., from bioconda) +RUN conda install -c bioconda perl-version -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-version_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-version_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-version_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-version/app/perl-version_server.py b/Biomni/mcp_generated/mcp_perl-version/app/perl-version_server.py new file mode 100644 index 0000000000000000000000000000000000000000..36cdc30928d72cd53118e23202660ee9fdad1bb0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/app/perl-version_server.py @@ -0,0 +1,258 @@ +import subprocess +from typing import Optional, Dict, Any, List + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_perl_version' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def perl_version_parse( + version_string: str, +) -> Dict[str, Any]: + """ + Parses a version string into a structured version object using Perl's version->parse(). + This is the most flexible way to create a version object. + + Args: + version_string: The version string to parse (e.g., '1.2.3', 'v1.2', '1.002003'). + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + # Perl code to parse and print the stringified version object + perl_code = 'eval { my $v = version->parse($ARGV[0]); print $v }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "parsed_version": result.stdout.strip(), + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to parse version string: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_declare( + version_string: str, +) -> Dict[str, Any]: + """ + Creates a version object using Perl's version->declare(). + This is typically used to ensure a version is treated as a v-string (dotted-decimal). + + Args: + version_string: The version string to declare (e.g., '1.2.3'). + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + perl_code = 'eval { my $v = version->declare($ARGV[0]); print $v }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "declared_version": result.stdout.strip(), + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to declare version string: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_numify( + version_string: str, +) -> Dict[str, Any]: + """ + Returns the numeric representation (floating point) of a version string. + Example: 'v1.2.3' becomes '1.002003'. + + Args: + version_string: The version string to numify. + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + perl_code = 'eval { my $v = version->parse($ARGV[0]); print $v->numify }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "numified_version": result.stdout.strip(), + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to numify version string: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_normal( + version_string: str, +) -> Dict[str, Any]: + """ + Returns the normalized dotted-decimal representation of a version string. + Example: '1.2' becomes 'v1.200.0'. + + Args: + version_string: The version string to normalize. + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + perl_code = 'eval { my $v = version->parse($ARGV[0]); print $v->normal }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "normalized_version": result.stdout.strip(), + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to normalize version string: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_is_alpha( + version_string: str, +) -> Dict[str, Any]: + """ + Checks if a version string is considered an alpha release (contains an underscore). + + Args: + version_string: The version string to check. + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + perl_code = 'eval { my $v = version->parse($ARGV[0]); print $v->is_alpha ? "true" : "false" }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "is_alpha": result.stdout.strip() == "true", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to check alpha status for: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_is_qv( + version_string: str, +) -> Dict[str, Any]: + """ + Checks if a version string was parsed as a "quoted version" (v-string). + + Args: + version_string: The version string to check. + """ + if not version_string: + return {"error": "version_string cannot be empty"} + + perl_code = 'eval { my $v = version->parse($ARGV[0]); print $v->is_qv ? "true" : "false" }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", version_string] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "is_qv": result.stdout.strip() == "true", + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to check qv status for: {version_string}", + "output_files": [] + } + +@mcp.tool() +def perl_version_compare( + v1: str, + v2: str, +) -> Dict[str, Any]: + """ + Compares two version strings using Perl's version comparison logic. + Returns 1 if v1 > v2, -1 if v1 < v2, and 0 if they are equal. + + Args: + v1: The first version string. + v2: The second version string. + """ + if not v1 or not v2: + return {"error": "Both v1 and v2 must be provided"} + + perl_code = 'eval { my $obj1 = version->parse($ARGV[0]); my $obj2 = version->parse($ARGV[1]); print $obj1 <=> $obj2 }; if ($@) { warn $@; exit 1; }' + cmd = ["perl", "-Mversion", "-e", perl_code, "--", v1, v2] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + comparison_result = int(result.stdout.strip()) + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "comparison_result": comparison_result, + "interpretation": "v1 > v2" if comparison_result > 0 else ("v1 < v2" if comparison_result < 0 else "v1 == v2"), + "output_files": [] + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout.strip(), + "stderr": e.stderr.strip(), + "error": f"Failed to compare versions: {v1} and {v2}", + "output_files": [] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_perl-version/app/perl-version_shim_server.py b/Biomni/mcp_generated/mcp_perl-version/app/perl-version_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..3c1b4c98f76f060d891851559078194b7e61bde4 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/app/perl-version_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-version/app/perl-version_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_perl_version' + + +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-version/app/requirements.txt b/Biomni/mcp_generated/mcp_perl-version/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_perl-version/docker-compose.yml b/Biomni/mcp_generated/mcp_perl-version/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..39c75c4464e0c8d5726d15a8fc1d9704d7732fcc --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-perl-version: + build: . + image: mcp-perl-version:latest + container_name: mcp-perl-version + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=perl-version + 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-version/environment.yaml b/Biomni/mcp_generated/mcp_perl-version/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3000a31cd98c1304e99cb3a989389a0fea9d05b6 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - perl-version + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_perl-version/requirements.txt b/Biomni/mcp_generated/mcp_perl-version/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_perl-version/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_prokka/app/__pycache__/prokka_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_prokka/app/__pycache__/prokka_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac839638cbccaeb424c7950d5d8fdc9268596f3 Binary files /dev/null and b/Biomni/mcp_generated/mcp_prokka/app/__pycache__/prokka_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_server.py b/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_server.py new file mode 100644 index 0000000000000000000000000000000000000000..d190b3c78aafe7f66d973bb07648c2acfc25d8a2 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_server.py @@ -0,0 +1,205 @@ +import subprocess +from pathlib import Path +from typing import Optional, Literal, List + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_pyrodigal' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def pyrodigal_find_orfs( + input_file: Path, + output_file: Optional[Path] = None, + trans_file: Optional[Path] = None, + nuc_file: Optional[Path] = None, + start_file: Optional[Path] = None, + training_file: Optional[Path] = None, + closed_ends: bool = False, + output_format: Literal["gff", "gbk", "tbl", "sco"] = "gff", + translation_table: int = 11, + mask_regions: bool = False, + force_full_motif_scan: bool = False, + mode: Literal["single", "meta"] = "single", + jobs: int = 1, + min_gene_length: int = 90, + min_edge_gene_length: int = 60, + max_overlap: int = 60, + no_stop_codon_translation: bool = False, + pool_type: Literal["thread", "process"] = "thread", +) -> dict: + """ + Finds open reading frames (ORFs) in microbial sequences using Pyrodigal. + + This tool provides Python bindings to Prodigal, an ORF finder for microbial + sequences, allowing for flexible gene prediction with various output options + and parallel processing capabilities. + + Args: + input_file: Path to the FASTA input file containing nucleotide sequences. + output_file: Optional path to write the main output (e.g., GFF, GenBank). + If not provided, output will be written to stdout. + trans_file: Optional path to write protein translations in FASTA format. + nuc_file: Optional path to write nucleotide sequences of genes in FASTA format. + start_file: Optional path to write all potential genes with scores in tabular format. + training_file: Optional path to a training file. If the file does not exist, + Pyrodigal will write training data to it. If it exists, it will + read and use the specified training file. + closed_ends: If True, do not allow genes to run off sequence edges. + output_format: Select the output format for the main output file. + Supported formats: 'gff', 'gbk', 'tbl', 'sco'. + translation_table: Specify a translation table ID to use (e.g., 11 for bacterial). + Must be a positive integer. + mask_regions: If True, treat runs of 'N' as masked sequence and do not build + genes across them. + force_full_motif_scan: If True, bypass Shine-Dalgarno trainer and force a full motif scan. + mode: Select the procedure for gene finding. 'single' uses a training sequence, + 'meta' uses pre-trained data for metagenomic sequences. + jobs: The number of threads or processes to use if input contains multiple sequences. + Must be a positive integer. + min_gene_length: The minimum length for a predicted gene in nucleotides. + Must be a positive integer. + min_edge_gene_length: The minimum length for a gene predicted at the edge of a sequence. + Must be a positive integer. + max_overlap: The maximum number of nucleotides that can overlap between two genes + on the same strand. Must be non-negative and less than or equal to + `min_gene_length`. + no_stop_codon_translation: If True, disables translation of stop codons into star + characters (*) for complete genes. + pool_type: The sort of pool to use for parallel processing. 'thread' or 'process'. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of + generated output files. + + Raises: + ValueError: If any input parameter is invalid. + subprocess.CalledProcessError: If the pyrodigal command fails. + """ + # Input validation + if not input_file.is_file(): + raise ValueError(f"Input file not found: {input_file}") + + output_files_generated: List[Path] = [] + cmd = ["pyrodigal"] + + # Handle output files and ensure parent directories exist + if trans_file: + if not trans_file.parent.is_dir(): + raise ValueError(f"Parent directory for trans_file does not exist: {trans_file.parent}") + cmd.extend(["-a", str(trans_file)]) + output_files_generated.append(trans_file) + + if nuc_file: + if not nuc_file.parent.is_dir(): + raise ValueError(f"Parent directory for nuc_file does not exist: {nuc_file.parent}") + cmd.extend(["-d", str(nuc_file)]) + output_files_generated.append(nuc_file) + + if output_file: + if not output_file.parent.is_dir(): + raise ValueError(f"Parent directory for output_file does not exist: {output_file.parent}") + cmd.extend(["-o", str(output_file)]) + output_files_generated.append(output_file) + + if start_file: + if not start_file.parent.is_dir(): + raise ValueError(f"Parent directory for start_file does not exist: {start_file.parent}") + cmd.extend(["-s", str(start_file)]) + output_files_generated.append(start_file) + + if training_file: + if not training_file.parent.is_dir(): + raise ValueError(f"Parent directory for training_file does not exist: {training_file.parent}") + cmd.extend(["-t", str(training_file)]) + # Add to output_files as it might be written if it doesn't exist + output_files_generated.append(training_file) + + # Add required input file + cmd.extend(["-i", str(input_file)]) + + # Boolean flags + if closed_ends: + cmd.append("-c") + if mask_regions: + cmd.append("-m") + if force_full_motif_scan: + cmd.append("-n") + if no_stop_codon_translation: + cmd.append("--no-stop-codon") + + # Parameters with specific values + cmd.extend(["-f", output_format]) + + if translation_table < 1: + raise ValueError(f"Translation table ID must be a positive integer, got {translation_table}") + cmd.extend(["-g", str(translation_table)]) + + # Literal type validation is implicitly handled by type checker, but explicit check adds robustness + if mode not in ["single", "meta"]: + raise ValueError(f"Invalid mode: {mode}. Must be 'single' or 'meta'.") + cmd.extend(["-p", mode]) + + if jobs < 1: + raise ValueError(f"Number of jobs must be a positive integer, got {jobs}") + cmd.extend(["-j", str(jobs)]) + + if min_gene_length < 1: + raise ValueError(f"Minimum gene length must be a positive integer, got {min_gene_length}") + cmd.extend(["--min-gene", str(min_gene_length)]) + + if min_edge_gene_length < 1: + raise ValueError(f"Minimum edge gene length must be a positive integer, got {min_edge_gene_length}") + cmd.extend(["--min-edge-gene", str(min_edge_gene_length)]) + + if max_overlap < 0: + raise ValueError(f"Maximum overlap must be a non-negative integer, got {max_overlap}") + if max_overlap > min_gene_length: + raise ValueError(f"Maximum overlap ({max_overlap}) cannot be greater than minimum gene length ({min_gene_length})") + cmd.extend(["--max-overlap", str(max_overlap)]) + + # Literal type validation is implicitly handled by type checker, but explicit check adds robustness + if pool_type not in ["thread", "process"]: + raise ValueError(f"Invalid pool type: {pool_type}. Must be 'thread' or 'process'.") + cmd.extend(["--pool", pool_type]) + + command_executed = " ".join(map(str, cmd)) + stdout = "" + stderr = "" + + try: + process = subprocess.run( + cmd, + check=True, + capture_output=True, + text=True, + encoding="utf-8" + ) + stdout = process.stdout + stderr = process.stderr + except subprocess.CalledProcessError as e: + return { + "command_executed": command_executed, + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"Pyrodigal command failed with exit code {e.returncode}", + "output_files": [] + } + except FileNotFoundError: + return { + "command_executed": command_executed, + "stdout": "", + "stderr": "pyrodigal command not found. Please ensure it is installed and in your PATH.", + "error": "Tool not found", + "output_files": [] + } + + return { + "command_executed": command_executed, + "stdout": stdout, + "stderr": stderr, + "output_files": [str(f) for f in output_files_generated] + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_shim_server.py b/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f461c8f140507fc7e58e7ef6c3c1bc075d778fb3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/app/pyrodigal_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_pyrodigal/app/pyrodigal_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_pyrodigal' + + +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_pyrodigal/app/requirements.txt b/Biomni/mcp_generated/mcp_pyrodigal/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_pyrodigal/docker-compose.yml b/Biomni/mcp_generated/mcp_pyrodigal/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..5f029259c9561bdb95600ceb10a303b502464847 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-pyrodigal: + build: . + image: mcp-pyrodigal:latest + container_name: mcp-pyrodigal + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=pyrodigal + 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_pyrodigal/environment.yaml b/Biomni/mcp_generated/mcp_pyrodigal/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cc5875aea82d2df024ddf0e8fe7ccf7e56df4c43 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - pyrodigal + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_pyrodigal/requirements.txt b/Biomni/mcp_generated/mcp_pyrodigal/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_pyrodigal/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_quast/requirements.txt b/Biomni/mcp_generated/mcp_quast/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_quast/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_raxml/Dockerfile b/Biomni/mcp_generated/mcp_raxml/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..04af583eaf090ea69ddac2acf23fe0ecbd56c27b --- /dev/null +++ b/Biomni/mcp_generated/mcp_raxml/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 raxml via conda (e.g., from bioconda) +RUN conda install -c bioconda raxml -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/raxml_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/raxml_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/raxml_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_raxml/docker-compose.yml b/Biomni/mcp_generated/mcp_raxml/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..204ce4e2ce0031c5c027d730ae71578026c5cbe3 --- /dev/null +++ b/Biomni/mcp_generated/mcp_raxml/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-raxml: + build: . + image: mcp-raxml:latest + container_name: mcp-raxml + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=raxml + 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_raxml/environment.yaml b/Biomni/mcp_generated/mcp_raxml/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..02d98431f54f35c978c5741609ad7b90b549a4e9 --- /dev/null +++ b/Biomni/mcp_generated/mcp_raxml/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - raxml + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snakemake-interface-common/requirements.txt b/Biomni/mcp_generated/mcp_snakemake-interface-common/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snakemake-interface-common/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_snp2cell/Dockerfile b/Biomni/mcp_generated/mcp_snp2cell/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c5594778177713c011b411fa0e4f36f33556cafb --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/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 snp2cell via conda (e.g., from bioconda) +RUN conda install -c bioconda snp2cell -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 snp2cell_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/snp2cell_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/snp2cell_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_server.py b/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0c6603d50cf87050d929e771176120cf33246fec --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_server.py @@ -0,0 +1,534 @@ +import subprocess +import tempfile +from pathlib import Path +from typing import Optional, List + +# Mock decorator for standalone execution +class mcp: + @staticmethod + def tool(func): + return func + +@mcp.tool +def create_object( + network_file: Path, + s2c_object_file: Path, + network_format: str = "sif", +) -> dict: + """ + Create an initial SNP2CELL object holding a network. + + This can be used as an input for other sub-commands. + + Args: + network_file: Path to the network file. + s2c_object_file: Path to save the SNP2CELL object. + network_format: Format of the network file. + """ + # Input validation + if not network_file.is_file(): + raise FileNotFoundError(f"Network file not found: {network_file}") + + if network_format not in ["sif", "edgelist"]: + raise ValueError(f"Invalid network format: {network_format}. Must be 'sif' or 'edgelist'.") + + # Command construction + cmd = [ + "snp2cell", + "create-object", + "--network-format", + network_format, + str(network_file), + str(s2c_object_file), + ] + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell create-object failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def create_gene2pos_mapping( + s2c_object_file: Path, + gene2pos_mapping_file: Path, + mart: str = "human", +) -> dict: + """ + Query pybiomart to obtain genomic locations for genes in the network + in the s2c object and save them to a csv file. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + gene2pos_mapping_file: Path to save the gene to position mapping. + mart: Which mart to use. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + + if mart not in ["human", "mouse"]: + raise ValueError(f"Invalid mart: {mart}. Must be 'human' or 'mouse'.") + + # Command construction + cmd = [ + "snp2cell", + "create-gene2pos-mapping", + "--mart", + mart, + str(s2c_object_file), + str(gene2pos_mapping_file), + ] + + # Subprocess execution + 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(gene2pos_mapping_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell create-gene2pos-mapping failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def export_locations( + s2c_object_file: Path, + locations_file: Path, +) -> dict: + """ + Save the genomic locations of network nodes in the s2c object to a tsv file. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + locations_file: Path to save the locations file. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + + # Command construction + cmd = [ + "snp2cell", + "export-locations", + str(s2c_object_file), + str(locations_file), + ] + + # Subprocess execution + 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(locations_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell export-locations failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def score_snp( + s2c_object_file: Path, + gwas_summary_stats_file: Path, + gene2pos_mapping_file: Path, + score_name: str = "snp_score", + chrom_col: str = "CHR", + pos_col: str = "POS", + snp_col: str = "SNP", + pval_col: str = "P", + z_col: Optional[str] = None, + or_col: Optional[str] = None, + beta_col: Optional[str] = None, + a1_col: Optional[str] = None, + a2_col: Optional[str] = None, + window_size: int = 250000, + n_permutations: int = 1000, + random_seed: int = 42, + n_jobs: int = 1, +) -> dict: + """ + Add fGWAS scores for network nodes based on GWAS summary statistics. + Then propagate the scores across the network and calculate statistics + based on random permutations. All calculated information will be saved + in the s2c object. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + gwas_summary_stats_file: Path to the GWAS summary statistics file. + gene2pos_mapping_file: Path to the gene to position mapping file. + score_name: Name of the score. + chrom_col: Name of the chromosome column. + pos_col: Name of the position column. + snp_col: Name of the SNP ID column. + pval_col: Name of the p-value column. + z_col: Name of the z-score column. + or_col: Name of the odds ratio column. + beta_col: Name of the beta column. + a1_col: Name of the A1 allele column. + a2_col: Name of the A2 allele column. + window_size: Window size for fGWAS. + n_permutations: Number of permutations for statistics. + random_seed: Random seed for permutations. + n_jobs: Number of jobs to use for parallel processing. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + if not gwas_summary_stats_file.is_file(): + raise FileNotFoundError(f"GWAS summary stats file not found: {gwas_summary_stats_file}") + if not gene2pos_mapping_file.is_file(): + raise FileNotFoundError(f"Gene to position mapping file not found: {gene2pos_mapping_file}") + + # Command construction + cmd = [ + "snp2cell", "score-snp", + "--score-name", score_name, + "--chrom-col", chrom_col, + "--pos-col", pos_col, + "--snp-col", snp_col, + "--pval-col", pval_col, + "--window-size", str(window_size), + "--n-permutations", str(n_permutations), + "--random-seed", str(random_seed), + "--n-jobs", str(n_jobs), + ] + if z_col: + cmd.extend(["--z-col", z_col]) + if or_col: + cmd.extend(["--or-col", or_col]) + if beta_col: + cmd.extend(["--beta-col", beta_col]) + if a1_col: + cmd.extend(["--a1-col", a1_col]) + if a2_col: + cmd.extend(["--a2-col", a2_col]) + + cmd.extend([ + str(s2c_object_file), + str(gwas_summary_stats_file), + str(gene2pos_mapping_file), + ]) + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell score-snp failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def add_score( + s2c_object_file: Path, + scores_file: Path, + score_name: str = "score", + n_permutations: int = 1000, + random_seed: int = 42, + n_jobs: int = 1, +) -> dict: + """ + Add scores for network nodes to the s2c object and propagate the scores + across the network. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + scores_file: Path to the scores file. + score_name: Name of the score. + n_permutations: Number of permutations for statistics. + random_seed: Random seed for permutations. + n_jobs: Number of jobs to use for parallel processing. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + if not scores_file.is_file(): + raise FileNotFoundError(f"Scores file not found: {scores_file}") + + # Command construction + cmd = [ + "snp2cell", "add-score", + "--score-name", score_name, + "--n-permutations", str(n_permutations), + "--random-seed", str(random_seed), + "--n-jobs", str(n_jobs), + str(s2c_object_file), + str(scores_file), + ] + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell add-score failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def contrast_scores( + s2c_object_file: Path, + score_name1: str, + score_name2: str, + new_score_name: str = "contrast_score", + n_permutations: int = 1000, + random_seed: int = 42, + n_jobs: int = 1, +) -> dict: + """ + Add a new score that is a contrast of two scores, propagate it across + the network and calculate statistics based on random permutations. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + score_name1: Name of the first score. + score_name2: Name of the second score. + new_score_name: Name of the new score. + n_permutations: Number of permutations for statistics. + random_seed: Random seed for permutations. + n_jobs: Number of jobs to use for parallel processing. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + + # Command construction + cmd = [ + "snp2cell", "contrast-scores", + "--score-name1", score_name1, + "--score-name2", score_name2, + "--new-score-name", new_score_name, + "--n-permutations", str(n_permutations), + "--random-seed", str(random_seed), + "--n-jobs", str(n_jobs), + str(s2c_object_file), + ] + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell contrast-scores failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def score_de( + s2c_object_file: Path, + anndata_file: Path, + obs_column: str, + group1: str, + group2: str, + de_score_name: str = "de_score", + snp_score_name: str = "snp_score", + combined_score_name: str = "combined_score", + n_permutations: int = 1000, + random_seed: int = 42, + n_jobs: int = 1, +) -> dict: + """ + Add an anndata object, find differentially expressed genes, propagate + scores, combine with SNP scores, and compute statistics. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + anndata_file: Path to the anndata object file. + obs_column: Name of the column in obs to group by. + group1: Name of the first group. + group2: Name of the second group. + de_score_name: Name of the DE score. + snp_score_name: Name of the SNP score. + combined_score_name: Name of the combined score. + n_permutations: Number of permutations for statistics. + random_seed: Random seed for permutations. + n_jobs: Number of jobs to use for parallel processing. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + if not anndata_file.is_file(): + raise FileNotFoundError(f"AnnData file not found: {anndata_file}") + + # Command construction + cmd = [ + "snp2cell", "score-de", + "--obs-column", obs_column, + "--group1", group1, + "--group2", group2, + "--de-score-name", de_score_name, + "--snp-score-name", snp_score_name, + "--combined-score-name", combined_score_name, + "--n-permutations", str(n_permutations), + "--random-seed", str(random_seed), + "--n-jobs", str(n_jobs), + str(s2c_object_file), + str(anndata_file), + ] + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell score-de failed with exit code {e.returncode}", + "output_files": [], + } + +@mcp.tool +def combine_scores( + s2c_object_file: Path, + snp_score_name: str = "snp_score", + de_score_name: str = "de_score", + combined_score_name: str = "combined_score", + n_permutations: int = 1000, + random_seed: int = 42, + n_jobs: int = 1, +) -> dict: + """ + Combine existing SNP and DE scores in the s2c object and compute statistics. + + Args: + s2c_object_file: Path to the SNP2CELL object file. + snp_score_name: Name of the SNP score. + de_score_name: Name of the DE score. + combined_score_name: Name of the combined score. + n_permutations: Number of permutations for statistics. + random_seed: Random seed for permutations. + n_jobs: Number of jobs to use for parallel processing. + """ + # Input validation + if not s2c_object_file.is_file(): + raise FileNotFoundError(f"SNP2CELL object file not found: {s2c_object_file}") + + # Command construction + cmd = [ + "snp2cell", "combine-scores", + "--snp-score-name", snp_score_name, + "--de-score-name", de_score_name, + "--combined-score-name", combined_score_name, + "--n-permutations", str(n_permutations), + "--random-seed", str(random_seed), + "--n-jobs", str(n_jobs), + str(s2c_object_file), + ] + + # Subprocess execution + 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(s2c_object_file)], + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "error": f"snp2cell combine-scores failed with exit code {e.returncode}", + "output_files": [], + } \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_shim_server.py b/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..7b996db8f6f93fd45f62445c3d411ca7c1c43e1a --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/app/snp2cell_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_snp2cell/app/snp2cell_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_snp2cell' + + +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_snp2cell/docker-compose.yml b/Biomni/mcp_generated/mcp_snp2cell/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1936c8bd0f809510927c8cf2341fb0bf93faf468 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-snp2cell: + build: . + image: mcp-snp2cell:latest + container_name: mcp-snp2cell + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=snp2cell + 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_snp2cell/environment.yaml b/Biomni/mcp_generated/mcp_snp2cell/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6555b3e60bede732ccb6a586e83bca8068584bbd --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - snp2cell + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_snp2cell/requirements.txt b/Biomni/mcp_generated/mcp_snp2cell/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_snp2cell/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_sra-tools/Dockerfile b/Biomni/mcp_generated/mcp_sra-tools/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2306cd89a3e9bbe428c275d0c156ca4ad77ed07b --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/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 sra-tools via conda (e.g., from bioconda) +RUN conda install -c bioconda sra-tools -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/sra-tools_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/sra-tools_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/sra-tools_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sra-tools/app/requirements.txt b/Biomni/mcp_generated/mcp_sra-tools/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_server.py b/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_server.py new file mode 100644 index 0000000000000000000000000000000000000000..825cfd2f7e2414083c4e26a4d2c6f2fc0a613a10 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_server.py @@ -0,0 +1,402 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Helper function for running SRA tools +def _run_sra_tool(command: List[str], tool_name: str, output_dir: Optional[Path] = None) -> Dict[str, Any]: + """ + Helper function to execute an SRA Toolkit command and return structured output. + """ + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + check=True + ) + output_files: List[str] = [] + + # Attempt to infer output files if an output_dir was specified and the command was successful. + # This is a heuristic; a more robust solution would involve parsing the tool's stdout + # for specific filenames or requiring the user to specify expected output patterns. + if output_dir and output_dir.is_dir(): + try: + output_files = [str(f) for f in output_dir.iterdir() if f.is_file()] + except Exception as e: + # If directory listing fails, include a warning in stderr. + result.stderr += f"\nWarning: Could not list output directory contents: {e}" + + return { + "command_executed": " ".join(command), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files, + } + except FileNotFoundError: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": f"Error: {tool_name} command not found. Is SRA Toolkit installed and in PATH?", + "output_files": [], + "error": "ToolNotFound" + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(command), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": "CalledProcessError", + "returncode": e.returncode + } + except Exception as e: + return { + "command_executed": " ".join(command), + "stdout": "", + "stderr": f"An unexpected error occurred: {str(e)}", + "output_files": [], + "error": "UnexpectedError" + } + +# The @mcp.tool() decorator is assumed to be available in the environment. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_sra_tools' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def prefetch( + accession_id: str, + output_directory: Optional[Path] = None, + eliminate_quals: bool = False, + force: bool = False, + verbose: bool = False, + ngc_file: Optional[Path] = None, +) -> Dict[str, Any]: + """ + Downloads SRA data (runs or cart files) from NCBI. + + Args: + accession_id: SRA accession (e.g., SRR123456) or path to a cart file. + output_directory: Optional path to the directory where downloaded files will be saved. + If not specified, files are saved in the current working directory. + eliminate_quals: If True, prefer SRA Lite data with simplified quality scores + to reduce download size and time. (Introduced in SRA Toolkit 3.1.0+) + force: If True, overwrite existing files without prompting. (Common inferred flag) + verbose: If True, enable verbose output. (Common inferred flag) + ngc_file: Optional path to an NCBI Generated Key (NGC) file for accessing dbGaP data. + (Support for NGC files mentioned in SRA Toolkit 3.0.5+) + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + if not accession_id: + raise ValueError("accession_id cannot be empty.") + + command = ["prefetch", accession_id] + + if output_directory: + output_directory.mkdir(parents=True, exist_ok=True) + command.extend(["-O", str(output_directory)]) + + if eliminate_quals: + command.append("--eliminate-quals") + + if force: + command.append("--force") + + if verbose: + command.append("--verbose") + + if ngc_file: + if not ngc_file.is_file(): + raise FileNotFoundError(f"NGC file not found at: {ngc_file}") + command.extend(["--ngc", str(ngc_file)]) # Inferred flag for NGC file + + return _run_sra_tool(command, "prefetch", output_dir=output_directory) + + +@mcp.tool() +def fasterq_dump( + accession_id: str, + output_directory: Optional[Path] = None, + threads: int = 1, + split_files: bool = False, + include_technical: bool = False, + fasta: bool = False, + fasta_unsorted: bool = False, + temp_directory: Optional[Path] = None, + force: bool = False, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Converts SRA objects into FASTQ or FASTA files. This tool is multi-threaded and + optimized for large-scale conversions. (Introduced in SRA Toolkit 2.9.1+) + + Args: + accession_id: SRA accession (e.g., SRR123456). + output_directory: Optional path to the directory where output files will be saved. + If not specified, files are saved in the current working directory. + threads: Number of CPU threads to use for the conversion. Must be 1 or greater. + (Inferred flag: -e, common for SRA tools) + split_files: If True, split reads into separate files (e.g., for paired-end data, + creates _1.fastq and _2.fastq files). Automatically enabled if + `include_technical` is True. + include_technical: If True, include technical reads in the output. This automatically + enables `split_files`. (Mentioned in SRA Toolkit 3.0.6+) + fasta: If True, output in FASTA format instead of FASTQ. Cannot be used with `fasta_unsorted`. + (Mentioned in SRA Toolkit 2.11.3+) + fasta_unsorted: If True, output in unsorted FASTA format. Cannot be used with `fasta`. + (Mentioned in SRA Toolkit 2.11.3+) + temp_directory: Optional path to a directory for temporary files. + (Inferred flag: -t, common for SRA tools) + force: If True, overwrite existing output files without prompting. (Common inferred flag) + verbose: If True, enable verbose output. (Common inferred flag) + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + if not accession_id: + raise ValueError("accession_id cannot be empty.") + if threads < 1: + raise ValueError("threads must be 1 or greater.") + if fasta and fasta_unsorted: + raise ValueError("Cannot use both 'fasta' and 'fasta_unsorted' simultaneously.") + + command = ["fasterq-dump", accession_id] + + if output_directory: + output_directory.mkdir(parents=True, exist_ok=True) + command.extend(["-O", str(output_directory)]) + + if threads > 1: # Only add if > 1, as 1 is often default and might not need explicit flag + command.extend(["-e", str(threads)]) + + # include_technical implies split_files as per documentation + if include_technical: + command.append("--include-technical") + if not split_files: # Avoid adding --split-files twice if user already set it + command.append("--split-files") + elif split_files: # Only add if not already handled by include_technical + command.append("--split-files") + + if fasta: + command.append("--fasta") + elif fasta_unsorted: + command.append("--fasta-unsorted") + + if temp_directory: + temp_directory.mkdir(parents=True, exist_ok=True) + command.extend(["-t", str(temp_directory)]) + + if force: + command.append("--force") + + if verbose: + command.append("--verbose") + + return _run_sra_tool(command, "fasterq-dump", output_dir=output_directory) + + +@mcp.tool() +def fastq_dump( + accession_id: str, + output_directory: Optional[Path] = None, + split_files: bool = False, + fasta: bool = False, + fasta_unsorted: bool = False, + gzip: bool = False, + bzip2: bool = False, + force: bool = False, + verbose: bool = False, +) -> Dict[str, Any]: + """ + (DEPRECATED) Converts SRA objects into FASTQ or FASTA files. This is an older, single-threaded + tool. `fasterq_dump` is generally preferred for performance. + + Args: + accession_id: SRA accession (e.g., SRR123456). + output_directory: Optional path to the directory where output files will be saved. + If not specified, files are saved in the current working directory. + split_files: If True, split reads into separate files (e.g., for paired-end data). + (Common inferred flag) + fasta: If True, output in FASTA format instead of FASTQ. Cannot be used with `fasta_unsorted`. + (Common inferred flag) + fasta_unsorted: If True, output in unsorted FASTA format. Cannot be used with `fasta`. + (Common inferred flag) + gzip: If True, compress the output files with gzip. Cannot be used with `bzip2`. + (Common inferred flag) + bzip2: If True, compress the output files with bzip2. Cannot be used with `gzip`. + (Common inferred flag) + force: If True, overwrite existing output files without prompting. (Common inferred flag) + verbose: If True, enable verbose output. (Common inferred flag) + + Returns: + A dictionary containing command execution details, stdout, stderr, and output files. + """ + if not accession_id: + raise ValueError("accession_id cannot be empty.") + if fasta and fasta_unsorted: + raise ValueError("Cannot use both 'fasta' and 'fasta_unsorted' simultaneously.") + if gzip and bzip2: + raise ValueError("Cannot use both 'gzip' and 'bzip2' simultaneously.") + + command = ["fastq-dump", accession_id] + + if output_directory: + output_directory.mkdir(parents=True, exist_ok=True) + command.extend(["-O", str(output_directory)]) + + if split_files: + command.append("--split-files") + + if fasta: + command.append("--fasta") + elif fasta_unsorted: + command.append("--fasta-unsorted") + + if gzip: + command.append("--gzip") + elif bzip2: + command.append("--bzip2") + + if force: + command.append("--force") + + if verbose: + command.append("--verbose") + + return _run_sra_tool(command, "fastq-dump", output_dir=output_directory) + + +@mcp.tool() +def vdb_config( + set_preference: Optional[str] = None, + aws_access_key_id: Optional[str] = None, + aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, + gcp_access_token: Optional[str] = None, + reset: bool = False, + list_config: bool = False, + interactive: bool = False, +) -> Dict[str, Any]: + """ + Configures SRA Toolkit settings, including cloud credentials and preferences. + Note: Interactive mode is generally not recommended for automated scripts. + + Args: + set_preference: A string in 'key=value' format to set a specific configuration preference. + Example: 'prefer_sra_lite=true'. (Inferred flag: --set-pref, based on wiki) + aws_access_key_id: AWS access key ID for cloud access. (Inferred flag: --aws-id, based on "improve AWS credential interface") + aws_secret_access_key: AWS secret access key for cloud access. (Inferred flag: --aws-secret) + aws_session_token: AWS session token for temporary credentials. (Inferred flag: --aws-token) + gcp_access_token: GCP access token for cloud access. (Inferred flag: --gcp-token, based on "supports the latest GCP access tokens") + reset: If True, reset the SRA Toolkit configuration to its default settings. + (Inferred flag: --reset, common for config tools) + list_config: If True, list the current SRA Toolkit configuration. + (Inferred flag: --list, common for config tools) + interactive: If True, run vdb-config in interactive mode. (Flag inferred, though interactive + requirement was removed in SRA Toolkit 3.0.1) + + Returns: + A dictionary containing command execution details, stdout, stderr. + """ + command = ["vdb-config"] + action_specified = False + + if reset: + command.append("--reset") + action_specified = True + if list_config: + command.append("--list") + action_specified = True + if interactive: + command.append("--interactive") + action_specified = True + if set_preference: + command.extend(["--set-pref", set_preference]) + action_specified = True + if aws_access_key_id: + command.extend(["--aws-id", aws_access_key_id]) + action_specified = True + if aws_secret_access_key: + command.extend(["--aws-secret", aws_secret_access_key]) + action_specified = True + if aws_session_token: + command.extend(["--aws-token", aws_session_token]) + action_specified = True + if gcp_access_token: + command.extend(["--gcp-token", gcp_access_token]) + action_specified = True + + if not action_specified: + raise ValueError("No action specified for vdb-config. Please provide a preference to set, " + "credentials, or use reset=True, list_config=True, or interactive=True.") + + return _run_sra_tool(command, "vdb-config") + + +@mcp.tool() +def sra_stat( + accession_id: str, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Provides statistics about SRA runs. (SRA Toolkit 3.0.3 fixed a regression in this tool) + + Args: + accession_id: SRA accession (e.g., SRR123456). + verbose: If True, enable verbose output. (Common inferred flag) + + Returns: + A dictionary containing command execution details, stdout, stderr. + """ + if not accession_id: + raise ValueError("accession_id cannot be empty.") + + command = ["sra-stat", accession_id] + + if verbose: + command.append("--verbose") + + return _run_sra_tool(command, "sra-stat") + + +@mcp.tool() +def vdb_validate( + path_to_sra_file: Path, + check_checksums: bool = True, + verbose: bool = False, +) -> Dict[str, Any]: + """ + Validates SRA/VDB files for integrity. + + Args: + path_to_sra_file: Path to the SRA file to validate. + check_checksums: If True, vdb-validate will report an error if data (blob) checksums are missing. + This is the default behavior of the tool as per documentation ("vdb-validate + will report an error if data (blob) checksums are missing."), so no explicit + flag is added for `check_checksums=True` unless a `--no-checksums` flag + were known to exist. + verbose: If True, enable verbose output. (Common inferred flag) + + Returns: + A dictionary containing command execution details, stdout, stderr. + """ + if not path_to_sra_file.is_file(): + raise FileNotFoundError(f"SRA file not found at: {path_to_sra_file}") + + command = ["vdb-validate", str(path_to_sra_file)] + + # As per documentation, "vdb-validate will report an error if data (blob) checksums are missing." + # This implies checksum checking is default. No flag is added for `check_checksums=True`. + # If a flag to disable checksums existed (e.g., --no-checksums), it would be added here if check_checksums was False. + + if verbose: + command.append("--verbose") + + return _run_sra_tool(command, "vdb-validate") + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_shim_server.py b/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_shim_server.py new file mode 100644 index 0000000000000000000000000000000000000000..a473ad759ec7a1c45712e4fbabb8cf4f6d24d3ca --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/app/sra-tools_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_sra-tools/app/sra-tools_server.py') +LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name) +SERVER_NAME = 'biosci_sra_tools' + + +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_sra-tools/docker-compose.yml b/Biomni/mcp_generated/mcp_sra-tools/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..b673aa81bb7546ee17d4dbf742f2b7877437ee44 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + mcp-sra-tools: + build: . + image: mcp-sra-tools:latest + container_name: mcp-sra-tools + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME=sra-tools + 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_sra-tools/environment.yaml b/Biomni/mcp_generated/mcp_sra-tools/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..839473e094e3d1f1b5751f87404a90c0aba48bae --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - sra-tools + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_sra-tools/requirements.txt b/Biomni/mcp_generated/mcp_sra-tools/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_sra-tools/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_star/app/__pycache__/star_server.cpython-311.pyc b/Biomni/mcp_generated/mcp_star/app/__pycache__/star_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d296ec8f424c3c814ef74042e700ab63d1c33af Binary files /dev/null and b/Biomni/mcp_generated/mcp_star/app/__pycache__/star_server.cpython-311.pyc differ diff --git a/Biomni/mcp_generated/mcp_transdecoder/requirements.txt b/Biomni/mcp_generated/mcp_transdecoder/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_transdecoder/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_treetime/Dockerfile b/Biomni/mcp_generated/mcp_treetime/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..df82cd59870c5197ae44c58105485ed440761488 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/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 treetime via conda (e.g., from bioconda) +RUN conda install -c bioconda treetime -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/treetime_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/treetime_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/treetime_server.py"] + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_treetime/app/requirements.txt b/Biomni/mcp_generated/mcp_treetime/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/app/requirements.txt @@ -0,0 +1 @@ + diff --git a/Biomni/mcp_generated/mcp_treetime/app/treetime_server.py b/Biomni/mcp_generated/mcp_treetime/app/treetime_server.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b45f588b934758d70dc5d71e1000861dfd5310 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/app/treetime_server.py @@ -0,0 +1,373 @@ +import subprocess +from pathlib import Path +from typing import Optional, List, Dict, Any + +# Note: mcp is not imported as per instructions, but @mcp.tool is used for clarity. + +from mcp.server.fastmcp import FastMCP + +SERVER_NAME = 'local_treetime' +mcp = FastMCP(SERVER_NAME) + +@mcp.tool() +def infer_timetree( + alignment_file: Path, + tree_file: Path, + dates_file: Path, + output_dir: Path, + dry_run: bool = False, +) -> Dict[str, Any]: + """ + Infers a time tree, ancestral sequences, a GTR model, and optionally confidence intervals + and coalescent models using the main treetime command. + + Args: + alignment_file: Path to the input alignment file (e.g., FASTA or PHYLIP format). + tree_file: Path to the input tree file (e.g., Newick, Nexus, or PHYLIP format). + dates_file: Path to the dates metadata file (CSV or TSV format). This file must + contain a 'name', 'accession', or 'strain' column matching tree tips, + and a 'date' column for sampling dates. + output_dir: Directory where all generated output files will be stored. This directory + will be created if it does not exist. + dry_run: If True, the command will be printed to stderr and returned without execution. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + If an error occurs, an 'error' key will be present with the exception message. + """ + # Input validation + if not alignment_file.is_file(): + raise FileNotFoundError(f"Input alignment file not found: {alignment_file}") + if not tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {tree_file}") + if not dates_file.is_file(): + raise FileNotFoundError(f"Input dates file not found: {dates_file}") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "treetime", + "--aln", str(alignment_file), + "--tree", str(tree_file), + "--dates", str(dates_file), + "--outdir", str(output_dir) + ] + + if dry_run: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "Dry run: Command not executed.", + "output_files": [] + } + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(f.resolve()) for f in output_dir.rglob("*") if f.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e) + } + + +@mcp.tool() +def analyze_clock_signal( + tree_file: Path, + dates_file: Path, + output_dir: Path, + alignment_file: Optional[Path] = None, + sequence_length: Optional[int] = None, + reroot_method: Optional[str] = None, + dry_run: bool = False, +) -> Dict[str, Any]: + """ + Explores the temporal signal in the data and estimates the substitution rate using + the 'treetime clock' subcommand. + + Args: + tree_file: Path to the input tree file (e.g., Newick, Nexus, or PHYLIP format). + dates_file: Path to the dates metadata file (CSV or TSV format). + output_dir: Directory where all generated output files will be stored. This directory + will be created if it does not exist. + alignment_file: Optional path to the input alignment file (e.g., FASTA or PHYLIP format). + Either `alignment_file` or `sequence_length` must be provided. + sequence_length: Optional sequence length. Can be provided instead of an alignment file. + Either `alignment_file` or `sequence_length` must be provided. + Must be a positive integer if provided. + reroot_method: Optional rerooting method. For example, 'least-squares'. + dry_run: If True, the command will be printed to stderr and returned without execution. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + If an error occurs, an 'error' key will be present with the exception message. + """ + # Input validation + if not tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {tree_file}") + if not dates_file.is_file(): + raise FileNotFoundError(f"Input dates file not found: {dates_file}") + + if alignment_file and not alignment_file.is_file(): + raise FileNotFoundError(f"Input alignment file not found: {alignment_file}") + + if not alignment_file and sequence_length is None: + raise ValueError("Either 'alignment_file' or 'sequence_length' must be provided.") + if alignment_file and sequence_length is not None: + raise ValueError("Cannot provide both 'alignment_file' and 'sequence_length'. Choose one.") + if sequence_length is not None and sequence_length <= 0: + raise ValueError("Sequence length must be a positive integer.") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "treetime", "clock", + "--tree", str(tree_file), + "--dates", str(dates_file), + "--outdir", str(output_dir) + ] + + if alignment_file: + cmd.extend(["--aln", str(alignment_file)]) + elif sequence_length is not None: + cmd.extend(["--sequence-length", str(sequence_length)]) + + if reroot_method: + cmd.extend(["--reroot", reroot_method]) + + if dry_run: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "Dry run: Command not executed.", + "output_files": [] + } + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(f.resolve()) for f in output_dir.rglob("*") if f.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e) + } + + +@mcp.tool() +def reconstruct_ancestral_sequences( + alignment_file: Path, + tree_file: Path, + output_dir: Path, + dry_run: bool = False, +) -> Dict[str, Any]: + """ + Reconstructs ancestral sequences at internal nodes of the input tree using + the 'treetime ancestral' subcommand. + + Args: + alignment_file: Path to the input alignment file (e.g., FASTA or PHYLIP format). + tree_file: Path to the input tree file (e.g., Newick, Nexus, or PHYLIP format). + output_dir: Directory where all generated output files will be stored. This directory + will be created if it does not exist. + dry_run: If True, the command will be printed to stderr and returned without execution. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + If an error occurs, an 'error' key will be present with the exception message. + """ + # Input validation + if not alignment_file.is_file(): + raise FileNotFoundError(f"Input alignment file not found: {alignment_file}") + if not tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {tree_file}") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "treetime", "ancestral", + "--aln", str(alignment_file), + "--tree", str(tree_file), + "--outdir", str(output_dir) + ] + + if dry_run: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "Dry run: Command not executed.", + "output_files": [] + } + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(f.resolve()) for f in output_dir.rglob("*") if f.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e) + } + + +@mcp.tool() +def analyze_homoplasy( + alignment_file: Path, + tree_file: Path, + output_dir: Path, + dry_run: bool = False, +) -> Dict[str, Any]: + """ + Detects and quantifies homoplasies or recurrent mutations in the data using + the 'treetime homoplasy' subcommand. + + Args: + alignment_file: Path to the input alignment file (e.g., FASTA or PHYLIP format). + tree_file: Path to the input tree file (e.g., Newick, Nexus, or PHYLIP format). + output_dir: Directory where all generated output files will be stored. This directory + will be created if it does not exist. + dry_run: If True, the command will be printed to stderr and returned without execution. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + If an error occurs, an 'error' key will be present with the exception message. + """ + # Input validation + if not alignment_file.is_file(): + raise FileNotFoundError(f"Input alignment file not found: {alignment_file}") + if not tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {tree_file}") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "treetime", "homoplasy", + "--aln", str(alignment_file), + "--tree", str(tree_file), + "--outdir", str(output_dir) + ] + + if dry_run: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "Dry run: Command not executed.", + "output_files": [] + } + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(f.resolve()) for f in output_dir.rglob("*") if f.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e) + } + + +@mcp.tool() +def infer_mugration_model( + tree_file: Path, + states_file: Path, + attribute_field: str, + output_dir: Path, + dry_run: bool = False, +) -> Dict[str, Any]: + """ + Infers mugration models for discrete state transitions (e.g., geographic regions, + host switching) using the 'treetime mugration' subcommand. + + Args: + tree_file: Path to the input tree file (e.g., Newick, Nexus, or PHYLIP format). + states_file: Path to the states metadata file (CSV or TSV format). This file must + contain a 'name', 'accession', or 'strain' column matching tree tips. + attribute_field: The relevant column name in the `states_file` that specifies + the discrete states for mugration analysis (e.g., 'country'). + output_dir: Directory where all generated output files will be stored. This directory + will be created if it does not exist. + dry_run: If True, the command will be printed to stderr and returned without execution. + + Returns: + A dictionary containing the command executed, stdout, stderr, and a list of output files. + If an error occurs, an 'error' key will be present with the exception message. + """ + # Input validation + if not tree_file.is_file(): + raise FileNotFoundError(f"Input tree file not found: {tree_file}") + if not states_file.is_file(): + raise FileNotFoundError(f"Input states file not found: {states_file}") + if not attribute_field: + raise ValueError("Attribute field cannot be empty.") + + output_dir.mkdir(parents=True, exist_ok=True) + + cmd = [ + "treetime", "mugration", + "--tree", str(tree_file), + "--states", str(states_file), + "--attribute", attribute_field, + "--outdir", str(output_dir) + ] + + if dry_run: + return { + "command_executed": " ".join(cmd), + "stdout": "", + "stderr": "Dry run: Command not executed.", + "output_files": [] + } + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + output_files = [str(f.resolve()) for f in output_dir.rglob("*") if f.is_file()] + return { + "command_executed": " ".join(cmd), + "stdout": result.stdout, + "stderr": result.stderr, + "output_files": output_files + } + except subprocess.CalledProcessError as e: + return { + "command_executed": " ".join(cmd), + "stdout": e.stdout, + "stderr": e.stderr, + "output_files": [], + "error": str(e) + } + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/Biomni/mcp_generated/mcp_treetime/environment.yaml b/Biomni/mcp_generated/mcp_treetime/environment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6d900a466c85fbd6ef5fbb1ca4aab9cdaad90a39 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/environment.yaml @@ -0,0 +1,10 @@ + +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - treetime + - python=3.10 + \ No newline at end of file diff --git a/Biomni/mcp_generated/mcp_treetime/requirements.txt b/Biomni/mcp_generated/mcp_treetime/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_treetime/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_trf/requirements.txt b/Biomni/mcp_generated/mcp_trf/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_trf/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp diff --git a/Biomni/mcp_generated/mcp_vardict/requirements.txt b/Biomni/mcp_generated/mcp_vardict/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fc4702c254694b94825ae9800a56d8f68b1bcda0 --- /dev/null +++ b/Biomni/mcp_generated/mcp_vardict/requirements.txt @@ -0,0 +1,2 @@ +fastmcp +mcp