czty commited on
Commit
432ccad
·
verified ·
1 Parent(s): f10eba7

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Biomni/mcp_generated/mcp_bioawk/Dockerfile +40 -0
  2. Biomni/mcp_generated/mcp_bioawk/app/bioawk_server.py +132 -0
  3. Biomni/mcp_generated/mcp_bioawk/app/bioawk_shim_server.py +55 -0
  4. Biomni/mcp_generated/mcp_bioawk/app/requirements.txt +1 -0
  5. Biomni/mcp_generated/mcp_bioawk/docker-compose.yml +22 -0
  6. Biomni/mcp_generated/mcp_bioawk/environment.yaml +10 -0
  7. Biomni/mcp_generated/mcp_bioawk/requirements.txt +2 -0
  8. Biomni/mcp_generated/mcp_bioconductor-ancombc/app/requirements.txt +1 -0
  9. Biomni/mcp_generated/mcp_bioconductor-ancombc/requirements.txt +2 -0
  10. Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py +204 -0
  11. Biomni/mcp_generated/mcp_bioconductor-bsgenome/docker-compose.yml +22 -0
  12. Biomni/mcp_generated/mcp_bioconductor-dnacopy/environment.yaml +10 -0
  13. Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/Dockerfile +40 -0
  14. Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/docker-compose.yml +22 -0
  15. Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/requirements.txt +2 -0
  16. Biomni/mcp_generated/mcp_bioconductor-go.db/app/bioconductor-go.db_server.py +196 -0
  17. Biomni/mcp_generated/mcp_bioconductor-go.db/app/requirements.txt +1 -0
  18. Biomni/mcp_generated/mcp_bioconductor-hoodscanr/Dockerfile +40 -0
  19. Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py +340 -0
  20. Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_shim_server.py +55 -0
  21. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/Dockerfile +40 -0
  22. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_server.py +279 -0
  23. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/requirements.txt +1 -0
  24. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/docker-compose.yml +22 -0
  25. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/environment.yaml +10 -0
  26. Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/requirements.txt +2 -0
  27. Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_shim_server.py +55 -0
  28. Biomni/mcp_generated/mcp_bioconductor-seraster/Dockerfile +40 -0
  29. Biomni/mcp_generated/mcp_bioconductor-seraster/environment.yaml +10 -0
  30. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/Dockerfile +40 -0
  31. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py +197 -0
  32. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_shim_server.py +55 -0
  33. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/requirements.txt +1 -0
  34. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/docker-compose.yml +22 -0
  35. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/environment.yaml +10 -0
  36. Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/requirements.txt +2 -0
  37. Biomni/mcp_generated/mcp_checkm-genome/Dockerfile +40 -0
  38. Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_server.py +1238 -0
  39. Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_shim_server.py +55 -0
  40. Biomni/mcp_generated/mcp_checkm-genome/app/requirements.txt +1 -0
  41. Biomni/mcp_generated/mcp_checkm-genome/docker-compose.yml +22 -0
  42. Biomni/mcp_generated/mcp_checkm-genome/environment.yaml +10 -0
  43. Biomni/mcp_generated/mcp_checkm-genome/requirements.txt +2 -0
  44. Biomni/mcp_generated/mcp_dca/Dockerfile +40 -0
  45. Biomni/mcp_generated/mcp_dca/app/dca_shim_server.py +55 -0
  46. Biomni/mcp_generated/mcp_dca/app/requirements.txt +1 -0
  47. Biomni/mcp_generated/mcp_dca/docker-compose.yml +22 -0
  48. Biomni/mcp_generated/mcp_dca/environment.yaml +10 -0
  49. Biomni/mcp_generated/mcp_dca/requirements.txt +2 -0
  50. Biomni/mcp_generated/mcp_echidna/app/echidna_server.py +140 -0
Biomni/mcp_generated/mcp_bioawk/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioawk via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioawk -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioawk_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioawk_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioawk_server.py"]
40
+
Biomni/mcp_generated/mcp_bioawk/app/bioawk_server.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from pathlib import Path
3
+ from typing import List, Optional, Dict, Any
4
+
5
+ # Assume 'mcp' is a globally available module in the execution environment.
6
+ # from some_mcp_library import mcp
7
+
8
+ @mcp.tool
9
+ def bioawk(
10
+ program: Optional[str] = None,
11
+ program_file: Optional[Path] = None,
12
+ input_files: Optional[List[Path]] = None,
13
+ output_file: Optional[Path] = None,
14
+ format: Optional[str] = None,
15
+ tab_separator: bool = False,
16
+ include_header: bool = False,
17
+ variables: Optional[List[str]] = None,
18
+ field_separator: Optional[str] = None,
19
+ ) -> Dict[str, Any]:
20
+ """
21
+ Executes the bioawk command, a powerful stream editor for biological data formats.
22
+
23
+ bioawk is an extension of awk that understands common biological data formats
24
+ like FASTA, FASTQ, SAM, VCF, BED, and GFF. At least one of 'program' or
25
+ 'program_file' must be provided.
26
+
27
+ Args:
28
+ program: The AWK program string to execute. Mutually exclusive with 'program_file'.
29
+ program_file: Path to a file containing the AWK program. Mutually exclusive with 'program'.
30
+ input_files: A list of input files to process. If not provided, bioawk reads from standard input.
31
+ output_file: Path to a file where the output (stdout) will be saved. If not provided, stdout is captured and returned.
32
+ format: Input format (-c). Supported formats include 'fasta', 'fastq', 'sam', 'vcf', 'bed', 'gff'.
33
+ tab_separator: Use tab as the input and output field separator (-t flag).
34
+ include_header: Include header in the output for formats like VCF/SAM (-H flag).
35
+ variables: A list of 'var=value' strings to define AWK variables (-v flag).
36
+ field_separator: The input field separator string (-F flag).
37
+
38
+ Returns:
39
+ A dictionary containing the executed command, stdout, stderr, and a list of output files.
40
+ """
41
+ # --- Input Validation ---
42
+ if not program and not program_file:
43
+ raise ValueError("Either 'program' or 'program_file' must be provided.")
44
+ if program and program_file:
45
+ raise ValueError("'program' and 'program_file' are mutually exclusive and cannot be used together.")
46
+
47
+ if program_file:
48
+ if not program_file.is_file():
49
+ raise FileNotFoundError(f"Program file not found: {program_file}")
50
+
51
+ if input_files:
52
+ for file_path in input_files:
53
+ if not file_path.is_file():
54
+ raise FileNotFoundError(f"Input file not found: {file_path}")
55
+
56
+ VALID_FORMATS = {"fasta", "fastq", "sam", "vcf", "bed", "gff"}
57
+ if format and format.lower() not in VALID_FORMATS:
58
+ raise ValueError(f"Invalid format '{format}'. Must be one of {VALID_FORMATS}.")
59
+
60
+ if variables:
61
+ for var in variables:
62
+ if "=" not in var:
63
+ raise ValueError(f"Invalid variable assignment '{var}'. Must be in 'var=value' format.")
64
+
65
+ # --- Command Construction ---
66
+ cmd = ["bioawk"]
67
+
68
+ if format:
69
+ cmd.extend(["-c", format.lower()])
70
+ if tab_separator:
71
+ cmd.append("-t")
72
+ if include_header:
73
+ cmd.append("-H")
74
+ if field_separator:
75
+ cmd.extend(["-F", field_separator])
76
+ if variables:
77
+ for var in variables:
78
+ cmd.extend(["-v", var])
79
+
80
+ # The program or program_file argument must come after options but before input files.
81
+ if program_file:
82
+ cmd.extend(["-f", str(program_file)])
83
+ elif program:
84
+ cmd.append(program)
85
+
86
+ if input_files:
87
+ cmd.extend([str(p) for p in input_files])
88
+
89
+ command_executed = " ".join(cmd)
90
+
91
+ # --- Subprocess Execution ---
92
+ try:
93
+ if output_file:
94
+ # Create parent directories if they don't exist
95
+ output_file.parent.mkdir(parents=True, exist_ok=True)
96
+ with open(output_file, "w") as f_out:
97
+ result = subprocess.run(
98
+ cmd,
99
+ check=True,
100
+ stdout=f_out,
101
+ stderr=subprocess.PIPE,
102
+ text=True,
103
+ )
104
+ stdout_content = f"Output successfully written to {output_file}"
105
+ output_files_list = [str(output_file)]
106
+ else:
107
+ result = subprocess.run(
108
+ cmd,
109
+ check=True,
110
+ capture_output=True,
111
+ text=True,
112
+ )
113
+ stdout_content = result.stdout
114
+ output_files_list = []
115
+
116
+ return {
117
+ "command_executed": command_executed,
118
+ "stdout": stdout_content,
119
+ "stderr": result.stderr,
120
+ "output_files": output_files_list,
121
+ }
122
+ except FileNotFoundError:
123
+ raise RuntimeError("bioawk command not found. Please ensure it is installed and in your system's PATH.")
124
+ except subprocess.CalledProcessError as e:
125
+ # Return a structured error response if the command fails
126
+ return {
127
+ "command_executed": command_executed,
128
+ "stdout": e.stdout or "",
129
+ "stderr": e.stderr or f"bioawk exited with error code {e.returncode}",
130
+ "output_files": [],
131
+ "error": f"Command failed with exit code {e.returncode}"
132
+ }
Biomni/mcp_generated/mcp_bioawk/app/bioawk_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioawk/app/bioawk_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioawk'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioawk/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioawk/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioawk:
5
+ build: .
6
+ image: mcp-bioawk:latest
7
+ container_name: mcp-bioawk
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioawk
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_bioawk/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bioawk
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioawk/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-ancombc/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-ancombc/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import shutil
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Optional, List
7
+
8
+ # This is a placeholder for the actual decorator.
9
+ # In a real MCP environment, you would use `@mcp.tool()`.
10
+ def mcp_tool_placeholder(*args, **kwargs):
11
+ def decorator(func):
12
+ return func
13
+ return decorator
14
+
15
+ @mcp_tool_placeholder()
16
+ def forge_bsgenome_data_pkg(
17
+ organism: str,
18
+ common_name: str,
19
+ organism_abbreviation: str,
20
+ provider: str,
21
+ provider_version: str,
22
+ release_date: str,
23
+ package_version: str,
24
+ author: str,
25
+ maintainer: str,
26
+ seqs_srcdir: Path,
27
+ output_pkg_path: Path,
28
+ circ_seqs: Optional[List[str]] = None,
29
+ seqfiles_prefix: Optional[str] = None,
30
+ seqfiles_suffix: Optional[str] = None,
31
+ license_str: str = "Artistic-2.0",
32
+ title: Optional[str] = None,
33
+ description: Optional[str] = None,
34
+ organism_biocview: Optional[str] = None,
35
+ ) -> dict:
36
+ """
37
+ Creates a Bioconductor BSgenome data package from a set of FASTA files.
38
+
39
+ This tool wraps the `forgeBSgenomeDataPkg` function from the R/Bioconductor
40
+ BSgenome package. It automates the creation of the necessary seed file and
41
+ executes an R script to build the package source directory, which is then
42
+ returned as a gzipped tarball.
43
+
44
+ Args:
45
+ organism: Full organism name (e.g., "Homo sapiens").
46
+ common_name: Common name for the organism (e.g., "Human").
47
+ organism_abbreviation: Abbreviation for the organism, used in package naming (e.g., "Hsapiens").
48
+ provider: Source of the genome data (e.g., "UCSC").
49
+ provider_version: Version or build of the genome from the provider (e.g., "hg38").
50
+ release_date: Release date of the genome build (e.g., "Dec. 2013").
51
+ package_version: Version for the new BSgenome package, in X.Y.Z format (e.g., "1.0.0").
52
+ author: The author of this BSgenome package.
53
+ maintainer: The maintainer of the package, including email (e.g., "Your Name <you@example.com>").
54
+ seqs_srcdir: Input directory containing the genome sequence files (e.g., FASTA files).
55
+ output_pkg_path: Path to save the final gzipped tarball of the package source (e.g., 'MyPackage.tar.gz').
56
+ circ_seqs: Optional list of sequence names that are circular (e.g., ["chrM"]).
57
+ seqfiles_prefix: Optional prefix for the sequence files within `seqs_srcdir`.
58
+ seqfiles_suffix: Optional suffix for the sequence files (e.g., ".fa", ".fasta").
59
+ license_str: The license for the generated package.
60
+ title: Optional title for the package. A default will be generated if not provided.
61
+ description: Optional description for the package. A default will be generated if not provided.
62
+ organism_biocview: Optional Bioconductor view term for the organism (e.g., "Homo_sapiens").
63
+
64
+ Returns:
65
+ A dictionary containing the execution details and path to the output file.
66
+ """
67
+ # 1. Input Validation
68
+ if not seqs_srcdir.is_dir():
69
+ raise ValueError(f"Input directory '{seqs_srcdir}' does not exist or is not a directory.")
70
+ if not re.match(r"^\d+\.\d+\.\d+$", package_version):
71
+ raise ValueError(f"Package version '{package_version}' is not in X.Y.Z format.")
72
+ for name_part in [organism_abbreviation, provider]:
73
+ if not name_part.isalnum():
74
+ raise ValueError(f"Package name component '{name_part}' contains invalid characters. Use only alphanumerics.")
75
+ if not output_pkg_path.parent.exists():
76
+ output_pkg_path.parent.mkdir(parents=True, exist_ok=True)
77
+
78
+ # 2. Generate derived values
79
+ sanitized_provider_version = re.sub(r'[^a-zA-Z0-9.]', '', provider_version)
80
+ package_name = f"BSgenome.{organism_abbreviation}.{provider}.{sanitized_provider_version}"
81
+ bsgenome_objname = organism_abbreviation
82
+
83
+ if title is None:
84
+ title = f"Full genome sequences for {organism} ({provider} version {provider_version})"
85
+ if description is None:
86
+ description = (
87
+ f"Full genome sequences for {organism} ({common_name}) as provided by "
88
+ f"{provider} ({provider_version}, {release_date}) and stored in Biostrings objects."
89
+ )
90
+
91
+ organism_biocview_val = organism_biocview if organism_biocview else organism.replace(" ", "_")
92
+
93
+ # 3. Create seed file content
94
+ seed_content = [
95
+ f"Package: {package_name}",
96
+ f"Title: {title}",
97
+ f"Description: {description}",
98
+ f"Version: {package_version}",
99
+ f"Author: {author}",
100
+ f"Maintainer: {maintainer}",
101
+ f"License: {license_str}",
102
+ f"organism: {organism}",
103
+ f"common_name: {common_name}",
104
+ f"provider: {provider}",
105
+ f"provider_version: {provider_version}",
106
+ f"release_date: {release_date}",
107
+ f"BSgenomeObjname: {bsgenome_objname}",
108
+ f"organism_biocview: {organism_biocview_val}",
109
+ f"seqs_srcdir: {seqs_srcdir.resolve()}",
110
+ ]
111
+
112
+ if circ_seqs:
113
+ r_vector = ', '.join(f'"{seq}"' for seq in circ_seqs)
114
+ seed_content.append(f"circ_seqs: c({r_vector})")
115
+ if seqfiles_prefix:
116
+ seed_content.append(f"seqfiles_prefix: {seqfiles_prefix}")
117
+ if seqfiles_suffix:
118
+ seed_content.append(f"seqfiles_suffix: {seqfiles_suffix}")
119
+
120
+ seed_file_text = "\n".join(seed_content)
121
+
122
+ # 4. Create R script
123
+ r_script_text = f"""
124
+ # Ensure BiocManager and BSgenome are installed
125
+ if (!requireNamespace("BiocManager", quietly = TRUE)) {{
126
+ install.packages("BiocManager", repos="http://cran.us.r-project.org")
127
+ }}
128
+ if (!requireNamespace("BSgenome", quietly = TRUE)) {{
129
+ BiocManager::install("BSgenome", update=FALSE, ask=FALSE)
130
+ }}
131
+
132
+ library(BSgenome)
133
+
134
+ # Forge the package
135
+ tryCatch({{
136
+ forgeBSgenomeDataPkg(seed_file="seed.txt", destdir=".")
137
+ cat("BSgenome package '{package_name}' successfully forged.\\n")
138
+ }}, error = function(e) {{
139
+ cat("Error during package forging:\\n")
140
+ cat(conditionMessage(e), "\\n")
141
+ quit(status = 1)
142
+ }})
143
+ """
144
+
145
+ # 5. Execute in a temporary directory
146
+ with tempfile.TemporaryDirectory() as tmpdir:
147
+ work_dir = Path(tmpdir)
148
+ seed_file_path = work_dir / "seed.txt"
149
+ r_script_path = work_dir / "run_forge.R"
150
+
151
+ seed_file_path.write_text(seed_file_text)
152
+ r_script_path.write_text(r_script_text)
153
+
154
+ cmd = ["Rscript", str(r_script_path)]
155
+ command_executed = " ".join(cmd)
156
+
157
+ try:
158
+ process = subprocess.run(
159
+ cmd,
160
+ cwd=work_dir,
161
+ check=True,
162
+ capture_output=True,
163
+ text=True,
164
+ encoding='utf-8',
165
+ )
166
+
167
+ # 6. Package the result
168
+ created_pkg_dir = work_dir / package_name
169
+ if not created_pkg_dir.is_dir():
170
+ raise FileNotFoundError(
171
+ f"BSgenome package directory '{package_name}' was not created. "
172
+ f"Check R script output for errors.\nStderr: {process.stderr}"
173
+ )
174
+
175
+ archive_base_name = str(output_pkg_path.with_suffix(''))
176
+ archive_path_str = shutil.make_archive(
177
+ base_name=archive_base_name,
178
+ format='gztar',
179
+ root_dir=work_dir,
180
+ base_dir=package_name
181
+ )
182
+
183
+ final_archive_path = Path(archive_path_str)
184
+ if final_archive_path != output_pkg_path:
185
+ final_archive_path.rename(output_pkg_path)
186
+
187
+ return {
188
+ "command_executed": command_executed,
189
+ "stdout": process.stdout,
190
+ "stderr": process.stderr,
191
+ "output_files": {"bsgenome_package_archive": str(output_pkg_path)}
192
+ }
193
+
194
+ except subprocess.CalledProcessError as e:
195
+ # Using .decode() in case the output is not valid utf-8 on error
196
+ stdout = e.stdout if isinstance(e.stdout, str) else e.stdout.decode('utf-8', errors='replace')
197
+ stderr = e.stderr if isinstance(e.stderr, str) else e.stderr.decode('utf-8', errors='replace')
198
+ return {
199
+ "command_executed": command_executed,
200
+ "stdout": stdout,
201
+ "stderr": stderr,
202
+ "error": "R script execution failed.",
203
+ "return_code": e.returncode,
204
+ }
Biomni/mcp_generated/mcp_bioconductor-bsgenome/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-bsgenome:
5
+ build: .
6
+ image: mcp-bioconductor-bsgenome:latest
7
+ container_name: mcp-bioconductor-bsgenome
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-bsgenome
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_bioconductor-dnacopy/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bioconductor-dnacopy
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioconductor-genomeinfodbdata via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-genomeinfodbdata -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioconductor-genomeinfodbdata_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioconductor-genomeinfodbdata_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioconductor-genomeinfodbdata_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-genomeinfodbdata:
5
+ build: .
6
+ image: mcp-bioconductor-genomeinfodbdata:latest
7
+ container_name: mcp-bioconductor-genomeinfodbdata
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-genomeinfodbdata
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_bioconductor-genomeinfodbdata/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-go.db/app/bioconductor-go.db_server.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import List, Optional, Dict, Any
5
+
6
+ # MCP decorator is not imported as per instructions.
7
+ # It is assumed to be available in the execution environment.
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ SERVER_NAME = 'local_bioconductor_go_db'
12
+ mcp = FastMCP(SERVER_NAME)
13
+
14
+ @mcp.tool()
15
+ def list_go_db_columns() -> Dict[str, Any]:
16
+ """
17
+ Lists all available columns for querying in the Bioconductor GO.db.
18
+
19
+ This function executes an R script to load the GO.db package and
20
+ extract the names of all queryable columns. It is useful for discovering
21
+ what data can be retrieved using the `query_go_db` function.
22
+
23
+ Requirements:
24
+ - R must be installed and in the system's PATH.
25
+ - The R packages 'BiocManager', 'AnnotationDbi', and 'GO.db' must be installed.
26
+ (e.g., in R: BiocManager::install(c("AnnotationDbi", "GO.db")))
27
+ """
28
+ r_script = "library(GO.db); cat(columns(GO.db), sep='\\n')"
29
+ command = ["Rscript", "-e", r_script]
30
+ command_executed = " ".join(command)
31
+
32
+ try:
33
+ result = subprocess.run(
34
+ command,
35
+ capture_output=True,
36
+ text=True,
37
+ check=True,
38
+ )
39
+ # The columns are returned as a newline-separated string
40
+ columns_list = result.stdout.strip().split('\n')
41
+ return {
42
+ "command_executed": command_executed,
43
+ "stdout": f"Available columns: {', '.join(columns_list)}",
44
+ "stderr": result.stderr,
45
+ "columns": columns_list,
46
+ }
47
+ except FileNotFoundError:
48
+ return {
49
+ "command_executed": command_executed,
50
+ "stdout": "",
51
+ "stderr": "Error: 'Rscript' not found. Please ensure R is installed and in your PATH.",
52
+ "error": "Dependency not found"
53
+ }
54
+ except subprocess.CalledProcessError as e:
55
+ stderr = e.stderr
56
+ if "there is no package called" in stderr:
57
+ stderr += "\n\nHint: Please ensure the 'GO.db' and 'AnnotationDbi' R packages are installed."
58
+ return {
59
+ "command_executed": command_executed,
60
+ "stdout": e.stdout,
61
+ "stderr": stderr,
62
+ "error": "R script execution failed"
63
+ }
64
+
65
+ @mcp.tool()
66
+ def query_go_db(
67
+ go_ids: List[str],
68
+ columns: Optional[List[str]] = None,
69
+ output_file: Optional[Path] = None,
70
+ ) -> Dict[str, Any]:
71
+ """
72
+ Query the Bioconductor GO.db to retrieve information about Gene Ontology terms.
73
+
74
+ This tool uses R/Bioconductor's GO.db annotation package to fetch details
75
+ for a given list of GO IDs. You can specify which columns of data to retrieve.
76
+
77
+ Requirements:
78
+ - R must be installed and in the system's PATH.
79
+ - The R packages 'BiocManager', 'AnnotationDbi', and 'GO.db' must be installed.
80
+ (e.g., in R: BiocManager::install(c("AnnotationDbi", "GO.db")))
81
+
82
+ Args:
83
+ go_ids: A list of Gene Ontology (GO) IDs to query (e.g., ['GO:0005575', 'GO:0008150']).
84
+ columns: A list of columns to retrieve. Defaults to ['GOID', 'TERM', 'DEFINITION', 'ONTOLOGY'].
85
+ Use the `list_go_db_columns` tool to see all available columns.
86
+ output_file: Optional path to save the output as a tab-separated (TSV) file.
87
+ If not provided, the output is returned in stdout.
88
+ """
89
+ # --- Input Validation ---
90
+ if not go_ids:
91
+ raise ValueError("The 'go_ids' list cannot be empty.")
92
+
93
+ for go_id in go_ids:
94
+ if not isinstance(go_id, str) or not go_id.startswith("GO:"):
95
+ raise ValueError(f"Invalid GO ID format: '{go_id}'. Expected format is 'GO:#######'.")
96
+
97
+ if columns is None:
98
+ # Default columns provide the most common information
99
+ columns_to_query = ["GOID", "TERM", "DEFINITION", "ONTOLOGY"]
100
+ else:
101
+ if not isinstance(columns, list) or not all(isinstance(c, str) for c in columns):
102
+ raise TypeError("The 'columns' parameter must be a list of strings.")
103
+ columns_to_query = columns
104
+
105
+ # --- R Script Construction ---
106
+ # Convert Python lists to R vector syntax: c("item1", "item2")
107
+ r_go_ids = 'c({})'.format(','.join(f'"{gid}"' for gid in go_ids))
108
+ r_columns = 'c({})'.format(','.join(f'"{col}"' for col in columns_to_query))
109
+
110
+ # Using a temporary file for the output if no output_file is specified by the user
111
+ # This avoids complex stdout redirection and ensures clean capture
112
+ temp_dir = tempfile.TemporaryDirectory()
113
+ final_output_path = Path(output_file) if output_file else Path(temp_dir.name) / "go_query_results.tsv"
114
+
115
+ r_script = f"""
116
+ # Suppress package startup messages for cleaner output
117
+ suppressPackageStartupMessages(library(AnnotationDbi))
118
+ suppressPackageStartupMessages(library(GO.db))
119
+
120
+ go_keys <- {r_go_ids}
121
+ cols_to_get <- {r_columns}
122
+ output_path <- "{str(final_output_path)}"
123
+
124
+ # Perform the query
125
+ results <- tryCatch(
126
+ AnnotationDbi::select(GO.db, keys=go_keys, columns=cols_to_get, keytype="GOID"),
127
+ error = function(e) {{
128
+ write(paste("Error during AnnotationDbi::select:", e$message), stderr())
129
+ return(NULL)
130
+ }}
131
+ )
132
+
133
+ # Write results to the specified output file
134
+ if (!is.null(results)) {{
135
+ write.table(
136
+ results,
137
+ file=output_path,
138
+ sep="\\t",
139
+ row.names=FALSE,
140
+ quote=FALSE
141
+ )
142
+ }}
143
+ """
144
+
145
+ command = ["Rscript", "-e", r_script]
146
+ command_executed = " ".join(command)
147
+ output_files_list = []
148
+
149
+ try:
150
+ # --- Subprocess Execution ---
151
+ result = subprocess.run(
152
+ command,
153
+ capture_output=True,
154
+ text=True,
155
+ check=True,
156
+ )
157
+
158
+ stdout_content = ""
159
+ if final_output_path.exists() and final_output_path.stat().st_size > 0:
160
+ output_files_list.append(str(final_output_path))
161
+ # If no output file was requested by the user, read the temp file content for stdout
162
+ if not output_file:
163
+ stdout_content = final_output_path.read_text()
164
+ else:
165
+ # Handle cases where the R script runs but produces no output (e.g., no matches)
166
+ stdout_content = "Query executed successfully, but no results were found for the given GO IDs."
167
+
168
+ return {
169
+ "command_executed": command_executed,
170
+ "stdout": stdout_content,
171
+ "stderr": result.stderr,
172
+ "output_files": output_files_list,
173
+ }
174
+ except FileNotFoundError:
175
+ return {
176
+ "command_executed": command_executed,
177
+ "stdout": "",
178
+ "stderr": "Error: 'Rscript' not found. Please ensure R is installed and in your PATH.",
179
+ "error": "Dependency not found"
180
+ }
181
+ except subprocess.CalledProcessError as e:
182
+ stderr = e.stderr
183
+ if "there is no package called" in stderr:
184
+ stderr += "\n\nHint: Please ensure the 'GO.db' and 'AnnotationDbi' R packages are installed."
185
+ return {
186
+ "command_executed": command_executed,
187
+ "stdout": e.stdout,
188
+ "stderr": stderr,
189
+ "error": "R script execution failed"
190
+ }
191
+ finally:
192
+ # Clean up the temporary directory
193
+ temp_dir.cleanup()
194
+
195
+ if __name__ == "__main__":
196
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-go.db/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-hoodscanr/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioconductor-hoodscanr via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-hoodscanr -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioconductor-hoodscanr_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioconductor-hoodscanr_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioconductor-hoodscanr_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from pathlib import Path
3
+ from typing import Optional, List, Dict, Any
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ SERVER_NAME = 'local_bioconductor_hoodscanr'
8
+ mcp = FastMCP(SERVER_NAME)
9
+
10
+ @mcp.tool()
11
+ def hoodscanr_estimate_radius(
12
+ input_spe_path: str,
13
+ k: int = 6,
14
+ method: str = "mean",
15
+ ) -> Dict[str, Any]:
16
+ """
17
+ Estimate the search radius for neighborhood scanning based on the k-nearest neighbors
18
+ of cells in a SpatialExperiment object.
19
+
20
+ Args:
21
+ input_spe_path: Path to the input SpatialExperiment object saved as an RDS file.
22
+ k: Number of nearest neighbors to consider for radius estimation. Default is 6.
23
+ method: Method to summarize distances ('mean' or 'median'). Default is 'mean'.
24
+ """
25
+ input_path = Path(input_spe_path)
26
+ if not input_path.exists():
27
+ return {"error": f"Input file {input_spe_path} does not exist."}
28
+
29
+ if k <= 0:
30
+ return {"error": "Parameter 'k' must be a positive integer."}
31
+
32
+ if method not in ["mean", "median"]:
33
+ return {"error": "Parameter 'method' must be either 'mean' or 'median'."}
34
+
35
+ # R script to estimate radius
36
+ r_command = f"""
37
+ library(hoodscanR)
38
+ library(SpatialExperiment)
39
+ spe <- readRDS('{input_spe_path}')
40
+ radius <- estimate_radius(spe, k = {k}, method = '{method}')
41
+ cat(radius)
42
+ """
43
+
44
+ try:
45
+ result = subprocess.run(
46
+ ["Rscript", "-e", r_command],
47
+ capture_output=True,
48
+ text=True,
49
+ check=True
50
+ )
51
+ return {
52
+ "command_executed": f"hoodscanR::estimate_radius(spe, k={k}, method='{method}')",
53
+ "estimated_radius": float(result.stdout.strip()),
54
+ "stdout": result.stdout,
55
+ "stderr": result.stderr
56
+ }
57
+ except subprocess.CalledProcessError as e:
58
+ return {
59
+ "error": "R execution failed",
60
+ "stdout": e.stdout,
61
+ "stderr": e.stderr
62
+ }
63
+ except ValueError:
64
+ return {
65
+ "error": "Could not parse radius from R output",
66
+ "stdout": result.stdout
67
+ }
68
+
69
+ @mcp.tool()
70
+ def hoodscanr_hoodscan(
71
+ input_spe_path: str,
72
+ output_spe_path: str,
73
+ radius: float,
74
+ focal_cell_type: Optional[str] = None,
75
+ neighbor_cell_type: Optional[str] = None,
76
+ ) -> Dict[str, Any]:
77
+ """
78
+ Perform cellular neighborhood scanning on a SpatialExperiment object.
79
+
80
+ Args:
81
+ input_spe_path: Path to the input SpatialExperiment RDS file.
82
+ output_spe_path: Path where the updated SpatialExperiment RDS file will be saved.
83
+ radius: The search radius for identifying neighbors.
84
+ focal_cell_type: Optional; specific cell type to act as the center of neighborhoods.
85
+ neighbor_cell_type: Optional; specific cell type to look for in the neighborhood.
86
+ """
87
+ input_path = Path(input_spe_path)
88
+ if not input_path.exists():
89
+ return {"error": f"Input file {input_spe_path} does not exist."}
90
+
91
+ if radius <= 0:
92
+ return {"error": "Radius must be a positive number."}
93
+
94
+ focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL"
95
+ neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL"
96
+
97
+ r_command = f"""
98
+ library(hoodscanR)
99
+ library(SpatialExperiment)
100
+ spe <- readRDS('{input_spe_path}')
101
+ spe <- hoodscan(spe, radius = {radius}, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg})
102
+ saveRDS(spe, '{output_spe_path}')
103
+ """
104
+
105
+ try:
106
+ result = subprocess.run(
107
+ ["Rscript", "-e", r_command],
108
+ capture_output=True,
109
+ text=True,
110
+ check=True
111
+ )
112
+ return {
113
+ "command_executed": f"hoodscanR::hoodscan(spe, radius={radius})",
114
+ "stdout": result.stdout,
115
+ "stderr": result.stderr,
116
+ "output_files": [output_spe_path]
117
+ }
118
+ except subprocess.CalledProcessError as e:
119
+ return {
120
+ "error": "R execution failed",
121
+ "stdout": e.stdout,
122
+ "stderr": e.stderr
123
+ }
124
+
125
+ @mcp.tool()
126
+ def hoodscanr_calc_colocalisation(
127
+ input_spe_path: str,
128
+ output_csv_path: str,
129
+ focal_cell_type: Optional[str] = None,
130
+ neighbor_cell_type: Optional[str] = None,
131
+ ) -> Dict[str, Any]:
132
+ """
133
+ Calculate the colocalisation matrix between cell types based on neighborhood scanning results.
134
+
135
+ Args:
136
+ input_spe_path: Path to the SpatialExperiment RDS file (must have been processed by hoodscan).
137
+ output_csv_path: Path to save the colocalisation matrix as a CSV file.
138
+ focal_cell_type: Optional; filter for specific focal cell types.
139
+ neighbor_cell_type: Optional; filter for specific neighbor cell types.
140
+ """
141
+ input_path = Path(input_spe_path)
142
+ if not input_path.exists():
143
+ return {"error": f"Input file {input_spe_path} does not exist."}
144
+
145
+ focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL"
146
+ neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL"
147
+
148
+ r_command = f"""
149
+ library(hoodscanR)
150
+ library(SpatialExperiment)
151
+ spe <- readRDS('{input_spe_path}')
152
+ coloc_mat <- calc_colocalisation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg})
153
+ write.csv(as.matrix(coloc_mat), '{output_csv_path}')
154
+ """
155
+
156
+ try:
157
+ result = subprocess.run(
158
+ ["Rscript", "-e", r_command],
159
+ capture_output=True,
160
+ text=True,
161
+ check=True
162
+ )
163
+ return {
164
+ "command_executed": "hoodscanR::calc_colocalisation(spe)",
165
+ "stdout": result.stdout,
166
+ "stderr": result.stderr,
167
+ "output_files": [output_csv_path]
168
+ }
169
+ except subprocess.CalledProcessError as e:
170
+ return {
171
+ "error": "R execution failed",
172
+ "stdout": e.stdout,
173
+ "stderr": e.stderr
174
+ }
175
+
176
+ @mcp.tool()
177
+ def hoodscanr_calc_spatial_correlation(
178
+ input_spe_path: str,
179
+ output_csv_path: str,
180
+ focal_cell_type: Optional[str] = None,
181
+ neighbor_cell_type: Optional[str] = None,
182
+ ) -> Dict[str, Any]:
183
+ """
184
+ Calculate the spatial correlation between cell types.
185
+
186
+ Args:
187
+ input_spe_path: Path to the SpatialExperiment RDS file.
188
+ output_csv_path: Path to save the spatial correlation matrix as a CSV file.
189
+ focal_cell_type: Optional; filter for specific focal cell types.
190
+ neighbor_cell_type: Optional; filter for specific neighbor cell types.
191
+ """
192
+ input_path = Path(input_spe_path)
193
+ if not input_path.exists():
194
+ return {"error": f"Input file {input_spe_path} does not exist."}
195
+
196
+ focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL"
197
+ neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL"
198
+
199
+ r_command = f"""
200
+ library(hoodscanR)
201
+ library(SpatialExperiment)
202
+ spe <- readRDS('{input_spe_path}')
203
+ corr_mat <- calc_spatial_correlation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg})
204
+ write.csv(as.matrix(corr_mat), '{output_csv_path}')
205
+ """
206
+
207
+ try:
208
+ result = subprocess.run(
209
+ ["Rscript", "-e", r_command],
210
+ capture_output=True,
211
+ text=True,
212
+ check=True
213
+ )
214
+ return {
215
+ "command_executed": "hoodscanR::calc_spatial_correlation(spe)",
216
+ "stdout": result.stdout,
217
+ "stderr": result.stderr,
218
+ "output_files": [output_csv_path]
219
+ }
220
+ except subprocess.CalledProcessError as e:
221
+ return {
222
+ "error": "R execution failed",
223
+ "stdout": e.stdout,
224
+ "stderr": e.stderr
225
+ }
226
+
227
+ @mcp.tool()
228
+ def hoodscanr_plot_colocalisation(
229
+ input_spe_path: str,
230
+ output_plot_path: str,
231
+ focal_cell_type: Optional[str] = None,
232
+ neighbor_cell_type: Optional[str] = None,
233
+ width: int = 8,
234
+ height: int = 6,
235
+ ) -> Dict[str, Any]:
236
+ """
237
+ Generate a heatmap of the colocalisation matrix.
238
+
239
+ Args:
240
+ input_spe_path: Path to the SpatialExperiment RDS file.
241
+ output_plot_path: Path to save the plot (e.g., 'plot.png' or 'plot.pdf').
242
+ focal_cell_type: Optional; filter for specific focal cell types.
243
+ neighbor_cell_type: Optional; filter for specific neighbor cell types.
244
+ width: Width of the output plot in inches. Default is 8.
245
+ height: Height of the output plot in inches. Default is 6.
246
+ """
247
+ input_path = Path(input_spe_path)
248
+ if not input_path.exists():
249
+ return {"error": f"Input file {input_spe_path} does not exist."}
250
+
251
+ focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL"
252
+ neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL"
253
+
254
+ r_command = f"""
255
+ library(hoodscanR)
256
+ library(SpatialExperiment)
257
+ library(ggplot2)
258
+ spe <- readRDS('{input_spe_path}')
259
+ p <- plot_colocalisation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg})
260
+ ggsave('{output_plot_path}', plot = p, width = {width}, height = {height})
261
+ """
262
+
263
+ try:
264
+ result = subprocess.run(
265
+ ["Rscript", "-e", r_command],
266
+ capture_output=True,
267
+ text=True,
268
+ check=True
269
+ )
270
+ return {
271
+ "command_executed": "hoodscanR::plot_colocalisation(spe)",
272
+ "stdout": result.stdout,
273
+ "stderr": result.stderr,
274
+ "output_files": [output_plot_path]
275
+ }
276
+ except subprocess.CalledProcessError as e:
277
+ return {
278
+ "error": "R execution failed",
279
+ "stdout": e.stdout,
280
+ "stderr": e.stderr
281
+ }
282
+
283
+ @mcp.tool()
284
+ def hoodscanr_plot_spatial_correlation(
285
+ input_spe_path: str,
286
+ output_plot_path: str,
287
+ focal_cell_type: Optional[str] = None,
288
+ neighbor_cell_type: Optional[str] = None,
289
+ width: int = 8,
290
+ height: int = 6,
291
+ ) -> Dict[str, Any]:
292
+ """
293
+ Generate a heatmap of the spatial correlation matrix.
294
+
295
+ Args:
296
+ input_spe_path: Path to the SpatialExperiment RDS file.
297
+ output_plot_path: Path to save the plot (e.g., 'plot.png' or 'plot.pdf').
298
+ focal_cell_type: Optional; filter for specific focal cell types.
299
+ neighbor_cell_type: Optional; filter for specific neighbor cell types.
300
+ width: Width of the output plot in inches. Default is 8.
301
+ height: Height of the output plot in inches. Default is 6.
302
+ """
303
+ input_path = Path(input_spe_path)
304
+ if not input_path.exists():
305
+ return {"error": f"Input file {input_spe_path} does not exist."}
306
+
307
+ focal_arg = f"'{focal_cell_type}'" if focal_cell_type else "NULL"
308
+ neighbor_arg = f"'{neighbor_cell_type}'" if neighbor_cell_type else "NULL"
309
+
310
+ r_command = f"""
311
+ library(hoodscanR)
312
+ library(SpatialExperiment)
313
+ library(ggplot2)
314
+ spe <- readRDS('{input_spe_path}')
315
+ p <- plot_spatial_correlation(spe, focal_cell_type = {focal_arg}, neighbor_cell_type = {neighbor_arg})
316
+ ggsave('{output_plot_path}', plot = p, width = {width}, height = {height})
317
+ """
318
+
319
+ try:
320
+ result = subprocess.run(
321
+ ["Rscript", "-e", r_command],
322
+ capture_output=True,
323
+ text=True,
324
+ check=True
325
+ )
326
+ return {
327
+ "command_executed": "hoodscanR::plot_spatial_correlation(spe)",
328
+ "stdout": result.stdout,
329
+ "stderr": result.stderr,
330
+ "output_files": [output_plot_path]
331
+ }
332
+ except subprocess.CalledProcessError as e:
333
+ return {
334
+ "error": "R execution failed",
335
+ "stdout": e.stdout,
336
+ "stderr": e.stderr
337
+ }
338
+
339
+ if __name__ == "__main__":
340
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_hoodscanr'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioconductor-org.mm.eg.db via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-org.mm.eg.db -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioconductor-org.mm.eg.db_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioconductor-org.mm.eg.db_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioconductor-org.mm.eg.db_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_server.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import List, Optional
5
+
6
+ # MCP.TOOL DECORATOR (should not be included in the final output)
7
+ # This is a placeholder for the actual decorator.
8
+ def mcp_tool_placeholder(*args, **kwargs):
9
+ def decorator(func):
10
+ return func
11
+ return decorator
12
+
13
+ # In a real MCP environment, the following line would be:
14
+ # import mcp
15
+ # and the decorator would be @mcp.tool()
16
+ mcp = type("mcp", (), {"tool": mcp_tool_placeholder})
17
+
18
+
19
+ from mcp.server.fastmcp import FastMCP
20
+
21
+ SERVER_NAME = 'local_bioconductor_org_mm_eg_db'
22
+ mcp = FastMCP(SERVER_NAME)
23
+
24
+ @mcp.tool()
25
+ def org_mm_eg_db_query(
26
+ keys: List[str],
27
+ keytype: str,
28
+ columns: List[str],
29
+ output_file: Path,
30
+ ) -> dict:
31
+ """
32
+ Performs an annotation query on the org.Mm.eg.db database.
33
+
34
+ This tool uses the AnnotationDbi::select function to retrieve annotations
35
+ for a given set of keys. It maps identifiers from a specified keytype to
36
+ the desired annotation columns.
37
+
38
+ Args:
39
+ keys: A list of identifiers to query (e.g., ["14189", "14190"]).
40
+ keytype: The type of identifier being provided in `keys`.
41
+ Use the `org_mm_eg_db_list_keytypes` tool to see available options.
42
+ Example: "ENTREZID".
43
+ columns: A list of annotation columns to retrieve.
44
+ Use the `org_mm_eg_db_list_columns` tool to see available options.
45
+ Example: ["SYMBOL", "GENENAME", "GO"].
46
+ output_file: The path to save the resulting annotation table in TSV format.
47
+
48
+ Returns:
49
+ A dictionary containing the command executed, stdout, stderr, and the output file path.
50
+ """
51
+ if not keys:
52
+ raise ValueError("The 'keys' list cannot be empty.")
53
+ if not keytype:
54
+ raise ValueError("The 'keytype' parameter cannot be empty.")
55
+ if not columns:
56
+ raise ValueError("The 'columns' list cannot be empty.")
57
+
58
+ # Convert Python lists to R vector strings, e.g., c("item1", "item2")
59
+ r_keys = 'c({})'.format(', '.join(f'"{key}"' for key in keys))
60
+ r_columns = 'c({})'.format(', '.join(f'"{col}"' for col in columns))
61
+
62
+ r_script_content = f"""
63
+ # Suppress startup messages for cleaner output
64
+ suppressPackageStartupMessages(library("AnnotationDbi"))
65
+ suppressPackageStartupMessages(library("org.Mm.eg.db"))
66
+
67
+ # Define parameters
68
+ keys_to_query <- {r_keys}
69
+ cols_to_fetch <- {r_columns}
70
+ key_type <- "{keytype}"
71
+ output_path <- "{output_file}"
72
+
73
+ # Perform the query
74
+ # Use tryCatch to handle potential errors, like invalid keytypes or columns
75
+ results <- tryCatch(
76
+ {{
77
+ AnnotationDbi::select(
78
+ org.Mm.eg.db,
79
+ keys = keys_to_query,
80
+ columns = cols_to_fetch,
81
+ keytype = key_type
82
+ )
83
+ }},
84
+ error = function(e) {{
85
+ stop(paste("AnnotationDbi::select failed:", e$message))
86
+ }}
87
+ )
88
+
89
+ # Write the output
90
+ write.table(
91
+ results,
92
+ file = output_path,
93
+ sep = "\\t",
94
+ row.names = FALSE,
95
+ quote = FALSE
96
+ )
97
+ """
98
+
99
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_script:
100
+ tmp_script.write(r_script_content)
101
+ script_path = tmp_script.name
102
+
103
+ cmd = ["Rscript", script_path]
104
+ command_executed = " ".join(cmd)
105
+
106
+ try:
107
+ result = subprocess.run(
108
+ cmd,
109
+ capture_output=True,
110
+ text=True,
111
+ check=True,
112
+ )
113
+ # Clean up the temporary script file
114
+ Path(script_path).unlink()
115
+ return {
116
+ "command_executed": command_executed,
117
+ "stdout": result.stdout,
118
+ "stderr": result.stderr,
119
+ "output_files": [str(output_file)],
120
+ }
121
+ except subprocess.CalledProcessError as e:
122
+ # Clean up the temporary script file even on error
123
+ Path(script_path).unlink()
124
+ raise RuntimeError(
125
+ f"R script execution failed with exit code {e.returncode}.\n"
126
+ f"Command: {command_executed}\n"
127
+ f"Stdout: {e.stdout}\n"
128
+ f"Stderr: {e.stderr}"
129
+ ) from e
130
+
131
+
132
+ @mcp.tool()
133
+ def org_mm_eg_db_list_columns() -> dict:
134
+ """
135
+ Lists all available annotation columns in the org.Mm.eg.db database.
136
+
137
+ These column names can be used in the `columns` parameter of the
138
+ `org_mm_eg_db_query` tool.
139
+
140
+ Returns:
141
+ A dictionary containing the command executed and a list of available columns in stdout.
142
+ """
143
+ r_script_content = """
144
+ suppressPackageStartupMessages(library("org.Mm.eg.db"))
145
+ cat(columns(org.Mm.eg.db), sep = "\\n")
146
+ """
147
+ cmd = ["Rscript", "-e", r_script_content]
148
+ command_executed = " ".join(cmd)
149
+
150
+ try:
151
+ result = subprocess.run(
152
+ cmd,
153
+ capture_output=True,
154
+ text=True,
155
+ check=True,
156
+ )
157
+ return {
158
+ "command_executed": command_executed,
159
+ "stdout": result.stdout,
160
+ "stderr": result.stderr,
161
+ }
162
+ except subprocess.CalledProcessError as e:
163
+ raise RuntimeError(
164
+ f"R script execution failed with exit code {e.returncode}.\n"
165
+ f"Command: {command_executed}\n"
166
+ f"Stdout: {e.stdout}\n"
167
+ f"Stderr: {e.stderr}"
168
+ ) from e
169
+
170
+
171
+ @mcp.tool()
172
+ def org_mm_eg_db_list_keytypes() -> dict:
173
+ """
174
+ Lists all available keytypes in the org.Mm.eg.db database.
175
+
176
+ Keytypes define the type of primary identifiers that can be used for querying
177
+ in the `keytype` parameter of the `org_mm_eg_db_query` and
178
+ `org_mm_eg_db_list_keys` tools.
179
+
180
+ Returns:
181
+ A dictionary containing the command executed and a list of available keytypes in stdout.
182
+ """
183
+ r_script_content = """
184
+ suppressPackageStartupMessages(library("org.Mm.eg.db"))
185
+ cat(keytypes(org.Mm.eg.db), sep = "\\n")
186
+ """
187
+ cmd = ["Rscript", "-e", r_script_content]
188
+ command_executed = " ".join(cmd)
189
+
190
+ try:
191
+ result = subprocess.run(
192
+ cmd,
193
+ capture_output=True,
194
+ text=True,
195
+ check=True,
196
+ )
197
+ return {
198
+ "command_executed": command_executed,
199
+ "stdout": result.stdout,
200
+ "stderr": result.stderr,
201
+ }
202
+ except subprocess.CalledProcessError as e:
203
+ raise RuntimeError(
204
+ f"R script execution failed with exit code {e.returncode}.\n"
205
+ f"Command: {command_executed}\n"
206
+ f"Stdout: {e.stdout}\n"
207
+ f"Stderr: {e.stderr}"
208
+ ) from e
209
+
210
+
211
+ @mcp.tool()
212
+ def org_mm_eg_db_list_keys(
213
+ keytype: str,
214
+ output_file: Optional[Path] = None,
215
+ ) -> dict:
216
+ """
217
+ Lists all primary keys for a given keytype from the org.Mm.eg.db database.
218
+
219
+ Args:
220
+ keytype: The type of identifiers to retrieve. Use the
221
+ `org_mm_eg_db_list_keytypes` tool to see available options.
222
+ output_file: Optional. If provided, saves the list of keys to this file,
223
+ one key per line. Otherwise, keys are printed to stdout.
224
+
225
+ Returns:
226
+ A dictionary containing the command executed, stdout, stderr, and the output file path if provided.
227
+ """
228
+ if not keytype:
229
+ raise ValueError("The 'keytype' parameter cannot be empty.")
230
+
231
+ if output_file:
232
+ r_script_content = f"""
233
+ suppressPackageStartupMessages(library("org.Mm.eg.db"))
234
+ key_type <- "{keytype}"
235
+ output_path <- "{output_file}"
236
+ keys_list <- keys(org.Mm.eg.db, keytype = key_type)
237
+ write.table(
238
+ keys_list,
239
+ file = output_path,
240
+ col.names = FALSE,
241
+ row.names = FALSE,
242
+ quote = FALSE
243
+ )
244
+ """
245
+ else:
246
+ r_script_content = f"""
247
+ suppressPackageStartupMessages(library("org.Mm.eg.db"))
248
+ key_type <- "{keytype}"
249
+ keys_list <- keys(org.Mm.eg.db, keytype = key_type)
250
+ cat(keys_list, sep = "\\n")
251
+ """
252
+
253
+ cmd = ["Rscript", "-e", r_script_content]
254
+ command_executed = " ".join(cmd)
255
+
256
+ try:
257
+ result = subprocess.run(
258
+ cmd,
259
+ capture_output=True,
260
+ text=True,
261
+ check=True,
262
+ )
263
+ output_files = [str(output_file)] if output_file else []
264
+ return {
265
+ "command_executed": command_executed,
266
+ "stdout": result.stdout,
267
+ "stderr": result.stderr,
268
+ "output_files": output_files,
269
+ }
270
+ except subprocess.CalledProcessError as e:
271
+ raise RuntimeError(
272
+ f"R script execution failed with exit code {e.returncode}.\n"
273
+ f"Command: {command_executed}\n"
274
+ f"Stdout: {e.stdout}\n"
275
+ f"Stderr: {e.stderr}"
276
+ ) from e
277
+
278
+ if __name__ == "__main__":
279
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-org.mm.eg.db:
5
+ build: .
6
+ image: mcp-bioconductor-org.mm.eg.db:latest
7
+ container_name: mcp-bioconductor-org.mm.eg.db
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-org.mm.eg.db
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bioconductor-org.mm.eg.db
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-org.mm.eg.db/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-poem/app/bioconductor-poem_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_poem'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-seraster/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioconductor-seraster via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-seraster -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioconductor-seraster_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioconductor-seraster_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioconductor-seraster_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-seraster/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bioconductor-seraster
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install bioconductor-spatialomicsoverlay via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-spatialomicsoverlay -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/bioconductor-spatialomicsoverlay_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/bioconductor-spatialomicsoverlay_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/bioconductor-spatialomicsoverlay_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import Optional, Literal, List
5
+ import textwrap
6
+
7
+ # from mcp import tool as mcp_tool
8
+ # The @mcp.tool decorator is commented out as 'mcp' is not available for import.
9
+ # In a real MCP environment, this would be uncommented.
10
+
11
+ # @mcp_tool()
12
+ def spatial_overlay(
13
+ spe_object: Path,
14
+ tissue_data: Path,
15
+ image_data: Path,
16
+ sample_id: str,
17
+ image_resolution: float,
18
+ output_file: Path,
19
+ fluorescence_data: Optional[Path] = None,
20
+ flip: Literal["none", "h", "v", "both"] = "none",
21
+ show_scale_bar: bool = True,
22
+ show_legend: bool = True,
23
+ point_alpha: Optional[float] = None,
24
+ point_size: Optional[float] = None,
25
+ point_color: Optional[str] = None,
26
+ ) -> dict:
27
+ """
28
+ Generates a spatial overlay plot using the Bioconductor spatialomicsOverlay package.
29
+
30
+ This tool overlays spatial transcriptomics data (from a SpatialExperiment or Seurat
31
+ object) onto high-resolution histology images, allowing for visualization of
32
+ gene expression or other features in their spatial context.
33
+
34
+ Args:
35
+ spe_object: Path to the input SpatialExperiment or Seurat object in RDS format.
36
+ tissue_data: Path to the tissue segmentation data in CSV or TSV format.
37
+ image_data: Path to the image data in CSV or TSV format.
38
+ sample_id: The sample ID within the object to plot.
39
+ image_resolution: The resolution of the image (e.g., pixels per micron).
40
+ output_file: Path to save the output plot (e.g., plot.png, plot.pdf).
41
+ fluorescence_data: Optional path to fluorescence data in CSV or TSV format.
42
+ flip: Image flip orientation. Can be 'none', 'h' (horizontal), 'v' (vertical), or 'both'.
43
+ show_scale_bar: If True, a scale bar is added to the plot.
44
+ show_legend: If True, a legend is added to the plot.
45
+ point_alpha: Optional opacity for the data points (value between 0.0 and 1.0).
46
+ point_size: Optional size for the data points.
47
+ point_color: Optional color for the data points (e.g., 'blue', '#FF0000').
48
+
49
+ Returns:
50
+ A dictionary containing the execution command, stdout, stderr, and a list of output files.
51
+ """
52
+ # 1. Input Validation
53
+ if not spe_object.is_file():
54
+ raise FileNotFoundError(f"Input SPE object not found: {spe_object}")
55
+ if not tissue_data.is_file():
56
+ raise FileNotFoundError(f"Input tissue data not found: {tissue_data}")
57
+ if not image_data.is_file():
58
+ raise FileNotFoundError(f"Input image data not found: {image_data}")
59
+ if fluorescence_data and not fluorescence_data.is_file():
60
+ raise FileNotFoundError(f"Input fluorescence data not found: {fluorescence_data}")
61
+
62
+ if point_alpha is not None and not (0.0 <= point_alpha <= 1.0):
63
+ raise ValueError("point_alpha must be between 0.0 and 1.0")
64
+ if point_size is not None and point_size < 0:
65
+ raise ValueError("point_size must be a non-negative number")
66
+
67
+ output_file.parent.mkdir(parents=True, exist_ok=True)
68
+
69
+ # 2. R Script Generation
70
+ # This R script is generated on-the-fly to call the spatialomicsOverlay functions.
71
+ r_script_content = textwrap.dedent("""\
72
+ suppressPackageStartupMessages(library(optparse))
73
+ suppressPackageStartupMessages(library(spatialomicsOverlay))
74
+ suppressPackageStartupMessages(library(SpatialExperiment))
75
+ suppressPackageStartupMessages(library(ggplot2))
76
+ suppressPackageStartupMessages(library(SeuratObject))
77
+
78
+ option_list <- list(
79
+ make_option(c("--spe_object"), type="character", help="Path to the SpatialExperiment/Seurat RDS object"),
80
+ make_option(c("--tissue_data"), type="character", help="Path to the tissue segmentation data (CSV/TSV)"),
81
+ make_option(c("--image_data"), type="character", help="Path to the image data (CSV/TSV)"),
82
+ make_option(c("--sample_id"), type="character", help="Sample ID to plot"),
83
+ make_option(c("--image_resolution"), type="double", help="Resolution of the image"),
84
+ make_option(c("--output_file"), type="character", help="Path for the output plot"),
85
+ make_option(c("--fluorescence_data"), type="character", default=NULL, help="Path to optional fluorescence data"),
86
+ make_option(c("--flip"), type="character", default="none", help="Image flip orientation (none, h, v, both)"),
87
+ make_option(c("--show_scale_bar"), action="store_true", default=FALSE, help="Flag to show scale bar"),
88
+ make_option(c("--show_legend"), action="store_true", default=FALSE, help="Flag to show legend"),
89
+ make_option(c("--point_alpha"), type="double", default=NULL, help="Alpha for points"),
90
+ make_option(c("--point_size"), type="double", default=NULL, help="Size for points"),
91
+ make_option(c("--point_color"), type="character", default=NULL, help="Color for points")
92
+ )
93
+
94
+ opt_parser <- OptionParser(option_list=option_list)
95
+ opt <- parse_args(opt_parser)
96
+
97
+ if (is.null(opt$spe_object) || is.null(opt$tissue_data) || is.null(opt$image_data) || is.null(opt$sample_id) || is.null(opt$image_resolution) || is.null(opt$output_file)) {
98
+ print_help(opt_parser)
99
+ stop("Missing one or more required arguments.", call.=FALSE)
100
+ }
101
+
102
+ # Load data
103
+ spe <- readRDS(opt$spe_object)
104
+ tissue <- read.csv(opt$tissue_data, row.names=1)
105
+ image <- read.csv(opt$image_data, row.names=1)
106
+ fluor <- if (!is.null(opt$fluorescence_data)) read.csv(opt$fluorescence_data, row.names=1) else NULL
107
+
108
+ # Prepare additional arguments for geom_point
109
+ geom_args <- list()
110
+ if (!is.null(opt$point_alpha)) geom_args$alpha <- opt$point_alpha
111
+ if (!is.null(opt$point_size)) geom_args$size <- opt$point_size
112
+ if (!is.null(opt$point_color)) geom_args$color <- opt$point_color
113
+
114
+ # Generate plot by calling spatialOverlay with dynamic arguments
115
+ plot_obj <- do.call(spatialOverlay, c(
116
+ list(
117
+ object = spe,
118
+ tissue = tissue,
119
+ sample = opt$sample_id,
120
+ image = image,
121
+ res = opt$image_resolution,
122
+ fluor = fluor,
123
+ flip = opt$flip,
124
+ scaleBar = opt$show_scale_bar,
125
+ legend = opt$show_legend
126
+ ),
127
+ geom_args
128
+ ))
129
+
130
+ # Save the generated plot
131
+ ggsave(opt$output_file, plot=plot_obj, width=10, height=10, units="in")
132
+
133
+ cat("Plot saved successfully to:", opt$output_file, "\\n")
134
+ """)
135
+
136
+ # 3. Command Construction
137
+ r_script_path = ""
138
+ try:
139
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file:
140
+ r_script_file.write(r_script_content)
141
+ r_script_path = r_script_file.name
142
+
143
+ cmd = [
144
+ "Rscript",
145
+ r_script_path,
146
+ "--spe_object", str(spe_object.resolve()),
147
+ "--tissue_data", str(tissue_data.resolve()),
148
+ "--image_data", str(image_data.resolve()),
149
+ "--sample_id", sample_id,
150
+ "--image_resolution", str(image_resolution),
151
+ "--output_file", str(output_file.resolve()),
152
+ "--flip", flip,
153
+ ]
154
+
155
+ if fluorescence_data:
156
+ cmd.extend(["--fluorescence_data", str(fluorescence_data.resolve())])
157
+ if show_scale_bar:
158
+ cmd.append("--show_scale_bar")
159
+ if show_legend:
160
+ cmd.append("--show_legend")
161
+ if point_alpha is not None:
162
+ cmd.extend(["--point_alpha", str(point_alpha)])
163
+ if point_size is not None:
164
+ cmd.extend(["--point_size", str(point_size)])
165
+ if point_color is not None:
166
+ cmd.extend(["--point_color", point_color])
167
+
168
+ # 4. Subprocess Execution
169
+ command_executed = " ".join(cmd)
170
+ process = subprocess.run(
171
+ cmd,
172
+ check=True,
173
+ capture_output=True,
174
+ text=True,
175
+ )
176
+
177
+ # 5. Structured Result Return
178
+ return {
179
+ "command_executed": command_executed,
180
+ "stdout": process.stdout,
181
+ "stderr": process.stderr,
182
+ "output_files": [str(output_file.resolve())]
183
+ }
184
+
185
+ except subprocess.CalledProcessError as e:
186
+ # Enhanced error reporting
187
+ error_message = (
188
+ f"R script execution failed with return code {e.returncode}.\n"
189
+ f"Stderr:\n{e.stderr}\n"
190
+ f"Stdout:\n{e.stdout}"
191
+ )
192
+ raise RuntimeError(error_message) from e
193
+
194
+ finally:
195
+ # Clean up the temporary R script
196
+ if r_script_path and Path(r_script_path).exists():
197
+ Path(r_script_path).unlink()
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_spatialomicsoverlay'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-spatialomicsoverlay:
5
+ build: .
6
+ image: mcp-bioconductor-spatialomicsoverlay:latest
7
+ container_name: mcp-bioconductor-spatialomicsoverlay
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-spatialomicsoverlay
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bioconductor-spatialomicsoverlay
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-spatialomicsoverlay/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_checkm-genome/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install checkm-genome via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda checkm-genome -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/checkm-genome_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/checkm-genome_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/checkm-genome_server.py"]
40
+
Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_server.py ADDED
@@ -0,0 +1,1238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import List, Optional, Dict, Any, Union
5
+
6
+ # Helper function to execute CheckM commands
7
+ def _run_checkm_command(
8
+ command: List[str],
9
+ output_dir: Optional[Path] = None,
10
+ log_file: Optional[Path] = None,
11
+ quiet: bool = False,
12
+ debug: bool = False,
13
+ force: bool = False,
14
+ tmpdir: Optional[Path] = None,
15
+ threads: int = 1,
16
+ pplacer_path: Optional[Path] = None,
17
+ hmmer_path: Optional[Path] = None,
18
+ prodigal_path: Optional[Path] = None,
19
+ ) -> Dict[str, Any]:
20
+ """
21
+ Internal helper function to execute CheckM commands and handle common options.
22
+
23
+ Args:
24
+ command: A list of strings representing the CheckM subcommand and its specific arguments.
25
+ output_dir: An optional Path to a directory where the tool is expected to write output files.
26
+ Used for collecting output_files in the result.
27
+ log_file: An optional Path to a file where stdout/stderr should be redirected.
28
+ quiet: If True, suppress CheckM's stdout.
29
+ debug: If True, enable CheckM's debug mode.
30
+ force: If True, force overwrite of existing files.
31
+ tmpdir: An optional Path to a temporary directory for CheckM.
32
+ threads: Number of threads for CheckM to use.
33
+ pplacer_path: Path to the pplacer executable.
34
+ hmmer_path: Path to HMMER executables.
35
+ prodigal_path: Path to the Prodigal executable.
36
+
37
+ Returns:
38
+ A dictionary containing the command executed, stdout, stderr, and any output files.
39
+ """
40
+ cmd = ["checkm"] + command
41
+
42
+ if quiet:
43
+ cmd.append("-q")
44
+ if debug:
45
+ cmd.append("-d")
46
+ if force:
47
+ cmd.append("--force")
48
+ if tmpdir:
49
+ if not tmpdir.is_dir():
50
+ raise ValueError(f"Temporary directory '{tmpdir}' does not exist or is not a directory.")
51
+ cmd.extend(["--tmpdir", str(tmpdir)])
52
+ if threads > 1:
53
+ cmd.extend(["-t", str(threads)])
54
+ if pplacer_path:
55
+ if not pplacer_path.is_file():
56
+ raise ValueError(f"pplacer executable '{pplacer_path}' not found.")
57
+ cmd.extend(["--pplacer_path", str(pplacer_path)])
58
+ if hmmer_path:
59
+ if not hmmer_path.is_file():
60
+ raise ValueError(f"HMMER executable '{hmmer_path}' not found.")
61
+ cmd.extend(["--hmmer_path", str(hmmer_path)])
62
+ if prodigal_path:
63
+ if not prodigal_path.is_file():
64
+ raise ValueError(f"Prodigal executable '{prodigal_path}' not found.")
65
+ cmd.extend(["--prodigal_path", str(prodigal_path)])
66
+
67
+ stdout_output = ""
68
+ stderr_output = ""
69
+ output_files = []
70
+
71
+ try:
72
+ process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
73
+ stdout_output = process.stdout
74
+ stderr_output = process.stderr
75
+ except subprocess.CalledProcessError as e:
76
+ stdout_output = e.stdout
77
+ stderr_output = e.stderr
78
+ return {
79
+ "command_executed": " ".join(cmd),
80
+ "stdout": stdout_output,
81
+ "stderr": stderr_output,
82
+ "output_files": [],
83
+ "error": str(e),
84
+ }
85
+
86
+ if log_file:
87
+ log_file.parent.mkdir(parents=True, exist_ok=True)
88
+ with open(log_file, "w") as f:
89
+ f.write(f"Command: {' '.join(cmd)}\n\n")
90
+ f.write("STDOUT:\n")
91
+ f.write(stdout_output)
92
+ f.write("\nSTDERR:\n")
93
+ f.write(stderr_output)
94
+
95
+ if output_dir and output_dir.is_dir():
96
+ output_files.extend([str(p) for p in output_dir.rglob("*") if p.is_file()])
97
+
98
+ return {
99
+ "command_executed": " ".join(cmd),
100
+ "stdout": stdout_output,
101
+ "stderr": stderr_output,
102
+ "output_files": output_files,
103
+ }
104
+
105
+ # Helper function to add plotting arguments
106
+ def _add_plotting_args(
107
+ cmd: List[str],
108
+ image_file: Optional[Path] = None,
109
+ image_type: Optional[str] = None,
110
+ font_size: Optional[int] = None,
111
+ dpi: Optional[int] = None,
112
+ width: Optional[float] = None,
113
+ height: Optional[float] = None,
114
+ title: Optional[str] = None,
115
+ label: Optional[str] = None,
116
+ color: Optional[str] = None,
117
+ marker_size: Optional[int] = None,
118
+ line_width: Optional[int] = None,
119
+ ) -> None:
120
+ """
121
+ Internal helper function to append common plotting arguments to a command list.
122
+ Only appends arguments if their values are explicitly provided (not None).
123
+ """
124
+ if image_file:
125
+ cmd.extend(["--image_file", str(image_file)])
126
+ if image_type:
127
+ cmd.extend(["--image_type", image_type])
128
+ if font_size is not None:
129
+ cmd.extend(["--font_size", str(font_size)])
130
+ if dpi is not None:
131
+ cmd.extend(["--dpi", str(dpi)])
132
+ if width is not None:
133
+ cmd.extend(["--width", str(width)])
134
+ if height is not None:
135
+ cmd.extend(["--height", str(height)])
136
+ if title:
137
+ cmd.extend(["--title", title])
138
+ if label:
139
+ cmd.extend(["--label", label])
140
+ if color:
141
+ cmd.extend(["--color", color])
142
+ if marker_size is not None:
143
+ cmd.extend(["--marker_size", str(marker_size)])
144
+ if line_width is not None:
145
+ cmd.extend(["--line_width", str(line_width)])
146
+
147
+
148
+ from mcp.server.fastmcp import FastMCP
149
+
150
+ SERVER_NAME = 'local_checkm_genome'
151
+ mcp = FastMCP(SERVER_NAME)
152
+
153
+ @mcp.tool()
154
+ def data(
155
+ download_folder: Optional[Path] = None,
156
+ output_file: Optional[Path] = None,
157
+ force: bool = False,
158
+ quiet: bool = False,
159
+ debug: bool = False,
160
+ ) -> Dict[str, Any]:
161
+ """
162
+ Downloads and installs the CheckM reference data.
163
+
164
+ Args:
165
+ download_folder: Folder to download data to. If not specified, CheckM's default data folder is used.
166
+ output_file: File to write output to. If not specified, output goes to stdout.
167
+ force: Force overwrite of existing files.
168
+ quiet: Suppress stdout.
169
+ debug: Enable debug mode.
170
+
171
+ Returns:
172
+ A dictionary containing the command executed, stdout, stderr, and any output files.
173
+ """
174
+ cmd = ["data"]
175
+
176
+ if download_folder:
177
+ download_folder.mkdir(parents=True, exist_ok=True)
178
+ cmd.extend(["--download_folder", str(download_folder)])
179
+
180
+ if output_file:
181
+ output_file.parent.mkdir(parents=True, exist_ok=True)
182
+ cmd.extend(["--file", str(output_file)])
183
+
184
+ return _run_checkm_command(
185
+ cmd,
186
+ log_file=output_file,
187
+ force=force,
188
+ quiet=quiet,
189
+ debug=debug,
190
+ )
191
+
192
+
193
+ @mcp.tool()
194
+ def lineage_wf(
195
+ genome_folder: Path,
196
+ output_folder: Path,
197
+ extension: str = "fna",
198
+ output_file: Optional[Path] = None,
199
+ reduced_tree: bool = False,
200
+ output_alignment_files: bool = False,
201
+ output_nucleotide_sequences: bool = False,
202
+ tab_output_file: Optional[Path] = None,
203
+ skip_gtdb_r207: bool = False,
204
+ skip_gtdb_r207_cpr: bool = False,
205
+ quiet: bool = False,
206
+ debug: bool = False,
207
+ force: bool = False,
208
+ tmpdir: Optional[Path] = None,
209
+ threads: int = 1,
210
+ pplacer_path: Optional[Path] = None,
211
+ hmmer_path: Optional[Path] = None,
212
+ prodigal_path: Optional[Path] = None,
213
+ ) -> Dict[str, Any]:
214
+ """
215
+ Performs a lineage-specific workflow for assessing genome quality.
216
+ This is the recommended workflow for most users.
217
+
218
+ Args:
219
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
220
+ output_folder: Folder to write output to.
221
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
222
+ output_file: File to write summary output to. If not specified, output goes to stdout.
223
+ reduced_tree: Use a reduced tree for phylogenetic placement.
224
+ output_alignment_files: Output alignment files.
225
+ output_nucleotide_sequences: Output nucleotide sequences.
226
+ tab_output_file: File to write tab-separated output to.
227
+ skip_gtdb_r207: Skip GTDB R207 marker set.
228
+ skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set.
229
+ quiet: Suppress stdout.
230
+ debug: Enable debug mode.
231
+ force: Force overwrite of existing files.
232
+ tmpdir: Temporary directory.
233
+ threads: Number of threads to use.
234
+ pplacer_path: Path to pplacer executable.
235
+ hmmer_path: Path to HMMER executables.
236
+ prodigal_path: Path to Prodigal executable.
237
+
238
+ Returns:
239
+ A dictionary containing the command executed, stdout, stderr, and any output files.
240
+ """
241
+ if not genome_folder.is_dir():
242
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
243
+ output_folder.mkdir(parents=True, exist_ok=True)
244
+
245
+ cmd = ["lineage_wf", str(genome_folder), str(output_folder)]
246
+
247
+ if extension:
248
+ cmd.extend(["-x", extension])
249
+ if output_file:
250
+ output_file.parent.mkdir(parents=True, exist_ok=True)
251
+ cmd.extend(["-f", str(output_file)])
252
+ if reduced_tree:
253
+ cmd.append("-r")
254
+ if output_alignment_files:
255
+ cmd.append("--ali")
256
+ if output_nucleotide_sequences:
257
+ cmd.append("--nt")
258
+ if tab_output_file:
259
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
260
+ cmd.extend(["--tab_file", str(tab_output_file)])
261
+ if skip_gtdb_r207:
262
+ cmd.append("--skip_gtdb_r207")
263
+ if skip_gtdb_r207_cpr:
264
+ cmd.append("--skip_gtdb_r207_cpr")
265
+
266
+ return _run_checkm_command(
267
+ cmd,
268
+ output_dir=output_folder,
269
+ log_file=output_file,
270
+ quiet=quiet,
271
+ debug=debug,
272
+ force=force,
273
+ tmpdir=tmpdir,
274
+ threads=threads,
275
+ pplacer_path=pplacer_path,
276
+ hmmer_path=hmmer_path,
277
+ prodigal_path=prodigal_path,
278
+ )
279
+
280
+
281
+ @mcp.tool()
282
+ def taxonomy_wf(
283
+ taxonomy_level: str,
284
+ taxonomy_id: str,
285
+ genome_folder: Path,
286
+ output_folder: Path,
287
+ extension: str = "fna",
288
+ output_file: Optional[Path] = None,
289
+ output_alignment_files: bool = False,
290
+ output_nucleotide_sequences: bool = False,
291
+ tab_output_file: Optional[Path] = None,
292
+ skip_gtdb_r207: bool = False,
293
+ skip_gtdb_r207_cpr: bool = False,
294
+ quiet: bool = False,
295
+ debug: bool = False,
296
+ force: bool = False,
297
+ tmpdir: Optional[Path] = None,
298
+ threads: int = 1,
299
+ pplacer_path: Optional[Path] = None,
300
+ hmmer_path: Optional[Path] = None,
301
+ prodigal_path: Optional[Path] = None,
302
+ ) -> Dict[str, Any]:
303
+ """
304
+ Performs a taxonomy-specific workflow for assessing genome quality.
305
+
306
+ Args:
307
+ taxonomy_level: Taxonomy level (e.g., 'domain', 'phylum', 'class', 'order', 'family', 'genus', 'species').
308
+ taxonomy_id: Taxonomy identifier (e.g., 'Bacteria', 'Firmicutes', 'Bacilli').
309
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
310
+ output_folder: Folder to write output to.
311
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
312
+ output_file: File to write summary output to. If not specified, output goes to stdout.
313
+ output_alignment_files: Output alignment files.
314
+ output_nucleotide_sequences: Output nucleotide sequences.
315
+ tab_output_file: File to write tab-separated output to.
316
+ skip_gtdb_r207: Skip GTDB R207 marker set.
317
+ skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set.
318
+ quiet: Suppress stdout.
319
+ debug: Enable debug mode.
320
+ force: Force overwrite of existing files.
321
+ tmpdir: Temporary directory.
322
+ threads: Number of threads to use.
323
+ pplacer_path: Path to pplacer executable.
324
+ hmmer_path: Path to HMMER executables.
325
+ prodigal_path: Path to Prodigal executable.
326
+
327
+ Returns:
328
+ A dictionary containing the command executed, stdout, stderr, and any output files.
329
+ """
330
+ valid_levels = ['domain', 'phylum', 'class', 'order', 'family', 'genus', 'species']
331
+ if taxonomy_level not in valid_levels:
332
+ raise ValueError(f"Invalid taxonomy_level: '{taxonomy_level}'. Must be one of {valid_levels}.")
333
+ if not genome_folder.is_dir():
334
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
335
+ output_folder.mkdir(parents=True, exist_ok=True)
336
+
337
+ cmd = ["taxonomy_wf", taxonomy_level, taxonomy_id, str(genome_folder), str(output_folder)]
338
+
339
+ if extension:
340
+ cmd.extend(["-x", extension])
341
+ if output_file:
342
+ output_file.parent.mkdir(parents=True, exist_ok=True)
343
+ cmd.extend(["-f", str(output_file)])
344
+ if output_alignment_files:
345
+ cmd.append("--ali")
346
+ if output_nucleotide_sequences:
347
+ cmd.append("--nt")
348
+ if tab_output_file:
349
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
350
+ cmd.extend(["--tab_file", str(tab_output_file)])
351
+ if skip_gtdb_r207:
352
+ cmd.append("--skip_gtdb_r207")
353
+ if skip_gtdb_r207_cpr:
354
+ cmd.append("--skip_gtdb_r207_cpr")
355
+
356
+ return _run_checkm_command(
357
+ cmd,
358
+ output_dir=output_folder,
359
+ log_file=output_file,
360
+ quiet=quiet,
361
+ debug=debug,
362
+ force=force,
363
+ tmpdir=tmpdir,
364
+ threads=threads,
365
+ pplacer_path=pplacer_path,
366
+ hmmer_path=hmmer_path,
367
+ prodigal_path=prodigal_path,
368
+ )
369
+
370
+
371
+ @mcp.tool()
372
+ def tree_wf(
373
+ genome_folder: Path,
374
+ output_folder: Path,
375
+ extension: str = "fna",
376
+ output_file: Optional[Path] = None,
377
+ full_tree: bool = False,
378
+ output_alignment_files: bool = False,
379
+ output_nucleotide_sequences: bool = False,
380
+ tab_output_file: Optional[Path] = None,
381
+ skip_gtdb_r207: bool = False,
382
+ skip_gtdb_r207_cpr: bool = False,
383
+ quiet: bool = False,
384
+ debug: bool = False,
385
+ force: bool = False,
386
+ tmpdir: Optional[Path] = None,
387
+ threads: int = 1,
388
+ pplacer_path: Optional[Path] = None,
389
+ hmmer_path: Optional[Path] = None,
390
+ prodigal_path: Optional[Path] = None,
391
+ ) -> Dict[str, Any]:
392
+ """
393
+ Performs a tree-specific workflow for assessing genome quality.
394
+
395
+ Args:
396
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
397
+ output_folder: Folder to write output to.
398
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
399
+ output_file: File to write summary output to. If not specified, output goes to stdout.
400
+ full_tree: Use a full tree for phylogenetic placement instead of the default reduced tree.
401
+ output_alignment_files: Output alignment files.
402
+ output_nucleotide_sequences: Output nucleotide sequences.
403
+ tab_output_file: File to write tab-separated output to.
404
+ skip_gtdb_r207: Skip GTDB R207 marker set.
405
+ skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set.
406
+ quiet: Suppress stdout.
407
+ debug: Enable debug mode.
408
+ force: Force overwrite of existing files.
409
+ tmpdir: Temporary directory.
410
+ threads: Number of threads to use.
411
+ pplacer_path: Path to pplacer executable.
412
+ hmmer_path: Path to HMMER executables.
413
+ prodigal_path: Path to Prodigal executable.
414
+
415
+ Returns:
416
+ A dictionary containing the command executed, stdout, stderr, and any output files.
417
+ """
418
+ if not genome_folder.is_dir():
419
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
420
+ output_folder.mkdir(parents=True, exist_ok=True)
421
+
422
+ cmd = ["tree_wf", str(genome_folder), str(output_folder)]
423
+
424
+ if extension:
425
+ cmd.extend(["-x", extension])
426
+ if output_file:
427
+ output_file.parent.mkdir(parents=True, exist_ok=True)
428
+ cmd.extend(["-f", str(output_file)])
429
+ if full_tree:
430
+ cmd.append("--full_tree")
431
+ if output_alignment_files:
432
+ cmd.append("--ali")
433
+ if output_nucleotide_sequences:
434
+ cmd.append("--nt")
435
+ if tab_output_file:
436
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
437
+ cmd.extend(["--tab_file", str(tab_output_file)])
438
+ if skip_gtdb_r207:
439
+ cmd.append("--skip_gtdb_r207")
440
+ if skip_gtdb_r207_cpr:
441
+ cmd.append("--skip_gtdb_r207_cpr")
442
+
443
+ return _run_checkm_command(
444
+ cmd,
445
+ output_dir=output_folder,
446
+ log_file=output_file,
447
+ quiet=quiet,
448
+ debug=debug,
449
+ force=force,
450
+ tmpdir=tmpdir,
451
+ threads=threads,
452
+ pplacer_path=pplacer_path,
453
+ hmmer_path=hmmer_path,
454
+ prodigal_path=prodigal_path,
455
+ )
456
+
457
+
458
+ @mcp.tool()
459
+ def tree(
460
+ genome_folder: Path,
461
+ output_folder: Path,
462
+ extension: str = "fna",
463
+ output_file: Optional[Path] = None,
464
+ reduced_tree: bool = False,
465
+ output_alignment_files: bool = False,
466
+ output_nucleotide_sequences: bool = False,
467
+ tab_output_file: Optional[Path] = None,
468
+ quiet: bool = False,
469
+ debug: bool = False,
470
+ force: bool = False,
471
+ tmpdir: Optional[Path] = None,
472
+ threads: int = 1,
473
+ pplacer_path: Optional[Path] = None,
474
+ hmmer_path: Optional[Path] = None,
475
+ prodigal_path: Optional[Path] = None,
476
+ ) -> Dict[str, Any]:
477
+ """
478
+ Constructs a phylogenetic tree from a set of genomes.
479
+
480
+ Args:
481
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
482
+ output_folder: Folder to write output to.
483
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
484
+ output_file: File to write summary output to. If not specified, output goes to stdout.
485
+ reduced_tree: Use a reduced tree for phylogenetic placement.
486
+ output_alignment_files: Output alignment files.
487
+ output_nucleotide_sequences: Output nucleotide sequences.
488
+ tab_output_file: File to write tab-separated output to.
489
+ quiet: Suppress stdout.
490
+ debug: Enable debug mode.
491
+ force: Force overwrite of existing files.
492
+ tmpdir: Temporary directory.
493
+ threads: Number of threads to use.
494
+ pplacer_path: Path to pplacer executable.
495
+ hmmer_path: Path to HMMER executables.
496
+ prodigal_path: Path to Prodigal executable.
497
+
498
+ Returns:
499
+ A dictionary containing the command executed, stdout, stderr, and any output files.
500
+ """
501
+ if not genome_folder.is_dir():
502
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
503
+ output_folder.mkdir(parents=True, exist_ok=True)
504
+
505
+ cmd = ["tree", str(genome_folder), str(output_folder)]
506
+
507
+ if extension:
508
+ cmd.extend(["-x", extension])
509
+ if output_file:
510
+ output_file.parent.mkdir(parents=True, exist_ok=True)
511
+ cmd.extend(["-f", str(output_file)])
512
+ if reduced_tree:
513
+ cmd.append("-r")
514
+ if output_alignment_files:
515
+ cmd.append("--ali")
516
+ if output_nucleotide_sequences:
517
+ cmd.append("--nt")
518
+ if tab_output_file:
519
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
520
+ cmd.extend(["--tab_file", str(tab_output_file)])
521
+
522
+ return _run_checkm_command(
523
+ cmd,
524
+ output_dir=output_folder,
525
+ log_file=output_file,
526
+ quiet=quiet,
527
+ debug=debug,
528
+ force=force,
529
+ tmpdir=tmpdir,
530
+ threads=threads,
531
+ pplacer_path=pplacer_path,
532
+ hmmer_path=hmmer_path,
533
+ prodigal_path=prodigal_path,
534
+ )
535
+
536
+
537
+ @mcp.tool()
538
+ def qa(
539
+ marker_gene_folder: Path,
540
+ genome_folder: Path,
541
+ output_folder: Path,
542
+ extension: str = "fna",
543
+ output_file: Optional[Path] = None,
544
+ tab_output_file: Optional[Path] = None,
545
+ skip_gtdb_r207: bool = False,
546
+ skip_gtdb_r207_cpr: bool = False,
547
+ quiet: bool = False,
548
+ debug: bool = False,
549
+ force: bool = False,
550
+ tmpdir: Optional[Path] = None,
551
+ threads: int = 1,
552
+ ) -> Dict[str, Any]:
553
+ """
554
+ Performs quality assessment on a set of genomes using marker genes.
555
+
556
+ Args:
557
+ marker_gene_folder: Folder containing marker gene files (output from `checkm tree` or `checkm lineage_wf`).
558
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
559
+ output_folder: Folder to write output to.
560
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
561
+ output_file: File to write summary output to. If not specified, output goes to stdout.
562
+ tab_output_file: File to write tab-separated output to.
563
+ skip_gtdb_r207: Skip GTDB R207 marker set.
564
+ skip_gtdb_r207_cpr: Skip GTDB R207 CPR marker set.
565
+ quiet: Suppress stdout.
566
+ debug: Enable debug mode.
567
+ force: Force overwrite of existing files.
568
+ tmpdir: Temporary directory.
569
+ threads: Number of threads to use.
570
+
571
+ Returns:
572
+ A dictionary containing the command executed, stdout, stderr, and any output files.
573
+ """
574
+ if not marker_gene_folder.is_dir():
575
+ raise ValueError(f"Marker gene folder '{marker_gene_folder}' does not exist or is not a directory.")
576
+ if not genome_folder.is_dir():
577
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
578
+ output_folder.mkdir(parents=True, exist_ok=True)
579
+
580
+ cmd = ["qa", str(marker_gene_folder), str(genome_folder), str(output_folder)]
581
+
582
+ if extension:
583
+ cmd.extend(["-x", extension])
584
+ if output_file:
585
+ output_file.parent.mkdir(parents=True, exist_ok=True)
586
+ cmd.extend(["-f", str(output_file)])
587
+ if tab_output_file:
588
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
589
+ cmd.extend(["--tab_file", str(tab_output_file)])
590
+ if skip_gtdb_r207:
591
+ cmd.append("--skip_gtdb_r207")
592
+ if skip_gtdb_r207_cpr:
593
+ cmd.append("--skip_gtdb_r207_cpr")
594
+
595
+ return _run_checkm_command(
596
+ cmd,
597
+ output_dir=output_folder,
598
+ log_file=output_file,
599
+ quiet=quiet,
600
+ debug=debug,
601
+ force=force,
602
+ tmpdir=tmpdir,
603
+ threads=threads,
604
+ )
605
+
606
+
607
+ @mcp.tool()
608
+ def plot(
609
+ checkm_output_folder: Path,
610
+ image_folder: Path,
611
+ output_file: Optional[Path] = None,
612
+ image_file: Optional[Path] = None,
613
+ image_type: str = 'png',
614
+ font_size: int = 12,
615
+ dpi: int = 300,
616
+ width: float = 10.0,
617
+ height: float = 8.0,
618
+ title: Optional[str] = None,
619
+ label: Optional[str] = None,
620
+ color: Optional[str] = None,
621
+ marker_size: int = 10,
622
+ line_width: int = 1,
623
+ force: bool = False,
624
+ quiet: bool = False,
625
+ debug: bool = False,
626
+ ) -> Dict[str, Any]:
627
+ """
628
+ Generates various plots for visualizing genome quality.
629
+
630
+ Args:
631
+ checkm_output_folder: Folder containing CheckM output files (e.g., from `lineage_wf`).
632
+ image_folder: Folder to write image files to.
633
+ output_file: File to write summary output to. If not specified, output goes to stdout.
634
+ image_file: File to write the main image to. If not specified, 'checkm_plot.png' in image_folder.
635
+ image_type: Image type (e.g., 'png', 'pdf', 'svg').
636
+ font_size: Font size for plots.
637
+ dpi: DPI for plots.
638
+ width: Width of plots in inches.
639
+ height: Height of plots in inches.
640
+ title: Title for plots.
641
+ label: Label for plots.
642
+ color: Color for plots (e.g., 'red', '#FF0000').
643
+ marker_size: Marker size for plots.
644
+ line_width: Line width for plots.
645
+ force: Force overwrite of existing files.
646
+ quiet: Suppress stdout.
647
+ debug: Enable debug mode.
648
+
649
+ Returns:
650
+ A dictionary containing the command executed, stdout, stderr, and any output files.
651
+ """
652
+ if not checkm_output_folder.is_dir():
653
+ raise ValueError(f"CheckM output folder '{checkm_output_folder}' does not exist or is not a directory.")
654
+ image_folder.mkdir(parents=True, exist_ok=True)
655
+
656
+ cmd = ["plot", str(checkm_output_folder), str(image_folder)]
657
+
658
+ if output_file:
659
+ output_file.parent.mkdir(parents=True, exist_ok=True)
660
+ cmd.extend(["-f", str(output_file)])
661
+
662
+ _add_plotting_args(
663
+ cmd,
664
+ image_file=image_file,
665
+ image_type=image_type,
666
+ font_size=font_size,
667
+ dpi=dpi,
668
+ width=width,
669
+ height=height,
670
+ title=title,
671
+ label=label,
672
+ color=color,
673
+ marker_size=marker_size,
674
+ line_width=line_width,
675
+ )
676
+
677
+ return _run_checkm_command(
678
+ cmd,
679
+ output_dir=image_folder,
680
+ log_file=output_file,
681
+ force=force,
682
+ quiet=quiet,
683
+ debug=debug,
684
+ )
685
+
686
+
687
+ @mcp.tool()
688
+ def ssu_finder(
689
+ genome_folder: Path,
690
+ output_folder: Path,
691
+ extension: str = "fna",
692
+ output_file: Optional[Path] = None,
693
+ tab_output_file: Optional[Path] = None,
694
+ quiet: bool = False,
695
+ debug: bool = False,
696
+ force: bool = False,
697
+ tmpdir: Optional[Path] = None,
698
+ threads: int = 1,
699
+ ) -> Dict[str, Any]:
700
+ """
701
+ Identifies SSU rRNA genes in genome files.
702
+
703
+ Args:
704
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
705
+ output_folder: Folder to write output to.
706
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
707
+ output_file: File to write summary output to. If not specified, output goes to stdout.
708
+ tab_output_file: File to write tab-separated output to.
709
+ quiet: Suppress stdout.
710
+ debug: Enable debug mode.
711
+ force: Force overwrite of existing files.
712
+ tmpdir: Temporary directory.
713
+ threads: Number of threads to use.
714
+
715
+ Returns:
716
+ A dictionary containing the command executed, stdout, stderr, and any output files.
717
+ """
718
+ if not genome_folder.is_dir():
719
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
720
+ output_folder.mkdir(parents=True, exist_ok=True)
721
+
722
+ cmd = ["ssu_finder", str(genome_folder), str(output_folder)]
723
+
724
+ if extension:
725
+ cmd.extend(["-x", extension])
726
+ if output_file:
727
+ output_file.parent.mkdir(parents=True, exist_ok=True)
728
+ cmd.extend(["-f", str(output_file)])
729
+ if tab_output_file:
730
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
731
+ cmd.extend(["--tab_file", str(tab_output_file)])
732
+
733
+ return _run_checkm_command(
734
+ cmd,
735
+ output_dir=output_folder,
736
+ log_file=output_file,
737
+ quiet=quiet,
738
+ debug=debug,
739
+ force=force,
740
+ tmpdir=tmpdir,
741
+ threads=threads,
742
+ )
743
+
744
+
745
+ @mcp.tool()
746
+ def unique_markers(
747
+ genome_folder: Path,
748
+ output_folder: Path,
749
+ extension: str = "fna",
750
+ output_file: Optional[Path] = None,
751
+ tab_output_file: Optional[Path] = None,
752
+ quiet: bool = False,
753
+ debug: bool = False,
754
+ force: bool = False,
755
+ tmpdir: Optional[Path] = None,
756
+ threads: int = 1,
757
+ ) -> Dict[str, Any]:
758
+ """
759
+ Identifies unique marker genes for a set of genomes.
760
+
761
+ Args:
762
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
763
+ output_folder: Folder to write output to.
764
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
765
+ output_file: File to write summary output to. If not specified, output goes to stdout.
766
+ tab_output_file: File to write tab-separated output to.
767
+ quiet: Suppress stdout.
768
+ debug: Enable debug mode.
769
+ force: Force overwrite of existing files.
770
+ tmpdir: Temporary directory.
771
+ threads: Number of threads to use.
772
+
773
+ Returns:
774
+ A dictionary containing the command executed, stdout, stderr, and any output files.
775
+ """
776
+ if not genome_folder.is_dir():
777
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
778
+ output_folder.mkdir(parents=True, exist_ok=True)
779
+
780
+ cmd = ["unique_markers", str(genome_folder), str(output_folder)]
781
+
782
+ if extension:
783
+ cmd.extend(["-x", extension])
784
+ if output_file:
785
+ output_file.parent.mkdir(parents=True, exist_ok=True)
786
+ cmd.extend(["-f", str(output_file)])
787
+ if tab_output_file:
788
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
789
+ cmd.extend(["--tab_file", str(tab_output_file)])
790
+
791
+ return _run_checkm_command(
792
+ cmd,
793
+ output_dir=output_folder,
794
+ log_file=output_file,
795
+ quiet=quiet,
796
+ debug=debug,
797
+ force=force,
798
+ tmpdir=tmpdir,
799
+ threads=threads,
800
+ )
801
+
802
+
803
+ @mcp.tool()
804
+ def profile(
805
+ genome_folder: Path,
806
+ output_folder: Path,
807
+ extension: str = "fna",
808
+ output_file: Optional[Path] = None,
809
+ tab_output_file: Optional[Path] = None,
810
+ quiet: bool = False,
811
+ debug: bool = False,
812
+ force: bool = False,
813
+ tmpdir: Optional[Path] = None,
814
+ threads: int = 1,
815
+ ) -> Dict[str, Any]:
816
+ """
817
+ Profiles the distribution of marker genes across a set of genomes.
818
+
819
+ Args:
820
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
821
+ output_folder: Folder to write output to.
822
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
823
+ output_file: File to write summary output to. If not specified, output goes to stdout.
824
+ tab_output_file: File to write tab-separated output to.
825
+ quiet: Suppress stdout.
826
+ debug: Enable debug mode.
827
+ force: Force overwrite of existing files.
828
+ tmpdir: Temporary directory.
829
+ threads: Number of threads to use.
830
+
831
+ Returns:
832
+ A dictionary containing the command executed, stdout, stderr, and any output files.
833
+ """
834
+ if not genome_folder.is_dir():
835
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
836
+ output_folder.mkdir(parents=True, exist_ok=True)
837
+
838
+ cmd = ["profile", str(genome_folder), str(output_folder)]
839
+
840
+ if extension:
841
+ cmd.extend(["-x", extension])
842
+ if output_file:
843
+ output_file.parent.mkdir(parents=True, exist_ok=True)
844
+ cmd.extend(["-f", str(output_file)])
845
+ if tab_output_file:
846
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
847
+ cmd.extend(["--tab_file", str(tab_output_file)])
848
+
849
+ return _run_checkm_command(
850
+ cmd,
851
+ output_dir=output_folder,
852
+ log_file=output_file,
853
+ quiet=quiet,
854
+ debug=debug,
855
+ force=force,
856
+ tmpdir=tmpdir,
857
+ threads=threads,
858
+ )
859
+
860
+
861
+ @mcp.tool()
862
+ def tetra(
863
+ genome_folder: Path,
864
+ output_folder: Path,
865
+ extension: str = "fna",
866
+ output_file: Optional[Path] = None,
867
+ tab_output_file: Optional[Path] = None,
868
+ quiet: bool = False,
869
+ debug: bool = False,
870
+ force: bool = False,
871
+ tmpdir: Optional[Path] = None,
872
+ threads: int = 1,
873
+ ) -> Dict[str, Any]:
874
+ """
875
+ Calculates tetranucleotide frequencies for a set of genomes.
876
+
877
+ Args:
878
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
879
+ output_folder: Folder to write output to.
880
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
881
+ output_file: File to write summary output to. If not specified, output goes to stdout.
882
+ tab_output_file: File to write tab-separated output to.
883
+ quiet: Suppress stdout.
884
+ debug: Enable debug mode.
885
+ force: Force overwrite of existing files.
886
+ tmpdir: Temporary directory.
887
+ threads: Number of threads to use.
888
+
889
+ Returns:
890
+ A dictionary containing the command executed, stdout, stderr, and any output files.
891
+ """
892
+ if not genome_folder.is_dir():
893
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
894
+ output_folder.mkdir(parents=True, exist_ok=True)
895
+
896
+ cmd = ["tetra", str(genome_folder), str(output_folder)]
897
+
898
+ if extension:
899
+ cmd.extend(["-x", extension])
900
+ if output_file:
901
+ output_file.parent.mkdir(parents=True, exist_ok=True)
902
+ cmd.extend(["-f", str(output_file)])
903
+ if tab_output_file:
904
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
905
+ cmd.extend(["--tab_file", str(tab_output_file)])
906
+
907
+ return _run_checkm_command(
908
+ cmd,
909
+ output_dir=output_folder,
910
+ log_file=output_file,
911
+ quiet=quiet,
912
+ debug=debug,
913
+ force=force,
914
+ tmpdir=tmpdir,
915
+ threads=threads,
916
+ )
917
+
918
+
919
+ @mcp.tool()
920
+ def dist(
921
+ genome_folder: Path,
922
+ output_folder: Path,
923
+ extension: str = "fna",
924
+ output_file: Optional[Path] = None,
925
+ tab_output_file: Optional[Path] = None,
926
+ quiet: bool = False,
927
+ debug: bool = False,
928
+ force: bool = False,
929
+ tmpdir: Optional[Path] = None,
930
+ threads: int = 1,
931
+ ) -> Dict[str, Any]:
932
+ """
933
+ Calculates genomic characteristics distributions for a set of genomes.
934
+
935
+ Args:
936
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
937
+ output_folder: Folder to write output to.
938
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
939
+ output_file: File to write summary output to. If not specified, output goes to stdout.
940
+ tab_output_file: File to write tab-separated output to.
941
+ quiet: Suppress stdout.
942
+ debug: Enable debug mode.
943
+ force: Force overwrite of existing files.
944
+ tmpdir: Temporary directory.
945
+ threads: Number of threads to use.
946
+
947
+ Returns:
948
+ A dictionary containing the command executed, stdout, stderr, and any output files.
949
+ """
950
+ if not genome_folder.is_dir():
951
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
952
+ output_folder.mkdir(parents=True, exist_ok=True)
953
+
954
+ cmd = ["dist", str(genome_folder), str(output_folder)]
955
+
956
+ if extension:
957
+ cmd.extend(["-x", extension])
958
+ if output_file:
959
+ output_file.parent.mkdir(parents=True, exist_ok=True)
960
+ cmd.extend(["-f", str(output_file)])
961
+ if tab_output_file:
962
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
963
+ cmd.extend(["--tab_file", str(tab_output_file)])
964
+
965
+ return _run_checkm_command(
966
+ cmd,
967
+ output_dir=output_folder,
968
+ log_file=output_file,
969
+ quiet=quiet,
970
+ debug=debug,
971
+ force=force,
972
+ tmpdir=tmpdir,
973
+ threads=threads,
974
+ )
975
+
976
+
977
+ @mcp.tool()
978
+ def coverage(
979
+ genome_folder: Path,
980
+ output_folder: Path,
981
+ extension: str = "fna",
982
+ output_file: Optional[Path] = None,
983
+ tab_output_file: Optional[Path] = None,
984
+ quiet: bool = False,
985
+ debug: bool = False,
986
+ force: bool = False,
987
+ tmpdir: Optional[Path] = None,
988
+ threads: int = 1,
989
+ ) -> Dict[str, Any]:
990
+ """
991
+ Calculates coverage statistics for a set of genomes.
992
+
993
+ Args:
994
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
995
+ output_folder: Folder to write output to.
996
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
997
+ output_file: File to write summary output to. If not specified, output goes to stdout.
998
+ tab_output_file: File to write tab-separated output to.
999
+ quiet: Suppress stdout.
1000
+ debug: Enable debug mode.
1001
+ force: Force overwrite of existing files.
1002
+ tmpdir: Temporary directory.
1003
+ threads: Number of threads to use.
1004
+
1005
+ Returns:
1006
+ A dictionary containing the command executed, stdout, stderr, and any output files.
1007
+ """
1008
+ if not genome_folder.is_dir():
1009
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
1010
+ output_folder.mkdir(parents=True, exist_ok=True)
1011
+
1012
+ cmd = ["coverage", str(genome_folder), str(output_folder)]
1013
+
1014
+ if extension:
1015
+ cmd.extend(["-x", extension])
1016
+ if output_file:
1017
+ output_file.parent.mkdir(parents=True, exist_ok=True)
1018
+ cmd.extend(["-f", str(output_file)])
1019
+ if tab_output_file:
1020
+ tab_output_file.parent.mkdir(parents=True, exist_ok=True)
1021
+ cmd.extend(["--tab_file", str(tab_output_file)])
1022
+
1023
+ return _run_checkm_command(
1024
+ cmd,
1025
+ output_dir=output_folder,
1026
+ log_file=output_file,
1027
+ quiet=quiet,
1028
+ debug=debug,
1029
+ force=force,
1030
+ tmpdir=tmpdir,
1031
+ threads=threads,
1032
+ )
1033
+
1034
+
1035
+ @mcp.tool()
1036
+ def len_hist(
1037
+ genome_folder: Path,
1038
+ output_folder: Path,
1039
+ extension: str = "fna",
1040
+ output_file: Optional[Path] = None,
1041
+ image_file: Optional[Path] = None,
1042
+ image_type: str = 'png',
1043
+ font_size: int = 12,
1044
+ dpi: int = 300,
1045
+ width: float = 10.0,
1046
+ height: float = 8.0,
1047
+ title: Optional[str] = None,
1048
+ label: Optional[str] = None,
1049
+ color: Optional[str] = None,
1050
+ force: bool = False,
1051
+ quiet: bool = False,
1052
+ debug: bool = False,
1053
+ ) -> Dict[str, Any]:
1054
+ """
1055
+ Generates a length histogram for a set of genomes.
1056
+
1057
+ Args:
1058
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
1059
+ output_folder: Folder to write output to.
1060
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
1061
+ output_file: File to write summary output to. If not specified, output goes to stdout.
1062
+ image_file: File to write the image to. If not specified, 'len_hist.png' in output_folder.
1063
+ image_type: Image type (e.g., 'png', 'pdf', 'svg').
1064
+ font_size: Font size for plots.
1065
+ dpi: DPI for plots.
1066
+ width: Width of plots in inches.
1067
+ height: Height of plots in inches.
1068
+ title: Title for plots.
1069
+ label: Label for plots.
1070
+ color: Color for plots (e.g., 'red', '#FF0000').
1071
+ force: Force overwrite of existing files.
1072
+ quiet: Suppress stdout.
1073
+ debug: Enable debug mode.
1074
+
1075
+ Returns:
1076
+ A dictionary containing the command executed, stdout, stderr, and any output files.
1077
+ """
1078
+ if not genome_folder.is_dir():
1079
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
1080
+ output_folder.mkdir(parents=True, exist_ok=True)
1081
+
1082
+ cmd = ["len_hist", str(genome_folder), str(output_folder)]
1083
+
1084
+ if extension:
1085
+ cmd.extend(["-x", extension])
1086
+ if output_file:
1087
+ output_file.parent.mkdir(parents=True, exist_ok=True)
1088
+ cmd.extend(["-f", str(output_file)])
1089
+
1090
+ _add_plotting_args(
1091
+ cmd,
1092
+ image_file=image_file,
1093
+ image_type=image_type,
1094
+ font_size=font_size,
1095
+ dpi=dpi,
1096
+ width=width,
1097
+ height=height,
1098
+ title=title,
1099
+ label=label,
1100
+ color=color,
1101
+ )
1102
+
1103
+ return _run_checkm_command(
1104
+ cmd,
1105
+ output_dir=output_folder,
1106
+ log_file=output_file,
1107
+ force=force,
1108
+ quiet=quiet,
1109
+ debug=debug,
1110
+ )
1111
+
1112
+
1113
+ @mcp.tool()
1114
+ def nx_plot(
1115
+ genome_folder: Path,
1116
+ output_folder: Path,
1117
+ extension: str = "fna",
1118
+ output_file: Optional[Path] = None,
1119
+ image_file: Optional[Path] = None,
1120
+ image_type: str = 'png',
1121
+ font_size: int = 12,
1122
+ dpi: int = 300,
1123
+ width: float = 10.0,
1124
+ height: float = 8.0,
1125
+ title: Optional[str] = None,
1126
+ label: Optional[str] = None,
1127
+ color: Optional[str] = None,
1128
+ force: bool = False,
1129
+ quiet: bool = False,
1130
+ debug: bool = False,
1131
+ ) -> Dict[str, Any]:
1132
+ """
1133
+ Generates an N50/L50 plot for a set of genomes.
1134
+
1135
+ Args:
1136
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
1137
+ output_folder: Folder to write output to.
1138
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
1139
+ output_file: File to write summary output to. If not specified, output goes to stdout.
1140
+ image_file: File to write the image to. If not specified, 'nx_plot.png' in output_folder.
1141
+ image_type: Image type (e.g., 'png', 'pdf', 'svg').
1142
+ font_size: Font size for plots.
1143
+ dpi: DPI for plots.
1144
+ width: Width of plots in inches.
1145
+ height: Height of plots in inches.
1146
+ title: Title for plots.
1147
+ label: Label for plots.
1148
+ color: Color for plots (e.g., 'red', '#FF0000').
1149
+ force: Force overwrite of existing files.
1150
+ quiet: Suppress stdout.
1151
+ debug: Enable debug mode.
1152
+
1153
+ Returns:
1154
+ A dictionary containing the command executed, stdout, stderr, and any output files.
1155
+ """
1156
+ if not genome_folder.is_dir():
1157
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
1158
+ output_folder.mkdir(parents=True, exist_ok=True)
1159
+
1160
+ cmd = ["nx_plot", str(genome_folder), str(output_folder)]
1161
+
1162
+ if extension:
1163
+ cmd.extend(["-x", extension])
1164
+ if output_file:
1165
+ output_file.parent.mkdir(parents=True, exist_ok=True)
1166
+ cmd.extend(["-f", str(output_file)])
1167
+
1168
+ _add_plotting_args(
1169
+ cmd,
1170
+ image_file=image_file,
1171
+ image_type=image_type,
1172
+ font_size=font_size,
1173
+ dpi=dpi,
1174
+ width=width,
1175
+ height=height,
1176
+ title=title,
1177
+ label=label,
1178
+ color=color,
1179
+ )
1180
+
1181
+ return _run_checkm_command(
1182
+ cmd,
1183
+ output_dir=output_folder,
1184
+ log_file=output_file,
1185
+ force=force,
1186
+ quiet=quiet,
1187
+ debug=debug,
1188
+ )
1189
+
1190
+
1191
+ @mcp.tool()
1192
+ def bin_set(
1193
+ genome_folder: Path,
1194
+ output_bin_set_file: Path,
1195
+ extension: str = "fna",
1196
+ output_file: Optional[Path] = None,
1197
+ force: bool = False,
1198
+ quiet: bool = False,
1199
+ debug: bool = False,
1200
+ ) -> Dict[str, Any]:
1201
+ """
1202
+ Creates a set of bins from a folder of genome files.
1203
+
1204
+ Args:
1205
+ genome_folder: Folder containing genome files (e.g., .fna, .fasta).
1206
+ output_bin_set_file: File to write the bin set to.
1207
+ extension: Extension for genome files (e.g., 'fna', 'fasta').
1208
+ output_file: File to write summary output to. If not specified, output goes to stdout.
1209
+ force: Force overwrite of existing files.
1210
+ quiet: Suppress stdout.
1211
+ debug: Enable debug mode.
1212
+
1213
+ Returns:
1214
+ A dictionary containing the command executed, stdout, stderr, and any output files.
1215
+ """
1216
+ if not genome_folder.is_dir():
1217
+ raise ValueError(f"Genome folder '{genome_folder}' does not exist or is not a directory.")
1218
+ output_bin_set_file.parent.mkdir(parents=True, exist_ok=True)
1219
+
1220
+ cmd = ["bin_set", str(genome_folder), str(output_bin_set_file)]
1221
+
1222
+ if extension:
1223
+ cmd.extend(["-x", extension])
1224
+ if output_file:
1225
+ output_file.parent.mkdir(parents=True, exist_ok=True)
1226
+ cmd.extend(["-f", str(output_file)])
1227
+
1228
+ return _run_checkm_command(
1229
+ cmd,
1230
+ output_dir=output_bin_set_file.parent,
1231
+ log_file=output_file,
1232
+ force=force,
1233
+ quiet=quiet,
1234
+ debug=debug,
1235
+ )
1236
+
1237
+ if __name__ == "__main__":
1238
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_checkm-genome/app/checkm-genome_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_checkm-genome/app/checkm-genome_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_checkm_genome'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_checkm-genome/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_checkm-genome/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-checkm-genome:
5
+ build: .
6
+ image: mcp-checkm-genome:latest
7
+ container_name: mcp-checkm-genome
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=checkm-genome
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_checkm-genome/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - checkm-genome
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_checkm-genome/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_dca/Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.10-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6
+
7
+ # Install Miniconda
8
+ 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
9
+
10
+ # Add conda to PATH
11
+ ENV PATH="/opt/conda/bin:$PATH"
12
+
13
+ # Install dca via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda dca -y && conda clean -a
15
+
16
+ # Install Python dependencies
17
+ RUN pip install uv
18
+ RUN uv pip install --system fastmcp
19
+
20
+ # Create app directory
21
+ WORKDIR /app
22
+
23
+ # Copy your MCP server
24
+ COPY app/dca_server.py /app/
25
+
26
+ # Create workspace and output directories
27
+ RUN mkdir -p /app/workspace /app/output
28
+
29
+ # Make sure the server script is executable
30
+ RUN chmod +x /app/dca_server.py
31
+
32
+ # Expose port for MCP over HTTP (optional)
33
+ EXPOSE 8000
34
+
35
+ # Health check
36
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
37
+
38
+ # Default command runs the MCP server via stdio
39
+ CMD ["python", "/app/dca_server.py"]
40
+
Biomni/mcp_generated/mcp_dca/app/dca_shim_server.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ from pathlib import Path
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+
10
+ SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dca/app/dca_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_dca'
13
+
14
+
15
+ class _ShimMCP:
16
+ @staticmethod
17
+ def tool(*args, **kwargs):
18
+ if args and callable(args[0]) and len(args) == 1 and not kwargs:
19
+ return args[0]
20
+ def _decorator(fn):
21
+ return fn
22
+ return _decorator
23
+
24
+
25
+ def _resolve_source_server():
26
+ if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
27
+ return LOCAL_SERVER
28
+ return SOURCE_SERVER
29
+
30
+
31
+ def _load_functions():
32
+ source_server = _resolve_source_server()
33
+ code = source_server.read_text(encoding="utf-8")
34
+ tree = ast.parse(code, filename=str(source_server))
35
+ function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
36
+ namespace = {
37
+ "__name__": "__mcp_source__",
38
+ "mcp": _ShimMCP(),
39
+ }
40
+ exec(compile(code, str(source_server), "exec"), namespace, namespace)
41
+ loaded = []
42
+ for name in function_names:
43
+ fn = namespace.get(name)
44
+ if callable(fn):
45
+ loaded.append(fn)
46
+ return loaded
47
+
48
+
49
+ mcp = FastMCP(SERVER_NAME)
50
+ for _fn in _load_functions():
51
+ mcp.tool()(_fn)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_dca/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_dca/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-dca:
5
+ build: .
6
+ image: mcp-dca:latest
7
+ container_name: mcp-dca
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=dca
12
+ volumes:
13
+ - ./workspace:/app/workspace
14
+ - ./output:/app/output
15
+ restart: unless-stopped
16
+ healthcheck:
17
+ test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 5s
22
+
Biomni/mcp_generated/mcp_dca/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - dca
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_dca/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_echidna/app/echidna_server.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import Optional, List
5
+
6
+ # This is a placeholder for the MCP decorator.
7
+ # In a real MCP environment, this would be provided by the MCP framework.
8
+ def tool(*args, **kwargs):
9
+ def decorator(func):
10
+ return func
11
+ return decorator
12
+
13
+ mcp = type("mcp", (), {"tool": tool})
14
+
15
+
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ SERVER_NAME = 'local_echidna'
19
+ mcp = FastMCP(SERVER_NAME)
20
+
21
+ @mcp.tool()
22
+ def run_echidna(
23
+ input_adata: Path,
24
+ output_prefix: str,
25
+ timepoint_label: str = "timepoint",
26
+ counts_layer: str = "counts",
27
+ clusters: str = "leiden",
28
+ seed: int = 42,
29
+ n_steps: int = 10000,
30
+ learning_rate: float = 0.1,
31
+ val_split: float = 0.1,
32
+ patience: int = 30,
33
+ device: str = "cpu",
34
+ verbose: bool = True,
35
+ inverse_gamma: bool = False,
36
+ eta_mean_init: float = 2.0,
37
+ lkj_concentration: float = 1.0,
38
+ q_corr_init: float = 0.01,
39
+ q_shape_rate_scaler: float = 10.0,
40
+ ):
41
+ """
42
+ Runs the Echidna model for joint probabilistic modeling of single-cell gene
43
+ expression and chromosomal copy number variation.
44
+
45
+ This tool serves as a command-line wrapper for the `echidna` Python library,
46
+ which integrates scRNA-seq and bulk WGS data to quantify gene dosage effects.
47
+ """
48
+ # --- Input Validation ---
49
+ if not input_adata.is_file():
50
+ raise FileNotFoundError(f"Input AnnData file not found: {input_adata}")
51
+
52
+ if n_steps <= 0:
53
+ raise ValueError("n_steps must be a positive integer.")
54
+ if learning_rate <= 0:
55
+ raise ValueError("learning_rate must be positive.")
56
+ if not (0.0 < val_split < 1.0):
57
+ raise ValueError("val_split must be between 0.0 and 1.0.")
58
+ if patience < 0:
59
+ raise ValueError("patience must be a non-negative integer.")
60
+ if device not in ["cpu", "cuda"]:
61
+ raise ValueError("device must be either 'cpu' or 'cuda'.")
62
+ if lkj_concentration <= 0:
63
+ raise ValueError("lkj_concentration must be positive.")
64
+
65
+ # --- Command-line construction ---
66
+ # Echidna is a library, so we are assuming a hypothetical wrapper script
67
+ # `run_echidna_script.py` that exposes its functionality via CLI.
68
+ # This is a common pattern for converting libraries to MCP tools.
69
+ cmd = [
70
+ "echidna_runner", # Hypothetical executable script
71
+ "--input-adata", str(input_adata),
72
+ "--output-prefix", output_prefix,
73
+ "--timepoint-label", timepoint_label,
74
+ "--counts-layer", counts_layer,
75
+ "--clusters", clusters,
76
+ "--seed", str(seed),
77
+ "--n-steps", str(n_steps),
78
+ "--learning-rate", str(learning_rate),
79
+ "--val-split", str(val_split),
80
+ "--patience", str(patience),
81
+ "--device", device,
82
+ "--eta-mean-init", str(eta_mean_init),
83
+ "--lkj-concentration", str(lkj_concentration),
84
+ "--q-corr-init", str(q_corr_init),
85
+ "--q-shape-rate-scaler", str(q_shape_rate_scaler),
86
+ ]
87
+
88
+ if verbose:
89
+ cmd.append("--verbose")
90
+ if inverse_gamma:
91
+ cmd.append("--inverse-gamma")
92
+
93
+ # --- Subprocess Execution ---
94
+ command_executed = " ".join(cmd)
95
+ try:
96
+ result = subprocess.run(
97
+ cmd,
98
+ capture_output=True,
99
+ text=True,
100
+ check=True,
101
+ )
102
+ stdout = result.stdout
103
+ stderr = result.stderr
104
+ except FileNotFoundError:
105
+ return {
106
+ "command_executed": command_executed,
107
+ "stdout": "",
108
+ "stderr": "Error: The 'echidna_runner' executable was not found. Please ensure the echidna environment is correctly configured and the wrapper script is in the system's PATH.",
109
+ "output_files": [],
110
+ "return_code": 1,
111
+ }
112
+ except subprocess.CalledProcessError as e:
113
+ return {
114
+ "command_executed": command_executed,
115
+ "stdout": e.stdout,
116
+ "stderr": e.stderr,
117
+ "output_files": [],
118
+ "return_code": e.returncode,
119
+ }
120
+
121
+ # --- Structured Result Return ---
122
+ # Define expected output files based on the hypothetical wrapper's behavior.
123
+ # This typically includes the updated AnnData object, a saved model, and plots.
124
+ output_files = [
125
+ f"{output_prefix}_adata.h5ad",
126
+ f"{output_prefix}_model.pt",
127
+ f"{output_prefix}_gene_dosage.csv",
128
+ f"{output_prefix}_gene_dosage.png",
129
+ ]
130
+
131
+ return {
132
+ "command_executed": command_executed,
133
+ "stdout": stdout,
134
+ "stderr": stderr,
135
+ "output_files": output_files,
136
+ "return_code": 0,
137
+ }
138
+
139
+ if __name__ == "__main__":
140
+ mcp.run(transport="stdio")