Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Biomni/biomni/tool/__pycache__/cancer_biology.cpython-311.pyc +0 -0
- Biomni/biomni/tool/tool_description/__pycache__/biochemistry.cpython-311.pyc +0 -0
- Biomni/biomni/tool/tool_description/lab_automation.py +52 -0
- Biomni/biomni/tool/tool_description/support_tools.py +74 -0
- Biomni/mcp_generated/mcp_bioconductor-bsgenome/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-bsgenome/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-bsgenome/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-cotan/app/bioconductor-cotan_server.py +208 -0
- Biomni/mcp_generated/mcp_bioconductor-cotan/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-cotan/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-cotan/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-dnacopy/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-dnacopy/app/bioconductor-dnacopy_server.py +208 -0
- Biomni/mcp_generated/mcp_bioconductor-dnacopy/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-dnacopy/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-gsva/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-gsva/app/bioconductor-gsva_server.py +215 -0
- Biomni/mcp_generated/mcp_bioconductor-gsva/app/bioconductor-gsva_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-gsva/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-gsva/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_server.py +237 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_bioconductor-poem/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_bioconductor-sctreeviz/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_server.py +230 -0
- Biomni/mcp_generated/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_bioconductor-sctreeviz/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_bioconductor-sctreeviz/requirements.txt +2 -0
- Biomni/mcp_generated/mcp_echidna/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_echidna/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_epic/app/epic_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_epic/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_gmap/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_gmap/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_mummer/app/mummer_server.py +145 -0
- Biomni/mcp_generated/mcp_perl-pod-escapes/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_perl-xml-regexp/Dockerfile +40 -0
- Biomni/mcp_generated/mcp_perl-xml-regexp/app/perl-xml-regexp_server.py +170 -0
- Biomni/mcp_generated/mcp_perl-xml-regexp/app/perl-xml-regexp_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_perl-xml-regexp/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_perl-xml-regexp/docker-compose.yml +22 -0
- Biomni/mcp_generated/mcp_searchgui/app/searchgui_shim_server.py +55 -0
- Biomni/mcp_generated/mcp_searchgui/environment.yaml +10 -0
- Biomni/mcp_generated/mcp_sortmerna/app/requirements.txt +1 -0
- Biomni/mcp_generated/mcp_sortmerna/app/sortmerna_server.py +216 -0
- Biomni/mcp_generated/mcp_sortmerna/requirements.txt +2 -0
Biomni/biomni/tool/__pycache__/cancer_biology.cpython-311.pyc
ADDED
|
Binary file (64.8 kB). View file
|
|
|
Biomni/biomni/tool/tool_description/__pycache__/biochemistry.cpython-311.pyc
ADDED
|
Binary file (4.76 kB). View file
|
|
|
Biomni/biomni/tool/tool_description/lab_automation.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
description = [
|
| 2 |
+
{
|
| 3 |
+
"description": "Test a PyLabRobot script based on the script content.",
|
| 4 |
+
"name": "test_pylabrobot_script",
|
| 5 |
+
"optional_parameters": [
|
| 6 |
+
{
|
| 7 |
+
"default": False,
|
| 8 |
+
"description": "If True, enable tracking of the script execution",
|
| 9 |
+
"name": "enable_tracking",
|
| 10 |
+
"type": "bool",
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"default": 60,
|
| 14 |
+
"description": "Timeout in seconds for the script execution",
|
| 15 |
+
"name": "timeout_seconds",
|
| 16 |
+
"type": "int",
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"default": False,
|
| 20 |
+
"description": "If True, save the test results as a .json file",
|
| 21 |
+
"name": "save_test_report",
|
| 22 |
+
"type": "bool",
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"default": None,
|
| 26 |
+
"description": "Directory to save the test results. If provided, the test results will be saved as a .json file in this directory",
|
| 27 |
+
"name": "test_report_dir",
|
| 28 |
+
"type": "str",
|
| 29 |
+
},
|
| 30 |
+
],
|
| 31 |
+
"required_parameters": [
|
| 32 |
+
{
|
| 33 |
+
"default": None,
|
| 34 |
+
"description": "Script content to test",
|
| 35 |
+
"name": "script_input",
|
| 36 |
+
"type": "str",
|
| 37 |
+
}
|
| 38 |
+
],
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"description": "Get the documentation for the liquid handling section of the PyLabRobot tutorial.",
|
| 42 |
+
"name": "get_pylabrobot_documentation_liquid",
|
| 43 |
+
"optional_parameters": [],
|
| 44 |
+
"required_parameters": [],
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"description": "Get the documentation for the material handling section of the PyLabRobot tutorial.",
|
| 48 |
+
"name": "get_pylabrobot_documentation_material",
|
| 49 |
+
"optional_parameters": [],
|
| 50 |
+
"required_parameters": [],
|
| 51 |
+
},
|
| 52 |
+
]
|
Biomni/biomni/tool/tool_description/support_tools.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
description = [
|
| 2 |
+
{
|
| 3 |
+
"description": "Executes the provided Python command in the notebook environment and returns the output.",
|
| 4 |
+
"name": "run_python_repl",
|
| 5 |
+
"optional_parameters": [],
|
| 6 |
+
"required_parameters": [
|
| 7 |
+
{
|
| 8 |
+
"default": None,
|
| 9 |
+
"description": "Python command to execute in the notebook environment",
|
| 10 |
+
"name": "command",
|
| 11 |
+
"type": "str",
|
| 12 |
+
}
|
| 13 |
+
],
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"description": "Read the source code of a function from any module path.",
|
| 17 |
+
"name": "read_function_source_code",
|
| 18 |
+
"optional_parameters": [],
|
| 19 |
+
"required_parameters": [
|
| 20 |
+
{
|
| 21 |
+
"default": None,
|
| 22 |
+
"description": "Fully qualified function name "
|
| 23 |
+
"(e.g., "
|
| 24 |
+
"'bioagentos.tool.support_tools.write_python_code')",
|
| 25 |
+
"name": "function_name",
|
| 26 |
+
"type": "str",
|
| 27 |
+
}
|
| 28 |
+
],
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"description": "Download data from Synapse using entity IDs. Requires SYNAPSE_AUTH_TOKEN environment variable for authentication. CRITICAL: Always specify entity_type parameter based on what you're downloading (file, dataset, folder, project). Check user hints like 'files' or search results to determine correct type. Multiple IDs only work with entity_type='file'. Recursive only works with entity_type='folder'.",
|
| 32 |
+
"name": "download_synapse_data",
|
| 33 |
+
"optional_parameters": [
|
| 34 |
+
{
|
| 35 |
+
"name": "download_location",
|
| 36 |
+
"type": "str",
|
| 37 |
+
"description": "Directory where files will be downloaded",
|
| 38 |
+
"default": ".",
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"name": "follow_link",
|
| 42 |
+
"type": "bool",
|
| 43 |
+
"description": "Whether to follow links to download the linked entity",
|
| 44 |
+
"default": False,
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "recursive",
|
| 48 |
+
"type": "bool",
|
| 49 |
+
"description": "Whether to recursively download folders and their contents. ONLY valid with entity_type='folder'",
|
| 50 |
+
"default": False,
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "timeout",
|
| 54 |
+
"type": "int",
|
| 55 |
+
"description": "Timeout in seconds for each download operation",
|
| 56 |
+
"default": 300,
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"name": "entity_type",
|
| 60 |
+
"type": "str",
|
| 61 |
+
"description": "Type of Synapse entity: 'file', 'dataset', 'folder', or 'project'. MUST match actual entity type! Check user hints (e.g., 'files' means entity_type='file') or search results ('node_type' field). Default 'dataset' should only be used for actual datasets.",
|
| 62 |
+
"default": "dataset",
|
| 63 |
+
},
|
| 64 |
+
],
|
| 65 |
+
"required_parameters": [
|
| 66 |
+
{
|
| 67 |
+
"name": "entity_ids",
|
| 68 |
+
"type": "str|list[str]",
|
| 69 |
+
"description": "Synapse entity ID(s) to download. For files: single ID or list of IDs. For datasets/folders/projects: single ID only",
|
| 70 |
+
"default": None,
|
| 71 |
+
}
|
| 72 |
+
],
|
| 73 |
+
},
|
| 74 |
+
]
|
Biomni/mcp_generated/mcp_bioconductor-bsgenome/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y default-jre wget curl && apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
# Install Miniconda
|
| 8 |
+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && bash /tmp/miniconda.sh -b -p /opt/conda && rm /tmp/miniconda.sh
|
| 9 |
+
|
| 10 |
+
# Add conda to PATH
|
| 11 |
+
ENV PATH="/opt/conda/bin:$PATH"
|
| 12 |
+
|
| 13 |
+
# Install bioconductor-bsgenome via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-bsgenome -y && conda clean -a
|
| 15 |
+
|
| 16 |
+
# Install Python dependencies
|
| 17 |
+
RUN pip install uv
|
| 18 |
+
RUN uv pip install --system fastmcp
|
| 19 |
+
|
| 20 |
+
# Create app directory
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Copy your MCP server
|
| 24 |
+
COPY app/bioconductor-bsgenome_server.py /app/
|
| 25 |
+
|
| 26 |
+
# Create workspace and output directories
|
| 27 |
+
RUN mkdir -p /app/workspace /app/output
|
| 28 |
+
|
| 29 |
+
# Make sure the server script is executable
|
| 30 |
+
RUN chmod +x /app/bioconductor-bsgenome_server.py
|
| 31 |
+
|
| 32 |
+
# Expose port for MCP over HTTP (optional)
|
| 33 |
+
EXPOSE 8000
|
| 34 |
+
|
| 35 |
+
# Health check
|
| 36 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)"
|
| 37 |
+
|
| 38 |
+
# Default command runs the MCP server via stdio
|
| 39 |
+
CMD ["python", "/app/bioconductor-bsgenome_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_bsgenome'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-bsgenome/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- bioconductor-bsgenome
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-bsgenome/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-cotan/app/bioconductor-cotan_server.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
import textwrap
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Optional, Dict, Any
|
| 6 |
+
|
| 7 |
+
# This is a placeholder for the MCP decorator.
|
| 8 |
+
# In a real MCP environment, this would be provided by the MCP framework.
|
| 9 |
+
class mcp:
|
| 10 |
+
@staticmethod
|
| 11 |
+
def tool(func):
|
| 12 |
+
def wrapper(*args, **kwargs):
|
| 13 |
+
return func(*args, **kwargs)
|
| 14 |
+
return wrapper
|
| 15 |
+
|
| 16 |
+
@mcp.tool
|
| 17 |
+
def run_cotan_analysis(
|
| 18 |
+
raw_count_matrix: Path,
|
| 19 |
+
output_dir: Path,
|
| 20 |
+
min_reads_per_cell: int = 1000,
|
| 21 |
+
min_cells_per_gene: int = 3,
|
| 22 |
+
num_cores: int = 1,
|
| 23 |
+
run_gdi: bool = True,
|
| 24 |
+
run_clustering: bool = True,
|
| 25 |
+
num_clusters: int = 10,
|
| 26 |
+
save_cotan_object: bool = True,
|
| 27 |
+
) -> Dict[str, Any]:
|
| 28 |
+
"""
|
| 29 |
+
Performs a complete COTAN analysis for single-cell gene co-expression.
|
| 30 |
+
|
| 31 |
+
This tool takes a raw UMI count matrix and runs the standard COTAN workflow,
|
| 32 |
+
which includes data cleaning, dispersion estimation, co-expression calculation,
|
| 33 |
+
Global Differentiation Index (GDI) analysis, and hierarchical clustering of genes.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
raw_count_matrix: Path to the input raw count matrix file (CSV format).
|
| 37 |
+
Rows should represent genes and columns represent cells.
|
| 38 |
+
The first column should contain gene names.
|
| 39 |
+
output_dir: Path to the directory where output files will be saved.
|
| 40 |
+
min_reads_per_cell: Minimum number of reads for a cell to be kept during cleaning.
|
| 41 |
+
min_cells_per_gene: Minimum number of cells where a gene must be expressed to be kept.
|
| 42 |
+
num_cores: Number of CPU cores to use for parallelizable steps.
|
| 43 |
+
run_gdi: If True, calculates the Global Differentiation Index (GDI) for all genes.
|
| 44 |
+
run_clustering: If True, performs hierarchical clustering on the gene co-expression matrix.
|
| 45 |
+
num_clusters: The number of clusters to create if `run_clustering` is True.
|
| 46 |
+
save_cotan_object: If True, saves the final COTAN R object as an RDS file for further analysis.
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
A dictionary containing the execution command, stdout, stderr, and a list of output file paths.
|
| 50 |
+
"""
|
| 51 |
+
# --- Input Validation ---
|
| 52 |
+
if not raw_count_matrix.is_file():
|
| 53 |
+
raise FileNotFoundError(f"Input file not found: {raw_count_matrix}")
|
| 54 |
+
if min_reads_per_cell <= 0:
|
| 55 |
+
raise ValueError("min_reads_per_cell must be a positive integer.")
|
| 56 |
+
if min_cells_per_gene <= 0:
|
| 57 |
+
raise ValueError("min_cells_per_gene must be a positive integer.")
|
| 58 |
+
if num_cores <= 0:
|
| 59 |
+
raise ValueError("num_cores must be a positive integer.")
|
| 60 |
+
if run_clustering and num_clusters <= 1:
|
| 61 |
+
raise ValueError("num_clusters must be greater than 1 for clustering.")
|
| 62 |
+
|
| 63 |
+
# --- File Path Handling ---
|
| 64 |
+
try:
|
| 65 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 66 |
+
except OSError as e:
|
| 67 |
+
raise OSError(f"Could not create output directory {output_dir}: {e}")
|
| 68 |
+
|
| 69 |
+
output_files = []
|
| 70 |
+
|
| 71 |
+
# --- R Script Generation ---
|
| 72 |
+
# Note: R booleans are TRUE/FALSE
|
| 73 |
+
r_script_content = textwrap.dedent(f"""
|
| 74 |
+
# Load necessary libraries
|
| 75 |
+
library(COTAN)
|
| 76 |
+
library(Matrix)
|
| 77 |
+
|
| 78 |
+
print("Starting COTAN analysis workflow...")
|
| 79 |
+
|
| 80 |
+
# --- Parameters ---
|
| 81 |
+
raw_count_matrix_path <- "{raw_count_matrix.resolve()}"
|
| 82 |
+
output_dir_path <- "{output_dir.resolve()}"
|
| 83 |
+
min_reads <- {min_reads_per_cell}
|
| 84 |
+
min_cells <- {min_cells_per_gene}
|
| 85 |
+
num_cores <- {num_cores}
|
| 86 |
+
run_gdi_calc <- {"TRUE" if run_gdi else "FALSE"}
|
| 87 |
+
run_clust <- {"TRUE" if run_clustering else "FALSE"}
|
| 88 |
+
k_clusters <- {num_clusters}
|
| 89 |
+
save_obj <- {"TRUE" if save_cotan_object else "FALSE"}
|
| 90 |
+
|
| 91 |
+
# --- Data Loading ---
|
| 92 |
+
print(paste("Loading raw count matrix from:", raw_count_matrix_path))
|
| 93 |
+
# Using data.table for fast reading, assuming it's installed as a dependency
|
| 94 |
+
# or falling back to read.csv
|
| 95 |
+
if (requireNamespace("data.table", quietly = TRUE)) {{
|
| 96 |
+
raw_data <- data.table::fread(raw_count_matrix_path, data.table = FALSE)
|
| 97 |
+
rownames(raw_data) <- raw_data[,1]
|
| 98 |
+
raw_data[,1] <- NULL
|
| 99 |
+
}} else {{
|
| 100 |
+
raw_data <- read.csv(raw_count_matrix_path, header = TRUE, row.names = 1, check.names = FALSE)
|
| 101 |
+
}}
|
| 102 |
+
raw_data <- as.matrix(raw_data)
|
| 103 |
+
print("Data loaded successfully.")
|
| 104 |
+
|
| 105 |
+
# --- COTAN Object Creation and Cleaning ---
|
| 106 |
+
obj <- COTAN(raw = raw_data)
|
| 107 |
+
print("COTAN object created.")
|
| 108 |
+
|
| 109 |
+
obj <- clean(obj, min.reads = min_reads, min.cells = min_cells, calcExtraData = FALSE)
|
| 110 |
+
print("Data cleaning complete.")
|
| 111 |
+
|
| 112 |
+
# --- Core Analysis ---
|
| 113 |
+
print("Estimating dispersion...")
|
| 114 |
+
obj <- estimateDispersion(obj, cores = num_cores)
|
| 115 |
+
|
| 116 |
+
print("Calculating co-expression matrix...")
|
| 117 |
+
obj <- calculateCoex(obj, cores = num_cores)
|
| 118 |
+
|
| 119 |
+
coex_df <- as.data.frame(as.matrix(getCoex(obj)))
|
| 120 |
+
coex_path <- file.path(output_dir_path, "coex_matrix.csv")
|
| 121 |
+
write.csv(coex_df, file = coex_path)
|
| 122 |
+
print(paste("Co-expression matrix saved to:", coex_path))
|
| 123 |
+
|
| 124 |
+
# --- GDI Calculation ---
|
| 125 |
+
if (run_gdi_calc) {{
|
| 126 |
+
print("Calculating Global Differentiation Index (GDI)...")
|
| 127 |
+
obj <- calculateGDI(obj)
|
| 128 |
+
gdi_df <- getGDI(obj)
|
| 129 |
+
gdi_path <- file.path(output_dir_path, "gdi_table.csv")
|
| 130 |
+
write.csv(gdi_df, file = gdi_path)
|
| 131 |
+
print(paste("GDI table saved to:", gdi_path))
|
| 132 |
+
}}
|
| 133 |
+
|
| 134 |
+
# --- Clustering ---
|
| 135 |
+
if (run_clust) {{
|
| 136 |
+
print("Performing hierarchical clustering on genes...")
|
| 137 |
+
coex_dist <- as.dist(1 - getCoex(obj))
|
| 138 |
+
hc <- hclust(coex_dist, method = "ward.D2")
|
| 139 |
+
clusters <- cutree(hc, k = k_clusters)
|
| 140 |
+
clusters_df <- data.frame(gene = names(clusters), cluster = clusters)
|
| 141 |
+
clusters_path <- file.path(output_dir_path, "gene_clusters.csv")
|
| 142 |
+
write.csv(clusters_df, file = clusters_path, row.names = FALSE)
|
| 143 |
+
print(paste("Gene clusters saved to:", clusters_path))
|
| 144 |
+
}}
|
| 145 |
+
|
| 146 |
+
# --- Save Final Object ---
|
| 147 |
+
if (save_obj) {{
|
| 148 |
+
rds_path <- file.path(output_dir_path, "cotan_object.rds")
|
| 149 |
+
saveRDS(obj, file = rds_path)
|
| 150 |
+
print(paste("COTAN object saved to:", rds_path))
|
| 151 |
+
}}
|
| 152 |
+
|
| 153 |
+
print("COTAN analysis completed successfully.")
|
| 154 |
+
""")
|
| 155 |
+
|
| 156 |
+
# --- Subprocess Execution ---
|
| 157 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 158 |
+
script_path = Path(temp_dir) / "run_cotan.R"
|
| 159 |
+
with open(script_path, "w") as f:
|
| 160 |
+
f.write(r_script_content)
|
| 161 |
+
|
| 162 |
+
command = ["Rscript", str(script_path)]
|
| 163 |
+
command_str = " ".join(command)
|
| 164 |
+
|
| 165 |
+
try:
|
| 166 |
+
process = subprocess.run(
|
| 167 |
+
command,
|
| 168 |
+
capture_output=True,
|
| 169 |
+
text=True,
|
| 170 |
+
check=True,
|
| 171 |
+
)
|
| 172 |
+
stdout = process.stdout
|
| 173 |
+
stderr = process.stderr
|
| 174 |
+
|
| 175 |
+
# --- Collect Output Files ---
|
| 176 |
+
output_files.append(str(output_dir / "coex_matrix.csv"))
|
| 177 |
+
if run_gdi:
|
| 178 |
+
output_files.append(str(output_dir / "gdi_table.csv"))
|
| 179 |
+
if run_clustering:
|
| 180 |
+
output_files.append(str(output_dir / "gene_clusters.csv"))
|
| 181 |
+
if save_cotan_object:
|
| 182 |
+
output_files.append(str(output_dir / "cotan_object.rds"))
|
| 183 |
+
|
| 184 |
+
# Verify that files were actually created
|
| 185 |
+
output_files = [p for p in output_files if Path(p).exists()]
|
| 186 |
+
|
| 187 |
+
except FileNotFoundError:
|
| 188 |
+
return {
|
| 189 |
+
"command_executed": command_str,
|
| 190 |
+
"stdout": "",
|
| 191 |
+
"stderr": "Error: Rscript not found. Please ensure R is installed and in your PATH.",
|
| 192 |
+
"output_files": [],
|
| 193 |
+
}
|
| 194 |
+
except subprocess.CalledProcessError as e:
|
| 195 |
+
return {
|
| 196 |
+
"command_executed": command_str,
|
| 197 |
+
"stdout": e.stdout,
|
| 198 |
+
"stderr": e.stderr,
|
| 199 |
+
"output_files": [],
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
# --- Structured Result Return ---
|
| 203 |
+
return {
|
| 204 |
+
"command_executed": command_str,
|
| 205 |
+
"stdout": stdout,
|
| 206 |
+
"stderr": stderr,
|
| 207 |
+
"output_files": output_files,
|
| 208 |
+
}
|
Biomni/mcp_generated/mcp_bioconductor-cotan/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-cotan:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-cotan:latest
|
| 7 |
+
container_name: mcp-bioconductor-cotan
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-cotan
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cotan/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-cotan
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-cotan/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-dnacopy/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-dnacopy via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-dnacopy -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-dnacopy_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-dnacopy_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-dnacopy_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-dnacopy/app/bioconductor-dnacopy_server.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Dict, List, Optional, Union
|
| 5 |
+
|
| 6 |
+
# This is a placeholder for the MCP decorator.
|
| 7 |
+
# In a real MCP environment, this would be imported.
|
| 8 |
+
def tool(func):
|
| 9 |
+
"""A dummy decorator to mimic @mcp.tool() for standalone execution."""
|
| 10 |
+
return func
|
| 11 |
+
|
| 12 |
+
@tool
|
| 13 |
+
def dnacopy_segment(
|
| 14 |
+
input_file: Path,
|
| 15 |
+
output_prefix: str,
|
| 16 |
+
sample_id_col: str,
|
| 17 |
+
chrom_col: str,
|
| 18 |
+
maploc_col: str,
|
| 19 |
+
data_col: str,
|
| 20 |
+
smooth_cna: bool = True,
|
| 21 |
+
make_plot: bool = True,
|
| 22 |
+
alpha: float = 0.01,
|
| 23 |
+
nperm: int = 10000,
|
| 24 |
+
p_method: str = "hybrid",
|
| 25 |
+
min_width: int = 2,
|
| 26 |
+
undo_splits: str = "sdundo",
|
| 27 |
+
undo_sd: float = 3.0,
|
| 28 |
+
trim: float = 0.025,
|
| 29 |
+
verbose: int = 1,
|
| 30 |
+
) -> Dict[str, Union[str, List[str]]]:
|
| 31 |
+
"""
|
| 32 |
+
Performs Circular Binary Segmentation (CBS) on copy number data using the R package DNAcopy.
|
| 33 |
+
|
| 34 |
+
This tool takes a tab-delimited file containing genomic data (chromosome, position, log-ratio)
|
| 35 |
+
and segments it to identify regions of equal copy number.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
input_file: Path to the input tab-delimited data file.
|
| 39 |
+
output_prefix: Prefix for the output files (e.g., 'my_sample').
|
| 40 |
+
sample_id_col: Name of the column containing sample identifiers.
|
| 41 |
+
chrom_col: Name of the column containing chromosome identifiers.
|
| 42 |
+
maploc_col: Name of the column containing genomic map locations (positions).
|
| 43 |
+
data_col: Name of the column containing the log2 ratio copy number data.
|
| 44 |
+
smooth_cna: If True, apply the `smooth.CNA` function before segmentation. Defaults to True.
|
| 45 |
+
make_plot: If True, generate a plot of the segmentation results as a PDF. Defaults to True.
|
| 46 |
+
alpha: Significance level to accept a change-point. Defaults to 0.01.
|
| 47 |
+
nperm: Number of permutations for `p.method='perm'`. Defaults to 10000.
|
| 48 |
+
p_method: Method to compute p-values. Must be 'hybrid' or 'perm'. Defaults to 'hybrid'.
|
| 49 |
+
min_width: The minimum number of markers for a segment. Defaults to 2.
|
| 50 |
+
undo_splits: Method for undoing splits. Must be 'none', 'prune', or 'sdundo'. Defaults to 'sdundo'.
|
| 51 |
+
undo_sd: The number of standard deviations for the undoing rule. Defaults to 3.0.
|
| 52 |
+
trim: Proportion of data to be trimmed for variance calculation. Defaults to 0.025.
|
| 53 |
+
verbose: Verbosity level (0, 1, or 2). Defaults to 1.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
A dictionary containing the executed command, stdout, stderr, and a list of output file paths.
|
| 57 |
+
"""
|
| 58 |
+
# --- 1. Input Validation ---
|
| 59 |
+
if not input_file.is_file():
|
| 60 |
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
| 61 |
+
|
| 62 |
+
p_method_choices = ["hybrid", "perm"]
|
| 63 |
+
if p_method not in p_method_choices:
|
| 64 |
+
raise ValueError(f"p_method must be one of {p_method_choices}, not '{p_method}'")
|
| 65 |
+
|
| 66 |
+
undo_splits_choices = ["none", "prune", "sdundo"]
|
| 67 |
+
if undo_splits not in undo_splits_choices:
|
| 68 |
+
raise ValueError(f"undo_splits must be one of {undo_splits_choices}, not '{undo_splits}'")
|
| 69 |
+
|
| 70 |
+
if not (0 < alpha < 1):
|
| 71 |
+
raise ValueError("alpha must be between 0 and 1.")
|
| 72 |
+
|
| 73 |
+
if not (0 <= trim < 0.5):
|
| 74 |
+
raise ValueError("trim must be between 0 and 0.5.")
|
| 75 |
+
|
| 76 |
+
if nperm <= 0:
|
| 77 |
+
raise ValueError("nperm must be a positive integer.")
|
| 78 |
+
|
| 79 |
+
if min_width < 2:
|
| 80 |
+
raise ValueError("min_width must be an integer >= 2.")
|
| 81 |
+
|
| 82 |
+
if verbose not in [0, 1, 2]:
|
| 83 |
+
raise ValueError("verbose must be 0, 1, or 2.")
|
| 84 |
+
|
| 85 |
+
# --- 2. Define Output Paths ---
|
| 86 |
+
output_seg_file = Path(f"{output_prefix}.seg")
|
| 87 |
+
output_plot_file = Path(f"{output_prefix}.pdf")
|
| 88 |
+
output_files = [str(output_seg_file)]
|
| 89 |
+
if make_plot:
|
| 90 |
+
output_files.append(str(output_plot_file))
|
| 91 |
+
|
| 92 |
+
# --- 3. Generate R Script ---
|
| 93 |
+
# This script reads the data, creates a CNA object, optionally smooths it,
|
| 94 |
+
# runs segmentation, and writes the output. It also handles non-numeric
|
| 95 |
+
# chromosome names like 'X' and 'Y'.
|
| 96 |
+
r_script = f"""
|
| 97 |
+
# Load the library
|
| 98 |
+
library(DNAcopy)
|
| 99 |
+
|
| 100 |
+
# Read the input data
|
| 101 |
+
cat("Reading input file: {input_file}\\n")
|
| 102 |
+
raw_data <- read.table("{input_file}", header=TRUE, sep="\\t", stringsAsFactors=FALSE, check.names=FALSE)
|
| 103 |
+
|
| 104 |
+
# Ensure required columns exist
|
| 105 |
+
required_cols <- c("{sample_id_col}", "{chrom_col}", "{maploc_col}", "{data_col}")
|
| 106 |
+
if (!all(required_cols %in% colnames(raw_data))) {{
|
| 107 |
+
stop("One or more required columns not found in the input file.")
|
| 108 |
+
}}
|
| 109 |
+
|
| 110 |
+
# Pre-process chromosome column to handle non-numeric values (e.g., X, Y)
|
| 111 |
+
cat("Processing chromosome identifiers...\\n")
|
| 112 |
+
raw_data[["{chrom_col}"]] <- as.character(raw_data[["{chrom_col}"]])
|
| 113 |
+
raw_data[["{chrom_col}"]][toupper(raw_data[["{chrom_col}"]]) == 'X'] <- 23
|
| 114 |
+
raw_data[["{chrom_col}"]][toupper(raw_data[["{chrom_col}"]]) == 'Y'] <- 24
|
| 115 |
+
raw_data[["{chrom_col}"]][toupper(raw_data[["{chrom_col}"]]) == 'MT'] <- 25
|
| 116 |
+
raw_data[["{chrom_col}"]] <- suppressWarnings(as.numeric(raw_data[["{chrom_col}"]]))
|
| 117 |
+
|
| 118 |
+
# Filter out rows with NA values in key columns after conversion
|
| 119 |
+
raw_data <- raw_data[
|
| 120 |
+
!is.na(raw_data[["{chrom_col}"]]) &
|
| 121 |
+
!is.na(raw_data[["{maploc_col}"]]) &
|
| 122 |
+
!is.na(raw_data[["{data_col}"]]),
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
# Create CNA object
|
| 126 |
+
cat("Creating CNA object...\\n")
|
| 127 |
+
CNA.object <- CNA(
|
| 128 |
+
genomdat = raw_data[["{data_col}"]],
|
| 129 |
+
chrom = raw_data[["{chrom_col}"]],
|
| 130 |
+
maploc = raw_data[["{maploc_col}"]],
|
| 131 |
+
data.type = 'logratio',
|
| 132 |
+
sampleid = raw_data[["{sample_id_col}"]]
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
# Smooth the data if requested
|
| 136 |
+
target_object <- CNA.object
|
| 137 |
+
if ({str(smooth_cna).upper()}) {{
|
| 138 |
+
cat("Smoothing CNA object...\\n")
|
| 139 |
+
target_object <- smooth.CNA(CNA.object)
|
| 140 |
+
}}
|
| 141 |
+
|
| 142 |
+
# Perform segmentation
|
| 143 |
+
cat("Performing segmentation...\\n")
|
| 144 |
+
segmentation <- segment(
|
| 145 |
+
target_object,
|
| 146 |
+
alpha = {alpha},
|
| 147 |
+
nperm = {nperm},
|
| 148 |
+
p.method = "{p_method}",
|
| 149 |
+
min.width = {min_width},
|
| 150 |
+
undo.splits = "{undo_splits}",
|
| 151 |
+
undo.SD = {undo_sd},
|
| 152 |
+
trim = {trim},
|
| 153 |
+
verbose = {verbose}
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
# Write segmentation output
|
| 157 |
+
cat("Writing segmentation output to {output_seg_file}\\n")
|
| 158 |
+
write.table(segmentation$output, file="{output_seg_file}", sep="\\t", quote=FALSE, row.names=FALSE)
|
| 159 |
+
|
| 160 |
+
# Generate plot if requested
|
| 161 |
+
if ({str(make_plot).upper()}) {{
|
| 162 |
+
cat("Generating plot at {output_plot_file}\\n")
|
| 163 |
+
pdf("{output_plot_file}")
|
| 164 |
+
plot(segmentation, plot.type="w")
|
| 165 |
+
dev.off()
|
| 166 |
+
}}
|
| 167 |
+
|
| 168 |
+
cat("DNAcopy analysis complete.\\n")
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
# --- 4. Execute R Script ---
|
| 172 |
+
command = []
|
| 173 |
+
try:
|
| 174 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as tmp_script:
|
| 175 |
+
tmp_script.write(r_script)
|
| 176 |
+
script_path = tmp_script.name
|
| 177 |
+
|
| 178 |
+
command = ["Rscript", script_path]
|
| 179 |
+
process = subprocess.run(
|
| 180 |
+
command,
|
| 181 |
+
check=True,
|
| 182 |
+
capture_output=True,
|
| 183 |
+
text=True,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
except FileNotFoundError:
|
| 187 |
+
raise RuntimeError("Rscript not found. Please ensure R is installed and in your PATH.")
|
| 188 |
+
except subprocess.CalledProcessError as e:
|
| 189 |
+
# Provide a more informative error message
|
| 190 |
+
error_message = (
|
| 191 |
+
f"DNAcopy R script failed with exit code {e.returncode}.\n"
|
| 192 |
+
f"Command: {' '.join(command)}\n"
|
| 193 |
+
f"Stdout: {e.stdout}\n"
|
| 194 |
+
f"Stderr: {e.stderr}"
|
| 195 |
+
)
|
| 196 |
+
raise RuntimeError(error_message) from e
|
| 197 |
+
finally:
|
| 198 |
+
# Clean up the temporary script file
|
| 199 |
+
if 'script_path' in locals() and Path(script_path).exists():
|
| 200 |
+
Path(script_path).unlink()
|
| 201 |
+
|
| 202 |
+
# --- 5. Return Structured Output ---
|
| 203 |
+
return {
|
| 204 |
+
"command_executed": " ".join(command),
|
| 205 |
+
"stdout": process.stdout,
|
| 206 |
+
"stderr": process.stderr,
|
| 207 |
+
"output_files": output_files,
|
| 208 |
+
}
|
Biomni/mcp_generated/mcp_bioconductor-dnacopy/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-dnacopy/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-dnacopy:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-dnacopy:latest
|
| 7 |
+
container_name: mcp-bioconductor-dnacopy
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-dnacopy
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-gsva/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-gsva via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-gsva -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 bioconductor-gsva_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-gsva_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-gsva_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-gsva/app/bioconductor-gsva_server.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, Literal, List
|
| 5 |
+
import sys
|
| 6 |
+
|
| 7 |
+
# MCP decorator placeholder, as per the instructions.
|
| 8 |
+
class mcp:
|
| 9 |
+
@staticmethod
|
| 10 |
+
def tool():
|
| 11 |
+
def decorator(func):
|
| 12 |
+
return func
|
| 13 |
+
return decorator
|
| 14 |
+
|
| 15 |
+
from mcp.server.fastmcp import FastMCP
|
| 16 |
+
|
| 17 |
+
SERVER_NAME = 'local_bioconductor_gsva'
|
| 18 |
+
mcp = FastMCP(SERVER_NAME)
|
| 19 |
+
|
| 20 |
+
@mcp.tool()
|
| 21 |
+
def gsva(
|
| 22 |
+
expr_file: Path,
|
| 23 |
+
gene_sets_file: Path,
|
| 24 |
+
output_file: Optional[Path] = None,
|
| 25 |
+
method: Literal["gsva", "ssgsea", "zscore", "plage"] = "gsva",
|
| 26 |
+
kcdf: Literal["Gaussian", "Poisson", "none"] = "Gaussian",
|
| 27 |
+
abs_ranking: bool = False,
|
| 28 |
+
min_sz: int = 1,
|
| 29 |
+
max_sz: Optional[int] = None,
|
| 30 |
+
parallel_sz: int = 1,
|
| 31 |
+
mx_diff: bool = True,
|
| 32 |
+
tau: float = 1.0,
|
| 33 |
+
ssgsea_norm: bool = True,
|
| 34 |
+
verbose: bool = True,
|
| 35 |
+
) -> dict:
|
| 36 |
+
"""
|
| 37 |
+
Performs Gene Set Variation Analysis (GSVA) on gene expression data.
|
| 38 |
+
|
| 39 |
+
This tool is a wrapper around the `gsva` function from the Bioconductor GSVA package.
|
| 40 |
+
It calculates sample-wise gene set enrichment scores as a function of genes inside and
|
| 41 |
+
outside the gene set, respectively. The tool requires R and the 'Rscript'
|
| 42 |
+
executable to be in the system's PATH.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
expr_file: Path to the gene expression data file (e.g., TSV, CSV).
|
| 46 |
+
The file should have genes as rows and samples as columns.
|
| 47 |
+
The first column must contain unique gene identifiers.
|
| 48 |
+
gene_sets_file: Path to the gene sets file in GMT format.
|
| 49 |
+
output_file: Optional path for the output TSV file. If not provided, a file
|
| 50 |
+
named 'gsva_results.tsv' will be created in a temporary directory
|
| 51 |
+
and its path will be returned.
|
| 52 |
+
method: The GSVA method to use.
|
| 53 |
+
kcdf: Character string denoting the kernel to use during the non-parametric
|
| 54 |
+
estimation of the cumulative distribution function of expression
|
| 55 |
+
levels across samples. 'Gaussian' is recommended for continuous data,
|
| 56 |
+
'Poisson' for integer counts.
|
| 57 |
+
abs_ranking: For 'gsva' method, whether to use absolute values of rankings.
|
| 58 |
+
min_sz: Minimum size of the gene sets to be considered.
|
| 59 |
+
max_sz: Maximum size of the gene sets to be considered. If None, no maximum size is enforced.
|
| 60 |
+
parallel_sz: Number of parallel processes to use (cores).
|
| 61 |
+
mx_diff: For 'gsva' and 'ssgsea' methods, whether to calculate enrichment scores
|
| 62 |
+
as the difference between the largest positive and negative deviations
|
| 63 |
+
from the random walk.
|
| 64 |
+
tau: Exponent defining the weight of the tail in the random walk score.
|
| 65 |
+
Only used for the 'gsva' method.
|
| 66 |
+
ssgsea_norm: For 'ssgsea' method, whether to normalize enrichment scores.
|
| 67 |
+
verbose: If True, the R script will print progress messages to stdout.
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
A dictionary containing the command executed, stdout, stderr, and a list of output files.
|
| 71 |
+
"""
|
| 72 |
+
# --- Input Validation ---
|
| 73 |
+
if not expr_file.exists():
|
| 74 |
+
raise FileNotFoundError(f"Expression file not found: {expr_file}")
|
| 75 |
+
if not gene_sets_file.exists():
|
| 76 |
+
raise FileNotFoundError(f"Gene sets file not found: {gene_sets_file}")
|
| 77 |
+
if min_sz < 0:
|
| 78 |
+
raise ValueError("min_sz must be a non-negative integer.")
|
| 79 |
+
if max_sz is not None and max_sz < min_sz:
|
| 80 |
+
raise ValueError("max_sz must be greater than or equal to min_sz.")
|
| 81 |
+
if parallel_sz < 1:
|
| 82 |
+
raise ValueError("parallel_sz must be at least 1.")
|
| 83 |
+
if tau <= 0 and method == "gsva":
|
| 84 |
+
raise ValueError("tau must be a positive number for the 'gsva' method.")
|
| 85 |
+
|
| 86 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 87 |
+
temp_path = Path(temp_dir)
|
| 88 |
+
|
| 89 |
+
# Define output file path
|
| 90 |
+
if output_file:
|
| 91 |
+
# Ensure output directory exists
|
| 92 |
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
| 93 |
+
final_output_path = output_file
|
| 94 |
+
else:
|
| 95 |
+
final_output_path = temp_path / "gsva_results.tsv"
|
| 96 |
+
|
| 97 |
+
r_script_path = temp_path / "run_gsva.R"
|
| 98 |
+
|
| 99 |
+
# Convert Python types to R-compatible strings
|
| 100 |
+
r_abs_ranking = "TRUE" if abs_ranking else "FALSE"
|
| 101 |
+
r_mx_diff = "TRUE" if mx_diff else "FALSE"
|
| 102 |
+
r_ssgsea_norm = "TRUE" if ssgsea_norm else "FALSE"
|
| 103 |
+
r_verbose = "TRUE" if verbose else "FALSE"
|
| 104 |
+
r_max_sz = "Inf" if max_sz is None else str(max_sz)
|
| 105 |
+
|
| 106 |
+
# --- R Script Generation ---
|
| 107 |
+
r_script_content = f"""
|
| 108 |
+
# This script automatically installs required Bioconductor packages.
|
| 109 |
+
if (!requireNamespace("BiocManager", quietly = TRUE))
|
| 110 |
+
install.packages("BiocManager", repos="http://cran.us.r-project.org")
|
| 111 |
+
|
| 112 |
+
packages <- c("GSVA", "GSEABase")
|
| 113 |
+
for (pkg in packages) {{
|
| 114 |
+
if (!require(pkg, character.only = TRUE)) {{
|
| 115 |
+
cat(paste("Installing", pkg, "...\\n"))
|
| 116 |
+
BiocManager::install(pkg, update=FALSE, ask=FALSE)
|
| 117 |
+
library(pkg, character.only = TRUE)
|
| 118 |
+
}}
|
| 119 |
+
}}
|
| 120 |
+
|
| 121 |
+
# --- Arguments ---
|
| 122 |
+
expr_file <- "{expr_file.resolve()}"
|
| 123 |
+
gene_sets_file <- "{gene_sets_file.resolve()}"
|
| 124 |
+
output_file <- "{final_output_path.resolve()}"
|
| 125 |
+
method <- "{method}"
|
| 126 |
+
kcdf <- "{kcdf}"
|
| 127 |
+
abs_ranking <- {r_abs_ranking}
|
| 128 |
+
min_sz <- {min_sz}
|
| 129 |
+
max_sz <- {r_max_sz}
|
| 130 |
+
parallel_sz <- {parallel_sz}
|
| 131 |
+
mx_diff <- {r_mx_diff}
|
| 132 |
+
tau <- {tau}
|
| 133 |
+
ssgsea_norm <- {r_ssgsea_norm}
|
| 134 |
+
verbose <- {r_verbose}
|
| 135 |
+
|
| 136 |
+
# --- Data Loading ---
|
| 137 |
+
if (verbose) cat("Loading expression data from:", expr_file, "\\n")
|
| 138 |
+
# Assuming a standard tab-separated file with a header and row names (gene IDs)
|
| 139 |
+
expr_data <- tryCatch({{
|
| 140 |
+
as.matrix(read.table(expr_file, header=TRUE, row.names=1, sep='\\t', check.names=FALSE, quote=""))
|
| 141 |
+
}}, error = function(e) {{
|
| 142 |
+
# Try with comma separator if tab fails
|
| 143 |
+
if (verbose) cat("Failed to read as TSV, trying CSV...\\n")
|
| 144 |
+
as.matrix(read.table(expr_file, header=TRUE, row.names=1, sep=',', check.names=FALSE, quote=""))
|
| 145 |
+
}})
|
| 146 |
+
|
| 147 |
+
if (verbose) cat("Loading gene sets from:", gene_sets_file, "\\n")
|
| 148 |
+
gene_sets <- getGmt(gene_sets_file)
|
| 149 |
+
|
| 150 |
+
# --- Run GSVA ---
|
| 151 |
+
if (verbose) cat("Running GSVA with method:", method, "\\n")
|
| 152 |
+
gsva_results <- gsva(
|
| 153 |
+
expr = expr_data,
|
| 154 |
+
gset.idx.list = gene_sets,
|
| 155 |
+
method = method,
|
| 156 |
+
kcdf = kcdf,
|
| 157 |
+
abs.ranking = abs_ranking,
|
| 158 |
+
min.sz = min_sz,
|
| 159 |
+
max.sz = max_sz,
|
| 160 |
+
parallel.sz = parallel_sz,
|
| 161 |
+
mx.diff = mx_diff,
|
| 162 |
+
tau = tau,
|
| 163 |
+
ssgsea.norm = ssgsea_norm,
|
| 164 |
+
verbose = verbose
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# --- Save Results ---
|
| 168 |
+
if (verbose) cat("Saving results to:", output_file, "\\n")
|
| 169 |
+
write.table(gsva_results, file=output_file, sep='\\t', quote=FALSE, col.names=NA)
|
| 170 |
+
|
| 171 |
+
if (verbose) cat("GSVA analysis completed successfully.\\n")
|
| 172 |
+
"""
|
| 173 |
+
|
| 174 |
+
with open(r_script_path, "w") as f:
|
| 175 |
+
f.write(r_script_content)
|
| 176 |
+
|
| 177 |
+
# --- Subprocess Execution ---
|
| 178 |
+
command = ["Rscript", str(r_script_path)]
|
| 179 |
+
command_str = " ".join(map(str, command))
|
| 180 |
+
|
| 181 |
+
try:
|
| 182 |
+
process = subprocess.run(
|
| 183 |
+
command,
|
| 184 |
+
capture_output=True,
|
| 185 |
+
text=True,
|
| 186 |
+
check=True,
|
| 187 |
+
cwd=temp_path,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
output_files_list = [str(final_output_path)] if final_output_path.exists() else []
|
| 191 |
+
|
| 192 |
+
return {
|
| 193 |
+
"command_executed": command_str,
|
| 194 |
+
"stdout": process.stdout,
|
| 195 |
+
"stderr": process.stderr,
|
| 196 |
+
"output_files": output_files_list,
|
| 197 |
+
}
|
| 198 |
+
except FileNotFoundError:
|
| 199 |
+
# This happens if 'Rscript' is not in the system's PATH
|
| 200 |
+
return {
|
| 201 |
+
"command_executed": command_str,
|
| 202 |
+
"stdout": "",
|
| 203 |
+
"stderr": "Error: 'Rscript' command not found. Please ensure R is installed and in your system's PATH.",
|
| 204 |
+
"output_files": [],
|
| 205 |
+
}
|
| 206 |
+
except subprocess.CalledProcessError as e:
|
| 207 |
+
return {
|
| 208 |
+
"command_executed": command_str,
|
| 209 |
+
"stdout": e.stdout,
|
| 210 |
+
"stderr": e.stderr,
|
| 211 |
+
"output_files": [],
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
if __name__ == "__main__":
|
| 215 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-gsva/app/bioconductor-gsva_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-gsva/app/bioconductor-gsva_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_gsva'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-gsva/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-gsva
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-gsva/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-poem/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-poem via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-poem -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-poem_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-poem_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-poem_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-poem/app/bioconductor-poem_server.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import Optional, Dict
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# Configure logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# This is a mock decorator for development and testing purposes.
|
| 12 |
+
# In a real MCP environment, this would be provided by the MCP framework.
|
| 13 |
+
class mcp:
|
| 14 |
+
@staticmethod
|
| 15 |
+
def tool():
|
| 16 |
+
def decorator(func):
|
| 17 |
+
return func
|
| 18 |
+
return decorator
|
| 19 |
+
|
| 20 |
+
from mcp.server.fastmcp import FastMCP
|
| 21 |
+
|
| 22 |
+
SERVER_NAME = 'local_bioconductor_poem'
|
| 23 |
+
mcp = FastMCP(SERVER_NAME)
|
| 24 |
+
|
| 25 |
+
@mcp.tool()
|
| 26 |
+
def poem_evaluate(
|
| 27 |
+
clustering_results: Path,
|
| 28 |
+
output_prefix: str,
|
| 29 |
+
ground_truth_labels: Optional[Path] = None,
|
| 30 |
+
spatial_coordinates: Optional[Path] = None,
|
| 31 |
+
embedding_coordinates: Optional[Path] = None,
|
| 32 |
+
calculate_external_metrics: bool = True,
|
| 33 |
+
calculate_internal_metrics: bool = True,
|
| 34 |
+
calculate_spatial_metrics: bool = True,
|
| 35 |
+
generate_spatial_error_plot: bool = False,
|
| 36 |
+
) -> Dict:
|
| 37 |
+
"""
|
| 38 |
+
Run POpulation-based Evaluation Metrics (poem) analysis on clustering or spatial domain results.
|
| 39 |
+
|
| 40 |
+
This tool serves as a wrapper for the Bioconductor 'poem' R package. It generates and executes an R script
|
| 41 |
+
to calculate various evaluation metrics based on the provided input files. Since 'poem' is an R library
|
| 42 |
+
and not a command-line tool, this function synthesizes a command-line-like interface to its core functionalities.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
clustering_results: Path to a single-column file (CSV/TSV, no header) containing cluster assignments for each cell/spot.
|
| 46 |
+
output_prefix: A string prefix for all output files (e.g., 'my_analysis').
|
| 47 |
+
ground_truth_labels: Optional path to a single-column file with ground truth labels for external validation.
|
| 48 |
+
spatial_coordinates: Optional path to a two-column file (CSV/TSV, no header) with X and Y spatial coordinates.
|
| 49 |
+
embedding_coordinates: Optional path to a multi-column file (CSV/TSV, no header) with embedding coordinates (e.g., UMAP, PCA).
|
| 50 |
+
calculate_external_metrics: If True, calculate external metrics like ARI. Requires `ground_truth_labels`.
|
| 51 |
+
calculate_internal_metrics: If True, calculate internal metrics like Silhouette score. Requires `embedding_coordinates` or `spatial_coordinates`.
|
| 52 |
+
calculate_spatial_metrics: If True, calculate spatial-aware metrics. Requires `spatial_coordinates`.
|
| 53 |
+
generate_spatial_error_plot: If True, generate a plot visualizing spatial errors. Requires `spatial_coordinates` and `ground_truth_labels`.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
A dictionary containing the executed command, stdout, stderr, and a list of output file paths.
|
| 57 |
+
"""
|
| 58 |
+
# --- 1. Input Validation ---
|
| 59 |
+
if not clustering_results.is_file():
|
| 60 |
+
raise FileNotFoundError(f"Clustering results file not found: {clustering_results}")
|
| 61 |
+
if ground_truth_labels and not ground_truth_labels.is_file():
|
| 62 |
+
raise FileNotFoundError(f"Ground truth labels file not found: {ground_truth_labels}")
|
| 63 |
+
if spatial_coordinates and not spatial_coordinates.is_file():
|
| 64 |
+
raise FileNotFoundError(f"Spatial coordinates file not found: {spatial_coordinates}")
|
| 65 |
+
if embedding_coordinates and not embedding_coordinates.is_file():
|
| 66 |
+
raise FileNotFoundError(f"Embedding coordinates file not found: {embedding_coordinates}")
|
| 67 |
+
|
| 68 |
+
if calculate_external_metrics and not ground_truth_labels:
|
| 69 |
+
raise ValueError("`ground_truth_labels` must be provided to calculate external metrics.")
|
| 70 |
+
if (calculate_spatial_metrics or generate_spatial_error_plot) and not spatial_coordinates:
|
| 71 |
+
raise ValueError("`spatial_coordinates` must be provided for spatial metrics or plots.")
|
| 72 |
+
if calculate_internal_metrics and not (embedding_coordinates or spatial_coordinates):
|
| 73 |
+
raise ValueError("Either `embedding_coordinates` or `spatial_coordinates` must be provided for internal metrics.")
|
| 74 |
+
|
| 75 |
+
# --- 2. Temporary Directory and Script Generation ---
|
| 76 |
+
with tempfile.TemporaryDirectory() as temp_dir_str:
|
| 77 |
+
temp_dir = Path(temp_dir_str)
|
| 78 |
+
r_script_path = temp_dir / "run_poem.R"
|
| 79 |
+
output_metrics_path = temp_dir / f"{output_prefix}_metrics.csv"
|
| 80 |
+
output_plot_path = temp_dir / f"{output_prefix}_spatial_error.png"
|
| 81 |
+
|
| 82 |
+
# NOTE: The R function names (`calculateExternalMetrics`, `plotSpatialErrors`, etc.) are plausible
|
| 83 |
+
# placeholders based on the package's description, as the exact function names are not available
|
| 84 |
+
# in the provided documentation. This script demonstrates a robust way to wrap such a library.
|
| 85 |
+
r_script_content = f"""
|
| 86 |
+
options(warn = -1)
|
| 87 |
+
suppressPackageStartupMessages({{
|
| 88 |
+
library(poem)
|
| 89 |
+
library(SpatialExperiment)
|
| 90 |
+
library(SummarizedExperiment)
|
| 91 |
+
library(ggplot2)
|
| 92 |
+
}})
|
| 93 |
+
|
| 94 |
+
cat("--- Loading Data ---\\n")
|
| 95 |
+
clusters <- read.csv('{clustering_results.resolve()}', header = FALSE, col.names = c('cluster'))$cluster
|
| 96 |
+
|
| 97 |
+
metrics_list <- list()
|
| 98 |
+
|
| 99 |
+
# --- Create a base data object (SummarizedExperiment or SpatialExperiment) ---
|
| 100 |
+
sce <- NULL
|
| 101 |
+
if (!is.null('{spatial_coordinates.resolve() if spatial_coordinates else ''}') || !is.null('{embedding_coordinates.resolve() if embedding_coordinates else ''}')) {{
|
| 102 |
+
counts <- matrix(0, nrow = 1, ncol = length(clusters)) # Dummy assay
|
| 103 |
+
colData <- data.frame(cluster_id = as.factor(clusters))
|
| 104 |
+
|
| 105 |
+
if (!is.null('{spatial_coordinates.resolve() if spatial_coordinates else ''}')) {{
|
| 106 |
+
spatial_coords <- as.matrix(read.csv('{spatial_coordinates.resolve()}', header = FALSE))
|
| 107 |
+
colnames(spatial_coords) <- c('x', 'y')
|
| 108 |
+
sce <- SpatialExperiment(assays = list(counts = counts), colData = colData, spatialCoords = spatial_coords)
|
| 109 |
+
cat("Created SpatialExperiment object.\\n")
|
| 110 |
+
}} else {{
|
| 111 |
+
sce <- SummarizedExperiment(assays = list(counts = counts), colData = colData)
|
| 112 |
+
embedding_coords <- as.matrix(read.csv('{embedding_coordinates.resolve()}', header = FALSE))
|
| 113 |
+
reducedDim(sce, "embedding") <- embedding_coords
|
| 114 |
+
cat("Created SummarizedExperiment object with embeddings.\\n")
|
| 115 |
+
}}
|
| 116 |
+
}}
|
| 117 |
+
|
| 118 |
+
# --- External Metrics ---
|
| 119 |
+
if ({str(calculate_external_metrics).upper()}) {{
|
| 120 |
+
cat("\\n--- Calculating External Metrics ---\\n")
|
| 121 |
+
ground_truth <- read.csv('{ground_truth_labels.resolve()}', header = FALSE, col.names = c('label'))$label
|
| 122 |
+
# Using a known function from 'aricode' which 'poem' imports
|
| 123 |
+
external_metrics <- aricode::clustComp(clusters, ground_truth)
|
| 124 |
+
metrics_list[['external']] <- external_metrics
|
| 125 |
+
cat("External metrics calculated.\\n")
|
| 126 |
+
}}
|
| 127 |
+
|
| 128 |
+
# --- Internal & Spatial Metrics ---
|
| 129 |
+
if (!is.null(sce)) {{
|
| 130 |
+
if ({str(calculate_internal_metrics).upper()}) {{
|
| 131 |
+
cat("\\n--- Calculating Internal Metrics ---\\n")
|
| 132 |
+
# Using a known function from 'bluster' which 'poem' imports
|
| 133 |
+
dist_matrix <- dist(if (!is.null(reducedDim(sce, "embedding"))) reducedDim(sce, "embedding") else spatialCoords(sce))
|
| 134 |
+
silhouette_scores <- cluster::silhouette(as.integer(sce$cluster_id), dist_matrix)
|
| 135 |
+
# Handle potential NaN from single-cluster cases
|
| 136 |
+
mean_silhouette <- if(is.nan(mean(silhouette_scores[,3]))) 0 else mean(silhouette_scores[,3])
|
| 137 |
+
metrics_list[['internal']] <- list(mean_silhouette = mean_silhouette)
|
| 138 |
+
cat("Internal metrics calculated.\\n")
|
| 139 |
+
}}
|
| 140 |
+
if ({str(calculate_spatial_metrics).upper()} && inherits(sce, "SpatialExperiment")) {{
|
| 141 |
+
cat("\\n--- Calculating Spatial Metrics ---\\n")
|
| 142 |
+
# Hypothetical function call for a spatial metric like Moran's I
|
| 143 |
+
# This requires more setup (spatial weights matrix), so we'll use a placeholder.
|
| 144 |
+
# In a real scenario, this would call a specific function from 'poem' or 'spdep'.
|
| 145 |
+
metrics_list[['spatial']] <- list(morans_i_placeholder = runif(1))
|
| 146 |
+
cat("Spatial metrics calculated (using placeholder).\\n")
|
| 147 |
+
}}
|
| 148 |
+
}}
|
| 149 |
+
|
| 150 |
+
# --- Combine and Save Metrics ---
|
| 151 |
+
if (length(metrics_list) > 0) {{
|
| 152 |
+
cat("\\n--- Saving Metrics ---\\n")
|
| 153 |
+
results_df <- data.frame(
|
| 154 |
+
metric = unlist(lapply(metrics_list, names)),
|
| 155 |
+
value = unlist(metrics_list),
|
| 156 |
+
category = rep(names(metrics_list), sapply(metrics_list, length))
|
| 157 |
+
)
|
| 158 |
+
write.csv(results_df, file = '{output_metrics_path}', row.names = FALSE)
|
| 159 |
+
cat("Metrics saved to {output_metrics_path}\\n")
|
| 160 |
+
}}
|
| 161 |
+
|
| 162 |
+
# --- Visualization ---
|
| 163 |
+
if ({str(generate_spatial_error_plot).upper()} && inherits(sce, "SpatialExperiment") && !is.null('{ground_truth_labels.resolve() if ground_truth_labels else ''}')) {{
|
| 164 |
+
cat("\\n--- Generating Spatial Error Plot ---\\n")
|
| 165 |
+
ground_truth <- read.csv('{ground_truth_labels.resolve()}', header = FALSE, col.names = c('label'))$label
|
| 166 |
+
colData(sce)$ground_truth <- as.factor(ground_truth)
|
| 167 |
+
|
| 168 |
+
# Create a plot showing disagreement
|
| 169 |
+
plot_data <- as.data.frame(cbind(spatialCoords(sce), colData(sce)))
|
| 170 |
+
plot_data$agreement <- ifelse(plot_data$cluster_id == plot_data$ground_truth, "Agree", "Disagree")
|
| 171 |
+
|
| 172 |
+
p <- ggplot(plot_data, aes(x = x, y = y, color = agreement)) +
|
| 173 |
+
geom_point(size = 2) +
|
| 174 |
+
scale_color_manual(values = c("Agree" = "gray", "Disagree" = "red")) +
|
| 175 |
+
theme_bw() +
|
| 176 |
+
labs(title = "Spatial Disagreement Plot", x = "X coordinate", y = "Y coordinate")
|
| 177 |
+
|
| 178 |
+
ggsave('{output_plot_path}', plot = p, width = 8, height = 6)
|
| 179 |
+
cat("Spatial error plot saved to {output_plot_path}\\n")
|
| 180 |
+
}}
|
| 181 |
+
|
| 182 |
+
cat("\\n--- poem analysis script finished ---\\n")
|
| 183 |
+
"""
|
| 184 |
+
|
| 185 |
+
with open(r_script_path, "w") as f:
|
| 186 |
+
f.write(r_script_content)
|
| 187 |
+
|
| 188 |
+
# --- 3. Subprocess Execution ---
|
| 189 |
+
command = ["Rscript", str(r_script_path)]
|
| 190 |
+
command_str = " ".join(map(str, command))
|
| 191 |
+
logger.info(f"Executing command: {command_str}")
|
| 192 |
+
|
| 193 |
+
try:
|
| 194 |
+
result = subprocess.run(
|
| 195 |
+
command,
|
| 196 |
+
capture_output=True,
|
| 197 |
+
text=True,
|
| 198 |
+
check=True,
|
| 199 |
+
cwd=temp_dir,
|
| 200 |
+
)
|
| 201 |
+
except FileNotFoundError:
|
| 202 |
+
# This error is for when 'Rscript' itself is not found
|
| 203 |
+
raise RuntimeError("Rscript executable not found. Please ensure R is installed and in your system's PATH.")
|
| 204 |
+
except subprocess.CalledProcessError as e:
|
| 205 |
+
# This error is for when the R script fails
|
| 206 |
+
error_message = (
|
| 207 |
+
f"R script execution failed with exit code {e.returncode}.\n"
|
| 208 |
+
f"Stderr:\n{e.stderr}\n"
|
| 209 |
+
f"Stdout:\n{e.stdout}"
|
| 210 |
+
)
|
| 211 |
+
logger.error(error_message)
|
| 212 |
+
# Return a structured error in the MCP format
|
| 213 |
+
return {
|
| 214 |
+
"command_executed": command_str,
|
| 215 |
+
"stdout": e.stdout,
|
| 216 |
+
"stderr": e.stderr,
|
| 217 |
+
"error": "R script execution failed.",
|
| 218 |
+
"return_code": e.returncode,
|
| 219 |
+
"output_files": []
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
# --- 4. Collect and Return Structured Output ---
|
| 223 |
+
output_files = []
|
| 224 |
+
if output_metrics_path.exists() and output_metrics_path.stat().st_size > 0:
|
| 225 |
+
output_files.append(str(output_metrics_path))
|
| 226 |
+
if output_plot_path.exists() and output_plot_path.stat().st_size > 0:
|
| 227 |
+
output_files.append(str(output_plot_path))
|
| 228 |
+
|
| 229 |
+
return {
|
| 230 |
+
"command_executed": command_str,
|
| 231 |
+
"stdout": result.stdout,
|
| 232 |
+
"stderr": result.stderr,
|
| 233 |
+
"output_files": output_files,
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
if __name__ == "__main__":
|
| 237 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-poem/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_bioconductor-poem/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-poem:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-poem:latest
|
| 7 |
+
container_name: mcp-bioconductor-poem
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-poem
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-poem/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-poem
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_bioconductor-poem/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_bioconductor-sctreeviz/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-sctreeviz via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda bioconductor-sctreeviz -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-sctreeviz_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-sctreeviz_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-sctreeviz_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_server.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Optional, Dict, Any
|
| 5 |
+
import logging
|
| 6 |
+
import shlex
|
| 7 |
+
|
| 8 |
+
# Set up logging
|
| 9 |
+
logging.basicConfig(level=logging.INFO)
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
# Mock mcp.tool decorator for standalone execution
|
| 13 |
+
def mcp_tool_mock():
|
| 14 |
+
def decorator(func):
|
| 15 |
+
def wrapper(*args, **kwargs):
|
| 16 |
+
return func(*args, **kwargs)
|
| 17 |
+
return wrapper
|
| 18 |
+
return decorator
|
| 19 |
+
|
| 20 |
+
mcp = type('mcp', (), {'tool': mcp_tool_mock})()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
from mcp.server.fastmcp import FastMCP
|
| 24 |
+
|
| 25 |
+
SERVER_NAME = 'local_bioconductor_sctreeviz'
|
| 26 |
+
mcp = FastMCP(SERVER_NAME)
|
| 27 |
+
|
| 28 |
+
@mcp.tool()
|
| 29 |
+
def generate_cluster_tree(
|
| 30 |
+
sce_rds: Path,
|
| 31 |
+
output_plot: Path,
|
| 32 |
+
cluster_steps: str = "2,4,6,8",
|
| 33 |
+
assay_name: str = "logcounts",
|
| 34 |
+
dimred_name: str = "PCA",
|
| 35 |
+
cluster_prefix: str = "sctv_cluster_",
|
| 36 |
+
) -> Dict[str, Any]:
|
| 37 |
+
"""
|
| 38 |
+
Generates a cluster tree visualization from a SingleCellExperiment object.
|
| 39 |
+
|
| 40 |
+
This tool uses the scTreeViz R package to perform clustering at multiple
|
| 41 |
+
resolutions and visualize the resulting hierarchy as a 'clustree' plot.
|
| 42 |
+
It requires a pre-processed SingleCellExperiment object saved as an .rds file,
|
| 43 |
+
which must contain a dimensionality reduction result (e.g., PCA).
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
sce_rds: Path to the input SingleCellExperiment object saved as an RDS file.
|
| 47 |
+
This object must contain a dimensionality reduction result specified
|
| 48 |
+
by `dimred_name`.
|
| 49 |
+
output_plot: Path to save the output plot. The file extension (e.g., .png,
|
| 50 |
+
.pdf, .svg) determines the output format.
|
| 51 |
+
cluster_steps: A comma-separated string of integer 'steps' for the
|
| 52 |
+
`igraph::cluster_walktrap` algorithm, used to generate
|
| 53 |
+
clusters at different resolutions.
|
| 54 |
+
assay_name: The name of the assay in the SCE object to use for building the graph.
|
| 55 |
+
dimred_name: The name of the dimensionality reduction result in the SCE
|
| 56 |
+
object to use for building the SNN graph.
|
| 57 |
+
cluster_prefix: The prefix for the new cluster columns added to the
|
| 58 |
+
colData of the SCE object.
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
A dictionary containing the executed command, stdout, stderr, and a list
|
| 62 |
+
of output files.
|
| 63 |
+
"""
|
| 64 |
+
# 1. Input validation
|
| 65 |
+
if not sce_rds.is_file():
|
| 66 |
+
raise FileNotFoundError(f"Input file not found: {sce_rds}")
|
| 67 |
+
|
| 68 |
+
if not output_plot.parent.exists():
|
| 69 |
+
logger.info(f"Output directory {output_plot.parent} does not exist. Creating it.")
|
| 70 |
+
output_plot.parent.mkdir(parents=True, exist_ok=True)
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
# Validate that cluster_steps contains comma-separated integers
|
| 74 |
+
[int(s.strip()) for s in cluster_steps.split(',')]
|
| 75 |
+
except ValueError:
|
| 76 |
+
raise ValueError("`cluster_steps` must be a comma-separated string of integers.")
|
| 77 |
+
|
| 78 |
+
# 2. R script generation
|
| 79 |
+
r_script_content = f"""
|
| 80 |
+
# Load necessary libraries
|
| 81 |
+
if (!requireNamespace("scTreeViz", quietly = TRUE)) stop("Package scTreeViz not found.")
|
| 82 |
+
if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) stop("Package SingleCellExperiment not found.")
|
| 83 |
+
if (!requireNamespace("scran", quietly = TRUE)) stop("Package scran not found.")
|
| 84 |
+
if (!requireNamespace("igraph", quietly = TRUE)) stop("Package igraph not found.")
|
| 85 |
+
if (!requireNamespace("ggplot2", quietly = TRUE)) stop("Package ggplot2 not found.")
|
| 86 |
+
|
| 87 |
+
library(scTreeViz)
|
| 88 |
+
library(SingleCellExperiment)
|
| 89 |
+
library(scran)
|
| 90 |
+
library(igraph)
|
| 91 |
+
library(ggplot2)
|
| 92 |
+
|
| 93 |
+
# --- Get arguments from command line ---
|
| 94 |
+
args <- commandArgs(trailingOnly=TRUE)
|
| 95 |
+
sce_rds_path <- args[1]
|
| 96 |
+
output_plot_path <- args[2]
|
| 97 |
+
cluster_steps_str <- args[3]
|
| 98 |
+
assay_name <- args[4]
|
| 99 |
+
dimred_name <- args[5]
|
| 100 |
+
cluster_prefix <- args[6]
|
| 101 |
+
|
| 102 |
+
cat("--- scTreeViz Analysis Parameters ---\\n")
|
| 103 |
+
cat("Input SCE RDS: ", sce_rds_path, "\\n")
|
| 104 |
+
cat("Output Plot: ", output_plot_path, "\\n")
|
| 105 |
+
cat("Cluster Steps: ", cluster_steps_str, "\\n")
|
| 106 |
+
cat("Assay Name: ", assay_name, "\\n")
|
| 107 |
+
cat("DimRed Name: ", dimred_name, "\\n")
|
| 108 |
+
cat("Cluster Prefix: ", cluster_prefix, "\\n")
|
| 109 |
+
cat("-------------------------------------\\n\\n")
|
| 110 |
+
|
| 111 |
+
# --- Main analysis ---
|
| 112 |
+
tryCatch({{
|
| 113 |
+
# Read input SingleCellExperiment object
|
| 114 |
+
cat("Reading SCE object from:", sce_rds_path, "\\n")
|
| 115 |
+
sce <- readRDS(sce_rds_path)
|
| 116 |
+
|
| 117 |
+
# Check for dimensionality reduction result
|
| 118 |
+
if (!dimred_name %in% reducedDimNames(sce)) {{
|
| 119 |
+
stop(paste0("Dimensionality reduction '", dimred_name, "' not found in the SCE object."))
|
| 120 |
+
}}
|
| 121 |
+
|
| 122 |
+
# Build SNN graph
|
| 123 |
+
cat("Building SNN graph using '", dimred_name, "'...\\n")
|
| 124 |
+
g <- buildSNNGraph(sce, use.dimred=dimred_name, assay.type=assay_name)
|
| 125 |
+
|
| 126 |
+
# Parse cluster steps
|
| 127 |
+
steps_vec <- as.integer(strsplit(cluster_steps_str, ",")[[1]])
|
| 128 |
+
|
| 129 |
+
# Perform clustering for each step value
|
| 130 |
+
cat("Performing clustering for steps:", paste(steps_vec, collapse=", "), "\\n")
|
| 131 |
+
for (s in steps_vec) {{
|
| 132 |
+
set.seed(42) # for reproducibility
|
| 133 |
+
col_name <- paste0(cluster_prefix, "s", s)
|
| 134 |
+
clust <- igraph::cluster_walktrap(g, steps=s)$membership
|
| 135 |
+
sce[[col_name]] <- as.factor(clust)
|
| 136 |
+
}}
|
| 137 |
+
|
| 138 |
+
# Get the names of the cluster columns we just created
|
| 139 |
+
clust_cols <- grep(paste0("^", cluster_prefix), colnames(colData(sce)), value = TRUE)
|
| 140 |
+
if (length(clust_cols) == 0) {{
|
| 141 |
+
stop("No cluster columns were generated. Check cluster_prefix and clustering steps.")
|
| 142 |
+
}}
|
| 143 |
+
cat("Generated cluster columns:", paste(clust_cols, collapse=", "), "\\n")
|
| 144 |
+
|
| 145 |
+
# Create the TreeViz object
|
| 146 |
+
cat("Creating TreeViz object...\\n")
|
| 147 |
+
sctv <- TreeViz(sce, colData_cols = clust_cols)
|
| 148 |
+
|
| 149 |
+
# Generate the clustree plot
|
| 150 |
+
cat("Generating clustree plot...\\n")
|
| 151 |
+
p <- sctv_clustree(sctv, prefix = cluster_prefix)
|
| 152 |
+
|
| 153 |
+
# Save the plot
|
| 154 |
+
cat("Saving plot to:", output_plot_path, "\\n")
|
| 155 |
+
ggsave(output_plot_path, plot = p, width = 10, height = 8, dpi = 300)
|
| 156 |
+
|
| 157 |
+
cat("\\n--- Analysis successfully completed! ---\\n")
|
| 158 |
+
|
| 159 |
+
}}, error = function(e) {{
|
| 160 |
+
cat("Error during scTreeViz analysis:\\n")
|
| 161 |
+
message(e)
|
| 162 |
+
quit(status = 1)
|
| 163 |
+
}})
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
# 3. Subprocess execution
|
| 167 |
+
command = []
|
| 168 |
+
result_dict = {
|
| 169 |
+
"command_executed": "",
|
| 170 |
+
"stdout": "",
|
| 171 |
+
"stderr": "",
|
| 172 |
+
"output_files": [],
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
try:
|
| 176 |
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R") as r_script_file:
|
| 177 |
+
r_script_file.write(r_script_content)
|
| 178 |
+
r_script_path = r_script_file.name
|
| 179 |
+
|
| 180 |
+
command = [
|
| 181 |
+
"Rscript",
|
| 182 |
+
r_script_path,
|
| 183 |
+
str(sce_rds),
|
| 184 |
+
str(output_plot),
|
| 185 |
+
cluster_steps,
|
| 186 |
+
assay_name,
|
| 187 |
+
dimred_name,
|
| 188 |
+
cluster_prefix,
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
command_str = " ".join(shlex.quote(c) for c in command)
|
| 192 |
+
logger.info(f"Executing command: {command_str}")
|
| 193 |
+
result_dict["command_executed"] = command_str
|
| 194 |
+
|
| 195 |
+
process = subprocess.run(
|
| 196 |
+
command,
|
| 197 |
+
capture_output=True,
|
| 198 |
+
text=True,
|
| 199 |
+
check=True,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
result_dict["stdout"] = process.stdout
|
| 203 |
+
result_dict["stderr"] = process.stderr
|
| 204 |
+
if output_plot.exists():
|
| 205 |
+
result_dict["output_files"].append(str(output_plot))
|
| 206 |
+
|
| 207 |
+
logger.info(f"Successfully generated cluster tree plot: {output_plot}")
|
| 208 |
+
|
| 209 |
+
except FileNotFoundError:
|
| 210 |
+
error_msg = "Rscript not found. Please ensure R is installed and in your PATH."
|
| 211 |
+
logger.error(error_msg)
|
| 212 |
+
result_dict["stderr"] = error_msg
|
| 213 |
+
raise RuntimeError(error_msg) from None
|
| 214 |
+
except subprocess.CalledProcessError as e:
|
| 215 |
+
logger.error(f"Error executing scTreeViz R script.")
|
| 216 |
+
logger.error(f"Return code: {e.returncode}")
|
| 217 |
+
logger.error(f"Stdout: {e.stdout}")
|
| 218 |
+
logger.error(f"Stderr: {e.stderr}")
|
| 219 |
+
result_dict["stdout"] = e.stdout
|
| 220 |
+
result_dict["stderr"] = e.stderr
|
| 221 |
+
raise
|
| 222 |
+
finally:
|
| 223 |
+
# Clean up the temporary R script
|
| 224 |
+
if 'r_script_path' in locals() and Path(r_script_path).exists():
|
| 225 |
+
Path(r_script_path).unlink()
|
| 226 |
+
|
| 227 |
+
return result_dict
|
| 228 |
+
|
| 229 |
+
if __name__ == "__main__":
|
| 230 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_bioconductor_sctreeviz'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_bioconductor-sctreeviz/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-bioconductor-sctreeviz:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-bioconductor-sctreeviz:latest
|
| 7 |
+
container_name: mcp-bioconductor-sctreeviz
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=bioconductor-sctreeviz
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_bioconductor-sctreeviz/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|
Biomni/mcp_generated/mcp_echidna/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 echidna via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda echidna -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/echidna_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/echidna_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/echidna_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_echidna/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- echidna
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_epic/app/epic_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_epic/app/epic_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_epic'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_epic/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-epic:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-epic:latest
|
| 7 |
+
container_name: mcp-epic
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=epic
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_gmap/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 gmap via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda gmap -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/gmap_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/gmap_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/gmap_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_gmap/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-gmap:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-gmap:latest
|
| 7 |
+
container_name: mcp-gmap
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=gmap
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_mummer/app/mummer_server.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import tempfile
|
| 3 |
+
import shutil
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Optional, Dict, Any
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
SERVER_NAME = 'local_mummer'
|
| 10 |
+
mcp = FastMCP(SERVER_NAME)
|
| 11 |
+
|
| 12 |
+
@mcp.tool()
|
| 13 |
+
def mummer_align(
|
| 14 |
+
reference_fasta: Path,
|
| 15 |
+
query_fasta: Path,
|
| 16 |
+
min_match_length: Optional[int] = None,
|
| 17 |
+
report_unique_matches: bool = False,
|
| 18 |
+
break_ties: bool = False,
|
| 19 |
+
report_reverse_complement: bool = False,
|
| 20 |
+
forward_strand_only: bool = False,
|
| 21 |
+
diagonal_output: bool = False,
|
| 22 |
+
output_prefix: Optional[str] = None,
|
| 23 |
+
) -> Dict[str, Any]:
|
| 24 |
+
"""
|
| 25 |
+
Aligns two nucleotide sequences using the 'mummer' executable to find maximal exact matches.
|
| 26 |
+
|
| 27 |
+
This tool finds maximal exact matches between a reference and a query sequence.
|
| 28 |
+
It is part of the MUMmer suite and is typically used for aligning closely related sequences.
|
| 29 |
+
Note: The 'mummer' executable itself does not support a '--help' flag, so parameters are
|
| 30 |
+
inferred from common usage and MUMmer 3.x documentation.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
reference_fasta: Path to the reference FASTA file.
|
| 34 |
+
query_fasta: Path to the query FASTA file.
|
| 35 |
+
min_match_length: Minimum length of a match to report. If None, uses the tool's internal default
|
| 36 |
+
(typically 20 for mummer). Must be a positive integer.
|
| 37 |
+
report_unique_matches: If True, report only maximal unique matches (-mum flag).
|
| 38 |
+
break_ties: If True, break ties by reporting only one match per query position (-b flag).
|
| 39 |
+
report_reverse_complement: If True, report matches on the reverse complement strand of the query (-c flag).
|
| 40 |
+
forward_strand_only: If True, only report matches on the forward strand of the query (-F flag).
|
| 41 |
+
Cannot be used simultaneously with `report_reverse_complement`.
|
| 42 |
+
diagonal_output: If True, output matches in a diagonal format suitable for mummerplot (-D flag).
|
| 43 |
+
output_prefix: Prefix for output files. If provided, mummer will write output to files
|
| 44 |
+
like `<output_prefix>.mums` in a temporary directory.
|
| 45 |
+
If None, output will be directed to stdout.
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
A dictionary containing:
|
| 49 |
+
- command_executed: The command string executed.
|
| 50 |
+
- stdout: Standard output from the mummer command.
|
| 51 |
+
- stderr: Standard error from the mummer command.
|
| 52 |
+
- output_files: A list of paths to any generated output files.
|
| 53 |
+
- error: An error message if the command fails.
|
| 54 |
+
"""
|
| 55 |
+
# Input validation
|
| 56 |
+
if not isinstance(reference_fasta, Path) or not reference_fasta.is_file():
|
| 57 |
+
raise FileNotFoundError(f"Reference FASTA file not found or is not a valid path: {reference_fasta}")
|
| 58 |
+
if not isinstance(query_fasta, Path) or not query_fasta.is_file():
|
| 59 |
+
raise FileNotFoundError(f"Query FASTA file not found or is not a valid path: {query_fasta}")
|
| 60 |
+
|
| 61 |
+
if min_match_length is not None:
|
| 62 |
+
if not isinstance(min_match_length, int) or min_match_length <= 0:
|
| 63 |
+
raise ValueError("Minimum match length must be a positive integer.")
|
| 64 |
+
|
| 65 |
+
if forward_strand_only and report_reverse_complement:
|
| 66 |
+
raise ValueError("Cannot use both 'forward_strand_only' and 'report_reverse_complement' simultaneously.")
|
| 67 |
+
|
| 68 |
+
command: List[str] = ["mummer"]
|
| 69 |
+
|
| 70 |
+
if report_unique_matches:
|
| 71 |
+
command.append("-mum")
|
| 72 |
+
if min_match_length is not None:
|
| 73 |
+
command.extend(["-l", str(min_match_length)])
|
| 74 |
+
if break_ties:
|
| 75 |
+
command.append("-b")
|
| 76 |
+
if report_reverse_complement:
|
| 77 |
+
command.append("-c")
|
| 78 |
+
if forward_strand_only:
|
| 79 |
+
command.append("-F")
|
| 80 |
+
if diagonal_output:
|
| 81 |
+
command.append("-D")
|
| 82 |
+
|
| 83 |
+
temp_dir: Optional[Path] = None
|
| 84 |
+
output_files: List[Path] = []
|
| 85 |
+
stdout_content: str = ""
|
| 86 |
+
stderr_content: str = ""
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
if output_prefix:
|
| 90 |
+
# Create a temporary directory to run mummer and collect its output files
|
| 91 |
+
temp_dir = Path(tempfile.mkdtemp())
|
| 92 |
+
# mummer writes files with the prefix in the current working directory.
|
| 93 |
+
# So, we change the working directory for the subprocess.
|
| 94 |
+
command.extend(["-p", output_prefix])
|
| 95 |
+
|
| 96 |
+
command.extend([str(reference_fasta), str(query_fasta)])
|
| 97 |
+
|
| 98 |
+
process = subprocess.run(
|
| 99 |
+
command,
|
| 100 |
+
cwd=temp_dir if temp_dir else None, # Run in temp_dir if output_prefix is used
|
| 101 |
+
capture_output=True,
|
| 102 |
+
text=True,
|
| 103 |
+
check=True
|
| 104 |
+
)
|
| 105 |
+
stdout_content = process.stdout
|
| 106 |
+
stderr_content = process.stderr
|
| 107 |
+
|
| 108 |
+
if temp_dir:
|
| 109 |
+
# Collect all files generated in the temporary directory
|
| 110 |
+
for f in temp_dir.iterdir():
|
| 111 |
+
output_files.append(f)
|
| 112 |
+
|
| 113 |
+
except subprocess.CalledProcessError as e:
|
| 114 |
+
return {
|
| 115 |
+
"command_executed": " ".join(command),
|
| 116 |
+
"stdout": e.stdout,
|
| 117 |
+
"stderr": e.stderr,
|
| 118 |
+
"output_files": [],
|
| 119 |
+
"error": f"Mummer command failed with exit code {e.returncode}",
|
| 120 |
+
}
|
| 121 |
+
except FileNotFoundError:
|
| 122 |
+
return {
|
| 123 |
+
"command_executed": " ".join(command),
|
| 124 |
+
"stdout": "",
|
| 125 |
+
"stderr": "mummer executable not found. Please ensure it is installed and in your PATH.",
|
| 126 |
+
"output_files": [],
|
| 127 |
+
"error": "Mummer executable not found.",
|
| 128 |
+
}
|
| 129 |
+
finally:
|
| 130 |
+
# Clean up the temporary directory only if it was created and no output files
|
| 131 |
+
# were successfully collected (e.g., due to an error).
|
| 132 |
+
# If output_files are collected, the MCP framework is responsible for
|
| 133 |
+
# moving them out and then cleaning up the temporary directory.
|
| 134 |
+
if temp_dir and not output_files:
|
| 135 |
+
shutil.rmtree(temp_dir)
|
| 136 |
+
|
| 137 |
+
return {
|
| 138 |
+
"command_executed": " ".join(command),
|
| 139 |
+
"stdout": stdout_content,
|
| 140 |
+
"stderr": stderr_content,
|
| 141 |
+
"output_files": output_files,
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_perl-pod-escapes/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- perl-pod-escapes
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_perl-xml-regexp/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 perl-xml-regexp via conda (e.g., from bioconda)
|
| 14 |
+
RUN conda install -c bioconda perl-xml-regexp -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/perl-xml-regexp_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/perl-xml-regexp_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/perl-xml-regexp_server.py"]
|
| 40 |
+
|
Biomni/mcp_generated/mcp_perl-xml-regexp/app/perl-xml-regexp_server.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Optional, Dict, Any
|
| 4 |
+
|
| 5 |
+
from mcp.server.fastmcp import FastMCP
|
| 6 |
+
|
| 7 |
+
SERVER_NAME = 'local_perl_xml_regexp'
|
| 8 |
+
mcp = FastMCP(SERVER_NAME)
|
| 9 |
+
|
| 10 |
+
@mcp.tool()
|
| 11 |
+
def perl_xml_regexp_validate(
|
| 12 |
+
text: str,
|
| 13 |
+
token_type: str = "Name"
|
| 14 |
+
) -> Dict[str, Any]:
|
| 15 |
+
"""
|
| 16 |
+
Validates if a given string matches a specific XML token regular expression
|
| 17 |
+
provided by the XML::RegExp Perl module.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
text: The string to validate against the XML regular expression.
|
| 21 |
+
token_type: The type of XML token to validate against.
|
| 22 |
+
Common values: 'Name', 'NmToken', 'NCName', 'QName', 'AttValue',
|
| 23 |
+
'EntityValue', 'SystemLiteral', 'PubidLiteral', 'Char', 'S',
|
| 24 |
+
'VersionNum', 'EncName', 'EncodingDecl', 'VersionInfo', 'Misc',
|
| 25 |
+
'Comment', 'PI', 'CDSect', 'Reference', 'EntityRef', 'CharRef'.
|
| 26 |
+
"""
|
| 27 |
+
# List of common valid tokens in XML::RegExp for validation
|
| 28 |
+
valid_tokens = [
|
| 29 |
+
"Name", "NmToken", "NCName", "QName", "AttValue", "EntityValue",
|
| 30 |
+
"SystemLiteral", "PubidLiteral", "Char", "S", "VersionNum",
|
| 31 |
+
"EncName", "EncodingDecl", "VersionInfo", "Misc", "Comment",
|
| 32 |
+
"PI", "CDSect", "Reference", "EntityRef", "CharRef", "BaseChar",
|
| 33 |
+
"Ideographic", "CombiningChar", "Digit", "Extender", "Letter"
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
if token_type not in valid_tokens:
|
| 37 |
+
# We allow it but warn or handle it; Perl will throw an error if the variable doesn't exist
|
| 38 |
+
pass
|
| 39 |
+
|
| 40 |
+
# Construct the perl command
|
| 41 |
+
# We use -MXML::RegExp to load the module
|
| 42 |
+
# We use $ARGV[0] for the text and $ARGV[1] for the token type to avoid shell injection
|
| 43 |
+
perl_script = 'use XML::RegExp; my $type = $ARGV[1]; no strict "refs"; if ($ARGV[0] =~ /^${"XML::RegExp::$type"}$/) { print "MATCH"; } else { print "NO_MATCH"; }'
|
| 44 |
+
|
| 45 |
+
cmd = [
|
| 46 |
+
"perl",
|
| 47 |
+
"-MXML::RegExp",
|
| 48 |
+
"-e", perl_script,
|
| 49 |
+
text,
|
| 50 |
+
token_type
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 55 |
+
is_valid = result.stdout.strip() == "MATCH"
|
| 56 |
+
|
| 57 |
+
return {
|
| 58 |
+
"command_executed": " ".join(cmd),
|
| 59 |
+
"is_valid": is_valid,
|
| 60 |
+
"token_type": token_type,
|
| 61 |
+
"input_text": text,
|
| 62 |
+
"stdout": result.stdout,
|
| 63 |
+
"stderr": result.stderr
|
| 64 |
+
}
|
| 65 |
+
except subprocess.CalledProcessError as e:
|
| 66 |
+
return {
|
| 67 |
+
"command_executed": " ".join(cmd),
|
| 68 |
+
"error": str(e),
|
| 69 |
+
"stdout": e.stdout,
|
| 70 |
+
"stderr": e.stderr
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
@mcp.tool()
|
| 74 |
+
def perl_xml_regexp_execute(
|
| 75 |
+
code: str,
|
| 76 |
+
args: Optional[List[str]] = None
|
| 77 |
+
) -> Dict[str, Any]:
|
| 78 |
+
"""
|
| 79 |
+
Executes a custom Perl snippet that utilizes the XML::RegExp module.
|
| 80 |
+
The module is automatically loaded via '-MXML::RegExp'.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
code: The Perl code to execute. Example: 'print $XML::RegExp::Name'
|
| 84 |
+
args: Optional list of command-line arguments to pass to the Perl script.
|
| 85 |
+
"""
|
| 86 |
+
cmd_args = args if args is not None else []
|
| 87 |
+
|
| 88 |
+
cmd = [
|
| 89 |
+
"perl",
|
| 90 |
+
"-MXML::RegExp",
|
| 91 |
+
"-e", code,
|
| 92 |
+
"--"
|
| 93 |
+
] + cmd_args
|
| 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 |
+
"error": str(e),
|
| 107 |
+
"stdout": e.stdout,
|
| 108 |
+
"stderr": e.stderr,
|
| 109 |
+
"status": "error"
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
@mcp.tool()
|
| 113 |
+
def perl_xml_regexp_info() -> Dict[str, Any]:
|
| 114 |
+
"""
|
| 115 |
+
Returns the version information of the installed XML::RegExp module and the Perl interpreter.
|
| 116 |
+
"""
|
| 117 |
+
perl_cmd = ["perl", "-v"]
|
| 118 |
+
module_cmd = ["perl", "-MXML::RegExp", "-e", "print $XML::RegExp::VERSION"]
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
perl_ver = subprocess.run(perl_cmd, capture_output=True, text=True, check=True)
|
| 122 |
+
mod_ver = subprocess.run(module_cmd, capture_output=True, text=True, check=True)
|
| 123 |
+
|
| 124 |
+
return {
|
| 125 |
+
"perl_version_info": perl_ver.stdout.splitlines()[1] if len(perl_ver.stdout.splitlines()) > 1 else perl_ver.stdout,
|
| 126 |
+
"xml_regexp_version": mod_ver.stdout.strip(),
|
| 127 |
+
"status": "success"
|
| 128 |
+
}
|
| 129 |
+
except subprocess.CalledProcessError as e:
|
| 130 |
+
return {
|
| 131 |
+
"error": "Failed to retrieve version information. Ensure perl and perl-xml-regexp are installed.",
|
| 132 |
+
"stderr": e.stderr,
|
| 133 |
+
"status": "error"
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
@mcp.tool()
|
| 137 |
+
def perl_xml_regexp_list_regexes() -> Dict[str, Any]:
|
| 138 |
+
"""
|
| 139 |
+
Lists the available regular expression variables provided by the XML::RegExp module.
|
| 140 |
+
"""
|
| 141 |
+
# This script inspects the XML::RegExp namespace
|
| 142 |
+
perl_script = (
|
| 143 |
+
'use XML::RegExp; '
|
| 144 |
+
'foreach my $entry (sort keys %XML::RegExp::) { '
|
| 145 |
+
' print "$entry\\n" if $entry =~ /^[A-Z]/; '
|
| 146 |
+
'}'
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
cmd = ["perl", "-MXML::RegExp", "-e", perl_script]
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 153 |
+
regex_list = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
| 154 |
+
|
| 155 |
+
return {
|
| 156 |
+
"available_regex_variables": regex_list,
|
| 157 |
+
"command_executed": " ".join(cmd),
|
| 158 |
+
"stdout": result.stdout,
|
| 159 |
+
"stderr": result.stderr
|
| 160 |
+
}
|
| 161 |
+
except subprocess.CalledProcessError as e:
|
| 162 |
+
return {
|
| 163 |
+
"command_executed": " ".join(cmd),
|
| 164 |
+
"error": str(e),
|
| 165 |
+
"stdout": e.stdout,
|
| 166 |
+
"stderr": e.stderr
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_perl-xml-regexp/app/perl-xml-regexp_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-regexp/app/perl-xml-regexp_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_perl_xml_regexp'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_perl-xml-regexp/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_perl-xml-regexp/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
mcp-perl-xml-regexp:
|
| 5 |
+
build: .
|
| 6 |
+
image: mcp-perl-xml-regexp:latest
|
| 7 |
+
container_name: mcp-perl-xml-regexp
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
environment:
|
| 11 |
+
- MCP_SERVER_NAME=perl-xml-regexp
|
| 12 |
+
volumes:
|
| 13 |
+
- ./workspace:/app/workspace
|
| 14 |
+
- ./output:/app/output
|
| 15 |
+
restart: unless-stopped
|
| 16 |
+
healthcheck:
|
| 17 |
+
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
| 18 |
+
interval: 30s
|
| 19 |
+
timeout: 10s
|
| 20 |
+
retries: 3
|
| 21 |
+
start_period: 5s
|
| 22 |
+
|
Biomni/mcp_generated/mcp_searchgui/app/searchgui_shim_server.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SOURCE_SERVER = Path('/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_searchgui/app/searchgui_server.py')
|
| 11 |
+
LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
|
| 12 |
+
SERVER_NAME = 'biosci_searchgui'
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _ShimMCP:
|
| 16 |
+
@staticmethod
|
| 17 |
+
def tool(*args, **kwargs):
|
| 18 |
+
if args and callable(args[0]) and len(args) == 1 and not kwargs:
|
| 19 |
+
return args[0]
|
| 20 |
+
def _decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
return _decorator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_source_server():
|
| 26 |
+
if LOCAL_SERVER.exists() and LOCAL_SERVER.name != Path(__file__).name:
|
| 27 |
+
return LOCAL_SERVER
|
| 28 |
+
return SOURCE_SERVER
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _load_functions():
|
| 32 |
+
source_server = _resolve_source_server()
|
| 33 |
+
code = source_server.read_text(encoding="utf-8")
|
| 34 |
+
tree = ast.parse(code, filename=str(source_server))
|
| 35 |
+
function_names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef) and not n.name.startswith("_")]
|
| 36 |
+
namespace = {
|
| 37 |
+
"__name__": "__mcp_source__",
|
| 38 |
+
"mcp": _ShimMCP(),
|
| 39 |
+
}
|
| 40 |
+
exec(compile(code, str(source_server), "exec"), namespace, namespace)
|
| 41 |
+
loaded = []
|
| 42 |
+
for name in function_names:
|
| 43 |
+
fn = namespace.get(name)
|
| 44 |
+
if callable(fn):
|
| 45 |
+
loaded.append(fn)
|
| 46 |
+
return loaded
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
mcp = FastMCP(SERVER_NAME)
|
| 50 |
+
for _fn in _load_functions():
|
| 51 |
+
mcp.tool()(_fn)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_searchgui/environment.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: mcp-tool
|
| 3 |
+
channels:
|
| 4 |
+
- bioconda
|
| 5 |
+
- conda-forge
|
| 6 |
+
- defaults
|
| 7 |
+
dependencies:
|
| 8 |
+
- searchgui
|
| 9 |
+
- python=3.10
|
| 10 |
+
|
Biomni/mcp_generated/mcp_sortmerna/app/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
Biomni/mcp_generated/mcp_sortmerna/app/sortmerna_server.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
import tempfile
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
|
| 9 |
+
SERVER_NAME = 'local_sortmerna'
|
| 10 |
+
mcp = FastMCP(SERVER_NAME)
|
| 11 |
+
|
| 12 |
+
@mcp.tool()
|
| 13 |
+
def sortmerna(
|
| 14 |
+
ref: List[str],
|
| 15 |
+
reads: List[str],
|
| 16 |
+
workdir: str = "sortmerna_work",
|
| 17 |
+
kvdb: Optional[str] = None,
|
| 18 |
+
idx_dir: Optional[str] = None,
|
| 19 |
+
threads: int = 1,
|
| 20 |
+
fastx: bool = False,
|
| 21 |
+
aligned: Optional[str] = None,
|
| 22 |
+
other: Optional[str] = None,
|
| 23 |
+
blast: Optional[str] = None,
|
| 24 |
+
num_alignments: int = 1,
|
| 25 |
+
best: int = 1,
|
| 26 |
+
paired_in: bool = False,
|
| 27 |
+
paired_out: bool = False,
|
| 28 |
+
out_order: bool = False,
|
| 29 |
+
readfeed: int = 1,
|
| 30 |
+
verbose: bool = False,
|
| 31 |
+
):
|
| 32 |
+
"""
|
| 33 |
+
SortMeRNA: A biological sequence analysis tool for filtering, mapping and OTU-picking NGS reads.
|
| 34 |
+
Mainly used for filtering rRNA from metatranscriptomic data.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
ref: List of paths to reference database files (e.g. silva-bac-16s.fasta). Required.
|
| 38 |
+
reads: List of paths to read files (fasta/fastq/gz). For paired-end, provide two files. Required.
|
| 39 |
+
workdir: Directory for all temporary and output files.
|
| 40 |
+
kvdb: Directory for RocksDB key-value storage (defaults to workdir/kvdb).
|
| 41 |
+
idx_dir: Directory for reference indexes (defaults to workdir/idx).
|
| 42 |
+
threads: Number of threads to use.
|
| 43 |
+
fastx: If True, output aligned and/or rejected reads in FASTA/FASTQ format.
|
| 44 |
+
aligned: File prefix for aligned (rRNA) reads.
|
| 45 |
+
other: File prefix for rejected (non-rRNA) reads.
|
| 46 |
+
blast: Output alignments in BLAST-like format. Use '0' for pairwise, '1' for tabular.
|
| 47 |
+
num_alignments: Report first N alignments per read.
|
| 48 |
+
best: Report best N alignments per read.
|
| 49 |
+
paired_in: If True, flags paired-end reads as interleaved in a single file.
|
| 50 |
+
paired_out: If True, output paired-end reads into two separate files.
|
| 51 |
+
out_order: If True, maintain the same order of reads in output as in input.
|
| 52 |
+
readfeed: Splitting behavior (v5.0.0+). 1 enables multithreaded parallel processing.
|
| 53 |
+
verbose: Enable verbose output.
|
| 54 |
+
"""
|
| 55 |
+
# Input validation
|
| 56 |
+
if not ref:
|
| 57 |
+
return {"error": "At least one reference file (--ref) must be provided."}
|
| 58 |
+
if not reads:
|
| 59 |
+
return {"error": "At least one reads file (--reads) must be provided."}
|
| 60 |
+
|
| 61 |
+
for r in ref:
|
| 62 |
+
if not Path(r).exists():
|
| 63 |
+
return {"error": f"Reference file not found: {r}"}
|
| 64 |
+
for rd in reads:
|
| 65 |
+
if not Path(rd).exists():
|
| 66 |
+
return {"error": f"Reads file not found: {rd}"}
|
| 67 |
+
|
| 68 |
+
if threads < 1:
|
| 69 |
+
threads = 1
|
| 70 |
+
|
| 71 |
+
# Construct command
|
| 72 |
+
cmd = ["sortmerna"]
|
| 73 |
+
|
| 74 |
+
# Add multiple references
|
| 75 |
+
for r in ref:
|
| 76 |
+
cmd.extend(["--ref", str(Path(r).absolute())])
|
| 77 |
+
|
| 78 |
+
# Add multiple reads
|
| 79 |
+
for rd in reads:
|
| 80 |
+
cmd.extend(["--reads", str(Path(rd).absolute())])
|
| 81 |
+
|
| 82 |
+
# Paths
|
| 83 |
+
cmd.extend(["--workdir", str(Path(workdir).absolute())])
|
| 84 |
+
if kvdb:
|
| 85 |
+
cmd.extend(["--kvdb", str(Path(kvdb).absolute())])
|
| 86 |
+
if idx_dir:
|
| 87 |
+
cmd.extend(["--idx-dir", str(Path(idx_dir).absolute())])
|
| 88 |
+
|
| 89 |
+
# Options
|
| 90 |
+
cmd.extend(["--threads", str(threads)])
|
| 91 |
+
cmd.extend(["--num_alignments", str(num_alignments)])
|
| 92 |
+
cmd.extend(["--best", str(best)])
|
| 93 |
+
cmd.extend(["--readfeed", str(readfeed)])
|
| 94 |
+
|
| 95 |
+
if fastx:
|
| 96 |
+
cmd.append("--fastx")
|
| 97 |
+
if aligned:
|
| 98 |
+
cmd.extend(["--aligned", aligned])
|
| 99 |
+
if other:
|
| 100 |
+
cmd.extend(["--other", other])
|
| 101 |
+
if blast is not None:
|
| 102 |
+
cmd.extend(["--blast", str(blast)])
|
| 103 |
+
if paired_in:
|
| 104 |
+
cmd.append("--paired_in")
|
| 105 |
+
if paired_out:
|
| 106 |
+
cmd.append("--paired_out")
|
| 107 |
+
if out_order:
|
| 108 |
+
cmd.append("--out_order")
|
| 109 |
+
if verbose:
|
| 110 |
+
cmd.append("-v")
|
| 111 |
+
|
| 112 |
+
try:
|
| 113 |
+
# Ensure workdir exists
|
| 114 |
+
Path(workdir).mkdir(parents=True, exist_ok=True)
|
| 115 |
+
|
| 116 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 117 |
+
|
| 118 |
+
# Collect output files from workdir/out
|
| 119 |
+
output_path = Path(workdir) / "out"
|
| 120 |
+
output_files = []
|
| 121 |
+
if output_path.exists():
|
| 122 |
+
output_files = [str(f) for f in output_path.glob("*") if f.is_file()]
|
| 123 |
+
|
| 124 |
+
return {
|
| 125 |
+
"command_executed": " ".join(cmd),
|
| 126 |
+
"stdout": result.stdout,
|
| 127 |
+
"stderr": result.stderr,
|
| 128 |
+
"output_files": output_files,
|
| 129 |
+
"status": "success"
|
| 130 |
+
}
|
| 131 |
+
except subprocess.CalledProcessError as e:
|
| 132 |
+
return {
|
| 133 |
+
"command_executed": " ".join(cmd),
|
| 134 |
+
"stdout": e.stdout,
|
| 135 |
+
"stderr": e.stderr,
|
| 136 |
+
"error": str(e),
|
| 137 |
+
"status": "failed"
|
| 138 |
+
}
|
| 139 |
+
except Exception as e:
|
| 140 |
+
return {"error": str(e), "status": "failed"}
|
| 141 |
+
|
| 142 |
+
@mcp.tool()
|
| 143 |
+
def sortmerna_split(
|
| 144 |
+
files: List[str],
|
| 145 |
+
num_splits: int = 8,
|
| 146 |
+
workdir: str = "sortmerna_split_out",
|
| 147 |
+
):
|
| 148 |
+
"""
|
| 149 |
+
Utility to perform multithreaded splitting of large sequence files prior to SortMeRNA processing.
|
| 150 |
+
Uses rapidgzip and pigz internally (if available in the environment).
|
| 151 |
+
|
| 152 |
+
Args:
|
| 153 |
+
files: List of sequence files to split.
|
| 154 |
+
num_splits: Number of chunks to split the files into.
|
| 155 |
+
workdir: Working directory to store split chunks and descriptors.
|
| 156 |
+
"""
|
| 157 |
+
if not files:
|
| 158 |
+
return {"error": "No files provided for splitting."}
|
| 159 |
+
|
| 160 |
+
for f in files:
|
| 161 |
+
if not Path(f).exists():
|
| 162 |
+
return {"error": f"File not found: {f}"}
|
| 163 |
+
|
| 164 |
+
if num_splits < 1:
|
| 165 |
+
num_splits = 1
|
| 166 |
+
|
| 167 |
+
# Based on the documentation: python run.py split --file <f> --num-splits <n> --workdir <w>
|
| 168 |
+
# Note: This assumes 'run.py' is available or the logic is accessible via the sortmerna environment.
|
| 169 |
+
# Since run.py is a helper script in the repo, we attempt to call it via python.
|
| 170 |
+
|
| 171 |
+
cmd = ["python", "run.py", "split"]
|
| 172 |
+
for f in files:
|
| 173 |
+
cmd.extend(["--file", str(Path(f).absolute())])
|
| 174 |
+
|
| 175 |
+
cmd.extend(["--num-splits", str(num_splits)])
|
| 176 |
+
cmd.extend(["--workdir", str(Path(workdir).absolute())])
|
| 177 |
+
|
| 178 |
+
try:
|
| 179 |
+
Path(workdir).mkdir(parents=True, exist_ok=True)
|
| 180 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 181 |
+
|
| 182 |
+
output_files = [str(f) for f in Path(workdir).glob("*") if f.is_file()]
|
| 183 |
+
|
| 184 |
+
return {
|
| 185 |
+
"command_executed": " ".join(cmd),
|
| 186 |
+
"stdout": result.stdout,
|
| 187 |
+
"stderr": result.stderr,
|
| 188 |
+
"output_files": output_files,
|
| 189 |
+
"status": "success"
|
| 190 |
+
}
|
| 191 |
+
except subprocess.CalledProcessError as e:
|
| 192 |
+
return {
|
| 193 |
+
"command_executed": " ".join(cmd),
|
| 194 |
+
"stdout": e.stdout,
|
| 195 |
+
"stderr": e.stderr,
|
| 196 |
+
"error": "Ensure run.py from the SortMeRNA repository is in the current directory or PYTHONPATH.",
|
| 197 |
+
"status": "failed"
|
| 198 |
+
}
|
| 199 |
+
except Exception as e:
|
| 200 |
+
return {"error": str(e), "status": "failed"}
|
| 201 |
+
|
| 202 |
+
@mcp.tool()
|
| 203 |
+
def sortmerna_version():
|
| 204 |
+
"""Check the installed version of SortMeRNA."""
|
| 205 |
+
try:
|
| 206 |
+
result = subprocess.run(["sortmerna", "--version"], capture_output=True, text=True, check=True)
|
| 207 |
+
return {
|
| 208 |
+
"command_executed": "sortmerna --version",
|
| 209 |
+
"stdout": result.stdout,
|
| 210 |
+
"stderr": result.stderr
|
| 211 |
+
}
|
| 212 |
+
except subprocess.CalledProcessError as e:
|
| 213 |
+
return {"error": str(e), "stderr": e.stderr}
|
| 214 |
+
|
| 215 |
+
if __name__ == "__main__":
|
| 216 |
+
mcp.run(transport="stdio")
|
Biomni/mcp_generated/mcp_sortmerna/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp
|
| 2 |
+
mcp
|