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_arvados-cwl-runner/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/arvados-cwl-runner_server.py +270 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/arvados-cwl-runner_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/auspice_server.py +398 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/auspice_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/barrnap_server.py +336 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/barrnap_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/bin2cell_server.py +278 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/bin2cell_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocbaseutils/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocbaseutils/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/app/bioconductor-curatedatlasqueryr_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-delayedmatrixstats/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ebseq/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/bioconductor-experimentsubset_server.py +178 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/bioconductor-experimentsubset_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/environment.yaml +10 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/requirements.txt +2 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genefilter/app/requirements.txt +1 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genefilter/docker-compose.yml +22 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/Dockerfile +40 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/app/bioconductor-ggsc_server.py +360 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/app/bioconductor-ggsc_shim_server.py +45 -0
- BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/docker-compose.yml +22 -0
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/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 arvados-cwl-runner via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda arvados-cwl-runner -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/arvados-cwl-runner_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/arvados-cwl-runner_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/arvados-cwl-runner_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/arvados-cwl-runner_server.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List, Union
|
| 5 |
+
|
| 6 |
+
@mcp.tool()
|
| 7 |
+
def arvados_cwl_runner(
|
| 8 |
+
workflow: str,
|
| 9 |
+
job_order: Optional[str] = None,
|
| 10 |
+
project_uuid: Optional[str] = None,
|
| 11 |
+
name: Optional[str] = None,
|
| 12 |
+
output_name: Optional[str] = None,
|
| 13 |
+
local: bool = False,
|
| 14 |
+
api: str = "containers",
|
| 15 |
+
eval_timeout: float = 20.0,
|
| 16 |
+
log_level: str = "INFO",
|
| 17 |
+
enable_reuse: bool = True,
|
| 18 |
+
submit: bool = True,
|
| 19 |
+
wait: bool = True,
|
| 20 |
+
priority: int = 1,
|
| 21 |
+
cluster_id: Optional[str] = None,
|
| 22 |
+
storage_classes: str = "default",
|
| 23 |
+
tmpdir_prefix: Optional[str] = None,
|
| 24 |
+
debug: bool = False,
|
| 25 |
+
):
|
| 26 |
+
"""
|
| 27 |
+
Run a CWL workflow on Arvados or locally.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
workflow: Path to the CWL workflow file (.cwl).
|
| 31 |
+
job_order: Path to the input parameters file (YAML or JSON).
|
| 32 |
+
project_uuid: Arvados project UUID where the workflow should run.
|
| 33 |
+
name: Name for the pipeline instance or container request.
|
| 34 |
+
output_name: Name for the output collection.
|
| 35 |
+
local: Run the workflow locally instead of on the Arvados cluster.
|
| 36 |
+
api: Arvados API to use (containers or jobs). Default is containers.
|
| 37 |
+
eval_timeout: Time to wait for CWL expression evaluation (seconds).
|
| 38 |
+
log_level: Logging level (DEBUG, INFO, WARNING, ERROR).
|
| 39 |
+
enable_reuse: Enable job/container reuse.
|
| 40 |
+
submit: Submit the workflow to Arvados (True) or run it in the foreground (False).
|
| 41 |
+
wait: Wait for the workflow to complete before exiting.
|
| 42 |
+
priority: Workflow priority (1-1000).
|
| 43 |
+
cluster_id: Specific Arvados cluster ID to submit to.
|
| 44 |
+
storage_classes: Comma-separated list of storage classes for outputs.
|
| 45 |
+
tmpdir_prefix: Path prefix for temporary directories.
|
| 46 |
+
debug: Enable debug logging and keep temporary files.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
# Input validation
|
| 50 |
+
workflow_path = Path(workflow)
|
| 51 |
+
if not workflow_path.exists():
|
| 52 |
+
return {"error": f"Workflow file not found: {workflow}"}
|
| 53 |
+
|
| 54 |
+
cmd = ["arvados-cwl-runner"]
|
| 55 |
+
|
| 56 |
+
# Boolean flags
|
| 57 |
+
if local:
|
| 58 |
+
cmd.append("--local")
|
| 59 |
+
if not enable_reuse:
|
| 60 |
+
cmd.append("--disable-reuse")
|
| 61 |
+
if not submit:
|
| 62 |
+
cmd.append("--no-submit")
|
| 63 |
+
if not wait:
|
| 64 |
+
cmd.append("--no-wait")
|
| 65 |
+
if debug:
|
| 66 |
+
cmd.append("--debug")
|
| 67 |
+
|
| 68 |
+
# String/Value parameters
|
| 69 |
+
cmd.extend(["--api", api])
|
| 70 |
+
cmd.extend(["--eval-timeout", str(eval_timeout)])
|
| 71 |
+
cmd.extend(["--log-level", log_level])
|
| 72 |
+
cmd.extend(["--priority", str(priority)])
|
| 73 |
+
cmd.extend(["--collection-storage-classes", storage_classes])
|
| 74 |
+
|
| 75 |
+
if project_uuid:
|
| 76 |
+
cmd.extend(["--project-uuid", project_uuid])
|
| 77 |
+
if name:
|
| 78 |
+
cmd.extend(["--name", name])
|
| 79 |
+
if output_name:
|
| 80 |
+
cmd.extend(["--output-name", output_name])
|
| 81 |
+
if cluster_id:
|
| 82 |
+
cmd.extend(["--cluster-id", cluster_id])
|
| 83 |
+
if tmpdir_prefix:
|
| 84 |
+
cmd.extend(["--tmpdir-prefix", tmpdir_prefix])
|
| 85 |
+
|
| 86 |
+
# Positional arguments
|
| 87 |
+
cmd.append(str(workflow_path))
|
| 88 |
+
|
| 89 |
+
if job_order:
|
| 90 |
+
job_order_path = Path(job_order)
|
| 91 |
+
if not job_order_path.exists():
|
| 92 |
+
return {"error": f"Job order file not found: {job_order}"}
|
| 93 |
+
cmd.append(str(job_order_path))
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 97 |
+
return {
|
| 98 |
+
"command_executed": " ".join(cmd),
|
| 99 |
+
"stdout": result.stdout,
|
| 100 |
+
"stderr": result.stderr,
|
| 101 |
+
"status": "success"
|
| 102 |
+
}
|
| 103 |
+
except subprocess.CalledProcessError as e:
|
| 104 |
+
return {
|
| 105 |
+
"command_executed": " ".join(cmd),
|
| 106 |
+
"stdout": e.stdout,
|
| 107 |
+
"stderr": e.stderr,
|
| 108 |
+
"error": str(e),
|
| 109 |
+
"status": "failed"
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
@mcp.tool()
|
| 113 |
+
def arvados_cwl_validate(
|
| 114 |
+
workflow: str,
|
| 115 |
+
):
|
| 116 |
+
"""
|
| 117 |
+
Validate a CWL workflow file for syntax and Arvados compatibility.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
workflow: Path to the CWL workflow file.
|
| 121 |
+
"""
|
| 122 |
+
workflow_path = Path(workflow)
|
| 123 |
+
if not workflow_path.exists():
|
| 124 |
+
return {"error": f"Workflow file not found: {workflow}"}
|
| 125 |
+
|
| 126 |
+
cmd = ["arvados-cwl-runner", "--validate", str(workflow_path)]
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 130 |
+
return {
|
| 131 |
+
"command_executed": " ".join(cmd),
|
| 132 |
+
"stdout": result.stdout,
|
| 133 |
+
"stderr": result.stderr,
|
| 134 |
+
"status": "valid"
|
| 135 |
+
}
|
| 136 |
+
except subprocess.CalledProcessError as e:
|
| 137 |
+
return {
|
| 138 |
+
"command_executed": " ".join(cmd),
|
| 139 |
+
"stdout": e.stdout,
|
| 140 |
+
"stderr": e.stderr,
|
| 141 |
+
"error": "Validation failed",
|
| 142 |
+
"status": "invalid"
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
@mcp.tool()
|
| 146 |
+
def arvados_cwl_create_workflow(
|
| 147 |
+
workflow: str,
|
| 148 |
+
project_uuid: Optional[str] = None,
|
| 149 |
+
name: Optional[str] = None,
|
| 150 |
+
description: Optional[str] = None,
|
| 151 |
+
):
|
| 152 |
+
"""
|
| 153 |
+
Register a CWL workflow in Arvados as a reusable Workflow object.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
workflow: Path to the CWL workflow file.
|
| 157 |
+
project_uuid: Arvados project UUID where the workflow should be stored.
|
| 158 |
+
name: Name for the workflow object in Arvados.
|
| 159 |
+
description: Description for the workflow.
|
| 160 |
+
"""
|
| 161 |
+
workflow_path = Path(workflow)
|
| 162 |
+
if not workflow_path.exists():
|
| 163 |
+
return {"error": f"Workflow file not found: {workflow}"}
|
| 164 |
+
|
| 165 |
+
cmd = ["arvados-cwl-runner", "--create-workflow"]
|
| 166 |
+
|
| 167 |
+
if project_uuid:
|
| 168 |
+
cmd.extend(["--project-uuid", project_uuid])
|
| 169 |
+
if name:
|
| 170 |
+
cmd.extend(["--name", name])
|
| 171 |
+
if description:
|
| 172 |
+
cmd.extend(["--description", description])
|
| 173 |
+
|
| 174 |
+
cmd.append(str(workflow_path))
|
| 175 |
+
|
| 176 |
+
try:
|
| 177 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 178 |
+
return {
|
| 179 |
+
"command_executed": " ".join(cmd),
|
| 180 |
+
"stdout": result.stdout,
|
| 181 |
+
"stderr": result.stderr,
|
| 182 |
+
"status": "success"
|
| 183 |
+
}
|
| 184 |
+
except subprocess.CalledProcessError as e:
|
| 185 |
+
return {
|
| 186 |
+
"command_executed": " ".join(cmd),
|
| 187 |
+
"stdout": e.stdout,
|
| 188 |
+
"stderr": e.stderr,
|
| 189 |
+
"error": str(e),
|
| 190 |
+
"status": "failed"
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
@mcp.tool()
|
| 194 |
+
def arvados_cwl_update_workflow(
|
| 195 |
+
workflow_uuid: str,
|
| 196 |
+
workflow: str,
|
| 197 |
+
name: Optional[str] = None,
|
| 198 |
+
description: Optional[str] = None,
|
| 199 |
+
):
|
| 200 |
+
"""
|
| 201 |
+
Update an existing Arvados Workflow object with a new CWL definition.
|
| 202 |
+
|
| 203 |
+
Args:
|
| 204 |
+
workflow_uuid: The UUID of the Arvados Workflow object to update.
|
| 205 |
+
workflow: Path to the new CWL workflow file.
|
| 206 |
+
name: New name for the workflow object.
|
| 207 |
+
description: New description for the workflow.
|
| 208 |
+
"""
|
| 209 |
+
workflow_path = Path(workflow)
|
| 210 |
+
if not workflow_path.exists():
|
| 211 |
+
return {"error": f"Workflow file not found: {workflow}"}
|
| 212 |
+
|
| 213 |
+
cmd = ["arvados-cwl-runner", "--update-workflow", workflow_uuid]
|
| 214 |
+
|
| 215 |
+
if name:
|
| 216 |
+
cmd.extend(["--name", name])
|
| 217 |
+
if description:
|
| 218 |
+
cmd.extend(["--description", description])
|
| 219 |
+
|
| 220 |
+
cmd.append(str(workflow_path))
|
| 221 |
+
|
| 222 |
+
try:
|
| 223 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 224 |
+
return {
|
| 225 |
+
"command_executed": " ".join(cmd),
|
| 226 |
+
"stdout": result.stdout,
|
| 227 |
+
"stderr": result.stderr,
|
| 228 |
+
"status": "success"
|
| 229 |
+
}
|
| 230 |
+
except subprocess.CalledProcessError as e:
|
| 231 |
+
return {
|
| 232 |
+
"command_executed": " ".join(cmd),
|
| 233 |
+
"stdout": e.stdout,
|
| 234 |
+
"stderr": e.stderr,
|
| 235 |
+
"error": str(e),
|
| 236 |
+
"status": "failed"
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
@mcp.tool()
|
| 240 |
+
def arvados_cwl_create_template(
|
| 241 |
+
workflow: str,
|
| 242 |
+
):
|
| 243 |
+
"""
|
| 244 |
+
Create an Arvados Pipeline Template from a CWL workflow (legacy API).
|
| 245 |
+
|
| 246 |
+
Args:
|
| 247 |
+
workflow: Path to the CWL workflow file.
|
| 248 |
+
"""
|
| 249 |
+
workflow_path = Path(workflow)
|
| 250 |
+
if not workflow_path.exists():
|
| 251 |
+
return {"error": f"Workflow file not found: {workflow}"}
|
| 252 |
+
|
| 253 |
+
cmd = ["arvados-cwl-runner", "--create-template", str(workflow_path)]
|
| 254 |
+
|
| 255 |
+
try:
|
| 256 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 257 |
+
return {
|
| 258 |
+
"command_executed": " ".join(cmd),
|
| 259 |
+
"stdout": result.stdout,
|
| 260 |
+
"stderr": result.stderr,
|
| 261 |
+
"status": "success"
|
| 262 |
+
}
|
| 263 |
+
except subprocess.CalledProcessError as e:
|
| 264 |
+
return {
|
| 265 |
+
"command_executed": " ".join(cmd),
|
| 266 |
+
"stdout": e.stdout,
|
| 267 |
+
"stderr": e.stderr,
|
| 268 |
+
"error": str(e),
|
| 269 |
+
"status": "failed"
|
| 270 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/arvados-cwl-runner_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_arvados-cwl-runner/app/arvados-cwl-runner_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_arvados_cwl_runner'
|
| 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_arvados-cwl-runner/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-arvados-cwl-runner:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-arvados-cwl-runner:latest
|
| 7 |
+
container_name: mcp-arvados-cwl-runner
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=arvados-cwl-runner
|
| 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_arvados-cwl-runner/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- arvados-cwl-runner
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/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 auspice via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda auspice -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/auspice_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/auspice_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/auspice_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/auspice_server.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Dict, List, Optional, Any
|
| 4 |
+
|
| 5 |
+
# This is a placeholder for the MCP decorator.
|
| 6 |
+
# In a real MCP environment, this would be provided by the MCP framework.
|
| 7 |
+
def tool():
|
| 8 |
+
def decorator(f):
|
| 9 |
+
return f
|
| 10 |
+
return decorator
|
| 11 |
+
|
| 12 |
+
mcp = type("mcp", (), {"tool": tool})()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@mcp.tool()
|
| 16 |
+
def view(
|
| 17 |
+
dataset_dir: Optional[List[Path]] = None,
|
| 18 |
+
host: str = "localhost",
|
| 19 |
+
port: int = 4000,
|
| 20 |
+
verbose: bool = False,
|
| 21 |
+
allow_remote_access: bool = False,
|
| 22 |
+
handlers: Optional[Path] = None,
|
| 23 |
+
config: Optional[Path] = None,
|
| 24 |
+
extend: Optional[Path] = None,
|
| 25 |
+
) -> Dict[str, str]:
|
| 26 |
+
"""
|
| 27 |
+
Starts the Auspice server to view and explore phylogenomic data.
|
| 28 |
+
Note: This command starts a long-running server process and may not be suitable
|
| 29 |
+
for automated workflows that expect a command to terminate.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
dataset_dir: Directory of datasets to serve. Can be specified multiple times.
|
| 33 |
+
host: Host IP address to listen on.
|
| 34 |
+
port: Port to listen on.
|
| 35 |
+
verbose: Print more information to the console.
|
| 36 |
+
allow_remote_access: Allow remote connections to the server.
|
| 37 |
+
handlers: Path to a Javascript file with custom API handlers.
|
| 38 |
+
config: Path to a custom config JSON file.
|
| 39 |
+
extend: Path to a directory containing custom client code.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
A dictionary containing the command executed, stdout, and stderr.
|
| 43 |
+
"""
|
| 44 |
+
cmd = ["auspice", "view"]
|
| 45 |
+
|
| 46 |
+
if dataset_dir:
|
| 47 |
+
for d in dataset_dir:
|
| 48 |
+
if not d.is_dir():
|
| 49 |
+
raise ValueError(f"Dataset directory not found: {d}")
|
| 50 |
+
cmd.extend(["--datasetDir", str(d)])
|
| 51 |
+
|
| 52 |
+
cmd.extend(["--host", host])
|
| 53 |
+
cmd.extend(["--port", str(port)])
|
| 54 |
+
|
| 55 |
+
if verbose:
|
| 56 |
+
cmd.append("--verbose")
|
| 57 |
+
if allow_remote_access:
|
| 58 |
+
cmd.append("--allow-remote-access")
|
| 59 |
+
|
| 60 |
+
if handlers:
|
| 61 |
+
if not handlers.is_file():
|
| 62 |
+
raise FileNotFoundError(f"Handlers file not found: {handlers}")
|
| 63 |
+
cmd.extend(["--handlers", str(handlers)])
|
| 64 |
+
|
| 65 |
+
if config:
|
| 66 |
+
if not config.is_file():
|
| 67 |
+
raise FileNotFoundError(f"Config file not found: {config}")
|
| 68 |
+
cmd.extend(["--config", str(config)])
|
| 69 |
+
|
| 70 |
+
if extend:
|
| 71 |
+
if not extend.is_dir():
|
| 72 |
+
raise ValueError(f"Extend directory not found: {extend}")
|
| 73 |
+
cmd.extend(["--extend", str(extend)])
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# This will block until the server is manually stopped.
|
| 77 |
+
result = subprocess.run(
|
| 78 |
+
cmd, capture_output=True, text=True, check=True
|
| 79 |
+
)
|
| 80 |
+
return {
|
| 81 |
+
"command_executed": " ".join(cmd),
|
| 82 |
+
"stdout": result.stdout,
|
| 83 |
+
"stderr": result.stderr,
|
| 84 |
+
}
|
| 85 |
+
except FileNotFoundError:
|
| 86 |
+
return {
|
| 87 |
+
"command_executed": " ".join(cmd),
|
| 88 |
+
"stdout": "",
|
| 89 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 90 |
+
}
|
| 91 |
+
except subprocess.CalledProcessError as e:
|
| 92 |
+
return {
|
| 93 |
+
"command_executed": " ".join(cmd),
|
| 94 |
+
"stdout": e.stdout,
|
| 95 |
+
"stderr": e.stderr,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@mcp.tool()
|
| 100 |
+
def build(
|
| 101 |
+
verbose: bool = False,
|
| 102 |
+
extend: Optional[Path] = None,
|
| 103 |
+
deploy_path: Optional[str] = None,
|
| 104 |
+
) -> Dict[str, Any]:
|
| 105 |
+
"""
|
| 106 |
+
Creates a production bundle of the Auspice client-side app.
|
| 107 |
+
The output is typically created in a './dist' directory.
|
| 108 |
+
|
| 109 |
+
Args:
|
| 110 |
+
verbose: Print more information to the console.
|
| 111 |
+
extend: Path to a directory containing custom client code.
|
| 112 |
+
deploy_path: Path to deploy the app to (e.g., for GitHub pages).
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
A dictionary containing the command executed, stdout, stderr, and output files.
|
| 116 |
+
"""
|
| 117 |
+
cmd = ["auspice", "build"]
|
| 118 |
+
|
| 119 |
+
if verbose:
|
| 120 |
+
cmd.append("--verbose")
|
| 121 |
+
|
| 122 |
+
if extend:
|
| 123 |
+
if not extend.is_dir():
|
| 124 |
+
raise ValueError(f"Extend directory not found: {extend}")
|
| 125 |
+
cmd.extend(["--extend", str(extend)])
|
| 126 |
+
|
| 127 |
+
if deploy_path:
|
| 128 |
+
cmd.extend(["--deploy-path", deploy_path])
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
result = subprocess.run(
|
| 132 |
+
cmd, capture_output=True, text=True, check=True
|
| 133 |
+
)
|
| 134 |
+
output_dir = Path("./dist")
|
| 135 |
+
return {
|
| 136 |
+
"command_executed": " ".join(cmd),
|
| 137 |
+
"stdout": result.stdout,
|
| 138 |
+
"stderr": result.stderr,
|
| 139 |
+
"output_files": {
|
| 140 |
+
"build_directory": str(output_dir) if output_dir.exists() else "Not created"
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
except FileNotFoundError:
|
| 144 |
+
return {
|
| 145 |
+
"command_executed": " ".join(cmd),
|
| 146 |
+
"stdout": "",
|
| 147 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 148 |
+
"output_files": {}
|
| 149 |
+
}
|
| 150 |
+
except subprocess.CalledProcessError as e:
|
| 151 |
+
return {
|
| 152 |
+
"command_executed": " ".join(cmd),
|
| 153 |
+
"stdout": e.stdout,
|
| 154 |
+
"stderr": e.stderr,
|
| 155 |
+
"output_files": {}
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@mcp.tool()
|
| 160 |
+
def export_v1(
|
| 161 |
+
dataset_dir: Path,
|
| 162 |
+
output_dir: Path,
|
| 163 |
+
verbose: bool = False,
|
| 164 |
+
) -> Dict[str, Any]:
|
| 165 |
+
"""
|
| 166 |
+
Exports auspice v1 JSONs to create a static site.
|
| 167 |
+
This is a deprecated command and will be removed in a future version.
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
dataset_dir: Directory of datasets to export.
|
| 171 |
+
output_dir: Directory to export the auspice client and datasets to.
|
| 172 |
+
verbose: Print more information to the console.
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
A dictionary containing the command executed, stdout, stderr, and output directory.
|
| 176 |
+
"""
|
| 177 |
+
cmd = ["auspice", "export", "v1"]
|
| 178 |
+
|
| 179 |
+
if not dataset_dir.is_dir():
|
| 180 |
+
raise ValueError(f"Dataset directory not found: {dataset_dir}")
|
| 181 |
+
cmd.extend(["--dataset-dir", str(dataset_dir)])
|
| 182 |
+
|
| 183 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 184 |
+
cmd.extend(["--output-dir", str(output_dir)])
|
| 185 |
+
|
| 186 |
+
if verbose:
|
| 187 |
+
cmd.append("--verbose")
|
| 188 |
+
|
| 189 |
+
try:
|
| 190 |
+
result = subprocess.run(
|
| 191 |
+
cmd, capture_output=True, text=True, check=True
|
| 192 |
+
)
|
| 193 |
+
return {
|
| 194 |
+
"command_executed": " ".join(cmd),
|
| 195 |
+
"stdout": result.stdout,
|
| 196 |
+
"stderr": result.stderr,
|
| 197 |
+
"output_files": {
|
| 198 |
+
"export_directory": str(output_dir)
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
except FileNotFoundError:
|
| 202 |
+
return {
|
| 203 |
+
"command_executed": " ".join(cmd),
|
| 204 |
+
"stdout": "",
|
| 205 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 206 |
+
"output_files": {}
|
| 207 |
+
}
|
| 208 |
+
except subprocess.CalledProcessError as e:
|
| 209 |
+
return {
|
| 210 |
+
"command_executed": " ".join(cmd),
|
| 211 |
+
"stdout": e.stdout,
|
| 212 |
+
"stderr": e.stderr,
|
| 213 |
+
"output_files": {}
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
@mcp.tool()
|
| 218 |
+
def export_v2(
|
| 219 |
+
dataset_dir: Path,
|
| 220 |
+
output_dir: Path,
|
| 221 |
+
verbose: bool = False,
|
| 222 |
+
config: Optional[Path] = None,
|
| 223 |
+
extend: Optional[Path] = None,
|
| 224 |
+
deploy_path: Optional[str] = None,
|
| 225 |
+
) -> Dict[str, Any]:
|
| 226 |
+
"""
|
| 227 |
+
Exports auspice v2 JSONs to create a static site.
|
| 228 |
+
|
| 229 |
+
Args:
|
| 230 |
+
dataset_dir: Directory of datasets to export.
|
| 231 |
+
output_dir: Directory to export the auspice client and datasets to.
|
| 232 |
+
verbose: Print more information to the console.
|
| 233 |
+
config: Path to a custom config JSON file.
|
| 234 |
+
extend: Path to a directory containing custom client code.
|
| 235 |
+
deploy_path: Path to deploy the app to (e.g., for GitHub pages).
|
| 236 |
+
|
| 237 |
+
Returns:
|
| 238 |
+
A dictionary containing the command executed, stdout, stderr, and output directory.
|
| 239 |
+
"""
|
| 240 |
+
cmd = ["auspice", "export", "v2"]
|
| 241 |
+
|
| 242 |
+
if not dataset_dir.is_dir():
|
| 243 |
+
raise ValueError(f"Dataset directory not found: {dataset_dir}")
|
| 244 |
+
cmd.extend(["--dataset-dir", str(dataset_dir)])
|
| 245 |
+
|
| 246 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 247 |
+
cmd.extend(["--output-dir", str(output_dir)])
|
| 248 |
+
|
| 249 |
+
if verbose:
|
| 250 |
+
cmd.append("--verbose")
|
| 251 |
+
|
| 252 |
+
if config:
|
| 253 |
+
if not config.is_file():
|
| 254 |
+
raise FileNotFoundError(f"Config file not found: {config}")
|
| 255 |
+
cmd.extend(["--config", str(config)])
|
| 256 |
+
|
| 257 |
+
if extend:
|
| 258 |
+
if not extend.is_dir():
|
| 259 |
+
raise ValueError(f"Extend directory not found: {extend}")
|
| 260 |
+
cmd.extend(["--extend", str(extend)])
|
| 261 |
+
|
| 262 |
+
if deploy_path:
|
| 263 |
+
cmd.extend(["--deploy-path", deploy_path])
|
| 264 |
+
|
| 265 |
+
try:
|
| 266 |
+
result = subprocess.run(
|
| 267 |
+
cmd, capture_output=True, text=True, check=True
|
| 268 |
+
)
|
| 269 |
+
return {
|
| 270 |
+
"command_executed": " ".join(cmd),
|
| 271 |
+
"stdout": result.stdout,
|
| 272 |
+
"stderr": result.stderr,
|
| 273 |
+
"output_files": {
|
| 274 |
+
"export_directory": str(output_dir)
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
except FileNotFoundError:
|
| 278 |
+
return {
|
| 279 |
+
"command_executed": " ".join(cmd),
|
| 280 |
+
"stdout": "",
|
| 281 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 282 |
+
"output_files": {}
|
| 283 |
+
}
|
| 284 |
+
except subprocess.CalledProcessError as e:
|
| 285 |
+
return {
|
| 286 |
+
"command_executed": " ".join(cmd),
|
| 287 |
+
"stdout": e.stdout,
|
| 288 |
+
"stderr": e.stderr,
|
| 289 |
+
"output_files": {}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
@mcp.tool()
|
| 294 |
+
def develop(
|
| 295 |
+
verbose: bool = False,
|
| 296 |
+
extend: Optional[Path] = None,
|
| 297 |
+
config: Optional[Path] = None,
|
| 298 |
+
dataset_dir: Optional[List[Path]] = None,
|
| 299 |
+
port: int = 4000,
|
| 300 |
+
host: str = "localhost",
|
| 301 |
+
) -> Dict[str, str]:
|
| 302 |
+
"""
|
| 303 |
+
Starts the Auspice development server with hot-reloading.
|
| 304 |
+
Note: This command starts a long-running server process and may not be suitable
|
| 305 |
+
for automated workflows that expect a command to terminate.
|
| 306 |
+
|
| 307 |
+
Args:
|
| 308 |
+
verbose: Print more information to the console.
|
| 309 |
+
extend: Path to a directory containing custom client code.
|
| 310 |
+
config: Path to a custom config JSON file.
|
| 311 |
+
dataset_dir: Directory of datasets to serve. Can be specified multiple times.
|
| 312 |
+
port: Port to listen on.
|
| 313 |
+
host: Host IP address to listen on.
|
| 314 |
+
|
| 315 |
+
Returns:
|
| 316 |
+
A dictionary containing the command executed, stdout, and stderr.
|
| 317 |
+
"""
|
| 318 |
+
cmd = ["auspice", "develop"]
|
| 319 |
+
|
| 320 |
+
if verbose:
|
| 321 |
+
cmd.append("--verbose")
|
| 322 |
+
|
| 323 |
+
if extend:
|
| 324 |
+
if not extend.is_dir():
|
| 325 |
+
raise ValueError(f"Extend directory not found: {extend}")
|
| 326 |
+
cmd.extend(["--extend", str(extend)])
|
| 327 |
+
|
| 328 |
+
if config:
|
| 329 |
+
if not config.is_file():
|
| 330 |
+
raise FileNotFoundError(f"Config file not found: {config}")
|
| 331 |
+
cmd.extend(["--config", str(config)])
|
| 332 |
+
|
| 333 |
+
if dataset_dir:
|
| 334 |
+
for d in dataset_dir:
|
| 335 |
+
if not d.is_dir():
|
| 336 |
+
raise ValueError(f"Dataset directory not found: {d}")
|
| 337 |
+
cmd.extend(["--datasetDir", str(d)])
|
| 338 |
+
|
| 339 |
+
cmd.extend(["--port", str(port)])
|
| 340 |
+
cmd.extend(["--host", host])
|
| 341 |
+
|
| 342 |
+
try:
|
| 343 |
+
# This will block until the server is manually stopped.
|
| 344 |
+
result = subprocess.run(
|
| 345 |
+
cmd, capture_output=True, text=True, check=True
|
| 346 |
+
)
|
| 347 |
+
return {
|
| 348 |
+
"command_executed": " ".join(cmd),
|
| 349 |
+
"stdout": result.stdout,
|
| 350 |
+
"stderr": result.stderr,
|
| 351 |
+
}
|
| 352 |
+
except FileNotFoundError:
|
| 353 |
+
return {
|
| 354 |
+
"command_executed": " ".join(cmd),
|
| 355 |
+
"stdout": "",
|
| 356 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 357 |
+
}
|
| 358 |
+
except subprocess.CalledProcessError as e:
|
| 359 |
+
return {
|
| 360 |
+
"command_executed": " ".join(cmd),
|
| 361 |
+
"stdout": e.stdout,
|
| 362 |
+
"stderr": e.stderr,
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
@mcp.tool()
|
| 367 |
+
def version() -> Dict[str, str]:
|
| 368 |
+
"""
|
| 369 |
+
Prints the version number of auspice and exits.
|
| 370 |
+
|
| 371 |
+
Returns:
|
| 372 |
+
A dictionary containing the command executed, stdout, stderr, and parsed version.
|
| 373 |
+
"""
|
| 374 |
+
cmd = ["auspice", "version"]
|
| 375 |
+
try:
|
| 376 |
+
result = subprocess.run(
|
| 377 |
+
cmd, capture_output=True, text=True, check=True
|
| 378 |
+
)
|
| 379 |
+
return {
|
| 380 |
+
"command_executed": " ".join(cmd),
|
| 381 |
+
"stdout": result.stdout,
|
| 382 |
+
"stderr": result.stderr,
|
| 383 |
+
"version": result.stdout.strip(),
|
| 384 |
+
}
|
| 385 |
+
except FileNotFoundError:
|
| 386 |
+
return {
|
| 387 |
+
"command_executed": " ".join(cmd),
|
| 388 |
+
"stdout": "",
|
| 389 |
+
"stderr": "Error: 'auspice' command not found. Please ensure it is installed and in your PATH.",
|
| 390 |
+
"version": "",
|
| 391 |
+
}
|
| 392 |
+
except subprocess.CalledProcessError as e:
|
| 393 |
+
return {
|
| 394 |
+
"command_executed": " ".join(cmd),
|
| 395 |
+
"stdout": e.stdout,
|
| 396 |
+
"stderr": e.stderr,
|
| 397 |
+
"version": "",
|
| 398 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/auspice_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_auspice/app/auspice_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_auspice'
|
| 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_auspice/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-auspice:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-auspice:latest
|
| 7 |
+
container_name: mcp-auspice
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=auspice
|
| 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_auspice/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- auspice
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/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 barrnap via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda barrnap -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/barrnap_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/barrnap_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/barrnap_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/barrnap_server.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# Helper function to execute barrnap commands and handle output.
|
| 7 |
+
# This function is internal and not exposed as an MCP tool.
|
| 8 |
+
def _run_barrnap_command(
|
| 9 |
+
command_args: List[str],
|
| 10 |
+
output_gff_path: Optional[Path] = None,
|
| 11 |
+
debug: bool = False,
|
| 12 |
+
quiet: bool = False,
|
| 13 |
+
) -> dict:
|
| 14 |
+
"""
|
| 15 |
+
Internal helper to execute barrnap commands.
|
| 16 |
+
Captures stdout, stderr, and handles CalledProcessError.
|
| 17 |
+
If output_gff_path is provided, stdout is written to that file.
|
| 18 |
+
"""
|
| 19 |
+
cmd = ["barrnap"] + command_args
|
| 20 |
+
|
| 21 |
+
if debug:
|
| 22 |
+
cmd.append("--debug")
|
| 23 |
+
if quiet:
|
| 24 |
+
cmd.append("--quiet")
|
| 25 |
+
|
| 26 |
+
stdout_capture = ""
|
| 27 |
+
stderr_capture = ""
|
| 28 |
+
output_files_generated = []
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
# barrnap writes GFF to stdout by default.
|
| 32 |
+
# If output_gff_path is provided, we capture stdout and write it to the file.
|
| 33 |
+
process = subprocess.run(
|
| 34 |
+
cmd,
|
| 35 |
+
capture_output=True,
|
| 36 |
+
text=True,
|
| 37 |
+
check=True
|
| 38 |
+
)
|
| 39 |
+
stdout_capture = process.stdout
|
| 40 |
+
stderr_capture = process.stderr
|
| 41 |
+
|
| 42 |
+
if output_gff_path:
|
| 43 |
+
output_gff_path.write_text(stdout_capture)
|
| 44 |
+
output_files_generated.append(str(output_gff_path))
|
| 45 |
+
stdout_capture = f"GFF output written to {output_gff_path}"
|
| 46 |
+
|
| 47 |
+
except subprocess.CalledProcessError as e:
|
| 48 |
+
return {
|
| 49 |
+
"command_executed": " ".join(e.cmd),
|
| 50 |
+
"stdout": e.stdout,
|
| 51 |
+
"stderr": e.stderr,
|
| 52 |
+
"error": str(e),
|
| 53 |
+
"returncode": e.returncode,
|
| 54 |
+
"output_files": [],
|
| 55 |
+
}
|
| 56 |
+
except FileNotFoundError:
|
| 57 |
+
return {
|
| 58 |
+
"command_executed": " ".join(cmd),
|
| 59 |
+
"stdout": "",
|
| 60 |
+
"stderr": "barrnap command not found. Please ensure barrnap is installed and in your PATH.",
|
| 61 |
+
"error": "barrnap not found",
|
| 62 |
+
"returncode": 127,
|
| 63 |
+
"output_files": [],
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"command_executed": " ".join(cmd),
|
| 68 |
+
"stdout": stdout_capture,
|
| 69 |
+
"stderr": stderr_capture,
|
| 70 |
+
"output_files": output_files_generated,
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
@mcp.tool()
|
| 74 |
+
def barrnap_annotate(
|
| 75 |
+
fasta_file: Path,
|
| 76 |
+
output_gff_file: Path,
|
| 77 |
+
kingdom: Optional[str] = "bac",
|
| 78 |
+
enable_all_rna: bool = False,
|
| 79 |
+
disable_rrna: bool = False,
|
| 80 |
+
enable_trna: bool = False,
|
| 81 |
+
enable_ncrna: bool = False,
|
| 82 |
+
enable_mrna: bool = False,
|
| 83 |
+
threads: int = 1,
|
| 84 |
+
fast: bool = False,
|
| 85 |
+
evalue: Optional[float] = None,
|
| 86 |
+
incseq: bool = False,
|
| 87 |
+
incseqreg: bool = False,
|
| 88 |
+
outseq_file: Optional[Path] = None,
|
| 89 |
+
add_ids: bool = False,
|
| 90 |
+
db_directory: Optional[Path] = None,
|
| 91 |
+
debug: bool = False,
|
| 92 |
+
quiet: bool = False,
|
| 93 |
+
) -> dict:
|
| 94 |
+
"""
|
| 95 |
+
Annotates RNA features (rRNA, tRNA, tmRNA, ncRNA, mRNA) in microbial genomes
|
| 96 |
+
(bacteria, archaea, fungi) from an input FASTA file.
|
| 97 |
+
Outputs results in GFF3 format to the specified output file.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
fasta_file: Path to the input FASTA file containing genomic sequences.
|
| 101 |
+
output_gff_file: Path to the output GFF3 file where annotations will be written.
|
| 102 |
+
kingdom: The database to use for annotation. Choices: "bac" (Bacteria),
|
| 103 |
+
"arc" (Archaea), "fun" (Fungi). Defaults to "bac".
|
| 104 |
+
enable_all_rna: If True, enables scanning for all RNA types (rRNA, tRNA, tmRNA, ncRNA, mRNA).
|
| 105 |
+
This is equivalent to `--all`.
|
| 106 |
+
disable_rrna: If True, disables rRNA scanning. This is equivalent to `--no-rrna`.
|
| 107 |
+
enable_trna: If True, enables tRNA scanning. This is equivalent to `--trna`.
|
| 108 |
+
enable_ncrna: If True, enables ncRNA scanning. This is equivalent to `--ncrna`.
|
| 109 |
+
enable_mrna: If True, enables mRNA scanning (including CDS, RBS, sig_pep, terminator).
|
| 110 |
+
This is equivalent to `--mrna`.
|
| 111 |
+
threads: Number of CPUs to use for the search. Must be at least 1. Defaults to 1.
|
| 112 |
+
fast: If True, uses simpler HMMs instead of CMs, which is faster but less accurate.
|
| 113 |
+
This is equivalent to `--fast`.
|
| 114 |
+
evalue: E-value cutoff for hits to keep. Must be greater than 0 if provided.
|
| 115 |
+
This is equivalent to `--evalue`.
|
| 116 |
+
incseq: If True, includes the full input sequences in the output GFF.
|
| 117 |
+
This is equivalent to `--incseq`.
|
| 118 |
+
incseqreg: If True, includes `##sequence-region` headers in the GFF.
|
| 119 |
+
This is equivalent to `--incseqreg`.
|
| 120 |
+
outseq_file: Path to a FASTA file where hit sequences will be written.
|
| 121 |
+
This is equivalent to `--outseq`.
|
| 122 |
+
add_ids: If True, adds unique ID= tags to each GFF3 feature.
|
| 123 |
+
This is equivalent to `--addids`.
|
| 124 |
+
db_directory: Path to a different database folder to use.
|
| 125 |
+
This is equivalent to `--dbdir`.
|
| 126 |
+
debug: If True, writes all temporary files to '.' and prints debug information.
|
| 127 |
+
This is equivalent to `--debug`.
|
| 128 |
+
quiet: If True, suppresses all messages to stderr. This is equivalent to `--quiet`.
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
A dictionary containing:
|
| 132 |
+
- "command_executed": The full command string executed.
|
| 133 |
+
- "stdout": Standard output from the tool.
|
| 134 |
+
- "stderr": Standard error from the tool.
|
| 135 |
+
- "output_files": A list of paths to generated output files (GFF3 and optional FASTA).
|
| 136 |
+
"""
|
| 137 |
+
# Input validation
|
| 138 |
+
if not fasta_file.is_file():
|
| 139 |
+
raise ValueError(f"Input FASTA file not found: {fasta_file}")
|
| 140 |
+
if not output_gff_file.parent.is_dir():
|
| 141 |
+
raise ValueError(f"Output GFF directory does not exist: {output_gff_file.parent}")
|
| 142 |
+
|
| 143 |
+
valid_kingdoms = {"bac", "arc", "fun"}
|
| 144 |
+
if kingdom is not None and kingdom not in valid_kingdoms:
|
| 145 |
+
raise ValueError(f"Invalid kingdom: '{kingdom}'. Must be one of {', '.join(valid_kingdoms)}.")
|
| 146 |
+
|
| 147 |
+
if threads < 1:
|
| 148 |
+
raise ValueError(f"Number of threads must be at least 1, got {threads}.")
|
| 149 |
+
|
| 150 |
+
if evalue is not None and evalue <= 0:
|
| 151 |
+
raise ValueError(f"E-value cutoff must be greater than 0, got {evalue}.")
|
| 152 |
+
|
| 153 |
+
if outseq_file and not outseq_file.parent.is_dir():
|
| 154 |
+
raise ValueError(f"Output FASTA directory for hit sequences does not exist: {outseq_file.parent}")
|
| 155 |
+
|
| 156 |
+
if db_directory and not db_directory.is_dir():
|
| 157 |
+
raise ValueError(f"Database directory not found: {db_directory}")
|
| 158 |
+
|
| 159 |
+
command_args = [str(fasta_file)]
|
| 160 |
+
|
| 161 |
+
# Database management
|
| 162 |
+
if db_directory:
|
| 163 |
+
command_args.extend(["--dbdir", str(db_directory)])
|
| 164 |
+
|
| 165 |
+
# Search options
|
| 166 |
+
if kingdom:
|
| 167 |
+
command_args.extend(["--kingdom", kingdom])
|
| 168 |
+
if enable_all_rna:
|
| 169 |
+
command_args.append("--all")
|
| 170 |
+
if disable_rrna:
|
| 171 |
+
command_args.append("--no-rrna")
|
| 172 |
+
if enable_trna:
|
| 173 |
+
command_args.append("--trna")
|
| 174 |
+
if enable_ncrna:
|
| 175 |
+
command_args.append("--ncrna")
|
| 176 |
+
if enable_mrna:
|
| 177 |
+
command_args.append("--mrna")
|
| 178 |
+
|
| 179 |
+
# Speed options
|
| 180 |
+
if threads > 1: # barrnap default is 1 thread, so only add if > 1
|
| 181 |
+
command_args.extend(["--threads", str(threads)])
|
| 182 |
+
if fast:
|
| 183 |
+
command_args.append("--fast")
|
| 184 |
+
|
| 185 |
+
# Filtering options
|
| 186 |
+
if evalue is not None:
|
| 187 |
+
command_args.extend(["--evalue", str(evalue)])
|
| 188 |
+
|
| 189 |
+
# Output options
|
| 190 |
+
if incseq:
|
| 191 |
+
command_args.append("--incseq")
|
| 192 |
+
if incseqreg:
|
| 193 |
+
command_args.append("--incseqreg")
|
| 194 |
+
if outseq_file:
|
| 195 |
+
command_args.extend(["--outseq", str(outseq_file)])
|
| 196 |
+
if add_ids:
|
| 197 |
+
command_args.append("--addids")
|
| 198 |
+
|
| 199 |
+
result = _run_barrnap_command(
|
| 200 |
+
command_args=command_args,
|
| 201 |
+
output_gff_path=output_gff_file,
|
| 202 |
+
debug=debug,
|
| 203 |
+
quiet=quiet,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
# Add outseq_file to output_files if it was generated by barrnap
|
| 207 |
+
if outseq_file and outseq_file.exists() and str(outseq_file) not in result["output_files"]:
|
| 208 |
+
result["output_files"].append(str(outseq_file))
|
| 209 |
+
|
| 210 |
+
return result
|
| 211 |
+
|
| 212 |
+
@mcp.tool()
|
| 213 |
+
def barrnap_list_databases(
|
| 214 |
+
db_directory: Optional[Path] = None,
|
| 215 |
+
debug: bool = False,
|
| 216 |
+
quiet: bool = False,
|
| 217 |
+
) -> dict:
|
| 218 |
+
"""
|
| 219 |
+
Lists the installed barrnap databases and their contents.
|
| 220 |
+
|
| 221 |
+
Args:
|
| 222 |
+
db_directory: Path to a different database folder to use.
|
| 223 |
+
This is equivalent to `--dbdir`.
|
| 224 |
+
debug: If True, writes all temporary files to '.' and prints debug information.
|
| 225 |
+
This is equivalent to `--debug`.
|
| 226 |
+
quiet: If True, suppresses all messages to stderr. This is equivalent to `--quiet`.
|
| 227 |
+
|
| 228 |
+
Returns:
|
| 229 |
+
A dictionary containing:
|
| 230 |
+
- "command_executed": The full command string executed.
|
| 231 |
+
- "stdout": Standard output from the tool, listing databases.
|
| 232 |
+
- "stderr": Standard error from the tool.
|
| 233 |
+
- "output_files": An empty list, as no files are generated.
|
| 234 |
+
"""
|
| 235 |
+
command_args = ["--listdb"]
|
| 236 |
+
|
| 237 |
+
if db_directory:
|
| 238 |
+
if not db_directory.is_dir():
|
| 239 |
+
raise ValueError(f"Database directory not found: {db_directory}")
|
| 240 |
+
command_args.extend(["--dbdir", str(db_directory)])
|
| 241 |
+
|
| 242 |
+
return _run_barrnap_command(
|
| 243 |
+
command_args=command_args,
|
| 244 |
+
debug=debug,
|
| 245 |
+
quiet=quiet,
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
@mcp.tool()
|
| 249 |
+
def barrnap_update_databases(
|
| 250 |
+
db_directory: Optional[Path] = None,
|
| 251 |
+
debug: bool = False,
|
| 252 |
+
quiet: bool = False,
|
| 253 |
+
) -> dict:
|
| 254 |
+
"""
|
| 255 |
+
Updates barrnap databases from the internet.
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
db_directory: Path to a different database folder to use.
|
| 259 |
+
This is equivalent to `--dbdir`.
|
| 260 |
+
debug: If True, writes all temporary files to '.' and prints debug information.
|
| 261 |
+
This is equivalent to `--debug`.
|
| 262 |
+
quiet: If True, suppresses all messages to stderr. This is equivalent to `--quiet`.
|
| 263 |
+
|
| 264 |
+
Returns:
|
| 265 |
+
A dictionary containing:
|
| 266 |
+
- "command_executed": The full command string executed.
|
| 267 |
+
- "stdout": Standard output from the tool, typically update messages.
|
| 268 |
+
- "stderr": Standard error from the tool.
|
| 269 |
+
- "output_files": An empty list, as no files are generated.
|
| 270 |
+
"""
|
| 271 |
+
command_args = ["--updatedb"]
|
| 272 |
+
|
| 273 |
+
if db_directory:
|
| 274 |
+
if not db_directory.is_dir():
|
| 275 |
+
raise ValueError(f"Database directory not found: {db_directory}")
|
| 276 |
+
command_args.extend(["--dbdir", str(db_directory)])
|
| 277 |
+
|
| 278 |
+
return _run_barrnap_command(
|
| 279 |
+
command_args=command_args,
|
| 280 |
+
debug=debug,
|
| 281 |
+
quiet=quiet,
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
@mcp.tool()
|
| 285 |
+
def barrnap_get_version(
|
| 286 |
+
debug: bool = False,
|
| 287 |
+
quiet: bool = False,
|
| 288 |
+
) -> dict:
|
| 289 |
+
"""
|
| 290 |
+
Prints the barrnap version in the format 'barrnap X.Y'.
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
debug: If True, writes all temporary files to '.' and prints debug information.
|
| 294 |
+
This is equivalent to `--debug`.
|
| 295 |
+
quiet: If True, suppresses all messages to stderr. This is equivalent to `--quiet`.
|
| 296 |
+
|
| 297 |
+
Returns:
|
| 298 |
+
A dictionary containing:
|
| 299 |
+
- "command_executed": The full command string executed.
|
| 300 |
+
- "stdout": Standard output from the tool, containing the version string.
|
| 301 |
+
- "stderr": Standard error from the tool.
|
| 302 |
+
- "output_files": An empty list, as no files are generated.
|
| 303 |
+
"""
|
| 304 |
+
command_args = ["--version"]
|
| 305 |
+
return _run_barrnap_command(
|
| 306 |
+
command_args=command_args,
|
| 307 |
+
debug=debug,
|
| 308 |
+
quiet=quiet,
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
@mcp.tool()
|
| 312 |
+
def barrnap_get_citation(
|
| 313 |
+
debug: bool = False,
|
| 314 |
+
quiet: bool = False,
|
| 315 |
+
) -> dict:
|
| 316 |
+
"""
|
| 317 |
+
Prints the barrnap citation information.
|
| 318 |
+
|
| 319 |
+
Args:
|
| 320 |
+
debug: If True, writes all temporary files to '.' and prints debug information.
|
| 321 |
+
This is equivalent to `--debug`.
|
| 322 |
+
quiet: If True, suppresses all messages to stderr. This is equivalent to `--quiet`.
|
| 323 |
+
|
| 324 |
+
Returns:
|
| 325 |
+
A dictionary containing:
|
| 326 |
+
- "command_executed": The full command string executed.
|
| 327 |
+
- "stdout": Standard output from the tool, containing the citation.
|
| 328 |
+
- "stderr": Standard error from the tool.
|
| 329 |
+
- "output_files": An empty list, as no files are generated.
|
| 330 |
+
"""
|
| 331 |
+
command_args = ["--citation"]
|
| 332 |
+
return _run_barrnap_command(
|
| 333 |
+
command_args=command_args,
|
| 334 |
+
debug=debug,
|
| 335 |
+
quiet=quiet,
|
| 336 |
+
)
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/barrnap_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_barrnap/app/barrnap_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_barrnap'
|
| 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_barrnap/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-barrnap:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-barrnap:latest
|
| 7 |
+
container_name: mcp-barrnap
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=barrnap
|
| 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_barrnap/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- barrnap
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/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 bin2cell via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bin2cell -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/bin2cell_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/bin2cell_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/bin2cell_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/bin2cell_server.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Optional, List
|
| 4 |
+
|
| 5 |
+
@mcp.tool()
|
| 6 |
+
def bin2cell_prepare_bins(
|
| 7 |
+
input_dir: str,
|
| 8 |
+
output_h5ad: str,
|
| 9 |
+
bin_size: int = 2,
|
| 10 |
+
destripe: bool = True,
|
| 11 |
+
):
|
| 12 |
+
"""
|
| 13 |
+
Reads Visium HD data from a SpaceRanger output directory and optionally performs destriping
|
| 14 |
+
to correct for technical effects in 2um bin data.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
input_dir: Path to the SpaceRanger output directory (containing 'outs' folder).
|
| 18 |
+
output_h5ad: Path where the processed bin-level AnnData object will be saved.
|
| 19 |
+
bin_size: Resolution of bins to load (default is 2um).
|
| 20 |
+
destripe: Whether to apply the destriping correction for variable bin dimensions.
|
| 21 |
+
"""
|
| 22 |
+
# Input validation
|
| 23 |
+
input_path = Path(input_dir)
|
| 24 |
+
if not input_path.exists():
|
| 25 |
+
return {"error": f"Input directory {input_dir} does not exist."}
|
| 26 |
+
|
| 27 |
+
output_path = Path(output_h5ad)
|
| 28 |
+
if not output_path.parent.exists():
|
| 29 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 30 |
+
|
| 31 |
+
if bin_size <= 0:
|
| 32 |
+
return {"error": "bin_size must be a positive integer."}
|
| 33 |
+
|
| 34 |
+
# Construct Python command
|
| 35 |
+
# We use a python script string to execute the library functions
|
| 36 |
+
destripe_cmd = "b2c.pp.destripe(adata)" if destripe else "pass"
|
| 37 |
+
python_script = f"""
|
| 38 |
+
import bin2cell as b2c
|
| 39 |
+
import scanpy as sc
|
| 40 |
+
import os
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
# Load Visium HD data
|
| 44 |
+
adata = b2c.pp.read_visium_hd_folder('{input_dir}', bin_size={bin_size})
|
| 45 |
+
|
| 46 |
+
# Perform destriping if requested
|
| 47 |
+
if {destripe}:
|
| 48 |
+
b2c.pp.destripe(adata)
|
| 49 |
+
|
| 50 |
+
# Save the result
|
| 51 |
+
adata.write('{output_h5ad}')
|
| 52 |
+
print("Successfully prepared bin data.")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"Error: {{str(e)}}")
|
| 55 |
+
exit(1)
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
result = subprocess.run(
|
| 60 |
+
["python", "-c", python_script],
|
| 61 |
+
capture_output=True,
|
| 62 |
+
text=True,
|
| 63 |
+
check=True
|
| 64 |
+
)
|
| 65 |
+
return {
|
| 66 |
+
"command_executed": f"bin2cell.pp.read_visium_hd_folder and destripe={destripe}",
|
| 67 |
+
"stdout": result.stdout,
|
| 68 |
+
"stderr": result.stderr,
|
| 69 |
+
"output_files": [output_h5ad]
|
| 70 |
+
}
|
| 71 |
+
except subprocess.CalledProcessError as e:
|
| 72 |
+
return {
|
| 73 |
+
"error": "Failed to prepare bin data",
|
| 74 |
+
"stdout": e.stdout,
|
| 75 |
+
"stderr": e.stderr,
|
| 76 |
+
"command_executed": e.cmd
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
@mcp.tool()
|
| 80 |
+
def bin2cell_run_stardist(
|
| 81 |
+
image_path: str,
|
| 82 |
+
output_mask_path: str,
|
| 83 |
+
model_name: str = "2D_versatile_he",
|
| 84 |
+
prob_thresh: float = 0.5,
|
| 85 |
+
nms_thresh: float = 0.3,
|
| 86 |
+
):
|
| 87 |
+
"""
|
| 88 |
+
Performs cell segmentation on a morphology image using StarDist.
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
image_path: Path to the high-resolution morphology image (e.g., tissue_hires_image.png).
|
| 92 |
+
output_mask_path: Path where the resulting segmentation mask (.tif) will be saved.
|
| 93 |
+
model_name: StarDist model to use (default: '2D_versatile_he').
|
| 94 |
+
prob_thresh: Probability threshold for StarDist detection.
|
| 95 |
+
nms_thresh: Non-maximum suppression threshold for StarDist.
|
| 96 |
+
"""
|
| 97 |
+
# Input validation
|
| 98 |
+
img_path = Path(image_path)
|
| 99 |
+
if not img_path.exists():
|
| 100 |
+
return {"error": f"Image file {image_path} does not exist."}
|
| 101 |
+
|
| 102 |
+
out_mask = Path(output_mask_path)
|
| 103 |
+
if not out_mask.parent.exists():
|
| 104 |
+
out_mask.parent.mkdir(parents=True, exist_ok=True)
|
| 105 |
+
|
| 106 |
+
python_script = f"""
|
| 107 |
+
import bin2cell as b2c
|
| 108 |
+
import cv2
|
| 109 |
+
import numpy as np
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
# Run StarDist segmentation via bin2cell wrapper
|
| 113 |
+
# Note: bin2cell.tl.stardist handles the model loading and prediction
|
| 114 |
+
b2c.tl.stardist(
|
| 115 |
+
'{image_path}',
|
| 116 |
+
'{output_mask_path}',
|
| 117 |
+
model='{model_name}',
|
| 118 |
+
prob_thresh={prob_thresh},
|
| 119 |
+
nms_thresh={nms_thresh}
|
| 120 |
+
)
|
| 121 |
+
print("Successfully generated segmentation mask.")
|
| 122 |
+
except Exception as e:
|
| 123 |
+
print(f"Error: {{str(e)}}")
|
| 124 |
+
exit(1)
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
try:
|
| 128 |
+
result = subprocess.run(
|
| 129 |
+
["python", "-c", python_script],
|
| 130 |
+
capture_output=True,
|
| 131 |
+
text=True,
|
| 132 |
+
check=True
|
| 133 |
+
)
|
| 134 |
+
return {
|
| 135 |
+
"command_executed": f"bin2cell.tl.stardist on {image_path}",
|
| 136 |
+
"stdout": result.stdout,
|
| 137 |
+
"stderr": result.stderr,
|
| 138 |
+
"output_files": [output_mask_path]
|
| 139 |
+
}
|
| 140 |
+
except subprocess.CalledProcessError as e:
|
| 141 |
+
return {
|
| 142 |
+
"error": "StarDist segmentation failed",
|
| 143 |
+
"stdout": e.stdout,
|
| 144 |
+
"stderr": e.stderr,
|
| 145 |
+
"command_executed": e.cmd
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
@mcp.tool()
|
| 149 |
+
def bin2cell_extract_cells(
|
| 150 |
+
bin_h5ad_path: str,
|
| 151 |
+
mask_path: str,
|
| 152 |
+
output_cell_h5ad_path: str,
|
| 153 |
+
qc_metrics: bool = True,
|
| 154 |
+
):
|
| 155 |
+
"""
|
| 156 |
+
Groups subcellular bins into cells based on a segmentation mask and generates a cell-level AnnData object.
|
| 157 |
+
|
| 158 |
+
Args:
|
| 159 |
+
bin_h5ad_path: Path to the bin-level AnnData object (output from bin2cell_prepare_bins).
|
| 160 |
+
mask_path: Path to the segmentation mask file (output from bin2cell_run_stardist).
|
| 161 |
+
output_cell_h5ad_path: Path where the final cell-level AnnData object will be saved.
|
| 162 |
+
qc_metrics: Whether to calculate standard scanpy QC metrics for the new cell object.
|
| 163 |
+
"""
|
| 164 |
+
# Input validation
|
| 165 |
+
bin_path = Path(bin_h5ad_path)
|
| 166 |
+
if not bin_path.exists():
|
| 167 |
+
return {"error": f"Bin h5ad file {bin_h5ad_path} does not exist."}
|
| 168 |
+
|
| 169 |
+
m_path = Path(mask_path)
|
| 170 |
+
if not m_path.exists():
|
| 171 |
+
return {"error": f"Mask file {mask_path} does not exist."}
|
| 172 |
+
|
| 173 |
+
out_cell_path = Path(output_cell_h5ad_path)
|
| 174 |
+
if not out_cell_path.parent.exists():
|
| 175 |
+
out_cell_path.parent.mkdir(parents=True, exist_ok=True)
|
| 176 |
+
|
| 177 |
+
python_script = f"""
|
| 178 |
+
import bin2cell as b2c
|
| 179 |
+
import scanpy as sc
|
| 180 |
+
|
| 181 |
+
try:
|
| 182 |
+
# Load the bin-level data
|
| 183 |
+
adata_bins = sc.read_h5ad('{bin_h5ad_path}')
|
| 184 |
+
|
| 185 |
+
# Extract cells based on the mask
|
| 186 |
+
adata_cells = b2c.tl.extract_cells(adata_bins, '{mask_path}')
|
| 187 |
+
|
| 188 |
+
# Calculate QC metrics if requested
|
| 189 |
+
if {qc_metrics}:
|
| 190 |
+
sc.pp.calculate_qc_metrics(adata_cells, inplace=True)
|
| 191 |
+
|
| 192 |
+
# Save the cell-level object
|
| 193 |
+
adata_cells.write('{output_cell_h5ad_path}')
|
| 194 |
+
print("Successfully extracted cells from bins.")
|
| 195 |
+
except Exception as e:
|
| 196 |
+
print(f"Error: {{str(e)}}")
|
| 197 |
+
exit(1)
|
| 198 |
+
"""
|
| 199 |
+
|
| 200 |
+
try:
|
| 201 |
+
result = subprocess.run(
|
| 202 |
+
["python", "-c", python_script],
|
| 203 |
+
capture_output=True,
|
| 204 |
+
text=True,
|
| 205 |
+
check=True
|
| 206 |
+
)
|
| 207 |
+
return {
|
| 208 |
+
"command_executed": f"bin2cell.tl.extract_cells using mask {mask_path}",
|
| 209 |
+
"stdout": result.stdout,
|
| 210 |
+
"stderr": result.stderr,
|
| 211 |
+
"output_files": [output_cell_h5ad_path]
|
| 212 |
+
}
|
| 213 |
+
except subprocess.CalledProcessError as e:
|
| 214 |
+
return {
|
| 215 |
+
"error": "Cell extraction failed",
|
| 216 |
+
"stdout": e.stdout,
|
| 217 |
+
"stderr": e.stderr,
|
| 218 |
+
"command_executed": e.cmd
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
@mcp.tool()
|
| 222 |
+
def bin2cell_visualize_segmentation(
|
| 223 |
+
bin_h5ad_path: str,
|
| 224 |
+
output_image_path: str,
|
| 225 |
+
basis: str = "spatial",
|
| 226 |
+
):
|
| 227 |
+
"""
|
| 228 |
+
Generates a visualization of the bin-to-cell assignments.
|
| 229 |
+
|
| 230 |
+
Args:
|
| 231 |
+
bin_h5ad_path: Path to the bin-level AnnData object containing cell assignments.
|
| 232 |
+
output_image_path: Path to save the visualization plot (e.g., .png or .pdf).
|
| 233 |
+
basis: The coordinate system to use for plotting (default: 'spatial').
|
| 234 |
+
"""
|
| 235 |
+
bin_path = Path(bin_h5ad_path)
|
| 236 |
+
if not bin_path.exists():
|
| 237 |
+
return {"error": f"Bin h5ad file {bin_h5ad_path} does not exist."}
|
| 238 |
+
|
| 239 |
+
python_script = f"""
|
| 240 |
+
import scanpy as sc
|
| 241 |
+
import matplotlib.pyplot as plt
|
| 242 |
+
import bin2cell as b2c
|
| 243 |
+
|
| 244 |
+
try:
|
| 245 |
+
adata = sc.read_h5ad('{bin_h5ad_path}')
|
| 246 |
+
if 'cell_id' not in adata.obs.columns:
|
| 247 |
+
print("Error: cell_id not found in adata.obs. Run extract_cells first.")
|
| 248 |
+
exit(1)
|
| 249 |
+
|
| 250 |
+
# Plotting logic
|
| 251 |
+
sc.pl.embedding(adata, basis='{basis}', color='cell_id', show=False)
|
| 252 |
+
plt.savefig('{output_image_path}')
|
| 253 |
+
print("Successfully saved visualization.")
|
| 254 |
+
except Exception as e:
|
| 255 |
+
print(f"Error: {{str(e)}}")
|
| 256 |
+
exit(1)
|
| 257 |
+
"""
|
| 258 |
+
|
| 259 |
+
try:
|
| 260 |
+
result = subprocess.run(
|
| 261 |
+
["python", "-c", python_script],
|
| 262 |
+
capture_output=True,
|
| 263 |
+
text=True,
|
| 264 |
+
check=True
|
| 265 |
+
)
|
| 266 |
+
return {
|
| 267 |
+
"command_executed": f"Visualization of cell_id on {basis}",
|
| 268 |
+
"stdout": result.stdout,
|
| 269 |
+
"stderr": result.stderr,
|
| 270 |
+
"output_files": [output_image_path]
|
| 271 |
+
}
|
| 272 |
+
except subprocess.CalledProcessError as e:
|
| 273 |
+
return {
|
| 274 |
+
"error": "Visualization failed",
|
| 275 |
+
"stdout": e.stdout,
|
| 276 |
+
"stderr": e.stderr,
|
| 277 |
+
"command_executed": e.cmd
|
| 278 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/bin2cell_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_bin2cell/app/bin2cell_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bin2cell'
|
| 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_bin2cell/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bin2cell:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bin2cell:latest
|
| 7 |
+
container_name: mcp-bin2cell
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bin2cell
|
| 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_bin2cell/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bin2cell
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocbaseutils/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-biocbaseutils
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocbaseutils/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/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-curatedatlasqueryr via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-curatedatlasqueryr -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-curatedatlasqueryr_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-curatedatlasqueryr_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-curatedatlasqueryr_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/app/bioconductor-curatedatlasqueryr_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-curatedatlasqueryr/app/bioconductor-curatedatlasqueryr_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_curatedatlasqueryr'
|
| 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-curatedatlasqueryr/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-curatedatlasqueryr:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-curatedatlasqueryr:latest
|
| 7 |
+
container_name: mcp-bioconductor-curatedatlasqueryr
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-curatedatlasqueryr
|
| 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-curatedatlasqueryr/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-delayedmatrixstats/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ebseq/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/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-experimentsubset via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-experimentsubset -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-experimentsubset_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-experimentsubset_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-experimentsubset_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/bioconductor-experimentsubset_server.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, Dict, List
|
| 5 |
+
|
| 6 |
+
# This is a placeholder for the MCP decorator.
|
| 7 |
+
# In a real MCP environment, this would be provided by the MCP framework.
|
| 8 |
+
def mcp_tool_placeholder(*args, **kwargs):
|
| 9 |
+
def decorator(func):
|
| 10 |
+
return func
|
| 11 |
+
return decorator
|
| 12 |
+
|
| 13 |
+
mcp = type('mcp', (), {'tool': mcp_tool_placeholder})
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@mcp.tool()
|
| 17 |
+
def manage_experiment_subset(
|
| 18 |
+
input_rds: Path,
|
| 19 |
+
output_rds: Path,
|
| 20 |
+
subset_name: str = "mcp_subset",
|
| 21 |
+
row_subset: Optional[str] = None,
|
| 22 |
+
col_subset: Optional[str] = None,
|
| 23 |
+
row_subset_file: Optional[Path] = None,
|
| 24 |
+
col_subset_file: Optional[Path] = None,
|
| 25 |
+
) -> Dict[str, any]:
|
| 26 |
+
"""
|
| 27 |
+
Provides a command-line interface to the R/Bioconductor 'ExperimentSubset' package.
|
| 28 |
+
|
| 29 |
+
This tool subsets a Bioconductor experiment object (e.g., SummarizedExperiment,
|
| 30 |
+
SingleCellExperiment) stored in an RDS file based on provided row (e.g., genes)
|
| 31 |
+
or column (e.g., cells) identifiers.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
input_rds: Path to the input RDS file containing a Bioconductor experiment object.
|
| 35 |
+
output_rds: Path to save the output subsetted RDS file.
|
| 36 |
+
subset_name: A name to assign to the created subset.
|
| 37 |
+
row_subset: A comma-separated string of row names to include in the subset.
|
| 38 |
+
col_subset: A comma-separated string of column names to include in the subset.
|
| 39 |
+
row_subset_file: Path to a file containing row names to include (one per line).
|
| 40 |
+
col_subset_file: Path to a file containing column names to include (one per line).
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
A dictionary containing the execution command, stdout, stderr, and a list of output files.
|
| 44 |
+
"""
|
| 45 |
+
# --- Input Validation ---
|
| 46 |
+
if not input_rds.exists():
|
| 47 |
+
raise FileNotFoundError(f"Input file not found: {input_rds}")
|
| 48 |
+
|
| 49 |
+
if row_subset and row_subset_file:
|
| 50 |
+
raise ValueError("Cannot specify both 'row_subset' and 'row_subset_file'.")
|
| 51 |
+
|
| 52 |
+
if col_subset and col_subset_file:
|
| 53 |
+
raise ValueError("Cannot specify both 'col_subset' and 'col_subset_file'.")
|
| 54 |
+
|
| 55 |
+
if not any([row_subset, col_subset, row_subset_file, col_subset_file]):
|
| 56 |
+
raise ValueError("At least one subsetting criterion must be provided "
|
| 57 |
+
"('row_subset', 'col_subset', 'row_subset_file', or 'col_subset_file').")
|
| 58 |
+
|
| 59 |
+
if not output_rds.parent.exists():
|
| 60 |
+
output_rds.parent.mkdir(parents=True, exist_ok=True)
|
| 61 |
+
|
| 62 |
+
# --- R Script Generation ---
|
| 63 |
+
r_script_content = f"""
|
| 64 |
+
# Load required libraries
|
| 65 |
+
if (!require("optparse", quietly = TRUE)) install.packages("optparse", repos = "http://cran.us.r-project.org")
|
| 66 |
+
if (!require("ExperimentSubset", quietly = TRUE)) {{
|
| 67 |
+
if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager", repos = "http://cran.us.r-project.org")
|
| 68 |
+
BiocManager::install("ExperimentSubset", update=FALSE)
|
| 69 |
+
}}
|
| 70 |
+
library(optparse)
|
| 71 |
+
library(ExperimentSubset)
|
| 72 |
+
|
| 73 |
+
# Define and parse command-line options
|
| 74 |
+
option_list <- list(
|
| 75 |
+
make_option(c("-i", "--input"), type="character", help="Input RDS file path"),
|
| 76 |
+
make_option(c("-o", "--output"), type="character", help="Output RDS file path"),
|
| 77 |
+
make_option(c("-n", "--name"), type="character", default="mcp_subset", help="Name for the subset"),
|
| 78 |
+
make_option(c("-r", "--rows"), type="character", default=NULL, help="Comma-separated row names"),
|
| 79 |
+
make_option(c("-c", "--cols"), type="character", default=NULL, help="Comma-separated column names"),
|
| 80 |
+
make_option(c("--row_file"), type="character", default=NULL, help="File with row names"),
|
| 81 |
+
make_option(c("--col_file"), type="character", default=NULL, help="File with column names")
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
opt_parser <- OptionParser(option_list=option_list)
|
| 85 |
+
opt <- parse_args(opt_parser)
|
| 86 |
+
|
| 87 |
+
if (is.null(opt$input) || is.null(opt$output)) {{
|
| 88 |
+
print_help(opt_parser)
|
| 89 |
+
stop("Input and output files must be supplied.", call.=FALSE)
|
| 90 |
+
}}
|
| 91 |
+
|
| 92 |
+
# Load the experiment object
|
| 93 |
+
cat("Loading input RDS file:", opt$input, "\\n")
|
| 94 |
+
exp_obj <- readRDS(opt$input)
|
| 95 |
+
|
| 96 |
+
# Create ExperimentSubset object
|
| 97 |
+
es <- ExperimentSubset(exp_obj)
|
| 98 |
+
|
| 99 |
+
# Determine row and column subsets
|
| 100 |
+
row_indices <- NULL
|
| 101 |
+
if (!is.null(opt$rows)) {{
|
| 102 |
+
row_indices <- trimws(strsplit(opt$rows, ",")[[1]])
|
| 103 |
+
}} else if (!is.null(opt$row_file)) {{
|
| 104 |
+
row_indices <- readLines(opt$row_file)
|
| 105 |
+
}}
|
| 106 |
+
|
| 107 |
+
col_indices <- NULL
|
| 108 |
+
if (!is.null(opt$cols)) {{
|
| 109 |
+
col_indices <- trimws(strsplit(opt$cols, ",")[[1]])
|
| 110 |
+
}} else if (!is.null(opt$col_file)) {{
|
| 111 |
+
col_indices <- readLines(opt$col_file)
|
| 112 |
+
}}
|
| 113 |
+
|
| 114 |
+
# Create the subset using the subsetData function
|
| 115 |
+
cat("Creating subset '", opt$name, "'...\\n", sep="")
|
| 116 |
+
es <- subsetData(es, subsetName = opt$name, rows = row_indices, cols = col_indices)
|
| 117 |
+
|
| 118 |
+
# Retrieve the actual subsetted object from the container
|
| 119 |
+
subset_obj <- getSubset(es, subsetName = opt$name)
|
| 120 |
+
|
| 121 |
+
# Save the subsetted object
|
| 122 |
+
cat("Saving subsetted object to:", opt$output, "\\n")
|
| 123 |
+
saveRDS(subset_obj, file = opt$output)
|
| 124 |
+
|
| 125 |
+
cat("Successfully created subset.\\n")
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
# --- Subprocess Execution ---
|
| 129 |
+
cmd: List[str] = []
|
| 130 |
+
try:
|
| 131 |
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file:
|
| 132 |
+
r_script_file.write(r_script_content)
|
| 133 |
+
r_script_path = r_script_file.name
|
| 134 |
+
|
| 135 |
+
cmd = [
|
| 136 |
+
"Rscript", r_script_path,
|
| 137 |
+
"--input", str(input_rds),
|
| 138 |
+
"--output", str(output_rds),
|
| 139 |
+
"--name", subset_name
|
| 140 |
+
]
|
| 141 |
+
|
| 142 |
+
if row_subset:
|
| 143 |
+
cmd.extend(["--rows", row_subset])
|
| 144 |
+
if col_subset:
|
| 145 |
+
cmd.extend(["--cols", col_subset])
|
| 146 |
+
if row_subset_file:
|
| 147 |
+
cmd.extend(["--row_file", str(row_subset_file)])
|
| 148 |
+
if col_subset_file:
|
| 149 |
+
cmd.extend(["--col_file", str(col_subset_file)])
|
| 150 |
+
|
| 151 |
+
result = subprocess.run(
|
| 152 |
+
cmd,
|
| 153 |
+
capture_output=True,
|
| 154 |
+
text=True,
|
| 155 |
+
check=True
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
return {
|
| 159 |
+
"command_executed": " ".join(cmd),
|
| 160 |
+
"stdout": result.stdout,
|
| 161 |
+
"stderr": result.stderr,
|
| 162 |
+
"output_files": [str(output_rds)]
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
except FileNotFoundError:
|
| 166 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your system's PATH.")
|
| 167 |
+
except subprocess.CalledProcessError as e:
|
| 168 |
+
return {
|
| 169 |
+
"command_executed": " ".join(cmd),
|
| 170 |
+
"stdout": e.stdout,
|
| 171 |
+
"stderr": e.stderr,
|
| 172 |
+
"error": "R script execution failed.",
|
| 173 |
+
"return_code": e.returncode
|
| 174 |
+
}
|
| 175 |
+
finally:
|
| 176 |
+
# Clean up the temporary R script
|
| 177 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 178 |
+
Path(r_script_path).unlink()
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/bioconductor-experimentsubset_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-experimentsubset/app/bioconductor-experimentsubset_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_experimentsubset'
|
| 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-experimentsubset/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-experimentsubset:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-experimentsubset:latest
|
| 7 |
+
container_name: mcp-bioconductor-experimentsubset
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-experimentsubset
|
| 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-experimentsubset/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-experimentsubset
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genefilter/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genefilter/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-genefilter:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-genefilter:latest
|
| 7 |
+
container_name: mcp-bioconductor-genefilter
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-genefilter
|
| 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-ggsc/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-ggsc via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-ggsc -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-ggsc_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-ggsc_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-ggsc_server.py"]
|
| 40 |
+
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/app/bioconductor-ggsc_server.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# This script assumes the presence of a companion R script (e.g., 'run_ggsc.R')
|
| 7 |
+
# that acts as a command-line interface for the 'ggsc' R package.
|
| 8 |
+
# The R environment must have 'ggsc', 'SingleCellExperiment', 'ggplot2',
|
| 9 |
+
# and an argument parser like 'optparse' installed.
|
| 10 |
+
R_SCRIPT_EXECUTABLE = "run_ggsc.R"
|
| 11 |
+
|
| 12 |
+
# Per instructions, the @mcp.tool decorator is used.
|
| 13 |
+
# A dummy decorator is defined here for syntax validity in a standalone context.
|
| 14 |
+
# In a true MCP environment, this would be provided by the MCP framework.
|
| 15 |
+
def tool(func):
|
| 16 |
+
"""Dummy decorator to match the required syntax."""
|
| 17 |
+
return func
|
| 18 |
+
|
| 19 |
+
class MCP:
|
| 20 |
+
"""Dummy class to hold the tool decorator."""
|
| 21 |
+
tool = staticmethod(tool)
|
| 22 |
+
|
| 23 |
+
mcp = MCP()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@mcp.tool()
|
| 27 |
+
def plot_reduced_dim(
|
| 28 |
+
input_rds: Path,
|
| 29 |
+
output_plot: Path,
|
| 30 |
+
dim_red: str,
|
| 31 |
+
color_by: str,
|
| 32 |
+
facet_by: Optional[str] = None,
|
| 33 |
+
plot_title: Optional[str] = None,
|
| 34 |
+
img_width: int = 7,
|
| 35 |
+
img_height: int = 7
|
| 36 |
+
) -> dict:
|
| 37 |
+
"""
|
| 38 |
+
Generates a reduced dimension plot (e.g., UMAP, t-SNE) from a SingleCellExperiment object.
|
| 39 |
+
|
| 40 |
+
This tool wraps the `plot_reduced_dim_sce` function from the R/Bioconductor package `ggsc`.
|
| 41 |
+
The input must be a .rds file containing a SingleCellExperiment object.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
input_rds: Path to the input RDS file containing a SingleCellExperiment object.
|
| 45 |
+
output_plot: Path to save the output plot image (e.g., plot.png).
|
| 46 |
+
dim_red: Name of the dimension reduction to use (e.g., "UMAP", "TSNE").
|
| 47 |
+
color_by: Variable in colData to color the points by (e.g., "label", "cluster").
|
| 48 |
+
facet_by: Optional variable in colData to facet the plot by.
|
| 49 |
+
plot_title: Optional title for the plot.
|
| 50 |
+
img_width: Width of the output image in inches.
|
| 51 |
+
img_height: Height of the output image in inches.
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
A dictionary containing the execution details and output file path.
|
| 55 |
+
"""
|
| 56 |
+
# --- Input Validation ---
|
| 57 |
+
if not input_rds.is_file():
|
| 58 |
+
raise FileNotFoundError(f"Input RDS file not found: {input_rds}")
|
| 59 |
+
if img_width <= 0 or img_height <= 0:
|
| 60 |
+
raise ValueError("Image width and height must be positive integers.")
|
| 61 |
+
|
| 62 |
+
# Ensure output directory exists
|
| 63 |
+
output_plot.parent.mkdir(parents=True, exist_ok=True)
|
| 64 |
+
|
| 65 |
+
# --- Command Construction ---
|
| 66 |
+
cmd = [
|
| 67 |
+
"Rscript",
|
| 68 |
+
R_SCRIPT_EXECUTABLE,
|
| 69 |
+
"reduced_dim",
|
| 70 |
+
"--input_rds", str(input_rds),
|
| 71 |
+
"--output_plot", str(output_plot),
|
| 72 |
+
"--dim_red", dim_red,
|
| 73 |
+
"--color_by", color_by,
|
| 74 |
+
"--img_width", str(img_width),
|
| 75 |
+
"--img_height", str(img_height)
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
if facet_by:
|
| 79 |
+
cmd.extend(["--facet_by", facet_by])
|
| 80 |
+
if plot_title:
|
| 81 |
+
cmd.extend(["--plot_title", plot_title])
|
| 82 |
+
|
| 83 |
+
# --- Subprocess Execution ---
|
| 84 |
+
command_executed = " ".join(cmd)
|
| 85 |
+
logging.info(f"Executing command: {command_executed}")
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
result = subprocess.run(
|
| 89 |
+
cmd,
|
| 90 |
+
capture_output=True,
|
| 91 |
+
text=True,
|
| 92 |
+
check=True,
|
| 93 |
+
encoding='utf-8'
|
| 94 |
+
)
|
| 95 |
+
except FileNotFoundError:
|
| 96 |
+
raise RuntimeError("Rscript or the wrapper script not found. Please ensure R and the tool's R script are in the system's PATH.")
|
| 97 |
+
except subprocess.CalledProcessError as e:
|
| 98 |
+
error_message = f"ggsc R script failed for reduced_dim plot.\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}"
|
| 99 |
+
logging.error(error_message)
|
| 100 |
+
raise RuntimeError(error_message) from e
|
| 101 |
+
|
| 102 |
+
return {
|
| 103 |
+
"command_executed": command_executed,
|
| 104 |
+
"stdout": result.stdout,
|
| 105 |
+
"stderr": result.stderr,
|
| 106 |
+
"output_files": [str(output_plot)]
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@mcp.tool()
|
| 111 |
+
def plot_gene_expression(
|
| 112 |
+
input_rds: Path,
|
| 113 |
+
output_plot: Path,
|
| 114 |
+
gene: str,
|
| 115 |
+
dim_red: str,
|
| 116 |
+
assay: str = "logcounts",
|
| 117 |
+
plot_title: Optional[str] = None,
|
| 118 |
+
img_width: int = 7,
|
| 119 |
+
img_height: int = 7
|
| 120 |
+
) -> dict:
|
| 121 |
+
"""
|
| 122 |
+
Plots the expression of a single gene on a reduced dimension plot.
|
| 123 |
+
|
| 124 |
+
This tool wraps the `plot_gene_sce` function from the R/Bioconductor package `ggsc`.
|
| 125 |
+
The input must be a .rds file containing a SingleCellExperiment object.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
input_rds: Path to the input RDS file containing a SingleCellExperiment object.
|
| 129 |
+
output_plot: Path to save the output plot image.
|
| 130 |
+
gene: The name of the gene to plot.
|
| 131 |
+
dim_red: Name of the dimension reduction to use (e.g., "UMAP").
|
| 132 |
+
assay: The assay to use for expression values (default: "logcounts").
|
| 133 |
+
plot_title: Optional title for the plot.
|
| 134 |
+
img_width: Width of the output image in inches.
|
| 135 |
+
img_height: Height of the output image in inches.
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
A dictionary containing the execution details and output file path.
|
| 139 |
+
"""
|
| 140 |
+
# --- Input Validation ---
|
| 141 |
+
if not input_rds.is_file():
|
| 142 |
+
raise FileNotFoundError(f"Input RDS file not found: {input_rds}")
|
| 143 |
+
if not gene:
|
| 144 |
+
raise ValueError("A gene name must be provided.")
|
| 145 |
+
if img_width <= 0 or img_height <= 0:
|
| 146 |
+
raise ValueError("Image width and height must be positive integers.")
|
| 147 |
+
|
| 148 |
+
output_plot.parent.mkdir(parents=True, exist_ok=True)
|
| 149 |
+
|
| 150 |
+
# --- Command Construction ---
|
| 151 |
+
cmd = [
|
| 152 |
+
"Rscript",
|
| 153 |
+
R_SCRIPT_EXECUTABLE,
|
| 154 |
+
"gene_plot",
|
| 155 |
+
"--input_rds", str(input_rds),
|
| 156 |
+
"--output_plot", str(output_plot),
|
| 157 |
+
"--gene", gene,
|
| 158 |
+
"--dim_red", dim_red,
|
| 159 |
+
"--assay", assay,
|
| 160 |
+
"--img_width", str(img_width),
|
| 161 |
+
"--img_height", str(img_height)
|
| 162 |
+
]
|
| 163 |
+
|
| 164 |
+
if plot_title:
|
| 165 |
+
cmd.extend(["--plot_title", plot_title])
|
| 166 |
+
|
| 167 |
+
# --- Subprocess Execution ---
|
| 168 |
+
command_executed = " ".join(cmd)
|
| 169 |
+
logging.info(f"Executing command: {command_executed}")
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
result = subprocess.run(
|
| 173 |
+
cmd,
|
| 174 |
+
capture_output=True,
|
| 175 |
+
text=True,
|
| 176 |
+
check=True,
|
| 177 |
+
encoding='utf-8'
|
| 178 |
+
)
|
| 179 |
+
except FileNotFoundError:
|
| 180 |
+
raise RuntimeError("Rscript or the wrapper script not found. Please ensure R and the tool's R script are in the system's PATH.")
|
| 181 |
+
except subprocess.CalledProcessError as e:
|
| 182 |
+
error_message = f"ggsc R script failed for gene expression plot.\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}"
|
| 183 |
+
logging.error(error_message)
|
| 184 |
+
raise RuntimeError(error_message) from e
|
| 185 |
+
|
| 186 |
+
return {
|
| 187 |
+
"command_executed": command_executed,
|
| 188 |
+
"stdout": result.stdout,
|
| 189 |
+
"stderr": result.stderr,
|
| 190 |
+
"output_files": [str(output_plot)]
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
@mcp.tool()
|
| 195 |
+
def plot_expression_heatmap(
|
| 196 |
+
input_rds: Path,
|
| 197 |
+
output_plot: Path,
|
| 198 |
+
genes: List[str],
|
| 199 |
+
annotation_col: str,
|
| 200 |
+
assay: str = "logcounts",
|
| 201 |
+
plot_title: Optional[str] = None,
|
| 202 |
+
img_width: int = 7,
|
| 203 |
+
img_height: int = 10
|
| 204 |
+
) -> dict:
|
| 205 |
+
"""
|
| 206 |
+
Generates a heatmap of gene expression for a set of genes.
|
| 207 |
+
|
| 208 |
+
This tool wraps the `plot_heatmap_sce` function from the R/Bioconductor package `ggsc`.
|
| 209 |
+
The input must be a .rds file containing a SingleCellExperiment object.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
input_rds: Path to the input RDS file containing a SingleCellExperiment object.
|
| 213 |
+
output_plot: Path to save the output plot image.
|
| 214 |
+
genes: A list of gene names to include in the heatmap.
|
| 215 |
+
annotation_col: Column in colData to use for cell annotation.
|
| 216 |
+
assay: The assay to use for expression values (default: "logcounts").
|
| 217 |
+
plot_title: Optional title for the plot.
|
| 218 |
+
img_width: Width of the output image in inches.
|
| 219 |
+
img_height: Height of the output image in inches.
|
| 220 |
+
|
| 221 |
+
Returns:
|
| 222 |
+
A dictionary containing the execution details and output file path.
|
| 223 |
+
"""
|
| 224 |
+
# --- Input Validation ---
|
| 225 |
+
if not input_rds.is_file():
|
| 226 |
+
raise FileNotFoundError(f"Input RDS file not found: {input_rds}")
|
| 227 |
+
if not genes:
|
| 228 |
+
raise ValueError("At least one gene must be provided in the list.")
|
| 229 |
+
if img_width <= 0 or img_height <= 0:
|
| 230 |
+
raise ValueError("Image width and height must be positive integers.")
|
| 231 |
+
|
| 232 |
+
output_plot.parent.mkdir(parents=True, exist_ok=True)
|
| 233 |
+
|
| 234 |
+
# --- Command Construction ---
|
| 235 |
+
genes_str = ",".join(genes)
|
| 236 |
+
cmd = [
|
| 237 |
+
"Rscript",
|
| 238 |
+
R_SCRIPT_EXECUTABLE,
|
| 239 |
+
"heatmap",
|
| 240 |
+
"--input_rds", str(input_rds),
|
| 241 |
+
"--output_plot", str(output_plot),
|
| 242 |
+
"--genes", genes_str,
|
| 243 |
+
"--annotation_col", annotation_col,
|
| 244 |
+
"--assay", assay,
|
| 245 |
+
"--img_width", str(img_width),
|
| 246 |
+
"--img_height", str(img_height)
|
| 247 |
+
]
|
| 248 |
+
|
| 249 |
+
if plot_title:
|
| 250 |
+
cmd.extend(["--plot_title", plot_title])
|
| 251 |
+
|
| 252 |
+
# --- Subprocess Execution ---
|
| 253 |
+
command_executed = " ".join(cmd)
|
| 254 |
+
logging.info(f"Executing command: {command_executed}")
|
| 255 |
+
|
| 256 |
+
try:
|
| 257 |
+
result = subprocess.run(
|
| 258 |
+
cmd,
|
| 259 |
+
capture_output=True,
|
| 260 |
+
text=True,
|
| 261 |
+
check=True,
|
| 262 |
+
encoding='utf-8'
|
| 263 |
+
)
|
| 264 |
+
except FileNotFoundError:
|
| 265 |
+
raise RuntimeError("Rscript or the wrapper script not found. Please ensure R and the tool's R script are in the system's PATH.")
|
| 266 |
+
except subprocess.CalledProcessError as e:
|
| 267 |
+
error_message = f"ggsc R script failed for expression heatmap.\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}"
|
| 268 |
+
logging.error(error_message)
|
| 269 |
+
raise RuntimeError(error_message) from e
|
| 270 |
+
|
| 271 |
+
return {
|
| 272 |
+
"command_executed": command_executed,
|
| 273 |
+
"stdout": result.stdout,
|
| 274 |
+
"stderr": result.stderr,
|
| 275 |
+
"output_files": [str(output_plot)]
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
@mcp.tool()
|
| 280 |
+
def plot_expression_violin(
|
| 281 |
+
input_rds: Path,
|
| 282 |
+
output_plot: Path,
|
| 283 |
+
gene: str,
|
| 284 |
+
group_by: str,
|
| 285 |
+
assay: str = "logcounts",
|
| 286 |
+
plot_title: Optional[str] = None,
|
| 287 |
+
img_width: int = 7,
|
| 288 |
+
img_height: int = 5
|
| 289 |
+
) -> dict:
|
| 290 |
+
"""
|
| 291 |
+
Generates a violin plot of gene expression across different groups.
|
| 292 |
+
|
| 293 |
+
This tool wraps the `plot_violin_sce` function from the R/Bioconductor package `ggsc`.
|
| 294 |
+
The input must be a .rds file containing a SingleCellExperiment object.
|
| 295 |
+
|
| 296 |
+
Args:
|
| 297 |
+
input_rds: Path to the input RDS file containing a SingleCellExperiment object.
|
| 298 |
+
output_plot: Path to save the output plot image.
|
| 299 |
+
gene: The name of the gene to plot.
|
| 300 |
+
group_by: Variable in colData to group the violins by (e.g., "cluster").
|
| 301 |
+
assay: The assay to use for expression values (default: "logcounts").
|
| 302 |
+
plot_title: Optional title for the plot.
|
| 303 |
+
img_width: Width of the output image in inches.
|
| 304 |
+
img_height: Height of the output image in inches.
|
| 305 |
+
|
| 306 |
+
Returns:
|
| 307 |
+
A dictionary containing the execution details and output file path.
|
| 308 |
+
"""
|
| 309 |
+
# --- Input Validation ---
|
| 310 |
+
if not input_rds.is_file():
|
| 311 |
+
raise FileNotFoundError(f"Input RDS file not found: {input_rds}")
|
| 312 |
+
if not gene:
|
| 313 |
+
raise ValueError("A gene name must be provided.")
|
| 314 |
+
if img_width <= 0 or img_height <= 0:
|
| 315 |
+
raise ValueError("Image width and height must be positive integers.")
|
| 316 |
+
|
| 317 |
+
output_plot.parent.mkdir(parents=True, exist_ok=True)
|
| 318 |
+
|
| 319 |
+
# --- Command Construction ---
|
| 320 |
+
cmd = [
|
| 321 |
+
"Rscript",
|
| 322 |
+
R_SCRIPT_EXECUTABLE,
|
| 323 |
+
"violin",
|
| 324 |
+
"--input_rds", str(input_rds),
|
| 325 |
+
"--output_plot", str(output_plot),
|
| 326 |
+
"--gene", gene,
|
| 327 |
+
"--group_by", group_by,
|
| 328 |
+
"--assay", assay,
|
| 329 |
+
"--img_width", str(img_width),
|
| 330 |
+
"--img_height", str(img_height)
|
| 331 |
+
]
|
| 332 |
+
|
| 333 |
+
if plot_title:
|
| 334 |
+
cmd.extend(["--plot_title", plot_title])
|
| 335 |
+
|
| 336 |
+
# --- Subprocess Execution ---
|
| 337 |
+
command_executed = " ".join(cmd)
|
| 338 |
+
logging.info(f"Executing command: {command_executed}")
|
| 339 |
+
|
| 340 |
+
try:
|
| 341 |
+
result = subprocess.run(
|
| 342 |
+
cmd,
|
| 343 |
+
capture_output=True,
|
| 344 |
+
text=True,
|
| 345 |
+
check=True,
|
| 346 |
+
encoding='utf-8'
|
| 347 |
+
)
|
| 348 |
+
except FileNotFoundError:
|
| 349 |
+
raise RuntimeError("Rscript or the wrapper script not found. Please ensure R and the tool's R script are in the system's PATH.")
|
| 350 |
+
except subprocess.CalledProcessError as e:
|
| 351 |
+
error_message = f"ggsc R script failed for expression violin plot.\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}"
|
| 352 |
+
logging.error(error_message)
|
| 353 |
+
raise RuntimeError(error_message) from e
|
| 354 |
+
|
| 355 |
+
return {
|
| 356 |
+
"command_executed": command_executed,
|
| 357 |
+
"stdout": result.stdout,
|
| 358 |
+
"stderr": result.stderr,
|
| 359 |
+
"output_files": [str(output_plot)]
|
| 360 |
+
}
|
BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/app/bioconductor-ggsc_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-ggsc/app/bioconductor-ggsc_server.py')
|
| 11 |
+
SERVER_NAME = 'biosci_bioconductor_ggsc'
|
| 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-ggsc/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-ggsc:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-ggsc:latest
|
| 7 |
+
container_name: mcp-bioconductor-ggsc
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-ggsc
|
| 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 |
+
|