ghh1125 commited on
Commit
a0477b5
·
verified ·
1 Parent(s): 7917211

Upload 14 files

Browse files
Dockerfile CHANGED
@@ -1,18 +1,23 @@
1
- FROM python:3.10
2
 
3
- RUN useradd -m -u 1000 user && python -m pip install --upgrade pip
4
- USER user
5
- ENV PATH="/home/user/.local/bin:$PATH"
6
 
7
  WORKDIR /app
8
 
9
- COPY --chown=user ./requirements.txt requirements.txt
10
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
 
 
 
 
 
11
 
12
- COPY --chown=user . /app
13
  ENV MCP_TRANSPORT=http
14
  ENV MCP_PORT=7860
15
 
16
  EXPOSE 7860
17
 
18
- CMD ["python", "deepTools/mcp_output/start_mcp.py"]
 
 
 
1
+ FROM python:3.11-slim
2
 
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
 
5
 
6
  WORKDIR /app
7
 
8
+ RUN useradd -m -u 1000 appuser
9
+
10
+ COPY requirements.txt /app/requirements.txt
11
+ RUN pip install --no-cache-dir -r /app/requirements.txt
12
+
13
+ COPY deepTools /app/deepTools
14
+ COPY app.py /app/app.py
15
 
 
16
  ENV MCP_TRANSPORT=http
17
  ENV MCP_PORT=7860
18
 
19
  EXPOSE 7860
20
 
21
+ USER appuser
22
+
23
+ ENTRYPOINT ["python", "deepTools/mcp_output/start_mcp.py"]
README.md CHANGED
@@ -1,10 +1,72 @@
1
  ---
2
- title: DeepTools
3
- emoji: 🔥
4
- colorFrom: purple
5
- colorTo: pink
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: deepTools MCP Service
3
+ emoji: 🔧
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
+ # deepTools MCP Service
12
+
13
+ This deployment package exposes selected `deeptools` functionality through an MCP-compatible server built with `FastMCP`.
14
+
15
+ ## Available Tools
16
+
17
+ - `health_check`
18
+ - `list_modules`
19
+ - `list_symbols`
20
+ - `call_function`
21
+ - `create_instance`
22
+ - `compute_ratio`
23
+ - `smart_label`
24
+ - `convert_colormap`
25
+
26
+ Detailed parameter docs and examples are available in `deepTools/mcp_output/README_MCP.md`.
27
+
28
+ ## Local stdio usage
29
+
30
+ Use this mode for local MCP clients (for example Claude Desktop / CLI):
31
+
32
+ ```bash
33
+ cd deepTools/mcp_output
34
+ python start_mcp.py
35
+ ```
36
+
37
+ Or:
38
+
39
+ ```bash
40
+ python mcp_plugin/main.py
41
+ ```
42
+
43
+ ## HTTP usage (Docker / hosted)
44
+
45
+ Run with HTTP transport:
46
+
47
+ ```bash
48
+ cd deepTools/mcp_output
49
+ MCP_TRANSPORT=http MCP_PORT=7860 python start_mcp.py
50
+ ```
51
+
52
+ Client connection endpoint:
53
+
54
+ `https://{host}/mcp`
55
+
56
+ ## Docker deployment
57
+
58
+ Build and run:
59
+
60
+ ```bash
61
+ ./run_docker.sh
62
+ ```
63
+
64
+ PowerShell:
65
+
66
+ ```powershell
67
+ ./run_docker.ps1
68
+ ```
69
+
70
+ The Docker entry point is:
71
+
72
+ `python deepTools/mcp_output/start_mcp.py`
app.py CHANGED
@@ -1,45 +1,55 @@
1
- from fastapi import FastAPI
 
2
  import os
3
  import sys
 
 
 
 
 
 
 
 
 
4
 
5
- mcp_plugin_path = os.path.join(os.path.dirname(__file__), "deepTools", "mcp_output", "mcp_plugin")
6
- sys.path.insert(0, mcp_plugin_path)
7
 
8
- app = FastAPI(
9
- title="Deeptools MCP Service",
10
- description="Auto-generated MCP service for deepTools",
11
- version="1.0.0"
12
- )
13
 
14
  @app.get("/")
15
- def root():
16
  return {
17
- "service": "Deeptools MCP Service",
18
- "version": "1.0.0",
19
- "status": "running",
20
- "transport": os.environ.get("MCP_TRANSPORT", "http")
21
  }
22
 
 
23
  @app.get("/health")
24
- def health_check():
25
- return {"status": "healthy", "service": "deepTools MCP"}
 
26
 
27
  @app.get("/tools")
28
- def list_tools():
29
- try:
30
- from mcp_service import create_app
31
- mcp_app = create_app()
32
- tools = []
33
- for tool_name, tool_func in mcp_app.tools.items():
34
- tools.append({
35
- "name": tool_name,
36
- "description": tool_func.__doc__ or "No description available"
37
- })
38
- return {"tools": tools}
39
- except Exception as e:
40
- return {"error": f"Failed to load tools: {str(e)}"}
 
 
 
 
41
 
42
  if __name__ == "__main__":
43
  import uvicorn
44
- port = int(os.environ.get("PORT", 7860))
45
- uvicorn.run(app, host="0.0.0.0", port=port)
 
1
+ from __future__ import annotations
2
+
3
  import os
4
  import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from fastapi import FastAPI
9
+
10
+ CURRENT_DIR = Path(__file__).resolve().parent
11
+ PLUGIN_DIR = CURRENT_DIR / "deepTools" / "mcp_output" / "mcp_plugin"
12
+ if str(PLUGIN_DIR) not in sys.path:
13
+ sys.path.insert(0, str(PLUGIN_DIR))
14
 
15
+ app = FastAPI(title="deepTools MCP Info App", version="1.0.0")
 
16
 
 
 
 
 
 
17
 
18
  @app.get("/")
19
+ def root() -> dict[str, Any]:
20
  return {
21
+ "service": "deepTools MCP Deployment",
22
+ "mcp_transport_default": "stdio",
23
+ "mcp_http_path": "/mcp",
24
+ "note": "This FastAPI app is supplementary and not the MCP runtime entry point.",
25
  }
26
 
27
+
28
  @app.get("/health")
29
+ def health() -> dict[str, str]:
30
+ return {"status": "healthy"}
31
+
32
 
33
  @app.get("/tools")
34
+ def tools() -> dict[str, Any]:
35
+ from mcp_service import create_app
36
+
37
+ mcp = create_app()
38
+ tool_items = []
39
+ for tool in getattr(mcp, "tools", []):
40
+ tool_items.append(
41
+ {
42
+ "name": getattr(tool, "name", getattr(tool, "__name__", "unknown")),
43
+ "description": getattr(tool, "description", ""),
44
+ }
45
+ )
46
+ return {"count": len(tool_items), "tools": tool_items}
47
+
48
+
49
+ PORT = int(os.getenv("PORT", "7860"))
50
+
51
 
52
  if __name__ == "__main__":
53
  import uvicorn
54
+
55
+ uvicorn.run(app, host="0.0.0.0", port=PORT)
deepTools/mcp_output/README_MCP.md CHANGED
@@ -1,131 +1,98 @@
1
- # deepTools MCP (Model Context Protocol) Service README
2
 
3
- ## 1) Project Introduction
4
 
5
- This MCP (Model Context Protocol) service wraps core **deepTools** genomics workflows so LLM clients or automation agents can run signal-processing and QC tasks in a consistent, tool-oriented interface.
 
 
6
 
7
- It is designed for common NGS analysis tasks such as:
8
 
9
- - BAM → coverage track generation
10
- - Differential/comparative signal tracks
11
- - Matrix computation around genomic regions
12
- - Heatmap/profile plotting
13
- - Multi-sample correlation and PCA QC
14
- - GC bias estimation/correction
15
- - Read filtering and alignment post-processing
16
 
17
- Repository: https://github.com/deeptools/deepTools
 
18
 
19
- ---
20
-
21
- ## 2) Installation Method
22
 
23
- ### Requirements
 
24
 
25
- - Python 3.8+ (recommended: 3.9/3.10)
26
- - Core Python deps:
27
- - numpy
28
- - scipy
29
- - matplotlib
30
- - pysam
31
- - pyBigWig
32
- - Optional (feature/environment dependent):
33
- - deeptoolsintervals
34
- - bx-python
35
- - Galaxy runtime components (only for Galaxy wrapper usage)
36
 
37
- ### Install with pip
 
38
 
39
- - Install from PyPI:
40
- pip install deeptools
41
 
42
- - Or install from source:
43
- git clone https://github.com/deeptools/deepTools.git
44
- cd deepTools
45
- pip install -e .
46
 
47
- After install, verify:
48
- bamCoverage --help
49
 
50
- ---
51
 
52
- ## 3) Quick Start
 
 
 
 
 
53
 
54
- ### Typical MCP (Model Context Protocol) workflow
55
 
56
- 1. Create normalized tracks:
57
- - Run `bamCoverage` for each BAM.
58
- - Optionally run `bamCompare` for treatment/control ratio.
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- 2. Build region-based matrix:
61
- - Run `computeMatrix` using bigWig signal + BED/GTF regions.
62
 
63
- 3. Visualize:
64
- - Run `plotHeatmap` and/or `plotProfile`.
 
 
 
 
 
 
 
 
65
 
66
- 4. Multi-sample QC:
67
- - Run `multiBamSummary` or `multiBigwigSummary`.
68
- - Then run `plotCorrelation` and `plotPCA`.
69
 
70
- ### Minimal command examples
 
 
 
71
 
72
- bamCoverage -b sample.bam -o sample.bw --normalizeUsing CPM
73
- computeMatrix reference-point -S sample.bw -R genes.bed -o matrix.gz
74
- plotHeatmap -m matrix.gz -out heatmap.png
75
- plotProfile -m matrix.gz -out profile.png
76
 
77
- ---
 
 
78
 
79
- ## 4) Available Tools and Endpoints List
80
 
81
- Each MCP (Model Context Protocol) endpoint maps to one deepTools command:
 
 
 
82
 
83
- - **bamCoverage**: Generate normalized coverage tracks from BAM.
84
- - **bamCompare**: Compare two BAMs (log2 ratio, subtraction, etc.).
85
- - **computeMatrix**: Build signal matrices around regions/reference points.
86
- - **computeMatrixOperations**: Filter/sort/subset/modify matrix files.
87
- - **plotHeatmap**: Render heatmaps from computed matrices.
88
- - **plotProfile**: Render aggregate profiles from matrices.
89
- - **multiBamSummary**: Summarize multiple BAMs into count matrices.
90
- - **multiBigwigSummary**: Summarize multiple bigWig files.
91
- - **plotCorrelation**: Correlation heatmap/scatter from summary matrices.
92
- - **plotPCA**: PCA visualization from summary outputs.
93
- - **plotCoverage**: Coverage QC summary plot across samples.
94
- - **plotFingerprint**: Enrichment/complexity QC fingerprint plot.
95
- - **plotEnrichment**: Feature-centric enrichment plotting.
96
- - **estimateReadFiltering**: Estimate effects of read filtering settings.
97
- - **alignmentSieve**: Filter/transform alignments by flags/fragment criteria.
98
- - **bigwigCompare**: Compare two bigWig tracks.
99
- - **bigwigAverage**: Average multiple bigWig tracks.
100
- - **computeGCBias**: Compute GC bias metrics.
101
- - **correctGCBias**: Apply GC bias correction.
102
- - **bamPEFragmentSize**: Estimate paired-end fragment size distributions.
103
-
104
- ---
105
-
106
- ## 5) Common Issues and Notes
107
-
108
- - **Chromosome naming mismatch** (e.g., `chr1` vs `1`) is a frequent failure source.
109
- - **Input indexing required**:
110
- - BAM files should be indexed (`.bai`).
111
- - bigWig inputs must be valid and readable.
112
- - **Memory/CPU usage**:
113
- - `computeMatrix`, summaries, and plotting can be heavy on large cohorts.
114
- - Tune bin size, region count, and processor options for performance.
115
- - **Headless environments**:
116
- - Ensure matplotlib backend works in server/CI contexts.
117
- - **Dependency issues**:
118
- - `pysam`/`pyBigWig` build or binary compatibility may vary by OS.
119
- - Prefer clean virtual environments.
120
- - **Output compatibility**:
121
- - Keep deepTools versions consistent across matrix generation and plotting steps.
122
-
123
- ---
124
-
125
- ## 6) Reference Links / Documentation
126
-
127
- - Main repository: https://github.com/deeptools/deepTools
128
- - Official docs: https://deeptools.readthedocs.io/
129
- - Package metadata/config: `pyproject.toml` in repo root
130
- - Changelog: `CHANGES.txt`
131
- - Contributing guide: `.github/CONTRIBUTING.md`
 
1
+ # deepTools MCP Tools
2
 
3
+ This MCP layer wraps selected `deeptools` capabilities with standardized responses:
4
 
5
+ ```json
6
+ {"success": true|false, "result": <any|null>, "error": "<message|null>"}
7
+ ```
8
 
9
+ ## Exposed Tools
10
 
11
+ 1. `health_check()`
12
+ - Returns dependency availability and adapter/module loading status.
 
 
 
 
 
13
 
14
+ 2. `list_modules(include_failed: bool = true)`
15
+ - Lists loaded `deeptools.*` modules and optional import failures.
16
 
17
+ 3. `list_symbols(module_name: str, public_only: bool = true, limit: int = 100)`
18
+ - Lists symbols from one loaded module.
 
19
 
20
+ 4. `call_function(module_name: str, function_name: str, args_json: str = "[]", kwargs_json: str = "{}")`
21
+ - Calls one module-level function using JSON-encoded args/kwargs.
22
 
23
+ 5. `create_instance(module_name: str, class_name: str, init_args_json: str = "[]", init_kwargs_json: str = "{}")`
24
+ - Creates one class instance and returns type/representation metadata.
 
 
 
 
 
 
 
 
 
25
 
26
+ 6. `compute_ratio(value1: float, value2: float, value_type: str = "ratio", scale_factor_1: float = 1.0, scale_factor_2: float = 1.0, pseudocount_1: float = 1.0, pseudocount_2: float = 1.0)`
27
+ - Computes bin values with `deeptools.getRatio.getRatio` semantics.
28
 
29
+ 7. `smart_label(label: str)`
30
+ - Normalizes a label/path using `deeptools.utilities.smartLabel`.
31
 
32
+ 8. `convert_colormap(cmap_name: str = "viridis", vmin: float = 0.0, vmax: float = 1.0)`
33
+ - Converts a Matplotlib colormap into deepTools-compatible RGB stops.
 
 
34
 
35
+ ## Example Tool Calls
 
36
 
37
+ ### Health check
38
 
39
+ ```json
40
+ {
41
+ "tool": "health_check",
42
+ "arguments": {}
43
+ }
44
+ ```
45
 
46
+ ### Compute ratio
47
 
48
+ ```json
49
+ {
50
+ "tool": "compute_ratio",
51
+ "arguments": {
52
+ "value1": 9.0,
53
+ "value2": 19.0,
54
+ "value_type": "ratio",
55
+ "scale_factor_1": 1.0,
56
+ "scale_factor_2": 1.0,
57
+ "pseudocount_1": 1.0,
58
+ "pseudocount_2": 1.0
59
+ }
60
+ }
61
+ ```
62
 
63
+ ### List symbols
 
64
 
65
+ ```json
66
+ {
67
+ "tool": "list_symbols",
68
+ "arguments": {
69
+ "module_name": "deeptools.utilities",
70
+ "public_only": true,
71
+ "limit": 20
72
+ }
73
+ }
74
+ ```
75
 
76
+ ## Local stdio run
 
 
77
 
78
+ ```bash
79
+ cd deepTools/mcp_output
80
+ python start_mcp.py
81
+ ```
82
 
83
+ Or directly:
 
 
 
84
 
85
+ ```bash
86
+ python mcp_plugin/main.py
87
+ ```
88
 
89
+ ## HTTP run
90
 
91
+ ```bash
92
+ cd deepTools/mcp_output
93
+ MCP_TRANSPORT=http MCP_PORT=7860 python start_mcp.py
94
+ ```
95
 
96
+ The MCP HTTP endpoint is served at:
97
+
98
+ `http://127.0.0.1:7860/mcp`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deepTools/mcp_output/mcp_plugin/adapter.py CHANGED
@@ -1,280 +1,172 @@
1
- import os
2
- import sys
3
- import traceback
4
- import importlib
5
- import inspect
6
- from typing import Any, Dict, List, Optional, Tuple
7
-
8
- source_path = os.path.join(
9
- os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
10
- "source",
11
- )
12
- sys.path.insert(0, source_path)
13
 
 
 
 
 
 
 
14
 
15
- class Adapter:
16
- """
17
- Adapter for deepTools in import-first mode with graceful fallback to CLI execution.
 
18
 
19
- This adapter targets modules identified in the analysis:
20
- - deeptools.bamCoverage
21
- - deeptools.bamCompare
22
- - deeptools.computeMatrix
23
- - deeptools.computeMatrixOperations
24
- - deeptools.plotHeatmap
25
- - deeptools.plotProfile
26
- - deeptools.multiBamSummary
27
- - deeptools.multiBigwigSummary
28
- - deeptools.plotCorrelation
29
- - deeptools.plotPCA
30
- - deeptools.plotCoverage
31
- - deeptools.plotFingerprint
32
- - deeptools.plotEnrichment
33
- - deeptools.estimateReadFiltering
34
- - deeptools.alignmentSieve
35
- - deeptools.bigwigCompare
36
- - deeptools.bigwigAverage
37
- - deeptools.computeGCBias
38
- - deeptools.correctGCBias
39
- - deeptools.bamPEFragmentSize
40
- """
41
 
42
- # -------------------------------------------------------------------------
43
- # Initialization and module registry
44
- # -------------------------------------------------------------------------
45
- def __init__(self) -> None:
46
- self.mode = "import"
47
- self._modules: Dict[str, Dict[str, Any]] = {}
48
- self._module_map: Dict[str, str] = {
49
- "bamCoverage": "deeptools.bamCoverage",
50
- "bamCompare": "deeptools.bamCompare",
51
- "computeMatrix": "deeptools.computeMatrix",
52
- "computeMatrixOperations": "deeptools.computeMatrixOperations",
53
- "plotHeatmap": "deeptools.plotHeatmap",
54
- "plotProfile": "deeptools.plotProfile",
55
- "multiBamSummary": "deeptools.multiBamSummary",
56
- "multiBigwigSummary": "deeptools.multiBigwigSummary",
57
- "plotCorrelation": "deeptools.plotCorrelation",
58
- "plotPCA": "deeptools.plotPCA",
59
- "plotCoverage": "deeptools.plotCoverage",
60
- "plotFingerprint": "deeptools.plotFingerprint",
61
- "plotEnrichment": "deeptools.plotEnrichment",
62
- "estimateReadFiltering": "deeptools.estimateReadFiltering",
63
- "alignmentSieve": "deeptools.alignmentSieve",
64
- "bigwigCompare": "deeptools.bigwigCompare",
65
- "bigwigAverage": "deeptools.bigwigAverage",
66
- "computeGCBias": "deeptools.computeGCBias",
67
- "correctGCBias": "deeptools.correctGCBias",
68
- "bamPEFragmentSize": "deeptools.bamPEFragmentSize",
69
- }
70
- self._load_all_modules()
71
 
72
- def _result(
73
- self,
74
- status: str,
75
- message: str,
76
- data: Optional[Dict[str, Any]] = None,
77
- error: Optional[str] = None,
78
- guidance: Optional[str] = None,
79
- ) -> Dict[str, Any]:
80
- return {
81
- "status": status,
82
- "mode": self.mode,
83
- "message": message,
84
- "data": data or {},
85
- "error": error,
86
- "guidance": guidance,
87
- }
88
 
89
- def _load_module(self, tool_name: str, module_path: str) -> None:
90
  try:
91
- module = importlib.import_module(module_path)
92
- self._modules[tool_name] = {
93
- "imported": True,
94
- "module": module,
95
- "module_path": module_path,
96
- "error": None,
97
- }
98
- except Exception as e:
99
- self._modules[tool_name] = {
100
- "imported": False,
101
- "module": None,
102
- "module_path": module_path,
103
- "error": f"{type(e).__name__}: {e}",
104
- }
105
-
106
- def _load_all_modules(self) -> None:
107
- for tool_name, module_path in self._module_map.items():
108
- self._load_module(tool_name, module_path)
 
109
 
110
- # -------------------------------------------------------------------------
111
- # Introspection and health
112
- # -------------------------------------------------------------------------
113
- def health_check(self) -> Dict[str, Any]:
114
- """
115
- Report adapter and import readiness status.
116
 
117
- Returns:
118
- Unified status dictionary with per-module import state.
119
- """
120
- imported = {k: v["imported"] for k, v in self._modules.items()}
121
- failed = {k: v["error"] for k, v in self._modules.items() if not v["imported"]}
122
- status = "success" if all(imported.values()) else "partial_success"
123
- message = (
124
- "All modules imported successfully."
125
- if status == "success"
126
- else "Some modules failed to import. CLI fallback is available."
127
- )
128
  return self._result(
129
- status=status,
130
- message=message,
131
- data={"imports": imported, "failed": failed},
132
- guidance=(
133
- "Verify local source tree under 'source/deeptools' and required dependencies "
134
- "(numpy, scipy, matplotlib, pysam, pyBigWig)."
135
- if failed
136
- else None
137
- ),
138
  )
139
 
140
- def list_tools(self) -> Dict[str, Any]:
141
- """
142
- List supported deepTools commands and their module paths.
143
- """
144
- data = []
145
- for name, path in self._module_map.items():
146
- meta = self._modules.get(name, {})
147
- data.append(
148
- {
149
- "tool": name,
150
- "module_path": path,
151
- "imported": meta.get("imported", False),
152
- "error": meta.get("error"),
153
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  )
155
- return self._result(status="success", message="Supported tools listed.", data={"tools": data})
156
-
157
- # -------------------------------------------------------------------------
158
- # Core invocation helpers
159
- # -------------------------------------------------------------------------
160
- def _build_argv(self, kwargs: Dict[str, Any]) -> List[str]:
161
- argv: List[str] = []
162
- for key, value in kwargs.items():
163
- if value is None:
164
- continue
165
- flag = f"--{key.replace('_', '-')}"
166
- if isinstance(value, bool):
167
- if value:
168
- argv.append(flag)
169
- elif isinstance(value, (list, tuple)):
170
- for item in value:
171
- argv.append(flag)
172
- argv.append(str(item))
173
- else:
174
- argv.append(flag)
175
- argv.append(str(value))
176
- return argv
177
 
178
- def _find_callable(self, module: Any) -> Tuple[Optional[str], Optional[Any]]:
179
- preferred = ["main", "run", "parseArguments", "process_args"]
180
- for name in preferred:
181
- fn = getattr(module, name, None)
182
- if callable(fn):
183
- return name, fn
184
- for name, obj in inspect.getmembers(module):
185
- if callable(obj) and name.lower() in {"main", "run"}:
186
- return name, obj
187
- return None, None
188
 
189
- def _invoke_module(self, tool_name: str, **kwargs: Any) -> Dict[str, Any]:
190
- meta = self._modules.get(tool_name)
191
- if not meta:
192
  return self._result(
193
- status="error",
194
- message=f"Unknown tool: {tool_name}.",
195
- error="ToolNotRegistered",
196
- guidance="Call list_tools() to view available tools.",
 
 
 
 
 
 
 
197
  )
198
 
199
- if meta["imported"] and meta["module"] is not None:
200
- module = meta["module"]
201
- fn_name, fn = self._find_callable(module)
202
- if not fn:
203
- return self._result(
204
- status="error",
205
- message=f"No callable entry point found for {tool_name}.",
206
- error="EntryPointNotFound",
207
- guidance="Inspect module for a callable main/run function or use CLI fallback.",
208
- )
209
- try:
210
- argv = self._build_argv(kwargs)
211
- try:
212
- result = fn(argv)
213
- except TypeError:
214
- result = fn()
215
- return self._result(
216
- status="success",
217
- message=f"{tool_name} executed via import mode.",
218
- data={
219
- "tool": tool_name,
220
- "module": meta["module_path"],
221
- "entry_point": fn_name,
222
- "argv": argv,
223
- "result": result,
224
- },
225
- )
226
- except SystemExit as e:
227
- code = int(getattr(e, "code", 0) or 0)
228
- if code == 0:
229
- return self._result(
230
- status="success",
231
- message=f"{tool_name} finished (SystemExit 0).",
232
- data={"tool": tool_name, "exit_code": code},
233
- )
234
- return self._result(
235
- status="error",
236
- message=f"{tool_name} exited with non-zero status.",
237
- error=f"SystemExit({code})",
238
- guidance="Validate command arguments and input file paths.",
239
- )
240
- except Exception as e:
241
- return self._result(
242
- status="error",
243
- message=f"{tool_name} failed during import-mode execution.",
244
- error=f"{type(e).__name__}: {e}",
245
- data={"traceback": traceback.format_exc()},
246
- guidance="Check argument names and values; verify required dependencies are installed.",
247
- )
248
-
249
- return self._result(
250
- status="fallback",
251
- message=f"{tool_name} import unavailable; CLI fallback suggested.",
252
- error=meta.get("error"),
253
- guidance=(
254
- f"Run command-line tool '{tool_name}' directly in an environment where deepTools is installed, "
255
- "or fix local imports under source/deeptools."
256
- ),
257
- )
258
-
259
- # -------------------------------------------------------------------------
260
- # Tool methods (one per identified command)
261
- # -------------------------------------------------------------------------
262
- def bamCoverage(self, **kwargs: Any) -> Dict[str, Any]:
263
- """Execute deeptools.bamCoverage."""
264
- return self._invoke_module("bamCoverage", **kwargs)
265
-
266
- def bamCompare(self, **kwargs: Any) -> Dict[str, Any]:
267
- """Execute deeptools.bamCompare."""
268
- return self._invoke_module("bamCompare", **kwargs)
269
-
270
- def computeMatrix(self, **kwargs: Any) -> Dict[str, Any]:
271
- """Execute deeptools.computeMatrix."""
272
- return self._invoke_module("computeMatrix", **kwargs)
273
 
274
- def computeMatrixOperations(self, **kwargs: Any) -> Dict[str, Any]:
275
- """Execute deeptools.computeMatrixOperations."""
276
- return self._invoke_module("computeMatrixOperations", **kwargs)
277
 
278
- def plotHeatmap(self, **kwargs: Any) -> Dict[str, Any]:
279
- """Execute deeptools.plotHeatmap."""
280
- return self._
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import importlib
4
+ import pkgutil
5
+ import sys
6
+ from pathlib import Path
7
+ from types import ModuleType
8
+ from typing import Any
9
 
10
+ CURRENT_DIR = Path(__file__).resolve().parent
11
+ SOURCE_DIR = CURRENT_DIR.parents[2] / "source"
12
+ if str(SOURCE_DIR) not in sys.path:
13
+ sys.path.insert(0, str(SOURCE_DIR))
14
 
15
+ try:
16
+ import deeptools # type: ignore
17
+ except Exception:
18
+ deeptools = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ class Adapter:
22
+ def __init__(self, package_name: str = "deeptools") -> None:
23
+ self.package_name = package_name
24
+ self.loaded_modules: dict[str, ModuleType] = {}
25
+ self.failed_modules: dict[str, str] = {}
26
+ self.mode = "normal"
27
+ self._load_modules()
28
+
29
+ def _result(self, status: str, **payload: Any) -> dict[str, Any]:
30
+ result: dict[str, Any] = {"status": status}
31
+ result.update(payload)
32
+ return result
33
+
34
+ def _load_modules(self) -> None:
35
+ self.loaded_modules.clear()
36
+ self.failed_modules.clear()
37
 
 
38
  try:
39
+ root_module = importlib.import_module(self.package_name)
40
+ self.loaded_modules[self.package_name] = root_module
41
+ except Exception as exc:
42
+ self.mode = "blackbox"
43
+ self.failed_modules[self.package_name] = f"{type(exc).__name__}: {exc}"
44
+ return
45
+
46
+ module_path = getattr(root_module, "__path__", None)
47
+ if not module_path:
48
+ self.mode = "normal"
49
+ return
50
+
51
+ for module_info in pkgutil.walk_packages(module_path, prefix=f"{self.package_name}."):
52
+ module_name = module_info.name
53
+ try:
54
+ mod = importlib.import_module(module_name)
55
+ self.loaded_modules[module_name] = mod
56
+ except Exception as exc:
57
+ self.failed_modules[module_name] = f"{type(exc).__name__}: {exc}"
58
 
59
+ self.mode = "normal" if self.loaded_modules else "blackbox"
 
 
 
 
 
60
 
61
+ def health(self) -> dict[str, Any]:
62
+ if self.mode == "blackbox":
63
+ return self._result(
64
+ "fallback",
65
+ mode=self.mode,
66
+ package=self.package_name,
67
+ loaded_count=0,
68
+ failed_count=len(self.failed_modules),
69
+ message="No modules were loaded; adapter is in blackbox mode.",
70
+ )
 
71
  return self._result(
72
+ "ok",
73
+ mode=self.mode,
74
+ package=self.package_name,
75
+ loaded_count=len(self.loaded_modules),
76
+ failed_count=len(self.failed_modules),
 
 
 
 
77
  )
78
 
79
+ def list_modules(self, include_failed: bool = True) -> dict[str, Any]:
80
+ payload: dict[str, Any] = {
81
+ "mode": self.mode,
82
+ "loaded": sorted(self.loaded_modules.keys()),
83
+ }
84
+ if include_failed:
85
+ payload["failed"] = dict(sorted(self.failed_modules.items()))
86
+ status = "fallback" if self.mode == "blackbox" else "ok"
87
+ return self._result(status, **payload)
88
+
89
+ def list_symbols(self, module_name: str, public_only: bool = True, limit: int = 200) -> dict[str, Any]:
90
+ module = self.loaded_modules.get(module_name)
91
+ if module is None:
92
+ return self._result("error", message=f"Module '{module_name}' is not loaded.")
93
+
94
+ symbols = dir(module)
95
+ if public_only:
96
+ symbols = [name for name in symbols if not name.startswith("_")]
97
+ return self._result("ok", module=module_name, symbols=sorted(symbols)[: max(1, limit)])
98
+
99
+ def call_function(
100
+ self,
101
+ module_name: str,
102
+ function_name: str,
103
+ args: list[Any] | None = None,
104
+ kwargs: dict[str, Any] | None = None,
105
+ ) -> dict[str, Any]:
106
+ module = self.loaded_modules.get(module_name)
107
+ if module is None:
108
+ return self._result("error", message=f"Module '{module_name}' is not loaded.")
109
+
110
+ target = getattr(module, function_name, None)
111
+ if target is None or not callable(target):
112
+ return self._result(
113
+ "error",
114
+ message=f"Function '{function_name}' not found in '{module_name}'.",
115
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ safe_args = args if args is not None else []
118
+ safe_kwargs = kwargs if kwargs is not None else {}
 
 
 
 
 
 
 
 
119
 
120
+ try:
121
+ output = target(*safe_args, **safe_kwargs)
 
122
  return self._result(
123
+ "ok",
124
+ module=module_name,
125
+ function=function_name,
126
+ result=output,
127
+ )
128
+ except Exception as exc:
129
+ return self._result(
130
+ "error",
131
+ module=module_name,
132
+ function=function_name,
133
+ message=f"{type(exc).__name__}: {exc}",
134
  )
135
 
136
+ def create_instance(
137
+ self,
138
+ module_name: str,
139
+ class_name: str,
140
+ init_args: list[Any] | None = None,
141
+ init_kwargs: dict[str, Any] | None = None,
142
+ ) -> dict[str, Any]:
143
+ module = self.loaded_modules.get(module_name)
144
+ if module is None:
145
+ return self._result("error", message=f"Module '{module_name}' is not loaded.")
146
+
147
+ class_obj = getattr(module, class_name, None)
148
+ if class_obj is None:
149
+ return self._result(
150
+ "error",
151
+ message=f"Class '{class_name}' not found in '{module_name}'.",
152
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
+ safe_args = init_args if init_args is not None else []
155
+ safe_kwargs = init_kwargs if init_kwargs is not None else {}
 
156
 
157
+ try:
158
+ instance = class_obj(*safe_args, **safe_kwargs)
159
+ return self._result(
160
+ "ok",
161
+ module=module_name,
162
+ class_name=class_name,
163
+ instance_type=type(instance).__name__,
164
+ repr=repr(instance),
165
+ )
166
+ except Exception as exc:
167
+ return self._result(
168
+ "error",
169
+ module=module_name,
170
+ class_name=class_name,
171
+ message=f"{type(exc).__name__}: {exc}",
172
+ )
deepTools/mcp_output/mcp_plugin/main.py CHANGED
@@ -1,13 +1,16 @@
1
- """
2
- MCP Service Auto-Wrapper - Auto-generated
3
- """
4
  from mcp_service import create_app
5
 
6
- def main():
7
- """Main entry point"""
 
8
  app = create_app()
9
- return app
 
 
 
 
10
 
11
  if __name__ == "__main__":
12
- app = main()
13
- app.run()
 
1
+ from __future__ import annotations
2
+
 
3
  from mcp_service import create_app
4
 
5
+
6
+ def main() -> None:
7
+ # Local stdio entry point only (Claude Desktop / CLI), not for web/Docker deployment.
8
  app = create_app()
9
+ try:
10
+ app.run(transport="stdio")
11
+ except TypeError:
12
+ app.run()
13
+
14
 
15
  if __name__ == "__main__":
16
+ main()
 
deepTools/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,340 +1,302 @@
1
- import os
 
 
 
2
  import sys
3
- from typing import Dict, Any, List, Optional
4
-
5
- source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
6
- if source_path not in sys.path:
7
- sys.path.insert(0, source_path)
8
-
9
- from fastmcp import FastMCP
10
-
11
- from deeptools import (
12
- bamCoverage,
13
- bamCompare,
14
- computeMatrix,
15
- computeMatrixOperations,
16
- plotHeatmap,
17
- plotProfile,
18
- multiBamSummary,
19
- multiBigwigSummary,
20
- plotCorrelation,
21
- plotPCA,
22
- plotCoverage,
23
- plotFingerprint,
24
- plotEnrichment,
25
- estimateReadFiltering,
26
- alignmentSieve,
27
- bigwigCompare,
28
- bigwigAverage,
29
- computeGCBias,
30
- correctGCBias,
31
- bamPEFragmentSize,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  )
33
 
34
- mcp = FastMCP("deeptools_mcp_service")
35
-
36
-
37
- def _run_module_main(module: Any, argv: List[str]) -> Dict[str, Any]:
38
- try:
39
- old_argv = sys.argv[:]
40
- sys.argv = [module.__name__.split(".")[-1]] + argv
41
- if hasattr(module, "main"):
42
- result = module.main()
43
- return {"success": True, "result": result, "error": None}
44
- return {"success": False, "result": None, "error": f"Module {module.__name__} has no main()"}
45
- except Exception as e:
46
- return {"success": False, "result": None, "error": str(e)}
47
- finally:
48
- sys.argv = old_argv
49
-
50
-
51
- def _split_args(args: str) -> List[str]:
52
- if not args.strip():
53
- return []
54
- return [a for a in args.strip().split(" ") if a]
55
-
56
-
57
- @mcp.tool(name="bam_coverage", description="Generate normalized coverage tracks from BAM files.")
58
- def bam_coverage(args: str) -> Dict[str, Any]:
59
- """
60
- Run deepTools bamCoverage CLI-compatible entrypoint.
61
-
62
- Parameters:
63
- - args: Space-delimited command-line arguments for bamCoverage.
64
-
65
- Returns:
66
- - Dict with success/result/error.
67
- """
68
- return _run_module_main(bamCoverage, _split_args(args))
69
-
70
-
71
- @mcp.tool(name="bam_compare", description="Compare two BAM files to produce ratio/difference tracks.")
72
- def bam_compare(args: str) -> Dict[str, Any]:
73
- """
74
- Run deepTools bamCompare CLI-compatible entrypoint.
75
-
76
- Parameters:
77
- - args: Space-delimited command-line arguments for bamCompare.
78
-
79
- Returns:
80
- - Dict with success/result/error.
81
- """
82
- return _run_module_main(bamCompare, _split_args(args))
83
-
84
-
85
- @mcp.tool(name="compute_matrix", description="Compute signal matrices around regions/reference points.")
86
- def compute_matrix(args: str) -> Dict[str, Any]:
87
- """
88
- Run deepTools computeMatrix CLI-compatible entrypoint.
89
-
90
- Parameters:
91
- - args: Space-delimited command-line arguments for computeMatrix.
92
-
93
- Returns:
94
- - Dict with success/result/error.
95
- """
96
- return _run_module_main(computeMatrix, _split_args(args))
97
-
98
-
99
- @mcp.tool(name="compute_matrix_operations", description="Perform operations on computed matrix files.")
100
- def compute_matrix_operations(args: str) -> Dict[str, Any]:
101
- """
102
- Run deepTools computeMatrixOperations CLI-compatible entrypoint.
103
-
104
- Parameters:
105
- - args: Space-delimited command-line arguments for computeMatrixOperations.
106
-
107
- Returns:
108
- - Dict with success/result/error.
109
- """
110
- return _run_module_main(computeMatrixOperations, _split_args(args))
111
-
112
-
113
- @mcp.tool(name="plot_heatmap", description="Render heatmaps from matrix files.")
114
- def plot_heatmap(args: str) -> Dict[str, Any]:
115
- """
116
- Run deepTools plotHeatmap CLI-compatible entrypoint.
117
-
118
- Parameters:
119
- - args: Space-delimited command-line arguments for plotHeatmap.
120
-
121
- Returns:
122
- - Dict with success/result/error.
123
- """
124
- return _run_module_main(plotHeatmap, _split_args(args))
125
-
126
-
127
- @mcp.tool(name="plot_profile", description="Render signal profiles from matrix files.")
128
- def plot_profile(args: str) -> Dict[str, Any]:
129
- """
130
- Run deepTools plotProfile CLI-compatible entrypoint.
131
-
132
- Parameters:
133
- - args: Space-delimited command-line arguments for plotProfile.
134
-
135
- Returns:
136
- - Dict with success/result/error.
137
- """
138
- return _run_module_main(plotProfile, _split_args(args))
139
-
140
-
141
- @mcp.tool(name="multi_bam_summary", description="Summarize multiple BAM files by bins/regions.")
142
- def multi_bam_summary(args: str) -> Dict[str, Any]:
143
- """
144
- Run deepTools multiBamSummary CLI-compatible entrypoint.
145
-
146
- Parameters:
147
- - args: Space-delimited command-line arguments for multiBamSummary.
148
-
149
- Returns:
150
- - Dict with success/result/error.
151
- """
152
- return _run_module_main(multiBamSummary, _split_args(args))
153
-
154
 
155
- @mcp.tool(name="multi_bigwig_summary", description="Summarize multiple bigWig files by bins/regions.")
156
- def multi_bigwig_summary(args: str) -> Dict[str, Any]:
157
- """
158
- Run deepTools multiBigwigSummary CLI-compatible entrypoint.
159
 
160
- Parameters:
161
- - args: Space-delimited command-line arguments for multiBigwigSummary.
162
 
163
- Returns:
164
- - Dict with success/result/error.
165
- """
166
- return _run_module_main(multiBigwigSummary, _split_args(args))
167
 
 
 
168
 
169
- @mcp.tool(name="plot_correlation", description="Plot correlation heatmap/scatter from summary results.")
170
- def plot_correlation(args: str) -> Dict[str, Any]:
171
- """
172
- Run deepTools plotCorrelation CLI-compatible entrypoint.
173
 
174
- Parameters:
175
- - args: Space-delimited command-line arguments for plotCorrelation.
 
 
 
176
 
177
- Returns:
178
- - Dict with success/result/error.
179
- """
180
- return _run_module_main(plotCorrelation, _split_args(args))
181
 
 
 
 
 
 
182
 
183
- @mcp.tool(name="plot_pca", description="Plot PCA from summary matrix.")
184
- def plot_pca(args: str) -> Dict[str, Any]:
185
- """
186
- Run deepTools plotPCA CLI-compatible entrypoint.
187
 
188
- Parameters:
189
- - args: Space-delimited command-line arguments for plotPCA.
 
190
 
191
  Returns:
192
- - Dict with success/result/error.
193
  """
194
- return _run_module_main(plotPCA, _split_args(args))
 
 
 
 
 
 
 
 
195
 
196
 
197
- @mcp.tool(name="plot_coverage", description="Coverage QC visualization over sample sets.")
198
- def plot_coverage(args: str) -> Dict[str, Any]:
199
- """
200
- Run deepTools plotCoverage CLI-compatible entrypoint.
201
 
202
- Parameters:
203
- - args: Space-delimited command-line arguments for plotCoverage.
204
 
205
  Returns:
206
- - Dict with success/result/error.
207
  """
208
- return _run_module_main(plotCoverage, _split_args(args))
209
 
210
 
211
- @mcp.tool(name="plot_fingerprint", description="ChIP/ATAC enrichment and complexity QC fingerprint plotting.")
212
- def plot_fingerprint(args: str) -> Dict[str, Any]:
213
- """
214
- Run deepTools plotFingerprint CLI-compatible entrypoint.
215
 
216
- Parameters:
217
- - args: Space-delimited command-line arguments for plotFingerprint.
 
 
218
 
219
  Returns:
220
- - Dict with success/result/error.
221
  """
222
- return _run_module_main(plotFingerprint, _split_args(args))
 
 
 
223
 
224
 
225
- @mcp.tool(name="plot_enrichment", description="Feature-centric enrichment plotting.")
226
- def plot_enrichment(args: str) -> Dict[str, Any]:
227
- """
228
- Run deepTools plotEnrichment CLI-compatible entrypoint.
229
 
230
- Parameters:
231
- - args: Space-delimited command-line arguments for plotEnrichment.
 
 
 
232
 
233
  Returns:
234
- - Dict with success/result/error.
235
- """
236
- return _run_module_main(plotEnrichment, _split_args(args))
237
-
238
-
239
- @mcp.tool(name="estimate_read_filtering", description="Estimate read filtering effects under selected criteria.")
240
- def estimate_read_filtering(args: str) -> Dict[str, Any]:
241
  """
242
- Run deepTools estimateReadFiltering CLI-compatible entrypoint.
243
-
244
- Parameters:
245
- - args: Space-delimited command-line arguments for estimateReadFiltering.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  Returns:
248
- - Dict with success/result/error.
249
- """
250
- return _run_module_main(estimateReadFiltering, _split_args(args))
251
-
252
-
253
- @mcp.tool(name="alignment_sieve", description="Filter and transform alignments based on flags/fragment criteria.")
254
- def alignment_sieve(args: str) -> Dict[str, Any]:
255
  """
256
- Run deepTools alignmentSieve CLI-compatible entrypoint.
257
-
258
- Parameters:
259
- - args: Space-delimited command-line arguments for alignmentSieve.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
  Returns:
262
- - Dict with success/result/error.
263
- """
264
- return _run_module_main(alignmentSieve, _split_args(args))
265
-
266
-
267
- @mcp.tool(name="bigwig_compare", description="Compare two bigWig tracks.")
268
- def bigwig_compare_tool(args: str) -> Dict[str, Any]:
269
  """
270
- Run deepTools bigwigCompare CLI-compatible entrypoint.
 
271
 
272
- Parameters:
273
- - args: Space-delimited command-line arguments for bigwigCompare.
 
274
 
275
- Returns:
276
- - Dict with success/result/error.
277
- """
278
- return _run_module_main(bigwigCompare, _split_args(args))
 
 
 
 
 
 
279
 
280
 
281
- @mcp.tool(name="bigwig_average", description="Average multiple bigWig tracks.")
282
- def bigwig_average_tool(args: str) -> Dict[str, Any]:
283
- """
284
- Run deepTools bigwigAverage CLI-compatible entrypoint.
285
 
286
- Parameters:
287
- - args: Space-delimited command-line arguments for bigwigAverage.
288
 
289
  Returns:
290
- - Dict with success/result/error.
291
- """
292
- return _run_module_main(bigwigAverage, _split_args(args))
293
-
294
-
295
- @mcp.tool(name="compute_gc_bias", description="Compute GC bias metrics.")
296
- def compute_gc_bias(args: str) -> Dict[str, Any]:
297
  """
298
- Run deepTools computeGCBias CLI-compatible entrypoint.
299
-
300
- Parameters:
301
- - args: Space-delimited command-line arguments for computeGCBias.
 
302
 
303
- Returns:
304
- - Dict with success/result/error.
305
- """
306
- return _run_module_main(computeGCBias, _split_args(args))
307
 
308
 
309
- @mcp.tool(name="correct_gc_bias", description="Apply GC bias correction.")
310
- def correct_gc_bias(args: str) -> Dict[str, Any]:
311
- """
312
- Run deepTools correctGCBias CLI-compatible entrypoint.
313
 
314
- Parameters:
315
- - args: Space-delimited command-line arguments for correctGCBias.
 
 
316
 
317
  Returns:
318
- - Dict with success/result/error.
319
  """
320
- return _run_module_main(correctGCBias, _split_args(args))
321
-
 
 
 
322
 
323
- @mcp.tool(name="bam_pe_fragment_size", description="Estimate paired-end fragment-size distributions.")
324
- def bam_pe_fragment_size(args: str) -> Dict[str, Any]:
325
- """
326
- Run deepTools bamPEFragmentSize CLI-compatible entrypoint.
327
-
328
- Parameters:
329
- - args: Space-delimited command-line arguments for bamPEFragmentSize.
330
-
331
- Returns:
332
- - Dict with success/result/error.
333
- """
334
- return _run_module_main(bamPEFragmentSize, _split_args(args))
335
 
336
 
337
- def create_app() -> FastMCP:
338
  return mcp
339
 
340
 
 
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import json
5
  import sys
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ CURRENT_DIR = Path(__file__).resolve().parent
10
+ SOURCE_DIR = CURRENT_DIR.parents[2] / "source"
11
+ if str(SOURCE_DIR) not in sys.path:
12
+ sys.path.insert(0, str(SOURCE_DIR))
13
+
14
+ try:
15
+ from fastmcp import FastMCP
16
+ except Exception:
17
+ FastMCP = None
18
+
19
+ try:
20
+ np = importlib.import_module("numpy")
21
+ except Exception:
22
+ np = None
23
+
24
+ try:
25
+ dt_utilities = importlib.import_module("deeptools.utilities")
26
+ except Exception:
27
+ dt_utilities = None
28
+
29
+ try:
30
+ dt_get_ratio = importlib.import_module("deeptools.getRatio")
31
+ except Exception:
32
+ dt_get_ratio = None
33
+
34
+ try:
35
+ matplotlib_mod = importlib.import_module("matplotlib")
36
+ except Exception:
37
+ matplotlib_mod = None
38
+
39
+ try:
40
+ dt_pkg = importlib.import_module("deeptools")
41
+ except Exception:
42
+ dt_pkg = None
43
+
44
+ try:
45
+ from .adapter import Adapter
46
+ except Exception:
47
+ from adapter import Adapter
48
+
49
+
50
+ class _FallbackMCP:
51
+ def __init__(self, name: str, description: str) -> None:
52
+ self.name = name
53
+ self.description = description
54
+ self.tools: list[Any] = []
55
+
56
+ def tool(self, name: str, description: str):
57
+ def decorator(func):
58
+ setattr(func, "name", name)
59
+ setattr(func, "description", description)
60
+ self.tools.append(func)
61
+ return func
62
+
63
+ return decorator
64
+
65
+ def run(self, transport: str = "stdio", host: str = "127.0.0.1", port: int = 8000) -> None:
66
+ raise RuntimeError("fastmcp is not installed. Install dependencies before running the MCP service.")
67
+
68
+
69
+ mcp = (
70
+ FastMCP(name="deepTools-mcp", description="MCP wrapper for deepTools utilities and module introspection")
71
+ if FastMCP is not None
72
+ else _FallbackMCP(name="deepTools-mcp", description="MCP wrapper for deepTools utilities and module introspection")
73
  )
74
 
75
+ adapter = Adapter(package_name="deeptools")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
 
 
 
 
77
 
78
+ def _success(result: Any) -> dict[str, Any]:
79
+ return {"success": True, "result": result, "error": None}
80
 
 
 
 
 
81
 
82
+ def _failure(error: str) -> dict[str, Any]:
83
+ return {"success": False, "result": None, "error": error}
84
 
 
 
 
 
85
 
86
+ def _parse_json_list(value: str) -> list[Any]:
87
+ parsed = json.loads(value)
88
+ if not isinstance(parsed, list):
89
+ raise ValueError("Expected a JSON list.")
90
+ return parsed
91
 
 
 
 
 
92
 
93
+ def _parse_json_dict(value: str) -> dict[str, Any]:
94
+ parsed = json.loads(value)
95
+ if not isinstance(parsed, dict):
96
+ raise ValueError("Expected a JSON object.")
97
+ return parsed
98
 
 
 
 
 
99
 
100
+ @mcp.tool(name="health_check", description="Check MCP and deepTools dependency health")
101
+ def health_check() -> dict[str, Any]:
102
+ """Return availability of runtime dependencies and adapter module loading status.
103
 
104
  Returns:
105
+ Standardized health payload with dependency booleans and adapter status.
106
  """
107
+ dependencies = {
108
+ "fastmcp": FastMCP is not None,
109
+ "numpy": np is not None,
110
+ "matplotlib": matplotlib_mod is not None,
111
+ "deeptools": dt_pkg is not None,
112
+ "deeptools.utilities": dt_utilities is not None,
113
+ "deeptools.getRatio": dt_get_ratio is not None,
114
+ }
115
+ return _success({"dependencies": dependencies, "adapter": adapter.health()})
116
 
117
 
118
+ @mcp.tool(name="list_modules", description="List importable deeptools modules")
119
+ def list_modules(include_failed: bool = True) -> dict[str, Any]:
120
+ """List modules discovered by the adapter.
 
121
 
122
+ Args:
123
+ include_failed: Whether to include module import failures.
124
 
125
  Returns:
126
+ Loaded module names plus optional failure details.
127
  """
128
+ return _success(adapter.list_modules(include_failed=include_failed))
129
 
130
 
131
+ @mcp.tool(name="list_symbols", description="List symbols from an imported deeptools module")
132
+ def list_symbols(module_name: str, public_only: bool = True, limit: int = 100) -> dict[str, Any]:
133
+ """List symbols exposed by one loaded module.
 
134
 
135
+ Args:
136
+ module_name: Fully qualified module name, for example ``deeptools.utilities``.
137
+ public_only: If true, hide symbols prefixed with underscore.
138
+ limit: Maximum symbol count returned.
139
 
140
  Returns:
141
+ Symbol list and metadata.
142
  """
143
+ data = adapter.list_symbols(module_name=module_name, public_only=public_only, limit=limit)
144
+ if data.get("status") == "error":
145
+ return _failure(str(data.get("message", "Unable to list symbols.")))
146
+ return _success(data)
147
 
148
 
149
+ @mcp.tool(name="call_function", description="Call a function from a loaded deeptools module")
150
+ def call_function(module_name: str, function_name: str, args_json: str = "[]", kwargs_json: str = "{}") -> dict[str, Any]:
151
+ """Call a module-level function from an imported deeptools module.
 
152
 
153
+ Args:
154
+ module_name: Fully qualified module name.
155
+ function_name: Function symbol to execute.
156
+ args_json: JSON-encoded list of positional arguments.
157
+ kwargs_json: JSON-encoded object of keyword arguments.
158
 
159
  Returns:
160
+ Function call result, or an error payload.
 
 
 
 
 
 
161
  """
162
+ try:
163
+ args = _parse_json_list(args_json)
164
+ kwargs = _parse_json_dict(kwargs_json)
165
+ except Exception as exc:
166
+ return _failure(f"Invalid JSON arguments: {type(exc).__name__}: {exc}")
167
+
168
+ data = adapter.call_function(
169
+ module_name=module_name,
170
+ function_name=function_name,
171
+ args=args,
172
+ kwargs=kwargs,
173
+ )
174
+ if data.get("status") == "error":
175
+ return _failure(str(data.get("message", "Function call failed.")))
176
+ return _success(data)
177
+
178
+
179
+ @mcp.tool(name="create_instance", description="Instantiate a class from a loaded deeptools module")
180
+ def create_instance(module_name: str, class_name: str, init_args_json: str = "[]", init_kwargs_json: str = "{}") -> dict[str, Any]:
181
+ """Create a class instance from an imported deeptools module.
182
+
183
+ Args:
184
+ module_name: Fully qualified module name.
185
+ class_name: Class symbol to instantiate.
186
+ init_args_json: JSON-encoded list of positional constructor arguments.
187
+ init_kwargs_json: JSON-encoded object of keyword constructor arguments.
188
 
189
  Returns:
190
+ Instance metadata or a standardized error.
 
 
 
 
 
 
191
  """
192
+ try:
193
+ init_args = _parse_json_list(init_args_json)
194
+ init_kwargs = _parse_json_dict(init_kwargs_json)
195
+ except Exception as exc:
196
+ return _failure(f"Invalid JSON constructor args: {type(exc).__name__}: {exc}")
197
+
198
+ data = adapter.create_instance(
199
+ module_name=module_name,
200
+ class_name=class_name,
201
+ init_args=init_args,
202
+ init_kwargs=init_kwargs,
203
+ )
204
+ if data.get("status") == "error":
205
+ return _failure(str(data.get("message", "Class creation failed.")))
206
+ return _success(data)
207
+
208
+
209
+ @mcp.tool(name="compute_ratio", description="Compute deeptools ratio-style bin value")
210
+ def compute_ratio(
211
+ value1: float,
212
+ value2: float,
213
+ value_type: str = "ratio",
214
+ scale_factor_1: float = 1.0,
215
+ scale_factor_2: float = 1.0,
216
+ pseudocount_1: float = 1.0,
217
+ pseudocount_2: float = 1.0,
218
+ ) -> dict[str, Any]:
219
+ """Compute a ratio-like bin value using ``deeptools.getRatio.getRatio`` semantics.
220
+
221
+ Args:
222
+ value1: Coverage value for sample 1.
223
+ value2: Coverage value for sample 2.
224
+ value_type: One of ``ratio``, ``log2``, ``reciprocal_ratio``, ``subtract``, ``add``, ``first``, ``second``, ``mean``.
225
+ scale_factor_1: Scaling factor for value1.
226
+ scale_factor_2: Scaling factor for value2.
227
+ pseudocount_1: Pseudocount added to sample 1 for ratio-like modes.
228
+ pseudocount_2: Pseudocount added to sample 2 for ratio-like modes.
229
 
230
  Returns:
231
+ Computed numeric value.
 
 
 
 
 
 
232
  """
233
+ if dt_get_ratio is None:
234
+ return _failure("deeptools.getRatio is unavailable.")
235
 
236
+ func = getattr(dt_get_ratio, "getRatio", None)
237
+ if func is None:
238
+ return _failure("getRatio function is not available in deeptools.getRatio.")
239
 
240
+ try:
241
+ args = {
242
+ "valueType": value_type,
243
+ "scaleFactors": (float(scale_factor_1), float(scale_factor_2)),
244
+ "pseudocount": [float(pseudocount_1), float(pseudocount_2)],
245
+ }
246
+ output = func([float(value1), float(value2)], args)
247
+ return _success({"value": output})
248
+ except Exception as exc:
249
+ return _failure(f"Failed to compute ratio: {type(exc).__name__}: {exc}")
250
 
251
 
252
+ @mcp.tool(name="smart_label", description="Create a normalized label from file path")
253
+ def smart_label(label: str) -> dict[str, Any]:
254
+ """Normalize one label by removing path and first file extension.
 
255
 
256
+ Args:
257
+ label: Raw file path or label string.
258
 
259
  Returns:
260
+ Normalized label.
 
 
 
 
 
 
261
  """
262
+ if dt_utilities is None:
263
+ return _failure("deeptools.utilities is unavailable.")
264
+ func = getattr(dt_utilities, "smartLabel", None)
265
+ if func is None:
266
+ return _failure("smartLabel function is not available in deeptools.utilities.")
267
 
268
+ try:
269
+ return _success({"label": func(label)})
270
+ except Exception as exc:
271
+ return _failure(f"Failed to normalize label: {type(exc).__name__}: {exc}")
272
 
273
 
274
+ @mcp.tool(name="convert_colormap", description="Convert matplotlib colormap to deeptools RGB scale")
275
+ def convert_colormap(cmap_name: str = "viridis", vmin: float = 0.0, vmax: float = 1.0) -> dict[str, Any]:
276
+ """Convert a matplotlib colormap into deepTools-compatible RGB scale values.
 
277
 
278
+ Args:
279
+ cmap_name: Matplotlib colormap name.
280
+ vmin: Minimum normalization value.
281
+ vmax: Maximum normalization value.
282
 
283
  Returns:
284
+ List of normalized rgb stop definitions.
285
  """
286
+ if dt_utilities is None:
287
+ return _failure("deeptools.utilities is unavailable.")
288
+ func = getattr(dt_utilities, "convertCmap", None)
289
+ if func is None:
290
+ return _failure("convertCmap function is not available in deeptools.utilities.")
291
 
292
+ try:
293
+ result = func(cmap_name, vmin=float(vmin), vmax=float(vmax))
294
+ return _success({"colormap": cmap_name, "scale": result})
295
+ except Exception as exc:
296
+ return _failure(f"Failed to convert colormap: {type(exc).__name__}: {exc}")
 
 
 
 
 
 
 
297
 
298
 
299
+ def create_app() -> Any:
300
  return mcp
301
 
302
 
deepTools/mcp_output/requirements.txt CHANGED
@@ -1,13 +1,5 @@
1
  fastmcp
2
- fastapi
3
- uvicorn[standard]
4
- pydantic>=2.0.0
5
- numpy >= 2.0.0
6
- scipy >= 0.17.0
7
- matplotlib >= 3.5.0
8
- pysam >= 0.14.0
9
- numpydoc >= 0.5
10
- pyBigWig >= 0.2.1
11
- py2bit >= 0.2.0
12
- plotly >= 4.9
13
- deeptoolsintervals >= 0.1.8
 
1
  fastmcp
2
+ deeptools
3
+ deeptoolsintervals
4
+ numpy
5
+ matplotlib
 
 
 
 
 
 
 
 
deepTools/mcp_output/start_mcp.py CHANGED
@@ -1,30 +1,38 @@
 
1
 
2
- """
3
- MCP Service Startup Entry
4
- """
5
- import sys
6
  import os
 
 
7
 
8
- project_root = os.path.dirname(os.path.abspath(__file__))
9
- mcp_plugin_dir = os.path.join(project_root, "mcp_plugin")
10
- if mcp_plugin_dir not in sys.path:
11
- sys.path.insert(0, mcp_plugin_dir)
12
 
13
  from mcp_service import create_app
14
 
15
- def main():
16
- """Start FastMCP service"""
 
 
17
  app = create_app()
18
- # Use environment variable to configure port, default 8000
19
- port = int(os.environ.get("MCP_PORT", "8000"))
20
-
21
- # Choose transport mode based on environment variable
22
- transport = os.environ.get("MCP_TRANSPORT", "stdio")
 
 
 
23
  if transport == "http":
24
- app.run(transport="http", host="0.0.0.0", port=port)
25
- else:
26
- # Default to STDIO mode
27
- app.run()
 
 
 
 
28
 
29
  if __name__ == "__main__":
30
- main()
 
1
+ from __future__ import annotations
2
 
 
 
 
 
3
  import os
4
+ import sys
5
+ from pathlib import Path
6
 
7
+ CURRENT_DIR = Path(__file__).resolve().parent
8
+ PLUGIN_DIR = CURRENT_DIR / "mcp_plugin"
9
+ if str(PLUGIN_DIR) not in sys.path:
10
+ sys.path.insert(0, str(PLUGIN_DIR))
11
 
12
  from mcp_service import create_app
13
 
14
+
15
+ def main() -> None:
16
+ transport = os.getenv("MCP_TRANSPORT", "stdio").strip().lower()
17
+ port = int(os.getenv("MCP_PORT", "8000"))
18
  app = create_app()
19
+
20
+ if transport == "stdio":
21
+ try:
22
+ app.run(transport="stdio")
23
+ except TypeError:
24
+ app.run()
25
+ return
26
+
27
  if transport == "http":
28
+ try:
29
+ app.run(transport="http", host="0.0.0.0", port=port, path="/mcp")
30
+ except TypeError:
31
+ app.run(transport="http", host="0.0.0.0", port=port)
32
+ return
33
+
34
+ raise ValueError(f"Unsupported MCP_TRANSPORT='{transport}'. Use 'stdio' or 'http'.")
35
+
36
 
37
  if __name__ == "__main__":
38
+ main()
port.json CHANGED
@@ -1,5 +1 @@
1
- {
2
- "repo": "deepTools",
3
- "port": 7963,
4
- "timestamp": 1773293952
5
- }
 
1
+ {"port": 7860}
 
 
 
 
requirements.txt CHANGED
@@ -1,13 +1,7 @@
1
  fastmcp
 
 
 
 
2
  fastapi
3
- uvicorn[standard]
4
- pydantic>=2.0.0
5
- numpy >= 2.0.0
6
- scipy >= 0.17.0
7
- matplotlib >= 3.5.0
8
- pysam >= 0.14.0
9
- numpydoc >= 0.5
10
- pyBigWig >= 0.2.1
11
- py2bit >= 0.2.0
12
- plotly >= 4.9
13
- deeptoolsintervals >= 0.1.8
 
1
  fastmcp
2
+ deeptools
3
+ deeptoolsintervals
4
+ numpy
5
+ matplotlib
6
  fastapi
7
+ uvicorn
 
 
 
 
 
 
 
 
 
 
run_docker.ps1 CHANGED
@@ -1,26 +1,8 @@
1
- cd $PSScriptRoot
2
  $ErrorActionPreference = "Stop"
3
- $entryName = if ($env:MCP_ENTRY_NAME) { $env:MCP_ENTRY_NAME } else { "deepTools" }
4
- $entryUrl = if ($env:MCP_ENTRY_URL) { $env:MCP_ENTRY_URL } else { "http://localhost:7963/mcp" }
5
- $imageName = if ($env:MCP_IMAGE_NAME) { $env:MCP_IMAGE_NAME } else { "deepTools-mcp" }
6
- $mcpDir = Join-Path $env:USERPROFILE ".cursor"
7
- $mcpPath = Join-Path $mcpDir "mcp.json"
8
- if (!(Test-Path $mcpDir)) { New-Item -ItemType Directory -Path $mcpDir | Out-Null }
9
- $config = @{}
10
- if (Test-Path $mcpPath) {
11
- try { $config = Get-Content $mcpPath -Raw | ConvertFrom-Json } catch { $config = @{} }
12
- }
13
- $serversOrdered = [ordered]@{}
14
- if ($config -and ($config.PSObject.Properties.Name -contains "mcpServers") -and $config.mcpServers) {
15
- $existing = $config.mcpServers
16
- if ($existing -is [pscustomobject]) {
17
- foreach ($p in $existing.PSObject.Properties) { if ($p.Name -ne $entryName) { $serversOrdered[$p.Name] = $p.Value } }
18
- } elseif ($existing -is [System.Collections.IDictionary]) {
19
- foreach ($k in $existing.Keys) { if ($k -ne $entryName) { $serversOrdered[$k] = $existing[$k] } }
20
- }
21
- }
22
- $serversOrdered[$entryName] = @{ url = $entryUrl }
23
- $config = @{ mcpServers = $serversOrdered }
24
- $config | ConvertTo-Json -Depth 10 | Set-Content -Path $mcpPath -Encoding UTF8
25
  docker build -t $imageName .
26
- docker run --rm -p 7963:7860 $imageName
 
 
1
  $ErrorActionPreference = "Stop"
2
+
3
+ $portConfig = Get-Content -Raw -Path "port.json" | ConvertFrom-Json
4
+ $port = [int]$portConfig.port
5
+ $imageName = "deepTools-mcp"
6
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  docker build -t $imageName .
8
+ docker run --rm -it -p "${port}:${port}" $imageName
run_docker.sh CHANGED
@@ -1,75 +1,8 @@
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
- cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
4
- mcp_entry_name="${MCP_ENTRY_NAME:-deepTools}"
5
- mcp_entry_url="${MCP_ENTRY_URL:-http://localhost:7963/mcp}"
6
- mcp_dir="${HOME}/.cursor"
7
- mcp_path="${mcp_dir}/mcp.json"
8
- mkdir -p "${mcp_dir}"
9
- if command -v python3 >/dev/null 2>&1; then
10
- python3 - "${mcp_path}" "${mcp_entry_name}" "${mcp_entry_url}" <<'PY'
11
- import json, os, sys
12
- path, name, url = sys.argv[1:4]
13
- cfg = {"mcpServers": {}}
14
- if os.path.exists(path):
15
- try:
16
- with open(path, "r", encoding="utf-8") as f:
17
- cfg = json.load(f)
18
- except Exception:
19
- cfg = {"mcpServers": {}}
20
- if not isinstance(cfg, dict):
21
- cfg = {"mcpServers": {}}
22
- servers = cfg.get("mcpServers")
23
- if not isinstance(servers, dict):
24
- servers = {}
25
- ordered = {}
26
- for k, v in servers.items():
27
- if k != name:
28
- ordered[k] = v
29
- ordered[name] = {"url": url}
30
- cfg = {"mcpServers": ordered}
31
- with open(path, "w", encoding="utf-8") as f:
32
- json.dump(cfg, f, indent=2, ensure_ascii=False)
33
- PY
34
- elif command -v python >/dev/null 2>&1; then
35
- python - "${mcp_path}" "${mcp_entry_name}" "${mcp_entry_url}" <<'PY'
36
- import json, os, sys
37
- path, name, url = sys.argv[1:4]
38
- cfg = {"mcpServers": {}}
39
- if os.path.exists(path):
40
- try:
41
- with open(path, "r", encoding="utf-8") as f:
42
- cfg = json.load(f)
43
- except Exception:
44
- cfg = {"mcpServers": {}}
45
- if not isinstance(cfg, dict):
46
- cfg = {"mcpServers": {}}
47
- servers = cfg.get("mcpServers")
48
- if not isinstance(servers, dict):
49
- servers = {}
50
- ordered = {}
51
- for k, v in servers.items():
52
- if k != name:
53
- ordered[k] = v
54
- ordered[name] = {"url": url}
55
- cfg = {"mcpServers": ordered}
56
- with open(path, "w", encoding="utf-8") as f:
57
- json.dump(cfg, f, indent=2, ensure_ascii=False)
58
- PY
59
- elif command -v jq >/dev/null 2>&1; then
60
- name="${mcp_entry_name}"; url="${mcp_entry_url}"
61
- if [ -f "${mcp_path}" ]; then
62
- tmp="$(mktemp)"
63
- jq --arg name "$name" --arg url "$url" '
64
- .mcpServers = (.mcpServers // {})
65
- | .mcpServers as $s
66
- | ($s | with_entries(select(.key != $name))) as $base
67
- | .mcpServers = ($base + {($name): {"url": $url}})
68
- ' "${mcp_path}" > "${tmp}" && mv "${tmp}" "${mcp_path}"
69
- else
70
- printf '{ "mcpServers": { "%s": { "url": "%s" } } }
71
- ' "$name" "$url" > "${mcp_path}"
72
- fi
73
- fi
74
- docker build -t deepTools-mcp .
75
- docker run --rm -p 7963:7860 deepTools-mcp
 
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
+
4
+ PORT=$(python3 -c "import json; print(json.load(open('port.json'))['port'])")
5
+ IMAGE_NAME="deepTools-mcp"
6
+
7
+ docker build -t "${IMAGE_NAME}" .
8
+ docker run --rm -it -p "${PORT}:${PORT}" "${IMAGE_NAME}"