Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/bioawk_server.py +120 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/bioawk_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/bioconductor-ancombc_server.py +251 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/bioconductor-ancombc_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/bioconductor-concordexr_server.py +221 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/bioconductor-concordexr_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/app/bioconductor-decipher_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/bioconductor-ensembldb_server.py +433 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/bioconductor-ensembldb_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/app/bioconductor-escher_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_server.py +130 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-go.db/Dockerfile +40 -0
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/bioawk_server.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Optional, List, Dict, Any
|
| 4 |
+
|
| 5 |
+
# @mcp.tool() is a placeholder for the actual decorator.
|
| 6 |
+
# The user prompt says "NO NEED to import mcp".
|
| 7 |
+
def tool(*args, **kwargs):
|
| 8 |
+
def decorator(f):
|
| 9 |
+
return f
|
| 10 |
+
return decorator
|
| 11 |
+
|
| 12 |
+
mcp = type("mcp", (), {"tool": tool})
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@mcp.tool()
|
| 16 |
+
def bioawk(
|
| 17 |
+
program: Optional[str] = None,
|
| 18 |
+
program_file: Optional[Path] = None,
|
| 19 |
+
input_files: Optional[List[Path]] = None,
|
| 20 |
+
format: Optional[str] = None,
|
| 21 |
+
tab_separator: bool = False,
|
| 22 |
+
retain_header: bool = False,
|
| 23 |
+
variables: Optional[List[str]] = None,
|
| 24 |
+
field_separator: Optional[str] = None,
|
| 25 |
+
) -> Dict[str, Any]:
|
| 26 |
+
"""
|
| 27 |
+
Executes the bioawk tool, a variant of awk for processing biological data formats.
|
| 28 |
+
|
| 29 |
+
bioawk extends awk by understanding common bioinformatics formats, making it easy
|
| 30 |
+
to parse and manipulate files like FASTA, FASTQ, SAM, VCF, etc.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
program: The awk program script to execute, provided as a string.
|
| 34 |
+
program_file: A file containing the awk program script.
|
| 35 |
+
Exactly one of 'program' or 'program_file' must be provided.
|
| 36 |
+
input_files: A list of input files to process. If not provided,
|
| 37 |
+
bioawk will read from standard input.
|
| 38 |
+
format: The format of the input data. Supported formats include:
|
| 39 |
+
fasta, fastq, sam, vcf, bed, gff, gtf, psl, blast, hmmer, cigar.
|
| 40 |
+
If not specified, bioawk defaults to 'fasta'.
|
| 41 |
+
tab_separator: Use tab as the input and output field separator (-t).
|
| 42 |
+
retain_header: Retain the header in the output (-H).
|
| 43 |
+
variables: A list of variable assignments in 'var=value' format (-v).
|
| 44 |
+
field_separator: The input field separator, equivalent to awk's -F option.
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
A dictionary containing the executed command, stdout, and stderr.
|
| 48 |
+
"""
|
| 49 |
+
# 1. Input Validation
|
| 50 |
+
if not (program or program_file) or (program and program_file):
|
| 51 |
+
raise ValueError("Exactly one of 'program' or 'program_file' must be provided.")
|
| 52 |
+
|
| 53 |
+
if program_file and not program_file.is_file():
|
| 54 |
+
raise FileNotFoundError(f"Program file not found: {program_file}")
|
| 55 |
+
|
| 56 |
+
if input_files:
|
| 57 |
+
for file_path in input_files:
|
| 58 |
+
if not file_path.is_file():
|
| 59 |
+
raise FileNotFoundError(f"Input file not found: {file_path}")
|
| 60 |
+
|
| 61 |
+
VALID_FORMATS = {
|
| 62 |
+
"fasta", "fastq", "sam", "vcf", "bed", "gff",
|
| 63 |
+
"gtf", "psl", "blast", "hmmer", "cigar"
|
| 64 |
+
}
|
| 65 |
+
if format and format.lower() not in VALID_FORMATS:
|
| 66 |
+
raise ValueError(f"Invalid format '{format}'. Must be one of {VALID_FORMATS}")
|
| 67 |
+
|
| 68 |
+
if variables:
|
| 69 |
+
for var in variables:
|
| 70 |
+
if "=" not in var:
|
| 71 |
+
raise ValueError(f"Invalid variable assignment '{var}'. Must be in 'var=value' format.")
|
| 72 |
+
|
| 73 |
+
# 2. Command Construction
|
| 74 |
+
cmd = ["bioawk"]
|
| 75 |
+
|
| 76 |
+
if format:
|
| 77 |
+
cmd.extend(["-c", format])
|
| 78 |
+
if tab_separator:
|
| 79 |
+
cmd.append("-t")
|
| 80 |
+
if retain_header:
|
| 81 |
+
cmd.append("-H")
|
| 82 |
+
if field_separator:
|
| 83 |
+
cmd.extend(["-F", field_separator])
|
| 84 |
+
if variables:
|
| 85 |
+
for var in variables:
|
| 86 |
+
cmd.extend(["-v", var])
|
| 87 |
+
|
| 88 |
+
if program_file:
|
| 89 |
+
cmd.extend(["-f", str(program_file)])
|
| 90 |
+
elif program:
|
| 91 |
+
cmd.append(program)
|
| 92 |
+
|
| 93 |
+
if input_files:
|
| 94 |
+
cmd.extend([str(p) for p in input_files])
|
| 95 |
+
|
| 96 |
+
# 3. Subprocess Execution
|
| 97 |
+
command_executed = " ".join(cmd)
|
| 98 |
+
try:
|
| 99 |
+
result = subprocess.run(
|
| 100 |
+
cmd,
|
| 101 |
+
capture_output=True,
|
| 102 |
+
text=True,
|
| 103 |
+
check=True,
|
| 104 |
+
)
|
| 105 |
+
return {
|
| 106 |
+
"command_executed": command_executed,
|
| 107 |
+
"stdout": result.stdout,
|
| 108 |
+
"stderr": result.stderr,
|
| 109 |
+
}
|
| 110 |
+
except FileNotFoundError:
|
| 111 |
+
# This error is raised if the 'bioawk' command itself is not found.
|
| 112 |
+
raise RuntimeError("bioawk command not found. Please ensure it is installed and in your PATH.")
|
| 113 |
+
except subprocess.CalledProcessError as e:
|
| 114 |
+
# This error is raised if bioawk returns a non-zero exit code.
|
| 115 |
+
return {
|
| 116 |
+
"command_executed": command_executed,
|
| 117 |
+
"stdout": e.stdout,
|
| 118 |
+
"stderr": e.stderr,
|
| 119 |
+
"error": f"bioawk failed with exit code {e.returncode}"
|
| 120 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/bioawk_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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_bioawk/app/bioawk_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioawk'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/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-ancombc via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-ancombc -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-ancombc_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-ancombc_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-ancombc_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/bioconductor-ancombc_server.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
import textwrap
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Dict, List, Optional
|
| 6 |
+
|
| 7 |
+
# @mcp.tool() is a placeholder for the actual decorator.
|
| 8 |
+
# The function is written to be compatible with the MCP server environment.
|
| 9 |
+
def mcp_tool_placeholder(*args, **kwargs):
|
| 10 |
+
def decorator(func):
|
| 11 |
+
return func
|
| 12 |
+
return decorator
|
| 13 |
+
|
| 14 |
+
@mcp_tool_placeholder()
|
| 15 |
+
def ancombc2(
|
| 16 |
+
otu_table: Path,
|
| 17 |
+
sample_metadata: Path,
|
| 18 |
+
formula: str,
|
| 19 |
+
output_dir: Path,
|
| 20 |
+
taxonomy_table: Optional[Path] = None,
|
| 21 |
+
p_adj_method: str = "holm",
|
| 22 |
+
zero_cut: float = 0.9,
|
| 23 |
+
lib_cut: int = 1000,
|
| 24 |
+
struc_zero: bool = False,
|
| 25 |
+
neg_lb: bool = False,
|
| 26 |
+
tol: float = 1e-05,
|
| 27 |
+
max_iter: int = 100,
|
| 28 |
+
conserve: bool = False,
|
| 29 |
+
alpha: float = 0.05,
|
| 30 |
+
global_test: bool = False,
|
| 31 |
+
group: Optional[str] = None,
|
| 32 |
+
mdfdr_dist: str = "normal",
|
| 33 |
+
n_cl: int = 1,
|
| 34 |
+
verbose: bool = False,
|
| 35 |
+
output_prefix: str = "ancombc2_results",
|
| 36 |
+
) -> Dict:
|
| 37 |
+
"""
|
| 38 |
+
Performs differential abundance analysis on microbiome data using ANCOM-BC2.
|
| 39 |
+
|
| 40 |
+
ANCOM-BC2 (Analysis of Compositions of Microbiomes with Bias Correction 2) is a
|
| 41 |
+
method for identifying differentially abundant taxa between groups while correcting
|
| 42 |
+
for sample-specific and taxon-specific biases.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
otu_table: Path to the feature/OTU count table (CSV format). Rows should be taxa
|
| 46 |
+
and columns should be samples. The first column should be taxon IDs.
|
| 47 |
+
sample_metadata: Path to the sample metadata file (CSV format). Rows should be
|
| 48 |
+
samples and columns should be metadata variables. The first
|
| 49 |
+
column should be sample IDs.
|
| 50 |
+
formula: An R-style formula string specifying the model, e.g., "age + sex + diagnosis".
|
| 51 |
+
Variables must correspond to columns in the sample_metadata file.
|
| 52 |
+
output_dir: Path to the directory where output files will be saved.
|
| 53 |
+
taxonomy_table: Optional path to the taxonomy table (CSV format). Rows should be
|
| 54 |
+
taxa, columns should be taxonomic ranks. The first column must
|
| 55 |
+
be taxon IDs matching the OTU table.
|
| 56 |
+
p_adj_method: Method for p-value adjustment.
|
| 57 |
+
Options: "holm", "hochberg", "hommel", "bonferroni", "BH", "BY", "fdr", "none".
|
| 58 |
+
zero_cut: A numerical value between 0 and 1. Taxa with a proportion of zeros greater
|
| 59 |
+
than this value will be excluded from the analysis.
|
| 60 |
+
lib_cut: A numerical value. Samples with library sizes less than this value will be
|
| 61 |
+
excluded from the analysis.
|
| 62 |
+
struc_zero: Whether to detect structural zeros.
|
| 63 |
+
neg_lb: Whether to use the negative binomial distribution for modeling sampling fractions.
|
| 64 |
+
If FALSE, a log-linear model is used.
|
| 65 |
+
tol: Convergence tolerance for the optimization algorithm.
|
| 66 |
+
max_iter: Maximum number of iterations for the optimization algorithm.
|
| 67 |
+
conserve: Whether to use a conservative variance estimate for the test statistic.
|
| 68 |
+
alpha: Significance level for identifying differentially abundant taxa.
|
| 69 |
+
global_test: Whether to perform a global test for the specified group variable.
|
| 70 |
+
group: The name of the group variable for the global test. Required if `global_test` is True.
|
| 71 |
+
Must be a column name in the sample_metadata file.
|
| 72 |
+
mdfdr_dist: The assumed distribution of the E-values in m-DFDR.
|
| 73 |
+
Options: "normal", "t".
|
| 74 |
+
n_cl: Number of CPU cores to use for parallel computation.
|
| 75 |
+
verbose: Whether to display progress messages during execution.
|
| 76 |
+
output_prefix: Prefix for the output result files.
|
| 77 |
+
|
| 78 |
+
Returns:
|
| 79 |
+
A dictionary containing the executed command, stdout, stderr, and a list of
|
| 80 |
+
paths to the generated output files.
|
| 81 |
+
"""
|
| 82 |
+
# --- 1. Input Validation ---
|
| 83 |
+
if not otu_table.is_file():
|
| 84 |
+
raise FileNotFoundError(f"OTU table not found at: {otu_table}")
|
| 85 |
+
if not sample_metadata.is_file():
|
| 86 |
+
raise FileNotFoundError(f"Sample metadata not found at: {sample_metadata}")
|
| 87 |
+
if taxonomy_table and not taxonomy_table.is_file():
|
| 88 |
+
raise FileNotFoundError(f"Taxonomy table not found at: {taxonomy_table}")
|
| 89 |
+
|
| 90 |
+
p_adj_methods = ["holm", "hochberg", "hommel", "bonferroni", "BH", "BY", "fdr", "none"]
|
| 91 |
+
if p_adj_method not in p_adj_methods:
|
| 92 |
+
raise ValueError(f"Invalid p_adj_method '{p_adj_method}'. Must be one of {p_adj_methods}")
|
| 93 |
+
|
| 94 |
+
mdfdr_dists = ["normal", "t"]
|
| 95 |
+
if mdfdr_dist not in mdfdr_dists:
|
| 96 |
+
raise ValueError(f"Invalid mdfdr_dist '{mdfdr_dist}'. Must be one of {mdfdr_dists}")
|
| 97 |
+
|
| 98 |
+
if not 0 <= zero_cut <= 1:
|
| 99 |
+
raise ValueError("zero_cut must be between 0 and 1.")
|
| 100 |
+
if not 0 < alpha < 1:
|
| 101 |
+
raise ValueError("alpha must be between 0 and 1.")
|
| 102 |
+
if lib_cut < 0:
|
| 103 |
+
raise ValueError("lib_cut must be a non-negative integer.")
|
| 104 |
+
if max_iter <= 0:
|
| 105 |
+
raise ValueError("max_iter must be a positive integer.")
|
| 106 |
+
if n_cl <= 0:
|
| 107 |
+
raise ValueError("n_cl must be a positive integer.")
|
| 108 |
+
if tol <= 0:
|
| 109 |
+
raise ValueError("tol must be a positive float.")
|
| 110 |
+
|
| 111 |
+
if global_test and not group:
|
| 112 |
+
raise ValueError("The 'group' parameter is required when 'global_test' is True.")
|
| 113 |
+
|
| 114 |
+
# --- 2. Prepare for Execution ---
|
| 115 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 116 |
+
|
| 117 |
+
output_res_path = output_dir / f"{output_prefix}_DA.csv"
|
| 118 |
+
output_files = [str(output_res_path)]
|
| 119 |
+
|
| 120 |
+
output_global_res_path = output_dir / f"{output_prefix}_global.csv"
|
| 121 |
+
if global_test:
|
| 122 |
+
output_files.append(str(output_global_res_path))
|
| 123 |
+
|
| 124 |
+
# Convert Python types to R-compatible strings
|
| 125 |
+
r_tax_file = f"'{taxonomy_table.resolve()}'" if taxonomy_table else "NULL"
|
| 126 |
+
r_group = f"'{group}'" if group else "NULL"
|
| 127 |
+
r_struc_zero = "TRUE" if struc_zero else "FALSE"
|
| 128 |
+
r_neg_lb = "TRUE" if neg_lb else "FALSE"
|
| 129 |
+
r_conserve = "TRUE" if conserve else "FALSE"
|
| 130 |
+
r_global_test = "TRUE" if global_test else "FALSE"
|
| 131 |
+
r_verbose = "TRUE" if verbose else "FALSE"
|
| 132 |
+
|
| 133 |
+
# --- 3. Generate R Script ---
|
| 134 |
+
r_script_content = textwrap.dedent(f"""
|
| 135 |
+
# Load required libraries
|
| 136 |
+
library(ANCOMBC)
|
| 137 |
+
library(phyloseq)
|
| 138 |
+
|
| 139 |
+
# --- Parameters ---
|
| 140 |
+
otu_file <- '{otu_table.resolve()}'
|
| 141 |
+
meta_file <- '{sample_metadata.resolve()}'
|
| 142 |
+
tax_file <- {r_tax_file}
|
| 143 |
+
output_res_path <- '{output_res_path.resolve()}'
|
| 144 |
+
output_global_res_path <- '{output_global_res_path.resolve()}'
|
| 145 |
+
|
| 146 |
+
# --- Load and prepare data ---
|
| 147 |
+
# read.csv with row.names = 1 assumes first column is the index
|
| 148 |
+
otu_mat <- as.matrix(read.csv(otu_file, row.names = 1, check.names = FALSE))
|
| 149 |
+
meta_data <- read.csv(meta_file, row.names = 1, check.names = FALSE)
|
| 150 |
+
|
| 151 |
+
# Ensure sample names match and are in the same order
|
| 152 |
+
samples_in_common <- intersect(rownames(meta_data), colnames(otu_mat))
|
| 153 |
+
if (length(samples_in_common) == 0) {{
|
| 154 |
+
stop("No common sample IDs found between OTU table and metadata.")
|
| 155 |
+
}}
|
| 156 |
+
otu_mat <- otu_mat[, samples_in_common, drop = FALSE]
|
| 157 |
+
meta_data <- meta_data[samples_in_common, , drop = FALSE]
|
| 158 |
+
|
| 159 |
+
OTU <- otu_table(otu_mat, taxa_are_rows = TRUE)
|
| 160 |
+
META <- sample_data(meta_data)
|
| 161 |
+
|
| 162 |
+
# Load taxonomy if provided
|
| 163 |
+
if (!is.null(tax_file)) {{
|
| 164 |
+
tax_mat <- as.matrix(read.csv(tax_file, row.names = 1, check.names = FALSE))
|
| 165 |
+
# Ensure taxon names match
|
| 166 |
+
taxa_in_common <- intersect(rownames(tax_mat), rownames(otu_mat))
|
| 167 |
+
if (length(taxa_in_common) == 0) {{
|
| 168 |
+
warning("No common taxon IDs found between OTU table and taxonomy table. Proceeding without taxonomy.")
|
| 169 |
+
pseq <- phyloseq(OTU, META)
|
| 170 |
+
}} else {{
|
| 171 |
+
tax_mat_filtered <- tax_mat[taxa_in_common, , drop = FALSE]
|
| 172 |
+
TAX <- tax_table(tax_mat_filtered)
|
| 173 |
+
pseq <- phyloseq(OTU, META, TAX)
|
| 174 |
+
}}
|
| 175 |
+
}} else {{
|
| 176 |
+
pseq <- phyloseq(OTU, META)
|
| 177 |
+
}}
|
| 178 |
+
|
| 179 |
+
# --- Run ANCOM-BC2 ---
|
| 180 |
+
output <- ancombc2(
|
| 181 |
+
data = pseq,
|
| 182 |
+
formula = "{formula}",
|
| 183 |
+
p_adj_method = "{p_adj_method}",
|
| 184 |
+
zero_cut = {zero_cut},
|
| 185 |
+
lib_cut = {lib_cut},
|
| 186 |
+
struc_zero = {r_struc_zero},
|
| 187 |
+
neg_lb = {r_neg_lb},
|
| 188 |
+
tol = {tol},
|
| 189 |
+
max_iter = {max_iter},
|
| 190 |
+
conserve = {r_conserve},
|
| 191 |
+
alpha = {alpha},
|
| 192 |
+
global = {r_global_test},
|
| 193 |
+
group = {r_group},
|
| 194 |
+
mdfdr_dist = "{mdfdr_dist}",
|
| 195 |
+
n_cl = {n_cl},
|
| 196 |
+
verbose = {r_verbose}
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# --- Save results ---
|
| 200 |
+
# Save differential abundance results
|
| 201 |
+
res_df <- data.frame(output$res)
|
| 202 |
+
write.csv(res_df, file = output_res_path, row.names = TRUE)
|
| 203 |
+
|
| 204 |
+
# Save global test results if applicable
|
| 205 |
+
if ({r_global_test} && !is.null(output$res_global)) {{
|
| 206 |
+
res_global_df <- data.frame(output$res_global)
|
| 207 |
+
write.csv(res_global_df, file = output_global_res_path, row.names = TRUE)
|
| 208 |
+
}}
|
| 209 |
+
|
| 210 |
+
print("ANCOM-BC2 analysis completed successfully.")
|
| 211 |
+
""")
|
| 212 |
+
|
| 213 |
+
# --- 4. Execute ---
|
| 214 |
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file:
|
| 215 |
+
r_script_file.write(r_script_content)
|
| 216 |
+
r_script_path = r_script_file.name
|
| 217 |
+
|
| 218 |
+
command = ["Rscript", r_script_path]
|
| 219 |
+
command_executed = " ".join(command)
|
| 220 |
+
|
| 221 |
+
try:
|
| 222 |
+
result = subprocess.run(
|
| 223 |
+
command,
|
| 224 |
+
capture_output=True,
|
| 225 |
+
text=True,
|
| 226 |
+
check=True,
|
| 227 |
+
)
|
| 228 |
+
stdout = result.stdout
|
| 229 |
+
stderr = result.stderr
|
| 230 |
+
except subprocess.CalledProcessError as e:
|
| 231 |
+
# Clean up the temporary script file on error
|
| 232 |
+
Path(r_script_path).unlink()
|
| 233 |
+
return {
|
| 234 |
+
"command_executed": command_executed,
|
| 235 |
+
"stdout": e.stdout,
|
| 236 |
+
"stderr": e.stderr,
|
| 237 |
+
"return_code": e.returncode,
|
| 238 |
+
"output_files": []
|
| 239 |
+
}
|
| 240 |
+
finally:
|
| 241 |
+
# Ensure temporary script is always cleaned up
|
| 242 |
+
if Path(r_script_path).exists():
|
| 243 |
+
Path(r_script_path).unlink()
|
| 244 |
+
|
| 245 |
+
# --- 5. Return results ---
|
| 246 |
+
return {
|
| 247 |
+
"command_executed": command_executed,
|
| 248 |
+
"stdout": stdout,
|
| 249 |
+
"stderr": stderr,
|
| 250 |
+
"output_files": output_files
|
| 251 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/bioconductor-ancombc_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-ancombc/app/bioconductor-ancombc_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_ancombc'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-ancombc:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-ancombc:latest
|
| 7 |
+
container_name: mcp-bioconductor-ancombc
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-ancombc
|
| 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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/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-ancombc
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/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-bsgenome via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-bsgenome -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-bsgenome_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-bsgenome_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-bsgenome_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/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-bsgenome
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/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-concordexr via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-concordexr -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-concordexr_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-concordexr_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-concordexr_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/bioconductor-concordexr_server.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# Mock the decorator for standalone execution
|
| 7 |
+
class mcp:
|
| 8 |
+
@staticmethod
|
| 9 |
+
def tool(func):
|
| 10 |
+
return func
|
| 11 |
+
|
| 12 |
+
@mcp.tool
|
| 13 |
+
def compute_concordex(
|
| 14 |
+
data_file: Path,
|
| 15 |
+
ranks_file: Path,
|
| 16 |
+
indices_file: Path,
|
| 17 |
+
output_dir: Path,
|
| 18 |
+
data_type: str = "counts",
|
| 19 |
+
abundance_min: float = 1.0,
|
| 20 |
+
abundance_max: Optional[float] = None,
|
| 21 |
+
sample_min: float = 0.0,
|
| 22 |
+
sample_max: Optional[float] = None,
|
| 23 |
+
filter_perc: float = 0.0,
|
| 24 |
+
filter_count: int = 0,
|
| 25 |
+
plot: bool = True,
|
| 26 |
+
plot_format: str = "png",
|
| 27 |
+
plot_width: int = 10,
|
| 28 |
+
plot_height: int = 10,
|
| 29 |
+
export: bool = True,
|
| 30 |
+
export_rds: bool = True,
|
| 31 |
+
):
|
| 32 |
+
"""
|
| 33 |
+
Computes the Concordance Index (Concordex) using the concordexR package.
|
| 34 |
+
|
| 35 |
+
This tool wraps the `computeConcordex` function. It takes expression/count data,
|
| 36 |
+
pre-computed ranks, and sample indices to calculate concordex scores. It can
|
| 37 |
+
filter data, generate plots, and export results to files.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
data_file: Path to the input data file (e.g., counts matrix). Assumed to be a tab-separated file with a header and row names in the first column.
|
| 41 |
+
ranks_file: Path to the ranks file. Assumed to be a tab-separated file with a header and row names in the first column.
|
| 42 |
+
indices_file: Path to the indices file. Assumed to be a single-column, tab-separated file without a header.
|
| 43 |
+
output_dir: Path to the directory where output files will be saved.
|
| 44 |
+
data_type: The type of data in `data_file`. Must be either 'counts' or 'ranks'.
|
| 45 |
+
abundance_min: The minimum abundance of a feature to be included in the analysis.
|
| 46 |
+
abundance_max: The maximum abundance of a feature. If None, no upper limit is applied.
|
| 47 |
+
sample_min: The minimum value of a sample to be included.
|
| 48 |
+
sample_max: The maximum value of a sample. If None, no upper limit is applied.
|
| 49 |
+
filter_perc: The percentage of samples a feature must be present in to be retained. Value must be between 0.0 and 1.0.
|
| 50 |
+
filter_count: The minimum number of counts a feature must have across all samples to be retained.
|
| 51 |
+
plot: If True, generate concordex plots.
|
| 52 |
+
plot_format: The format for the output plots (e.g., 'png', 'pdf', 'svg').
|
| 53 |
+
plot_width: The width of the output plots in inches.
|
| 54 |
+
plot_height: The height of the output plots in inches.
|
| 55 |
+
export: If True, export the concordex results to text files.
|
| 56 |
+
export_rds: If True, save the final concordex R object to an .rds file for later use (e.g., with plot_concordex).
|
| 57 |
+
"""
|
| 58 |
+
# Input validation
|
| 59 |
+
if not data_file.is_file():
|
| 60 |
+
raise FileNotFoundError(f"Input data file not found: {data_file}")
|
| 61 |
+
if not ranks_file.is_file():
|
| 62 |
+
raise FileNotFoundError(f"Ranks file not found: {ranks_file}")
|
| 63 |
+
if not indices_file.is_file():
|
| 64 |
+
raise FileNotFoundError(f"Indices file not found: {indices_file}")
|
| 65 |
+
|
| 66 |
+
if data_type not in ["counts", "ranks"]:
|
| 67 |
+
raise ValueError("data_type must be either 'counts' or 'ranks'.")
|
| 68 |
+
if not (0.0 <= filter_perc <= 1.0):
|
| 69 |
+
raise ValueError("filter_perc must be between 0.0 and 1.0.")
|
| 70 |
+
if filter_count < 0:
|
| 71 |
+
raise ValueError("filter_count must be a non-negative integer.")
|
| 72 |
+
if plot_format not in ["png", "pdf", "svg", "jpeg", "tiff"]:
|
| 73 |
+
raise ValueError(f"Unsupported plot format: {plot_format}")
|
| 74 |
+
|
| 75 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 76 |
+
|
| 77 |
+
# Build the R script
|
| 78 |
+
r_script_lines = [
|
| 79 |
+
"library(concordexR)",
|
| 80 |
+
f'data <- read.table("{data_file.resolve()}", header=TRUE, sep="\\t", row.names=1, check.names=FALSE)',
|
| 81 |
+
f'ranks <- read.table("{ranks_file.resolve()}", header=TRUE, sep="\\t", row.names=1, check.names=FALSE)',
|
| 82 |
+
f'indices_df <- read.table("{indices_file.resolve()}", header=FALSE, sep="\\t")',
|
| 83 |
+
"indices <- as.list(indices_df$V1)",
|
| 84 |
+
"concordex_result <- computeConcordex(",
|
| 85 |
+
" data = data,",
|
| 86 |
+
" ranks = ranks,",
|
| 87 |
+
" indices = indices,",
|
| 88 |
+
f' type = "{data_type}",',
|
| 89 |
+
f" abundance.min = {abundance_min},",
|
| 90 |
+
f" abundance.max = {abundance_max if abundance_max is not None else 'Inf'},",
|
| 91 |
+
f" sample.min = {sample_min},",
|
| 92 |
+
f" sample.max = {sample_max if sample_max is not None else 'Inf'},",
|
| 93 |
+
f" filter.perc = {filter_perc},",
|
| 94 |
+
f" filter.count = {filter_count},",
|
| 95 |
+
f' output.dir = "{output_dir.resolve()}",',
|
| 96 |
+
f" plot = {'TRUE' if plot else 'FALSE'},",
|
| 97 |
+
f' plot.format = "{plot_format}",',
|
| 98 |
+
f" plot.width = {plot_width},",
|
| 99 |
+
f" plot.height = {plot_height},",
|
| 100 |
+
f" export = {'TRUE' if export else 'FALSE'}",
|
| 101 |
+
")",
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
if export_rds:
|
| 105 |
+
rds_path = output_dir.resolve() / "concordex_object.rds"
|
| 106 |
+
r_script_lines.append(f'saveRDS(concordex_result, file = "{rds_path}")')
|
| 107 |
+
|
| 108 |
+
r_script = "\n".join(r_script_lines)
|
| 109 |
+
|
| 110 |
+
# Use a temporary file for the R script
|
| 111 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as f:
|
| 112 |
+
f.write(r_script)
|
| 113 |
+
r_script_path = Path(f.name)
|
| 114 |
+
|
| 115 |
+
cmd = ["Rscript", str(r_script_path)]
|
| 116 |
+
command_executed = " ".join(cmd)
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
result = subprocess.run(
|
| 120 |
+
cmd,
|
| 121 |
+
capture_output=True,
|
| 122 |
+
text=True,
|
| 123 |
+
check=True,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
output_files = [str(p) for p in output_dir.glob("**/*") if p.is_file()]
|
| 127 |
+
|
| 128 |
+
return {
|
| 129 |
+
"command_executed": command_executed,
|
| 130 |
+
"stdout": result.stdout,
|
| 131 |
+
"stderr": result.stderr,
|
| 132 |
+
"output_files": output_files,
|
| 133 |
+
}
|
| 134 |
+
except subprocess.CalledProcessError as e:
|
| 135 |
+
raise RuntimeError(
|
| 136 |
+
f"R script execution failed with return code {e.returncode}.\n"
|
| 137 |
+
f"Command: {command_executed}\n"
|
| 138 |
+
f"Stdout: {e.stdout}\n"
|
| 139 |
+
f"Stderr: {e.stderr}"
|
| 140 |
+
) from e
|
| 141 |
+
finally:
|
| 142 |
+
r_script_path.unlink()
|
| 143 |
+
|
| 144 |
+
@mcp.tool
|
| 145 |
+
def plot_concordex(
|
| 146 |
+
concordex_object_file: Path,
|
| 147 |
+
output_dir: Path,
|
| 148 |
+
plot_format: str = "png",
|
| 149 |
+
plot_width: int = 10,
|
| 150 |
+
plot_height: int = 10,
|
| 151 |
+
):
|
| 152 |
+
"""
|
| 153 |
+
Generates plots from a pre-computed concordex object.
|
| 154 |
+
|
| 155 |
+
This tool wraps the `plotConcordex` function from the concordexR package.
|
| 156 |
+
It takes an .rds file containing a concordex object (generated by the
|
| 157 |
+
compute_concordex tool) and creates visualization plots.
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
concordex_object_file: Path to the .rds file containing the concordex object.
|
| 161 |
+
output_dir: Path to the directory where output plots will be saved.
|
| 162 |
+
plot_format: The format for the output plots (e.g., 'png', 'pdf', 'svg').
|
| 163 |
+
plot_width: The width of the output plots in inches.
|
| 164 |
+
plot_height: The height of the output plots in inches.
|
| 165 |
+
"""
|
| 166 |
+
# Input validation
|
| 167 |
+
if not concordex_object_file.is_file():
|
| 168 |
+
raise FileNotFoundError(f"Concordex object file not found: {concordex_object_file}")
|
| 169 |
+
if concordex_object_file.suffix != ".rds":
|
| 170 |
+
raise ValueError("concordex_object_file must be an .rds file.")
|
| 171 |
+
if plot_format not in ["png", "pdf", "svg", "jpeg", "tiff"]:
|
| 172 |
+
raise ValueError(f"Unsupported plot format: {plot_format}")
|
| 173 |
+
|
| 174 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 175 |
+
|
| 176 |
+
# Build the R script
|
| 177 |
+
r_script_lines = [
|
| 178 |
+
"library(concordexR)",
|
| 179 |
+
f'concordex_obj <- readRDS("{concordex_object_file.resolve()}")',
|
| 180 |
+
"plotConcordex(",
|
| 181 |
+
" concordex.object = concordex_obj,",
|
| 182 |
+
f' output.dir = "{output_dir.resolve()}",',
|
| 183 |
+
f' plot.format = "{plot_format}",',
|
| 184 |
+
f" plot.width = {plot_width},",
|
| 185 |
+
f" plot.height = {plot_height}",
|
| 186 |
+
")",
|
| 187 |
+
]
|
| 188 |
+
r_script = "\n".join(r_script_lines)
|
| 189 |
+
|
| 190 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as f:
|
| 191 |
+
f.write(r_script)
|
| 192 |
+
r_script_path = Path(f.name)
|
| 193 |
+
|
| 194 |
+
cmd = ["Rscript", str(r_script_path)]
|
| 195 |
+
command_executed = " ".join(cmd)
|
| 196 |
+
|
| 197 |
+
try:
|
| 198 |
+
result = subprocess.run(
|
| 199 |
+
cmd,
|
| 200 |
+
capture_output=True,
|
| 201 |
+
text=True,
|
| 202 |
+
check=True,
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
output_files = [str(p) for p in output_dir.glob("**/*") if p.is_file()]
|
| 206 |
+
|
| 207 |
+
return {
|
| 208 |
+
"command_executed": command_executed,
|
| 209 |
+
"stdout": result.stdout,
|
| 210 |
+
"stderr": result.stderr,
|
| 211 |
+
"output_files": output_files,
|
| 212 |
+
}
|
| 213 |
+
except subprocess.CalledProcessError as e:
|
| 214 |
+
raise RuntimeError(
|
| 215 |
+
f"R script execution failed with return code {e.returncode}.\n"
|
| 216 |
+
f"Command: {command_executed}\n"
|
| 217 |
+
f"Stdout: {e.stdout}\n"
|
| 218 |
+
f"Stderr: {e.stderr}"
|
| 219 |
+
) from e
|
| 220 |
+
finally:
|
| 221 |
+
r_script_path.unlink()
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/bioconductor-concordexr_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-concordexr/app/bioconductor-concordexr_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_concordexr'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-concordexr:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-concordexr:latest
|
| 7 |
+
container_name: mcp-bioconductor-concordexr
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-concordexr
|
| 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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/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-concordexr
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/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-decipher via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-decipher -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-decipher_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-decipher_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-decipher_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/app/bioconductor-decipher_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-decipher/app/bioconductor-decipher_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_decipher'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-decipher:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-decipher:latest
|
| 7 |
+
container_name: mcp-bioconductor-decipher
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-decipher
|
| 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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/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-decipher
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/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-ensembldb via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-ensembldb -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-ensembldb_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-ensembldb_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-ensembldb_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/bioconductor-ensembldb_server.py
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
import textwrap
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Optional, List, Dict, Any
|
| 6 |
+
|
| 7 |
+
# Assume mcp.tool is imported from a framework library
|
| 8 |
+
class mcp:
|
| 9 |
+
def tool(func):
|
| 10 |
+
return func
|
| 11 |
+
|
| 12 |
+
def _run_r_script(r_script_content: str, args: List[str]) -> Dict[str, Any]:
|
| 13 |
+
"""
|
| 14 |
+
A helper function to execute an R script using Rscript, handling errors.
|
| 15 |
+
|
| 16 |
+
This function requires that R and the required Bioconductor packages
|
| 17 |
+
(e.g., ensembldb, AnnotationFilter) are installed in the execution environment.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
r_script_content: A string containing the R code to execute.
|
| 21 |
+
args: A list of command-line arguments for the R script.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
A dictionary containing the executed command, stdout, and stderr.
|
| 25 |
+
|
| 26 |
+
Raises:
|
| 27 |
+
RuntimeError: If Rscript is not found in the system's PATH.
|
| 28 |
+
subprocess.CalledProcessError: If the R script execution fails.
|
| 29 |
+
"""
|
| 30 |
+
try:
|
| 31 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, encoding='utf-8') as tmp_script:
|
| 32 |
+
script_path = Path(tmp_script.name)
|
| 33 |
+
tmp_script.write(r_script_content)
|
| 34 |
+
|
| 35 |
+
command = ["Rscript", str(script_path)] + args
|
| 36 |
+
|
| 37 |
+
process = subprocess.run(
|
| 38 |
+
command,
|
| 39 |
+
check=True,
|
| 40 |
+
capture_output=True,
|
| 41 |
+
text=True,
|
| 42 |
+
encoding='utf-8'
|
| 43 |
+
)
|
| 44 |
+
return {
|
| 45 |
+
"command_executed": " ".join(command),
|
| 46 |
+
"stdout": process.stdout,
|
| 47 |
+
"stderr": process.stderr
|
| 48 |
+
}
|
| 49 |
+
except FileNotFoundError:
|
| 50 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your PATH.")
|
| 51 |
+
except subprocess.CalledProcessError as e:
|
| 52 |
+
error_message = (
|
| 53 |
+
f"R script execution failed with exit code {e.returncode}.\n"
|
| 54 |
+
f"Command: {' '.join(e.cmd)}\n"
|
| 55 |
+
f"Stdout: {e.stdout}\n"
|
| 56 |
+
f"Stderr: {e.stderr}"
|
| 57 |
+
)
|
| 58 |
+
# Re-raise with a more informative stderr
|
| 59 |
+
raise subprocess.CalledProcessError(e.returncode, e.cmd, output=e.stdout, stderr=error_message)
|
| 60 |
+
finally:
|
| 61 |
+
if 'script_path' in locals() and script_path.exists():
|
| 62 |
+
script_path.unlink()
|
| 63 |
+
|
| 64 |
+
@mcp.tool
|
| 65 |
+
def create_ensdb_from_gtf(
|
| 66 |
+
gtf_file: Path,
|
| 67 |
+
output_db: Path,
|
| 68 |
+
organism: str,
|
| 69 |
+
genome_version: str,
|
| 70 |
+
ensembl_version: str,
|
| 71 |
+
) -> Dict[str, Any]:
|
| 72 |
+
"""
|
| 73 |
+
Creates an EnsemblDB SQLite database from a GTF file.
|
| 74 |
+
|
| 75 |
+
This tool wraps the `ensDbFromGtf` function from the R/Bioconductor
|
| 76 |
+
package 'ensembldb'. It requires R and the 'ensembldb' package to be
|
| 77 |
+
installed in the environment.
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
gtf_file: Path to the input GTF file.
|
| 81 |
+
output_db: Path for the output SQLite database file.
|
| 82 |
+
organism: Name of the organism (e.g., 'Homo_sapiens').
|
| 83 |
+
genome_version: Genome build version (e.g., 'GRCh38').
|
| 84 |
+
ensembl_version: Ensembl release version (e.g., '104').
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 88 |
+
and a list of output files.
|
| 89 |
+
"""
|
| 90 |
+
# Input validation
|
| 91 |
+
if not gtf_file.is_file():
|
| 92 |
+
raise FileNotFoundError(f"Input GTF file not found: {gtf_file}")
|
| 93 |
+
if not output_db.parent.is_dir():
|
| 94 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_db.parent}")
|
| 95 |
+
if not all([organism, genome_version, ensembl_version]):
|
| 96 |
+
raise ValueError("organism, genome_version, and ensembl_version must be non-empty strings.")
|
| 97 |
+
|
| 98 |
+
r_script = textwrap.dedent("""
|
| 99 |
+
library("ensembldb")
|
| 100 |
+
args <- commandArgs(trailingOnly = TRUE)
|
| 101 |
+
ensDbFromGtf(
|
| 102 |
+
gtf = args[1],
|
| 103 |
+
outfile = args[2],
|
| 104 |
+
organism = args[3],
|
| 105 |
+
genomeVersion = args[4],
|
| 106 |
+
version = args[5]
|
| 107 |
+
)
|
| 108 |
+
""")
|
| 109 |
+
|
| 110 |
+
args = [
|
| 111 |
+
str(gtf_file),
|
| 112 |
+
str(output_db),
|
| 113 |
+
organism,
|
| 114 |
+
genome_version,
|
| 115 |
+
ensembl_version
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
result = _run_r_script(r_script, args)
|
| 119 |
+
result["output_files"] = [str(output_db)]
|
| 120 |
+
return result
|
| 121 |
+
|
| 122 |
+
@mcp.tool
|
| 123 |
+
def create_ensdb_from_gff(
|
| 124 |
+
gff_file: Path,
|
| 125 |
+
output_db: Path,
|
| 126 |
+
organism: str,
|
| 127 |
+
genome_version: str,
|
| 128 |
+
ensembl_version: str,
|
| 129 |
+
) -> Dict[str, Any]:
|
| 130 |
+
"""
|
| 131 |
+
Creates an EnsemblDB SQLite database from a GFF3 file.
|
| 132 |
+
|
| 133 |
+
This tool wraps the `ensDbFromGff` function from the R/Bioconductor
|
| 134 |
+
package 'ensembldb'. It requires R and the 'ensembldb' package to be
|
| 135 |
+
installed in the environment.
|
| 136 |
+
|
| 137 |
+
Args:
|
| 138 |
+
gff_file: Path to the input GFF3 file.
|
| 139 |
+
output_db: Path for the output SQLite database file.
|
| 140 |
+
organism: Name of the organism (e.g., 'Homo_sapiens').
|
| 141 |
+
genome_version: Genome build version (e.g., 'GRCh38').
|
| 142 |
+
ensembl_version: Ensembl release version (e.g., '104').
|
| 143 |
+
|
| 144 |
+
Returns:
|
| 145 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 146 |
+
and a list of output files.
|
| 147 |
+
"""
|
| 148 |
+
# Input validation
|
| 149 |
+
if not gff_file.is_file():
|
| 150 |
+
raise FileNotFoundError(f"Input GFF file not found: {gff_file}")
|
| 151 |
+
if not output_db.parent.is_dir():
|
| 152 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_db.parent}")
|
| 153 |
+
if not all([organism, genome_version, ensembl_version]):
|
| 154 |
+
raise ValueError("organism, genome_version, and ensembl_version must be non-empty strings.")
|
| 155 |
+
|
| 156 |
+
r_script = textwrap.dedent("""
|
| 157 |
+
library("ensembldb")
|
| 158 |
+
args <- commandArgs(trailingOnly = TRUE)
|
| 159 |
+
ensDbFromGff(
|
| 160 |
+
gff = args[1],
|
| 161 |
+
outfile = args[2],
|
| 162 |
+
organism = args[3],
|
| 163 |
+
genomeVersion = args[4],
|
| 164 |
+
version = args[5]
|
| 165 |
+
)
|
| 166 |
+
""")
|
| 167 |
+
|
| 168 |
+
args = [
|
| 169 |
+
str(gff_file),
|
| 170 |
+
str(output_db),
|
| 171 |
+
organism,
|
| 172 |
+
genome_version,
|
| 173 |
+
ensembl_version
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
+
result = _run_r_script(r_script, args)
|
| 177 |
+
result["output_files"] = [str(output_db)]
|
| 178 |
+
return result
|
| 179 |
+
|
| 180 |
+
def _build_query_script(query_function: str, extra_r_code: str = "") -> str:
|
| 181 |
+
"""Helper to generate the R script for various query types."""
|
| 182 |
+
return textwrap.dedent(f"""
|
| 183 |
+
library("ensembldb")
|
| 184 |
+
library("AnnotationFilter")
|
| 185 |
+
|
| 186 |
+
args <- commandArgs(trailingOnly = TRUE)
|
| 187 |
+
|
| 188 |
+
parse_args <- function(args) {{
|
| 189 |
+
params <- list()
|
| 190 |
+
for (arg in args) {{
|
| 191 |
+
if (startsWith(arg, "--")) {{
|
| 192 |
+
parts <- strsplit(substring(arg, 3), "=", fixed = TRUE)[[1]]
|
| 193 |
+
key <- parts[1]
|
| 194 |
+
value <- if (length(parts) > 1) parts[2] else TRUE
|
| 195 |
+
params[[key]] <- value
|
| 196 |
+
}}
|
| 197 |
+
}}
|
| 198 |
+
return(params)
|
| 199 |
+
}}
|
| 200 |
+
|
| 201 |
+
params <- parse_args(args)
|
| 202 |
+
|
| 203 |
+
if (is.null(params$db) || is.null(params$outfile)) {{
|
| 204 |
+
stop("Both --db and --outfile arguments are required.")
|
| 205 |
+
}}
|
| 206 |
+
|
| 207 |
+
edb <- EnsDb(params$db)
|
| 208 |
+
|
| 209 |
+
filter_list <- list()
|
| 210 |
+
if (!is.null(params$filter_gene_id)) {{
|
| 211 |
+
filter_list <- c(filter_list, GeneIdFilter(params$filter_gene_id))
|
| 212 |
+
}}
|
| 213 |
+
if (!is.null(params$filter_gene_biotype)) {{
|
| 214 |
+
filter_list <- c(filter_list, GeneBiotypeFilter(params$filter_gene_biotype))
|
| 215 |
+
}}
|
| 216 |
+
if (!is.null(params$filter_seq_name)) {{
|
| 217 |
+
filter_list <- c(filter_list, SeqNameFilter(params$filter_seq_name))
|
| 218 |
+
}}
|
| 219 |
+
if (!is.null(params$filter_tx_id)) {{
|
| 220 |
+
filter_list <- c(filter_list, TxIdFilter(params$filter_tx_id))
|
| 221 |
+
}}
|
| 222 |
+
if (!is.null(params$filter_tx_biotype)) {{
|
| 223 |
+
filter_list <- c(filter_list, TxBiotypeFilter(params$filter_tx_biotype))
|
| 224 |
+
}}
|
| 225 |
+
if (!is.null(params$filter_exon_id)) {{
|
| 226 |
+
filter_list <- c(filter_list, ExonIdFilter(params$filter_exon_id))
|
| 227 |
+
}}
|
| 228 |
+
|
| 229 |
+
final_filter <- NULL
|
| 230 |
+
if (length(filter_list) > 0) {{
|
| 231 |
+
final_filter <- AnnotationFilterList(filter_list, logicOp = "&")
|
| 232 |
+
}}
|
| 233 |
+
|
| 234 |
+
{extra_r_code}
|
| 235 |
+
|
| 236 |
+
results <- {query_function}(edb, filter = final_filter)
|
| 237 |
+
|
| 238 |
+
write.table(as.data.frame(results), file = params$outfile, sep = "\t", row.names = FALSE, quote = FALSE)
|
| 239 |
+
|
| 240 |
+
cat("Successfully wrote query results to:", params$outfile, "\n")
|
| 241 |
+
""")
|
| 242 |
+
|
| 243 |
+
@mcp.tool
|
| 244 |
+
def query_genes(
|
| 245 |
+
db_file: Path,
|
| 246 |
+
output_tsv: Path,
|
| 247 |
+
filter_gene_id: Optional[str] = None,
|
| 248 |
+
filter_gene_biotype: Optional[str] = None,
|
| 249 |
+
filter_seq_name: Optional[str] = None,
|
| 250 |
+
) -> Dict[str, Any]:
|
| 251 |
+
"""
|
| 252 |
+
Queries gene information from an EnsemblDB SQLite database.
|
| 253 |
+
|
| 254 |
+
This tool wraps the `genes` function from 'ensembldb'. It requires R,
|
| 255 |
+
'ensembldb', and 'AnnotationFilter' packages.
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
db_file: Path to the input EnsemblDB SQLite file.
|
| 259 |
+
output_tsv: Path for the output TSV file.
|
| 260 |
+
filter_gene_id: Optional filter by Ensembl gene ID.
|
| 261 |
+
filter_gene_biotype: Optional filter by gene biotype (e.g., 'protein_coding').
|
| 262 |
+
filter_seq_name: Optional filter by sequence/chromosome name (e.g., 'X').
|
| 263 |
+
|
| 264 |
+
Returns:
|
| 265 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 266 |
+
and a list of output files.
|
| 267 |
+
"""
|
| 268 |
+
if not db_file.is_file():
|
| 269 |
+
raise FileNotFoundError(f"Input database file not found: {db_file}")
|
| 270 |
+
if not output_tsv.parent.is_dir():
|
| 271 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_tsv.parent}")
|
| 272 |
+
|
| 273 |
+
r_script = _build_query_script("genes")
|
| 274 |
+
|
| 275 |
+
args = [f"--db={db_file}", f"--outfile={output_tsv}"]
|
| 276 |
+
if filter_gene_id:
|
| 277 |
+
args.append(f"--filter_gene_id={filter_gene_id}")
|
| 278 |
+
if filter_gene_biotype:
|
| 279 |
+
args.append(f"--filter_gene_biotype={filter_gene_biotype}")
|
| 280 |
+
if filter_seq_name:
|
| 281 |
+
args.append(f"--filter_seq_name={filter_seq_name}")
|
| 282 |
+
|
| 283 |
+
result = _run_r_script(r_script, args)
|
| 284 |
+
result["output_files"] = [str(output_tsv)]
|
| 285 |
+
return result
|
| 286 |
+
|
| 287 |
+
@mcp.tool
|
| 288 |
+
def query_transcripts(
|
| 289 |
+
db_file: Path,
|
| 290 |
+
output_tsv: Path,
|
| 291 |
+
filter_gene_id: Optional[str] = None,
|
| 292 |
+
filter_tx_id: Optional[str] = None,
|
| 293 |
+
filter_tx_biotype: Optional[str] = None,
|
| 294 |
+
) -> Dict[str, Any]:
|
| 295 |
+
"""
|
| 296 |
+
Queries transcript information from an EnsemblDB SQLite database.
|
| 297 |
+
|
| 298 |
+
This tool wraps the `transcripts` function from 'ensembldb'. It requires R,
|
| 299 |
+
'ensembldb', and 'AnnotationFilter' packages.
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
db_file: Path to the input EnsemblDB SQLite file.
|
| 303 |
+
output_tsv: Path for the output TSV file.
|
| 304 |
+
filter_gene_id: Optional filter by Ensembl gene ID.
|
| 305 |
+
filter_tx_id: Optional filter by Ensembl transcript ID.
|
| 306 |
+
filter_tx_biotype: Optional filter by transcript biotype (e.g., 'protein_coding').
|
| 307 |
+
|
| 308 |
+
Returns:
|
| 309 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 310 |
+
and a list of output files.
|
| 311 |
+
"""
|
| 312 |
+
if not db_file.is_file():
|
| 313 |
+
raise FileNotFoundError(f"Input database file not found: {db_file}")
|
| 314 |
+
if not output_tsv.parent.is_dir():
|
| 315 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_tsv.parent}")
|
| 316 |
+
|
| 317 |
+
r_script = _build_query_script("transcripts")
|
| 318 |
+
|
| 319 |
+
args = [f"--db={db_file}", f"--outfile={output_tsv}"]
|
| 320 |
+
if filter_gene_id:
|
| 321 |
+
args.append(f"--filter_gene_id={filter_gene_id}")
|
| 322 |
+
if filter_tx_id:
|
| 323 |
+
args.append(f"--filter_tx_id={filter_tx_id}")
|
| 324 |
+
if filter_tx_biotype:
|
| 325 |
+
args.append(f"--filter_tx_biotype={filter_tx_biotype}")
|
| 326 |
+
|
| 327 |
+
result = _run_r_script(r_script, args)
|
| 328 |
+
result["output_files"] = [str(output_tsv)]
|
| 329 |
+
return result
|
| 330 |
+
|
| 331 |
+
@mcp.tool
|
| 332 |
+
def query_exons(
|
| 333 |
+
db_file: Path,
|
| 334 |
+
output_tsv: Path,
|
| 335 |
+
filter_gene_id: Optional[str] = None,
|
| 336 |
+
filter_tx_id: Optional[str] = None,
|
| 337 |
+
filter_exon_id: Optional[str] = None,
|
| 338 |
+
) -> Dict[str, Any]:
|
| 339 |
+
"""
|
| 340 |
+
Queries exon information from an EnsemblDB SQLite database.
|
| 341 |
+
|
| 342 |
+
This tool wraps the `exons` function from 'ensembldb'. It requires R,
|
| 343 |
+
'ensembldb', and 'AnnotationFilter' packages.
|
| 344 |
+
|
| 345 |
+
Args:
|
| 346 |
+
db_file: Path to the input EnsemblDB SQLite file.
|
| 347 |
+
output_tsv: Path for the output TSV file.
|
| 348 |
+
filter_gene_id: Optional filter by Ensembl gene ID.
|
| 349 |
+
filter_tx_id: Optional filter by Ensembl transcript ID.
|
| 350 |
+
filter_exon_id: Optional filter by Ensembl exon ID.
|
| 351 |
+
|
| 352 |
+
Returns:
|
| 353 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 354 |
+
and a list of output files.
|
| 355 |
+
"""
|
| 356 |
+
if not db_file.is_file():
|
| 357 |
+
raise FileNotFoundError(f"Input database file not found: {db_file}")
|
| 358 |
+
if not output_tsv.parent.is_dir():
|
| 359 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_tsv.parent}")
|
| 360 |
+
|
| 361 |
+
r_script = _build_query_script("exons")
|
| 362 |
+
|
| 363 |
+
args = [f"--db={db_file}", f"--outfile={output_tsv}"]
|
| 364 |
+
if filter_gene_id:
|
| 365 |
+
args.append(f"--filter_gene_id={filter_gene_id}")
|
| 366 |
+
if filter_tx_id:
|
| 367 |
+
args.append(f"--filter_tx_id={filter_tx_id}")
|
| 368 |
+
if filter_exon_id:
|
| 369 |
+
args.append(f"--filter_exon_id={filter_exon_id}")
|
| 370 |
+
|
| 371 |
+
result = _run_r_script(r_script, args)
|
| 372 |
+
result["output_files"] = [str(output_tsv)]
|
| 373 |
+
return result
|
| 374 |
+
|
| 375 |
+
@mcp.tool
|
| 376 |
+
def query_promoters(
|
| 377 |
+
db_file: Path,
|
| 378 |
+
output_tsv: Path,
|
| 379 |
+
upstream: int = 2000,
|
| 380 |
+
downstream: int = 200,
|
| 381 |
+
filter_gene_id: Optional[str] = None,
|
| 382 |
+
filter_tx_id: Optional[str] = None,
|
| 383 |
+
) -> Dict[str, Any]:
|
| 384 |
+
"""
|
| 385 |
+
Extracts promoter regions for transcripts from an EnsemblDB.
|
| 386 |
+
|
| 387 |
+
This tool wraps the `promoters` function from 'ensembldb'. It requires R,
|
| 388 |
+
'ensembldb', and 'AnnotationFilter' packages.
|
| 389 |
+
|
| 390 |
+
Args:
|
| 391 |
+
db_file: Path to the input EnsemblDB SQLite file.
|
| 392 |
+
output_tsv: Path for the output TSV file.
|
| 393 |
+
upstream: The number of bases upstream of the TSS to include.
|
| 394 |
+
downstream: The number of bases downstream of the TSS to include.
|
| 395 |
+
filter_gene_id: Optional filter by Ensembl gene ID.
|
| 396 |
+
filter_tx_id: Optional filter by Ensembl transcript ID.
|
| 397 |
+
|
| 398 |
+
Returns:
|
| 399 |
+
A dictionary containing the command executed, stdout, stderr,
|
| 400 |
+
and a list of output files.
|
| 401 |
+
"""
|
| 402 |
+
if not db_file.is_file():
|
| 403 |
+
raise FileNotFoundError(f"Input database file not found: {db_file}")
|
| 404 |
+
if not output_tsv.parent.is_dir():
|
| 405 |
+
raise NotADirectoryError(f"Output directory does not exist: {output_tsv.parent}")
|
| 406 |
+
if upstream < 0 or downstream < 0:
|
| 407 |
+
raise ValueError("upstream and downstream values must be non-negative.")
|
| 408 |
+
|
| 409 |
+
# The 'promoters' function takes upstream/downstream as direct arguments,
|
| 410 |
+
# so we modify the R script generation slightly.
|
| 411 |
+
extra_r_code = textwrap.dedent(f"""
|
| 412 |
+
up <- as.numeric(params$upstream)
|
| 413 |
+
down <- as.numeric(params$downstream)
|
| 414 |
+
results <- promoters(edb, upstream = up, downstream = down, filter = final_filter)
|
| 415 |
+
""")
|
| 416 |
+
|
| 417 |
+
# We remove the final `results <- ...` line from the base template
|
| 418 |
+
base_script = _build_query_script("", extra_r_code)
|
| 419 |
+
|
| 420 |
+
args = [
|
| 421 |
+
f"--db={db_file}",
|
| 422 |
+
f"--outfile={output_tsv}",
|
| 423 |
+
f"--upstream={upstream}",
|
| 424 |
+
f"--downstream={downstream}"
|
| 425 |
+
]
|
| 426 |
+
if filter_gene_id:
|
| 427 |
+
args.append(f"--filter_gene_id={filter_gene_id}")
|
| 428 |
+
if filter_tx_id:
|
| 429 |
+
args.append(f"--filter_tx_id={filter_tx_id}")
|
| 430 |
+
|
| 431 |
+
result = _run_r_script(base_script, args)
|
| 432 |
+
result["output_files"] = [str(output_tsv)]
|
| 433 |
+
return result
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/bioconductor-ensembldb_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-ensembldb/app/bioconductor-ensembldb_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_ensembldb'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-ensembldb:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-ensembldb:latest
|
| 7 |
+
container_name: mcp-bioconductor-ensembldb
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-ensembldb
|
| 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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/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-ensembldb
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/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-escher via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-escher -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-escher_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-escher_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-escher_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/app/bioconductor-escher_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-escher/app/bioconductor-escher_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_escher'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/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-escher
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_server.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Optional, Dict, Any
|
| 4 |
+
|
| 5 |
+
# MCP decorator placeholder
|
| 6 |
+
def tool(*args, **kwargs):
|
| 7 |
+
def decorator(f):
|
| 8 |
+
return f
|
| 9 |
+
return decorator
|
| 10 |
+
|
| 11 |
+
# In a real MCP environment, the 'mcp' object would be imported.
|
| 12 |
+
# For this standalone script, we define a placeholder.
|
| 13 |
+
class mcp:
|
| 14 |
+
tool = tool
|
| 15 |
+
|
| 16 |
+
@mcp.tool()
|
| 17 |
+
def run_rscript(
|
| 18 |
+
script_file: Optional[Path] = None,
|
| 19 |
+
expressions: Optional[List[str]] = None,
|
| 20 |
+
args: Optional[List[str]] = None,
|
| 21 |
+
verbose: bool = False,
|
| 22 |
+
default_packages: Optional[str] = None,
|
| 23 |
+
save: bool = False,
|
| 24 |
+
no_environ: bool = False,
|
| 25 |
+
no_site_file: bool = False,
|
| 26 |
+
no_init_file: bool = False,
|
| 27 |
+
restore: bool = False,
|
| 28 |
+
vanilla: bool = False,
|
| 29 |
+
) -> Dict[str, Any]:
|
| 30 |
+
"""
|
| 31 |
+
Executes an R script using the Rscript interpreter.
|
| 32 |
+
|
| 33 |
+
This tool is a general-purpose wrapper for the Rscript command-line utility.
|
| 34 |
+
The 'bioconductor-genomeinfodbdata' package is a data-only package and does not
|
| 35 |
+
have its own executable. It is intended to be used within R scripts, which can
|
| 36 |
+
be run with this tool.
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
script_file: Path to the R script file to be executed.
|
| 40 |
+
expressions: A list of R expressions to be executed. Use this or script_file, not both.
|
| 41 |
+
args: A list of arguments to be passed to the R script.
|
| 42 |
+
verbose: Print information on progress (--verbose).
|
| 43 |
+
default_packages: A comma-separated list of package names to be loaded by default (--default-packages).
|
| 44 |
+
save: Save the workspace at the end of the session (--save).
|
| 45 |
+
no_environ: Don't read the site and user environment files (--no-environ).
|
| 46 |
+
no_site_file: Don't read the site-wide Rprofile (--no-site-file).
|
| 47 |
+
no_init_file: Don't read the user R profile (--no-init-file).
|
| 48 |
+
restore: Restore previously saved objects at startup (--restore).
|
| 49 |
+
vanilla: Combine --no-save, --no-restore, --no-site-file, --no-init-file, and --no-environ (--vanilla).
|
| 50 |
+
|
| 51 |
+
Returns:
|
| 52 |
+
A dictionary containing the command executed, stdout, stderr, and a list of output files.
|
| 53 |
+
"""
|
| 54 |
+
# Input validation
|
| 55 |
+
if script_file and expressions:
|
| 56 |
+
raise ValueError("Provide either 'script_file' or 'expressions', not both.")
|
| 57 |
+
if not script_file and not expressions:
|
| 58 |
+
raise ValueError("Either 'script_file' or 'expressions' must be provided.")
|
| 59 |
+
|
| 60 |
+
if script_file:
|
| 61 |
+
if not script_file.is_file():
|
| 62 |
+
raise FileNotFoundError(f"The script file does not exist: {script_file}")
|
| 63 |
+
|
| 64 |
+
cmd = ["Rscript"]
|
| 65 |
+
|
| 66 |
+
# Add R-specific options
|
| 67 |
+
if vanilla:
|
| 68 |
+
cmd.append("--vanilla")
|
| 69 |
+
else:
|
| 70 |
+
if save:
|
| 71 |
+
cmd.append("--save")
|
| 72 |
+
if no_environ:
|
| 73 |
+
cmd.append("--no-environ")
|
| 74 |
+
if no_site_file:
|
| 75 |
+
cmd.append("--no-site-file")
|
| 76 |
+
if no_init_file:
|
| 77 |
+
cmd.append("--no-init-file")
|
| 78 |
+
if restore:
|
| 79 |
+
cmd.append("--restore")
|
| 80 |
+
|
| 81 |
+
if verbose:
|
| 82 |
+
cmd.append("--verbose")
|
| 83 |
+
if default_packages:
|
| 84 |
+
cmd.extend(["--default-packages", default_packages])
|
| 85 |
+
|
| 86 |
+
# Add script file or expressions
|
| 87 |
+
if script_file:
|
| 88 |
+
cmd.append(str(script_file))
|
| 89 |
+
elif expressions:
|
| 90 |
+
for expr in expressions:
|
| 91 |
+
cmd.extend(["-e", expr])
|
| 92 |
+
|
| 93 |
+
# Add script arguments
|
| 94 |
+
if args:
|
| 95 |
+
cmd.extend(args)
|
| 96 |
+
|
| 97 |
+
command_executed = " ".join(cmd)
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
result = subprocess.run(
|
| 101 |
+
cmd,
|
| 102 |
+
capture_output=True,
|
| 103 |
+
text=True,
|
| 104 |
+
check=True,
|
| 105 |
+
)
|
| 106 |
+
stdout = result.stdout
|
| 107 |
+
stderr = result.stderr
|
| 108 |
+
except FileNotFoundError:
|
| 109 |
+
return {
|
| 110 |
+
"command_executed": command_executed,
|
| 111 |
+
"stdout": "",
|
| 112 |
+
"stderr": "Error: Rscript not found. Is R installed and in your PATH?",
|
| 113 |
+
"output_files": []
|
| 114 |
+
}
|
| 115 |
+
except subprocess.CalledProcessError as e:
|
| 116 |
+
return {
|
| 117 |
+
"command_executed": command_executed,
|
| 118 |
+
"stdout": e.stdout,
|
| 119 |
+
"stderr": e.stderr,
|
| 120 |
+
"output_files": []
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
# Since the script's output files are unknown, we return an empty list.
|
| 124 |
+
# The user's R script is responsible for managing its own outputs.
|
| 125 |
+
return {
|
| 126 |
+
"command_executed": command_executed,
|
| 127 |
+
"stdout": stdout,
|
| 128 |
+
"stderr": stderr,
|
| 129 |
+
"output_files": [],
|
| 130 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_shim_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-genomeinfodbdata/app/bioconductor-genomeinfodbdata_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_genomeinfodbdata'
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class _ShimMCP:
|
| 15 |
+
@staticmethod
|
| 16 |
+
def tool():
|
| 17 |
+
def _decorator(fn):
|
| 18 |
+
return fn
|
| 19 |
+
return _decorator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_functions():
|
| 23 |
+
code = SOURCE_SERVER.read_text(encoding="utf-8")
|
| 24 |
+
tree = ast.parse(code, filename=str(SOURCE_SERVER))
|
| 25 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 26 |
+
namespace = {
|
| 27 |
+
"__name__": "__mcp_source__",
|
| 28 |
+
"mcp": _ShimMCP(),
|
| 29 |
+
}
|
| 30 |
+
exec(compile(code, str(SOURCE_SERVER), "exec"), namespace, namespace)
|
| 31 |
+
loaded = []
|
| 32 |
+
for name in function_names:
|
| 33 |
+
fn = namespace.get(name)
|
| 34 |
+
if callable(fn):
|
| 35 |
+
loaded.append(fn)
|
| 36 |
+
return loaded
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
mcp = FastMCP(SERVER_NAME)
|
| 40 |
+
for _fn in _load_functions():
|
| 41 |
+
mcp.tool()(_fn)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport="stdio")
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/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 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/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-genomeinfodbdata
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-go.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-go.db via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-go.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-go.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-go.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-go.db_server.py"]
|
| 40 |
+
|